From f5c4671bfbad96bf346bd7e9a21fc4317b4959df Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Sat, 3 Dec 2022 17:00:20 +0530 Subject: Adds most of the tools --- v_windows/v/vlib/.vdocignore | 16 + v_windows/v/vlib/arrays/arrays.v | 126 + v_windows/v/vlib/arrays/arrays_test.v | 87 + v_windows/v/vlib/benchmark/README.md | 45 + v_windows/v/vlib/benchmark/benchmark.v | 218 + v_windows/v/vlib/bitfield/README.md | 11 + v_windows/v/vlib/bitfield/bitfield.v | 553 + v_windows/v/vlib/bitfield/bitfield_test.v | 333 + v_windows/v/vlib/builtin/array.c.v | 1 + v_windows/v/vlib/builtin/array.v | 644 ++ v_windows/v/vlib/builtin/array_d_gcboehm_opt.v | 267 + v_windows/v/vlib/builtin/array_notd_gcboehm_opt.v | 10 + v_windows/v/vlib/builtin/array_test.v | 1507 +++ v_windows/v/vlib/builtin/builtin.c.v | 547 + v_windows/v/vlib/builtin/builtin.v | 128 + v_windows/v/vlib/builtin/builtin_d_gcboehm.c.v | 91 + v_windows/v/vlib/builtin/builtin_ios.c.v | 6 + v_windows/v/vlib/builtin/builtin_nix.c.v | 144 + v_windows/v/vlib/builtin/builtin_notd_gcboehm.c.v | 20 + v_windows/v/vlib/builtin/builtin_windows.c.v | 304 + v_windows/v/vlib/builtin/byte_test.v | 22 + v_windows/v/vlib/builtin/cfns.c.v | 462 + v_windows/v/vlib/builtin/cfns_wrapper.c.v | 72 + v_windows/v/vlib/builtin/chan.v | 32 + v_windows/v/vlib/builtin/float.c.v | 205 + v_windows/v/vlib/builtin/float_test.v | 147 + v_windows/v/vlib/builtin/float_x64.v | 6 + v_windows/v/vlib/builtin/int.v | 481 + v_windows/v/vlib/builtin/int_test.v | 241 + v_windows/v/vlib/builtin/isnil_test.v | 19 + v_windows/v/vlib/builtin/js/array.js.v | 253 + v_windows/v/vlib/builtin/js/builtin.js.v | 61 + v_windows/v/vlib/builtin/js/builtin.v | 84 + v_windows/v/vlib/builtin/js/byte.js.v | 16 + v_windows/v/vlib/builtin/js/int.js.v | 8 + v_windows/v/vlib/builtin/js/jsfns.js.v | 125 + v_windows/v/vlib/builtin/js/jsfns_browser.js.v | 59 + v_windows/v/vlib/builtin/js/jsfns_node.js.v | 31 + v_windows/v/vlib/builtin/js/map.js.v | 26 + v_windows/v/vlib/builtin/js/string.js.v | 720 ++ v_windows/v/vlib/builtin/linux_bare/libc_impl.v | 162 + .../v/vlib/builtin/linux_bare/linux_syscalls.v | 433 + .../v/vlib/builtin/linux_bare/memory_managment.v | 29 + .../vlib/builtin/linux_bare/old/.checks/.gitignore | 5 + .../v/vlib/builtin/linux_bare/old/.checks/checks.v | 32 + .../builtin/linux_bare/old/.checks/consts/consts.v | 22 + .../linux_bare/old/.checks/forkedtest/forkedtest.v | 49 + .../linux_bare/old/.checks/linuxsys/linuxsys.v | 300 + .../vlib/builtin/linux_bare/old/.checks/readme.md | 5 + .../linux_bare/old/.checks/sample_text1.txt | 1 + .../builtin/linux_bare/old/.checks/string/string.v | 63 + .../linux_bare/old/.checks/structs/structs.v | 42 + .../v/vlib/builtin/linux_bare/old/array_bare.v | 53 + .../v/vlib/builtin/linux_bare/old/builtin_bare.v | 60 + .../v/vlib/builtin/linux_bare/old/linuxsys_bare.v | 759 ++ v_windows/v/vlib/builtin/linux_bare/old/mm_bare.v | 58 + .../v/vlib/builtin/linux_bare/old/string_bare.v | 150 + .../builtin/linux_bare/old/syscallwrapper_test.v | 27 + v_windows/v/vlib/builtin/map.c.v | 79 + v_windows/v/vlib/builtin/map.v | 716 ++ v_windows/v/vlib/builtin/map_d_gcboehm_opt.v | 146 + v_windows/v/vlib/builtin/map_of_floats_test.v | 27 + v_windows/v/vlib/builtin/map_test.v | 947 ++ v_windows/v/vlib/builtin/option.c.v | 15 + v_windows/v/vlib/builtin/option.v | 89 + v_windows/v/vlib/builtin/prealloc.c.v | 114 + v_windows/v/vlib/builtin/rune.v | 65 + v_windows/v/vlib/builtin/sorted_map.v | 457 + v_windows/v/vlib/builtin/sorting_test.v | 84 + v_windows/v/vlib/builtin/string.v | 1604 +++ .../vlib/builtin/string_charptr_byteptr_helpers.v | 104 + v_windows/v/vlib/builtin/string_int_test.v | 221 + v_windows/v/vlib/builtin/string_interpolation.v | 713 ++ .../v/vlib/builtin/string_strip_margin_test.v | 95 + v_windows/v/vlib/builtin/string_test.v | 912 ++ v_windows/v/vlib/builtin/utf8.c.v | 79 + v_windows/v/vlib/builtin/utf8.v | 191 + v_windows/v/vlib/builtin/utf8_test.v | 28 + v_windows/v/vlib/cli/README.md | 30 + v_windows/v/vlib/cli/command.v | 307 + v_windows/v/vlib/cli/command_test.v | 222 + v_windows/v/vlib/cli/flag.v | 296 + v_windows/v/vlib/cli/flag_test.v | 216 + v_windows/v/vlib/cli/help.v | 176 + v_windows/v/vlib/cli/help_test.v | 65 + v_windows/v/vlib/cli/version.v | 25 + v_windows/v/vlib/clipboard/clipboard.v | 37 + v_windows/v/vlib/clipboard/clipboard_android.c.v | 15 + v_windows/v/vlib/clipboard/clipboard_darwin.c.v | 70 + v_windows/v/vlib/clipboard/clipboard_darwin.m | 23 + v_windows/v/vlib/clipboard/clipboard_default.c.v | 15 + v_windows/v/vlib/clipboard/clipboard_solaris.c.v | 15 + v_windows/v/vlib/clipboard/clipboard_test.v | 29 + v_windows/v/vlib/clipboard/clipboard_windows.c.v | 186 + v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v | 49 + v_windows/v/vlib/clipboard/x11/clipboard.c.v | 501 + v_windows/v/vlib/context/README.md | 166 + v_windows/v/vlib/context/_context.v | 81 + v_windows/v/vlib/context/cancel.v | 181 + v_windows/v/vlib/context/cancel_test.v | 42 + v_windows/v/vlib/context/deadline.v | 94 + v_windows/v/vlib/context/deadline_test.v | 48 + v_windows/v/vlib/context/empty.v | 42 + v_windows/v/vlib/context/empty_test.v | 17 + v_windows/v/vlib/context/err.v | 12 + v_windows/v/vlib/context/value.v | 57 + v_windows/v/vlib/context/value_test.v | 23 + v_windows/v/vlib/crypto/aes/aes.v | 77 + v_windows/v/vlib/crypto/aes/aes_cbc.v | 126 + v_windows/v/vlib/crypto/aes/aes_test.v | 27 + v_windows/v/vlib/crypto/aes/block_generic.v | 183 + v_windows/v/vlib/crypto/aes/const.v | 374 + v_windows/v/vlib/crypto/aes/cypher_generic.v | 16 + v_windows/v/vlib/crypto/cipher/xor_generic.v | 33 + v_windows/v/vlib/crypto/crypto.v | 23 + v_windows/v/vlib/crypto/hmac/hmac.v | 44 + v_windows/v/vlib/crypto/hmac/hmac_test.v | 226 + v_windows/v/vlib/crypto/internal/subtle/aliasing.v | 29 + .../v/vlib/crypto/internal/subtle/comparison.v | 53 + .../vlib/crypto/internal/subtle/comparison_test.v | 65 + v_windows/v/vlib/crypto/md5/md5.v | 154 + v_windows/v/vlib/crypto/md5/md5_test.v | 8 + v_windows/v/vlib/crypto/md5/md5block_generic.v | 132 + .../v/vlib/crypto/rand/crypto_rand_read_test.v | 15 + v_windows/v/vlib/crypto/rand/rand.v | 10 + v_windows/v/vlib/crypto/rand/rand_darwin.c.v | 21 + v_windows/v/vlib/crypto/rand/rand_default.c.v | 9 + v_windows/v/vlib/crypto/rand/rand_linux.c.v | 39 + v_windows/v/vlib/crypto/rand/rand_solaris.c.v | 42 + v_windows/v/vlib/crypto/rand/rand_windows.c.v | 25 + v_windows/v/vlib/crypto/rand/utils.v | 55 + v_windows/v/vlib/crypto/rc4/rc4.v | 79 + v_windows/v/vlib/crypto/rc4/rc4_test.v | 22 + v_windows/v/vlib/crypto/readme.txt | 29 + v_windows/v/vlib/crypto/sha1/sha1.v | 155 + v_windows/v/vlib/crypto/sha1/sha1_test.v | 8 + v_windows/v/vlib/crypto/sha1/sha1block_generic.v | 118 + v_windows/v/vlib/crypto/sha256/sha256.v | 226 + v_windows/v/vlib/crypto/sha256/sha256_test.v | 16 + .../v/vlib/crypto/sha256/sha256block_generic.v | 154 + v_windows/v/vlib/crypto/sha512/sha512.v | 324 + v_windows/v/vlib/crypto/sha512/sha512_test.v | 8 + .../v/vlib/crypto/sha512/sha512block_generic.v | 108 + v_windows/v/vlib/darwin/darwin.m | 9 + v_windows/v/vlib/darwin/darwin.v | 57 + v_windows/v/vlib/dl/dl.v | 48 + v_windows/v/vlib/dl/dl_nix.c.v | 43 + v_windows/v/vlib/dl/dl_test.v | 46 + v_windows/v/vlib/dl/dl_windows.c.v | 39 + v_windows/v/vlib/encoding/base58/alphabet.v | 65 + v_windows/v/vlib/encoding/base58/base58.v | 181 + v_windows/v/vlib/encoding/base58/base58_test.v | 89 + v_windows/v/vlib/encoding/base64/base64.v | 308 + .../v/vlib/encoding/base64/base64_memory_test.v | 59 + v_windows/v/vlib/encoding/base64/base64_test.v | 150 + v_windows/v/vlib/encoding/binary/binary.v | 100 + v_windows/v/vlib/encoding/csv/README.md | 19 + v_windows/v/vlib/encoding/csv/reader.v | 196 + v_windows/v/vlib/encoding/csv/reader_test.v | 253 + v_windows/v/vlib/encoding/csv/writer.v | 80 + v_windows/v/vlib/encoding/csv/writer_test.v | 11 + v_windows/v/vlib/encoding/hex/hex.v | 62 + v_windows/v/vlib/encoding/hex/hex_test.v | 54 + .../encoding/utf8/east_asian/east_asian_width.v | 1204 +++ .../utf8/east_asian/east_asian_width_test.v | 23 + .../v/vlib/encoding/utf8/encoding_utf8_test.v | 9 + v_windows/v/vlib/encoding/utf8/utf8.v | 88 + v_windows/v/vlib/encoding/utf8/utf8_util.v | 1161 ++ v_windows/v/vlib/encoding/utf8/utf8_util_test.v | 66 + v_windows/v/vlib/eventbus/README.md | 122 + v_windows/v/vlib/eventbus/eventbus.v | 121 + v_windows/v/vlib/eventbus/eventbus_test.v | 109 + v_windows/v/vlib/flag/README.md | 36 + v_windows/v/vlib/flag/default_flag_options_test.v | 35 + v_windows/v/vlib/flag/flag.v | 624 ++ v_windows/v/vlib/flag/flag_test.v | 412 + .../simplest_flag_program.dashdash.help.out | 1 + .../simplest_flag_program.dashdash.version.out | 1 + .../flag/testdata/simplest_flag_program.help.out | 7 + .../v/vlib/flag/testdata/simplest_flag_program.out | 1 + .../v/vlib/flag/testdata/simplest_flag_program.v | 14 + .../testdata/simplest_flag_program.version.out | 1 + .../v/vlib/flag/testdata/usage_example.help.out | 13 + v_windows/v/vlib/flag/testdata/usage_example.out | 1 + v_windows/v/vlib/flag/testdata/usage_example.v | 17 + .../v/vlib/flag/testdata/usage_example.version.out | 1 + v_windows/v/vlib/flag/usage_example_test.v | 35 + v_windows/v/vlib/fontstash/a_d_use_freetype.v | 19 + v_windows/v/vlib/fontstash/fontstash.c.v | 172 + v_windows/v/vlib/fontstash/fontstash_funcs.c.v | 53 + v_windows/v/vlib/fontstash/fontstash_structs.c.v | 51 + v_windows/v/vlib/fontstash/fontstash_structs.v | 29 + v_windows/v/vlib/gg/enums.v | 161 + v_windows/v/vlib/gg/gg.c.v | 278 + v_windows/v/vlib/gg/gg.v | 750 ++ v_windows/v/vlib/gg/gg_android.c.v | 37 + v_windows/v/vlib/gg/gg_darwin.c.v | 23 + v_windows/v/vlib/gg/gg_darwin.m | 125 + v_windows/v/vlib/gg/image.c.v | 169 + v_windows/v/vlib/gg/image.v | 223 + v_windows/v/vlib/gg/m4/graphic.v | 110 + v_windows/v/vlib/gg/m4/m4_test.v | 235 + v_windows/v/vlib/gg/m4/matrix.v | 595 ++ v_windows/v/vlib/gg/m4/vector.v | 230 + v_windows/v/vlib/gg/text_rendering.c.v | 226 + v_windows/v/vlib/gg/text_rendering.v | 150 + v_windows/v/vlib/glm/glm.v | 428 + v_windows/v/vlib/glm/glm_test.v | 155 + v_windows/v/vlib/gx/color.v | 234 + v_windows/v/vlib/gx/color_test.v | 63 + v_windows/v/vlib/gx/image.v | 14 + v_windows/v/vlib/gx/text.c.v | 18 + v_windows/v/vlib/gx/text.v | 20 + v_windows/v/vlib/hash/crc32/crc32.v | 63 + v_windows/v/vlib/hash/crc32/crc32_test.v | 14 + v_windows/v/vlib/hash/fnv1a/fnv1a.v | 44 + v_windows/v/vlib/hash/fnv1a/fnv1a_test.v | 12 + v_windows/v/vlib/hash/hash.v | 20 + v_windows/v/vlib/hash/wyhash.c.v | 33 + v_windows/v/vlib/hash/wyhash.js.v | 1 + v_windows/v/vlib/hash/wyhash.v | 72 + v_windows/v/vlib/io/buffered_reader.v | 145 + v_windows/v/vlib/io/custom_string_reading_test.v | 57 + v_windows/v/vlib/io/io.v | 16 + v_windows/v/vlib/io/io_cp_test.v | 13 + v_windows/v/vlib/io/io_test.v | 41 + v_windows/v/vlib/io/multi_writer.v | 33 + v_windows/v/vlib/io/multi_writer_test.v | 66 + v_windows/v/vlib/io/os_file_reader_test.v | 29 + v_windows/v/vlib/io/reader.v | 66 + v_windows/v/vlib/io/reader_test.v | 130 + v_windows/v/vlib/io/readerwriter.v | 34 + v_windows/v/vlib/io/util/util.v | 104 + v_windows/v/vlib/io/util/util_test.v | 127 + v_windows/v/vlib/io/writer.v | 12 + v_windows/v/vlib/json/json_decode_test.v | 46 + v_windows/v/vlib/json/json_primitives.v | 204 + v_windows/v/vlib/json/json_test.v | 368 + v_windows/v/vlib/log/log.v | 185 + v_windows/v/vlib/math/ROADMAP.md | 4 + v_windows/v/vlib/math/abs.c.v | 8 + v_windows/v/vlib/math/abs.js.v | 9 + v_windows/v/vlib/math/abs.v | 18 + v_windows/v/vlib/math/big/big.c.v | 344 + v_windows/v/vlib/math/big/big.js.v | 198 + v_windows/v/vlib/math/big/big_test.v | 181 + v_windows/v/vlib/math/bits.js.v | 18 + v_windows/v/vlib/math/bits.v | 63 + v_windows/v/vlib/math/bits/bits.v | 478 + v_windows/v/vlib/math/bits/bits_tables.v | 79 + v_windows/v/vlib/math/bits/bits_test.v | 288 + v_windows/v/vlib/math/cbrt.c.v | 9 + v_windows/v/vlib/math/cbrt.js.v | 9 + v_windows/v/vlib/math/cbrt.v | 52 + v_windows/v/vlib/math/complex/complex.v | 374 + v_windows/v/vlib/math/complex/complex_test.v | 797 ++ v_windows/v/vlib/math/const.v | 51 + v_windows/v/vlib/math/div.c.v | 9 + v_windows/v/vlib/math/div.v | 87 + v_windows/v/vlib/math/erf.c.v | 17 + v_windows/v/vlib/math/erf.v | 327 + v_windows/v/vlib/math/erf_test.v | 29 + v_windows/v/vlib/math/exp.c.v | 17 + v_windows/v/vlib/math/exp.js.v | 12 + v_windows/v/vlib/math/exp.v | 214 + v_windows/v/vlib/math/factorial.v | 55 + v_windows/v/vlib/math/factorial_tables.v | 711 ++ v_windows/v/vlib/math/factorial_test.v | 13 + v_windows/v/vlib/math/floor.c.v | 34 + v_windows/v/vlib/math/floor.js.v | 34 + v_windows/v/vlib/math/floor.v | 105 + v_windows/v/vlib/math/fractions/approximations.v | 119 + .../v/vlib/math/fractions/approximations_test.v | 189 + v_windows/v/vlib/math/fractions/fraction.v | 259 + v_windows/v/vlib/math/fractions/fraction_test.v | 269 + v_windows/v/vlib/math/gamma.c.v | 17 + v_windows/v/vlib/math/gamma.v | 335 + v_windows/v/vlib/math/gamma_tables.v | 163 + v_windows/v/vlib/math/hypot.c.v | 9 + v_windows/v/vlib/math/hypot.v | 24 + v_windows/v/vlib/math/internal/machine.v | 58 + v_windows/v/vlib/math/invhyp.v | 51 + v_windows/v/vlib/math/invtrig.c.v | 33 + v_windows/v/vlib/math/invtrig.js.v | 33 + v_windows/v/vlib/math/invtrig.v | 219 + v_windows/v/vlib/math/log.c.v | 25 + v_windows/v/vlib/math/log.js.v | 9 + v_windows/v/vlib/math/log.v | 76 + v_windows/v/vlib/math/math.c.v | 14 + v_windows/v/vlib/math/math.v | 169 + v_windows/v/vlib/math/math_test.v | 970 ++ v_windows/v/vlib/math/mathutil/mathutil.v | 19 + v_windows/v/vlib/math/mathutil/mathutil_test.v | 22 + v_windows/v/vlib/math/modf.v | 29 + v_windows/v/vlib/math/nextafter.v | 45 + v_windows/v/vlib/math/poly.v | 65 + v_windows/v/vlib/math/pow.c.v | 17 + v_windows/v/vlib/math/pow.js.v | 7 + v_windows/v/vlib/math/pow.v | 37 + v_windows/v/vlib/math/q_rsqrt.v | 12 + v_windows/v/vlib/math/sin.c.v | 33 + v_windows/v/vlib/math/sin.js.v | 17 + v_windows/v/vlib/math/sin.v | 179 + v_windows/v/vlib/math/sinh.c.v | 17 + v_windows/v/vlib/math/sinh.js.v | 17 + v_windows/v/vlib/math/sinh.v | 49 + v_windows/v/vlib/math/sqrt.c.v | 17 + v_windows/v/vlib/math/sqrt.v | 37 + v_windows/v/vlib/math/stats/stats.v | 249 + v_windows/v/vlib/math/stats/stats_test.v | 269 + v_windows/v/vlib/math/tan.c.v | 17 + v_windows/v/vlib/math/tan.js.v | 9 + v_windows/v/vlib/math/tan.v | 113 + v_windows/v/vlib/math/tanh.c.v | 9 + v_windows/v/vlib/math/tanh.js.v | 9 + v_windows/v/vlib/math/tanh.v | 45 + v_windows/v/vlib/math/unsafe.js.v | 59 + v_windows/v/vlib/math/unsafe.v | 38 + v_windows/v/vlib/math/util/util.v | 82 + v_windows/v/vlib/mssql/README.md | 69 + v_windows/v/vlib/mssql/_cdef_nix.c.v | 6 + v_windows/v/vlib/mssql/_cdef_windows.c.v | 12 + v_windows/v/vlib/mssql/_cdefs.c.v | 27 + v_windows/v/vlib/mssql/config.v | 20 + v_windows/v/vlib/mssql/mssql.v | 125 + v_windows/v/vlib/mssql/result.v | 13 + v_windows/v/vlib/mssql/stmt_handle.v | 127 + v_windows/v/vlib/mysql/README.md | 30 + v_windows/v/vlib/mysql/_cdefs.c.v | 101 + v_windows/v/vlib/mysql/_cdefs_nix.c.v | 11 + v_windows/v/vlib/mysql/_cdefs_windows.c.v | 5 + v_windows/v/vlib/mysql/consts.v | 13 + v_windows/v/vlib/mysql/enums.v | 78 + v_windows/v/vlib/mysql/mysql.v | 239 + v_windows/v/vlib/mysql/mysql_orm_test.v | 77 + v_windows/v/vlib/mysql/orm.v | 296 + v_windows/v/vlib/mysql/result.v | 153 + v_windows/v/vlib/mysql/stmt.c.v | 233 + v_windows/v/vlib/mysql/utils.v | 26 + v_windows/v/vlib/net/aasocket.c.v | 104 + v_windows/v/vlib/net/address.v | 258 + v_windows/v/vlib/net/address_darwin.c.v | 74 + v_windows/v/vlib/net/address_default.c.v | 32 + v_windows/v/vlib/net/address_freebsd.c.v | 77 + v_windows/v/vlib/net/address_linux.c.v | 63 + v_windows/v/vlib/net/address_test.v | 98 + v_windows/v/vlib/net/address_windows.c.v | 58 + v_windows/v/vlib/net/afunix.h | 26 + v_windows/v/vlib/net/common.v | 129 + v_windows/v/vlib/net/conv/conv.c.v | 21 + v_windows/v/vlib/net/conv/conv_default.c.v | 46 + v_windows/v/vlib/net/conv/conv_windows.c.v | 21 + v_windows/v/vlib/net/errors.v | 70 + v_windows/v/vlib/net/ftp/ftp.v | 265 + v_windows/v/vlib/net/ftp/ftp_test.v | 50 + v_windows/v/vlib/net/html/README.md | 16 + v_windows/v/vlib/net/html/data_structures.v | 91 + v_windows/v/vlib/net/html/dom.v | 189 + v_windows/v/vlib/net/html/dom_test.v | 56 + v_windows/v/vlib/net/html/html.v | 18 + v_windows/v/vlib/net/html/html_test.v | 15 + v_windows/v/vlib/net/html/parser.v | 260 + v_windows/v/vlib/net/html/parser_test.v | 41 + v_windows/v/vlib/net/html/tag.v | 68 + v_windows/v/vlib/net/http/backend_nix.c.v | 74 + v_windows/v/vlib/net/http/backend_windows.c.v | 28 + v_windows/v/vlib/net/http/chunked/dechunk.v | 72 + v_windows/v/vlib/net/http/cookie.v | 413 + v_windows/v/vlib/net/http/cookie_test.v | 468 + v_windows/v/vlib/net/http/download.v | 18 + v_windows/v/vlib/net/http/download_nix.c.v | 52 + v_windows/v/vlib/net/http/download_windows.c.v | 29 + v_windows/v/vlib/net/http/header.v | 698 ++ v_windows/v/vlib/net/http/header_test.v | 387 + v_windows/v/vlib/net/http/http.v | 186 + v_windows/v/vlib/net/http/http_httpbin_test.v | 95 + v_windows/v/vlib/net/http/http_test.v | 56 + v_windows/v/vlib/net/http/method.v | 48 + v_windows/v/vlib/net/http/request.v | 324 + v_windows/v/vlib/net/http/request_test.v | 138 + v_windows/v/vlib/net/http/response.v | 152 + v_windows/v/vlib/net/http/response_test.v | 36 + v_windows/v/vlib/net/http/server.v | 123 + v_windows/v/vlib/net/http/server_test.v | 90 + v_windows/v/vlib/net/http/status.v | 255 + v_windows/v/vlib/net/http/status_test.v | 49 + v_windows/v/vlib/net/http/version.v | 40 + v_windows/v/vlib/net/ipv6_v6only.h | 5 + v_windows/v/vlib/net/net_nix.c.v | 26 + v_windows/v/vlib/net/net_windows.c.v | 780 ++ v_windows/v/vlib/net/openssl/c.v | 120 + v_windows/v/vlib/net/openssl/openssl.v | 32 + v_windows/v/vlib/net/openssl/ssl_connection.v | 268 + v_windows/v/vlib/net/smtp/smtp.v | 190 + v_windows/v/vlib/net/smtp/smtp_test.v | 89 + v_windows/v/vlib/net/socket_options.c.v | 50 + v_windows/v/vlib/net/tcp.v | 420 + v_windows/v/vlib/net/tcp_read_line.v | 90 + .../v/vlib/net/tcp_simple_client_server_test.v | 150 + v_windows/v/vlib/net/tcp_test.v | 100 + v_windows/v/vlib/net/udp.v | 289 + v_windows/v/vlib/net/udp_test.v | 67 + v_windows/v/vlib/net/unix/aasocket.c.v | 104 + v_windows/v/vlib/net/unix/common.v | 128 + v_windows/v/vlib/net/unix/stream_nix.v | 288 + v_windows/v/vlib/net/unix/unix_test.v | 50 + v_windows/v/vlib/net/urllib/urllib.v | 1095 ++ v_windows/v/vlib/net/urllib/urllib_test.v | 51 + v_windows/v/vlib/net/urllib/values.v | 87 + v_windows/v/vlib/net/util.v | 27 + v_windows/v/vlib/net/websocket/events.v | 227 + v_windows/v/vlib/net/websocket/handshake.v | 185 + v_windows/v/vlib/net/websocket/io.v | 100 + v_windows/v/vlib/net/websocket/message.v | 295 + .../v/vlib/net/websocket/tests/autobahn/README.md | 20 + .../net/websocket/tests/autobahn/autobahn_client.v | 33 + .../websocket/tests/autobahn/autobahn_client_wss.v | 35 + .../net/websocket/tests/autobahn/autobahn_server.v | 27 + .../websocket/tests/autobahn/docker-compose.yml | 21 + .../tests/autobahn/fuzzing_server/Dockerfile | 5 + .../tests/autobahn/fuzzing_server/check_results.py | 46 + .../fuzzing_server/config/fuzzingclient.json | 22 + .../fuzzing_server/config/fuzzingserver.json | 14 + .../tests/autobahn/fuzzing_server_wss/Dockerfile | 9 + .../autobahn/fuzzing_server_wss/check_results.py | 35 + .../fuzzing_server_wss/config/fuzzingserver.json | 16 + .../autobahn/fuzzing_server_wss/config/server.crt | 19 + .../autobahn/fuzzing_server_wss/config/server.csr | 16 + .../autobahn/fuzzing_server_wss/config/server.key | 27 + .../autobahn/fuzzing_server_wss/config/server.pem | 19 + .../websocket/tests/autobahn/local_run/Dockerfile | 12 + .../tests/autobahn/local_run/autobahn_client.v | 33 + .../tests/autobahn/local_run/autobahn_client_wss.v | 35 + .../websocket/tests/autobahn/ws_test/Dockerfile | 12 + v_windows/v/vlib/net/websocket/uri.v | 16 + v_windows/v/vlib/net/websocket/utils.v | 54 + v_windows/v/vlib/net/websocket/websocket_client.v | 488 + v_windows/v/vlib/net/websocket/websocket_nix.c.v | 10 + v_windows/v/vlib/net/websocket/websocket_server.v | 189 + v_windows/v/vlib/net/websocket/websocket_test.v | 122 + .../v/vlib/net/websocket/websocket_windows.c.v | 12 + v_windows/v/vlib/orm/README.md | 85 + v_windows/v/vlib/orm/orm.v | 473 + v_windows/v/vlib/orm/orm_fn_test.v | 272 + v_windows/v/vlib/orm/orm_test.v | 316 + v_windows/v/vlib/os/args.v | 51 + v_windows/v/vlib/os/bare/bare_example_linux.v | 8 + v_windows/v/vlib/os/cmdline/cmdline.v | 82 + v_windows/v/vlib/os/cmdline/cmdline_test.v | 37 + v_windows/v/vlib/os/const.v | 1 + v_windows/v/vlib/os/const_nix.c.v | 16 + v_windows/v/vlib/os/const_windows.c.v | 161 + v_windows/v/vlib/os/environment.c.v | 108 + v_windows/v/vlib/os/environment.js.v | 37 + v_windows/v/vlib/os/environment_test.v | 49 + v_windows/v/vlib/os/fd.c.v | 61 + v_windows/v/vlib/os/file.c.v | 787 ++ v_windows/v/vlib/os/file.js.v | 136 + v_windows/v/vlib/os/file_test.v | 372 + v_windows/v/vlib/os/filelock/filelock_test.v | 27 + v_windows/v/vlib/os/filelock/lib.v | 14 + v_windows/v/vlib/os/filelock/lib_nix.c.v | 82 + v_windows/v/vlib/os/filelock/lib_windows.c.v | 75 + v_windows/v/vlib/os/glob_test.v | 80 + v_windows/v/vlib/os/inode.c.v | 92 + v_windows/v/vlib/os/inode_test.v | 43 + v_windows/v/vlib/os/notify/backend_default.c.v | 6 + v_windows/v/vlib/os/notify/backend_linux.c.v | 206 + v_windows/v/vlib/os/notify/notify.v | 35 + v_windows/v/vlib/os/notify/notify_test.v | 155 + v_windows/v/vlib/os/os.c.v | 1010 ++ v_windows/v/vlib/os/os.js.v | 97 + v_windows/v/vlib/os/os.v | 633 ++ v_windows/v/vlib/os/os_android.c.v | 39 + v_windows/v/vlib/os/os_darwin.c.v | 18 + v_windows/v/vlib/os/os_darwin.m | 7 + v_windows/v/vlib/os/os_js.js.v | 127 + v_windows/v/vlib/os/os_linux.c.v | 19 + v_windows/v/vlib/os/os_nix.c.v | 549 + v_windows/v/vlib/os/os_test.v | 752 ++ v_windows/v/vlib/os/os_windows.c.v | 544 + v_windows/v/vlib/os/process.c.v | 248 + v_windows/v/vlib/os/process.js.v | 117 + v_windows/v/vlib/os/process.v | 70 + v_windows/v/vlib/os/process_nix.c.v | 146 + v_windows/v/vlib/os/process_test.v | 96 + v_windows/v/vlib/os/process_windows.c.v | 243 + v_windows/v/vlib/os/signal.c.v | 58 + v_windows/v/vlib/os/signal_test.v | 35 + v_windows/v/vlib/pg/README.md | 25 + v_windows/v/vlib/pg/oid.v | 171 + v_windows/v/vlib/pg/orm.v | 272 + v_windows/v/vlib/pg/pg.v | 277 + v_windows/v/vlib/pg/pg_orm_test.v | 77 + v_windows/v/vlib/picoev/picoev.v | 265 + v_windows/v/vlib/picohttpparser/misc.v | 20 + v_windows/v/vlib/picohttpparser/picohttpparser.v | 35 + v_windows/v/vlib/picohttpparser/request.v | 65 + v_windows/v/vlib/picohttpparser/response.v | 114 + v_windows/v/vlib/rand/README.md | 87 + v_windows/v/vlib/rand/config/config.v | 13 + v_windows/v/vlib/rand/constants/constants.v | 12 + v_windows/v/vlib/rand/dist/README.md | 10 + v_windows/v/vlib/rand/dist/dist.v | 85 + v_windows/v/vlib/rand/dist/dist_test.v | 134 + v_windows/v/vlib/rand/mt19937/mt19937.v | 325 + v_windows/v/vlib/rand/mt19937/mt19937_test.v | 341 + v_windows/v/vlib/rand/musl/musl_rng.v | 241 + v_windows/v/vlib/rand/musl/musl_rng_test.v | 331 + v_windows/v/vlib/rand/pcg32/pcg32.v | 226 + v_windows/v/vlib/rand/pcg32/pcg32_test.v | 337 + v_windows/v/vlib/rand/rand.c.v | 127 + v_windows/v/vlib/rand/rand.v | 183 + v_windows/v/vlib/rand/random_identifiers_test.v | 70 + v_windows/v/vlib/rand/random_numbers_test.v | 319 + v_windows/v/vlib/rand/seed/seed.v | 40 + v_windows/v/vlib/rand/splitmix64/splitmix64.v | 225 + v_windows/v/vlib/rand/splitmix64/splitmix64_test.v | 331 + v_windows/v/vlib/rand/sys/system_rng.c.v | 275 + v_windows/v/vlib/rand/sys/system_rng.js.v | 15 + v_windows/v/vlib/rand/sys/system_rng_test.v | 354 + v_windows/v/vlib/rand/util/util.v | 52 + v_windows/v/vlib/rand/util/util_test.v | 57 + v_windows/v/vlib/rand/wyrand/wyrand.v | 252 + v_windows/v/vlib/rand/wyrand/wyrand_test.v | 331 + v_windows/v/vlib/readline/README.md | 20 + v_windows/v/vlib/readline/readline.v | 45 + v_windows/v/vlib/readline/readline_default.c.v | 78 + v_windows/v/vlib/readline/readline_linux.c.v | 554 + v_windows/v/vlib/readline/readline_test.v | 20 + v_windows/v/vlib/readline/readline_windows.c.v | 74 + v_windows/v/vlib/regex/README.md | 874 ++ v_windows/v/vlib/regex/regex.v | 2324 ++++ v_windows/v/vlib/regex/regex_opt.v | 53 + v_windows/v/vlib/regex/regex_test.v | 608 ++ v_windows/v/vlib/regex/regex_util.v | 436 + v_windows/v/vlib/runtime/runtime.v | 56 + v_windows/v/vlib/runtime/runtime_nix.c.v | 11 + v_windows/v/vlib/runtime/runtime_test.v | 39 + v_windows/v/vlib/runtime/runtime_windows.c.v | 21 + v_windows/v/vlib/semver/LICENSE.md | 21 + v_windows/v/vlib/semver/README.md | 37 + v_windows/v/vlib/semver/compare.v | 59 + v_windows/v/vlib/semver/parse.v | 85 + v_windows/v/vlib/semver/range.v | 252 + v_windows/v/vlib/semver/semver.v | 110 + v_windows/v/vlib/semver/semver_test.v | 192 + v_windows/v/vlib/semver/util.v | 55 + v_windows/v/vlib/semver/v.mod | 5 + v_windows/v/vlib/sokol/audio/audio.v | 134 + v_windows/v/vlib/sokol/c/declaration.c.v | 58 + v_windows/v/vlib/sokol/f/f.v | 15 + v_windows/v/vlib/sokol/gfx/enums.v | 297 + v_windows/v/vlib/sokol/gfx/gfx.c.v | 266 + v_windows/v/vlib/sokol/gfx/gfx_funcs.c.v | 71 + v_windows/v/vlib/sokol/gfx/gfx_structs.c.v | 535 + v_windows/v/vlib/sokol/gfx/gfx_utils.c.v | 17 + v_windows/v/vlib/sokol/sapp/enums.v | 165 + v_windows/v/vlib/sokol/sapp/sapp.c.v | 247 + v_windows/v/vlib/sokol/sapp/sapp_funcs.c.v | 111 + v_windows/v/vlib/sokol/sapp/sapp_structs.c.v | 104 + v_windows/v/vlib/sokol/sfons/sfons.c.v | 26 + v_windows/v/vlib/sokol/sfons/sfons_funcs.c.v | 6 + v_windows/v/vlib/sokol/sgl/sgl.c.v | 375 + v_windows/v/vlib/sokol/sgl/sgl_funcs.c.v | 91 + v_windows/v/vlib/sokol/sgl/sgl_structs.c.v | 24 + v_windows/v/vlib/sokol/sokol.v | 19 + v_windows/v/vlib/sqlite/README.md | 16 + v_windows/v/vlib/sqlite/orm.v | 162 + v_windows/v/vlib/sqlite/sqlite.v | 236 + v_windows/v/vlib/sqlite/sqlite_orm_test.v | 70 + v_windows/v/vlib/sqlite/sqlite_test.v | 31 + v_windows/v/vlib/sqlite/stmt.v | 74 + v_windows/v/vlib/stbi/stbi.c.v | 87 + v_windows/v/vlib/strconv/atof.v | 441 + v_windows/v/vlib/strconv/atof_test.v | 75 + v_windows/v/vlib/strconv/atofq.v | 348 + v_windows/v/vlib/strconv/atoi.v | 261 + v_windows/v/vlib/strconv/atoi_test.v | 84 + v_windows/v/vlib/strconv/f32_f64_to_string_test.v | 171 + v_windows/v/vlib/strconv/f32_str.v | 377 + v_windows/v/vlib/strconv/f64_str.v | 418 + v_windows/v/vlib/strconv/format.md | 250 + v_windows/v/vlib/strconv/format.v | 113 + v_windows/v/vlib/strconv/format_mem.v | 498 + v_windows/v/vlib/strconv/format_test.v | 110 + v_windows/v/vlib/strconv/ftoa.v | 39 + v_windows/v/vlib/strconv/number_to_base.v | 61 + v_windows/v/vlib/strconv/number_to_base_test.v | 38 + v_windows/v/vlib/strconv/structs.v | 55 + v_windows/v/vlib/strconv/tables.v | 738 ++ v_windows/v/vlib/strconv/utilities.v | 556 + v_windows/v/vlib/strconv/vprintf.v | 726 ++ v_windows/v/vlib/strings/builder.js.v | 51 + v_windows/v/vlib/strings/builder.v | 163 + v_windows/v/vlib/strings/builder_test.v | 114 + v_windows/v/vlib/strings/similarity.v | 69 + v_windows/v/vlib/strings/similarity_test.v | 13 + v_windows/v/vlib/strings/strings.c.v | 38 + v_windows/v/vlib/strings/strings.js.v | 17 + v_windows/v/vlib/strings/strings.v | 13 + v_windows/v/vlib/strings/strings_test.v | 14 + v_windows/v/vlib/strings/textscanner/textscanner.v | 154 + .../v/vlib/strings/textscanner/textscanner_test.v | 159 + v_windows/v/vlib/sync/array_rlock_test.v | 38 + v_windows/v/vlib/sync/atomic2/atomic.v | 88 + v_windows/v/vlib/sync/atomic2/atomic_test.v | 105 + v_windows/v/vlib/sync/bench/channel_bench_go.go | 68 + v_windows/v/vlib/sync/bench/channel_bench_v.v | 64 + .../many_writers_and_receivers_on_1_channel.v | 150 + v_windows/v/vlib/sync/bench/results.md | 48 + v_windows/v/vlib/sync/channel_1_test.v | 25 + v_windows/v/vlib/sync/channel_2_test.v | 19 + v_windows/v/vlib/sync/channel_3_test.v | 32 + v_windows/v/vlib/sync/channel_4_test.v | 32 + v_windows/v/vlib/sync/channel_array_mut_test.v | 35 + v_windows/v/vlib/sync/channel_close_test.v | 104 + v_windows/v/vlib/sync/channel_fill_test.v | 22 + v_windows/v/vlib/sync/channel_opt_propagate_test.v | 39 + v_windows/v/vlib/sync/channel_polling_test.v | 56 + v_windows/v/vlib/sync/channel_push_or_1_test.v | 65 + v_windows/v/vlib/sync/channel_push_or_2_test.v | 25 + v_windows/v/vlib/sync/channel_select_2_test.v | 62 + v_windows/v/vlib/sync/channel_select_3_test.v | 122 + v_windows/v/vlib/sync/channel_select_4_test.v | 43 + v_windows/v/vlib/sync/channel_select_5_test.v | 61 + v_windows/v/vlib/sync/channel_select_6_test.v | 75 + v_windows/v/vlib/sync/channel_select_test.v | 84 + v_windows/v/vlib/sync/channel_try_buf_test.v | 17 + v_windows/v/vlib/sync/channel_try_unbuf_test.v | 13 + v_windows/v/vlib/sync/channels.v | 730 ++ v_windows/v/vlib/sync/pool/README.md | 36 + v_windows/v/vlib/sync/pool/pool.v | 165 + v_windows/v/vlib/sync/pool/pool_test.v | 52 + v_windows/v/vlib/sync/select_close_test.v | 92 + v_windows/v/vlib/sync/struct_chan_init_test.v | 14 + v_windows/v/vlib/sync/sync_default.c.v | 193 + v_windows/v/vlib/sync/sync_macos.c.v | 232 + v_windows/v/vlib/sync/sync_windows.c.v | 212 + v_windows/v/vlib/sync/threads/threads.c.v | 13 + v_windows/v/vlib/sync/threads/threads.v | 4 + v_windows/v/vlib/sync/waitgroup.v | 84 + v_windows/v/vlib/sync/waitgroup_test.v | 41 + v_windows/v/vlib/szip/szip.v | 296 + v_windows/v/vlib/szip/szip_test.v | 88 + v_windows/v/vlib/term/README.md | 85 + v_windows/v/vlib/term/colors.v | 198 + v_windows/v/vlib/term/control.v | 108 + v_windows/v/vlib/term/term.js.v | 8 + v_windows/v/vlib/term/term.v | 198 + v_windows/v/vlib/term/term_nix.c.v | 105 + v_windows/v/vlib/term/term_test.v | 115 + v_windows/v/vlib/term/term_windows.c.v | 125 + v_windows/v/vlib/term/ui/README.md | 99 + v_windows/v/vlib/term/ui/color.v | 88 + v_windows/v/vlib/term/ui/consoleapi_windows.c.v | 82 + v_windows/v/vlib/term/ui/input.v | 241 + v_windows/v/vlib/term/ui/input_nix.c.v | 70 + v_windows/v/vlib/term/ui/input_windows.c.v | 326 + v_windows/v/vlib/term/ui/termios_nix.c.v | 530 + v_windows/v/vlib/term/ui/ui.v | 256 + v_windows/v/vlib/time/Y2K38_test.v | 13 + v_windows/v/vlib/time/chrono.c.v | 18 + v_windows/v/vlib/time/chrono.v | 14 + v_windows/v/vlib/time/format.v | 174 + v_windows/v/vlib/time/misc/misc.v | 13 + v_windows/v/vlib/time/misc/misc_test.v | 17 + v_windows/v/vlib/time/operator.v | 21 + v_windows/v/vlib/time/operator_test.v | 391 + v_windows/v/vlib/time/parse.c.v | 140 + v_windows/v/vlib/time/parse.js.v | 24 + v_windows/v/vlib/time/parse_test.v | 138 + v_windows/v/vlib/time/private_test.v | 13 + v_windows/v/vlib/time/stopwatch.v | 72 + v_windows/v/vlib/time/stopwatch_test.v | 36 + v_windows/v/vlib/time/time.c.v | 125 + v_windows/v/vlib/time/time.js.v | 38 + v_windows/v/vlib/time/time.v | 315 + v_windows/v/vlib/time/time_addition_test.v | 33 + v_windows/v/vlib/time/time_darwin.c.v | 84 + v_windows/v/vlib/time/time_format_test.v | 90 + v_windows/v/vlib/time/time_js.js.v | 31 + v_windows/v/vlib/time/time_linux.c.v | 26 + v_windows/v/vlib/time/time_nix.c.v | 156 + v_windows/v/vlib/time/time_solaris.c.v | 32 + v_windows/v/vlib/time/time_test.v | 247 + v_windows/v/vlib/time/time_windows.c.v | 231 + v_windows/v/vlib/time/unix.v | 124 + v_windows/v/vlib/v/README.md | 119 + v_windows/v/vlib/v/TEMPLATES.md | 129 + v_windows/v/vlib/v/ast/ast.v | 2026 ++++ v_windows/v/vlib/v/ast/attr.v | 62 + v_windows/v/vlib/v/ast/cflags.v | 89 + v_windows/v/vlib/v/ast/cflags_test.v | 72 + v_windows/v/vlib/v/ast/comptime_const_values.v | 271 + v_windows/v/vlib/v/ast/init.v | 70 + v_windows/v/vlib/v/ast/scope.v | 204 + v_windows/v/vlib/v/ast/str.v | 588 + v_windows/v/vlib/v/ast/table.v | 1463 +++ v_windows/v/vlib/v/ast/types.v | 1310 +++ v_windows/v/vlib/v/ast/types_test.v | 81 + v_windows/v/vlib/v/ast/walker/walker.v | 37 + v_windows/v/vlib/v/ast/walker/walker_test.v | 66 + v_windows/v/vlib/v/builder/builder.v | 467 + v_windows/v/vlib/v/builder/c.v | 63 + v_windows/v/vlib/v/builder/cc.v | 1021 ++ v_windows/v/vlib/v/builder/cflags.v | 45 + v_windows/v/vlib/v/builder/compile.v | 326 + v_windows/v/vlib/v/builder/js.v | 51 + v_windows/v/vlib/v/builder/msvc.v | 505 + v_windows/v/vlib/v/builder/native.v | 22 + v_windows/v/vlib/v/callgraph/callgraph.v | 129 + v_windows/v/vlib/v/cflag/cflags.v | 131 + v_windows/v/vlib/v/checker/check_types.v | 733 ++ v_windows/v/vlib/v/checker/checker.v | 8392 +++++++++++++++ v_windows/v/vlib/v/checker/comptime_const_eval.v | 159 + v_windows/v/vlib/v/checker/noreturn.v | 112 + v_windows/v/vlib/v/checker/tests/.gitattributes | 2 + v_windows/v/vlib/v/checker/tests/.gitignore | 4 + .../tests/a_test_file_with_0_test_fns_test.out | 6 + .../tests/a_test_file_with_0_test_fns_test.vv | 1 + .../vlib/v/checker/tests/add_op_wrong_type_err.out | 42 + .../vlib/v/checker/tests/add_op_wrong_type_err.vv | 10 + .../alias_array_unknown_op_overloading_err.out | 13 + .../alias_array_unknown_op_overloading_err.vv | 19 + .../tests/alias_map_unknown_op_overloading_err.out | 13 + .../tests/alias_map_unknown_op_overloading_err.vv | 21 + .../v/vlib/v/checker/tests/alias_type_exists.out | 3 + .../v/vlib/v/checker/tests/alias_type_exists.vv | 1 + .../v/checker/tests/ambiguous_field_method_err.out | 13 + .../v/checker/tests/ambiguous_field_method_err.vv | 24 + .../v/checker/tests/ambiguous_function_call.out | 13 + .../v/checker/tests/ambiguous_function_call.vv | 13 + .../vlib/v/checker/tests/any_int_float_ban_err.out | 45 + .../vlib/v/checker/tests/any_int_float_ban_err.vv | 15 + v_windows/v/vlib/v/checker/tests/append_err.out | 20 + v_windows/v/vlib/v/checker/tests/append_err.vv | 7 + .../tests/array_append_array_type_mismatch_err.out | 7 + .../tests/array_append_array_type_mismatch_err.vv | 5 + .../v/checker/tests/array_builtin_redefinition.out | 7 + .../v/checker/tests/array_builtin_redefinition.vv | 11 + v_windows/v/vlib/v/checker/tests/array_cmp_err.out | 26 + v_windows/v/vlib/v/checker/tests/array_cmp_err.vv | 6 + .../v/checker/tests/array_declare_element_a.out | 5 + .../v/checker/tests/array_declare_element_a.vv | 3 + .../v/checker/tests/array_declare_element_b.out | 6 + .../v/checker/tests/array_declare_element_b.vv | 4 + .../v/checker/tests/array_declare_element_c.out | 6 + .../v/checker/tests/array_declare_element_c.vv | 4 + .../v/vlib/v/checker/tests/array_element_type.out | 12 + .../v/vlib/v/checker/tests/array_element_type.vv | 4 + .../v/vlib/v/checker/tests/array_filter_fn_err.out | 27 + .../v/vlib/v/checker/tests/array_filter_fn_err.vv | 21 + v_windows/v/vlib/v/checker/tests/array_index.out | 34 + v_windows/v/vlib/v/checker/tests/array_index.vv | 11 + ...nit_sum_type_without_init_value_and_len_err.out | 14 + ...init_sum_type_without_init_value_and_len_err.vv | 9 + .../tests/array_insert_prepend_args_err.out | 34 + .../checker/tests/array_insert_prepend_args_err.vv | 13 + .../v/checker/tests/array_insert_type_mismatch.out | 56 + .../v/checker/tests/array_insert_type_mismatch.vv | 15 + .../v/checker/tests/array_literal_modify_err.out | 12 + .../v/checker/tests/array_literal_modify_err.vv | 4 + .../v/checker/tests/array_map_arg_mismatch.out | 13 + .../vlib/v/checker/tests/array_map_arg_mismatch.vv | 5 + .../v/vlib/v/checker/tests/array_map_fn_err.out | 41 + .../v/vlib/v/checker/tests/array_map_fn_err.vv | 30 + .../vlib/v/checker/tests/array_map_void_fn_err.out | 6 + .../vlib/v/checker/tests/array_map_void_fn_err.vv | 4 + .../array_of_interfaces_with_len_without_init.out | 7 + .../array_of_interfaces_with_len_without_init.vv | 16 + .../v/checker/tests/array_or_map_assign_err.out | 35 + .../v/checker/tests/array_or_map_assign_err.vv | 29 + .../checker/tests/array_prepend_type_mismatch.out | 56 + .../v/checker/tests/array_prepend_type_mismatch.vv | 15 + .../v/vlib/v/checker/tests/array_sort_err.out | 27 + v_windows/v/vlib/v/checker/tests/array_sort_err.vv | 7 + .../tests/array_sort_struct_no_body_err.out | 5 + .../checker/tests/array_sort_struct_no_body_err.vv | 6 + .../tests/arrow_op_wrong_left_type_err_a.out | 6 + .../tests/arrow_op_wrong_left_type_err_a.vv | 5 + .../tests/arrow_op_wrong_left_type_err_b.out | 7 + .../tests/arrow_op_wrong_left_type_err_b.vv | 6 + .../tests/arrow_op_wrong_right_type_err_a.out | 6 + .../tests/arrow_op_wrong_right_type_err_a.vv | 5 + .../tests/arrow_op_wrong_right_type_err_b.out | 7 + .../tests/arrow_op_wrong_right_type_err_b.vv | 5 + .../v/vlib/v/checker/tests/asm_immutable_err.out | 7 + .../v/vlib/v/checker/tests/asm_immutable_err.vv | 16 + .../v/vlib/v/checker/tests/assert_optional_err.out | 6 + .../v/vlib/v/checker/tests/assert_optional_err.vv | 5 + .../tests/assign_array_init_with_no_type.out | 12 + .../tests/assign_array_init_with_no_type.vv | 4 + .../assign_deref_fn_call_on_left_side_err.out | 6 + .../tests/assign_deref_fn_call_on_left_side_err.vv | 9 + .../v/checker/tests/assign_expr_type_err_a.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_a.vv | 5 + .../v/checker/tests/assign_expr_type_err_b.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_b.vv | 5 + .../v/checker/tests/assign_expr_type_err_c.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_c.vv | 5 + .../v/checker/tests/assign_expr_type_err_d.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_d.vv | 5 + .../v/checker/tests/assign_expr_type_err_e.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_e.vv | 5 + .../v/checker/tests/assign_expr_type_err_f.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_f.vv | 5 + .../v/checker/tests/assign_expr_type_err_g.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_g.vv | 5 + .../v/checker/tests/assign_expr_type_err_h.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_h.vv | 5 + .../v/checker/tests/assign_expr_type_err_i.out | 7 + .../vlib/v/checker/tests/assign_expr_type_err_i.vv | 5 + .../checker/tests/assign_expr_undefined_err_a.out | 6 + .../v/checker/tests/assign_expr_undefined_err_a.vv | 4 + .../checker/tests/assign_expr_undefined_err_b.out | 6 + .../v/checker/tests/assign_expr_undefined_err_b.vv | 4 + .../checker/tests/assign_expr_undefined_err_c.out | 6 + .../v/checker/tests/assign_expr_undefined_err_c.vv | 4 + .../checker/tests/assign_expr_undefined_err_d.out | 6 + .../v/checker/tests/assign_expr_undefined_err_d.vv | 4 + .../checker/tests/assign_expr_undefined_err_e.out | 6 + .../v/checker/tests/assign_expr_undefined_err_e.vv | 4 + .../checker/tests/assign_expr_undefined_err_f.out | 6 + .../v/checker/tests/assign_expr_undefined_err_f.vv | 4 + .../checker/tests/assign_expr_undefined_err_g.out | 5 + .../v/checker/tests/assign_expr_undefined_err_g.vv | 3 + .../checker/tests/assign_expr_undefined_err_h.out | 7 + .../v/checker/tests/assign_expr_undefined_err_h.vv | 8 + .../checker/tests/assign_expr_undefined_err_i.out | 6 + .../v/checker/tests/assign_expr_undefined_err_i.vv | 4 + .../checker/tests/assign_expr_undefined_err_j.out | 6 + .../v/checker/tests/assign_expr_undefined_err_j.vv | 4 + .../checker/tests/assign_expr_undefined_err_k.out | 6 + .../v/checker/tests/assign_expr_undefined_err_k.vv | 4 + .../assign_expr_unresolved_variables_err_chain.out | 13 + .../assign_expr_unresolved_variables_err_chain.vv | 8 + .../tests/assign_fn_call_on_left_side_err.out | 6 + .../tests/assign_fn_call_on_left_side_err.vv | 7 + .../v/checker/tests/assign_multi_immutable_err.out | 21 + .../v/checker/tests/assign_multi_immutable_err.vv | 21 + .../vlib/v/checker/tests/assign_multi_mismatch.out | 81 + .../vlib/v/checker/tests/assign_multi_mismatch.vv | 30 + v_windows/v/vlib/v/checker/tests/assign_mut.out | 7 + v_windows/v/vlib/v/checker/tests/assign_mut.vv | 6 + .../v/vlib/v/checker/tests/assign_sumtype2_err.out | 7 + .../v/vlib/v/checker/tests/assign_sumtype2_err.vv | 15 + .../v/vlib/v/checker/tests/assign_sumtype_err.out | 7 + .../v/vlib/v/checker/tests/assign_sumtype_err.vv | 15 + .../tests/assign_to_typeless_variable_err.out | 12 + .../tests/assign_to_typeless_variable_err.vv | 4 + .../tests/bad_types_in_string_inter_lit.out | 8 + .../checker/tests/bad_types_in_string_inter_lit.vv | 2 + .../v/checker/tests/bin_lit_without_digit_err.out | 5 + .../v/checker/tests/bin_lit_without_digit_err.vv | 3 + .../v/checker/tests/bin_lit_wrong_digit_err.out | 5 + .../v/checker/tests/bin_lit_wrong_digit_err.vv | 3 + .../v/checker/tests/bit_op_wrong_left_type_err.out | 5 + .../v/checker/tests/bit_op_wrong_left_type_err.vv | 3 + .../checker/tests/bit_op_wrong_right_type_err.out | 5 + .../v/checker/tests/bit_op_wrong_right_type_err.vv | 3 + .../v/checker/tests/blank_ident_invalid_use.out | 5 + .../v/checker/tests/blank_ident_invalid_use.vv | 3 + v_windows/v/vlib/v/checker/tests/blank_modify.out | 5 + v_windows/v/vlib/v/checker/tests/blank_modify.vv | 3 + .../vlib/v/checker/tests/bool_string_cast_err.out | 12 + .../v/vlib/v/checker/tests/bool_string_cast_err.vv | 4 + .../v/vlib/v/checker/tests/break_anon_fn_err.out | 7 + .../v/vlib/v/checker/tests/break_anon_fn_err.vv | 8 + .../v/vlib/v/checker/tests/c_fn_surplus_args.out | 42 + .../v/vlib/v/checker/tests/c_fn_surplus_args.vv | 19 + .../v/vlib/v/checker/tests/cannot_assign_array.out | 6 + .../v/vlib/v/checker/tests/cannot_assign_array.vv | 10 + .../vlib/v/checker/tests/cannot_cast_to_alias.out | 6 + .../v/vlib/v/checker/tests/cannot_cast_to_alias.vv | 7 + .../vlib/v/checker/tests/cannot_cast_to_struct.out | 27 + .../vlib/v/checker/tests/cannot_cast_to_struct.vv | 16 + v_windows/v/vlib/v/checker/tests/cast_err.out | 27 + v_windows/v/vlib/v/checker/tests/cast_err.vv | 10 + .../v/vlib/v/checker/tests/cast_string_err.out | 6 + .../v/vlib/v/checker/tests/cast_string_err.vv | 4 + .../v/checker/tests/cast_string_with_byte_err.out | 5 + .../v/checker/tests/cast_string_with_byte_err.vv | 3 + v_windows/v/vlib/v/checker/tests/cast_void.out | 4 + v_windows/v/vlib/v/checker/tests/cast_void.vv | 2 + v_windows/v/vlib/v/checker/tests/chan_args.out | 28 + v_windows/v/vlib/v/checker/tests/chan_args.vv | 15 + v_windows/v/vlib/v/checker/tests/chan_mut.out | 28 + v_windows/v/vlib/v/checker/tests/chan_mut.vv | 27 + v_windows/v/vlib/v/checker/tests/chan_ref.out | 14 + v_windows/v/vlib/v/checker/tests/chan_ref.vv | 33 + v_windows/v/vlib/v/checker/tests/char_str.out | 3 + v_windows/v/vlib/v/checker/tests/char_str.vv | 1 + .../v/vlib/v/checker/tests/closure_immutable.out | 21 + .../v/vlib/v/checker/tests/closure_immutable.vv | 19 + ...ing_typesymbol_to_a_type_should_not_compile.out | 7 + ...ring_typesymbol_to_a_type_should_not_compile.vv | 17 + .../vlib/v/checker/tests/comptime_call_method.out | 7 + .../v/vlib/v/checker/tests/comptime_call_method.vv | 12 + .../checker/tests/comptime_call_no_unused_var.out | 27 + .../v/checker/tests/comptime_call_no_unused_var.vv | 18 + .../tests/comptime_env/env_parser_errors_1.run.out | 3 + .../tests/comptime_env/env_parser_errors_1.vv | 1 + .../tests/comptime_env/env_parser_errors_2.run.out | 3 + .../tests/comptime_env/env_parser_errors_2.vv | 1 + .../tests/comptime_env/env_parser_errors_3.run.out | 3 + .../tests/comptime_env/env_parser_errors_3.vv | 1 + .../tests/comptime_env/using_comptime_env.run.out | 11 + .../comptime_env/using_comptime_env.var.run.out | 2 + .../using_comptime_env.var_invalid.run.out | 1 + .../tests/comptime_env/using_comptime_env.vv | 8 + .../comptime_field_selector_not_in_for_err.out | 7 + .../comptime_field_selector_not_in_for_err.vv | 14 + .../tests/comptime_field_selector_not_name_err.out | 21 + .../tests/comptime_field_selector_not_name_err.vv | 20 + v_windows/v/vlib/v/checker/tests/comptime_for.out | 55 + v_windows/v/vlib/v/checker/tests/comptime_for.vv | 23 + .../checker/tests/const_array_unknown_type_err.out | 4 + .../checker/tests/const_array_unknown_type_err.vv | 1 + .../checker/tests/const_define_in_function_err.out | 6 + .../checker/tests/const_define_in_function_err.vv | 4 + .../v/vlib/v/checker/tests/const_field_add_err.out | 6 + .../v/vlib/v/checker/tests/const_field_add_err.vv | 7 + .../v/vlib/v/checker/tests/const_field_dec_err.out | 6 + .../v/vlib/v/checker/tests/const_field_dec_err.vv | 7 + .../v/vlib/v/checker/tests/const_field_inc_err.out | 6 + .../v/vlib/v/checker/tests/const_field_inc_err.vv | 7 + .../tests/const_field_name_duplicate_err.out | 7 + .../tests/const_field_name_duplicate_err.vv | 7 + .../checker/tests/const_field_name_snake_case.out | 6 + .../v/checker/tests/const_field_name_snake_case.vv | 4 + .../v/vlib/v/checker/tests/const_field_sub_err.out | 6 + .../v/vlib/v/checker/tests/const_field_sub_err.vv | 7 + v_windows/v/vlib/v/checker/tests/ctdefine.out | 12 + v_windows/v/vlib/v/checker/tests/ctdefine.vv | 6 + .../custom_comptime_define_error.mysymbol.run.out | 2 + .../checker/tests/custom_comptime_define_error.out | 7 + .../checker/tests/custom_comptime_define_error.vv | 10 + .../custom_comptime_define_if_debug.cg.run.out | 3 + ...stom_comptime_define_if_debug.debug.bar.run.out | 3 + .../custom_comptime_define_if_debug.debug.run.out | 2 + .../custom_comptime_define_if_debug.g.run.out | 3 + .../tests/custom_comptime_define_if_debug.out | 0 .../tests/custom_comptime_define_if_debug.run.out | 1 + .../tests/custom_comptime_define_if_debug.vv | 18 + .../custom_comptime_define_if_flag.mydebug.run.out | 4 + .../custom_comptime_define_if_flag.nodebug.run.out | 2 + .../tests/custom_comptime_define_if_flag.out | 0 .../tests/custom_comptime_define_if_flag.vv | 13 + .../v/checker/tests/dec_lit_wrong_digit_err.out | 5 + .../v/checker/tests/dec_lit_wrong_digit_err.vv | 3 + .../v/vlib/v/checker/tests/decompose_type_err.out | 6 + .../v/vlib/v/checker/tests/decompose_type_err.vv | 5 + v_windows/v/vlib/v/checker/tests/defer_in_for.out | 7 + v_windows/v/vlib/v/checker/tests/defer_in_for.vv | 7 + .../v/vlib/v/checker/tests/defer_optional.out | 7 + v_windows/v/vlib/v/checker/tests/defer_optional.vv | 8 + v_windows/v/vlib/v/checker/tests/deprecations.out | 55 + v_windows/v/vlib/v/checker/tests/deprecations.vv | 70 + .../v/checker/tests/direct_map_alias_init_err.out | 6 + .../v/checker/tests/direct_map_alias_init_err.vv | 5 + .../tests/disallow_pointer_arithmetic_err.out | 34 + .../tests/disallow_pointer_arithmetic_err.vv | 9 + .../checker/tests/div_mod_by_cast_zero_int_err.out | 40 + .../checker/tests/div_mod_by_cast_zero_int_err.vv | 10 + .../vlib/v/checker/tests/div_op_wrong_type_err.out | 42 + .../vlib/v/checker/tests/div_op_wrong_type_err.vv | 10 + .../tests/division_by_cast_zero_float_err.out | 12 + .../tests/division_by_cast_zero_float_err.vv | 4 + .../v/checker/tests/division_by_zero_float_err.out | 5 + .../v/checker/tests/division_by_zero_float_err.vv | 3 + .../v/checker/tests/division_by_zero_int_err.out | 26 + .../v/checker/tests/division_by_zero_int_err.vv | 6 + .../v/vlib/v/checker/tests/dump_of_void_expr.out | 5 + .../v/vlib/v/checker/tests/dump_of_void_expr.vv | 3 + .../v/checker/tests/duplicate_field_method_err.out | 13 + .../v/checker/tests/duplicate_field_method_err.vv | 10 + .../v/vlib/v/checker/tests/enum_as_int_err.out | 21 + .../v/vlib/v/checker/tests/enum_as_int_err.vv | 13 + v_windows/v/vlib/v/checker/tests/enum_cast.out | 6 + v_windows/v/vlib/v/checker/tests/enum_cast.vv | 7 + v_windows/v/vlib/v/checker/tests/enum_empty.out | 3 + v_windows/v/vlib/v/checker/tests/enum_empty.vv | 1 + v_windows/v/vlib/v/checker/tests/enum_err.out | 14 + v_windows/v/vlib/v/checker/tests/enum_err.vv | 11 + .../tests/enum_field_name_duplicate_err.out | 7 + .../checker/tests/enum_field_name_duplicate_err.vv | 10 + .../v/vlib/v/checker/tests/enum_field_overflow.out | 6 + .../v/vlib/v/checker/tests/enum_field_overflow.vv | 5 + .../checker/tests/enum_field_value_duplicate_a.out | 13 + .../checker/tests/enum_field_value_duplicate_a.vv | 6 + .../checker/tests/enum_field_value_duplicate_b.out | 6 + .../checker/tests/enum_field_value_duplicate_b.vv | 5 + .../v/checker/tests/enum_field_value_overflow.out | 7 + .../v/checker/tests/enum_field_value_overflow.vv | 5 + v_windows/v/vlib/v/checker/tests/enum_op_err.out | 34 + v_windows/v/vlib/v/checker/tests/enum_op_err.vv | 13 + .../v/vlib/v/checker/tests/enum_op_flag_err.out | 20 + .../v/vlib/v/checker/tests/enum_op_flag_err.vv | 12 + .../v/vlib/v/checker/tests/enum_single_letter.out | 5 + .../v/vlib/v/checker/tests/enum_single_letter.vv | 4 + .../v/checker/tests/eq_ne_op_wrong_type_err.out | 139 + .../v/checker/tests/eq_ne_op_wrong_type_err.vv | 38 + .../vlib/v/checker/tests/error_fn_with_0_args.out | 5 + .../v/vlib/v/checker/tests/error_fn_with_0_args.vv | 3 + .../tests/error_with_comment_with_crlf_ending.out | 4 + .../tests/error_with_comment_with_crlf_ending.vv | 2 + .../tests/error_with_comment_with_lf_ending.out | 4 + .../tests/error_with_comment_with_lf_ending.vv | 2 + ...rror_with_several_comments_with_crlf_ending.out | 13 + ...error_with_several_comments_with_crlf_ending.vv | 11 + .../v/vlib/v/checker/tests/error_with_unicode.out | 56 + .../v/vlib/v/checker/tests/error_with_unicode.vv | 15 + .../tests/expression_should_return_an_option.out | 7 + .../tests/expression_should_return_an_option.vv | 31 + .../tests/filter_func_return_nonbool_err.out | 6 + .../tests/filter_func_return_nonbool_err.vv | 7 + .../vlib/v/checker/tests/filter_on_non_arr_err.out | 5 + .../vlib/v/checker/tests/filter_on_non_arr_err.vv | 3 + .../v/vlib/v/checker/tests/fixed_array_conv.out | 41 + .../v/vlib/v/checker/tests/fixed_array_conv.vv | 14 + .../tests/fixed_array_non_const_size_err.out | 7 + .../tests/fixed_array_non_const_size_err.vv | 7 + .../vlib/v/checker/tests/fixed_array_size_err.out | 14 + .../v/vlib/v/checker/tests/fixed_array_size_err.vv | 8 + .../tests/float_lit_exp_not_integer_err.out | 5 + .../checker/tests/float_lit_exp_not_integer_err.vv | 3 + .../tests/float_lit_exp_without_digit_err.out | 5 + .../tests/float_lit_exp_without_digit_err.vv | 3 + .../tests/float_lit_too_many_points_err.out | 5 + .../checker/tests/float_lit_too_many_points_err.vv | 3 + .../v/vlib/v/checker/tests/float_modulo_err.out | 12 + .../v/vlib/v/checker/tests/float_modulo_err.vv | 4 + v_windows/v/vlib/v/checker/tests/fn_args.out | 35 + v_windows/v/vlib/v/checker/tests/fn_args.vv | 23 + .../v/vlib/v/checker/tests/fn_call_no_body.out | 13 + .../v/vlib/v/checker/tests/fn_call_no_body.vv | 9 + v_windows/v/vlib/v/checker/tests/fn_duplicate.out | 13 + v_windows/v/vlib/v/checker/tests/fn_duplicate.vv | 7 + v_windows/v/vlib/v/checker/tests/fn_init_sig.out | 12 + v_windows/v/vlib/v/checker/tests/fn_init_sig.vv | 6 + .../v/checker/tests/fn_return_array_sort_err.out | 6 + .../v/checker/tests/fn_return_array_sort_err.vv | 7 + .../v/vlib/v/checker/tests/fn_return_or_err.out | 7 + .../v/vlib/v/checker/tests/fn_return_or_err.vv | 13 + .../v/vlib/v/checker/tests/fn_type_exists.out | 10 + v_windows/v/vlib/v/checker/tests/fn_type_exists.vv | 3 + v_windows/v/vlib/v/checker/tests/fn_var.out | 23 + v_windows/v/vlib/v/checker/tests/fn_var.vv | 8 + v_windows/v/vlib/v/checker/tests/fn_variadic.out | 14 + v_windows/v/vlib/v/checker/tests/fn_variadic.vv | 13 + .../vlib/v/checker/tests/for_in_index_optional.out | 7 + .../vlib/v/checker/tests/for_in_index_optional.vv | 6 + .../v/vlib/v/checker/tests/for_in_index_type.out | 13 + .../v/vlib/v/checker/tests/for_in_index_type.vv | 5 + .../checker/tests/for_in_map_one_variable_err.out | 8 + .../v/checker/tests/for_in_map_one_variable_err.vv | 6 + .../v/vlib/v/checker/tests/for_in_mut_val_type.out | 42 + .../v/vlib/v/checker/tests/for_in_mut_val_type.vv | 23 + .../checker/tests/for_in_range_not_match_type.out | 6 + .../v/checker/tests/for_in_range_not_match_type.vv | 5 + .../v/checker/tests/for_in_range_string_type.out | 6 + .../v/checker/tests/for_in_range_string_type.vv | 5 + v_windows/v/vlib/v/checker/tests/for_match_err.out | 7 + v_windows/v/vlib/v/checker/tests/for_match_err.vv | 15 + .../v/checker/tests/function_arg_mutable_err.out | 6 + .../v/checker/tests/function_arg_mutable_err.vv | 8 + .../v/checker/tests/function_arg_redefinition.out | 5 + .../v/checker/tests/function_arg_redefinition.vv | 7 + .../tests/function_count_of_args_mismatch_err.out | 27 + .../tests/function_count_of_args_mismatch_err.vv | 12 + .../checker/tests/function_missing_return_type.out | 12 + .../checker/tests/function_missing_return_type.vv | 22 + .../function_variadic_arg_array_decompose.out | 6 + .../tests/function_variadic_arg_array_decompose.vv | 12 + .../v/checker/tests/function_wrong_arg_type.out | 7 + .../v/checker/tests/function_wrong_arg_type.vv | 9 + .../v/checker/tests/function_wrong_return_type.out | 6 + .../v/checker/tests/function_wrong_return_type.vv | 8 + .../generic_fn_decl_without_generic_names_err.out | 21 + .../generic_fn_decl_without_generic_names_err.vv | 34 + .../tests/generic_param_used_as_an_array_err.out | 6 + .../tests/generic_param_used_as_an_array_err.vv | 12 + .../tests/generic_sumtype_invalid_variant.out | 14 + .../tests/generic_sumtype_invalid_variant.vv | 11 + .../tests/generics_fn_called_arg_mismatch.out | 27 + .../tests/generics_fn_called_arg_mismatch.vv | 10 + .../generics_fn_called_multi_args_mismatch.out | 14 + .../generics_fn_called_multi_args_mismatch.vv | 17 + .../tests/generics_fn_called_no_arg_err.out | 7 + .../checker/tests/generics_fn_called_no_arg_err.vv | 15 + .../generics_fn_called_outside_of_generic_fn.out | 6 + .../generics_fn_called_outside_of_generic_fn.vv | 5 + .../generics_fn_called_variadic_arg_mismatch.out | 7 + .../generics_fn_called_variadic_arg_mismatch.vv | 14 + .../generics_fn_return_generic_struct_err.out | 7 + .../tests/generics_fn_return_generic_struct_err.vv | 18 + .../tests/generics_inst_non_generic_struct_err.out | 1 + .../tests/generics_inst_non_generic_struct_err.vv | 6 + .../tests/generics_method_receiver_type_err.out | 7 + .../tests/generics_method_receiver_type_err.vv | 16 + ...cs_non_generic_fn_called_like_a_generic_one.out | 7 + ...ics_non_generic_fn_called_like_a_generic_one.vv | 6 + .../tests/generics_struct_declaration_err.out | 7 + .../tests/generics_struct_declaration_err.vv | 24 + .../v/checker/tests/generics_struct_init_err.out | 14 + .../v/checker/tests/generics_struct_init_err.vv | 69 + .../checker/tests/generics_too_many_parameters.out | 6 + .../checker/tests/generics_too_many_parameters.vv | 7 + .../v/checker/tests/generics_type_ambiguous.out | 6 + .../v/checker/tests/generics_type_ambiguous.vv | 8 + .../v/checker/tests/globals/assign_no_value.out | 3 + .../v/checker/tests/globals/assign_no_value.vv | 1 + .../tests/globals/incorrect_name_global.out | 3 + .../checker/tests/globals/incorrect_name_global.vv | 1 + .../v/vlib/v/checker/tests/globals/no_type.out | 3 + .../v/vlib/v/checker/tests/globals/no_type.vv | 1 + .../v/checker/tests/globals/unexpected_eof.out | 3 + .../vlib/v/checker/tests/globals/unexpected_eof.vv | 2 + .../v/vlib/v/checker/tests/globals/unknown_typ.out | 11 + .../v/vlib/v/checker/tests/globals/unknown_typ.vv | 4 + v_windows/v/vlib/v/checker/tests/globals_error.out | 6 + .../v/vlib/v/checker/tests/globals_error.run.out | 1 + v_windows/v/vlib/v/checker/tests/globals_error.vv | 12 + .../globals_run/function_stored_in_global.run.out | 4 + .../tests/globals_run/function_stored_in_global.vv | 20 + .../global_array_indexed_by_global_fn.run.out | 3 + .../global_array_indexed_by_global_fn.vv | 40 + v_windows/v/vlib/v/checker/tests/go_expr.out | 5 + v_windows/v/vlib/v/checker/tests/go_expr.vv | 3 + v_windows/v/vlib/v/checker/tests/go_mut_arg.out | 6 + v_windows/v/vlib/v/checker/tests/go_mut_arg.vv | 15 + .../v/vlib/v/checker/tests/go_mut_receiver.out | 6 + .../v/vlib/v/checker/tests/go_mut_receiver.vv | 15 + v_windows/v/vlib/v/checker/tests/go_wait_or.out | 69 + v_windows/v/vlib/v/checker/tests/go_wait_or.vv | 41 + v_windows/v/vlib/v/checker/tests/goto_label.out | 63 + v_windows/v/vlib/v/checker/tests/goto_label.vv | 33 + .../v/checker/tests/hex_lit_without_digit_err.out | 5 + .../v/checker/tests/hex_lit_without_digit_err.vv | 3 + .../v/checker/tests/hex_lit_wrong_digit_err.out | 5 + .../v/checker/tests/hex_lit_wrong_digit_err.vv | 3 + .../vlib/v/checker/tests/hex_literal_overflow.out | 18 + .../v/vlib/v/checker/tests/hex_literal_overflow.vv | 6 + .../v/checker/tests/ierror_in_return_tuple.out | 12 + .../vlib/v/checker/tests/ierror_in_return_tuple.vv | 7 + .../v/vlib/v/checker/tests/if_expr_last_stmt.out | 14 + .../v/vlib/v/checker/tests/if_expr_last_stmt.vv | 7 + .../v/vlib/v/checker/tests/if_expr_mismatch.out | 6 + .../v/vlib/v/checker/tests/if_expr_mismatch.vv | 4 + .../v/vlib/v/checker/tests/if_expr_no_else.out | 5 + .../v/vlib/v/checker/tests/if_expr_no_else.vv | 3 + .../vlib/v/checker/tests/if_expr_optional_err.out | 7 + .../v/vlib/v/checker/tests/if_expr_optional_err.vv | 10 + v_windows/v/vlib/v/checker/tests/if_match_expr.out | 42 + v_windows/v/vlib/v/checker/tests/if_match_expr.vv | 27 + .../v/vlib/v/checker/tests/if_match_expr_err.out | 7 + .../v/vlib/v/checker/tests/if_match_expr_err.vv | 13 + .../v/vlib/v/checker/tests/if_match_result.out | 34 + .../v/vlib/v/checker/tests/if_match_result.vv | 19 + .../v/vlib/v/checker/tests/if_non_bool_cond.out | 20 + .../v/vlib/v/checker/tests/if_non_bool_cond.vv | 13 + v_windows/v/vlib/v/checker/tests/immutable_arg.out | 6 + v_windows/v/vlib/v/checker/tests/immutable_arg.vv | 14 + .../checker/tests/immutable_array_field_assign.out | 6 + .../checker/tests/immutable_array_field_assign.vv | 10 + .../checker/tests/immutable_array_field_shift.out | 6 + .../v/checker/tests/immutable_array_field_shift.vv | 15 + .../tests/immutable_array_struct_assign.out | 6 + .../checker/tests/immutable_array_struct_assign.vv | 9 + .../checker/tests/immutable_array_struct_shift.out | 6 + .../checker/tests/immutable_array_struct_shift.vv | 9 + .../v/vlib/v/checker/tests/immutable_array_var.out | 6 + .../v/vlib/v/checker/tests/immutable_array_var.vv | 4 + .../v/checker/tests/immutable_builtin_modify.out | 11 + .../v/checker/tests/immutable_builtin_modify.vv | 5 + .../v/vlib/v/checker/tests/immutable_field.out | 6 + .../v/vlib/v/checker/tests/immutable_field.vv | 9 + .../v/checker/tests/immutable_field_postfix.out | 13 + .../v/checker/tests/immutable_field_postfix.vv | 9 + .../v/checker/tests/immutable_interface_field.out | 7 + .../v/checker/tests/immutable_interface_field.vv | 16 + v_windows/v/vlib/v/checker/tests/immutable_map.out | 27 + v_windows/v/vlib/v/checker/tests/immutable_map.vv | 7 + v_windows/v/vlib/v/checker/tests/immutable_rec.out | 6 + v_windows/v/vlib/v/checker/tests/immutable_rec.vv | 14 + .../v/checker/tests/immutable_struct_postfix.out | 13 + .../v/checker/tests/immutable_struct_postfix.vv | 10 + v_windows/v/vlib/v/checker/tests/immutable_var.out | 7 + v_windows/v/vlib/v/checker/tests/immutable_var.vv | 5 + .../vlib/v/checker/tests/immutable_var_postfix.out | 13 + .../vlib/v/checker/tests/immutable_var_postfix.vv | 5 + .../vlib/v/checker/tests/import_duplicate_err.out | 6 + .../v/vlib/v/checker/tests/import_duplicate_err.vv | 5 + .../v/vlib/v/checker/tests/import_middle_err.out | 7 + .../v/vlib/v/checker/tests/import_middle_err.vv | 8 + .../vlib/v/checker/tests/import_mod_as_mod_err.out | 5 + .../vlib/v/checker/tests/import_mod_as_mod_err.vv | 5 + .../v/checker/tests/import_mod_sub_as_sub_err.out | 5 + .../v/checker/tests/import_mod_sub_as_sub_err.vv | 5 + .../checker/tests/import_multiple_modules_err.out | 5 + .../v/checker/tests/import_multiple_modules_err.vv | 4 + .../vlib/v/checker/tests/import_not_found_err.out | 5 + .../v/vlib/v/checker/tests/import_not_found_err.vv | 4 + .../v/checker/tests/import_not_same_line_err.out | 6 + .../v/checker/tests/import_not_same_line_err.vv | 5 + .../v/vlib/v/checker/tests/import_symbol_empty.out | 3 + .../v/vlib/v/checker/tests/import_symbol_empty.vv | 1 + .../vlib/v/checker/tests/import_symbol_fn_err.out | 11 + .../v/vlib/v/checker/tests/import_symbol_fn_err.vv | 4 + .../vlib/v/checker/tests/import_symbol_invalid.out | 3 + .../vlib/v/checker/tests/import_symbol_invalid.vv | 1 + .../v/checker/tests/import_symbol_private_err.out | 60 + .../v/checker/tests/import_symbol_private_err.vv | 12 + .../v/checker/tests/import_symbol_type_err.out | 12 + .../vlib/v/checker/tests/import_symbol_type_err.vv | 4 + .../v/checker/tests/import_symbol_unclosed.out | 3 + .../vlib/v/checker/tests/import_symbol_unclosed.vv | 1 + .../v/vlib/v/checker/tests/import_syntax_err.out | 5 + .../v/vlib/v/checker/tests/import_syntax_err.vv | 4 + .../vlib/v/checker/tests/import_unused_warning.out | 5 + .../vlib/v/checker/tests/import_unused_warning.vv | 4 + .../v/vlib/v/checker/tests/in_mismatch_type.out | 77 + .../v/vlib/v/checker/tests/in_mismatch_type.vv | 44 + .../tests/incorrect_for_in_name_variable.out | 8 + .../tests/incorrect_for_in_name_variable.vv | 6 + .../v/checker/tests/incorrect_name_alias_type.out | 3 + .../v/checker/tests/incorrect_name_alias_type.vv | 1 + .../vlib/v/checker/tests/incorrect_name_const.out | 0 .../v/vlib/v/checker/tests/incorrect_name_const.vv | 3 + .../v/vlib/v/checker/tests/incorrect_name_enum.out | 5 + .../v/vlib/v/checker/tests/incorrect_name_enum.vv | 4 + .../v/checker/tests/incorrect_name_enum_field.out | 6 + .../v/checker/tests/incorrect_name_enum_field.vv | 5 + .../v/checker/tests/incorrect_name_fn_type.out | 3 + .../vlib/v/checker/tests/incorrect_name_fn_type.vv | 1 + .../v/checker/tests/incorrect_name_function.out | 3 + .../v/checker/tests/incorrect_name_function.vv | 1 + .../v/checker/tests/incorrect_name_interface.out | 3 + .../v/checker/tests/incorrect_name_interface.vv | 1 + .../tests/incorrect_name_interface_method.out | 5 + .../tests/incorrect_name_interface_method.vv | 3 + .../vlib/v/checker/tests/incorrect_name_module.out | 3 + .../vlib/v/checker/tests/incorrect_name_module.vv | 1 + .../vlib/v/checker/tests/incorrect_name_struct.out | 3 + .../vlib/v/checker/tests/incorrect_name_struct.vv | 1 + .../checker/tests/incorrect_name_struct_field.out | 5 + .../v/checker/tests/incorrect_name_struct_field.vv | 3 + .../v/checker/tests/incorrect_name_sum_type.out | 12 + .../v/checker/tests/incorrect_name_sum_type.vv | 6 + .../v/checker/tests/incorrect_name_variable.out | 6 + .../v/checker/tests/incorrect_name_variable.vv | 4 + v_windows/v/vlib/v/checker/tests/index_expr.out | 69 + v_windows/v/vlib/v/checker/tests/index_expr.vv | 21 + v_windows/v/vlib/v/checker/tests/infix_err.out | 81 + v_windows/v/vlib/v/checker/tests/infix_err.vv | 24 + .../v/checker/tests/int_modulo_by_zero_err.out | 5 + .../vlib/v/checker/tests/int_modulo_by_zero_err.vv | 3 + .../tests/interface_implementing_interface.out | 6 + .../tests/interface_implementing_interface.vv | 16 + ...interface_implementing_own_interface_method.out | 7 + .../interface_implementing_own_interface_method.vv | 7 + .../v/vlib/v/checker/tests/interface_init_err.out | 6 + .../v/vlib/v/checker/tests/interface_init_err.vv | 16 + .../tests/interface_return_parameter_err.out | 14 + .../tests/interface_return_parameter_err.vv | 4 + .../tests/interface_too_many_embedding_levels.out | 7 + .../tests/interface_too_many_embedding_levels.vv | 431 + .../tests/interpolation_recursive_str_err.out | 7 + .../tests/interpolation_recursive_str_err.vv | 15 + .../v/vlib/v/checker/tests/invalid_char_err.out | 3 + .../v/vlib/v/checker/tests/invalid_char_err.vv | 1 + .../v/vlib/v/checker/tests/invalid_property.out | 18 + .../v/vlib/v/checker/tests/invalid_property.vv | 6 + .../v/checker/tests/invalid_vweb_param_type.out | 7 + .../v/checker/tests/invalid_vweb_param_type.vv | 12 + .../tests/invert_other_types_bits_error.out | 26 + .../checker/tests/invert_other_types_bits_error.vv | 6 + .../v/vlib/v/checker/tests/is_type_invalid.out | 14 + .../v/vlib/v/checker/tests/is_type_invalid.vv | 21 + .../v/vlib/v/checker/tests/is_type_not_exist.out | 7 + .../v/vlib/v/checker/tests/is_type_not_exist.vv | 11 + .../v/checker/tests/labelled_break_continue.out | 35 + .../v/checker/tests/labelled_break_continue.vv | 26 + .../v/vlib/v/checker/tests/lock_already_locked.out | 13 + .../v/vlib/v/checker/tests/lock_already_locked.vv | 16 + .../vlib/v/checker/tests/lock_already_rlocked.out | 13 + .../v/vlib/v/checker/tests/lock_already_rlocked.vv | 16 + v_windows/v/vlib/v/checker/tests/lock_const.out | 7 + v_windows/v/vlib/v/checker/tests/lock_const.vv | 11 + v_windows/v/vlib/v/checker/tests/lock_needed.out | 27 + v_windows/v/vlib/v/checker/tests/lock_needed.vv | 31 + .../v/vlib/v/checker/tests/lock_nonshared.out | 7 + v_windows/v/vlib/v/checker/tests/lock_nonshared.vv | 14 + .../v/vlib/v/checker/tests/main_and_script_err.out | 5 + .../v/vlib/v/checker/tests/main_and_script_err.vv | 4 + v_windows/v/vlib/v/checker/tests/main_args_err.out | 5 + v_windows/v/vlib/v/checker/tests/main_args_err.vv | 3 + .../v/vlib/v/checker/tests/main_called_err.out | 5 + .../v/vlib/v/checker/tests/main_called_err.vv | 3 + .../v/vlib/v/checker/tests/main_no_body_err.out | 3 + .../v/vlib/v/checker/tests/main_no_body_err.vv | 1 + .../v/vlib/v/checker/tests/main_return_err.out | 5 + .../v/vlib/v/checker/tests/main_return_err.vv | 3 + v_windows/v/vlib/v/checker/tests/map_delete.out | 20 + v_windows/v/vlib/v/checker/tests/map_delete.vv | 11 + .../v/checker/tests/map_func_void_return_err.out | 6 + .../v/checker/tests/map_func_void_return_err.vv | 7 + .../v/checker/tests/map_init_invalid_syntax.out | 6 + .../v/checker/tests/map_init_invalid_syntax.vv | 4 + .../v/checker/tests/map_init_key_duplicate_err.out | 13 + .../v/checker/tests/map_init_key_duplicate_err.vv | 10 + .../v/vlib/v/checker/tests/map_init_wrong_type.out | 21 + .../v/vlib/v/checker/tests/map_init_wrong_type.vv | 7 + v_windows/v/vlib/v/checker/tests/map_ops.out | 20 + v_windows/v/vlib/v/checker/tests/map_ops.vv | 6 + .../v/vlib/v/checker/tests/map_unknown_value.out | 5 + .../v/vlib/v/checker/tests/map_unknown_value.vv | 3 + .../vlib/v/checker/tests/match_alias_type_err.out | 7 + .../v/vlib/v/checker/tests/match_alias_type_err.vv | 16 + .../v/checker/tests/match_duplicate_branch.out | 42 + .../vlib/v/checker/tests/match_duplicate_branch.vv | 61 + .../vlib/v/checker/tests/match_else_last_expr.out | 7 + .../v/vlib/v/checker/tests/match_else_last_expr.vv | 7 + .../tests/match_expr_and_expected_type_error.out | 14 + .../tests/match_expr_and_expected_type_error.vv | 11 + .../v/vlib/v/checker/tests/match_expr_else.out | 21 + .../v/vlib/v/checker/tests/match_expr_else.vv | 41 + .../v/checker/tests/match_expr_empty_branch.out | 6 + .../v/checker/tests/match_expr_empty_branch.vv | 4 + .../tests/match_expr_non_void_stmt_last.out | 7 + .../checker/tests/match_expr_non_void_stmt_last.vv | 17 + .../v/vlib/v/checker/tests/match_invalid_type.out | 21 + .../v/vlib/v/checker/tests/match_invalid_type.vv | 29 + .../tests/match_return_mismatch_type_err.out | 7 + .../tests/match_return_mismatch_type_err.vv | 7 + .../checker/tests/match_sumtype_multiple_types.out | 14 + .../checker/tests/match_sumtype_multiple_types.vv | 33 + .../vlib/v/checker/tests/match_undefined_cond.out | 21 + .../v/vlib/v/checker/tests/match_undefined_cond.vv | 10 + .../v/vlib/v/checker/tests/method_array_slice.out | 7 + .../v/vlib/v/checker/tests/method_array_slice.vv | 7 + .../v/checker/tests/method_generic_infer_err.out | 6 + .../v/checker/tests/method_generic_infer_err.vv | 10 + .../v/vlib/v/checker/tests/method_op_alias_err.out | 42 + .../v/vlib/v/checker/tests/method_op_alias_err.vv | 18 + v_windows/v/vlib/v/checker/tests/method_op_err.out | 69 + v_windows/v/vlib/v/checker/tests/method_op_err.vv | 40 + .../vlib/v/checker/tests/method_wrong_arg_type.out | 13 + .../vlib/v/checker/tests/method_wrong_arg_type.vv | 19 + .../v/checker/tests/minus_op_wrong_type_err.out | 62 + .../v/checker/tests/minus_op_wrong_type_err.vv | 20 + .../vlib/v/checker/tests/mismatched_ptr_op_ptr.out | 14 + .../vlib/v/checker/tests/mismatched_ptr_op_ptr.vv | 8 + .../v/checker/tests/missing_c_lib_header_1.out | 1 + .../vlib/v/checker/tests/missing_c_lib_header_1.vv | 6 + .../missing_c_lib_header_with_explanation_2.out | 1 + .../missing_c_lib_header_with_explanation_2.vv | 6 + .../vlib/v/checker/tests/mod_op_wrong_type_err.out | 56 + .../vlib/v/checker/tests/mod_op_wrong_type_err.vv | 13 + .../vlib/v/checker/tests/modify_const_with_ref.out | 14 + .../vlib/v/checker/tests/modify_const_with_ref.vv | 13 + .../checker/tests/module_not_at_same_line_err.out | 6 + .../v/checker/tests/module_not_at_same_line_err.vv | 5 + .../module_alias_started_with_underscore.out | 7 + .../module_alias_started_with_underscore/main.v | 7 + .../underscore.v | 5 + .../checker/tests/modules/overload_return_type.out | 6 + .../tests/modules/overload_return_type/main.v | 15 + .../tests/modules/overload_return_type/point.v | 11 + .../vlib/v/checker/tests/mul_op_wrong_type_err.out | 57 + .../vlib/v/checker/tests/mul_op_wrong_type_err.vv | 17 + .../tests/multi_const_field_name_duplicate_err.out | 6 + .../tests/multi_const_field_name_duplicate_err.vv | 5 + .../v/vlib/v/checker/tests/multi_names_err.out | 5 + .../v/vlib/v/checker/tests/multi_names_err.vv | 3 + .../v/checker/tests/multi_value_method_err.out | 3 + .../vlib/v/checker/tests/multi_value_method_err.vv | 1 + .../v/checker/tests/multiple_pointer_yield_err.out | 6 + .../v/checker/tests/multiple_pointer_yield_err.vv | 10 + v_windows/v/vlib/v/checker/tests/mut_arg.out | 25 + v_windows/v/vlib/v/checker/tests/mut_arg.vv | 11 + .../v/vlib/v/checker/tests/mut_args_warning.out | 5 + .../v/vlib/v/checker/tests/mut_args_warning.vv | 5 + .../tests/mut_array_get_element_address_err.out | 7 + .../tests/mut_array_get_element_address_err.vv | 5 + v_windows/v/vlib/v/checker/tests/mut_int.out | 6 + v_windows/v/vlib/v/checker/tests/mut_int.vv | 5 + .../tests/mut_map_get_value_address_err.out | 7 + .../checker/tests/mut_map_get_value_address_err.vv | 5 + v_windows/v/vlib/v/checker/tests/mut_receiver.out | 14 + v_windows/v/vlib/v/checker/tests/mut_receiver.vv | 13 + .../v/vlib/v/checker/tests/mut_receiver_lit.out | 5 + .../v/vlib/v/checker/tests/mut_receiver_lit.vv | 10 + .../checker/tests/negative_assign_to_unsigned.out | 7 + .../v/checker/tests/negative_assign_to_unsigned.vv | 5 + .../v/vlib/v/checker/tests/nested_aliases.out | 4 + v_windows/v/vlib/v/checker/tests/nested_aliases.vv | 2 + .../v/vlib/v/checker/tests/no_heap_struct.out | 21 + v_windows/v/vlib/v/checker/tests/no_heap_struct.vv | 25 + .../checker/tests/no_interface_instantiation_a.out | 6 + .../checker/tests/no_interface_instantiation_a.vv | 5 + .../checker/tests/no_interface_instantiation_b.out | 6 + .../checker/tests/no_interface_instantiation_b.vv | 7 + .../checker/tests/no_interface_instantiation_c.out | 7 + .../checker/tests/no_interface_instantiation_c.vv | 11 + .../v/vlib/v/checker/tests/no_interface_str.out | 6 + .../v/vlib/v/checker/tests/no_interface_str.vv | 19 + v_windows/v/vlib/v/checker/tests/no_main_mod.out | 3 + v_windows/v/vlib/v/checker/tests/no_main_mod.vv | 1 + .../v/vlib/v/checker/tests/no_main_println_err.out | 3 + .../v/vlib/v/checker/tests/no_main_println_err.vv | 1 + .../tests/no_method_on_interface_propagation.out | 7 + .../tests/no_method_on_interface_propagation.vv | 20 + .../v/vlib/v/checker/tests/no_pub_in_main.out | 49 + .../tests/no_warning_for_in_mut_var_unused.out | 0 .../tests/no_warning_for_in_mut_var_unused.vv | 7 + .../vlib/v/checker/tests/non_lvalue_as_voidptr.out | 5 + .../vlib/v/checker/tests/non_lvalue_as_voidptr.vv | 5 + .../checker/tests/non_matching_functional_args.out | 16 + .../checker/tests/non_matching_functional_args.vv | 33 + .../v/vlib/v/checker/tests/none_type_cast_err.out | 75 + .../v/vlib/v/checker/tests/none_type_cast_err.vv | 13 + .../tests/noreturn_with_non_empty_loop_at_end.out | 13 + .../tests/noreturn_with_non_empty_loop_at_end.vv | 19 + .../vlib/v/checker/tests/noreturn_with_return.out | 19 + .../v/vlib/v/checker/tests/noreturn_with_return.vv | 19 + ...urn_without_loop_or_another_noreturn_at_end.out | 13 + ...turn_without_loop_or_another_noreturn_at_end.vv | 16 + .../v/checker/tests/oct_lit_without_digit_err.out | 5 + .../v/checker/tests/oct_lit_without_digit_err.vv | 3 + .../v/checker/tests/oct_lit_wrong_digit_err.out | 5 + .../v/checker/tests/oct_lit_wrong_digit_err.vv | 3 + .../v/vlib/v/checker/tests/optional_fn_err.out | 168 + .../v/vlib/v/checker/tests/optional_fn_err.vv | 76 + .../checker/tests/optional_in_println_mismatch.out | 6 + .../checker/tests/optional_in_println_mismatch.vv | 7 + .../checker/tests/optional_interface_mismatch.out | 6 + .../v/checker/tests/optional_interface_mismatch.vv | 12 + .../v/checker/tests/optional_or_block_mismatch.out | 7 + .../v/checker/tests/optional_or_block_mismatch.vv | 12 + .../v/checker/tests/optional_or_block_none_err.out | 7 + .../v/checker/tests/optional_or_block_none_err.vv | 22 + ...or_block_returns_value_of_incompatible_type.out | 7 + ..._or_block_returns_value_of_incompatible_type.vv | 16 + .../v/checker/tests/optional_propagate_nested.out | 14 + .../v/checker/tests/optional_propagate_nested.vv | 31 + .../v/checker/tests/optional_type_call_err.out | 6 + .../vlib/v/checker/tests/optional_type_call_err.vv | 5 + v_windows/v/vlib/v/checker/tests/or_err.out | 14 + v_windows/v/vlib/v/checker/tests/or_err.vv | 13 + .../v/checker/tests/or_expr_types_mismatch.out | 14 + .../vlib/v/checker/tests/or_expr_types_mismatch.vv | 17 + .../v/vlib/v/checker/tests/orm_empty_struct.out | 7 + .../v/vlib/v/checker/tests/orm_empty_struct.vv | 11 + v_windows/v/vlib/v/checker/tests/os_prefix.out | 18 + v_windows/v/vlib/v/checker/tests/os_prefix.vv | 7 + .../v/vlib/v/checker/tests/overflow_int_err.out | 14 + .../v/vlib/v/checker/tests/overflow_int_err.vv | 10 + .../vlib/v/checker/tests/overload_return_type.out | 6 + .../v/vlib/v/checker/tests/overload_return_type.vv | 15 + .../v/vlib/v/checker/tests/oversized_int_lit.out | 8 + .../v/vlib/v/checker/tests/oversized_int_lit.vv | 2 + v_windows/v/vlib/v/checker/tests/pass_mut_lit.out | 5 + v_windows/v/vlib/v/checker/tests/pass_mut_lit.vv | 10 + .../tests/passing_expr_to_fn_expecting_voidptr.out | 5 + .../tests/passing_expr_to_fn_expecting_voidptr.vv | 3 + v_windows/v/vlib/v/checker/tests/pointer_ops.out | 49 + v_windows/v/vlib/v/checker/tests/pointer_ops.vv | 13 + v_windows/v/vlib/v/checker/tests/prefix_err.out | 95 + v_windows/v/vlib/v/checker/tests/prefix_err.vv | 22 + .../checker/tests/prefix_expr_decl_assign_err.out | 12 + .../v/checker/tests/prefix_expr_decl_assign_err.vv | 4 + v_windows/v/vlib/v/checker/tests/print_char.out | 3 + v_windows/v/vlib/v/checker/tests/print_char.vv | 1 + .../println_can_not_print_void_expressions.out | 27 + .../println_can_not_print_void_expressions.vv | 7 + v_windows/v/vlib/v/checker/tests/ptr_assign.out | 6 + v_windows/v/vlib/v/checker/tests/ptr_assign.vv | 4 + .../tests/receiver_unknown_type_single_letter.out | 4 + .../tests/receiver_unknown_type_single_letter.vv | 2 + .../v/checker/tests/recursive_interface_err.out | 5 + .../v/checker/tests/recursive_interface_err.vv | 3 + .../tests/redundant_parentheses_warning.out | 7 + .../checker/tests/redundant_parentheses_warning.vv | 5 + .../tests/reference_field_must_be_initialized.out | 7 + .../tests/reference_field_must_be_initialized.vv | 10 + .../v/vlib/v/checker/tests/reference_return.out | 7 + .../v/vlib/v/checker/tests/reference_return.vv | 14 + .../vlib/v/checker/tests/return_count_mismatch.out | 34 + .../vlib/v/checker/tests/return_count_mismatch.vv | 24 + .../tests/return_duplicate_with_none_err_a.out | 7 + .../tests/return_duplicate_with_none_err_a.vv | 9 + .../tests/return_duplicate_with_none_err_b.out | 7 + .../tests/return_duplicate_with_none_err_b.vv | 9 + .../v/vlib/v/checker/tests/return_fixed_array.out | 12 + .../v/vlib/v/checker/tests/return_fixed_array.vv | 7 + .../v/checker/tests/return_missing_comp_if.out | 7 + .../vlib/v/checker/tests/return_missing_comp_if.vv | 9 + .../tests/return_missing_comp_if_nested.out | 7 + .../checker/tests/return_missing_comp_if_nested.vv | 17 + .../tests/return_missing_if_else_simple.out | 12 + .../checker/tests/return_missing_if_else_simple.vv | 35 + .../v/checker/tests/return_missing_if_match.out | 7 + .../v/checker/tests/return_missing_if_match.vv | 13 + .../v/checker/tests/return_missing_match_if.out | 7 + .../v/checker/tests/return_missing_match_if.vv | 15 + .../checker/tests/return_missing_match_simple.out | 12 + .../v/checker/tests/return_missing_match_simple.vv | 12 + .../vlib/v/checker/tests/return_missing_nested.out | 7 + .../vlib/v/checker/tests/return_missing_nested.vv | 11 + .../vlib/v/checker/tests/return_missing_simple.out | 7 + .../vlib/v/checker/tests/return_missing_simple.vv | 7 + .../v/checker/tests/return_ref_as_no_ref_bug.out | 7 + .../v/checker/tests/return_ref_as_no_ref_bug.vv | 14 + v_windows/v/vlib/v/checker/tests/return_type.out | 6 + v_windows/v/vlib/v/checker/tests/return_type.vv | 5 + .../v/checker/tests/return_working_comp_if.out | 0 .../vlib/v/checker/tests/return_working_comp_if.vv | 9 + .../tests/return_working_comp_if_nested.out | 0 .../checker/tests/return_working_comp_if_nested.vv | 17 + .../v/checker/tests/return_working_if_match.out | 0 .../v/checker/tests/return_working_if_match.vv | 13 + .../v/checker/tests/return_working_match_if.out | 0 .../v/checker/tests/return_working_match_if.vv | 15 + .../vlib/v/checker/tests/return_working_nested.out | 0 .../vlib/v/checker/tests/return_working_nested.vv | 12 + .../vlib/v/checker/tests/return_working_simple.out | 0 .../vlib/v/checker/tests/return_working_simple.vv | 9 + .../vlib/v/checker/tests/return_working_two_if.out | 0 .../vlib/v/checker/tests/return_working_two_if.vv | 17 + .../vlib/v/checker/tests/return_working_unsafe.out | 0 .../vlib/v/checker/tests/return_working_unsafe.vv | 7 + .../checker/tests/returns/return_missing_simple.vv | 7 + .../tests/rshift_op_wrong_left_type_err.out | 5 + .../checker/tests/rshift_op_wrong_left_type_err.vv | 3 + .../tests/rshift_op_wrong_right_type_err.out | 5 + .../tests/rshift_op_wrong_right_type_err.vv | 3 + ...oreturn_fn_can_be_used_instead_of_panic.run.out | 1 + .../noreturn_fn_can_be_used_instead_of_panic.vv | 14 + .../tests/run/unused_variable_warning.run.out | 15 + .../v/checker/tests/run/unused_variable_warning.vv | 6 + .../v/checker/tests/selective_const_import.out | 3 + .../vlib/v/checker/tests/selective_const_import.vv | 1 + .../vlib/v/checker/tests/selector_expr_assign.out | 6 + .../v/vlib/v/checker/tests/selector_expr_assign.vv | 8 + .../v/checker/tests/selector_expr_optional_err.out | 6 + .../v/checker/tests/selector_expr_optional_err.vv | 5 + .../v/vlib/v/checker/tests/shared_bad_args.out | 83 + .../v/vlib/v/checker/tests/shared_bad_args.vv | 81 + .../v/vlib/v/checker/tests/shared_element_lock.out | 27 + .../v/vlib/v/checker/tests/shared_element_lock.vv | 46 + v_windows/v/vlib/v/checker/tests/shared_lock.out | 21 + v_windows/v/vlib/v/checker/tests/shared_lock.vv | 23 + .../vlib/v/checker/tests/shared_type_mismatch.out | 7 + .../v/vlib/v/checker/tests/shared_type_mismatch.vv | 18 + .../checker/tests/shift_op_wrong_left_type_err.out | 5 + .../checker/tests/shift_op_wrong_left_type_err.vv | 3 + .../tests/shift_op_wrong_right_type_err.out | 5 + .../checker/tests/shift_op_wrong_right_type_err.vv | 3 + .../v/checker/tests/short_struct_wrong_number.out | 13 + .../v/checker/tests/short_struct_wrong_number.vv | 9 + .../v/vlib/v/checker/tests/slice_reassignment.out | 7 + .../v/vlib/v/checker/tests/slice_reassignment.vv | 5 + .../sort_method_called_on_immutable_receiver.out | 13 + .../sort_method_called_on_immutable_receiver.vv | 9 + .../tests/static_vars_in_translated_mode.out | 6 + .../tests/static_vars_in_translated_mode.vv | 12 + .../v/vlib/v/checker/tests/store_string_err.out | 6 + .../v/vlib/v/checker/tests/store_string_err.vv | 6 + .../v/checker/tests/str_method_0_arguments.out | 7 + .../vlib/v/checker/tests/str_method_0_arguments.vv | 11 + .../v/checker/tests/str_method_return_string.out | 7 + .../v/checker/tests/str_method_return_string.vv | 11 + .../vlib/v/checker/tests/string_char_null_err.out | 5 + .../v/vlib/v/checker/tests/string_char_null_err.vv | 3 + .../vlib/v/checker/tests/string_escape_u_err_a.out | 5 + .../vlib/v/checker/tests/string_escape_u_err_a.vv | 3 + .../vlib/v/checker/tests/string_escape_u_err_b.out | 5 + .../vlib/v/checker/tests/string_escape_u_err_b.vv | 3 + .../vlib/v/checker/tests/string_escape_x_err_a.out | 5 + .../vlib/v/checker/tests/string_escape_x_err_a.vv | 3 + .../vlib/v/checker/tests/string_escape_x_err_b.out | 5 + .../vlib/v/checker/tests/string_escape_x_err_b.vv | 3 + .../v/checker/tests/string_index_assign_error.out | 7 + .../v/checker/tests/string_index_assign_error.vv | 4 + .../v/checker/tests/string_index_non_int_err.out | 20 + .../v/checker/tests/string_index_non_int_err.vv | 6 + .../tests/string_interpolation_invalid_fmt.out | 7 + .../tests/string_interpolation_invalid_fmt.vv | 5 + .../tests/string_interpolation_wrong_fmt.out | 63 + .../tests/string_interpolation_wrong_fmt.vv | 24 + .../tests/struct_assigned_to_pointer_to_struct.out | 7 + .../tests/struct_assigned_to_pointer_to_struct.vv | 7 + .../tests/struct_cast_to_struct_generic_err.out | 6 + .../tests/struct_cast_to_struct_generic_err.vv | 12 + .../tests/struct_cast_to_struct_mut_err_a.out | 6 + .../tests/struct_cast_to_struct_mut_err_a.vv | 13 + .../tests/struct_cast_to_struct_mut_err_b.out | 6 + .../tests/struct_cast_to_struct_mut_err_b.vv | 13 + .../tests/struct_cast_to_struct_pub_err_a.out | 6 + .../tests/struct_cast_to_struct_pub_err_a.vv | 13 + .../tests/struct_cast_to_struct_pub_err_b.out | 6 + .../tests/struct_cast_to_struct_pub_err_b.vv | 13 + .../v/checker/tests/struct_embed_invalid_type.out | 6 + .../v/checker/tests/struct_embed_invalid_type.vv | 5 + .../tests/struct_field_name_duplicate_err.out | 6 + .../tests/struct_field_name_duplicate_err.vv | 4 + .../vlib/v/checker/tests/struct_field_type_err.out | 43 + .../vlib/v/checker/tests/struct_field_type_err.vv | 20 + .../checker/tests/struct_init_update_type_err.out | 35 + .../v/checker/tests/struct_init_update_type_err.vv | 35 + .../v/vlib/v/checker/tests/struct_pub_field.out | 6 + .../v/vlib/v/checker/tests/struct_pub_field.vv | 10 + .../vlib/v/checker/tests/struct_required_field.out | 7 + .../vlib/v/checker/tests/struct_required_field.vv | 16 + .../v/checker/tests/struct_required_fn_field.out | 7 + .../v/checker/tests/struct_required_fn_field.vv | 16 + .../vlib/v/checker/tests/struct_type_cast_err.out | 63 + .../v/vlib/v/checker/tests/struct_type_cast_err.vv | 17 + .../vlib/v/checker/tests/struct_unknown_field.out | 7 + .../v/vlib/v/checker/tests/struct_unknown_field.vv | 11 + .../v/checker/tests/struct_unneeded_default.out | 20 + .../v/checker/tests/struct_unneeded_default.vv | 8 + v_windows/v/vlib/v/checker/tests/sum.out | 28 + v_windows/v/vlib/v/checker/tests/sum.vv | 14 + .../tests/sum_type_assign_non_variant_err.out | 7 + .../tests/sum_type_assign_non_variant_err.vv | 13 + .../tests/sum_type_common_fields_alias_error.out | 20 + .../tests/sum_type_common_fields_alias_error.vv | 38 + .../checker/tests/sum_type_common_fields_error.out | 6 + .../checker/tests/sum_type_common_fields_error.vv | 54 + .../v/vlib/v/checker/tests/sum_type_exists.out | 3 + .../v/vlib/v/checker/tests/sum_type_exists.vv | 1 + .../v/vlib/v/checker/tests/sum_type_infix_err.out | 14 + .../v/vlib/v/checker/tests/sum_type_infix_err.vv | 8 + .../tests/sum_type_multiple_type_define.out | 5 + .../checker/tests/sum_type_multiple_type_define.vv | 3 + .../v/checker/tests/sum_type_mutable_cast_err.out | 14 + .../v/checker/tests/sum_type_mutable_cast_err.vv | 21 + .../v/checker/tests/sum_type_ref_variant_err.out | 18 + .../v/checker/tests/sum_type_ref_variant_err.vv | 9 + .../v/checker/tests/sumtype_in_sumtype_err.out | 3 + .../vlib/v/checker/tests/sumtype_in_sumtype_err.vv | 1 + .../tests/sumtype_mismatch_of_aggregate_err.out | 7 + .../tests/sumtype_mismatch_of_aggregate_err.vv | 13 + .../v/checker/tests/sumtype_mismatched_type.out | 5 + .../v/checker/tests/sumtype_mismatched_type.vv | 4 + .../v/vlib/v/checker/tests/templates/index.html | 1 + .../test_functions_should_not_return_test.out | 7 + .../tests/test_functions_should_not_return_test.vv | 17 + .../v/checker/tests/trailing_comma_struct_attr.out | 6 + .../v/checker/tests/trailing_comma_struct_attr.vv | 4 + .../v/checker/tests/type_cast_optional_err.out | 5 + .../vlib/v/checker/tests/type_cast_optional_err.vv | 3 + .../v/checker/tests/typedef_attr_v_struct_err.out | 6 + .../v/checker/tests/typedef_attr_v_struct_err.vv | 7 + .../v/checker/tests/undefined_ident_of_struct.out | 7 + .../v/checker/tests/undefined_ident_of_struct.vv | 9 + v_windows/v/vlib/v/checker/tests/unexpected_or.out | 6 + v_windows/v/vlib/v/checker/tests/unexpected_or.vv | 7 + .../v/checker/tests/unexpected_or_propagate.out | 7 + .../v/checker/tests/unexpected_or_propagate.vv | 12 + .../v/vlib/v/checker/tests/unfinished_string.out | 2 + .../v/vlib/v/checker/tests/unfinished_string.vv | 1 + .../v/checker/tests/unimplemented_interface_a.out | 6 + .../v/checker/tests/unimplemented_interface_a.vv | 11 + .../v/checker/tests/unimplemented_interface_b.out | 7 + .../v/checker/tests/unimplemented_interface_b.vv | 14 + .../v/checker/tests/unimplemented_interface_c.out | 7 + .../v/checker/tests/unimplemented_interface_c.vv | 13 + .../v/checker/tests/unimplemented_interface_d.out | 7 + .../v/checker/tests/unimplemented_interface_d.vv | 13 + .../v/checker/tests/unimplemented_interface_e.out | 15 + .../v/checker/tests/unimplemented_interface_e.vv | 14 + .../v/checker/tests/unimplemented_interface_f.out | 7 + .../v/checker/tests/unimplemented_interface_f.vv | 12 + .../v/checker/tests/unimplemented_interface_g.out | 7 + .../tests/unimplemented_interface_g.vv.disabled | 14 + .../v/checker/tests/unimplemented_interface_h.out | 6 + .../v/checker/tests/unimplemented_interface_h.vv | 10 + .../v/checker/tests/unimplemented_interface_i.out | 6 + .../v/checker/tests/unimplemented_interface_i.vv | 12 + .../v/checker/tests/unimplemented_interface_j.out | 6 + .../v/checker/tests/unimplemented_interface_j.vv | 13 + .../v/vlib/v/checker/tests/union_unsafe_fields.out | 13 + .../v/vlib/v/checker/tests/union_unsafe_fields.vv | 12 + .../checker/tests/unknown_array_element_type_b.out | 7 + .../checker/tests/unknown_array_element_type_b.vv | 9 + .../v/vlib/v/checker/tests/unknown_as_type.out | 8 + .../v/vlib/v/checker/tests/unknown_as_type.vv | 13 + .../vlib/v/checker/tests/unknown_comptime_expr.out | 42 + .../vlib/v/checker/tests/unknown_comptime_expr.vv | 22 + v_windows/v/vlib/v/checker/tests/unknown_field.out | 6 + v_windows/v/vlib/v/checker/tests/unknown_field.vv | 8 + .../vlib/v/checker/tests/unknown_generic_type.out | 7 + .../v/vlib/v/checker/tests/unknown_generic_type.vv | 8 + .../v/vlib/v/checker/tests/unknown_method.out | 6 + v_windows/v/vlib/v/checker/tests/unknown_method.vv | 8 + .../checker/tests/unknown_method_suggest_name.out | 22 + .../v/checker/tests/unknown_method_suggest_name.vv | 31 + .../v/checker/tests/unknown_sizeof_type_err_a.out | 6 + .../v/checker/tests/unknown_sizeof_type_err_a.vv | 15 + .../v/checker/tests/unknown_sizeof_type_err_b.out | 6 + .../v/checker/tests/unknown_sizeof_type_err_b.vv | 15 + .../tests/unknown_struct_field_suggest_name.out | 15 + .../tests/unknown_struct_field_suggest_name.vv | 14 + .../v/vlib/v/checker/tests/unknown_struct_name.out | 6 + .../v/vlib/v/checker/tests/unknown_struct_name.vv | 5 + .../v/vlib/v/checker/tests/unknown_var_assign.out | 5 + .../v/vlib/v/checker/tests/unknown_var_assign.vv | 3 + .../v/checker/tests/unnecessary_parenthesis.out | 20 + .../v/checker/tests/unnecessary_parenthesis.vv | 9 + .../v/vlib/v/checker/tests/unreachable_code.out | 7 + .../v/vlib/v/checker/tests/unreachable_code.vv | 8 + .../tests/unsafe_c_calls_should_be_checked.out | 13 + .../tests/unsafe_c_calls_should_be_checked.vv | 6 + .../v/checker/tests/unsafe_fixed_array_assign.out | 7 + .../v/checker/tests/unsafe_fixed_array_assign.vv | 10 + ...unsafe_pointer_arithmetic_should_be_checked.out | 28 + .../unsafe_pointer_arithmetic_should_be_checked.vv | 15 + .../v/vlib/v/checker/tests/unsafe_required.out | 20 + .../v/vlib/v/checker/tests/unsafe_required.vv | 21 + .../v/checker/tests/unwrapped_optional_infix.out | 6 + .../v/checker/tests/unwrapped_optional_infix.vv | 5 + .../tests/use_deprecated_function_warning.out | 20 + .../tests/use_deprecated_function_warning.vv | 24 + .../v/vlib/v/checker/tests/var_duplicate_const.out | 7 + .../v/vlib/v/checker/tests/var_duplicate_const.vv | 6 + .../v/vlib/v/checker/tests/var_eval_not_used.out | 6 + .../v/vlib/v/checker/tests/var_eval_not_used.vv | 7 + .../v/checker/tests/var_eval_not_used_scope.out | 7 + .../v/checker/tests/var_eval_not_used_scope.vv | 9 + .../checker/tests/var_used_before_declaration.out | 6 + .../v/checker/tests/var_used_before_declaration.vv | 5 + .../v/vlib/v/checker/tests/void_fn_as_value.out | 7 + .../v/vlib/v/checker/tests/void_fn_as_value.vv | 9 + .../tests/void_function_assign_to_string.out | 7 + .../tests/void_function_assign_to_string.vv | 8 + .../v/vlib/v/checker/tests/void_optional_err.out | 5 + .../v/vlib/v/checker/tests/void_optional_err.vv | 7 + .../v/vlib/v/checker/tests/vweb_routing_checks.out | 14 + .../v/vlib/v/checker/tests/vweb_routing_checks.vv | 44 + .../v/vlib/v/checker/tests/vweb_tmpl_used_var.out | 1 + .../v/vlib/v/checker/tests/vweb_tmpl_used_var.vv | 14 + .../tests/warnings_for_string_c2v_calls.out | 7 + .../checker/tests/warnings_for_string_c2v_calls.vv | 11 + .../v/checker/tests/wrong_propagate_ret_type.out | 7 + .../v/checker/tests/wrong_propagate_ret_type.vv | 8 + v_windows/v/vlib/v/compiler_errors_test.v | 337 + v_windows/v/vlib/v/depgraph/depgraph.v | 218 + v_windows/v/vlib/v/doc/comment.v | 25 + v_windows/v/vlib/v/doc/doc.v | 525 + v_windows/v/vlib/v/doc/doc_private_fn_test.v | 47 + v_windows/v/vlib/v/doc/doc_test.v | 18 + v_windows/v/vlib/v/doc/module.v | 90 + v_windows/v/vlib/v/doc/node.v | 70 + v_windows/v/vlib/v/doc/utils.v | 183 + v_windows/v/vlib/v/dotgraph/dotgraph.c.v | 8 + v_windows/v/vlib/v/dotgraph/dotgraph.v | 81 + v_windows/v/vlib/v/embed_file/embed_file.v | 105 + v_windows/v/vlib/v/embed_file/embed_file_test.v | 25 + v_windows/v/vlib/v/embed_file/v.png | Bin 0 -> 603 bytes v_windows/v/vlib/v/errors/errors.v | 38 + v_windows/v/vlib/v/eval/eval.v | 99 + v_windows/v/vlib/v/fmt/align.v | 56 + v_windows/v/vlib/v/fmt/asm.v | 185 + v_windows/v/vlib/v/fmt/attrs.v | 60 + v_windows/v/vlib/v/fmt/comments.v | 141 + v_windows/v/vlib/v/fmt/fmt.v | 2454 +++++ v_windows/v/vlib/v/fmt/fmt_keep_test.v | 115 + v_windows/v/vlib/v/fmt/fmt_test.v | 75 + v_windows/v/vlib/v/fmt/fmt_vlib_test.v | 73 + v_windows/v/vlib/v/fmt/struct.v | 288 + .../v/vlib/v/fmt/tests/anon_fn_as_param_keep.vv | 20 + v_windows/v/vlib/v/fmt/tests/anon_fn_call_keep.vv | 7 + v_windows/v/vlib/v/fmt/tests/anon_fn_expected.vv | 24 + v_windows/v/vlib/v/fmt/tests/anon_fn_input.vv | 23 + .../v/vlib/v/fmt/tests/array_decomposition_keep.vv | 8 + .../v/fmt/tests/array_init_comment_ending_keep.vv | 23 + .../v/fmt/tests/array_init_eol_comments_keep.vv | 104 + .../v/vlib/v/fmt/tests/array_init_expected.vv | 35 + .../v/fmt/tests/array_init_inline_comments_keep.vv | 2 + v_windows/v/vlib/v/fmt/tests/array_init_input.vv | 31 + v_windows/v/vlib/v/fmt/tests/array_init_keep.vv | 9 + .../v/vlib/v/fmt/tests/array_newlines_keep.vv | 17 + .../v/vlib/v/fmt/tests/array_slices_expected.vv | 12 + v_windows/v/vlib/v/fmt/tests/array_slices_input.vv | 14 + v_windows/v/vlib/v/fmt/tests/array_static_keep.vv | 14 + v_windows/v/vlib/v/fmt/tests/assembly/asm_keep.vv | 10 + v_windows/v/vlib/v/fmt/tests/asserts_expected.vv | 5 + v_windows/v/vlib/v/fmt/tests/asserts_input.vv | 5 + v_windows/v/vlib/v/fmt/tests/asserts_keep.vv | 6 + v_windows/v/vlib/v/fmt/tests/attrs_expected.vv | 9 + v_windows/v/vlib/v/fmt/tests/attrs_input.vv | 10 + v_windows/v/vlib/v/fmt/tests/attrs_keep.vv | 32 + v_windows/v/vlib/v/fmt/tests/bin2v_keep.vv | 2 + v_windows/v/vlib/v/fmt/tests/blocks_expected.vv | 11 + v_windows/v/vlib/v/fmt/tests/blocks_input.vv | 7 + v_windows/v/vlib/v/fmt/tests/c_struct_init_keep.vv | 30 + v_windows/v/vlib/v/fmt/tests/cast_expected.vv | 13 + v_windows/v/vlib/v/fmt/tests/cast_input.vv | 14 + v_windows/v/vlib/v/fmt/tests/chan_init_keep.vv | 27 + v_windows/v/vlib/v/fmt/tests/chan_ops_keep.vv | 65 + v_windows/v/vlib/v/fmt/tests/chan_or_keep.vv | 33 + v_windows/v/vlib/v/fmt/tests/char_literal_keep.vv | 3 + v_windows/v/vlib/v/fmt/tests/closure_keep.vv | 10 + .../v/vlib/v/fmt/tests/comments_array_keep.vv | 79 + v_windows/v/vlib/v/fmt/tests/comments_expected.vv | 105 + v_windows/v/vlib/v/fmt/tests/comments_input.vv | 89 + v_windows/v/vlib/v/fmt/tests/comments_keep.vv | 101 + .../v/fmt/tests/comptime_field_selector_keep.vv | 23 + .../comptime_field_selector_parentheses_keep.vv | 23 + v_windows/v/vlib/v/fmt/tests/comptime_keep.vv | 91 + .../v/vlib/v/fmt/tests/concat_expr_expected.vv | 24 + v_windows/v/vlib/v/fmt/tests/concat_expr_input.vv | 22 + .../v/fmt/tests/conditional_compilation_keep.vv | 19 + .../v/vlib/v/fmt/tests/conditions_expected.vv | 12 + v_windows/v/vlib/v/fmt/tests/conditions_input.vv | 10 + v_windows/v/vlib/v/fmt/tests/consts_expected.vv | 60 + v_windows/v/vlib/v/fmt/tests/consts_input.vv | 54 + v_windows/v/vlib/v/fmt/tests/consts_keep.vv | 27 + .../vlib/v/fmt/tests/consts_with_comments_keep.vv | 4 + v_windows/v/vlib/v/fmt/tests/embed_file_keep.vv | 8 + .../v/fmt/tests/empty_curlies_and_parens_keep.vv | 44 + .../v/vlib/v/fmt/tests/empty_lines_expected.vv | 28 + v_windows/v/vlib/v/fmt/tests/empty_lines_input.vv | 34 + v_windows/v/vlib/v/fmt/tests/empty_lines_keep.vv | 98 + v_windows/v/vlib/v/fmt/tests/enum_comments_keep.vv | 17 + v_windows/v/vlib/v/fmt/tests/enums_expected.vv | 9 + v_windows/v/vlib/v/fmt/tests/enums_input.vv | 5 + .../v/vlib/v/fmt/tests/expressions_expected.vv | 103 + v_windows/v/vlib/v/fmt/tests/expressions_input.vv | 102 + .../v/fmt/tests/file_with_just_imports_keep.vv | 3 + .../vlib/v/fmt/tests/fixed_size_array_type_keep.vv | 28 + .../v/vlib/v/fmt/tests/float_literals_expected.vv | 4 + .../v/vlib/v/fmt/tests/float_literals_input.vv | 4 + .../v/fmt/tests/fn_headers_with_no_bodies_keep.vv | 5 + .../v/vlib/v/fmt/tests/fn_multi_return_keep.vv | 13 + ...fn_parameter_the_same_as_a_module_const_keep.vv | 12 + .../v/fmt/tests/fn_return_generic_struct_keep.vv | 39 + .../v/fmt/tests/fn_trailing_arg_syntax_expected.vv | 50 + .../v/fmt/tests/fn_trailing_arg_syntax_input.vv | 36 + .../v/fmt/tests/fn_trailing_arg_syntax_keep.vv | 56 + .../v/vlib/v/fmt/tests/fn_with_anon_params_keep.vv | 1 + .../v/vlib/v/fmt/tests/fntype_alias_array_keep.vv | 4 + v_windows/v/vlib/v/fmt/tests/fntype_alias_keep.vv | 65 + .../tests/fntype_mut_args_with_optional_keep.vv | 1 + .../v/fmt/tests/fntype_return_optional_keep.vv | 3 + v_windows/v/vlib/v/fmt/tests/functions_expected.vv | 68 + v_windows/v/vlib/v/fmt/tests/functions_input.vv | 63 + .../v/fmt/tests/generic_recursive_structs_keep.vv | 28 + .../v/vlib/v/fmt/tests/generic_structs_keep.vv | 45 + .../v/fmt/tests/generics_cascade_types_keep.vv | 27 + v_windows/v/vlib/v/fmt/tests/generics_keep.vv | 32 + v_windows/v/vlib/v/fmt/tests/global_keep.vv | 10 + v_windows/v/vlib/v/fmt/tests/go_stmt_expected.vv | 14 + v_windows/v/vlib/v/fmt/tests/go_stmt_input.vv | 16 + v_windows/v/vlib/v/fmt/tests/go_stmt_keep.vv | 9 + v_windows/v/vlib/v/fmt/tests/goto_expected.vv | 4 + v_windows/v/vlib/v/fmt/tests/goto_input.vv | 4 + v_windows/v/vlib/v/fmt/tests/hashstmt_expected.vv | 3 + v_windows/v/vlib/v/fmt/tests/hashstmt_input.vv | 2 + v_windows/v/vlib/v/fmt/tests/hashstmt_keep.vv | 12 + .../v/fmt/tests/if_brace_on_newline_expected.vv | 12 + .../vlib/v/fmt/tests/if_brace_on_newline_input.vv | 10 + v_windows/v/vlib/v/fmt/tests/if_expected.vv | 9 + v_windows/v/vlib/v/fmt/tests/if_input.vv | 3 + v_windows/v/vlib/v/fmt/tests/if_keep.vv | 11 + .../v/vlib/v/fmt/tests/if_not_in_is_expected.vv | 17 + v_windows/v/vlib/v/fmt/tests/if_not_in_is_input.vv | 17 + .../v/vlib/v/fmt/tests/if_ternary_expected.vv | 48 + v_windows/v/vlib/v/fmt/tests/if_ternary_input.vv | 19 + v_windows/v/vlib/v/fmt/tests/if_ternary_keep.vv | 44 + .../vlib/v/fmt/tests/import_auto_added_expected.vv | 7 + .../v/vlib/v/fmt/tests/import_auto_added_input.vv | 5 + .../v/vlib/v/fmt/tests/import_comments_keep.vv | 11 + .../vlib/v/fmt/tests/import_duplicate_expected.vv | 10 + .../v/vlib/v/fmt/tests/import_duplicate_input.vv | 11 + .../tests/import_multiple_with_alias_expected.vv | 11 + .../fmt/tests/import_multiple_with_alias_input.vv | 11 + .../vlib/v/fmt/tests/import_selective_expected.vv | 67 + .../v/vlib/v/fmt/tests/import_selective_input.vv | 71 + .../v/vlib/v/fmt/tests/import_selective_keep.vv | 12 + v_windows/v/vlib/v/fmt/tests/import_single_keep.vv | 5 + .../v/vlib/v/fmt/tests/import_with_alias_keep.vv | 25 + .../v/vlib/v/fmt/tests/infix_expr_expected.vv | 33 + v_windows/v/vlib/v/fmt/tests/infix_expr_input.vv | 24 + v_windows/v/vlib/v/fmt/tests/infix_expr_keep.vv | 15 + .../v/vlib/v/fmt/tests/integer_literal_keep.vv | 19 + .../tests/interface_declaration_comments_keep.vv | 31 + .../v/vlib/v/fmt/tests/interface_variadic_keep.vv | 4 + .../v/fmt/tests/interface_with_mut_fields_keep.vv | 7 + .../v/fmt/tests/labelled_break_continue_keep.vv | 38 + .../v/vlib/v/fmt/tests/language_prefixes_keep.vv | 13 + v_windows/v/vlib/v/fmt/tests/loops_expected.vv | 18 + v_windows/v/vlib/v/fmt/tests/loops_input.vv | 18 + v_windows/v/vlib/v/fmt/tests/manualfree_keep.v | 18 + v_windows/v/vlib/v/fmt/tests/maps_expected.vv | 16 + .../v/vlib/v/fmt/tests/maps_in_fn_args__keep.vv | 15 + v_windows/v/vlib/v/fmt/tests/maps_input.vv | 16 + v_windows/v/vlib/v/fmt/tests/maps_keep.vv | 17 + .../fmt/tests/maps_of_fns_with_string_keys_keep.vv | 13 + v_windows/v/vlib/v/fmt/tests/match_expected.vv | 38 + v_windows/v/vlib/v/fmt/tests/match_input.vv | 36 + v_windows/v/vlib/v/fmt/tests/match_keep.vv | 76 + .../tests/match_range_expression_branches_keep.vv | 15 + .../tests/match_with_commented_branches_keep.vv | 57 + .../v/vlib/v/fmt/tests/missing_import_expected.vv | 5 + .../v/vlib/v/fmt/tests/missing_import_input.vv | 3 + v_windows/v/vlib/v/fmt/tests/module_alias_keep.vv | 36 + .../v/vlib/v/fmt/tests/module_interface_keep.vv | 5 + v_windows/v/vlib/v/fmt/tests/module_struct_keep.vv | 11 + .../v/vlib/v/fmt/tests/multi_generic_test_keep.vv | 2 + .../v/vlib/v/fmt/tests/multiline_comment_keep.vv | 22 + .../v/vlib/v/fmt/tests/nested_map_type_keep.vv | 5 + v_windows/v/vlib/v/fmt/tests/newlines_keep.vv | 32 + v_windows/v/vlib/v/fmt/tests/no_main_expected.vv | 1 + v_windows/v/vlib/v/fmt/tests/no_main_input.vv | 1 + v_windows/v/vlib/v/fmt/tests/offset_keep.vv | 8 + .../v/vlib/v/fmt/tests/operator_overload_keep.vv | 11 + v_windows/v/vlib/v/fmt/tests/optional_keep.vv | 5 + .../v/vlib/v/fmt/tests/optional_propagate_keep.vv | 3 + v_windows/v/vlib/v/fmt/tests/or_keep.vv | 40 + v_windows/v/vlib/v/fmt/tests/orm_keep.vv | 67 + v_windows/v/vlib/v/fmt/tests/par_expr_expected.vv | 3 + v_windows/v/vlib/v/fmt/tests/par_expr_input.vv | 3 + v_windows/v/vlib/v/fmt/tests/pointer_casts_keep.vv | 50 + .../tests/proto_module_importing_vproto_keep.vv | 35 + v_windows/v/vlib/v/fmt/tests/ref_type_cast_keep.vv | 9 + v_windows/v/vlib/v/fmt/tests/select_keep.vv | 128 + v_windows/v/vlib/v/fmt/tests/shared_expected.vv | 85 + v_windows/v/vlib/v/fmt/tests/shared_input.vv | 84 + .../v/vlib/v/fmt/tests/star__amp_int__cast_keep.vv | 5 + v_windows/v/vlib/v/fmt/tests/static_mut_keep.vv | 12 + v_windows/v/vlib/v/fmt/tests/stmt_keep.vv | 8 + .../fmt/tests/string_interpolation_complex_keep.vv | 9 + .../v/fmt/tests/string_interpolation_expected.vv | 32 + .../vlib/v/fmt/tests/string_interpolation_input.vv | 32 + .../vlib/v/fmt/tests/string_interpolation_keep.vv | 17 + .../v/vlib/v/fmt/tests/string_quotes_expected.vv | 10 + .../v/vlib/v/fmt/tests/string_quotes_input.vv | 10 + .../v/vlib/v/fmt/tests/string_raw_and_cstr_keep.vv | 6 + v_windows/v/vlib/v/fmt/tests/struct_decl_keep.vv | 5 + .../tests/struct_default_field_expressions_keep.vv | 14 + v_windows/v/vlib/v/fmt/tests/struct_embed_keep.vv | 18 + v_windows/v/vlib/v/fmt/tests/struct_init_keep.vv | 14 + .../v/fmt/tests/struct_init_with_comments_keep.vv | 20 + .../fmt/tests/struct_init_with_custom_len_keep.vv | 17 + .../v/fmt/tests/struct_init_with_ref_cast_keep.vv | 22 + v_windows/v/vlib/v/fmt/tests/struct_keep.vv | 46 + .../vlib/v/fmt/tests/struct_no_extra_attr_keep.vv | 27 + .../vlib/v/fmt/tests/struct_update_comment_keep.vv | 18 + v_windows/v/vlib/v/fmt/tests/struct_update_keep.vv | 17 + .../vlib/v/fmt/tests/struct_with_fn_fields_keep.vv | 4 + v_windows/v/vlib/v/fmt/tests/structs_expected.vv | 62 + v_windows/v/vlib/v/fmt/tests/structs_input.vv | 65 + v_windows/v/vlib/v/fmt/tests/sum_smartcast_keep.vv | 20 + .../v/vlib/v/fmt/tests/thread_in_a_module_keep.vv | 17 + .../v/vlib/v/fmt/tests/to_string_2_forms_keep.vv | 33 + .../v/vlib/v/fmt/tests/trailing_space_expected.vv | 10 + .../v/vlib/v/fmt/tests/trailing_space_input.vv | 10 + v_windows/v/vlib/v/fmt/tests/type_ptr_keep.vv | 16 + v_windows/v/vlib/v/fmt/tests/typeof_keep.vv | 3 + v_windows/v/vlib/v/fmt/tests/types_expected.vv | 27 + v_windows/v/vlib/v/fmt/tests/types_input.vv | 37 + v_windows/v/vlib/v/fmt/tests/union_keep.vv | 4 + v_windows/v/vlib/v/fmt/tests/unsafe_keep.vv | 17 + .../vlib/v/fmt/tests/vargs_reference_param_keep.vv | 27 + v_windows/v/vlib/v/fmt/tests/void_optional_keep.vv | 7 + v_windows/v/vlib/v/fmt/tests/vscript_keep.vv | 9 + v_windows/v/vlib/v/gen/c/array.v | 723 ++ v_windows/v/vlib/v/gen/c/assert.v | 159 + v_windows/v/vlib/v/gen/c/auto_eq_methods.v | 343 + v_windows/v/vlib/v/gen/c/auto_str_methods.v | 900 ++ v_windows/v/vlib/v/gen/c/cgen.v | 6878 ++++++++++++ v_windows/v/vlib/v/gen/c/cheaders.v | 718 ++ v_windows/v/vlib/v/gen/c/cmain.v | 214 + v_windows/v/vlib/v/gen/c/comptime.v | 676 ++ v_windows/v/vlib/v/gen/c/coutput_test.v | 174 + v_windows/v/vlib/v/gen/c/ctempvars.v | 25 + v_windows/v/vlib/v/gen/c/dumpexpr.v | 72 + v_windows/v/vlib/v/gen/c/embed.v | 69 + v_windows/v/vlib/v/gen/c/fn.v | 1558 +++ v_windows/v/vlib/v/gen/c/index.v | 452 + v_windows/v/vlib/v/gen/c/infix_expr.v | 628 ++ v_windows/v/vlib/v/gen/c/json.v | 339 + v_windows/v/vlib/v/gen/c/live.v | 104 + v_windows/v/vlib/v/gen/c/profile.v | 52 + v_windows/v/vlib/v/gen/c/sql.v | 836 ++ v_windows/v/vlib/v/gen/c/str.v | 145 + v_windows/v/vlib/v/gen/c/str_intp.v | 206 + .../v/vlib/v/gen/c/testdata/addition.c.must_have | 1 + v_windows/v/vlib/v/gen/c/testdata/addition.out | 1 + v_windows/v/vlib/v/gen/c/testdata/addition.vv | 4 + .../v/gen/c/testdata/const_references.c.must_have | 3 + .../v/vlib/v/gen/c/testdata/const_references.out | 3 + .../v/vlib/v/gen/c/testdata/const_references.vv | 11 + v_windows/v/vlib/v/gen/c/utils.v | 49 + v_windows/v/vlib/v/gen/js/array.v | 89 + v_windows/v/vlib/v/gen/js/builtin_types.v | 419 + v_windows/v/vlib/v/gen/js/comptime.v | 311 + v_windows/v/vlib/v/gen/js/fast_deep_equal.js | 72 + v_windows/v/vlib/v/gen/js/fn.v | 241 + v_windows/v/vlib/v/gen/js/js.v | 3000 ++++++ v_windows/v/vlib/v/gen/js/jsdoc.v | 96 + v_windows/v/vlib/v/gen/js/jsgen_test.v | 86 + v_windows/v/vlib/v/gen/js/program_test.v | 98 + v_windows/v/vlib/v/gen/js/sourcemap/basic_test.v | 158 + v_windows/v/vlib/v/gen/js/sourcemap/compare_test.v | 322 + v_windows/v/vlib/v/gen/js/sourcemap/mappings.v | 170 + v_windows/v/vlib/v/gen/js/sourcemap/sets.v | 16 + v_windows/v/vlib/v/gen/js/sourcemap/source_map.v | 131 + .../vlib/v/gen/js/sourcemap/source_map_generator.v | 46 + v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq.v | 115 + .../vlib/v/gen/js/sourcemap/vlq/vlq_decode_test.v | 52 + .../vlib/v/gen/js/sourcemap/vlq/vlq_encode_test.v | 35 + v_windows/v/vlib/v/gen/js/str.v | 85 + v_windows/v/vlib/v/gen/js/temp_fast_deep_equal.v | 89 + v_windows/v/vlib/v/gen/js/tests/.gitignore | 1 + v_windows/v/vlib/v/gen/js/tests/array.v | 136 + v_windows/v/vlib/v/gen/js/tests/auto_deref_args.v | 13 + v_windows/v/vlib/v/gen/js/tests/enum.v | 19 + v_windows/v/vlib/v/gen/js/tests/hello/hello.js.v | 5 + v_windows/v/vlib/v/gen/js/tests/hello/hello.v | 33 + .../v/vlib/v/gen/js/tests/hello/hello1/hello1.v | 5 + v_windows/v/vlib/v/gen/js/tests/interface.v | 47 + v_windows/v/vlib/v/gen/js/tests/interp.v | 188 + v_windows/v/vlib/v/gen/js/tests/js.v | 141 + v_windows/v/vlib/v/gen/js/tests/life.v | 98 + v_windows/v/vlib/v/gen/js/tests/optional.v | 33 + v_windows/v/vlib/v/gen/js/tests/simple.v | 5 + v_windows/v/vlib/v/gen/js/tests/simple_sourcemap.v | 23 + v_windows/v/vlib/v/gen/js/tests/struct.v | 40 + v_windows/v/vlib/v/gen/js/tests/testdata/array.out | 303 + v_windows/v/vlib/v/gen/js/tests/testdata/array.v | 1180 +++ .../vlib/v/gen/js/tests/testdata/byte_is_space.out | 2 + .../v/vlib/v/gen/js/tests/testdata/byte_is_space.v | 4 + .../vlib/v/gen/js/tests/testdata/compare_ints.out | 1 + .../v/vlib/v/gen/js/tests/testdata/compare_ints.v | 15 + v_windows/v/vlib/v/gen/js/tests/testdata/hw.out | 1 + v_windows/v/vlib/v/gen/js/tests/testdata/hw.v | 1 + v_windows/v/vlib/v/gen/js/tests/testdata/match.out | 4 + v_windows/v/vlib/v/gen/js/tests/testdata/match.v | 57 + .../v/vlib/v/gen/js/tests/testdata/overloading.out | 3 + .../v/vlib/v/gen/js/tests/testdata/overloading.v | 24 + .../v/vlib/v/gen/js/tests/testdata/string.out | 7 + v_windows/v/vlib/v/gen/js/tests/testdata/string.v | 200 + .../v/gen/js/tests/testdata/string_methods.out | 3 + .../vlib/v/gen/js/tests/testdata/string_methods.v | 3 + v_windows/v/vlib/v/gen/js/tests/testdata/u64.out | 4 + v_windows/v/vlib/v/gen/js/tests/testdata/u64.v | 7 + v_windows/v/vlib/v/gen/native/amd64.v | 1425 +++ v_windows/v/vlib/v/gen/native/arm64.v | 203 + v_windows/v/vlib/v/gen/native/elf.v | 113 + v_windows/v/vlib/v/gen/native/elf_obj.v | 157 + v_windows/v/vlib/v/gen/native/gen.v | 495 + v_windows/v/vlib/v/gen/native/macho.v | 405 + v_windows/v/vlib/v/gen/native/macho_test.v | 16 + v_windows/v/vlib/v/gen/native/tests/asm.vv | 10 + v_windows/v/vlib/v/gen/native/tests/asm.vv.out | 1 + v_windows/v/vlib/v/gen/native/tests/assert.vv | 10 + v_windows/v/vlib/v/gen/native/tests/assert.vv.out | 1 + v_windows/v/vlib/v/gen/native/tests/expressions.vv | 60 + .../v/vlib/v/gen/native/tests/expressions.vv.out | 8 + v_windows/v/vlib/v/gen/native/tests/general.vv | 95 + v_windows/v/vlib/v/gen/native/tests/general.vv.out | 14 + v_windows/v/vlib/v/gen/native/tests/hello.vv | 3 + v_windows/v/vlib/v/gen/native/tests/hello.vv.out | 1 + v_windows/v/vlib/v/gen/native/tests/ifs.vv | 65 + v_windows/v/vlib/v/gen/native/tests/ifs.vv.out | 7 + v_windows/v/vlib/v/gen/native/tests/native_test.v | 98 + v_windows/v/vlib/v/gen/native/tests/print.vv | 14 + v_windows/v/vlib/v/gen/native/tests/print.vv.err | 1 + v_windows/v/vlib/v/gen/native/tests/print.vv.out | 1 + .../v/vlib/v/gen/native/tests/simple_fn_calls.vv | 14 + .../vlib/v/gen/native/tests/simple_fn_calls.vv.out | 4 + v_windows/v/vlib/v/live/common.v | 69 + v_windows/v/vlib/v/live/executable/reloader.v | 163 + v_windows/v/vlib/v/live/live_test.v | 177 + v_windows/v/vlib/v/live/live_test_template.vv | 66 + v_windows/v/vlib/v/live/sharedlib/live_sharedlib.v | 7 + v_windows/v/vlib/v/markused/markused.v | 424 + v_windows/v/vlib/v/markused/walker.v | 464 + v_windows/v/vlib/v/parser/assign.v | 236 + v_windows/v/vlib/v/parser/comptime.v | 359 + v_windows/v/vlib/v/parser/containers.v | 190 + v_windows/v/vlib/v/parser/expr.v | 628 ++ v_windows/v/vlib/v/parser/fn.v | 1029 ++ v_windows/v/vlib/v/parser/for.v | 201 + v_windows/v/vlib/v/parser/if_match.v | 443 + v_windows/v/vlib/v/parser/lock.v | 113 + v_windows/v/vlib/v/parser/module.v | 66 + v_windows/v/vlib/v/parser/parse_type.v | 576 + v_windows/v/vlib/v/parser/parser.v | 3454 ++++++ v_windows/v/vlib/v/parser/sql.v | 262 + v_windows/v/vlib/v/parser/struct.v | 622 ++ v_windows/v/vlib/v/parser/tests/README.md | 1 + .../v/vlib/v/parser/tests/anon_fn_return_type.out | 6 + .../v/vlib/v/parser/tests/anon_fn_return_type.vv | 8 + .../v/vlib/v/parser/tests/anon_unused_param.out | 4 + .../v/vlib/v/parser/tests/anon_unused_param.vv | 2 + v_windows/v/vlib/v/parser/tests/array_init.out | 12 + v_windows/v/vlib/v/parser/tests/array_init.vv | 4 + v_windows/v/vlib/v/parser/tests/array_pos_err.out | 5 + v_windows/v/vlib/v/parser/tests/array_pos_err.vv | 3 + .../v/vlib/v/parser/tests/c_struct_no_embed.out | 5 + .../v/vlib/v/parser/tests/c_struct_no_embed.vv | 7 + .../v/vlib/v/parser/tests/closure_not_declared.out | 7 + .../v/vlib/v/parser/tests/closure_not_declared.vv | 8 + .../v/vlib/v/parser/tests/closure_not_used.out | 7 + .../v/vlib/v/parser/tests/closure_not_used.vv | 7 + .../vlib/v/parser/tests/closure_undefined_var.out | 6 + .../v/vlib/v/parser/tests/closure_undefined_var.vv | 6 + v_windows/v/vlib/v/parser/tests/const_index.out | 1 + v_windows/v/vlib/v/parser/tests/const_index.vv | 22 + .../v/vlib/v/parser/tests/const_missing_rpar_a.out | 3 + .../v/vlib/v/parser/tests/const_missing_rpar_a.vv | 2 + .../v/vlib/v/parser/tests/const_missing_rpar_b.out | 3 + .../v/vlib/v/parser/tests/const_missing_rpar_b.vv | 3 + .../v/vlib/v/parser/tests/const_only_keyword.out | 2 + .../v/vlib/v/parser/tests/const_only_keyword.vv | 1 + .../v/vlib/v/parser/tests/const_unexpected_eof.out | 2 + .../v/vlib/v/parser/tests/const_unexpected_eof.vv | 1 + .../v/vlib/v/parser/tests/dec_use_as_value.out | 6 + .../v/vlib/v/parser/tests/dec_use_as_value.vv | 4 + .../v/vlib/v/parser/tests/defer_propagate.out | 7 + v_windows/v/vlib/v/parser/tests/defer_propagate.vv | 16 + v_windows/v/vlib/v/parser/tests/defer_return.out | 7 + v_windows/v/vlib/v/parser/tests/defer_return.vv | 5 + v_windows/v/vlib/v/parser/tests/defer_return2.out | 7 + v_windows/v/vlib/v/parser/tests/defer_return2.vv | 12 + .../v/parser/tests/duplicate_field_embed_err.out | 6 + .../v/parser/tests/duplicate_field_embed_err.vv | 9 + .../v/vlib/v/parser/tests/duplicate_type_a.out | 5 + .../v/vlib/v/parser/tests/duplicate_type_a.vv | 3 + .../v/vlib/v/parser/tests/duplicate_type_b.out | 5 + .../v/vlib/v/parser/tests/duplicate_type_b.vv | 3 + .../vlib/v/parser/tests/duplicated_generic_err.out | 3 + .../vlib/v/parser/tests/duplicated_generic_err.vv | 1 + .../v/vlib/v/parser/tests/empty_name_expr_err.out | 5 + .../v/vlib/v/parser/tests/empty_name_expr_err.vv | 3 + .../vlib/v/parser/tests/expected_type_enum_err.out | 6 + .../vlib/v/parser/tests/expected_type_enum_err.vv | 7 + .../v/parser/tests/expecting_assign_type_alias.out | 5 + .../v/parser/tests/expecting_assign_type_alias.vv | 3 + .../v/parser/tests/export_interop_func_err.out | 4 + .../vlib/v/parser/tests/export_interop_func_err.vv | 2 + .../parser/tests/expr_evaluated_but_not_used_a.out | 5 + .../parser/tests/expr_evaluated_but_not_used_a.vv | 3 + .../parser/tests/expr_evaluated_but_not_used_b.out | 5 + .../parser/tests/expr_evaluated_but_not_used_b.vv | 3 + .../parser/tests/expr_evaluated_but_not_used_c.out | 7 + .../parser/tests/expr_evaluated_but_not_used_c.vv | 5 + .../parser/tests/expr_evaluated_but_not_used_d.out | 6 + .../parser/tests/expr_evaluated_but_not_used_d.vv | 5 + .../parser/tests/expr_evaluated_but_not_used_e.out | 7 + .../parser/tests/expr_evaluated_but_not_used_e.vv | 5 + .../tests/expr_evaluated_but_not_used_if.out | 7 + .../parser/tests/expr_evaluated_but_not_used_if.vv | 8 + .../tests/expr_evaluated_but_not_used_or.out | 7 + .../parser/tests/expr_evaluated_but_not_used_or.vv | 10 + .../tests/fn_attributes_duplicate_multiple.out | 6 + .../tests/fn_attributes_duplicate_multiple.vv | 7 + .../tests/fn_attributes_duplicate_single.out | 5 + .../parser/tests/fn_attributes_duplicate_single.vv | 6 + .../v/parser/tests/fn_attributes_empty_err.out | 5 + .../vlib/v/parser/tests/fn_attributes_empty_err.vv | 6 + .../vlib/v/parser/tests/fn_decl_unexpected_eof.out | 3 + .../vlib/v/parser/tests/fn_decl_unexpected_eof.vv | 1 + .../tests/fn_type_only_args_in_interfaces.out | 5 + .../tests/fn_type_only_args_in_interfaces.vv | 22 + .../v/parser/tests/fn_type_only_args_no_body.out | 6 + .../v/parser/tests/fn_type_only_args_no_body.vv | 6 + .../tests/fn_type_only_args_unknown_name.out | 6 + .../parser/tests/fn_type_only_args_unknown_name.vv | 6 + .../v/vlib/v/parser/tests/fn_use_builtin_err.out | 5 + .../v/vlib/v/parser/tests/fn_use_builtin_err.vv | 9 + v_windows/v/vlib/v/parser/tests/for.out | 4 + v_windows/v/vlib/v/parser/tests/for.vv | 2 + .../v/parser/tests/for_in_mut_index_of_array.out | 7 + .../v/parser/tests/for_in_mut_index_of_array.vv | 6 + .../vlib/v/parser/tests/for_in_mut_key_of_map.out | 7 + .../v/vlib/v/parser/tests/for_in_mut_key_of_map.vv | 6 + .../tests/function_variadic_arg_non_final.out | 5 + .../tests/function_variadic_arg_non_final.vv | 7 + .../vlib/v/parser/tests/generic_lowercase_err.out | 3 + .../v/vlib/v/parser/tests/generic_lowercase_err.vv | 1 + .../v/parser/tests/generic_type_alias_decl.out | 3 + .../vlib/v/parser/tests/generic_type_alias_decl.vv | 1 + .../vlib/v/parser/tests/if_guard_redefinition.out | 7 + .../v/vlib/v/parser/tests/if_guard_redefinition.vv | 10 + .../v/vlib/v/parser/tests/inc_use_as_value.out | 6 + .../v/vlib/v/parser/tests/inc_use_as_value.vv | 4 + .../tests/interface_duplicate_interface_method.out | 6 + .../tests/interface_duplicate_interface_method.vv | 5 + .../v/parser/tests/interface_duplicate_method.out | 5 + .../v/parser/tests/interface_duplicate_method.vv | 5 + .../vlib/v/parser/tests/interop_func_body_err.out | 3 + .../v/vlib/v/parser/tests/interop_func_body_err.vv | 1 + .../v/vlib/v/parser/tests/invalid_attribute_a.out | 5 + .../v/vlib/v/parser/tests/invalid_attribute_a.vv | 3 + .../v/vlib/v/parser/tests/invalid_attribute_b.out | 5 + .../v/vlib/v/parser/tests/invalid_attribute_b.vv | 3 + .../v/vlib/v/parser/tests/invalid_attribute_c.out | 5 + .../v/vlib/v/parser/tests/invalid_attribute_c.vv | 3 + .../v/vlib/v/parser/tests/invalid_attribute_d.out | 5 + .../v/vlib/v/parser/tests/invalid_attribute_d.vv | 3 + .../v/parser/tests/invalid_fn_decl_script_err.out | 7 + .../v/parser/tests/invalid_fn_decl_script_err.vv | 5 + .../parser/tests/invalid_recursive_struct1_err.out | 5 + .../parser/tests/invalid_recursive_struct1_err.vv | 3 + .../parser/tests/invalid_recursive_struct2_err.out | 7 + .../parser/tests/invalid_recursive_struct2_err.vv | 7 + .../v/vlib/v/parser/tests/long_generic_err.out | 3 + .../v/vlib/v/parser/tests/long_generic_err.vv | 1 + v_windows/v/vlib/v/parser/tests/map_init.out | 6 + v_windows/v/vlib/v/parser/tests/map_init.vv | 4 + v_windows/v/vlib/v/parser/tests/map_init_void.out | 5 + v_windows/v/vlib/v/parser/tests/map_init_void.vv | 3 + v_windows/v/vlib/v/parser/tests/map_init_void2.out | 5 + v_windows/v/vlib/v/parser/tests/map_init_void2.vv | 7 + .../vlib/v/parser/tests/match_range_dotdot_err.out | 7 + .../vlib/v/parser/tests/match_range_dotdot_err.vv | 6 + .../tests/method_decl_on_non_local_array.out | 5 + .../parser/tests/method_decl_on_non_local_array.vv | 3 + .../parser/tests/method_decl_on_non_local_map.out | 5 + .../v/parser/tests/method_decl_on_non_local_map.vv | 3 + .../parser/tests/method_decl_on_non_local_type.out | 5 + .../parser/tests/method_decl_on_non_local_type.vv | 3 + .../v/parser/tests/module_multiple_names_err.out | 5 + .../v/parser/tests/module_multiple_names_err.vv | 4 + .../v/vlib/v/parser/tests/module_syntax_err.out | 5 + .../v/vlib/v/parser/tests/module_syntax_err.vv | 4 + .../v/parser/tests/multi_argumented_assign_err.out | 7 + .../v/parser/tests/multi_argumented_assign_err.vv | 5 + v_windows/v/vlib/v/parser/tests/nested_defer.out | 7 + v_windows/v/vlib/v/parser/tests/nested_defer.vv | 14 + .../v/vlib/v/parser/tests/nested_unsafe_expr.out | 7 + .../v/vlib/v/parser/tests/nested_unsafe_expr.vv | 7 + .../v/vlib/v/parser/tests/nested_unsafe_stmt.out | 7 + .../v/vlib/v/parser/tests/nested_unsafe_stmt.vv | 9 + .../v/vlib/v/parser/tests/operator_normal_fn.out | 5 + .../v/vlib/v/parser/tests/operator_normal_fn.vv | 3 + .../v/vlib/v/parser/tests/or_default_missing.out | 14 + .../v/vlib/v/parser/tests/or_default_missing.vv | 20 + v_windows/v/vlib/v/parser/tests/postfix_err.out | 21 + v_windows/v/vlib/v/parser/tests/postfix_err.vv | 11 + v_windows/v/vlib/v/parser/tests/postfix_inc.out | 5 + v_windows/v/vlib/v/parser/tests/postfix_inc.vv | 3 + v_windows/v/vlib/v/parser/tests/prefix_first.out | 28 + v_windows/v/vlib/v/parser/tests/prefix_first.vv | 29 + .../prohibit_redeclaration_of_builtin_types.out | 3 + .../prohibit_redeclaration_of_builtin_types.vv | 1 + .../parser/tests/redeclaration_of_imported_fn.out | 7 + .../v/parser/tests/redeclaration_of_imported_fn.vv | 5 + .../v/parser/tests/register_imported_alias.out | 4 + .../vlib/v/parser/tests/register_imported_alias.vv | 2 + .../vlib/v/parser/tests/register_imported_enum.out | 4 + .../vlib/v/parser/tests/register_imported_enum.vv | 2 + .../v/parser/tests/register_imported_interface.out | 4 + .../v/parser/tests/register_imported_interface.vv | 2 + .../v/parser/tests/register_imported_struct.out | 4 + .../v/parser/tests/register_imported_struct.vv | 2 + .../v/vlib/v/parser/tests/select_bad_key_1.out | 14 + .../v/vlib/v/parser/tests/select_bad_key_1.vv | 47 + .../v/vlib/v/parser/tests/select_bad_key_2.out | 7 + .../v/vlib/v/parser/tests/select_bad_key_2.vv | 13 + .../v/vlib/v/parser/tests/select_bad_key_3.out | 7 + .../v/vlib/v/parser/tests/select_bad_key_3.vv | 12 + .../v/vlib/v/parser/tests/select_bad_key_4.out | 7 + .../v/vlib/v/parser/tests/select_bad_key_4.vv | 13 + v_windows/v/vlib/v/parser/tests/select_else_1.out | 7 + v_windows/v/vlib/v/parser/tests/select_else_1.vv | 18 + v_windows/v/vlib/v/parser/tests/select_else_2.out | 14 + v_windows/v/vlib/v/parser/tests/select_else_2.vv | 18 + .../v/vlib/v/parser/tests/sql_no_db_expr_a.out | 5 + .../v/vlib/v/parser/tests/sql_no_db_expr_a.vv | 4 + .../v/vlib/v/parser/tests/sql_no_db_expr_b.out | 6 + .../v/vlib/v/parser/tests/sql_no_db_expr_b.vv | 4 + .../v/parser/tests/string_invalid_prefix_err.out | 6 + .../v/parser/tests/string_invalid_prefix_err.vv | 4 + .../vlib/v/parser/tests/struct_embed_duplicate.out | 7 + .../vlib/v/parser/tests/struct_embed_duplicate.vv | 9 + .../v/parser/tests/struct_embed_unknown_module.out | 5 + .../v/parser/tests/struct_embed_unknown_module.vv | 3 + .../tests/struct_embed_wrong_pos_long_err.out | 7 + .../tests/struct_embed_wrong_pos_long_err.vv | 6 + .../tests/struct_embed_wrong_pos_short_err.out | 7 + .../tests/struct_embed_wrong_pos_short_err.vv | 8 + .../vlib/v/parser/tests/struct_field_expected.out | 7 + .../v/vlib/v/parser/tests/struct_field_expected.vv | 9 + .../parser/tests/struct_field_unknown_module_a.out | 5 + .../parser/tests/struct_field_unknown_module_a.vv | 3 + .../parser/tests/struct_field_unknown_module_b.out | 5 + .../parser/tests/struct_field_unknown_module_b.vv | 3 + .../parser/tests/struct_field_unknown_module_c.out | 6 + .../parser/tests/struct_field_unknown_module_c.vv | 5 + .../vlib/v/parser/tests/struct_module_section.out | 12 + .../v/vlib/v/parser/tests/struct_module_section.vv | 18 + .../v/vlib/v/parser/tests/struct_update_err.out | 7 + .../v/vlib/v/parser/tests/struct_update_err.vv | 17 + .../v/vlib/v/parser/tests/sum_type_exists_err.out | 3 + .../v/vlib/v/parser/tests/sum_type_exists_err.vv | 1 + .../vlib/v/parser/tests/too_many_generics_err.out | 3 + .../v/vlib/v/parser/tests/too_many_generics_err.vv | 1 + .../parser/tests/type_alias_existing_type_err.out | 7 + .../v/parser/tests/type_alias_existing_type_err.vv | 7 + .../v/parser/tests/type_alias_same_type_err.out | 5 + .../v/parser/tests/type_alias_same_type_err.vv | 5 + .../v/parser/tests/uncomplete_module_call_err.out | 5 + .../v/parser/tests/uncomplete_module_call_err.vv | 7 + .../v/vlib/v/parser/tests/unexpected_expr.out | 3 + v_windows/v/vlib/v/parser/tests/unexpected_expr.vv | 1 + .../v/vlib/v/parser/tests/unexpected_keyword.out | 7 + .../v/vlib/v/parser/tests/unexpected_keyword.vv | 12 + .../v/vlib/v/parser/tests/unnecessary_mut.out | 7 + v_windows/v/vlib/v/parser/tests/unnecessary_mut.vv | 7 + .../v/vlib/v/parser/tests/unnecessary_mut_2.out | 6 + .../v/vlib/v/parser/tests/unnecessary_mut_2.vv | 5 + v_windows/v/vlib/v/parser/tmpl.v | 238 + v_windows/v/vlib/v/parser/v_parser_test.v | 263 + v_windows/v/vlib/v/pkgconfig/README.md | 68 + v_windows/v/vlib/v/pkgconfig/bin/pkgconfig.v | 18 + v_windows/v/vlib/v/pkgconfig/main.v | 203 + v_windows/v/vlib/v/pkgconfig/pkgconfig.v | 277 + v_windows/v/vlib/v/pkgconfig/pkgconfig_test.v | 91 + v_windows/v/vlib/v/pkgconfig/test_samples/alsa.pc | 12 + v_windows/v/vlib/v/pkgconfig/test_samples/atk.pc | 10 + .../v/vlib/v/pkgconfig/test_samples/autoopts.pc | 26 + .../pkgconfig/test_samples/dep-resolution-fail.pc | 7 + v_windows/v/vlib/v/pkgconfig/test_samples/expat.pc | 11 + v_windows/v/vlib/v/pkgconfig/test_samples/form.pc | 19 + .../v/vlib/v/pkgconfig/test_samples/gio-2.0.pc | 25 + .../vlib/v/pkgconfig/test_samples/gio-unix-2.0.pc | 9 + .../v/vlib/v/pkgconfig/test_samples/glib-2.0.pc | 16 + .../v/vlib/v/pkgconfig/test_samples/gmodule-2.0.pc | 12 + .../test_samples/gmodule-no-export-2.0.pc | 13 + .../v/vlib/v/pkgconfig/test_samples/gobject-2.0.pc | 12 + .../v/vlib/v/pkgconfig/test_samples/libffi.pc | 10 + .../v/vlib/v/pkgconfig/test_samples/libpcre.pc | 13 + .../v/vlib/v/pkgconfig/test_samples/ncurses.pc | 19 + v_windows/v/vlib/v/pkgconfig/test_samples/sdl2.pc | 15 + v_windows/v/vlib/v/pkgconfig/test_samples/zlib.pc | 13 + v_windows/v/vlib/v/pkgconfig/v.mod | 12 + v_windows/v/vlib/v/pref/default.v | 206 + v_windows/v/vlib/v/pref/os.v | 116 + v_windows/v/vlib/v/pref/pref.v | 839 ++ v_windows/v/vlib/v/pref/should_compile.v | 228 + v_windows/v/vlib/v/preludes/README.md | 28 + v_windows/v/vlib/v/preludes/live.v | 9 + v_windows/v/vlib/v/preludes/live_main.v | 9 + v_windows/v/vlib/v/preludes/live_shared.v | 9 + v_windows/v/vlib/v/preludes/profiled_program.v | 7 + v_windows/v/vlib/v/preludes/tests_assertions.v | 108 + v_windows/v/vlib/v/preludes/tests_with_stats.v | 91 + v_windows/v/vlib/v/preludes_js/stats_import.js.v | 15 + v_windows/v/vlib/v/preludes_js/tests_assertions.v | 89 + v_windows/v/vlib/v/preludes_js/tests_with_stats.v | 90 + v_windows/v/vlib/v/scanner/scanner.v | 1444 +++ v_windows/v/vlib/v/scanner/scanner_test.v | 143 + .../tests/bin_consecutively_separator_err.out | 5 + .../tests/bin_consecutively_separator_err.vv | 3 + .../v/scanner/tests/bin_separator_in_front_err.out | 5 + .../v/scanner/tests/bin_separator_in_front_err.vv | 3 + .../tests/dec_consecutively_separator_err.out | 5 + .../tests/dec_consecutively_separator_err.vv | 3 + .../v/scanner/tests/float_literals_dot_err.out | 6 + .../vlib/v/scanner/tests/float_literals_dot_err.vv | 4 + .../tests/hex_consecutively_separator_err.out | 5 + .../tests/hex_consecutively_separator_err.vv | 3 + .../v/scanner/tests/hex_separator_in_front_err.out | 5 + .../v/scanner/tests/hex_separator_in_front_err.vv | 3 + .../tests/oct_consecutively_separator_err.out | 5 + .../tests/oct_consecutively_separator_err.vv | 3 + .../v/scanner/tests/oct_separator_in_front_err.out | 5 + .../v/scanner/tests/oct_separator_in_front_err.vv | 3 + .../v/vlib/v/scanner/tests/position_0_err.out | 5 + v_windows/v/vlib/v/scanner/tests/position_0_err.vv | 4 + .../tests/alias_array_operator_overloading_test.v | 19 + .../v/vlib/v/tests/alias_custom_type_map_test.v | 8 + .../v/vlib/v/tests/alias_fixed_array_init_test.v | 12 + v_windows/v/vlib/v/tests/alias_fixed_array_test.v | 12 + .../v/tests/alias_in_a_struct_field_autostr_test.v | 12 + .../v/tests/alias_map_operator_overloading_test.v | 23 + .../vlib/v/tests/alias_sumtype_method_call_test.v | 16 + .../v/vlib/v/tests/aliased_array_operations_test.v | 16 + v_windows/v/vlib/v/tests/anon_fn_call_test.v | 10 + .../v/vlib/v/tests/anon_fn_in_containers_test.v | 37 + .../v/vlib/v/tests/anon_fn_redefinition_test.v | 24 + .../vlib/v/tests/anon_fn_returning_question_test.v | 25 + v_windows/v/vlib/v/tests/anon_fn_test.v | 23 + .../v/tests/anon_fn_with_array_arguments_test.v | 12 + .../v/vlib/v/tests/anon_fn_with_optional_test.v | 30 + .../appending_to_mut_array_in_fn_param_test.v | 34 + .../vlib/v/tests/array_append_short_struct_test.v | 31 + v_windows/v/vlib/v/tests/array_cast_test.v | 19 + v_windows/v/vlib/v/tests/array_equality_test.v | 93 + v_windows/v/vlib/v/tests/array_init_test.v | 259 + v_windows/v/vlib/v/tests/array_map_or_test.v | 118 + v_windows/v/vlib/v/tests/array_map_ref_test.v | 56 + v_windows/v/vlib/v/tests/array_methods_test.v | 32 + .../v/vlib/v/tests/array_of_alias_slice_test.v | 12 + .../array_of_sumtype_append_aggregate_type_test.v | 29 + v_windows/v/vlib/v/tests/array_of_sumtypes_test.v | 20 + v_windows/v/vlib/v/tests/array_slice_test.v | 109 + .../v/vlib/v/tests/array_sort_lt_overload_test.v | 22 + v_windows/v/vlib/v/tests/array_test.v | 27 + v_windows/v/vlib/v/tests/array_to_string_test.v | 57 + v_windows/v/vlib/v/tests/array_type_alias_test.v | 11 + .../tests/as_cast_already_smartcast_sumtype_test.v | 30 + .../tests/as_cast_is_expr_sumtype_fn_result_test.v | 30 + v_windows/v/vlib/v/tests/assembly/asm_test.amd64.v | 236 + v_windows/v/vlib/v/tests/assembly/asm_test.i386.v | 188 + .../vlib/v/tests/assembly/naked_attr_test.amd64.v | 14 + .../v/vlib/v/tests/assembly/naked_attr_test.i386.v | 14 + .../v/tests/assembly/util/dot_amd64_util.amd64.v | 15 + .../v/tests/assert_fn_call_with_parentheses_test.v | 7 + v_windows/v/vlib/v/tests/assert_sumtype_test.v | 11 + .../v/vlib/v/tests/assert_with_newlines_test.v | 8 + .../v/tests/assign_bitops_with_type_aliases_test.v | 18 + .../v/tests/assign_map_value_of_fixed_array_test.v | 18 + v_windows/v/vlib/v/tests/attribute_test.v | 49 + v_windows/v/vlib/v/tests/autolock_array1_test.v | 50 + v_windows/v/vlib/v/tests/autolock_array2_test.v | 32 + v_windows/v/vlib/v/tests/backtrace_test.v | 11 + .../v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf | Bin 0 -> 19044 bytes .../v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.svg | 10619 +++++++++++++++++++ .../v/vlib/v/tests/bench/gcboehm/GC_bench.plt | 17 + v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench.v | 65 + .../v/vlib/v/tests/bench/gcboehm/GC_bench_full.plt | 9 + .../v/vlib/v/tests/bench/gcboehm/GC_bench_incr.plt | 9 + .../v/tests/bench/gcboehm/GC_bench_non_opt.plt | 9 + .../v/vlib/v/tests/bench/gcboehm/GC_bench_opt.plt | 9 + v_windows/v/vlib/v/tests/bench/gcboehm/Makefile | 66 + .../v/vlib/v/tests/bench/gcboehm/Resources.plt | 45 + .../bench/gcboehm/Resources_Ryzen_3800X_Linux.pdf | Bin 0 -> 18959 bytes .../bench/gcboehm/Resources_Ryzen_3800X_Linux.svg | 613 ++ .../v/vlib/v/tests/bench/gcboehm/resources.txt | 6 + v_windows/v/vlib/v/tests/bench/val_vs_ptr.c | 21 + v_windows/v/vlib/v/tests/blank_ident_test.v | 323 + v_windows/v/vlib/v/tests/break_in_lock_test.v | 55 + .../c_struct_free/c_struct_free_property_test.v | 13 + .../v/vlib/v/tests/c_struct_free/free_struct.c | 3 + ...ing_module_functions_with_maps_of_arrays_test.v | 10 + v_windows/v/vlib/v/tests/cast_to_byte_test.v | 41 + v_windows/v/vlib/v/tests/cast_to_interface_test.v | 17 + .../v/vlib/v/tests/cflags/includes/myinclude.h | 4 + v_windows/v/vlib/v/tests/cflags/v.mod | 3 + .../vlib/v/tests/cflags/vmodroot_and_vroot_test.v | 18 + v_windows/v/vlib/v/tests/channels_test.v | 29 + .../v/tests/clash_var_name_of_array_and_map_test.v | 78 + v_windows/v/vlib/v/tests/closure_test.v | 153 + v_windows/v/vlib/v/tests/complex_assign_test.v | 115 + v_windows/v/vlib/v/tests/comptime_at_test.v | 122 + .../v/tests/comptime_attribute_selector_test.v | 33 + .../v/tests/comptime_bittness_and_endianess_test.v | 25 + v_windows/v/vlib/v/tests/comptime_call_test.v | 67 + .../v/vlib/v/tests/comptime_field_selector_test.v | 62 + ..._over_struct_with_C_reserved_word_fields_test.v | 25 + v_windows/v/vlib/v/tests/comptime_for_test.v | 100 + v_windows/v/vlib/v/tests/comptime_if_expr_test.v | 125 + v_windows/v/vlib/v/tests/comptime_if_is_test.v | 28 + .../v/vlib/v/tests/comptime_if_pkgconfig_test.v | 10 + v_windows/v/vlib/v/tests/comptime_if_test.v | 54 + .../v/vlib/v/tests/comptime_if_threads_no_test.v | 11 + .../v/vlib/v/tests/comptime_if_threads_yes_test.v | 11 + .../v/vlib/v/tests/comptime_method_args_test.v | 54 + .../v/vlib/v/tests/const_can_use_optionals_test.v | 22 + .../tests/const_comptime_eval_before_vinit_test.v | 144 + v_windows/v/vlib/v/tests/const_embed_test.v | 11 + ..._eval_simple_int_expressions_at_comptime_test.v | 26 + v_windows/v/vlib/v/tests/const_init_order_test.v | 10 + .../v/vlib/v/tests/const_reference_argument_test.v | 9 + .../v/vlib/v/tests/const_representation_test.v | 39 + v_windows/v/vlib/v/tests/const_test.v | 54 + v_windows/v/vlib/v/tests/conversions_test.v | 16 + v_windows/v/vlib/v/tests/cross_assign_test.v | 159 + v_windows/v/vlib/v/tests/cstrings_test.v | 14 + v_windows/v/vlib/v/tests/defer_return_test.v | 119 + v_windows/v/vlib/v/tests/defer_test.v | 156 + .../vlib/v/tests/differently_named_structs_test.v | 30 + v_windows/v/vlib/v/tests/double_ref_deref_test.v | 10 + v_windows/v/vlib/v/tests/dump_fns_test.v | 22 + v_windows/v/vlib/v/tests/enum_array_field_test.v | 20 + v_windows/v/vlib/v/tests/enum_bitfield_test.v | 52 + .../v/tests/enum_default_value_in_struct_test.v | 35 + v_windows/v/vlib/v/tests/enum_hex_test.v | 29 + v_windows/v/vlib/v/tests/enum_test.v | 146 + v_windows/v/vlib/v/tests/failing_tests_test.v | 32 + v_windows/v/vlib/v/tests/field_publicity/embed.v | 10 + v_windows/v/vlib/v/tests/filter_in_map_test.v | 5 + .../v/vlib/v/tests/fixed_array_const_size_test.v | 46 + v_windows/v/vlib/v/tests/fixed_array_init_test.v | 48 + v_windows/v/vlib/v/tests/fixed_array_of_fn_test.v | 9 + v_windows/v/vlib/v/tests/fixed_array_test.v | 121 + .../v/vlib/v/tests/fixed_array_to_string_test.v | 41 + v_windows/v/vlib/v/tests/fn_assignment_test.v | 53 + v_windows/v/vlib/v/tests/fn_cross_assign_test.v | 68 + .../fn_expecting_ref_but_returning_struct_test.v | 27 + ...ing_ref_but_returning_struct_time_module_test.v | 19 + v_windows/v/vlib/v/tests/fn_high_test.v | 148 + .../v/vlib/v/tests/fn_index_direct_call_test.v | 33 + .../v/vlib/v/tests/fn_multiple_returns_test.v | 84 + .../v/vlib/v/tests/fn_mut_arg_of_array_test.v | 24 + v_windows/v/vlib/v/tests/fn_mut_args_test.v | 69 + v_windows/v/vlib/v/tests/fn_return_fn_test.v | 20 + v_windows/v/vlib/v/tests/fn_shared_return_test.v | 70 + v_windows/v/vlib/v/tests/fn_test.v | 174 + v_windows/v/vlib/v/tests/fn_type_aliases_test.v | 15 + v_windows/v/vlib/v/tests/fn_variadic_test.v | 99 + .../tests/fn_with_fixed_array_function_args_test.v | 23 + v_windows/v/vlib/v/tests/for_c_multi_vars_test.v | 34 + v_windows/v/vlib/v/tests/for_in_alias_test.v | 26 + .../tests/for_in_containers_of_fixed_array_test.v | 113 + v_windows/v/vlib/v/tests/for_in_iterator_test.v | 44 + .../tests/for_in_mut_reference_selector_val_test.v | 26 + v_windows/v/vlib/v/tests/for_in_mut_val_test.v | 109 + v_windows/v/vlib/v/tests/for_in_optional_test.v | 8 + .../v/vlib/v/tests/for_label_continue_break_test.v | 89 + v_windows/v/vlib/v/tests/for_loops_2_test.v | 55 + v_windows/v/vlib/v/tests/for_loops_test.v | 77 + v_windows/v/vlib/v/tests/for_smartcast_test.v | 56 + v_windows/v/vlib/v/tests/generic_chan_test.v | 22 + .../tests/generic_fn_assign_generics_struct_test.v | 65 + .../v/tests/generic_fn_infer_fixed_array_test.v | 30 + .../v/vlib/v/tests/generic_fn_infer_map_test.v | 36 + .../vlib/v/tests/generic_fn_infer_modifier_test.v | 16 + .../v/tests/generic_fn_infer_multi_paras_test.v | 50 + .../v/tests/generic_fn_infer_nested_struct_test.v | 20 + .../v/vlib/v/tests/generic_fn_infer_struct_test.v | 19 + v_windows/v/vlib/v/tests/generic_fn_infer_test.v | 42 + .../vlib/v/tests/generic_fn_infer_variadic_test.v | 17 + .../tests/generic_fn_returning_type_with_T_test.v | 40 + .../v/vlib/v/tests/generic_fn_typeof_name_test.v | 60 + .../vlib/v/tests/generic_fn_upper_name_type_test.v | 18 + .../generic_functions_with_normal_function_test.v | 12 + v_windows/v/vlib/v/tests/generic_interface_test.v | 98 + v_windows/v/vlib/v/tests/generic_sumtype_test.v | 78 + v_windows/v/vlib/v/tests/generics_T_typ_test.v | 55 + .../v/vlib/v/tests/generics_array_typedef_test.v | 31 + ...generics_assign_reference_generic_struct_test.v | 46 + .../tests/generics_call_with_reference_arg_test.v | 26 + .../generics_fn_return_generic_interface_test.v | 32 + .../generics_from_modules/genericmodule/take.v | 8 + .../v/tests/generics_from_modules/inference_test.v | 35 + .../v/tests/generics_in_big_struct_method_test.v | 27 + .../v/vlib/v/tests/generics_in_generics_test.v | 12 + v_windows/v/vlib/v/tests/generics_indirect_test.v | 32 + .../v/vlib/v/tests/generics_interface_decl_test.v | 23 + .../vlib/v/tests/generics_interface_method_test.v | 35 + ...ics_interface_with_multi_generic_structs_test.v | 58 + ...erics_interface_with_multi_generic_types_test.v | 37 + ...generics_method_on_alias_struct_receiver_test.v | 22 + .../tests/generics_method_on_receiver_types_test.v | 13 + v_windows/v/vlib/v/tests/generics_method_test.v | 70 + .../v/vlib/v/tests/generics_multi_array_in_test.v | 14 + .../tests/generics_multi_types_struct_init_test.v | 41 + .../v/tests/generics_return_generics_struct_test.v | 145 + ...eturn_inconsistent_types_generics_struct_test.v | 97 + .../v/tests/generics_return_multi_array_test.v | 25 + ...generics_return_multiple_generics_struct_test.v | 60 + ...enerics_return_recursive_generics_struct_test.v | 30 + ...enerics_return_reference_generics_struct_test.v | 23 + .../v/tests/generics_struct_anon_fn_fields_test.v | 35 + .../v/tests/generics_struct_anon_fn_type_test.v | 69 + .../v/vlib/v/tests/generics_struct_init_test.v | 15 + .../vlib/v/tests/generics_struct_to_string_test.v | 33 + .../vlib/v/tests/generics_struct_with_array_test.v | 46 + ...nerics_struct_with_non_generic_interface_test.v | 39 + v_windows/v/vlib/v/tests/generics_test.v | 490 + .../v/tests/generics_with_anon_generics_fn_test.v | 57 + ...ith_cascaded_multiple_nested_generics_fn_test.v | 35 + .../v/tests/generics_with_embed_generics_test.v | 54 + .../v/tests/generics_with_fixed_array_type_test.v | 10 + ...with_generics_fn_return_generics_fn_type_test.v | 105 + ...generics_with_generics_fn_type_parameter_test.v | 67 + .../generics_with_generics_struct_init_test.v | 48 + .../generics_with_generics_struct_receiver_test.v | 25 + ...enerics_with_multi_generics_struct_types_test.v | 19 + ...s_with_multiple_generics_struct_receiver_test.v | 28 + ...enerics_with_nested_external_generics_fn_test.v | 21 + ...generics_with_nested_generic_struct_init_test.v | 24 + .../tests/generics_with_nested_generics_fn_test.v | 27 + .../generics_with_recursive_generics_fn_test.v | 43 + .../generics_with_recursive_generics_struct_test.v | 29 + .../generics_with_variadic_generic_args_test.v | 31 + v_windows/v/vlib/v/tests/go_array_wait_test.v | 51 + v_windows/v/vlib/v/tests/go_call_generic_fn_test.v | 14 + .../v/vlib/v/tests/go_call_interface_method_test.v | 28 + .../go_handle_for_functions_returning_array_test.v | 9 + v_windows/v/vlib/v/tests/go_wait_1_test.v | 10 + v_windows/v/vlib/v/tests/go_wait_2_test.v | 23 + v_windows/v/vlib/v/tests/go_wait_3_test.v | 22 + v_windows/v/vlib/v/tests/go_wait_option_test.v | 79 + .../v/tests/go_wait_with_fn_of_interface_para.v | 17 + v_windows/v/vlib/v/tests/goto_test.v | 13 + v_windows/v/vlib/v/tests/heap_interface_test.v | 53 + v_windows/v/vlib/v/tests/heap_reference_test.v | 101 + v_windows/v/vlib/v/tests/heap_struct_test.v | 134 + .../v/vlib/v/tests/if_expr_of_multi_stmts_test.v | 15 + .../v/vlib/v/tests/if_expr_of_optional_test.v | 55 + .../v/vlib/v/tests/if_expr_with_struct_init_test.v | 11 + v_windows/v/vlib/v/tests/if_expression_test.v | 215 + v_windows/v/vlib/v/tests/if_guard_test.v | 126 + .../v/vlib/v/tests/if_smartcast_multi_conds_test.v | 47 + .../if_smartcast_nested_selector_exprs_test.v | 28 + v_windows/v/vlib/v/tests/if_smartcast_test.v | 311 + v_windows/v/vlib/v/tests/imported_symbols_test.v | 53 + v_windows/v/vlib/v/tests/in_expression_test.v | 266 + v_windows/v/vlib/v/tests/infix_expr_test.v | 83 + v_windows/v/vlib/v/tests/init_global_test.v | 174 + v_windows/v/vlib/v/tests/inout/.gitignore | 3 + v_windows/v/vlib/v/tests/inout/bad_st_as.out | 5 + v_windows/v/vlib/v/tests/inout/bad_st_as.vv | 13 + .../vlib/v/tests/inout/cli_command_no_execute.out | 0 .../v/vlib/v/tests/inout/cli_command_no_execute.vv | 11 + .../v/vlib/v/tests/inout/cli_root_default_help.out | 7 + .../v/vlib/v/tests/inout/cli_root_default_help.vv | 7 + v_windows/v/vlib/v/tests/inout/compiler_test.v | 101 + v_windows/v/vlib/v/tests/inout/dump_expression.out | 17 + v_windows/v/vlib/v/tests/inout/dump_expression.vv | 38 + v_windows/v/vlib/v/tests/inout/enum_print.out | 8 + v_windows/v/vlib/v/tests/inout/enum_print.vv | 26 + v_windows/v/vlib/v/tests/inout/file.html | 37 + v_windows/v/vlib/v/tests/inout/file.md | 8 + .../v/vlib/v/tests/inout/fixed_array_index.out | 6 + .../v/vlib/v/tests/inout/fixed_array_index.vv | 4 + .../v/vlib/v/tests/inout/fixed_array_slice.out | 5 + .../v/vlib/v/tests/inout/fixed_array_slice.vv | 3 + v_windows/v/vlib/v/tests/inout/footer.md | 1 + v_windows/v/vlib/v/tests/inout/header.md | 1 + v_windows/v/vlib/v/tests/inout/hello.out | 1 + v_windows/v/vlib/v/tests/inout/hello.vv | 3 + v_windows/v/vlib/v/tests/inout/hello_devs.out | 6 + v_windows/v/vlib/v/tests/inout/hello_devs.vv | 7 + v_windows/v/vlib/v/tests/inout/nested_structs.out | 11 + v_windows/v/vlib/v/tests/inout/nested_structs.vv | 22 + v_windows/v/vlib/v/tests/inout/os.out | 6 + v_windows/v/vlib/v/tests/inout/os.vv | 13 + v_windows/v/vlib/v/tests/inout/panic_with_cg.out | 5 + v_windows/v/vlib/v/tests/inout/panic_with_cg.vv | 8 + .../inout/printing_fixed_array_of_pointers.out | 60 + .../inout/printing_fixed_array_of_pointers.vv | 47 + v_windows/v/vlib/v/tests/inout/string_interp.out | 1 + v_windows/v/vlib/v/tests/inout/string_interp.vv | 5 + .../vlib/v/tests/inout/tmpl_all_in_one_folder.out | 8 + .../v/vlib/v/tests/inout/tmpl_all_in_one_folder.vv | 8 + v_windows/v/vlib/v/tests/inout/tmpl_parse_html.out | 37 + v_windows/v/vlib/v/tests/inout/tmpl_parse_html.vv | 8 + v_windows/v/vlib/v/tests/int_cmp_test.v | 22 + .../v/vlib/v/tests/interface_auto_str_gen_test.v | 50 + .../array_of_interfaces_test.v | 28 + .../array_of_interfaces_with_utility_fn_test.v | 28 + .../assign_to_interface_field_test.v | 27 + .../interface_edge_cases/empty_interface_1_test.v | 16 + .../empty_interface_println_test.v | 11 + ...fn_returning_voidptr_casted_as_interface_test.v | 27 + .../v/vlib/v/tests/interface_edge_cases/i1_test.v | 31 + .../v/vlib/v/tests/interface_edge_cases/i2_test.v | 34 + .../v/vlib/v/tests/interface_edge_cases/i3_test.v | 33 + .../v/vlib/v/tests/interface_edge_cases/i4_test.v | 31 + .../v/vlib/v/tests/interface_edge_cases/i5_test.v | 31 + .../v/vlib/v/tests/interface_edge_cases/i6_test.v | 31 + .../v/vlib/v/tests/interface_edge_cases/i7_test.v | 38 + .../v/vlib/v/tests/interface_edge_cases/i8_test.v | 31 + .../v/vlib/v/tests/interface_edge_cases/i9_test.v | 35 + .../interface_many_named_test.v | 26 + .../pass_voidptr_as_interface_reference_test.v | 19 + .../voidptr_casted_as_an_interface_test.v | 26 + .../tests/interface_embedding_deep_nesting_test.v | 415 + .../v/tests/interface_embedding_recursive_test.v | 78 + .../v/vlib/v/tests/interface_embedding_test.v | 56 + .../interface_embedding_with_interface_para_test.v | 38 + v_windows/v/vlib/v/tests/interface_fields_test.v | 74 + .../vlib/v/tests/interface_fields_typearray_test.v | 7 + .../interface_fn_return_array_of_interface_test.v | 26 + .../v/tests/interface_fn_return_with_struct_init.v | 34 + .../v/vlib/v/tests/interface_nested_field_test.v | 75 + .../tests/interface_only_decl_with_optional_test.v | 8 + .../v/tests/interface_runtime_conversions_test.v | 48 + v_windows/v/vlib/v/tests/interface_struct_test.v | 117 + v_windows/v/vlib/v/tests/interface_test.v | 338 + v_windows/v/vlib/v/tests/interface_variadic_test.v | 42 + v_windows/v/vlib/v/tests/interfaces_map_test.v | 54 + v_windows/v/vlib/v/tests/interop_test.v | 23 + v_windows/v/vlib/v/tests/isreftype_test.v | 58 + v_windows/v/vlib/v/tests/keep_args_alive_test.v | 75 + v_windows/v/vlib/v/tests/keep_args_alive_test_c.h | 44 + v_windows/v/vlib/v/tests/local/local.v | 5 + v_windows/v/vlib/v/tests/local_test.v | 5 + v_windows/v/vlib/v/tests/lock_selector_test.v | 50 + v_windows/v/vlib/v/tests/map_alias_key_test.v | 34 + .../v/vlib/v/tests/map_and_array_with_fns_test.v | 122 + .../v/tests/map_assign_array_of_interface_test.v | 40 + .../v/vlib/v/tests/map_complex_fixed_array_test.v | 45 + v_windows/v/vlib/v/tests/map_enum_keys_test.v | 38 + v_windows/v/vlib/v/tests/map_equality_test.v | 38 + .../v/vlib/v/tests/map_high_order_assign_test.v | 6 + v_windows/v/vlib/v/tests/map_key_expr_test.v | 31 + .../v/vlib/v/tests/map_literals_method_call_test.v | 10 + v_windows/v/vlib/v/tests/map_mut_fn_test.v | 20 + v_windows/v/vlib/v/tests/map_to_string_test.v | 49 + v_windows/v/vlib/v/tests/map_type_alias_test.v | 8 + v_windows/v/vlib/v/tests/map_value_init_test.v | 9 + v_windows/v/vlib/v/tests/maps_equal_test.v | 13 + .../v/vlib/v/tests/match_compound_type_cond_test.v | 49 + .../v/vlib/v/tests/match_error_to_none_test.v | 25 + .../v/tests/match_expr_returning_optional_test.v | 36 + .../tests/match_expr_with_if_or_match_expr_test.v | 43 + .../vlib/v/tests/match_expr_with_one_branch_test.v | 12 + .../v/tests/match_expr_with_promote_number_test.v | 34 + .../vlib/v/tests/match_expression_for_types_test.v | 64 + ...tch_expression_with_fn_names_in_branches_test.v | 63 + v_windows/v/vlib/v/tests/match_in_fn_call_test.v | 49 + .../v/vlib/v/tests/match_in_if_expression_test.v | 13 + v_windows/v/vlib/v/tests/match_in_map_init_test.v | 28 + .../v/vlib/v/tests/match_in_map_or_expr_test.v | 15 + v_windows/v/vlib/v/tests/match_interface_test.v | 22 + v_windows/v/vlib/v/tests/match_smartcast_test.v | 99 + .../v/tests/match_sumtype_var_aggregate_test.v | 21 + .../v/tests/match_sumtype_var_shadow_and_as_test.v | 44 + v_windows/v/vlib/v/tests/match_test.v | 303 + .../match_with_complex_exprs_in_branches_test.v | 44 + .../tests/match_with_complex_sumtype_exprs_test.v | 153 + .../v/tests/match_with_multi_sumtype_exprs_test.v | 37 + .../v/vlib/v/tests/methods_on_interfaces_test.v | 20 + .../vlib/v/tests/missing_config_struct_arg_test.v | 24 + v_windows/v/vlib/v/tests/module_test.v | 46 + v_windows/v/vlib/v/tests/module_type_cast_test.v | 8 + .../tests/modules/acommentedmodule/commentedfile.v | 3 + .../modules/amodule/another_internal_module_test.v | 14 + .../v/tests/modules/amodule/internal_module_test.v | 15 + v_windows/v/vlib/v/tests/modules/amodule/module.v | 14 + .../v/vlib/v/tests/modules/geometry/geometry.v | 50 + .../methods_struct_test.v | 25 + .../v/tests/modules/simplemodule/importing_test.v | 11 + .../v/tests/modules/simplemodule/simplemodule.v | 20 + .../v/vlib/v/tests/modules/submodules/submodules.v | 10 + .../v/tests/modules/submodules/submodules_test.v | 21 + .../v/vlib/v/tests/modules/submodules/test/test.v | 10 + .../v/tests/modules/submodules/test/test2/test2.v | 10 + .../v/tests/multiple_assign_array_index_test.v | 24 + v_windows/v/vlib/v/tests/multiple_assign_test.v | 66 + .../v/tests/multiple_paths_in_vmodules/main.vv | 9 + .../multiple_paths_in_vmodules/path1/.gitignore | 4 + .../tests/multiple_paths_in_vmodules/path1/xxx/m.v | 5 + .../tests/multiple_paths_in_vmodules/path2/yyy/m.v | 5 + .../tests/multiple_paths_in_vmodules/path3/zzz/m.v | 5 + .../vmodules_overrides_test.v | 33 + .../v/vlib/v/tests/multiret_with_ptrtype_test.v | 16 + .../mut_receiver_returned_as_reference_test.v | 36 + v_windows/v/vlib/v/tests/mut_test.v | 370 + .../v/vlib/v/tests/named_break_continue_test.v | 46 + v_windows/v/vlib/v/tests/nest_defer_fn_test.v | 30 + .../v/tests/nested_anonfunc_and_for_break_test.v | 16 + v_windows/v/vlib/v/tests/nested_map_test.v | 34 + .../vlib/v/tests/nested_multiline_comments_test.v | 5 + v_windows/v/vlib/v/tests/nested_option_call_test.v | 67 + .../v/vlib/v/tests/num_lit_call_method_test.v | 37 + v_windows/v/vlib/v/tests/offsetof_test.v | 43 + .../v/vlib/v/tests/operator_overloading_cmp_test.v | 35 + ...or_overloading_with_string_interpolation_test.v | 83 + v_windows/v/vlib/v/tests/option_2_test.v | 66 + .../v/vlib/v/tests/option_default_values_test.v | 136 + .../tests/option_if_assign_and_fallthrough_test.v | 46 + v_windows/v/vlib/v/tests/option_if_expr_test.v | 16 + v_windows/v/vlib/v/tests/option_in_loop_test.v | 36 + .../v/vlib/v/tests/option_print_errors_test.v | 20 + v_windows/v/vlib/v/tests/option_struct_init_test.v | 22 + v_windows/v/vlib/v/tests/option_test.v | 381 + v_windows/v/vlib/v/tests/option_void_test.v | 50 + v_windows/v/vlib/v/tests/optional_assign_test.v | 25 + .../v/vlib/v/tests/orm_sub_array_struct_test.v | 49 + v_windows/v/vlib/v/tests/orm_sub_struct_test.v | 34 + v_windows/v/vlib/v/tests/parser_line_test.v | 9 + v_windows/v/vlib/v/tests/pointer_arithmetic_test.v | 6 + .../vlib/v/tests/pointers_multilevel_casts_test.v | 76 + v_windows/v/vlib/v/tests/pointers_str_test.v | 8 + v_windows/v/vlib/v/tests/pointers_test.v | 46 + v_windows/v/vlib/v/tests/prefix_expr_test.v | 29 + v_windows/v/vlib/v/tests/print_test.v | 4 + .../v/vlib/v/tests/printing_c_structs/cstruct.h | 4 + .../printing_c_structs/string_interpolation_test.v | 26 + v_windows/v/vlib/v/tests/printing_c_structs/v.mod | 0 v_windows/v/vlib/v/tests/prod/.gitignore | 2 + .../v/tests/prod/asserts_should_be_skipped.prod.v | 4 + .../asserts_should_be_skipped.prod.v.expected.txt | 1 + v_windows/v/vlib/v/tests/prod/assoc.prod.v | 27 + .../v/vlib/v/tests/prod/assoc.prod.v.expected.txt | 4 + v_windows/v/vlib/v/tests/prod_test.v | 28 + v_windows/v/vlib/v/tests/profile/profile_test.v | 23 + v_windows/v/vlib/v/tests/profile/profile_test_1.v | 11 + .../v/vlib/v/tests/project_with_c_code/.gitignore | 3 + .../v/vlib/v/tests/project_with_c_code/.v.mod.stop | 4 + .../v/vlib/v/tests/project_with_c_code/main.v | 9 + .../v/vlib/v/tests/project_with_c_code/main_test.v | 5 + .../v/tests/project_with_c_code/mod1/c/header.h | 10 + .../project_with_c_code/mod1/c/implementation.c | 5 + .../v/vlib/v/tests/project_with_c_code/mod1/v.mod | 5 + .../v/tests/project_with_c_code/mod1/wrapper.v | 17 + .../vlib/v/tests/project_with_c_code_2/.gitignore | 4 + .../vlib/v/tests/project_with_c_code_2/.v.mod.stop | 5 + .../v/vlib/v/tests/project_with_c_code_2/main.v | 14 + .../v/tests/project_with_c_code_2/main2_test.v | 7 + .../v/tests/project_with_c_code_2/modc/header.h | 19 + .../vlib/v/tests/project_with_c_code_2/modc/impl.c | 32 + .../vlib/v/tests/project_with_c_code_2/modc/v.mod | 5 + .../v/tests/project_with_c_code_2/modc/wrapper.v | 53 + .../vlib/v/tests/project_with_c_code_3/.gitignore | 3 + .../vlib/v/tests/project_with_c_code_3/.v.mod.stop | 4 + .../v/vlib/v/tests/project_with_c_code_3/main.v | 15 + .../vlib/v/tests/project_with_c_code_3/main_test.v | 13 + .../vlib/v/tests/project_with_c_code_3/mod1/v.mod | 5 + .../v/tests/project_with_c_code_3/mod1/wrapper.c.v | 5 + .../tests/project_with_c_code_3/mod1/wrapper.js.v | 6 + .../v/tests/project_with_c_code_3/mod1/wrapper.v | 11 + .../v/tests/project_with_c_code_ct_ifs/a_linux.h | 1 + .../tests/project_with_c_code_ct_ifs/a_nonlinux.h | 1 + .../project_with_c_code_ct_ifs/ctimeifblock.v | 11 + .../vlib/v/tests/project_with_c_code_ct_ifs/v.mod | 0 .../.gitignore | 2 + .../README.md | 16 + .../bin/a_program_under_bin_can_find_mod1_test.v | 12 + .../bin/main.vsh | 14 + .../mod1/m.v | 5 + .../mod1/mod11/m.v | 5 + .../mod1/mod12/m.v | 5 + .../mod1/mod13/m.v | 5 + .../mod1/mod14/m.v | 7 + .../mod1/submodule/m.v | 15 + .../mod1/v.mod | 7 + .../tests/submodule_test.v | 10 + .../project_with_modules_having_submodules/v.mod | 7 + .../v/tests/project_with_tests_for_main/README.md | 25 + .../v/tests/project_with_tests_for_main/main.v | 8 + .../project_with_tests_for_main/my_other_test.v | 7 + .../v/tests/project_with_tests_for_main/my_test.v | 11 + .../vlib/v/tests/project_with_tests_for_main/v.mod | 5 + .../v/tests/projects_that_should_compile_test.v | 25 + v_windows/v/vlib/v/tests/ptr_arithmetic_test.v | 70 + v_windows/v/vlib/v/tests/raw_string_test.v | 3 + v_windows/v/vlib/v/tests/ref_return_test.v | 14 + v_windows/v/vlib/v/tests/ref_struct_test.v | 96 + v_windows/v/vlib/v/tests/reference_return_test.v | 25 + .../v/vlib/v/tests/reliability/semaphore_wait.v | 142 + .../v/vlib/v/tests/repeated_multiret_values_test.v | 13 + v_windows/v/vlib/v/tests/repl/.gitattributes | 2 + v_windows/v/vlib/v/tests/repl/.gitignore | 3 + v_windows/v/vlib/v/tests/repl/README.md | 25 + v_windows/v/vlib/v/tests/repl/array_filter.repl | 3 + v_windows/v/vlib/v/tests/repl/array_init.repl | 3 + .../v/vlib/v/tests/repl/bad_in_type.repl.skip | 4 + .../vlib/v/tests/repl/chained_fields/bd.repl.skip | 17 + .../v/vlib/v/tests/repl/chained_fields/c.repl.skip | 17 + .../vlib/v/tests/repl/chained_fields/c2.repl.skip | 5 + .../v/vlib/v/tests/repl/chained_fields/d.repl.skip | 5 + .../vlib/v/tests/repl/chained_fields/ef.repl.skip | 19 + .../vlib/v/tests/repl/conditional_blocks/for.repl | 8 + .../v/vlib/v/tests/repl/conditional_blocks/if.repl | 5 + .../v/tests/repl/conditional_blocks/if_else.repl | 7 + .../v/vlib/v/tests/repl/default_printing.repl | 5 + .../v/vlib/v/tests/repl/empty_struct.repl.skip | 4 + .../vlib/v/tests/repl/entire_commented_module.repl | 7 + v_windows/v/vlib/v/tests/repl/error.repl | 7 + .../v/vlib/v/tests/repl/error_nosave.repl.skip | 5 + v_windows/v/vlib/v/tests/repl/function.repl.skip | 4 + .../repl/immutable_len_fields/fields.1.repl.skip | 11 + .../repl/immutable_len_fields/fields.2.repl.skip | 11 + .../repl/immutable_len_fields/fields.3.repl.skip | 11 + v_windows/v/vlib/v/tests/repl/import.repl | 4 + v_windows/v/vlib/v/tests/repl/naked_strings.repl | 3 + v_windows/v/vlib/v/tests/repl/newlines.repl | 3 + v_windows/v/vlib/v/tests/repl/nomain.repl | 4 + v_windows/v/vlib/v/tests/repl/nothing.repl | 1 + .../vlib/v/tests/repl/open_close_string_check.repl | 5 + v_windows/v/vlib/v/tests/repl/option.repl.skip | 7 + v_windows/v/vlib/v/tests/repl/optional_call.repl | 3 + .../v/vlib/v/tests/repl/postfix_operators.repl | 4 + v_windows/v/vlib/v/tests/repl/println.repl | 3 + v_windows/v/vlib/v/tests/repl/repl_test.v | 88 + v_windows/v/vlib/v/tests/repl/runner/runner.v | 145 + v_windows/v/vlib/v/tests/repl/var_decl.repl | 4 + ...reserved_keywords_can_be_escaped_with_at_test.v | 13 + v_windows/v/vlib/v/tests/return_in_lock_test.v | 53 + v_windows/v/vlib/v/tests/return_voidptr_test.v | 26 + .../v/tests/reusable_mut_multiret_values_test.v | 23 + .../a.v | 8 + .../b.v | 14 + .../v/vlib/v/tests/run_project_folders_test.v | 36 + .../v/vlib/v/tests/run_v_code_from_stdin_test.v | 28 + v_windows/v/vlib/v/tests/semaphore_test.v | 25 + v_windows/v/vlib/v/tests/semaphore_timed_test.v | 28 + v_windows/v/vlib/v/tests/shared_arg_test.v | 94 + v_windows/v/vlib/v/tests/shared_array_test.v | 74 + v_windows/v/vlib/v/tests/shared_autolock_test.v | 38 + v_windows/v/vlib/v/tests/shared_elem_test.v | 170 + v_windows/v/vlib/v/tests/shared_fn_return_test.v | 68 + v_windows/v/vlib/v/tests/shared_in_test.v | 8 + v_windows/v/vlib/v/tests/shared_lock_2_test.v | 52 + v_windows/v/vlib/v/tests/shared_lock_3_test.v | 57 + v_windows/v/vlib/v/tests/shared_lock_4_test.v | 57 + v_windows/v/vlib/v/tests/shared_lock_5_test.v | 68 + v_windows/v/vlib/v/tests/shared_lock_6_test.v | 59 + v_windows/v/vlib/v/tests/shared_lock_expr_test.v | 123 + v_windows/v/vlib/v/tests/shared_lock_test.v | 77 + v_windows/v/vlib/v/tests/shared_map_test.v | 157 + .../v/vlib/v/tests/shared_unordered_mixed_test.v | 89 + v_windows/v/vlib/v/tests/shift_test.v | 71 + .../vlib/v/tests/short_struct_param_syntax_test.v | 18 + v_windows/v/vlib/v/tests/sizeof_2_test.v | 8 + v_windows/v/vlib/v/tests/sizeof_test.v | 26 + .../v/tests/skip_unused/assert_passes_test.run.out | 0 .../assert_passes_test.skip_unused.run.out | 0 .../vlib/v/tests/skip_unused/assert_passes_test.vv | 3 + .../v/tests/skip_unused/assert_works_test.run.out | 5 + .../assert_works_test.skip_unused.run.out | 5 + .../vlib/v/tests/skip_unused/assert_works_test.vv | 3 + v_windows/v/vlib/v/tests/skip_unused/hw.run.out | 1 + .../v/tests/skip_unused/hw.skip_unused.run.out | 1 + v_windows/v/vlib/v/tests/skip_unused/hw.vv | 1 + .../skip_unused/println_os_executable.run.out | 1 + .../println_os_executable.skip_unused.run.out | 1 + .../v/tests/skip_unused/println_os_executable.vv | 7 + .../v/tests/sorting_by_different_criteria_test.v | 31 + .../v/vlib/v/tests/sorting_by_references_test.v | 32 + .../static_arrays_using_const_for_size_test.v | 39 + v_windows/v/vlib/v/tests/static_vars_test.v | 16 + v_windows/v/vlib/v/tests/str_circular_test.v | 21 + v_windows/v/vlib/v/tests/str_gen_test.v | 446 + .../v/vlib/v/tests/string_alias_of_struct_test.v | 27 + v_windows/v/vlib/v/tests/string_alias_test.v | 17 + .../v/vlib/v/tests/string_escape_backslash_test.v | 5 + v_windows/v/vlib/v/tests/string_index_or_test.v | 52 + .../vlib/v/tests/string_interpolation_alias_test.v | 28 + .../vlib/v/tests/string_interpolation_array_test.v | 110 + .../v/tests/string_interpolation_custom_str_test.v | 20 + .../v/tests/string_interpolation_floats_test.v | 29 + .../v/tests/string_interpolation_function_test.v | 53 + .../tests/string_interpolation_multi_return_test.v | 47 + .../v/tests/string_interpolation_multistmt_test.v | 16 + ...string_interpolation_of_array_of_structs_test.v | 113 + .../v/tests/string_interpolation_shared_test.v | 66 + .../tests/string_interpolation_string_args_test.v | 31 + .../v/tests/string_interpolation_struct_test.v | 74 + .../v/tests/string_interpolation_sumtype_test.v | 9 + .../v/vlib/v/tests/string_interpolation_test.v | 201 + .../v/tests/string_interpolation_variadic_test.v | 44 + .../v/tests/string_struct_interpolation_test.v | 39 + v_windows/v/vlib/v/tests/strings_unicode_test.v | 10 + ..._allow_both_field_defaults_and_skip_flag_test.v | 23 + .../v/tests/struct_chained_fields_correct_test.v | 47 + .../vlib/v/tests/struct_child_field_default_test.v | 22 + v_windows/v/vlib/v/tests/struct_embed_test.v | 224 + v_windows/v/vlib/v/tests/struct_eq_op_only_test.v | 15 + v_windows/v/vlib/v/tests/struct_equality_test.v | 25 + ...truct_field_default_value_interface_cast_test.v | 19 + .../struct_field_default_value_sumtype_cast_test.v | 19 + .../v/vlib/v/tests/struct_fields_required_test.v | 32 + .../v/tests/struct_fields_storing_functions_test.v | 29 + v_windows/v/vlib/v/tests/struct_ierror_test.v | 10 + .../v/vlib/v/tests/struct_init_and_assign_test.v | 26 + .../struct_init_with_fixed_array_field_test.v | 28 + v_windows/v/vlib/v/tests/struct_map_method_test.v | 35 + v_windows/v/vlib/v/tests/struct_test.v | 416 + v_windows/v/vlib/v/tests/struct_transmute_test.v | 42 + ...ructs_with_voidptr_fields_can_be_printed_test.v | 13 + .../v/vlib/v/tests/sum_type_common_fields_test.v | 47 + v_windows/v/vlib/v/tests/sum_type_test.v | 759 ++ v_windows/v/vlib/v/tests/sumtype_assign_test.v | 46 + v_windows/v/vlib/v/tests/sumtype_calls_test.v | 77 + v_windows/v/vlib/v/tests/sumtype_equality_test.v | 33 + v_windows/v/vlib/v/tests/sumtype_literal_test.v | 84 + .../tests/sumtype_str_for_subtypes_with_str_test.v | 43 + v_windows/v/vlib/v/tests/sumtype_str_test.v | 77 + v_windows/v/vlib/v/tests/supports__likely__test.v | 32 + v_windows/v/vlib/v/tests/testcase_leak.v | 12 + .../v/tests/testdata/enum_in_builtin/builtin.v | 18 + .../v/vlib/v/tests/testdata/enum_in_builtin/c.v | 6 + .../v/vlib/v/tests/testdata/enum_in_builtin/main.v | 5 + .../v/tests/testdata/sizeof_used_in_assert_test.v | 16 + .../tests_returning_options_failing_test.v | 20 + v_windows/v/vlib/v/tests/thread_to_string_test.v | 8 + v_windows/v/vlib/v/tests/tmpl/base.txt | 9 + v_windows/v/vlib/v/tests/tmpl/inner.txt | 17 + v_windows/v/vlib/v/tests/tmpl_test.v | 84 + .../v/tests/type_alias_of_pointer_types_test.v | 74 + .../v/tests/type_alias_str_method_override_test.v | 29 + v_windows/v/vlib/v/tests/type_alias_test.v | 42 + v_windows/v/vlib/v/tests/type_idx_test.v | 101 + v_windows/v/vlib/v/tests/type_name_in_if_test.v | 13 + v_windows/v/vlib/v/tests/type_name_test.v | 18 + v_windows/v/vlib/v/tests/type_promotion_test.v | 100 + v_windows/v/vlib/v/tests/type_voidptr_test.v | 21 + .../v/vlib/v/tests/typeof_simple_types_test.v | 72 + v_windows/v/vlib/v/tests/typeof_test.v | 187 + .../v/vlib/v/tests/unreachable_code_paths_test.v | 11 + v_windows/v/vlib/v/tests/unsafe_test.v | 104 + .../v/vlib/v/tests/valgrind/1.strings_and_arrays.v | 410 + .../valgrind/array_init_with_string_variable.v | 11 + v_windows/v/vlib/v/tests/valgrind/base64.v | 59 + .../buffer_passed_in_fn_that_uses_tos_on_it.v | 7 + .../v/tests/valgrind/fn_returning_string_param.v | 13 + .../fn_with_return_should_free_local_vars.v | 22 + v_windows/v/vlib/v/tests/valgrind/if_expr.v | 11 + v_windows/v/vlib/v/tests/valgrind/if_expr_skip.v | 13 + v_windows/v/vlib/v/tests/valgrind/logging.v | 18 + .../v/vlib/v/tests/valgrind/option_reassigned.v | 7 + v_windows/v/vlib/v/tests/valgrind/option_simple.v | 6 + .../v/vlib/v/tests/valgrind/rune_str_method.v | 4 + .../v/vlib/v/tests/valgrind/simple_interpolation.v | 5 + .../valgrind/simple_interpolation_script_mode.v | 4 + .../simple_interpolation_script_mode_more_scopes.v | 7 + .../v/vlib/v/tests/valgrind/string_str_method.v | 4 + v_windows/v/vlib/v/tests/valgrind/struct_field.v | 17 + v_windows/v/vlib/v/tests/valgrind/valgrind_test.v | 113 + .../tests/vargs_auto_str_method_and_println_test.v | 50 + v_windows/v/vlib/v/tests/vargs_empty_param_test.v | 20 + .../v/vlib/v/tests/vargs_reference_param_test.v | 27 + v_windows/v/vlib/v/tests/vmod_parser_test.v | 51 + .../v/vlib/v/tests/voidptr_to_u64_cast_a_test.v | 10 + .../v/vlib/v/tests/voidptr_to_u64_cast_b_test.v | 10 + .../v/tests/working_with_an_empty_struct_test.v | 16 + v_windows/v/vlib/v/token/position.v | 47 + v_windows/v/vlib/v/token/token.v | 490 + v_windows/v/vlib/v/transformer/transformer.v | 220 + v_windows/v/vlib/v/util/diff.v | 17 + v_windows/v/vlib/v/util/diff/diff.v | 85 + v_windows/v/vlib/v/util/errors.v | 186 + v_windows/v/vlib/v/util/module.v | 122 + v_windows/v/vlib/v/util/quote.v | 134 + .../v/vlib/v/util/recompilation/recompilation.v | 26 + v_windows/v/vlib/v/util/scanning.v | 44 + v_windows/v/vlib/v/util/suggestions.v | 95 + v_windows/v/vlib/v/util/timers.v | 133 + v_windows/v/vlib/v/util/util.v | 526 + v_windows/v/vlib/v/util/version/version.v | 85 + v_windows/v/vlib/v/util/vtest/vtest.v | 37 + v_windows/v/vlib/v/vcache/vcache.v | 131 + v_windows/v/vlib/v/vcache/vcache_test.v | 101 + v_windows/v/vlib/v/vet/vet.v | 36 + v_windows/v/vlib/v/vmod/parser.v | 265 + v_windows/v/vlib/v/vmod/vmod.v | 171 + v_windows/v/vlib/vweb/README.md | 141 + v_windows/v/vlib/vweb/assets/assets.v | 201 + v_windows/v/vlib/vweb/assets/assets_test.v | 179 + v_windows/v/vlib/vweb/request.v | 157 + v_windows/v/vlib/vweb/request_test.v | 138 + v_windows/v/vlib/vweb/route_test.v | 282 + v_windows/v/vlib/vweb/sse/sse.v | 77 + v_windows/v/vlib/vweb/tests/vweb_test.v | 300 + v_windows/v/vlib/vweb/tests/vweb_test_server.v | 118 + v_windows/v/vlib/vweb/vweb.v | 714 ++ v_windows/v/vlib/vweb/vweb_app_test.v | 66 + v_windows/v/vlib/x/json2/README.md | 175 + v_windows/v/vlib/x/json2/any_test.v | 130 + v_windows/v/vlib/x/json2/decoder.v | 200 + v_windows/v/vlib/x/json2/decoder_test.v | 61 + v_windows/v/vlib/x/json2/encoder.v | 179 + v_windows/v/vlib/x/json2/encoder_test.v | 29 + v_windows/v/vlib/x/json2/json2.v | 122 + v_windows/v/vlib/x/json2/json2_test.v | 398 + v_windows/v/vlib/x/json2/scanner.v | 306 + v_windows/v/vlib/x/json2/scanner_test.v | 351 + v_windows/v/vlib/x/ttf/README.md | 310 + v_windows/v/vlib/x/ttf/common.v | 205 + v_windows/v/vlib/x/ttf/render_bmp.v | 825 ++ v_windows/v/vlib/x/ttf/render_sokol_cpu.v | 210 + v_windows/v/vlib/x/ttf/text_block.v | 120 + v_windows/v/vlib/x/ttf/ttf.v | 1085 ++ v_windows/v/vlib/x/ttf/ttf_test.v | 237 + v_windows/v/vlib/x/ttf/ttf_test_data.bin | Bin 0 -> 16124 bytes 3072 files changed, 231134 insertions(+) create mode 100644 v_windows/v/vlib/.vdocignore create mode 100644 v_windows/v/vlib/arrays/arrays.v create mode 100644 v_windows/v/vlib/arrays/arrays_test.v create mode 100644 v_windows/v/vlib/benchmark/README.md create mode 100644 v_windows/v/vlib/benchmark/benchmark.v create mode 100644 v_windows/v/vlib/bitfield/README.md create mode 100644 v_windows/v/vlib/bitfield/bitfield.v create mode 100644 v_windows/v/vlib/bitfield/bitfield_test.v create mode 100644 v_windows/v/vlib/builtin/array.c.v create mode 100644 v_windows/v/vlib/builtin/array.v create mode 100644 v_windows/v/vlib/builtin/array_d_gcboehm_opt.v create mode 100644 v_windows/v/vlib/builtin/array_notd_gcboehm_opt.v create mode 100644 v_windows/v/vlib/builtin/array_test.v create mode 100644 v_windows/v/vlib/builtin/builtin.c.v create mode 100644 v_windows/v/vlib/builtin/builtin.v create mode 100644 v_windows/v/vlib/builtin/builtin_d_gcboehm.c.v create mode 100644 v_windows/v/vlib/builtin/builtin_ios.c.v create mode 100644 v_windows/v/vlib/builtin/builtin_nix.c.v create mode 100644 v_windows/v/vlib/builtin/builtin_notd_gcboehm.c.v create mode 100644 v_windows/v/vlib/builtin/builtin_windows.c.v create mode 100644 v_windows/v/vlib/builtin/byte_test.v create mode 100644 v_windows/v/vlib/builtin/cfns.c.v create mode 100644 v_windows/v/vlib/builtin/cfns_wrapper.c.v create mode 100644 v_windows/v/vlib/builtin/chan.v create mode 100644 v_windows/v/vlib/builtin/float.c.v create mode 100644 v_windows/v/vlib/builtin/float_test.v create mode 100644 v_windows/v/vlib/builtin/float_x64.v create mode 100644 v_windows/v/vlib/builtin/int.v create mode 100644 v_windows/v/vlib/builtin/int_test.v create mode 100644 v_windows/v/vlib/builtin/isnil_test.v create mode 100644 v_windows/v/vlib/builtin/js/array.js.v create mode 100644 v_windows/v/vlib/builtin/js/builtin.js.v create mode 100644 v_windows/v/vlib/builtin/js/builtin.v create mode 100644 v_windows/v/vlib/builtin/js/byte.js.v create mode 100644 v_windows/v/vlib/builtin/js/int.js.v create mode 100644 v_windows/v/vlib/builtin/js/jsfns.js.v create mode 100644 v_windows/v/vlib/builtin/js/jsfns_browser.js.v create mode 100644 v_windows/v/vlib/builtin/js/jsfns_node.js.v create mode 100644 v_windows/v/vlib/builtin/js/map.js.v create mode 100644 v_windows/v/vlib/builtin/js/string.js.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/libc_impl.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/linux_syscalls.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/memory_managment.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/.gitignore create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/checks.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/consts/consts.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/forkedtest/forkedtest.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/linuxsys/linuxsys.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/readme.md create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/sample_text1.txt create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/string/string.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/.checks/structs/structs.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/array_bare.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/builtin_bare.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/linuxsys_bare.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/mm_bare.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/string_bare.v create mode 100644 v_windows/v/vlib/builtin/linux_bare/old/syscallwrapper_test.v create mode 100644 v_windows/v/vlib/builtin/map.c.v create mode 100644 v_windows/v/vlib/builtin/map.v create mode 100644 v_windows/v/vlib/builtin/map_d_gcboehm_opt.v create mode 100644 v_windows/v/vlib/builtin/map_of_floats_test.v create mode 100644 v_windows/v/vlib/builtin/map_test.v create mode 100644 v_windows/v/vlib/builtin/option.c.v create mode 100644 v_windows/v/vlib/builtin/option.v create mode 100644 v_windows/v/vlib/builtin/prealloc.c.v create mode 100644 v_windows/v/vlib/builtin/rune.v create mode 100644 v_windows/v/vlib/builtin/sorted_map.v create mode 100644 v_windows/v/vlib/builtin/sorting_test.v create mode 100644 v_windows/v/vlib/builtin/string.v create mode 100644 v_windows/v/vlib/builtin/string_charptr_byteptr_helpers.v create mode 100644 v_windows/v/vlib/builtin/string_int_test.v create mode 100644 v_windows/v/vlib/builtin/string_interpolation.v create mode 100644 v_windows/v/vlib/builtin/string_strip_margin_test.v create mode 100644 v_windows/v/vlib/builtin/string_test.v create mode 100644 v_windows/v/vlib/builtin/utf8.c.v create mode 100644 v_windows/v/vlib/builtin/utf8.v create mode 100644 v_windows/v/vlib/builtin/utf8_test.v create mode 100644 v_windows/v/vlib/cli/README.md create mode 100644 v_windows/v/vlib/cli/command.v create mode 100644 v_windows/v/vlib/cli/command_test.v create mode 100644 v_windows/v/vlib/cli/flag.v create mode 100644 v_windows/v/vlib/cli/flag_test.v create mode 100644 v_windows/v/vlib/cli/help.v create mode 100644 v_windows/v/vlib/cli/help_test.v create mode 100644 v_windows/v/vlib/cli/version.v create mode 100644 v_windows/v/vlib/clipboard/clipboard.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_android.c.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_darwin.c.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_darwin.m create mode 100644 v_windows/v/vlib/clipboard/clipboard_default.c.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_solaris.c.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_test.v create mode 100644 v_windows/v/vlib/clipboard/clipboard_windows.c.v create mode 100644 v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v create mode 100644 v_windows/v/vlib/clipboard/x11/clipboard.c.v create mode 100644 v_windows/v/vlib/context/README.md create mode 100644 v_windows/v/vlib/context/_context.v create mode 100644 v_windows/v/vlib/context/cancel.v create mode 100644 v_windows/v/vlib/context/cancel_test.v create mode 100644 v_windows/v/vlib/context/deadline.v create mode 100644 v_windows/v/vlib/context/deadline_test.v create mode 100644 v_windows/v/vlib/context/empty.v create mode 100644 v_windows/v/vlib/context/empty_test.v create mode 100644 v_windows/v/vlib/context/err.v create mode 100644 v_windows/v/vlib/context/value.v create mode 100644 v_windows/v/vlib/context/value_test.v create mode 100644 v_windows/v/vlib/crypto/aes/aes.v create mode 100644 v_windows/v/vlib/crypto/aes/aes_cbc.v create mode 100644 v_windows/v/vlib/crypto/aes/aes_test.v create mode 100644 v_windows/v/vlib/crypto/aes/block_generic.v create mode 100644 v_windows/v/vlib/crypto/aes/const.v create mode 100644 v_windows/v/vlib/crypto/aes/cypher_generic.v create mode 100644 v_windows/v/vlib/crypto/cipher/xor_generic.v create mode 100644 v_windows/v/vlib/crypto/crypto.v create mode 100644 v_windows/v/vlib/crypto/hmac/hmac.v create mode 100644 v_windows/v/vlib/crypto/hmac/hmac_test.v create mode 100644 v_windows/v/vlib/crypto/internal/subtle/aliasing.v create mode 100644 v_windows/v/vlib/crypto/internal/subtle/comparison.v create mode 100644 v_windows/v/vlib/crypto/internal/subtle/comparison_test.v create mode 100644 v_windows/v/vlib/crypto/md5/md5.v create mode 100644 v_windows/v/vlib/crypto/md5/md5_test.v create mode 100644 v_windows/v/vlib/crypto/md5/md5block_generic.v create mode 100644 v_windows/v/vlib/crypto/rand/crypto_rand_read_test.v create mode 100644 v_windows/v/vlib/crypto/rand/rand.v create mode 100644 v_windows/v/vlib/crypto/rand/rand_darwin.c.v create mode 100644 v_windows/v/vlib/crypto/rand/rand_default.c.v create mode 100644 v_windows/v/vlib/crypto/rand/rand_linux.c.v create mode 100644 v_windows/v/vlib/crypto/rand/rand_solaris.c.v create mode 100644 v_windows/v/vlib/crypto/rand/rand_windows.c.v create mode 100644 v_windows/v/vlib/crypto/rand/utils.v create mode 100644 v_windows/v/vlib/crypto/rc4/rc4.v create mode 100644 v_windows/v/vlib/crypto/rc4/rc4_test.v create mode 100644 v_windows/v/vlib/crypto/readme.txt create mode 100644 v_windows/v/vlib/crypto/sha1/sha1.v create mode 100644 v_windows/v/vlib/crypto/sha1/sha1_test.v create mode 100644 v_windows/v/vlib/crypto/sha1/sha1block_generic.v create mode 100644 v_windows/v/vlib/crypto/sha256/sha256.v create mode 100644 v_windows/v/vlib/crypto/sha256/sha256_test.v create mode 100644 v_windows/v/vlib/crypto/sha256/sha256block_generic.v create mode 100644 v_windows/v/vlib/crypto/sha512/sha512.v create mode 100644 v_windows/v/vlib/crypto/sha512/sha512_test.v create mode 100644 v_windows/v/vlib/crypto/sha512/sha512block_generic.v create mode 100644 v_windows/v/vlib/darwin/darwin.m create mode 100644 v_windows/v/vlib/darwin/darwin.v create mode 100644 v_windows/v/vlib/dl/dl.v create mode 100644 v_windows/v/vlib/dl/dl_nix.c.v create mode 100644 v_windows/v/vlib/dl/dl_test.v create mode 100644 v_windows/v/vlib/dl/dl_windows.c.v create mode 100644 v_windows/v/vlib/encoding/base58/alphabet.v create mode 100644 v_windows/v/vlib/encoding/base58/base58.v create mode 100644 v_windows/v/vlib/encoding/base58/base58_test.v create mode 100644 v_windows/v/vlib/encoding/base64/base64.v create mode 100644 v_windows/v/vlib/encoding/base64/base64_memory_test.v create mode 100644 v_windows/v/vlib/encoding/base64/base64_test.v create mode 100644 v_windows/v/vlib/encoding/binary/binary.v create mode 100644 v_windows/v/vlib/encoding/csv/README.md create mode 100644 v_windows/v/vlib/encoding/csv/reader.v create mode 100644 v_windows/v/vlib/encoding/csv/reader_test.v create mode 100644 v_windows/v/vlib/encoding/csv/writer.v create mode 100644 v_windows/v/vlib/encoding/csv/writer_test.v create mode 100644 v_windows/v/vlib/encoding/hex/hex.v create mode 100644 v_windows/v/vlib/encoding/hex/hex_test.v create mode 100644 v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width.v create mode 100644 v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width_test.v create mode 100644 v_windows/v/vlib/encoding/utf8/encoding_utf8_test.v create mode 100644 v_windows/v/vlib/encoding/utf8/utf8.v create mode 100644 v_windows/v/vlib/encoding/utf8/utf8_util.v create mode 100644 v_windows/v/vlib/encoding/utf8/utf8_util_test.v create mode 100644 v_windows/v/vlib/eventbus/README.md create mode 100644 v_windows/v/vlib/eventbus/eventbus.v create mode 100644 v_windows/v/vlib/eventbus/eventbus_test.v create mode 100644 v_windows/v/vlib/flag/README.md create mode 100644 v_windows/v/vlib/flag/default_flag_options_test.v create mode 100644 v_windows/v/vlib/flag/flag.v create mode 100644 v_windows/v/vlib/flag/flag_test.v create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.help.out create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.version.out create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.help.out create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.out create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.v create mode 100644 v_windows/v/vlib/flag/testdata/simplest_flag_program.version.out create mode 100644 v_windows/v/vlib/flag/testdata/usage_example.help.out create mode 100644 v_windows/v/vlib/flag/testdata/usage_example.out create mode 100644 v_windows/v/vlib/flag/testdata/usage_example.v create mode 100644 v_windows/v/vlib/flag/testdata/usage_example.version.out create mode 100644 v_windows/v/vlib/flag/usage_example_test.v create mode 100644 v_windows/v/vlib/fontstash/a_d_use_freetype.v create mode 100644 v_windows/v/vlib/fontstash/fontstash.c.v create mode 100644 v_windows/v/vlib/fontstash/fontstash_funcs.c.v create mode 100644 v_windows/v/vlib/fontstash/fontstash_structs.c.v create mode 100644 v_windows/v/vlib/fontstash/fontstash_structs.v create mode 100644 v_windows/v/vlib/gg/enums.v create mode 100644 v_windows/v/vlib/gg/gg.c.v create mode 100644 v_windows/v/vlib/gg/gg.v create mode 100644 v_windows/v/vlib/gg/gg_android.c.v create mode 100644 v_windows/v/vlib/gg/gg_darwin.c.v create mode 100644 v_windows/v/vlib/gg/gg_darwin.m create mode 100644 v_windows/v/vlib/gg/image.c.v create mode 100644 v_windows/v/vlib/gg/image.v create mode 100644 v_windows/v/vlib/gg/m4/graphic.v create mode 100644 v_windows/v/vlib/gg/m4/m4_test.v create mode 100644 v_windows/v/vlib/gg/m4/matrix.v create mode 100644 v_windows/v/vlib/gg/m4/vector.v create mode 100644 v_windows/v/vlib/gg/text_rendering.c.v create mode 100644 v_windows/v/vlib/gg/text_rendering.v create mode 100644 v_windows/v/vlib/glm/glm.v create mode 100644 v_windows/v/vlib/glm/glm_test.v create mode 100644 v_windows/v/vlib/gx/color.v create mode 100644 v_windows/v/vlib/gx/color_test.v create mode 100644 v_windows/v/vlib/gx/image.v create mode 100644 v_windows/v/vlib/gx/text.c.v create mode 100644 v_windows/v/vlib/gx/text.v create mode 100644 v_windows/v/vlib/hash/crc32/crc32.v create mode 100644 v_windows/v/vlib/hash/crc32/crc32_test.v create mode 100644 v_windows/v/vlib/hash/fnv1a/fnv1a.v create mode 100644 v_windows/v/vlib/hash/fnv1a/fnv1a_test.v create mode 100644 v_windows/v/vlib/hash/hash.v create mode 100644 v_windows/v/vlib/hash/wyhash.c.v create mode 100644 v_windows/v/vlib/hash/wyhash.js.v create mode 100644 v_windows/v/vlib/hash/wyhash.v create mode 100644 v_windows/v/vlib/io/buffered_reader.v create mode 100644 v_windows/v/vlib/io/custom_string_reading_test.v create mode 100644 v_windows/v/vlib/io/io.v create mode 100644 v_windows/v/vlib/io/io_cp_test.v create mode 100644 v_windows/v/vlib/io/io_test.v create mode 100644 v_windows/v/vlib/io/multi_writer.v create mode 100644 v_windows/v/vlib/io/multi_writer_test.v create mode 100644 v_windows/v/vlib/io/os_file_reader_test.v create mode 100644 v_windows/v/vlib/io/reader.v create mode 100644 v_windows/v/vlib/io/reader_test.v create mode 100644 v_windows/v/vlib/io/readerwriter.v create mode 100644 v_windows/v/vlib/io/util/util.v create mode 100644 v_windows/v/vlib/io/util/util_test.v create mode 100644 v_windows/v/vlib/io/writer.v create mode 100644 v_windows/v/vlib/json/json_decode_test.v create mode 100644 v_windows/v/vlib/json/json_primitives.v create mode 100644 v_windows/v/vlib/json/json_test.v create mode 100644 v_windows/v/vlib/log/log.v create mode 100644 v_windows/v/vlib/math/ROADMAP.md create mode 100644 v_windows/v/vlib/math/abs.c.v create mode 100644 v_windows/v/vlib/math/abs.js.v create mode 100644 v_windows/v/vlib/math/abs.v create mode 100644 v_windows/v/vlib/math/big/big.c.v create mode 100644 v_windows/v/vlib/math/big/big.js.v create mode 100644 v_windows/v/vlib/math/big/big_test.v create mode 100644 v_windows/v/vlib/math/bits.js.v create mode 100644 v_windows/v/vlib/math/bits.v create mode 100644 v_windows/v/vlib/math/bits/bits.v create mode 100644 v_windows/v/vlib/math/bits/bits_tables.v create mode 100644 v_windows/v/vlib/math/bits/bits_test.v create mode 100644 v_windows/v/vlib/math/cbrt.c.v create mode 100644 v_windows/v/vlib/math/cbrt.js.v create mode 100644 v_windows/v/vlib/math/cbrt.v create mode 100644 v_windows/v/vlib/math/complex/complex.v create mode 100644 v_windows/v/vlib/math/complex/complex_test.v create mode 100644 v_windows/v/vlib/math/const.v create mode 100644 v_windows/v/vlib/math/div.c.v create mode 100644 v_windows/v/vlib/math/div.v create mode 100644 v_windows/v/vlib/math/erf.c.v create mode 100644 v_windows/v/vlib/math/erf.v create mode 100644 v_windows/v/vlib/math/erf_test.v create mode 100644 v_windows/v/vlib/math/exp.c.v create mode 100644 v_windows/v/vlib/math/exp.js.v create mode 100644 v_windows/v/vlib/math/exp.v create mode 100644 v_windows/v/vlib/math/factorial.v create mode 100644 v_windows/v/vlib/math/factorial_tables.v create mode 100644 v_windows/v/vlib/math/factorial_test.v create mode 100644 v_windows/v/vlib/math/floor.c.v create mode 100644 v_windows/v/vlib/math/floor.js.v create mode 100644 v_windows/v/vlib/math/floor.v create mode 100644 v_windows/v/vlib/math/fractions/approximations.v create mode 100644 v_windows/v/vlib/math/fractions/approximations_test.v create mode 100644 v_windows/v/vlib/math/fractions/fraction.v create mode 100644 v_windows/v/vlib/math/fractions/fraction_test.v create mode 100644 v_windows/v/vlib/math/gamma.c.v create mode 100644 v_windows/v/vlib/math/gamma.v create mode 100644 v_windows/v/vlib/math/gamma_tables.v create mode 100644 v_windows/v/vlib/math/hypot.c.v create mode 100644 v_windows/v/vlib/math/hypot.v create mode 100644 v_windows/v/vlib/math/internal/machine.v create mode 100644 v_windows/v/vlib/math/invhyp.v create mode 100644 v_windows/v/vlib/math/invtrig.c.v create mode 100644 v_windows/v/vlib/math/invtrig.js.v create mode 100644 v_windows/v/vlib/math/invtrig.v create mode 100644 v_windows/v/vlib/math/log.c.v create mode 100644 v_windows/v/vlib/math/log.js.v create mode 100644 v_windows/v/vlib/math/log.v create mode 100644 v_windows/v/vlib/math/math.c.v create mode 100644 v_windows/v/vlib/math/math.v create mode 100644 v_windows/v/vlib/math/math_test.v create mode 100644 v_windows/v/vlib/math/mathutil/mathutil.v create mode 100644 v_windows/v/vlib/math/mathutil/mathutil_test.v create mode 100644 v_windows/v/vlib/math/modf.v create mode 100644 v_windows/v/vlib/math/nextafter.v create mode 100644 v_windows/v/vlib/math/poly.v create mode 100644 v_windows/v/vlib/math/pow.c.v create mode 100644 v_windows/v/vlib/math/pow.js.v create mode 100644 v_windows/v/vlib/math/pow.v create mode 100644 v_windows/v/vlib/math/q_rsqrt.v create mode 100644 v_windows/v/vlib/math/sin.c.v create mode 100644 v_windows/v/vlib/math/sin.js.v create mode 100644 v_windows/v/vlib/math/sin.v create mode 100644 v_windows/v/vlib/math/sinh.c.v create mode 100644 v_windows/v/vlib/math/sinh.js.v create mode 100644 v_windows/v/vlib/math/sinh.v create mode 100644 v_windows/v/vlib/math/sqrt.c.v create mode 100644 v_windows/v/vlib/math/sqrt.v create mode 100644 v_windows/v/vlib/math/stats/stats.v create mode 100644 v_windows/v/vlib/math/stats/stats_test.v create mode 100644 v_windows/v/vlib/math/tan.c.v create mode 100644 v_windows/v/vlib/math/tan.js.v create mode 100644 v_windows/v/vlib/math/tan.v create mode 100644 v_windows/v/vlib/math/tanh.c.v create mode 100644 v_windows/v/vlib/math/tanh.js.v create mode 100644 v_windows/v/vlib/math/tanh.v create mode 100644 v_windows/v/vlib/math/unsafe.js.v create mode 100644 v_windows/v/vlib/math/unsafe.v create mode 100644 v_windows/v/vlib/math/util/util.v create mode 100644 v_windows/v/vlib/mssql/README.md create mode 100644 v_windows/v/vlib/mssql/_cdef_nix.c.v create mode 100644 v_windows/v/vlib/mssql/_cdef_windows.c.v create mode 100644 v_windows/v/vlib/mssql/_cdefs.c.v create mode 100644 v_windows/v/vlib/mssql/config.v create mode 100644 v_windows/v/vlib/mssql/mssql.v create mode 100644 v_windows/v/vlib/mssql/result.v create mode 100644 v_windows/v/vlib/mssql/stmt_handle.v create mode 100644 v_windows/v/vlib/mysql/README.md create mode 100644 v_windows/v/vlib/mysql/_cdefs.c.v create mode 100644 v_windows/v/vlib/mysql/_cdefs_nix.c.v create mode 100644 v_windows/v/vlib/mysql/_cdefs_windows.c.v create mode 100644 v_windows/v/vlib/mysql/consts.v create mode 100644 v_windows/v/vlib/mysql/enums.v create mode 100644 v_windows/v/vlib/mysql/mysql.v create mode 100644 v_windows/v/vlib/mysql/mysql_orm_test.v create mode 100644 v_windows/v/vlib/mysql/orm.v create mode 100644 v_windows/v/vlib/mysql/result.v create mode 100644 v_windows/v/vlib/mysql/stmt.c.v create mode 100644 v_windows/v/vlib/mysql/utils.v create mode 100644 v_windows/v/vlib/net/aasocket.c.v create mode 100644 v_windows/v/vlib/net/address.v create mode 100644 v_windows/v/vlib/net/address_darwin.c.v create mode 100644 v_windows/v/vlib/net/address_default.c.v create mode 100644 v_windows/v/vlib/net/address_freebsd.c.v create mode 100644 v_windows/v/vlib/net/address_linux.c.v create mode 100644 v_windows/v/vlib/net/address_test.v create mode 100644 v_windows/v/vlib/net/address_windows.c.v create mode 100644 v_windows/v/vlib/net/afunix.h create mode 100644 v_windows/v/vlib/net/common.v create mode 100644 v_windows/v/vlib/net/conv/conv.c.v create mode 100644 v_windows/v/vlib/net/conv/conv_default.c.v create mode 100644 v_windows/v/vlib/net/conv/conv_windows.c.v create mode 100644 v_windows/v/vlib/net/errors.v create mode 100644 v_windows/v/vlib/net/ftp/ftp.v create mode 100644 v_windows/v/vlib/net/ftp/ftp_test.v create mode 100644 v_windows/v/vlib/net/html/README.md create mode 100644 v_windows/v/vlib/net/html/data_structures.v create mode 100644 v_windows/v/vlib/net/html/dom.v create mode 100644 v_windows/v/vlib/net/html/dom_test.v create mode 100644 v_windows/v/vlib/net/html/html.v create mode 100644 v_windows/v/vlib/net/html/html_test.v create mode 100644 v_windows/v/vlib/net/html/parser.v create mode 100644 v_windows/v/vlib/net/html/parser_test.v create mode 100644 v_windows/v/vlib/net/html/tag.v create mode 100644 v_windows/v/vlib/net/http/backend_nix.c.v create mode 100644 v_windows/v/vlib/net/http/backend_windows.c.v create mode 100644 v_windows/v/vlib/net/http/chunked/dechunk.v create mode 100644 v_windows/v/vlib/net/http/cookie.v create mode 100644 v_windows/v/vlib/net/http/cookie_test.v create mode 100644 v_windows/v/vlib/net/http/download.v create mode 100644 v_windows/v/vlib/net/http/download_nix.c.v create mode 100644 v_windows/v/vlib/net/http/download_windows.c.v create mode 100644 v_windows/v/vlib/net/http/header.v create mode 100644 v_windows/v/vlib/net/http/header_test.v create mode 100644 v_windows/v/vlib/net/http/http.v create mode 100644 v_windows/v/vlib/net/http/http_httpbin_test.v create mode 100644 v_windows/v/vlib/net/http/http_test.v create mode 100644 v_windows/v/vlib/net/http/method.v create mode 100644 v_windows/v/vlib/net/http/request.v create mode 100644 v_windows/v/vlib/net/http/request_test.v create mode 100644 v_windows/v/vlib/net/http/response.v create mode 100644 v_windows/v/vlib/net/http/response_test.v create mode 100644 v_windows/v/vlib/net/http/server.v create mode 100644 v_windows/v/vlib/net/http/server_test.v create mode 100644 v_windows/v/vlib/net/http/status.v create mode 100644 v_windows/v/vlib/net/http/status_test.v create mode 100644 v_windows/v/vlib/net/http/version.v create mode 100644 v_windows/v/vlib/net/ipv6_v6only.h create mode 100644 v_windows/v/vlib/net/net_nix.c.v create mode 100644 v_windows/v/vlib/net/net_windows.c.v create mode 100644 v_windows/v/vlib/net/openssl/c.v create mode 100644 v_windows/v/vlib/net/openssl/openssl.v create mode 100644 v_windows/v/vlib/net/openssl/ssl_connection.v create mode 100644 v_windows/v/vlib/net/smtp/smtp.v create mode 100644 v_windows/v/vlib/net/smtp/smtp_test.v create mode 100644 v_windows/v/vlib/net/socket_options.c.v create mode 100644 v_windows/v/vlib/net/tcp.v create mode 100644 v_windows/v/vlib/net/tcp_read_line.v create mode 100644 v_windows/v/vlib/net/tcp_simple_client_server_test.v create mode 100644 v_windows/v/vlib/net/tcp_test.v create mode 100644 v_windows/v/vlib/net/udp.v create mode 100644 v_windows/v/vlib/net/udp_test.v create mode 100644 v_windows/v/vlib/net/unix/aasocket.c.v create mode 100644 v_windows/v/vlib/net/unix/common.v create mode 100644 v_windows/v/vlib/net/unix/stream_nix.v create mode 100644 v_windows/v/vlib/net/unix/unix_test.v create mode 100644 v_windows/v/vlib/net/urllib/urllib.v create mode 100644 v_windows/v/vlib/net/urllib/urllib_test.v create mode 100644 v_windows/v/vlib/net/urllib/values.v create mode 100644 v_windows/v/vlib/net/util.v create mode 100644 v_windows/v/vlib/net/websocket/events.v create mode 100644 v_windows/v/vlib/net/websocket/handshake.v create mode 100644 v_windows/v/vlib/net/websocket/io.v create mode 100644 v_windows/v/vlib/net/websocket/message.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/README.md create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client_wss.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_server.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/docker-compose.yml create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/Dockerfile create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/check_results.py create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingclient.json create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingserver.json create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/Dockerfile create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/check_results.py create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/fuzzingserver.json create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.crt create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.csr create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.key create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.pem create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/local_run/Dockerfile create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client_wss.v create mode 100644 v_windows/v/vlib/net/websocket/tests/autobahn/ws_test/Dockerfile create mode 100644 v_windows/v/vlib/net/websocket/uri.v create mode 100644 v_windows/v/vlib/net/websocket/utils.v create mode 100644 v_windows/v/vlib/net/websocket/websocket_client.v create mode 100644 v_windows/v/vlib/net/websocket/websocket_nix.c.v create mode 100644 v_windows/v/vlib/net/websocket/websocket_server.v create mode 100644 v_windows/v/vlib/net/websocket/websocket_test.v create mode 100644 v_windows/v/vlib/net/websocket/websocket_windows.c.v create mode 100644 v_windows/v/vlib/orm/README.md create mode 100644 v_windows/v/vlib/orm/orm.v create mode 100644 v_windows/v/vlib/orm/orm_fn_test.v create mode 100644 v_windows/v/vlib/orm/orm_test.v create mode 100644 v_windows/v/vlib/os/args.v create mode 100644 v_windows/v/vlib/os/bare/bare_example_linux.v create mode 100644 v_windows/v/vlib/os/cmdline/cmdline.v create mode 100644 v_windows/v/vlib/os/cmdline/cmdline_test.v create mode 100644 v_windows/v/vlib/os/const.v create mode 100644 v_windows/v/vlib/os/const_nix.c.v create mode 100644 v_windows/v/vlib/os/const_windows.c.v create mode 100644 v_windows/v/vlib/os/environment.c.v create mode 100644 v_windows/v/vlib/os/environment.js.v create mode 100644 v_windows/v/vlib/os/environment_test.v create mode 100644 v_windows/v/vlib/os/fd.c.v create mode 100644 v_windows/v/vlib/os/file.c.v create mode 100644 v_windows/v/vlib/os/file.js.v create mode 100644 v_windows/v/vlib/os/file_test.v create mode 100644 v_windows/v/vlib/os/filelock/filelock_test.v create mode 100644 v_windows/v/vlib/os/filelock/lib.v create mode 100644 v_windows/v/vlib/os/filelock/lib_nix.c.v create mode 100644 v_windows/v/vlib/os/filelock/lib_windows.c.v create mode 100644 v_windows/v/vlib/os/glob_test.v create mode 100644 v_windows/v/vlib/os/inode.c.v create mode 100644 v_windows/v/vlib/os/inode_test.v create mode 100644 v_windows/v/vlib/os/notify/backend_default.c.v create mode 100644 v_windows/v/vlib/os/notify/backend_linux.c.v create mode 100644 v_windows/v/vlib/os/notify/notify.v create mode 100644 v_windows/v/vlib/os/notify/notify_test.v create mode 100644 v_windows/v/vlib/os/os.c.v create mode 100644 v_windows/v/vlib/os/os.js.v create mode 100644 v_windows/v/vlib/os/os.v create mode 100644 v_windows/v/vlib/os/os_android.c.v create mode 100644 v_windows/v/vlib/os/os_darwin.c.v create mode 100644 v_windows/v/vlib/os/os_darwin.m create mode 100644 v_windows/v/vlib/os/os_js.js.v create mode 100644 v_windows/v/vlib/os/os_linux.c.v create mode 100644 v_windows/v/vlib/os/os_nix.c.v create mode 100644 v_windows/v/vlib/os/os_test.v create mode 100644 v_windows/v/vlib/os/os_windows.c.v create mode 100644 v_windows/v/vlib/os/process.c.v create mode 100644 v_windows/v/vlib/os/process.js.v create mode 100644 v_windows/v/vlib/os/process.v create mode 100644 v_windows/v/vlib/os/process_nix.c.v create mode 100644 v_windows/v/vlib/os/process_test.v create mode 100644 v_windows/v/vlib/os/process_windows.c.v create mode 100644 v_windows/v/vlib/os/signal.c.v create mode 100644 v_windows/v/vlib/os/signal_test.v create mode 100644 v_windows/v/vlib/pg/README.md create mode 100644 v_windows/v/vlib/pg/oid.v create mode 100644 v_windows/v/vlib/pg/orm.v create mode 100644 v_windows/v/vlib/pg/pg.v create mode 100644 v_windows/v/vlib/pg/pg_orm_test.v create mode 100644 v_windows/v/vlib/picoev/picoev.v create mode 100644 v_windows/v/vlib/picohttpparser/misc.v create mode 100644 v_windows/v/vlib/picohttpparser/picohttpparser.v create mode 100644 v_windows/v/vlib/picohttpparser/request.v create mode 100644 v_windows/v/vlib/picohttpparser/response.v create mode 100644 v_windows/v/vlib/rand/README.md create mode 100644 v_windows/v/vlib/rand/config/config.v create mode 100644 v_windows/v/vlib/rand/constants/constants.v create mode 100644 v_windows/v/vlib/rand/dist/README.md create mode 100644 v_windows/v/vlib/rand/dist/dist.v create mode 100644 v_windows/v/vlib/rand/dist/dist_test.v create mode 100644 v_windows/v/vlib/rand/mt19937/mt19937.v create mode 100644 v_windows/v/vlib/rand/mt19937/mt19937_test.v create mode 100644 v_windows/v/vlib/rand/musl/musl_rng.v create mode 100644 v_windows/v/vlib/rand/musl/musl_rng_test.v create mode 100644 v_windows/v/vlib/rand/pcg32/pcg32.v create mode 100644 v_windows/v/vlib/rand/pcg32/pcg32_test.v create mode 100644 v_windows/v/vlib/rand/rand.c.v create mode 100644 v_windows/v/vlib/rand/rand.v create mode 100644 v_windows/v/vlib/rand/random_identifiers_test.v create mode 100644 v_windows/v/vlib/rand/random_numbers_test.v create mode 100644 v_windows/v/vlib/rand/seed/seed.v create mode 100644 v_windows/v/vlib/rand/splitmix64/splitmix64.v create mode 100644 v_windows/v/vlib/rand/splitmix64/splitmix64_test.v create mode 100644 v_windows/v/vlib/rand/sys/system_rng.c.v create mode 100644 v_windows/v/vlib/rand/sys/system_rng.js.v create mode 100644 v_windows/v/vlib/rand/sys/system_rng_test.v create mode 100644 v_windows/v/vlib/rand/util/util.v create mode 100644 v_windows/v/vlib/rand/util/util_test.v create mode 100644 v_windows/v/vlib/rand/wyrand/wyrand.v create mode 100644 v_windows/v/vlib/rand/wyrand/wyrand_test.v create mode 100644 v_windows/v/vlib/readline/README.md create mode 100644 v_windows/v/vlib/readline/readline.v create mode 100644 v_windows/v/vlib/readline/readline_default.c.v create mode 100644 v_windows/v/vlib/readline/readline_linux.c.v create mode 100644 v_windows/v/vlib/readline/readline_test.v create mode 100644 v_windows/v/vlib/readline/readline_windows.c.v create mode 100644 v_windows/v/vlib/regex/README.md create mode 100644 v_windows/v/vlib/regex/regex.v create mode 100644 v_windows/v/vlib/regex/regex_opt.v create mode 100644 v_windows/v/vlib/regex/regex_test.v create mode 100644 v_windows/v/vlib/regex/regex_util.v create mode 100644 v_windows/v/vlib/runtime/runtime.v create mode 100644 v_windows/v/vlib/runtime/runtime_nix.c.v create mode 100644 v_windows/v/vlib/runtime/runtime_test.v create mode 100644 v_windows/v/vlib/runtime/runtime_windows.c.v create mode 100644 v_windows/v/vlib/semver/LICENSE.md create mode 100644 v_windows/v/vlib/semver/README.md create mode 100644 v_windows/v/vlib/semver/compare.v create mode 100644 v_windows/v/vlib/semver/parse.v create mode 100644 v_windows/v/vlib/semver/range.v create mode 100644 v_windows/v/vlib/semver/semver.v create mode 100644 v_windows/v/vlib/semver/semver_test.v create mode 100644 v_windows/v/vlib/semver/util.v create mode 100644 v_windows/v/vlib/semver/v.mod create mode 100644 v_windows/v/vlib/sokol/audio/audio.v create mode 100644 v_windows/v/vlib/sokol/c/declaration.c.v create mode 100644 v_windows/v/vlib/sokol/f/f.v create mode 100644 v_windows/v/vlib/sokol/gfx/enums.v create mode 100644 v_windows/v/vlib/sokol/gfx/gfx.c.v create mode 100644 v_windows/v/vlib/sokol/gfx/gfx_funcs.c.v create mode 100644 v_windows/v/vlib/sokol/gfx/gfx_structs.c.v create mode 100644 v_windows/v/vlib/sokol/gfx/gfx_utils.c.v create mode 100644 v_windows/v/vlib/sokol/sapp/enums.v create mode 100644 v_windows/v/vlib/sokol/sapp/sapp.c.v create mode 100644 v_windows/v/vlib/sokol/sapp/sapp_funcs.c.v create mode 100644 v_windows/v/vlib/sokol/sapp/sapp_structs.c.v create mode 100644 v_windows/v/vlib/sokol/sfons/sfons.c.v create mode 100644 v_windows/v/vlib/sokol/sfons/sfons_funcs.c.v create mode 100644 v_windows/v/vlib/sokol/sgl/sgl.c.v create mode 100644 v_windows/v/vlib/sokol/sgl/sgl_funcs.c.v create mode 100644 v_windows/v/vlib/sokol/sgl/sgl_structs.c.v create mode 100644 v_windows/v/vlib/sokol/sokol.v create mode 100644 v_windows/v/vlib/sqlite/README.md create mode 100644 v_windows/v/vlib/sqlite/orm.v create mode 100644 v_windows/v/vlib/sqlite/sqlite.v create mode 100644 v_windows/v/vlib/sqlite/sqlite_orm_test.v create mode 100644 v_windows/v/vlib/sqlite/sqlite_test.v create mode 100644 v_windows/v/vlib/sqlite/stmt.v create mode 100644 v_windows/v/vlib/stbi/stbi.c.v create mode 100644 v_windows/v/vlib/strconv/atof.v create mode 100644 v_windows/v/vlib/strconv/atof_test.v create mode 100644 v_windows/v/vlib/strconv/atofq.v create mode 100644 v_windows/v/vlib/strconv/atoi.v create mode 100644 v_windows/v/vlib/strconv/atoi_test.v create mode 100644 v_windows/v/vlib/strconv/f32_f64_to_string_test.v create mode 100644 v_windows/v/vlib/strconv/f32_str.v create mode 100644 v_windows/v/vlib/strconv/f64_str.v create mode 100644 v_windows/v/vlib/strconv/format.md create mode 100644 v_windows/v/vlib/strconv/format.v create mode 100644 v_windows/v/vlib/strconv/format_mem.v create mode 100644 v_windows/v/vlib/strconv/format_test.v create mode 100644 v_windows/v/vlib/strconv/ftoa.v create mode 100644 v_windows/v/vlib/strconv/number_to_base.v create mode 100644 v_windows/v/vlib/strconv/number_to_base_test.v create mode 100644 v_windows/v/vlib/strconv/structs.v create mode 100644 v_windows/v/vlib/strconv/tables.v create mode 100644 v_windows/v/vlib/strconv/utilities.v create mode 100644 v_windows/v/vlib/strconv/vprintf.v create mode 100644 v_windows/v/vlib/strings/builder.js.v create mode 100644 v_windows/v/vlib/strings/builder.v create mode 100644 v_windows/v/vlib/strings/builder_test.v create mode 100644 v_windows/v/vlib/strings/similarity.v create mode 100644 v_windows/v/vlib/strings/similarity_test.v create mode 100644 v_windows/v/vlib/strings/strings.c.v create mode 100644 v_windows/v/vlib/strings/strings.js.v create mode 100644 v_windows/v/vlib/strings/strings.v create mode 100644 v_windows/v/vlib/strings/strings_test.v create mode 100644 v_windows/v/vlib/strings/textscanner/textscanner.v create mode 100644 v_windows/v/vlib/strings/textscanner/textscanner_test.v create mode 100644 v_windows/v/vlib/sync/array_rlock_test.v create mode 100644 v_windows/v/vlib/sync/atomic2/atomic.v create mode 100644 v_windows/v/vlib/sync/atomic2/atomic_test.v create mode 100644 v_windows/v/vlib/sync/bench/channel_bench_go.go create mode 100644 v_windows/v/vlib/sync/bench/channel_bench_v.v create mode 100644 v_windows/v/vlib/sync/bench/many_writers_and_receivers_on_1_channel.v create mode 100644 v_windows/v/vlib/sync/bench/results.md create mode 100644 v_windows/v/vlib/sync/channel_1_test.v create mode 100644 v_windows/v/vlib/sync/channel_2_test.v create mode 100644 v_windows/v/vlib/sync/channel_3_test.v create mode 100644 v_windows/v/vlib/sync/channel_4_test.v create mode 100644 v_windows/v/vlib/sync/channel_array_mut_test.v create mode 100644 v_windows/v/vlib/sync/channel_close_test.v create mode 100644 v_windows/v/vlib/sync/channel_fill_test.v create mode 100644 v_windows/v/vlib/sync/channel_opt_propagate_test.v create mode 100644 v_windows/v/vlib/sync/channel_polling_test.v create mode 100644 v_windows/v/vlib/sync/channel_push_or_1_test.v create mode 100644 v_windows/v/vlib/sync/channel_push_or_2_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_2_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_3_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_4_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_5_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_6_test.v create mode 100644 v_windows/v/vlib/sync/channel_select_test.v create mode 100644 v_windows/v/vlib/sync/channel_try_buf_test.v create mode 100644 v_windows/v/vlib/sync/channel_try_unbuf_test.v create mode 100644 v_windows/v/vlib/sync/channels.v create mode 100644 v_windows/v/vlib/sync/pool/README.md create mode 100644 v_windows/v/vlib/sync/pool/pool.v create mode 100644 v_windows/v/vlib/sync/pool/pool_test.v create mode 100644 v_windows/v/vlib/sync/select_close_test.v create mode 100644 v_windows/v/vlib/sync/struct_chan_init_test.v create mode 100644 v_windows/v/vlib/sync/sync_default.c.v create mode 100644 v_windows/v/vlib/sync/sync_macos.c.v create mode 100644 v_windows/v/vlib/sync/sync_windows.c.v create mode 100644 v_windows/v/vlib/sync/threads/threads.c.v create mode 100644 v_windows/v/vlib/sync/threads/threads.v create mode 100644 v_windows/v/vlib/sync/waitgroup.v create mode 100644 v_windows/v/vlib/sync/waitgroup_test.v create mode 100644 v_windows/v/vlib/szip/szip.v create mode 100644 v_windows/v/vlib/szip/szip_test.v create mode 100644 v_windows/v/vlib/term/README.md create mode 100644 v_windows/v/vlib/term/colors.v create mode 100644 v_windows/v/vlib/term/control.v create mode 100644 v_windows/v/vlib/term/term.js.v create mode 100644 v_windows/v/vlib/term/term.v create mode 100644 v_windows/v/vlib/term/term_nix.c.v create mode 100644 v_windows/v/vlib/term/term_test.v create mode 100644 v_windows/v/vlib/term/term_windows.c.v create mode 100644 v_windows/v/vlib/term/ui/README.md create mode 100644 v_windows/v/vlib/term/ui/color.v create mode 100644 v_windows/v/vlib/term/ui/consoleapi_windows.c.v create mode 100644 v_windows/v/vlib/term/ui/input.v create mode 100644 v_windows/v/vlib/term/ui/input_nix.c.v create mode 100644 v_windows/v/vlib/term/ui/input_windows.c.v create mode 100644 v_windows/v/vlib/term/ui/termios_nix.c.v create mode 100644 v_windows/v/vlib/term/ui/ui.v create mode 100644 v_windows/v/vlib/time/Y2K38_test.v create mode 100644 v_windows/v/vlib/time/chrono.c.v create mode 100644 v_windows/v/vlib/time/chrono.v create mode 100644 v_windows/v/vlib/time/format.v create mode 100644 v_windows/v/vlib/time/misc/misc.v create mode 100644 v_windows/v/vlib/time/misc/misc_test.v create mode 100644 v_windows/v/vlib/time/operator.v create mode 100644 v_windows/v/vlib/time/operator_test.v create mode 100644 v_windows/v/vlib/time/parse.c.v create mode 100644 v_windows/v/vlib/time/parse.js.v create mode 100644 v_windows/v/vlib/time/parse_test.v create mode 100644 v_windows/v/vlib/time/private_test.v create mode 100644 v_windows/v/vlib/time/stopwatch.v create mode 100644 v_windows/v/vlib/time/stopwatch_test.v create mode 100644 v_windows/v/vlib/time/time.c.v create mode 100644 v_windows/v/vlib/time/time.js.v create mode 100644 v_windows/v/vlib/time/time.v create mode 100644 v_windows/v/vlib/time/time_addition_test.v create mode 100644 v_windows/v/vlib/time/time_darwin.c.v create mode 100644 v_windows/v/vlib/time/time_format_test.v create mode 100644 v_windows/v/vlib/time/time_js.js.v create mode 100644 v_windows/v/vlib/time/time_linux.c.v create mode 100644 v_windows/v/vlib/time/time_nix.c.v create mode 100644 v_windows/v/vlib/time/time_solaris.c.v create mode 100644 v_windows/v/vlib/time/time_test.v create mode 100644 v_windows/v/vlib/time/time_windows.c.v create mode 100644 v_windows/v/vlib/time/unix.v create mode 100644 v_windows/v/vlib/v/README.md create mode 100644 v_windows/v/vlib/v/TEMPLATES.md create mode 100644 v_windows/v/vlib/v/ast/ast.v create mode 100644 v_windows/v/vlib/v/ast/attr.v create mode 100644 v_windows/v/vlib/v/ast/cflags.v create mode 100644 v_windows/v/vlib/v/ast/cflags_test.v create mode 100644 v_windows/v/vlib/v/ast/comptime_const_values.v create mode 100644 v_windows/v/vlib/v/ast/init.v create mode 100644 v_windows/v/vlib/v/ast/scope.v create mode 100644 v_windows/v/vlib/v/ast/str.v create mode 100644 v_windows/v/vlib/v/ast/table.v create mode 100644 v_windows/v/vlib/v/ast/types.v create mode 100644 v_windows/v/vlib/v/ast/types_test.v create mode 100644 v_windows/v/vlib/v/ast/walker/walker.v create mode 100644 v_windows/v/vlib/v/ast/walker/walker_test.v create mode 100644 v_windows/v/vlib/v/builder/builder.v create mode 100644 v_windows/v/vlib/v/builder/c.v create mode 100644 v_windows/v/vlib/v/builder/cc.v create mode 100644 v_windows/v/vlib/v/builder/cflags.v create mode 100644 v_windows/v/vlib/v/builder/compile.v create mode 100644 v_windows/v/vlib/v/builder/js.v create mode 100644 v_windows/v/vlib/v/builder/msvc.v create mode 100644 v_windows/v/vlib/v/builder/native.v create mode 100644 v_windows/v/vlib/v/callgraph/callgraph.v create mode 100644 v_windows/v/vlib/v/cflag/cflags.v create mode 100644 v_windows/v/vlib/v/checker/check_types.v create mode 100644 v_windows/v/vlib/v/checker/checker.v create mode 100644 v_windows/v/vlib/v/checker/comptime_const_eval.v create mode 100644 v_windows/v/vlib/v/checker/noreturn.v create mode 100644 v_windows/v/vlib/v/checker/tests/.gitattributes create mode 100644 v_windows/v/vlib/v/checker/tests/.gitignore create mode 100644 v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.out create mode 100644 v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.vv create mode 100644 v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/alias_type_exists.out create mode 100644 v_windows/v/vlib/v/checker/tests/alias_type_exists.vv create mode 100644 v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/ambiguous_function_call.out create mode 100644 v_windows/v/vlib/v/checker/tests/ambiguous_function_call.vv create mode 100644 v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/append_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/append_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_cmp_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_cmp_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_c.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_declare_element_c.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_element_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_element_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_filter_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_filter_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_index.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_index.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_literal_modify_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_literal_modify_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_sort_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_sort_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/asm_immutable_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/asm_immutable_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assert_optional_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assert_optional_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_mut.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_mut.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_sumtype_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_sumtype_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.out create mode 100644 v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.out create mode 100644 v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.vv create mode 100644 v_windows/v/vlib/v/checker/tests/blank_modify.out create mode 100644 v_windows/v/vlib/v/checker/tests/blank_modify.vv create mode 100644 v_windows/v/vlib/v/checker/tests/bool_string_cast_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/bool_string_cast_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/break_anon_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/break_anon_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_assign_array.out create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_assign_array.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.out create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cast_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/cast_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cast_string_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/cast_string_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/cast_void.out create mode 100644 v_windows/v/vlib/v/checker/tests/cast_void.vv create mode 100644 v_windows/v/vlib/v/checker/tests/chan_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/chan_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/chan_mut.out create mode 100644 v_windows/v/vlib/v/checker/tests/chan_mut.vv create mode 100644 v_windows/v/vlib/v/checker/tests/chan_ref.out create mode 100644 v_windows/v/vlib/v/checker/tests/chan_ref.vv create mode 100644 v_windows/v/vlib/v/checker/tests/char_str.out create mode 100644 v_windows/v/vlib/v/checker/tests/char_str.vv create mode 100644 v_windows/v/vlib/v/checker/tests/closure_immutable.out create mode 100644 v_windows/v/vlib/v/checker/tests/closure_immutable.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.out create mode 100644 v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_call_method.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_call_method.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var_invalid.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_for.out create mode 100644 v_windows/v/vlib/v/checker/tests/comptime_for.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_define_in_function_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_define_in_function_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_add_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_add_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_dec_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_dec_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_inc_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_inc_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.vv create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_sub_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/const_field_sub_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/ctdefine.out create mode 100644 v_windows/v/vlib/v/checker/tests/ctdefine.vv create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.mysymbol.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.cg.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.bar.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.g.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.vv create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.mydebug.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.nodebug.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.out create mode 100644 v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.vv create mode 100644 v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/decompose_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/decompose_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/defer_in_for.out create mode 100644 v_windows/v/vlib/v/checker/tests/defer_in_for.vv create mode 100644 v_windows/v/vlib/v/checker/tests/defer_optional.out create mode 100644 v_windows/v/vlib/v/checker/tests/defer_optional.vv create mode 100644 v_windows/v/vlib/v/checker/tests/deprecations.out create mode 100644 v_windows/v/vlib/v/checker/tests/deprecations.vv create mode 100644 v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/dump_of_void_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/dump_of_void_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_as_int_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_as_int_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_cast.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_cast.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_empty.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_empty.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_overflow.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_overflow.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_op_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_op_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_op_flag_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_op_flag_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/enum_single_letter.out create mode 100644 v_windows/v/vlib/v/checker/tests/enum_single_letter.vv create mode 100644 v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.out create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.vv create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.out create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.vv create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.out create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_unicode.out create mode 100644 v_windows/v/vlib/v/checker/tests/error_with_unicode.vv create mode 100644 v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.out create mode 100644 v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.vv create mode 100644 v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_conv.out create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_conv.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_size_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/fixed_array_size_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/float_modulo_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/float_modulo_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_call_no_body.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_call_no_body.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_duplicate.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_duplicate.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_init_sig.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_init_sig.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_return_or_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_return_or_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_type_exists.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_type_exists.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_var.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_var.vv create mode 100644 v_windows/v/vlib/v/checker/tests/fn_variadic.out create mode 100644 v_windows/v/vlib/v/checker/tests/fn_variadic.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_index_optional.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_index_optional.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_index_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_index_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_range_string_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_in_range_string_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/for_match_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/for_match_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_arg_redefinition.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_arg_redefinition.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_missing_return_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_missing_return_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/function_wrong_return_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/function_wrong_return_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.out create mode 100644 v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_struct_init_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_struct_init_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.vv create mode 100644 v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.out create mode 100644 v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals/assign_no_value.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals/assign_no_value.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals/no_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals/no_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals/unknown_typ.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals/unknown_typ.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals_error.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.vv create mode 100644 v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv create mode 100644 v_windows/v/vlib/v/checker/tests/go_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/go_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/go_mut_arg.out create mode 100644 v_windows/v/vlib/v/checker/tests/go_mut_arg.vv create mode 100644 v_windows/v/vlib/v/checker/tests/go_mut_receiver.out create mode 100644 v_windows/v/vlib/v/checker/tests/go_mut_receiver.vv create mode 100644 v_windows/v/vlib/v/checker/tests/go_wait_or.out create mode 100644 v_windows/v/vlib/v/checker/tests/go_wait_or.vv create mode 100644 v_windows/v/vlib/v/checker/tests/goto_label.out create mode 100644 v_windows/v/vlib/v/checker/tests/goto_label.vv create mode 100644 v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/hex_literal_overflow.out create mode 100644 v_windows/v/vlib/v/checker/tests/hex_literal_overflow.vv create mode 100644 v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.out create mode 100644 v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_no_else.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_no_else.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_optional_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_expr_optional_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_expr_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_expr_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_result.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_match_result.vv create mode 100644 v_windows/v/vlib/v/checker/tests/if_non_bool_cond.out create mode 100644 v_windows/v/vlib/v/checker/tests/if_non_bool_cond.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_arg.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_arg.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_var.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_array_var.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_field_postfix.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_field_postfix.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_interface_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_interface_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_map.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_map.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_rec.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_rec.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_var.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_var.vv create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_var_postfix.out create mode 100644 v_windows/v/vlib/v/checker/tests/immutable_var_postfix.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_middle_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_middle_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_not_found_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_not_found_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_not_same_line_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_not_same_line_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_empty.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_empty.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_invalid.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_invalid.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_private_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_private_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_syntax_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_syntax_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/import_unused_warning.out create mode 100644 v_windows/v/vlib/v/checker/tests/import_unused_warning.vv create mode 100644 v_windows/v/vlib/v/checker/tests/in_mismatch_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/in_mismatch_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_const.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_const.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_enum.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_enum.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_function.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_function.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_interface.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_interface.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_module.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_module.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_variable.out create mode 100644 v_windows/v/vlib/v/checker/tests/incorrect_name_variable.vv create mode 100644 v_windows/v/vlib/v/checker/tests/index_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/index_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/infix_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/infix_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interface_implementing_interface.out create mode 100644 v_windows/v/vlib/v/checker/tests/interface_implementing_interface.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.out create mode 100644 v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interface_init_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/interface_init_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.out create mode 100644 v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.vv create mode 100644 v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_char_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_char_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_property.out create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_property.vv create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/is_type_invalid.out create mode 100644 v_windows/v/vlib/v/checker/tests/is_type_invalid.vv create mode 100644 v_windows/v/vlib/v/checker/tests/is_type_not_exist.out create mode 100644 v_windows/v/vlib/v/checker/tests/is_type_not_exist.vv create mode 100644 v_windows/v/vlib/v/checker/tests/labelled_break_continue.out create mode 100644 v_windows/v/vlib/v/checker/tests/labelled_break_continue.vv create mode 100644 v_windows/v/vlib/v/checker/tests/lock_already_locked.out create mode 100644 v_windows/v/vlib/v/checker/tests/lock_already_locked.vv create mode 100644 v_windows/v/vlib/v/checker/tests/lock_already_rlocked.out create mode 100644 v_windows/v/vlib/v/checker/tests/lock_already_rlocked.vv create mode 100644 v_windows/v/vlib/v/checker/tests/lock_const.out create mode 100644 v_windows/v/vlib/v/checker/tests/lock_const.vv create mode 100644 v_windows/v/vlib/v/checker/tests/lock_needed.out create mode 100644 v_windows/v/vlib/v/checker/tests/lock_needed.vv create mode 100644 v_windows/v/vlib/v/checker/tests/lock_nonshared.out create mode 100644 v_windows/v/vlib/v/checker/tests/lock_nonshared.vv create mode 100644 v_windows/v/vlib/v/checker/tests/main_and_script_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/main_and_script_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/main_args_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/main_args_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/main_called_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/main_called_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/main_no_body_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/main_no_body_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/main_return_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/main_return_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_delete.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_delete.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_func_void_return_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_func_void_return_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_wrong_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_init_wrong_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_ops.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_ops.vv create mode 100644 v_windows/v/vlib/v/checker/tests/map_unknown_value.out create mode 100644 v_windows/v/vlib/v/checker/tests/map_unknown_value.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_alias_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_alias_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_duplicate_branch.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_duplicate_branch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_else_last_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_else_last_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_else.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_else.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_invalid_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_invalid_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.vv create mode 100644 v_windows/v/vlib/v/checker/tests/match_undefined_cond.out create mode 100644 v_windows/v/vlib/v/checker/tests/match_undefined_cond.vv create mode 100644 v_windows/v/vlib/v/checker/tests/method_array_slice.out create mode 100644 v_windows/v/vlib/v/checker/tests/method_array_slice.vv create mode 100644 v_windows/v/vlib/v/checker/tests/method_generic_infer_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/method_generic_infer_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/method_op_alias_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/method_op_alias_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/method_op_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/method_op_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.out create mode 100644 v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.out create mode 100644 v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.vv create mode 100644 v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out create mode 100644 v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/modify_const_with_ref.out create mode 100644 v_windows/v/vlib/v/checker/tests/modify_const_with_ref.vv create mode 100644 v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore.out create mode 100644 v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/main.v create mode 100644 v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/underscore.v create mode 100644 v_windows/v/vlib/v/checker/tests/modules/overload_return_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/modules/overload_return_type/main.v create mode 100644 v_windows/v/vlib/v/checker/tests/modules/overload_return_type/point.v create mode 100644 v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/multi_names_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/multi_names_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/multi_value_method_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/multi_value_method_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_arg.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_arg.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_args_warning.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_args_warning.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_int.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_int.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_receiver.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_receiver.vv create mode 100644 v_windows/v/vlib/v/checker/tests/mut_receiver_lit.out create mode 100644 v_windows/v/vlib/v/checker/tests/mut_receiver_lit.vv create mode 100644 v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.out create mode 100644 v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.vv create mode 100644 v_windows/v/vlib/v/checker/tests/nested_aliases.out create mode 100644 v_windows/v/vlib/v/checker/tests/nested_aliases.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_heap_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_heap_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_str.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_interface_str.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_main_mod.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_main_mod.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_main_println_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_main_println_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.vv create mode 100644 v_windows/v/vlib/v/checker/tests/no_pub_in_main.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.out create mode 100644 v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.vv create mode 100644 v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.out create mode 100644 v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/non_matching_functional_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/non_matching_functional_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/none_type_cast_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/none_type_cast_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.out create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_with_return.out create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_with_return.vv create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.out create mode 100644 v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv create mode 100644 v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_fn_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_fn_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_propagate_nested.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_propagate_nested.vv create mode 100644 v_windows/v/vlib/v/checker/tests/optional_type_call_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/optional_type_call_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/or_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/or_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/orm_empty_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/orm_empty_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/os_prefix.out create mode 100644 v_windows/v/vlib/v/checker/tests/os_prefix.vv create mode 100644 v_windows/v/vlib/v/checker/tests/overflow_int_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/overflow_int_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/overload_return_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/overload_return_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/oversized_int_lit.out create mode 100644 v_windows/v/vlib/v/checker/tests/oversized_int_lit.vv create mode 100644 v_windows/v/vlib/v/checker/tests/pass_mut_lit.out create mode 100644 v_windows/v/vlib/v/checker/tests/pass_mut_lit.vv create mode 100644 v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.out create mode 100644 v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/pointer_ops.out create mode 100644 v_windows/v/vlib/v/checker/tests/pointer_ops.vv create mode 100644 v_windows/v/vlib/v/checker/tests/prefix_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/prefix_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/print_char.out create mode 100644 v_windows/v/vlib/v/checker/tests/print_char.vv create mode 100644 v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.out create mode 100644 v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.vv create mode 100644 v_windows/v/vlib/v/checker/tests/ptr_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/ptr_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.out create mode 100644 v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.vv create mode 100644 v_windows/v/vlib/v/checker/tests/recursive_interface_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/recursive_interface_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.out create mode 100644 v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.vv create mode 100644 v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.out create mode 100644 v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.vv create mode 100644 v_windows/v/vlib/v/checker/tests/reference_return.out create mode 100644 v_windows/v/vlib/v/checker/tests/reference_return.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_count_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_count_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_fixed_array.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_fixed_array.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_comp_if.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_comp_if.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_if_match.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_if_match.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_match_if.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_match_if.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_match_simple.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_match_simple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_nested.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_nested.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_simple.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_missing_simple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_comp_if.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_comp_if.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_if_match.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_if_match.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_match_if.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_match_if.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_nested.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_nested.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_simple.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_simple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_two_if.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_two_if.vv create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_unsafe.out create mode 100644 v_windows/v/vlib/v/checker/tests/return_working_unsafe.vv create mode 100644 v_windows/v/vlib/v/checker/tests/returns/return_missing_simple.vv create mode 100644 v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.vv create mode 100644 v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.run.out create mode 100644 v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.vv create mode 100644 v_windows/v/vlib/v/checker/tests/selective_const_import.out create mode 100644 v_windows/v/vlib/v/checker/tests/selective_const_import.vv create mode 100644 v_windows/v/vlib/v/checker/tests/selector_expr_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/selector_expr_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shared_bad_args.out create mode 100644 v_windows/v/vlib/v/checker/tests/shared_bad_args.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shared_element_lock.out create mode 100644 v_windows/v/vlib/v/checker/tests/shared_element_lock.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shared_lock.out create mode 100644 v_windows/v/vlib/v/checker/tests/shared_lock.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shared_type_mismatch.out create mode 100644 v_windows/v/vlib/v/checker/tests/shared_type_mismatch.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.out create mode 100644 v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.vv create mode 100644 v_windows/v/vlib/v/checker/tests/slice_reassignment.out create mode 100644 v_windows/v/vlib/v/checker/tests/slice_reassignment.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.out create mode 100644 v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv create mode 100644 v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.out create mode 100644 v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.vv create mode 100644 v_windows/v/vlib/v/checker/tests/store_string_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/store_string_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/str_method_0_arguments.out create mode 100644 v_windows/v/vlib/v/checker/tests/str_method_0_arguments.vv create mode 100644 v_windows/v/vlib/v/checker/tests/str_method_return_string.out create mode 100644 v_windows/v/vlib/v/checker/tests/str_method_return_string.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_char_null_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_char_null_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_index_assign_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_index_assign_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_index_non_int_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_index_non_int_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.vv create mode 100644 v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.out create mode 100644 v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_field_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_field_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_pub_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_pub_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_required_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_required_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_required_fn_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_required_fn_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_type_cast_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_type_cast_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_unknown_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_unknown_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/struct_unneeded_default.out create mode 100644 v_windows/v/vlib/v/checker/tests/struct_unneeded_default.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_exists.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_exists.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_infix_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_infix_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/templates/index.html create mode 100644 v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.out create mode 100644 v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.vv create mode 100644 v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.out create mode 100644 v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/type_cast_optional_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/type_cast_optional_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.out create mode 100644 v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unexpected_or.out create mode 100644 v_windows/v/vlib/v/checker/tests/unexpected_or.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.out create mode 100644 v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unfinished_string.out create mode 100644 v_windows/v/vlib/v/checker/tests/unfinished_string.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.vv.disabled create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.out create mode 100644 v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.vv create mode 100644 v_windows/v/vlib/v/checker/tests/union_unsafe_fields.out create mode 100644 v_windows/v/vlib/v/checker/tests/union_unsafe_fields.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_as_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_as_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_field.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_field.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_generic_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_generic_type.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_method.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_method.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_struct_name.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_struct_name.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_var_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/unknown_var_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.out create mode 100644 v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unreachable_code.out create mode 100644 v_windows/v/vlib/v/checker/tests/unreachable_code.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.out create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.out create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.out create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_required.out create mode 100644 v_windows/v/vlib/v/checker/tests/unsafe_required.vv create mode 100644 v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.out create mode 100644 v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.vv create mode 100644 v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.out create mode 100644 v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.vv create mode 100644 v_windows/v/vlib/v/checker/tests/var_duplicate_const.out create mode 100644 v_windows/v/vlib/v/checker/tests/var_duplicate_const.vv create mode 100644 v_windows/v/vlib/v/checker/tests/var_eval_not_used.out create mode 100644 v_windows/v/vlib/v/checker/tests/var_eval_not_used.vv create mode 100644 v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.out create mode 100644 v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.vv create mode 100644 v_windows/v/vlib/v/checker/tests/var_used_before_declaration.out create mode 100644 v_windows/v/vlib/v/checker/tests/var_used_before_declaration.vv create mode 100644 v_windows/v/vlib/v/checker/tests/void_fn_as_value.out create mode 100644 v_windows/v/vlib/v/checker/tests/void_fn_as_value.vv create mode 100644 v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.out create mode 100644 v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.vv create mode 100644 v_windows/v/vlib/v/checker/tests/void_optional_err.out create mode 100644 v_windows/v/vlib/v/checker/tests/void_optional_err.vv create mode 100644 v_windows/v/vlib/v/checker/tests/vweb_routing_checks.out create mode 100644 v_windows/v/vlib/v/checker/tests/vweb_routing_checks.vv create mode 100644 v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.out create mode 100644 v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.vv create mode 100644 v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.out create mode 100644 v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.vv create mode 100644 v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.out create mode 100644 v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.vv create mode 100644 v_windows/v/vlib/v/compiler_errors_test.v create mode 100644 v_windows/v/vlib/v/depgraph/depgraph.v create mode 100644 v_windows/v/vlib/v/doc/comment.v create mode 100644 v_windows/v/vlib/v/doc/doc.v create mode 100644 v_windows/v/vlib/v/doc/doc_private_fn_test.v create mode 100644 v_windows/v/vlib/v/doc/doc_test.v create mode 100644 v_windows/v/vlib/v/doc/module.v create mode 100644 v_windows/v/vlib/v/doc/node.v create mode 100644 v_windows/v/vlib/v/doc/utils.v create mode 100644 v_windows/v/vlib/v/dotgraph/dotgraph.c.v create mode 100644 v_windows/v/vlib/v/dotgraph/dotgraph.v create mode 100644 v_windows/v/vlib/v/embed_file/embed_file.v create mode 100644 v_windows/v/vlib/v/embed_file/embed_file_test.v create mode 100644 v_windows/v/vlib/v/embed_file/v.png create mode 100644 v_windows/v/vlib/v/errors/errors.v create mode 100644 v_windows/v/vlib/v/eval/eval.v create mode 100644 v_windows/v/vlib/v/fmt/align.v create mode 100644 v_windows/v/vlib/v/fmt/asm.v create mode 100644 v_windows/v/vlib/v/fmt/attrs.v create mode 100644 v_windows/v/vlib/v/fmt/comments.v create mode 100644 v_windows/v/vlib/v/fmt/fmt.v create mode 100644 v_windows/v/vlib/v/fmt/fmt_keep_test.v create mode 100644 v_windows/v/vlib/v/fmt/fmt_test.v create mode 100644 v_windows/v/vlib/v/fmt/fmt_vlib_test.v create mode 100644 v_windows/v/vlib/v/fmt/struct.v create mode 100644 v_windows/v/vlib/v/fmt/tests/anon_fn_as_param_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/anon_fn_call_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/anon_fn_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/anon_fn_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_decomposition_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_comment_ending_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_eol_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_inline_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_init_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_newlines_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_slices_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_slices_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/array_static_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/assembly/asm_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/asserts_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/asserts_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/asserts_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/attrs_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/attrs_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/attrs_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/bin2v_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/blocks_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/blocks_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/c_struct_init_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/cast_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/cast_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/chan_init_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/chan_ops_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/chan_or_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/char_literal_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/closure_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comments_array_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comments_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comments_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comptime_field_selector_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/comptime_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/concat_expr_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/concat_expr_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/conditional_compilation_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/conditions_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/conditions_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/consts_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/consts_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/consts_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/consts_with_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/embed_file_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/empty_curlies_and_parens_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/empty_lines_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/empty_lines_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/empty_lines_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/enum_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/enums_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/enums_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/expressions_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/expressions_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/file_with_just_imports_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fixed_size_array_type_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/float_literals_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/float_literals_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_headers_with_no_bodies_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_multi_return_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_parameter_the_same_as_a_module_const_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_return_generic_struct_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fn_with_anon_params_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fntype_alias_array_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fntype_alias_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fntype_mut_args_with_optional_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/fntype_return_optional_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/functions_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/functions_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/generic_recursive_structs_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/generic_structs_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/generics_cascade_types_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/generics_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/global_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/go_stmt_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/go_stmt_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/go_stmt_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/goto_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/goto_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/hashstmt_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/hashstmt_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/hashstmt_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_not_in_is_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_not_in_is_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_ternary_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_ternary_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/if_ternary_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_auto_added_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_auto_added_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_duplicate_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_duplicate_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_selective_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_selective_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_selective_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_single_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/import_with_alias_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/infix_expr_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/infix_expr_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/infix_expr_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/integer_literal_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/interface_declaration_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/interface_variadic_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/interface_with_mut_fields_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/labelled_break_continue_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/language_prefixes_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/loops_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/loops_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/manualfree_keep.v create mode 100644 v_windows/v/vlib/v/fmt/tests/maps_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/maps_in_fn_args__keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/maps_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/maps_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/maps_of_fns_with_string_keys_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/match_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/match_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/match_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/match_range_expression_branches_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/match_with_commented_branches_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/missing_import_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/missing_import_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/module_alias_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/module_interface_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/module_struct_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/multi_generic_test_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/multiline_comment_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/nested_map_type_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/newlines_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/no_main_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/no_main_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/offset_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/operator_overload_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/optional_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/optional_propagate_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/or_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/orm_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/par_expr_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/par_expr_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/pointer_casts_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/proto_module_importing_vproto_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/ref_type_cast_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/select_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/shared_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/shared_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/star__amp_int__cast_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/static_mut_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/stmt_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_interpolation_complex_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_interpolation_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_interpolation_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_interpolation_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_quotes_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_quotes_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/string_raw_and_cstr_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_decl_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_default_field_expressions_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_embed_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_init_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_init_with_comments_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_init_with_custom_len_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_init_with_ref_cast_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_no_extra_attr_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_update_comment_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_update_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/struct_with_fn_fields_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/structs_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/structs_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/sum_smartcast_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/thread_in_a_module_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/to_string_2_forms_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/trailing_space_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/trailing_space_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/type_ptr_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/typeof_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/types_expected.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/types_input.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/union_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/unsafe_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/vargs_reference_param_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/void_optional_keep.vv create mode 100644 v_windows/v/vlib/v/fmt/tests/vscript_keep.vv create mode 100644 v_windows/v/vlib/v/gen/c/array.v create mode 100644 v_windows/v/vlib/v/gen/c/assert.v create mode 100644 v_windows/v/vlib/v/gen/c/auto_eq_methods.v create mode 100644 v_windows/v/vlib/v/gen/c/auto_str_methods.v create mode 100644 v_windows/v/vlib/v/gen/c/cgen.v create mode 100644 v_windows/v/vlib/v/gen/c/cheaders.v create mode 100644 v_windows/v/vlib/v/gen/c/cmain.v create mode 100644 v_windows/v/vlib/v/gen/c/comptime.v create mode 100644 v_windows/v/vlib/v/gen/c/coutput_test.v create mode 100644 v_windows/v/vlib/v/gen/c/ctempvars.v create mode 100644 v_windows/v/vlib/v/gen/c/dumpexpr.v create mode 100644 v_windows/v/vlib/v/gen/c/embed.v create mode 100644 v_windows/v/vlib/v/gen/c/fn.v create mode 100644 v_windows/v/vlib/v/gen/c/index.v create mode 100644 v_windows/v/vlib/v/gen/c/infix_expr.v create mode 100644 v_windows/v/vlib/v/gen/c/json.v create mode 100644 v_windows/v/vlib/v/gen/c/live.v create mode 100644 v_windows/v/vlib/v/gen/c/profile.v create mode 100644 v_windows/v/vlib/v/gen/c/sql.v create mode 100644 v_windows/v/vlib/v/gen/c/str.v create mode 100644 v_windows/v/vlib/v/gen/c/str_intp.v create mode 100644 v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have create mode 100644 v_windows/v/vlib/v/gen/c/testdata/addition.out create mode 100644 v_windows/v/vlib/v/gen/c/testdata/addition.vv create mode 100644 v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have create mode 100644 v_windows/v/vlib/v/gen/c/testdata/const_references.out create mode 100644 v_windows/v/vlib/v/gen/c/testdata/const_references.vv create mode 100644 v_windows/v/vlib/v/gen/c/utils.v create mode 100644 v_windows/v/vlib/v/gen/js/array.v create mode 100644 v_windows/v/vlib/v/gen/js/builtin_types.v create mode 100644 v_windows/v/vlib/v/gen/js/comptime.v create mode 100644 v_windows/v/vlib/v/gen/js/fast_deep_equal.js create mode 100644 v_windows/v/vlib/v/gen/js/fn.v create mode 100644 v_windows/v/vlib/v/gen/js/js.v create mode 100644 v_windows/v/vlib/v/gen/js/jsdoc.v create mode 100644 v_windows/v/vlib/v/gen/js/jsgen_test.v create mode 100644 v_windows/v/vlib/v/gen/js/program_test.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/basic_test.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/compare_test.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/mappings.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/sets.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/source_map.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/source_map_generator.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_decode_test.v create mode 100644 v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_encode_test.v create mode 100644 v_windows/v/vlib/v/gen/js/str.v create mode 100644 v_windows/v/vlib/v/gen/js/temp_fast_deep_equal.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/.gitignore create mode 100644 v_windows/v/vlib/v/gen/js/tests/array.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/auto_deref_args.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/enum.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/hello/hello.js.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/hello/hello.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/hello/hello1/hello1.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/interface.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/interp.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/js.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/life.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/optional.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/simple.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/simple_sourcemap.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/struct.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/array.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/array.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/hw.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/hw.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/match.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/match.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/overloading.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/overloading.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/string.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/string.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.v create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/u64.out create mode 100644 v_windows/v/vlib/v/gen/js/tests/testdata/u64.v create mode 100644 v_windows/v/vlib/v/gen/native/amd64.v create mode 100644 v_windows/v/vlib/v/gen/native/arm64.v create mode 100644 v_windows/v/vlib/v/gen/native/elf.v create mode 100644 v_windows/v/vlib/v/gen/native/elf_obj.v create mode 100644 v_windows/v/vlib/v/gen/native/gen.v create mode 100644 v_windows/v/vlib/v/gen/native/macho.v create mode 100644 v_windows/v/vlib/v/gen/native/macho_test.v create mode 100644 v_windows/v/vlib/v/gen/native/tests/asm.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/asm.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/assert.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/assert.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/expressions.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/expressions.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/general.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/general.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/hello.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/hello.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/ifs.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/ifs.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/native_test.v create mode 100644 v_windows/v/vlib/v/gen/native/tests/print.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/print.vv.err create mode 100644 v_windows/v/vlib/v/gen/native/tests/print.vv.out create mode 100644 v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv create mode 100644 v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv.out create mode 100644 v_windows/v/vlib/v/live/common.v create mode 100644 v_windows/v/vlib/v/live/executable/reloader.v create mode 100644 v_windows/v/vlib/v/live/live_test.v create mode 100644 v_windows/v/vlib/v/live/live_test_template.vv create mode 100644 v_windows/v/vlib/v/live/sharedlib/live_sharedlib.v create mode 100644 v_windows/v/vlib/v/markused/markused.v create mode 100644 v_windows/v/vlib/v/markused/walker.v create mode 100644 v_windows/v/vlib/v/parser/assign.v create mode 100644 v_windows/v/vlib/v/parser/comptime.v create mode 100644 v_windows/v/vlib/v/parser/containers.v create mode 100644 v_windows/v/vlib/v/parser/expr.v create mode 100644 v_windows/v/vlib/v/parser/fn.v create mode 100644 v_windows/v/vlib/v/parser/for.v create mode 100644 v_windows/v/vlib/v/parser/if_match.v create mode 100644 v_windows/v/vlib/v/parser/lock.v create mode 100644 v_windows/v/vlib/v/parser/module.v create mode 100644 v_windows/v/vlib/v/parser/parse_type.v create mode 100644 v_windows/v/vlib/v/parser/parser.v create mode 100644 v_windows/v/vlib/v/parser/sql.v create mode 100644 v_windows/v/vlib/v/parser/struct.v create mode 100644 v_windows/v/vlib/v/parser/tests/README.md create mode 100644 v_windows/v/vlib/v/parser/tests/anon_fn_return_type.out create mode 100644 v_windows/v/vlib/v/parser/tests/anon_fn_return_type.vv create mode 100644 v_windows/v/vlib/v/parser/tests/anon_unused_param.out create mode 100644 v_windows/v/vlib/v/parser/tests/anon_unused_param.vv create mode 100644 v_windows/v/vlib/v/parser/tests/array_init.out create mode 100644 v_windows/v/vlib/v/parser/tests/array_init.vv create mode 100644 v_windows/v/vlib/v/parser/tests/array_pos_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/array_pos_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/c_struct_no_embed.out create mode 100644 v_windows/v/vlib/v/parser/tests/c_struct_no_embed.vv create mode 100644 v_windows/v/vlib/v/parser/tests/closure_not_declared.out create mode 100644 v_windows/v/vlib/v/parser/tests/closure_not_declared.vv create mode 100644 v_windows/v/vlib/v/parser/tests/closure_not_used.out create mode 100644 v_windows/v/vlib/v/parser/tests/closure_not_used.vv create mode 100644 v_windows/v/vlib/v/parser/tests/closure_undefined_var.out create mode 100644 v_windows/v/vlib/v/parser/tests/closure_undefined_var.vv create mode 100644 v_windows/v/vlib/v/parser/tests/const_index.out create mode 100644 v_windows/v/vlib/v/parser/tests/const_index.vv create mode 100644 v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/const_only_keyword.out create mode 100644 v_windows/v/vlib/v/parser/tests/const_only_keyword.vv create mode 100644 v_windows/v/vlib/v/parser/tests/const_unexpected_eof.out create mode 100644 v_windows/v/vlib/v/parser/tests/const_unexpected_eof.vv create mode 100644 v_windows/v/vlib/v/parser/tests/dec_use_as_value.out create mode 100644 v_windows/v/vlib/v/parser/tests/dec_use_as_value.vv create mode 100644 v_windows/v/vlib/v/parser/tests/defer_propagate.out create mode 100644 v_windows/v/vlib/v/parser/tests/defer_propagate.vv create mode 100644 v_windows/v/vlib/v/parser/tests/defer_return.out create mode 100644 v_windows/v/vlib/v/parser/tests/defer_return.vv create mode 100644 v_windows/v/vlib/v/parser/tests/defer_return2.out create mode 100644 v_windows/v/vlib/v/parser/tests/defer_return2.vv create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_type_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_type_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_type_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/duplicate_type_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/duplicated_generic_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/duplicated_generic_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/empty_name_expr_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/empty_name_expr_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expected_type_enum_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/expected_type_enum_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.out create mode 100644 v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.vv create mode 100644 v_windows/v/vlib/v/parser/tests/export_interop_func_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/export_interop_func_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.vv create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.out create mode 100644 v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.vv create mode 100644 v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/for.out create mode 100644 v_windows/v/vlib/v/parser/tests/for.vv create mode 100644 v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.out create mode 100644 v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.vv create mode 100644 v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.out create mode 100644 v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.vv create mode 100644 v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.out create mode 100644 v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.vv create mode 100644 v_windows/v/vlib/v/parser/tests/generic_lowercase_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/generic_lowercase_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.out create mode 100644 v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.vv create mode 100644 v_windows/v/vlib/v/parser/tests/if_guard_redefinition.out create mode 100644 v_windows/v/vlib/v/parser/tests/if_guard_redefinition.vv create mode 100644 v_windows/v/vlib/v/parser/tests/inc_use_as_value.out create mode 100644 v_windows/v/vlib/v/parser/tests/inc_use_as_value.vv create mode 100644 v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.out create mode 100644 v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.vv create mode 100644 v_windows/v/vlib/v/parser/tests/interface_duplicate_method.out create mode 100644 v_windows/v/vlib/v/parser/tests/interface_duplicate_method.vv create mode 100644 v_windows/v/vlib/v/parser/tests/interop_func_body_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/interop_func_body_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_c.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_c.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_d.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_attribute_d.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/long_generic_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/long_generic_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/map_init.out create mode 100644 v_windows/v/vlib/v/parser/tests/map_init.vv create mode 100644 v_windows/v/vlib/v/parser/tests/map_init_void.out create mode 100644 v_windows/v/vlib/v/parser/tests/map_init_void.vv create mode 100644 v_windows/v/vlib/v/parser/tests/map_init_void2.out create mode 100644 v_windows/v/vlib/v/parser/tests/map_init_void2.vv create mode 100644 v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.out create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.vv create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.out create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.vv create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.out create mode 100644 v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.vv create mode 100644 v_windows/v/vlib/v/parser/tests/module_multiple_names_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/module_multiple_names_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/module_syntax_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/module_syntax_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/nested_defer.out create mode 100644 v_windows/v/vlib/v/parser/tests/nested_defer.vv create mode 100644 v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.out create mode 100644 v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.vv create mode 100644 v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.out create mode 100644 v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.vv create mode 100644 v_windows/v/vlib/v/parser/tests/operator_normal_fn.out create mode 100644 v_windows/v/vlib/v/parser/tests/operator_normal_fn.vv create mode 100644 v_windows/v/vlib/v/parser/tests/or_default_missing.out create mode 100644 v_windows/v/vlib/v/parser/tests/or_default_missing.vv create mode 100644 v_windows/v/vlib/v/parser/tests/postfix_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/postfix_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/postfix_inc.out create mode 100644 v_windows/v/vlib/v/parser/tests/postfix_inc.vv create mode 100644 v_windows/v/vlib/v/parser/tests/prefix_first.out create mode 100644 v_windows/v/vlib/v/parser/tests/prefix_first.vv create mode 100644 v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.out create mode 100644 v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.vv create mode 100644 v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.out create mode 100644 v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.vv create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_alias.out create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_alias.vv create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_enum.out create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_enum.vv create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_interface.out create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_interface.vv create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_struct.out create mode 100644 v_windows/v/vlib/v/parser/tests/register_imported_struct.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_1.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_1.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_2.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_2.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_3.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_3.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_4.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_bad_key_4.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_else_1.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_else_1.vv create mode 100644 v_windows/v/vlib/v/parser/tests/select_else_2.out create mode 100644 v_windows/v/vlib/v/parser/tests/select_else_2.vv create mode 100644 v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_expected.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_expected.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_module_section.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_module_section.vv create mode 100644 v_windows/v/vlib/v/parser/tests/struct_update_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/struct_update_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/sum_type_exists_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/sum_type_exists_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/too_many_generics_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/too_many_generics_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.out create mode 100644 v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.vv create mode 100644 v_windows/v/vlib/v/parser/tests/unexpected_expr.out create mode 100644 v_windows/v/vlib/v/parser/tests/unexpected_expr.vv create mode 100644 v_windows/v/vlib/v/parser/tests/unexpected_keyword.out create mode 100644 v_windows/v/vlib/v/parser/tests/unexpected_keyword.vv create mode 100644 v_windows/v/vlib/v/parser/tests/unnecessary_mut.out create mode 100644 v_windows/v/vlib/v/parser/tests/unnecessary_mut.vv create mode 100644 v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.out create mode 100644 v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.vv create mode 100644 v_windows/v/vlib/v/parser/tmpl.v create mode 100644 v_windows/v/vlib/v/parser/v_parser_test.v create mode 100644 v_windows/v/vlib/v/pkgconfig/README.md create mode 100644 v_windows/v/vlib/v/pkgconfig/bin/pkgconfig.v create mode 100644 v_windows/v/vlib/v/pkgconfig/main.v create mode 100644 v_windows/v/vlib/v/pkgconfig/pkgconfig.v create mode 100644 v_windows/v/vlib/v/pkgconfig/pkgconfig_test.v create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/alsa.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/atk.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/autoopts.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/dep-resolution-fail.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/expat.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/form.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/gio-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/gio-unix-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/glib-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/gmodule-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/gmodule-no-export-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/gobject-2.0.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/libffi.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/libpcre.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/ncurses.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/sdl2.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/test_samples/zlib.pc create mode 100644 v_windows/v/vlib/v/pkgconfig/v.mod create mode 100644 v_windows/v/vlib/v/pref/default.v create mode 100644 v_windows/v/vlib/v/pref/os.v create mode 100644 v_windows/v/vlib/v/pref/pref.v create mode 100644 v_windows/v/vlib/v/pref/should_compile.v create mode 100644 v_windows/v/vlib/v/preludes/README.md create mode 100644 v_windows/v/vlib/v/preludes/live.v create mode 100644 v_windows/v/vlib/v/preludes/live_main.v create mode 100644 v_windows/v/vlib/v/preludes/live_shared.v create mode 100644 v_windows/v/vlib/v/preludes/profiled_program.v create mode 100644 v_windows/v/vlib/v/preludes/tests_assertions.v create mode 100644 v_windows/v/vlib/v/preludes/tests_with_stats.v create mode 100644 v_windows/v/vlib/v/preludes_js/stats_import.js.v create mode 100644 v_windows/v/vlib/v/preludes_js/tests_assertions.v create mode 100644 v_windows/v/vlib/v/preludes_js/tests_with_stats.v create mode 100644 v_windows/v/vlib/v/scanner/scanner.v create mode 100644 v_windows/v/vlib/v/scanner/scanner_test.v create mode 100644 v_windows/v/vlib/v/scanner/tests/bin_consecutively_separator_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/bin_consecutively_separator_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/bin_separator_in_front_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/bin_separator_in_front_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/dec_consecutively_separator_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/dec_consecutively_separator_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/float_literals_dot_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/float_literals_dot_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/hex_consecutively_separator_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/hex_consecutively_separator_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/hex_separator_in_front_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/hex_separator_in_front_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/oct_consecutively_separator_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/oct_consecutively_separator_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/oct_separator_in_front_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/oct_separator_in_front_err.vv create mode 100644 v_windows/v/vlib/v/scanner/tests/position_0_err.out create mode 100644 v_windows/v/vlib/v/scanner/tests/position_0_err.vv create mode 100644 v_windows/v/vlib/v/tests/alias_array_operator_overloading_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_custom_type_map_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_fixed_array_init_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_in_a_struct_field_autostr_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_map_operator_overloading_test.v create mode 100644 v_windows/v/vlib/v/tests/alias_sumtype_method_call_test.v create mode 100644 v_windows/v/vlib/v/tests/aliased_array_operations_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_call_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_in_containers_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_redefinition_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_returning_question_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_with_array_arguments_test.v create mode 100644 v_windows/v/vlib/v/tests/anon_fn_with_optional_test.v create mode 100644 v_windows/v/vlib/v/tests/appending_to_mut_array_in_fn_param_test.v create mode 100644 v_windows/v/vlib/v/tests/array_append_short_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/array_cast_test.v create mode 100644 v_windows/v/vlib/v/tests/array_equality_test.v create mode 100644 v_windows/v/vlib/v/tests/array_init_test.v create mode 100644 v_windows/v/vlib/v/tests/array_map_or_test.v create mode 100644 v_windows/v/vlib/v/tests/array_map_ref_test.v create mode 100644 v_windows/v/vlib/v/tests/array_methods_test.v create mode 100644 v_windows/v/vlib/v/tests/array_of_alias_slice_test.v create mode 100644 v_windows/v/vlib/v/tests/array_of_sumtype_append_aggregate_type_test.v create mode 100644 v_windows/v/vlib/v/tests/array_of_sumtypes_test.v create mode 100644 v_windows/v/vlib/v/tests/array_slice_test.v create mode 100644 v_windows/v/vlib/v/tests/array_sort_lt_overload_test.v create mode 100644 v_windows/v/vlib/v/tests/array_test.v create mode 100644 v_windows/v/vlib/v/tests/array_to_string_test.v create mode 100644 v_windows/v/vlib/v/tests/array_type_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/as_cast_already_smartcast_sumtype_test.v create mode 100644 v_windows/v/vlib/v/tests/as_cast_is_expr_sumtype_fn_result_test.v create mode 100644 v_windows/v/vlib/v/tests/assembly/asm_test.amd64.v create mode 100644 v_windows/v/vlib/v/tests/assembly/asm_test.i386.v create mode 100644 v_windows/v/vlib/v/tests/assembly/naked_attr_test.amd64.v create mode 100644 v_windows/v/vlib/v/tests/assembly/naked_attr_test.i386.v create mode 100644 v_windows/v/vlib/v/tests/assembly/util/dot_amd64_util.amd64.v create mode 100644 v_windows/v/vlib/v/tests/assert_fn_call_with_parentheses_test.v create mode 100644 v_windows/v/vlib/v/tests/assert_sumtype_test.v create mode 100644 v_windows/v/vlib/v/tests/assert_with_newlines_test.v create mode 100644 v_windows/v/vlib/v/tests/assign_bitops_with_type_aliases_test.v create mode 100644 v_windows/v/vlib/v/tests/assign_map_value_of_fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/attribute_test.v create mode 100644 v_windows/v/vlib/v/tests/autolock_array1_test.v create mode 100644 v_windows/v/vlib/v/tests/autolock_array2_test.v create mode 100644 v_windows/v/vlib/v/tests/backtrace_test.v create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.svg create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench.v create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench_full.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench_incr.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench_non_opt.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/GC_bench_opt.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/Makefile create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/Resources.plt create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.pdf create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.svg create mode 100644 v_windows/v/vlib/v/tests/bench/gcboehm/resources.txt create mode 100644 v_windows/v/vlib/v/tests/bench/val_vs_ptr.c create mode 100644 v_windows/v/vlib/v/tests/blank_ident_test.v create mode 100644 v_windows/v/vlib/v/tests/break_in_lock_test.v create mode 100644 v_windows/v/vlib/v/tests/c_struct_free/c_struct_free_property_test.v create mode 100644 v_windows/v/vlib/v/tests/c_struct_free/free_struct.c create mode 100644 v_windows/v/vlib/v/tests/calling_module_functions_with_maps_of_arrays_test.v create mode 100644 v_windows/v/vlib/v/tests/cast_to_byte_test.v create mode 100644 v_windows/v/vlib/v/tests/cast_to_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/cflags/includes/myinclude.h create mode 100644 v_windows/v/vlib/v/tests/cflags/v.mod create mode 100644 v_windows/v/vlib/v/tests/cflags/vmodroot_and_vroot_test.v create mode 100644 v_windows/v/vlib/v/tests/channels_test.v create mode 100644 v_windows/v/vlib/v/tests/clash_var_name_of_array_and_map_test.v create mode 100644 v_windows/v/vlib/v/tests/closure_test.v create mode 100644 v_windows/v/vlib/v/tests/complex_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_at_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_attribute_selector_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_bittness_and_endianess_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_call_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_field_selector_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_for_over_struct_with_C_reserved_word_fields_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_for_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_is_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_pkgconfig_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_threads_no_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_if_threads_yes_test.v create mode 100644 v_windows/v/vlib/v/tests/comptime_method_args_test.v create mode 100644 v_windows/v/vlib/v/tests/const_can_use_optionals_test.v create mode 100644 v_windows/v/vlib/v/tests/const_comptime_eval_before_vinit_test.v create mode 100644 v_windows/v/vlib/v/tests/const_embed_test.v create mode 100644 v_windows/v/vlib/v/tests/const_eval_simple_int_expressions_at_comptime_test.v create mode 100644 v_windows/v/vlib/v/tests/const_init_order_test.v create mode 100644 v_windows/v/vlib/v/tests/const_reference_argument_test.v create mode 100644 v_windows/v/vlib/v/tests/const_representation_test.v create mode 100644 v_windows/v/vlib/v/tests/const_test.v create mode 100644 v_windows/v/vlib/v/tests/conversions_test.v create mode 100644 v_windows/v/vlib/v/tests/cross_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/cstrings_test.v create mode 100644 v_windows/v/vlib/v/tests/defer_return_test.v create mode 100644 v_windows/v/vlib/v/tests/defer_test.v create mode 100644 v_windows/v/vlib/v/tests/differently_named_structs_test.v create mode 100644 v_windows/v/vlib/v/tests/double_ref_deref_test.v create mode 100644 v_windows/v/vlib/v/tests/dump_fns_test.v create mode 100644 v_windows/v/vlib/v/tests/enum_array_field_test.v create mode 100644 v_windows/v/vlib/v/tests/enum_bitfield_test.v create mode 100644 v_windows/v/vlib/v/tests/enum_default_value_in_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/enum_hex_test.v create mode 100644 v_windows/v/vlib/v/tests/enum_test.v create mode 100644 v_windows/v/vlib/v/tests/failing_tests_test.v create mode 100644 v_windows/v/vlib/v/tests/field_publicity/embed.v create mode 100644 v_windows/v/vlib/v/tests/filter_in_map_test.v create mode 100644 v_windows/v/vlib/v/tests/fixed_array_const_size_test.v create mode 100644 v_windows/v/vlib/v/tests/fixed_array_init_test.v create mode 100644 v_windows/v/vlib/v/tests/fixed_array_of_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/fixed_array_to_string_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_assignment_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_cross_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_expecting_ref_but_returning_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_expecting_ref_but_returning_struct_time_module_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_high_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_index_direct_call_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_multiple_returns_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_mut_arg_of_array_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_mut_args_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_return_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_shared_return_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_type_aliases_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_variadic_test.v create mode 100644 v_windows/v/vlib/v/tests/fn_with_fixed_array_function_args_test.v create mode 100644 v_windows/v/vlib/v/tests/for_c_multi_vars_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_containers_of_fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_iterator_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_mut_reference_selector_val_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_mut_val_test.v create mode 100644 v_windows/v/vlib/v/tests/for_in_optional_test.v create mode 100644 v_windows/v/vlib/v/tests/for_label_continue_break_test.v create mode 100644 v_windows/v/vlib/v/tests/for_loops_2_test.v create mode 100644 v_windows/v/vlib/v/tests/for_loops_test.v create mode 100644 v_windows/v/vlib/v/tests/for_smartcast_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_chan_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_assign_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_map_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_modifier_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_multi_paras_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_nested_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_infer_variadic_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_returning_type_with_T_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_typeof_name_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_fn_upper_name_type_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_functions_with_normal_function_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/generic_sumtype_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_T_typ_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_array_typedef_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_assign_reference_generic_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_call_with_reference_arg_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_fn_return_generic_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_from_modules/genericmodule/take.v create mode 100644 v_windows/v/vlib/v/tests/generics_from_modules/inference_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_in_big_struct_method_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_in_generics_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_indirect_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_interface_decl_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_interface_method_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_interface_with_multi_generic_structs_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_interface_with_multi_generic_types_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_method_on_alias_struct_receiver_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_method_on_receiver_types_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_method_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_multi_array_in_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_multi_types_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_inconsistent_types_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_multi_array_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_multiple_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_recursive_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_return_reference_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_anon_fn_fields_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_anon_fn_type_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_to_string_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_with_array_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_struct_with_non_generic_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_anon_generics_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_cascaded_multiple_nested_generics_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_embed_generics_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_fixed_array_type_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_generics_fn_return_generics_fn_type_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_generics_fn_type_parameter_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_generics_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_generics_struct_receiver_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_multi_generics_struct_types_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_multiple_generics_struct_receiver_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_nested_external_generics_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_nested_generic_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_nested_generics_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_recursive_generics_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_recursive_generics_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/generics_with_variadic_generic_args_test.v create mode 100644 v_windows/v/vlib/v/tests/go_array_wait_test.v create mode 100644 v_windows/v/vlib/v/tests/go_call_generic_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/go_call_interface_method_test.v create mode 100644 v_windows/v/vlib/v/tests/go_handle_for_functions_returning_array_test.v create mode 100644 v_windows/v/vlib/v/tests/go_wait_1_test.v create mode 100644 v_windows/v/vlib/v/tests/go_wait_2_test.v create mode 100644 v_windows/v/vlib/v/tests/go_wait_3_test.v create mode 100644 v_windows/v/vlib/v/tests/go_wait_option_test.v create mode 100644 v_windows/v/vlib/v/tests/go_wait_with_fn_of_interface_para.v create mode 100644 v_windows/v/vlib/v/tests/goto_test.v create mode 100644 v_windows/v/vlib/v/tests/heap_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/heap_reference_test.v create mode 100644 v_windows/v/vlib/v/tests/heap_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/if_expr_of_multi_stmts_test.v create mode 100644 v_windows/v/vlib/v/tests/if_expr_of_optional_test.v create mode 100644 v_windows/v/vlib/v/tests/if_expr_with_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/if_expression_test.v create mode 100644 v_windows/v/vlib/v/tests/if_guard_test.v create mode 100644 v_windows/v/vlib/v/tests/if_smartcast_multi_conds_test.v create mode 100644 v_windows/v/vlib/v/tests/if_smartcast_nested_selector_exprs_test.v create mode 100644 v_windows/v/vlib/v/tests/if_smartcast_test.v create mode 100644 v_windows/v/vlib/v/tests/imported_symbols_test.v create mode 100644 v_windows/v/vlib/v/tests/in_expression_test.v create mode 100644 v_windows/v/vlib/v/tests/infix_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/init_global_test.v create mode 100644 v_windows/v/vlib/v/tests/inout/.gitignore create mode 100644 v_windows/v/vlib/v/tests/inout/bad_st_as.out create mode 100644 v_windows/v/vlib/v/tests/inout/bad_st_as.vv create mode 100644 v_windows/v/vlib/v/tests/inout/cli_command_no_execute.out create mode 100644 v_windows/v/vlib/v/tests/inout/cli_command_no_execute.vv create mode 100644 v_windows/v/vlib/v/tests/inout/cli_root_default_help.out create mode 100644 v_windows/v/vlib/v/tests/inout/cli_root_default_help.vv create mode 100644 v_windows/v/vlib/v/tests/inout/compiler_test.v create mode 100644 v_windows/v/vlib/v/tests/inout/dump_expression.out create mode 100644 v_windows/v/vlib/v/tests/inout/dump_expression.vv create mode 100644 v_windows/v/vlib/v/tests/inout/enum_print.out create mode 100644 v_windows/v/vlib/v/tests/inout/enum_print.vv create mode 100644 v_windows/v/vlib/v/tests/inout/file.html create mode 100644 v_windows/v/vlib/v/tests/inout/file.md create mode 100644 v_windows/v/vlib/v/tests/inout/fixed_array_index.out create mode 100644 v_windows/v/vlib/v/tests/inout/fixed_array_index.vv create mode 100644 v_windows/v/vlib/v/tests/inout/fixed_array_slice.out create mode 100644 v_windows/v/vlib/v/tests/inout/fixed_array_slice.vv create mode 100644 v_windows/v/vlib/v/tests/inout/footer.md create mode 100644 v_windows/v/vlib/v/tests/inout/header.md create mode 100644 v_windows/v/vlib/v/tests/inout/hello.out create mode 100644 v_windows/v/vlib/v/tests/inout/hello.vv create mode 100644 v_windows/v/vlib/v/tests/inout/hello_devs.out create mode 100644 v_windows/v/vlib/v/tests/inout/hello_devs.vv create mode 100644 v_windows/v/vlib/v/tests/inout/nested_structs.out create mode 100644 v_windows/v/vlib/v/tests/inout/nested_structs.vv create mode 100644 v_windows/v/vlib/v/tests/inout/os.out create mode 100644 v_windows/v/vlib/v/tests/inout/os.vv create mode 100644 v_windows/v/vlib/v/tests/inout/panic_with_cg.out create mode 100644 v_windows/v/vlib/v/tests/inout/panic_with_cg.vv create mode 100644 v_windows/v/vlib/v/tests/inout/printing_fixed_array_of_pointers.out create mode 100644 v_windows/v/vlib/v/tests/inout/printing_fixed_array_of_pointers.vv create mode 100644 v_windows/v/vlib/v/tests/inout/string_interp.out create mode 100644 v_windows/v/vlib/v/tests/inout/string_interp.vv create mode 100644 v_windows/v/vlib/v/tests/inout/tmpl_all_in_one_folder.out create mode 100644 v_windows/v/vlib/v/tests/inout/tmpl_all_in_one_folder.vv create mode 100644 v_windows/v/vlib/v/tests/inout/tmpl_parse_html.out create mode 100644 v_windows/v/vlib/v/tests/inout/tmpl_parse_html.vv create mode 100644 v_windows/v/vlib/v/tests/int_cmp_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_auto_str_gen_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/array_of_interfaces_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/array_of_interfaces_with_utility_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/assign_to_interface_field_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/empty_interface_1_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/empty_interface_println_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/fn_returning_voidptr_casted_as_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i1_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i2_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i3_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i4_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i5_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i6_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i7_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i8_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/i9_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/interface_many_named_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/pass_voidptr_as_interface_reference_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_edge_cases/voidptr_casted_as_an_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_embedding_deep_nesting_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_embedding_recursive_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_embedding_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_embedding_with_interface_para_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_fields_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_fields_typearray_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_fn_return_array_of_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_fn_return_with_struct_init.v create mode 100644 v_windows/v/vlib/v/tests/interface_nested_field_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_only_decl_with_optional_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_runtime_conversions_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_test.v create mode 100644 v_windows/v/vlib/v/tests/interface_variadic_test.v create mode 100644 v_windows/v/vlib/v/tests/interfaces_map_test.v create mode 100644 v_windows/v/vlib/v/tests/interop_test.v create mode 100644 v_windows/v/vlib/v/tests/isreftype_test.v create mode 100644 v_windows/v/vlib/v/tests/keep_args_alive_test.v create mode 100644 v_windows/v/vlib/v/tests/keep_args_alive_test_c.h create mode 100644 v_windows/v/vlib/v/tests/local/local.v create mode 100644 v_windows/v/vlib/v/tests/local_test.v create mode 100644 v_windows/v/vlib/v/tests/lock_selector_test.v create mode 100644 v_windows/v/vlib/v/tests/map_alias_key_test.v create mode 100644 v_windows/v/vlib/v/tests/map_and_array_with_fns_test.v create mode 100644 v_windows/v/vlib/v/tests/map_assign_array_of_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/map_complex_fixed_array_test.v create mode 100644 v_windows/v/vlib/v/tests/map_enum_keys_test.v create mode 100644 v_windows/v/vlib/v/tests/map_equality_test.v create mode 100644 v_windows/v/vlib/v/tests/map_high_order_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/map_key_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/map_literals_method_call_test.v create mode 100644 v_windows/v/vlib/v/tests/map_mut_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/map_to_string_test.v create mode 100644 v_windows/v/vlib/v/tests/map_type_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/map_value_init_test.v create mode 100644 v_windows/v/vlib/v/tests/maps_equal_test.v create mode 100644 v_windows/v/vlib/v/tests/match_compound_type_cond_test.v create mode 100644 v_windows/v/vlib/v/tests/match_error_to_none_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expr_returning_optional_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expr_with_if_or_match_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expr_with_one_branch_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expr_with_promote_number_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expression_for_types_test.v create mode 100644 v_windows/v/vlib/v/tests/match_expression_with_fn_names_in_branches_test.v create mode 100644 v_windows/v/vlib/v/tests/match_in_fn_call_test.v create mode 100644 v_windows/v/vlib/v/tests/match_in_if_expression_test.v create mode 100644 v_windows/v/vlib/v/tests/match_in_map_init_test.v create mode 100644 v_windows/v/vlib/v/tests/match_in_map_or_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/match_interface_test.v create mode 100644 v_windows/v/vlib/v/tests/match_smartcast_test.v create mode 100644 v_windows/v/vlib/v/tests/match_sumtype_var_aggregate_test.v create mode 100644 v_windows/v/vlib/v/tests/match_sumtype_var_shadow_and_as_test.v create mode 100644 v_windows/v/vlib/v/tests/match_test.v create mode 100644 v_windows/v/vlib/v/tests/match_with_complex_exprs_in_branches_test.v create mode 100644 v_windows/v/vlib/v/tests/match_with_complex_sumtype_exprs_test.v create mode 100644 v_windows/v/vlib/v/tests/match_with_multi_sumtype_exprs_test.v create mode 100644 v_windows/v/vlib/v/tests/methods_on_interfaces_test.v create mode 100644 v_windows/v/vlib/v/tests/missing_config_struct_arg_test.v create mode 100644 v_windows/v/vlib/v/tests/module_test.v create mode 100644 v_windows/v/vlib/v/tests/module_type_cast_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/acommentedmodule/commentedfile.v create mode 100644 v_windows/v/vlib/v/tests/modules/amodule/another_internal_module_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/amodule/internal_module_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/amodule/module.v create mode 100644 v_windows/v/vlib/v/tests/modules/geometry/geometry.v create mode 100644 v_windows/v/vlib/v/tests/modules/methods_struct_another_module/methods_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/simplemodule/importing_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/simplemodule/simplemodule.v create mode 100644 v_windows/v/vlib/v/tests/modules/submodules/submodules.v create mode 100644 v_windows/v/vlib/v/tests/modules/submodules/submodules_test.v create mode 100644 v_windows/v/vlib/v/tests/modules/submodules/test/test.v create mode 100644 v_windows/v/vlib/v/tests/modules/submodules/test/test2/test2.v create mode 100644 v_windows/v/vlib/v/tests/multiple_assign_array_index_test.v create mode 100644 v_windows/v/vlib/v/tests/multiple_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/main.vv create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/path1/.gitignore create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/path1/xxx/m.v create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/path2/yyy/m.v create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/path3/zzz/m.v create mode 100644 v_windows/v/vlib/v/tests/multiple_paths_in_vmodules/vmodules_overrides_test.v create mode 100644 v_windows/v/vlib/v/tests/multiret_with_ptrtype_test.v create mode 100644 v_windows/v/vlib/v/tests/mut_receiver_returned_as_reference_test.v create mode 100644 v_windows/v/vlib/v/tests/mut_test.v create mode 100644 v_windows/v/vlib/v/tests/named_break_continue_test.v create mode 100644 v_windows/v/vlib/v/tests/nest_defer_fn_test.v create mode 100644 v_windows/v/vlib/v/tests/nested_anonfunc_and_for_break_test.v create mode 100644 v_windows/v/vlib/v/tests/nested_map_test.v create mode 100644 v_windows/v/vlib/v/tests/nested_multiline_comments_test.v create mode 100644 v_windows/v/vlib/v/tests/nested_option_call_test.v create mode 100644 v_windows/v/vlib/v/tests/num_lit_call_method_test.v create mode 100644 v_windows/v/vlib/v/tests/offsetof_test.v create mode 100644 v_windows/v/vlib/v/tests/operator_overloading_cmp_test.v create mode 100644 v_windows/v/vlib/v/tests/operator_overloading_with_string_interpolation_test.v create mode 100644 v_windows/v/vlib/v/tests/option_2_test.v create mode 100644 v_windows/v/vlib/v/tests/option_default_values_test.v create mode 100644 v_windows/v/vlib/v/tests/option_if_assign_and_fallthrough_test.v create mode 100644 v_windows/v/vlib/v/tests/option_if_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/option_in_loop_test.v create mode 100644 v_windows/v/vlib/v/tests/option_print_errors_test.v create mode 100644 v_windows/v/vlib/v/tests/option_struct_init_test.v create mode 100644 v_windows/v/vlib/v/tests/option_test.v create mode 100644 v_windows/v/vlib/v/tests/option_void_test.v create mode 100644 v_windows/v/vlib/v/tests/optional_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/orm_sub_array_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/orm_sub_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/parser_line_test.v create mode 100644 v_windows/v/vlib/v/tests/pointer_arithmetic_test.v create mode 100644 v_windows/v/vlib/v/tests/pointers_multilevel_casts_test.v create mode 100644 v_windows/v/vlib/v/tests/pointers_str_test.v create mode 100644 v_windows/v/vlib/v/tests/pointers_test.v create mode 100644 v_windows/v/vlib/v/tests/prefix_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/print_test.v create mode 100644 v_windows/v/vlib/v/tests/printing_c_structs/cstruct.h create mode 100644 v_windows/v/vlib/v/tests/printing_c_structs/string_interpolation_test.v create mode 100644 v_windows/v/vlib/v/tests/printing_c_structs/v.mod create mode 100644 v_windows/v/vlib/v/tests/prod/.gitignore create mode 100644 v_windows/v/vlib/v/tests/prod/asserts_should_be_skipped.prod.v create mode 100644 v_windows/v/vlib/v/tests/prod/asserts_should_be_skipped.prod.v.expected.txt create mode 100644 v_windows/v/vlib/v/tests/prod/assoc.prod.v create mode 100644 v_windows/v/vlib/v/tests/prod/assoc.prod.v.expected.txt create mode 100644 v_windows/v/vlib/v/tests/prod_test.v create mode 100644 v_windows/v/vlib/v/tests/profile/profile_test.v create mode 100644 v_windows/v/vlib/v/tests/profile/profile_test_1.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/.gitignore create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/.v.mod.stop create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/main.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/main_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/mod1/c/header.h create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/mod1/c/implementation.c create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/mod1/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code/mod1/wrapper.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/.gitignore create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/.v.mod.stop create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/main.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/main2_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/modc/header.h create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/modc/impl.c create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/modc/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_2/modc/wrapper.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/.gitignore create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/.v.mod.stop create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/main.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/main_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/mod1/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/mod1/wrapper.c.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/mod1/wrapper.js.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_3/mod1/wrapper.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_ct_ifs/a_linux.h create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_ct_ifs/a_nonlinux.h create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_ct_ifs/ctimeifblock.v create mode 100644 v_windows/v/vlib/v/tests/project_with_c_code_ct_ifs/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/.gitignore create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/README.md create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/bin/a_program_under_bin_can_find_mod1_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/bin/main.vsh create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/mod11/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/mod12/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/mod13/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/mod14/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/submodule/m.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/mod1/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/tests/submodule_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_modules_having_submodules/v.mod create mode 100644 v_windows/v/vlib/v/tests/project_with_tests_for_main/README.md create mode 100644 v_windows/v/vlib/v/tests/project_with_tests_for_main/main.v create mode 100644 v_windows/v/vlib/v/tests/project_with_tests_for_main/my_other_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_tests_for_main/my_test.v create mode 100644 v_windows/v/vlib/v/tests/project_with_tests_for_main/v.mod create mode 100644 v_windows/v/vlib/v/tests/projects_that_should_compile_test.v create mode 100644 v_windows/v/vlib/v/tests/ptr_arithmetic_test.v create mode 100644 v_windows/v/vlib/v/tests/raw_string_test.v create mode 100644 v_windows/v/vlib/v/tests/ref_return_test.v create mode 100644 v_windows/v/vlib/v/tests/ref_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/reference_return_test.v create mode 100644 v_windows/v/vlib/v/tests/reliability/semaphore_wait.v create mode 100644 v_windows/v/vlib/v/tests/repeated_multiret_values_test.v create mode 100644 v_windows/v/vlib/v/tests/repl/.gitattributes create mode 100644 v_windows/v/vlib/v/tests/repl/.gitignore create mode 100644 v_windows/v/vlib/v/tests/repl/README.md create mode 100644 v_windows/v/vlib/v/tests/repl/array_filter.repl create mode 100644 v_windows/v/vlib/v/tests/repl/array_init.repl create mode 100644 v_windows/v/vlib/v/tests/repl/bad_in_type.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/chained_fields/bd.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/chained_fields/c.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/chained_fields/c2.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/chained_fields/d.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/chained_fields/ef.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/conditional_blocks/for.repl create mode 100644 v_windows/v/vlib/v/tests/repl/conditional_blocks/if.repl create mode 100644 v_windows/v/vlib/v/tests/repl/conditional_blocks/if_else.repl create mode 100644 v_windows/v/vlib/v/tests/repl/default_printing.repl create mode 100644 v_windows/v/vlib/v/tests/repl/empty_struct.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/entire_commented_module.repl create mode 100644 v_windows/v/vlib/v/tests/repl/error.repl create mode 100644 v_windows/v/vlib/v/tests/repl/error_nosave.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/function.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/immutable_len_fields/fields.1.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/immutable_len_fields/fields.2.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/immutable_len_fields/fields.3.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/import.repl create mode 100644 v_windows/v/vlib/v/tests/repl/naked_strings.repl create mode 100644 v_windows/v/vlib/v/tests/repl/newlines.repl create mode 100644 v_windows/v/vlib/v/tests/repl/nomain.repl create mode 100644 v_windows/v/vlib/v/tests/repl/nothing.repl create mode 100644 v_windows/v/vlib/v/tests/repl/open_close_string_check.repl create mode 100644 v_windows/v/vlib/v/tests/repl/option.repl.skip create mode 100644 v_windows/v/vlib/v/tests/repl/optional_call.repl create mode 100644 v_windows/v/vlib/v/tests/repl/postfix_operators.repl create mode 100644 v_windows/v/vlib/v/tests/repl/println.repl create mode 100644 v_windows/v/vlib/v/tests/repl/repl_test.v create mode 100644 v_windows/v/vlib/v/tests/repl/runner/runner.v create mode 100644 v_windows/v/vlib/v/tests/repl/var_decl.repl create mode 100644 v_windows/v/vlib/v/tests/reserved_keywords_can_be_escaped_with_at_test.v create mode 100644 v_windows/v/vlib/v/tests/return_in_lock_test.v create mode 100644 v_windows/v/vlib/v/tests/return_voidptr_test.v create mode 100644 v_windows/v/vlib/v/tests/reusable_mut_multiret_values_test.v create mode 100644 v_windows/v/vlib/v/tests/run_project_folders/issue_10023_multiple_anon_fns_with_same_position/a.v create mode 100644 v_windows/v/vlib/v/tests/run_project_folders/issue_10023_multiple_anon_fns_with_same_position/b.v create mode 100644 v_windows/v/vlib/v/tests/run_project_folders_test.v create mode 100644 v_windows/v/vlib/v/tests/run_v_code_from_stdin_test.v create mode 100644 v_windows/v/vlib/v/tests/semaphore_test.v create mode 100644 v_windows/v/vlib/v/tests/semaphore_timed_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_arg_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_array_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_autolock_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_elem_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_fn_return_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_in_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_2_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_3_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_4_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_5_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_6_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_expr_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_lock_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_map_test.v create mode 100644 v_windows/v/vlib/v/tests/shared_unordered_mixed_test.v create mode 100644 v_windows/v/vlib/v/tests/shift_test.v create mode 100644 v_windows/v/vlib/v/tests/short_struct_param_syntax_test.v create mode 100644 v_windows/v/vlib/v/tests/sizeof_2_test.v create mode 100644 v_windows/v/vlib/v/tests/sizeof_test.v create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_passes_test.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_passes_test.skip_unused.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_passes_test.vv create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_works_test.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_works_test.skip_unused.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/assert_works_test.vv create mode 100644 v_windows/v/vlib/v/tests/skip_unused/hw.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/hw.skip_unused.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/hw.vv create mode 100644 v_windows/v/vlib/v/tests/skip_unused/println_os_executable.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/println_os_executable.skip_unused.run.out create mode 100644 v_windows/v/vlib/v/tests/skip_unused/println_os_executable.vv create mode 100644 v_windows/v/vlib/v/tests/sorting_by_different_criteria_test.v create mode 100644 v_windows/v/vlib/v/tests/sorting_by_references_test.v create mode 100644 v_windows/v/vlib/v/tests/static_arrays_using_const_for_size_test.v create mode 100644 v_windows/v/vlib/v/tests/static_vars_test.v create mode 100644 v_windows/v/vlib/v/tests/str_circular_test.v create mode 100644 v_windows/v/vlib/v/tests/str_gen_test.v create mode 100644 v_windows/v/vlib/v/tests/string_alias_of_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/string_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/string_escape_backslash_test.v create mode 100644 v_windows/v/vlib/v/tests/string_index_or_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_array_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_custom_str_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_floats_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_function_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_multi_return_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_multistmt_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_of_array_of_structs_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_shared_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_string_args_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_struct_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_sumtype_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_test.v create mode 100644 v_windows/v/vlib/v/tests/string_interpolation_variadic_test.v create mode 100644 v_windows/v/vlib/v/tests/string_struct_interpolation_test.v create mode 100644 v_windows/v/vlib/v/tests/strings_unicode_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_allow_both_field_defaults_and_skip_flag_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_chained_fields_correct_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_child_field_default_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_embed_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_eq_op_only_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_equality_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_field_default_value_interface_cast_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_field_default_value_sumtype_cast_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_fields_required_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_fields_storing_functions_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_ierror_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_init_and_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_init_with_fixed_array_field_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_map_method_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_test.v create mode 100644 v_windows/v/vlib/v/tests/struct_transmute_test.v create mode 100644 v_windows/v/vlib/v/tests/structs_with_voidptr_fields_can_be_printed_test.v create mode 100644 v_windows/v/vlib/v/tests/sum_type_common_fields_test.v create mode 100644 v_windows/v/vlib/v/tests/sum_type_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_assign_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_calls_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_equality_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_literal_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_str_for_subtypes_with_str_test.v create mode 100644 v_windows/v/vlib/v/tests/sumtype_str_test.v create mode 100644 v_windows/v/vlib/v/tests/supports__likely__test.v create mode 100644 v_windows/v/vlib/v/tests/testcase_leak.v create mode 100644 v_windows/v/vlib/v/tests/testdata/enum_in_builtin/builtin.v create mode 100644 v_windows/v/vlib/v/tests/testdata/enum_in_builtin/c.v create mode 100644 v_windows/v/vlib/v/tests/testdata/enum_in_builtin/main.v create mode 100644 v_windows/v/vlib/v/tests/testdata/sizeof_used_in_assert_test.v create mode 100644 v_windows/v/vlib/v/tests/testdata/tests_returning_options_failing_test.v create mode 100644 v_windows/v/vlib/v/tests/thread_to_string_test.v create mode 100644 v_windows/v/vlib/v/tests/tmpl/base.txt create mode 100644 v_windows/v/vlib/v/tests/tmpl/inner.txt create mode 100644 v_windows/v/vlib/v/tests/tmpl_test.v create mode 100644 v_windows/v/vlib/v/tests/type_alias_of_pointer_types_test.v create mode 100644 v_windows/v/vlib/v/tests/type_alias_str_method_override_test.v create mode 100644 v_windows/v/vlib/v/tests/type_alias_test.v create mode 100644 v_windows/v/vlib/v/tests/type_idx_test.v create mode 100644 v_windows/v/vlib/v/tests/type_name_in_if_test.v create mode 100644 v_windows/v/vlib/v/tests/type_name_test.v create mode 100644 v_windows/v/vlib/v/tests/type_promotion_test.v create mode 100644 v_windows/v/vlib/v/tests/type_voidptr_test.v create mode 100644 v_windows/v/vlib/v/tests/typeof_simple_types_test.v create mode 100644 v_windows/v/vlib/v/tests/typeof_test.v create mode 100644 v_windows/v/vlib/v/tests/unreachable_code_paths_test.v create mode 100644 v_windows/v/vlib/v/tests/unsafe_test.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/1.strings_and_arrays.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/array_init_with_string_variable.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/base64.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/buffer_passed_in_fn_that_uses_tos_on_it.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/fn_returning_string_param.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/fn_with_return_should_free_local_vars.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/if_expr.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/if_expr_skip.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/logging.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/option_reassigned.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/option_simple.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/rune_str_method.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/simple_interpolation.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode_more_scopes.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/string_str_method.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/struct_field.v create mode 100644 v_windows/v/vlib/v/tests/valgrind/valgrind_test.v create mode 100644 v_windows/v/vlib/v/tests/vargs_auto_str_method_and_println_test.v create mode 100644 v_windows/v/vlib/v/tests/vargs_empty_param_test.v create mode 100644 v_windows/v/vlib/v/tests/vargs_reference_param_test.v create mode 100644 v_windows/v/vlib/v/tests/vmod_parser_test.v create mode 100644 v_windows/v/vlib/v/tests/voidptr_to_u64_cast_a_test.v create mode 100644 v_windows/v/vlib/v/tests/voidptr_to_u64_cast_b_test.v create mode 100644 v_windows/v/vlib/v/tests/working_with_an_empty_struct_test.v create mode 100644 v_windows/v/vlib/v/token/position.v create mode 100644 v_windows/v/vlib/v/token/token.v create mode 100644 v_windows/v/vlib/v/transformer/transformer.v create mode 100644 v_windows/v/vlib/v/util/diff.v create mode 100644 v_windows/v/vlib/v/util/diff/diff.v create mode 100644 v_windows/v/vlib/v/util/errors.v create mode 100644 v_windows/v/vlib/v/util/module.v create mode 100644 v_windows/v/vlib/v/util/quote.v create mode 100644 v_windows/v/vlib/v/util/recompilation/recompilation.v create mode 100644 v_windows/v/vlib/v/util/scanning.v create mode 100644 v_windows/v/vlib/v/util/suggestions.v create mode 100644 v_windows/v/vlib/v/util/timers.v create mode 100644 v_windows/v/vlib/v/util/util.v create mode 100644 v_windows/v/vlib/v/util/version/version.v create mode 100644 v_windows/v/vlib/v/util/vtest/vtest.v create mode 100644 v_windows/v/vlib/v/vcache/vcache.v create mode 100644 v_windows/v/vlib/v/vcache/vcache_test.v create mode 100644 v_windows/v/vlib/v/vet/vet.v create mode 100644 v_windows/v/vlib/v/vmod/parser.v create mode 100644 v_windows/v/vlib/v/vmod/vmod.v create mode 100644 v_windows/v/vlib/vweb/README.md create mode 100644 v_windows/v/vlib/vweb/assets/assets.v create mode 100644 v_windows/v/vlib/vweb/assets/assets_test.v create mode 100644 v_windows/v/vlib/vweb/request.v create mode 100644 v_windows/v/vlib/vweb/request_test.v create mode 100644 v_windows/v/vlib/vweb/route_test.v create mode 100644 v_windows/v/vlib/vweb/sse/sse.v create mode 100644 v_windows/v/vlib/vweb/tests/vweb_test.v create mode 100644 v_windows/v/vlib/vweb/tests/vweb_test_server.v create mode 100644 v_windows/v/vlib/vweb/vweb.v create mode 100644 v_windows/v/vlib/vweb/vweb_app_test.v create mode 100644 v_windows/v/vlib/x/json2/README.md create mode 100644 v_windows/v/vlib/x/json2/any_test.v create mode 100644 v_windows/v/vlib/x/json2/decoder.v create mode 100644 v_windows/v/vlib/x/json2/decoder_test.v create mode 100644 v_windows/v/vlib/x/json2/encoder.v create mode 100644 v_windows/v/vlib/x/json2/encoder_test.v create mode 100644 v_windows/v/vlib/x/json2/json2.v create mode 100644 v_windows/v/vlib/x/json2/json2_test.v create mode 100644 v_windows/v/vlib/x/json2/scanner.v create mode 100644 v_windows/v/vlib/x/json2/scanner_test.v create mode 100644 v_windows/v/vlib/x/ttf/README.md create mode 100644 v_windows/v/vlib/x/ttf/common.v create mode 100644 v_windows/v/vlib/x/ttf/render_bmp.v create mode 100644 v_windows/v/vlib/x/ttf/render_sokol_cpu.v create mode 100644 v_windows/v/vlib/x/ttf/text_block.v create mode 100644 v_windows/v/vlib/x/ttf/ttf.v create mode 100644 v_windows/v/vlib/x/ttf/ttf_test.v create mode 100644 v_windows/v/vlib/x/ttf/ttf_test_data.bin (limited to 'v_windows/v/vlib') diff --git a/v_windows/v/vlib/.vdocignore b/v_windows/v/vlib/.vdocignore new file mode 100644 index 0000000..b7f2232 --- /dev/null +++ b/v_windows/v/vlib/.vdocignore @@ -0,0 +1,16 @@ +builtin/bare +builtin/js +builtin/linux_bare +dl/example +oldgg/ +os/bare +os2/ +net/websocket/examples +uiold/ +v/tests/ +v/checker/tests/ +v/gen/js/tests/ +v/gen/x64/tests/ +v/gen/tests/ +v/preludes_js/ +vweb/tests diff --git a/v_windows/v/vlib/arrays/arrays.v b/v_windows/v/vlib/arrays/arrays.v new file mode 100644 index 0000000..26636d1 --- /dev/null +++ b/v_windows/v/vlib/arrays/arrays.v @@ -0,0 +1,126 @@ +module arrays + +// Common arrays functions: +// - min / max - return the value of the minumum / maximum +// - idx_min / idx_max - return the index of the first minumum / maximum +// - merge - combine two sorted arrays and maintain sorted order + +// min returns the minimum +pub fn min(a []T) T { + if a.len == 0 { + panic('.min called on an empty array') + } + mut val := a[0] + for e in a { + if e < val { + val = e + } + } + return val +} + +// max returns the maximum +pub fn max(a []T) T { + if a.len == 0 { + panic('.max called on an empty array') + } + mut val := a[0] + for e in a { + if e > val { + val = e + } + } + return val +} + +// idx_min returns the index of the first minimum +pub fn idx_min(a []T) int { + if a.len == 0 { + panic('.idx_min called on an empty array') + } + mut idx := 0 + mut val := a[0] + for i, e in a { + if e < val { + val = e + idx = i + } + } + return idx +} + +// idx_max returns the index of the first maximum +pub fn idx_max(a []T) int { + if a.len == 0 { + panic('.idx_max called on an empty array') + } + mut idx := 0 + mut val := a[0] + for i, e in a { + if e > val { + val = e + idx = i + } + } + return idx +} + +// merge two sorted arrays (ascending) and maintain sorted order +[direct_array_access] +pub fn merge(a []T, b []T) []T { + mut m := []T{len: a.len + b.len} + mut ia := 0 + mut ib := 0 + mut j := 0 + // TODO efficient approach to merge_desc where: a[ia] >= b[ib] + for ia < a.len && ib < b.len { + if a[ia] <= b[ib] { + m[j] = a[ia] + ia++ + } else { + m[j] = b[ib] + ib++ + } + j++ + } + // a leftovers + for ia < a.len { + m[j] = a[ia] + ia++ + j++ + } + // b leftovers + for ib < b.len { + m[j] = b[ib] + ib++ + j++ + } + return m +} + +// group n arrays into a single array of arrays with n elements +pub fn group(lists ...[]T) [][]T { + mut length := if lists.len > 0 { lists[0].len } else { 0 } + // calculate length of output by finding shortest input array + for ndx in 1 .. lists.len { + if lists[ndx].len < length { + length = lists[ndx].len + } + } + + if length > 0 { + mut arr := [][]T{cap: length} + // append all combined arrays into the resultant array + for ndx in 0 .. length { + mut zipped := []T{cap: lists.len} + // combine each list item for the ndx position into one array + for list_ndx in 0 .. lists.len { + zipped << lists[list_ndx][ndx] + } + arr << zipped + } + return arr + } + + return [][]T{} +} diff --git a/v_windows/v/vlib/arrays/arrays_test.v b/v_windows/v/vlib/arrays/arrays_test.v new file mode 100644 index 0000000..54042b1 --- /dev/null +++ b/v_windows/v/vlib/arrays/arrays_test.v @@ -0,0 +1,87 @@ +module arrays + +fn test_min() { + a := [8, 2, 6, 4] + assert min(a) == 2 + assert min(a[2..]) == 4 + b := [f32(5.1), 3.1, 1.1, 9.1] + assert min(b) == f32(1.1) + assert min(b[..2]) == f32(3.1) + c := [byte(4), 9, 3, 1] + assert min(c) == byte(1) + assert min(c[..3]) == byte(3) +} + +fn test_max() { + a := [8, 2, 6, 4] + assert max(a) == 8 + assert max(a[1..]) == 6 + b := [f32(5.1), 3.1, 1.1, 9.1] + assert max(b) == f32(9.1) + assert max(b[..3]) == f32(5.1) + c := [byte(4), 9, 3, 1] + assert max(c) == byte(9) + assert max(c[2..]) == byte(3) +} + +fn test_idx_min() { + a := [8, 2, 6, 4] + assert idx_min(a) == 1 + b := [f32(5.1), 3.1, 1.1, 9.1] + assert idx_min(b) == 2 + c := [byte(4), 9, 3, 1] + assert idx_min(c) == 3 +} + +fn test_idx_max() { + a := [8, 2, 6, 4] + assert idx_max(a) == 0 + b := [f32(5.1), 3.1, 1.1, 9.1] + assert idx_max(b) == 3 + c := [byte(4), 9, 3, 1] + assert idx_max(c) == 1 +} + +fn test_merge() { + a := [1, 3, 5, 5, 7] + b := [2, 4, 4, 5, 6, 8] + c := []int{} + d := []int{} + assert merge(a, b) == [1, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8] + assert merge(c, d) == [] + assert merge(a, c) == a + assert merge(d, b) == b +} + +fn test_fixed_array_assignment() { + mut a := [2]int{} + a[0] = 111 + a[1] = 222 + b := a + assert b[0] == a[0] + assert b[1] == a[1] + mut c := [2]int{} + c = a + assert c[0] == a[0] + assert c[1] == a[1] + d := [3]int{init: 333} + for val in d { + assert val == 333 + } + e := [3]string{init: 'vlang'} + for val in e { + assert val == 'vlang' + } +} + +fn test_group() { + x := [4, 5, 6] + y := [2, 1, 3] + + z := group(x, y) + assert z == [[4, 2], [5, 1], [6, 3]] + x2 := [8, 9] + z2 := group(x2, y) + assert z2 == [[8, 2], [9, 1]] + assert group(x, []int{}) == [][]int{} +} diff --git a/v_windows/v/vlib/benchmark/README.md b/v_windows/v/vlib/benchmark/README.md new file mode 100644 index 0000000..6f4f7db --- /dev/null +++ b/v_windows/v/vlib/benchmark/README.md @@ -0,0 +1,45 @@ +Example usage of this module: +```v +import benchmark + +mut bmark := benchmark.new_benchmark() +// by default the benchmark will be verbose, i.e. it will include timing information +// if you want it to be silent, set bmark.verbose = false +for { + bmark.step() // call this when you want to advance the benchmark. + // The timing info in bmark.step_message will be measured starting from the last call to bmark.step + // .... + // bmark.fail() // call this if the step failed + // bmark.step_message(('failed') + bmark.ok() // call this when the step succeeded + println(bmark.step_message('ok')) +} +bmark.stop() +// call when you want to finalize the benchmark +println(bmark.total_message('remarks about the benchmark')) +``` + +benchmark.start() and b.measure() are convenience methods, +intended to be used in combination. Their goal is to make +benchmarking of small snippets of code as *short*, easy to +write, and then to read and analyze the results, as possible. + +Example: +```v +import time +import benchmark + +mut b := benchmark.start() +// your code section 1 ... +time.sleep(1500 * time.millisecond) +b.measure('code_1') +// your code section 2 ... +time.sleep(500 * time.millisecond) +b.measure('code_2') +``` + +... which will produce on stdout something like this: +```text +SPENT 1500.063 ms in code_1 +SPENT 500.061 ms in code_2 +``` diff --git a/v_windows/v/vlib/benchmark/benchmark.v b/v_windows/v/vlib/benchmark/benchmark.v new file mode 100644 index 0000000..cd9f994 --- /dev/null +++ b/v_windows/v/vlib/benchmark/benchmark.v @@ -0,0 +1,218 @@ +module benchmark + +import time +import term + +pub const ( + b_ok = term.ok_message('OK ') + b_fail = term.fail_message('FAIL') + b_skip = term.warn_message('SKIP') + b_spent = term.ok_message('SPENT') +) + +pub struct Benchmark { +pub mut: + bench_timer time.StopWatch + verbose bool + no_cstep bool + step_timer time.StopWatch + ntotal int + nok int + nfail int + nskip int + nexpected_steps int + cstep int + bok string + bfail string +} + +// new_benchmark returns a `Benchmark` instance on the stack. +pub fn new_benchmark() Benchmark { + return Benchmark{ + bench_timer: time.new_stopwatch() + verbose: true + } +} + +// new_benchmark_no_cstep returns a new `Benchmark` instance with step counting disabled. +pub fn new_benchmark_no_cstep() Benchmark { + return Benchmark{ + bench_timer: time.new_stopwatch() + verbose: true + no_cstep: true + } +} + +// new_benchmark_pointer returns a new `Benchmark` instance allocated on the heap. +// This is useful for long-lived use of `Benchmark` instances. +pub fn new_benchmark_pointer() &Benchmark { + return &Benchmark{ + bench_timer: time.new_stopwatch() + verbose: true + } +} + +// set_total_expected_steps sets the the total amount of steps the benchmark is expected to take. +pub fn (mut b Benchmark) set_total_expected_steps(n int) { + b.nexpected_steps = n +} + +// stop stops the internal benchmark timer. +pub fn (mut b Benchmark) stop() { + b.bench_timer.stop() +} + +// step increases the step count by 1 and restarts the internal timer. +pub fn (mut b Benchmark) step() { + b.step_timer.restart() + if !b.no_cstep { + b.cstep++ + } +} + +// fail increases the fail count by 1 and stops the internal timer. +pub fn (mut b Benchmark) fail() { + b.step_timer.stop() + b.ntotal++ + b.nfail++ +} + +// ok increases the ok count by 1 and stops the internal timer. +pub fn (mut b Benchmark) ok() { + b.step_timer.stop() + b.ntotal++ + b.nok++ +} + +// skip increases the skip count by 1 and stops the internal timer. +pub fn (mut b Benchmark) skip() { + b.step_timer.stop() + b.ntotal++ + b.nskip++ +} + +// fail_many increases the fail count by `n` and stops the internal timer. +pub fn (mut b Benchmark) fail_many(n int) { + b.step_timer.stop() + b.ntotal += n + b.nfail += n +} + +// ok_many increases the ok count by `n` and stops the internal timer. +pub fn (mut b Benchmark) ok_many(n int) { + b.step_timer.stop() + b.ntotal += n + b.nok += n +} + +// neither_fail_nor_ok stops the internal timer. +pub fn (mut b Benchmark) neither_fail_nor_ok() { + b.step_timer.stop() +} + +// start returns a new, running, instance of `Benchmark`. +// This is a shorthand for calling `new_benchmark().step()`. +pub fn start() Benchmark { + mut b := new_benchmark() + b.step() + return b +} + +// measure prints the current time spent doing `label`, since the benchmark was started. +pub fn (mut b Benchmark) measure(label string) i64 { + b.ok() + res := b.step_timer.elapsed().microseconds() + println(b.step_message_with_label(benchmark.b_spent, 'in $label')) + b.step() + return res +} + +// step_message_with_label_and_duration returns a string describing the current step. +pub fn (b &Benchmark) step_message_with_label_and_duration(label string, msg string, sduration time.Duration) string { + timed_line := b.tdiff_in_ms(msg, sduration.microseconds()) + if b.nexpected_steps > 1 { + mut sprogress := '' + if b.nexpected_steps < 10 { + sprogress = if b.no_cstep { + 'TMP1/${b.nexpected_steps:1d}' + } else { + '${b.cstep:1d}/${b.nexpected_steps:1d}' + } + } else if b.nexpected_steps >= 10 && b.nexpected_steps < 100 { + sprogress = if b.no_cstep { + 'TMP2/${b.nexpected_steps:2d}' + } else { + '${b.cstep:2d}/${b.nexpected_steps:2d}' + } + } else if b.nexpected_steps >= 100 && b.nexpected_steps < 1000 { + sprogress = if b.no_cstep { + 'TMP3/${b.nexpected_steps:3d}' + } else { + '${b.cstep:3d}/${b.nexpected_steps:3d}' + } + } else { + sprogress = if b.no_cstep { + 'TMP4/${b.nexpected_steps:4d}' + } else { + '${b.cstep:4d}/${b.nexpected_steps:4d}' + } + } + return '${label:-5s} [$sprogress] $timed_line' + } + return '${label:-5s}$timed_line' +} + +// step_message_with_label returns a string describing the current step using current time as duration. +pub fn (b &Benchmark) step_message_with_label(label string, msg string) string { + return b.step_message_with_label_and_duration(label, msg, b.step_timer.elapsed()) +} + +// step_message returns a string describing the current step. +pub fn (b &Benchmark) step_message(msg string) string { + return b.step_message_with_label('', msg) +} + +// step_message_ok returns a string describing the current step with an standard "OK" label. +pub fn (b &Benchmark) step_message_ok(msg string) string { + return b.step_message_with_label(benchmark.b_ok, msg) +} + +// step_message_fail returns a string describing the current step with an standard "FAIL" label. +pub fn (b &Benchmark) step_message_fail(msg string) string { + return b.step_message_with_label(benchmark.b_fail, msg) +} + +// step_message_skip returns a string describing the current step with an standard "SKIP" label. +pub fn (b &Benchmark) step_message_skip(msg string) string { + return b.step_message_with_label(benchmark.b_skip, msg) +} + +// total_message returns a string with total summary of the benchmark run. +pub fn (b &Benchmark) total_message(msg string) string { + the_label := term.colorize(term.gray, msg) + mut tmsg := '${term.colorize(term.bold, 'Summary for $the_label:')} ' + if b.nfail > 0 { + tmsg += term.colorize(term.bold, term.colorize(term.red, '$b.nfail failed')) + ', ' + } + if b.nok > 0 { + tmsg += term.colorize(term.bold, term.colorize(term.green, '$b.nok passed')) + ', ' + } + if b.nskip > 0 { + tmsg += term.colorize(term.bold, term.colorize(term.yellow, '$b.nskip skipped')) + ', ' + } + tmsg += '$b.ntotal total. ${term.colorize(term.bold, 'Runtime:')} ${b.bench_timer.elapsed().microseconds() / 1000} ms.\n' + return tmsg +} + +// total_duration returns the duration in ms. +pub fn (b &Benchmark) total_duration() i64 { + return b.bench_timer.elapsed().milliseconds() +} + +// tdiff_in_ms prefixes `s` with a time difference calculation. +fn (b &Benchmark) tdiff_in_ms(s string, tdiff i64) string { + if b.verbose { + return '${f64(tdiff) / 1000.0:9.3f} ms $s' + } + return s +} diff --git a/v_windows/v/vlib/bitfield/README.md b/v_windows/v/vlib/bitfield/README.md new file mode 100644 index 0000000..8b82c4c --- /dev/null +++ b/v_windows/v/vlib/bitfield/README.md @@ -0,0 +1,11 @@ +# Quickstart + +`bitfield` is a module for +manipulating arrays of bits, i.e. series of zeroes and ones spread across an +array of storage units (unsigned 32-bit integers). + +## BitField structure + +Bit arrays are stored in data structures called 'BitField'. The structure is +'opaque', i.e. its internals are not available to the end user. This module +provides API (functions and methods) for accessing and modifying bit arrays. diff --git a/v_windows/v/vlib/bitfield/bitfield.v b/v_windows/v/vlib/bitfield/bitfield.v new file mode 100644 index 0000000..35ac552 --- /dev/null +++ b/v_windows/v/vlib/bitfield/bitfield.v @@ -0,0 +1,553 @@ +module bitfield + +/* +bitfield is a module for +manipulating arrays of bits, i.e. series of zeroes and ones spread across an +array of storage units (unsigned 32-bit integers). + +BitField structure +------------------ + +Bit arrays are stored in data structures called 'BitField'. The structure is +'opaque', i.e. its internals are not available to the end user. This module +provides API (functions and methods) for accessing and modifying bit arrays. +*/ +pub struct BitField { +mut: + size int + // field *u32 + field []u32 +} + +// helper functions +const ( + slot_size = 32 +) + +// from_bytes converts a byte array into a bitfield. +// [0x0F, 0x01] => 0000 1111 0000 0001 +pub fn from_bytes(input []byte) BitField { + mut output := new(input.len * 8) + for i, b in input { + mut ob := byte(0) + if b & 0b10000000 > 0 { + ob |= 0b00000001 + } + if b & 0b01000000 > 0 { + ob |= 0b00000010 + } + if b & 0b00100000 > 0 { + ob |= 0b00000100 + } + if b & 0b00010000 > 0 { + ob |= 0b00001000 + } + if b & 0b00001000 > 0 { + ob |= 0b00010000 + } + if b & 0b00000100 > 0 { + ob |= 0b00100000 + } + if b & 0b00000010 > 0 { + ob |= 0b01000000 + } + if b & 0b00000001 > 0 { + ob |= 0b10000000 + } + output.field[i / 4] |= u32(ob) << ((i % 4) * 8) + } + return output +} + +// from_bytes_lowest_bits_first converts a byte array into a bitfield +// [0x0F, 0x01] => 1111 0000 1000 0000 +pub fn from_bytes_lowest_bits_first(input []byte) BitField { + mut output := new(input.len * 8) + for i, b in input { + output.field[i / 4] |= u32(b) << ((i % 4) * 8) + } + return output +} + +// from_str converts a string of characters ('0' and '1') to a bit +// array. Any character different from '0' is treated as '1'. +pub fn from_str(input string) BitField { + mut output := new(input.len) + for i in 0 .. input.len { + if input[i] != `0` { + output.set_bit(i) + } + } + return output +} + +// str converts the bit array to a string of characters ('0' and '1') and +// return the string +pub fn (input BitField) str() string { + mut output := '' + for i in 0 .. input.size { + if input.get_bit(i) == 1 { + output = output + '1' + } else { + output = output + '0' + } + } + return output +} + +// new creates an empty bit array of capable of storing 'size' bits. +pub fn new(size int) BitField { + output := BitField{ + size: size + // field: *u32(calloc(zbitnslots(size) * slot_size / 8)) + field: []u32{len: zbitnslots(size)} + } + return output +} + +// frees the memory allocated for the bitfield instance +[unsafe] +pub fn (instance &BitField) free() { + unsafe { + instance.field.free() + } +} + +// get_bit returns the value (0 or 1) of bit number 'bit_nr' (count from 0). +pub fn (instance BitField) get_bit(bitnr int) int { + if bitnr >= instance.size { + return 0 + } + return int((instance.field[bitslot(bitnr)] >> (bitnr % bitfield.slot_size)) & u32(1)) +} + +// set_bit sets bit number 'bit_nr' to 1 (count from 0). +pub fn (mut instance BitField) set_bit(bitnr int) { + if bitnr >= instance.size { + return + } + instance.field[bitslot(bitnr)] |= bitmask(bitnr) +} + +// clear_bit clears (sets to zero) bit number 'bit_nr' (count from 0). +pub fn (mut instance BitField) clear_bit(bitnr int) { + if bitnr >= instance.size { + return + } + instance.field[bitslot(bitnr)] &= ~bitmask(bitnr) +} + +// extract returns the value converted from a slice of bit numbers +// from 'start' by the length of 'len'. +// 0101 (1, 2) => 0b10 +pub fn (instance BitField) extract(start int, len int) u64 { + // panic? + if start < 0 { + return 0 + } + mut output := u64(0) + for i in 0 .. len { + output |= u64(instance.get_bit(start + len - i - 1)) << i + } + return output +} + +// insert sets bit numbers from 'start' to 'len' length with +// the value converted from the number 'value'. +// 0000 (1, 2, 0b10) => 0100 +pub fn (mut instance BitField) insert(start int, len int, _value T) { + // panic? + if start < 0 { + return + } + mut value := _value + for i in 0 .. len { + pos := start + len - i - 1 + if value & 1 == 1 { + instance.set_bit(pos) + } else { + instance.clear_bit(pos) + } + value >>= 1 + } +} + +// extract returns the value converted from a slice of bit numbers +// from 'start' by the length of 'len'. +// 0101 (1, 2) => 0b01 +pub fn (instance BitField) extract_lowest_bits_first(start int, len int) u64 { + // panic? + if start < 0 { + return 0 + } + mut output := u64(0) + for i in 0 .. len { + output |= u64(instance.get_bit(start + i)) << i + } + return output +} + +// insert sets bit numbers from 'start' to 'len' length with +// the value converted from the number 'value'. +// 0000 (1, 2, 0b10) => 0010 +pub fn (mut instance BitField) insert_lowest_bits_first(start int, len int, _value T) { + // panic? + if start < 0 { + return + } + mut value := _value + for pos in start .. start + len { + if value & 1 == 1 { + instance.set_bit(pos) + } else { + instance.clear_bit(pos) + } + value >>= 1 + } +} + +// set_all sets all bits in the array to 1. +pub fn (mut instance BitField) set_all() { + for i in 0 .. zbitnslots(instance.size) { + instance.field[i] = u32(-1) + } + instance.clear_tail() +} + +// clear_all clears (sets to zero) all bits in the array. +pub fn (mut instance BitField) clear_all() { + for i in 0 .. zbitnslots(instance.size) { + instance.field[i] = u32(0) + } +} + +// toggle_bit changes the value (from 0 to 1 or from 1 to 0) of bit +// number 'bit_nr'. +pub fn (mut instance BitField) toggle_bit(bitnr int) { + if bitnr >= instance.size { + return + } + instance.field[bitslot(bitnr)] ^= bitmask(bitnr) +} + +// bf_and performs logical AND operation on every pair of bits from 'input1' and +// 'input2' and returns the result as a new array. If inputs differ in size, +// the tail of the longer one is ignored. +pub fn bf_and(input1 BitField, input2 BitField) BitField { + size := min(input1.size, input2.size) + bitnslots := zbitnslots(size) + mut output := new(size) + for i in 0 .. bitnslots { + output.field[i] = input1.field[i] & input2.field[i] + } + output.clear_tail() + return output +} + +// bf_not toggles all bits in a bit array and returns the result as a new array. +pub fn bf_not(input BitField) BitField { + size := input.size + bitnslots := zbitnslots(size) + mut output := new(size) + for i in 0 .. bitnslots { + output.field[i] = ~input.field[i] + } + output.clear_tail() + return output +} + +// bf_or performs logical OR operation on every pair of bits from 'input1' and +// 'input2' and returns the result as a new array. If inputs differ in size, +// the tail of the longer one is ignored. +pub fn bf_or(input1 BitField, input2 BitField) BitField { + size := min(input1.size, input2.size) + bitnslots := zbitnslots(size) + mut output := new(size) + for i in 0 .. bitnslots { + output.field[i] = input1.field[i] | input2.field[i] + } + output.clear_tail() + return output +} + +// bf_xor perform logical XOR operation on every pair of bits from 'input1' and +// 'input2' and returns the result as a new array. If inputs differ in size, +// the tail of the longer one is ignored. +pub fn bf_xor(input1 BitField, input2 BitField) BitField { + size := min(input1.size, input2.size) + bitnslots := zbitnslots(size) + mut output := new(size) + for i in 0 .. bitnslots { + output.field[i] = input1.field[i] ^ input2.field[i] + } + output.clear_tail() + return output +} + +// join concatenates two bit arrays and return the result as a new array. +pub fn join(input1 BitField, input2 BitField) BitField { + output_size := input1.size + input2.size + mut output := new(output_size) + // copy the first input to output as is + for i in 0 .. zbitnslots(input1.size) { + output.field[i] = input1.field[i] + } + // find offset bit and offset slot + offset_bit := input1.size % bitfield.slot_size + offset_slot := input1.size / bitfield.slot_size + for i in 0 .. zbitnslots(input2.size) { + output.field[i + offset_slot] |= u32(input2.field[i] << u32(offset_bit)) + } + /* + * If offset_bit is not zero, additional operations are needed. + * Number of iterations depends on the nr of slots in output. Two + * options: + * (a) nr of slots in output is the sum of inputs' slots. In this + * case, the nr of bits in the last slot of output is less than the + * nr of bits in the second input (i.e. ), OR + * (b) nr of slots of output is the sum of inputs' slots less one + * (i.e. less iterations needed). In this case, the nr of bits in + * the last slot of output is greater than the nr of bits in the second + * input. + * If offset_bit is zero, no additional copies needed. + */ + if (output_size - 1) % bitfield.slot_size < (input2.size - 1) % bitfield.slot_size { + for i in 0 .. zbitnslots(input2.size) { + output.field[i + offset_slot + 1] |= u32(input2.field[i] >> u32(bitfield.slot_size - offset_bit)) + } + } else if (output_size - 1) % bitfield.slot_size > (input2.size - 1) % bitfield.slot_size { + for i in 0 .. zbitnslots(input2.size) - 1 { + output.field[i + offset_slot + 1] |= u32(input2.field[i] >> u32(bitfield.slot_size - offset_bit)) + } + } + return output +} + +// get_size returns the number of bits the array can hold. +pub fn (instance BitField) get_size() int { + return instance.size +} + +// clone creates a copy of a bit array. +pub fn (instance BitField) clone() BitField { + bitnslots := zbitnslots(instance.size) + mut output := new(instance.size) + for i in 0 .. bitnslots { + output.field[i] = instance.field[i] + } + return output +} + +pub fn (a BitField) == (b BitField) bool { + if a.size != b.size { + return false + } + for i in 0 .. zbitnslots(a.size) { + if a.field[i] != b.field[i] { + return false + } + } + return true +} + +// pop_count returns the number of set bits (ones) in the array. +pub fn (instance BitField) pop_count() int { + size := instance.size + bitnslots := zbitnslots(size) + tail := size % bitfield.slot_size + mut count := 0 + for i in 0 .. bitnslots - 1 { + for j in 0 .. bitfield.slot_size { + if u32(instance.field[i] >> u32(j)) & u32(1) == u32(1) { + count++ + } + } + } + for j in 0 .. tail { + if u32(instance.field[bitnslots - 1] >> u32(j)) & u32(1) == u32(1) { + count++ + } + } + return count +} + +// hamming computes the Hamming distance between two bit arrays. +pub fn hamming(input1 BitField, input2 BitField) int { + input_xored := bf_xor(input1, input2) + return input_xored.pop_count() +} + +// pos checks if the array contains a sub-array 'needle' and returns its +// position if it does, -1 if it does not, and -2 on error. +pub fn (haystack BitField) pos(needle BitField) int { + heystack_size := haystack.size + needle_size := needle.size + diff := heystack_size - needle_size + // needle longer than haystack; return error code -2 + if diff < 0 { + return -2 + } + for i := 0; i <= diff; i++ { + needle_candidate := haystack.slice(i, needle_size + i) + if needle_candidate == needle { + // needle matches a sub-array of haystack; return starting position of the sub-array + return i + } + } + // nothing matched; return -1 + return -1 +} + +// slice returns a sub-array of bits between 'start_bit_nr' (included) and +// 'end_bit_nr' (excluded). +pub fn (input BitField) slice(_start int, _end int) BitField { + // boundary checks + mut start := _start + mut end := _end + if end > input.size { + end = input.size // or panic? + } + if start > end { + start = end // or panic? + } + mut output := new(end - start) + start_offset := start % bitfield.slot_size + end_offset := (end - 1) % bitfield.slot_size + start_slot := start / bitfield.slot_size + end_slot := (end - 1) / bitfield.slot_size + output_slots := zbitnslots(end - start) + if output_slots > 1 { + if start_offset != 0 { + for i in 0 .. output_slots - 1 { + output.field[i] = u32(input.field[start_slot + i] >> u32(start_offset)) + output.field[i] = output.field[i] | u32(input.field[start_slot + i + 1] << u32(bitfield.slot_size - start_offset)) + } + } else { + for i in 0 .. output_slots - 1 { + output.field[i] = u32(input.field[start_slot + i]) + } + } + } + if start_offset > end_offset { + output.field[(end - start - 1) / bitfield.slot_size] = u32(input.field[end_slot - 1] >> u32(start_offset)) + mut mask := u32((1 << (end_offset + 1)) - 1) + mask = input.field[end_slot] & mask + mask = u32(mask << u32(bitfield.slot_size - start_offset)) + output.field[(end - start - 1) / bitfield.slot_size] |= mask + } else if start_offset == 0 { + mut mask := u32(0) + if end_offset == bitfield.slot_size - 1 { + mask = u32(-1) + } else { + mask = u32(u32(1) << u32(end_offset + 1)) + mask = mask - u32(1) + } + output.field[(end - start - 1) / bitfield.slot_size] = (input.field[end_slot] & mask) + } else { + mut mask := u32(((1 << (end_offset - start_offset + 1)) - 1) << start_offset) + mask = input.field[end_slot] & mask + mask = u32(mask >> u32(start_offset)) + output.field[(end - start - 1) / bitfield.slot_size] |= mask + } + return output +} + +// reverse reverses the order of bits in the array (swap the first with the +// last, the second with the last but one and so on). +pub fn (instance BitField) reverse() BitField { + size := instance.size + bitnslots := zbitnslots(size) + mut output := new(size) + for i := 0; i < (bitnslots - 1); i++ { + for j in 0 .. bitfield.slot_size { + if u32(instance.field[i] >> u32(j)) & u32(1) == u32(1) { + output.set_bit(size - i * bitfield.slot_size - j - 1) + } + } + } + bits_in_last_input_slot := (size - 1) % bitfield.slot_size + 1 + for j in 0 .. bits_in_last_input_slot { + if u32(instance.field[bitnslots - 1] >> u32(j)) & u32(1) == u32(1) { + output.set_bit(bits_in_last_input_slot - j - 1) + } + } + return output +} + +// resize changes the size of the bit array to 'new_size'. +pub fn (mut instance BitField) resize(new_size int) { + new_bitnslots := zbitnslots(new_size) + old_size := instance.size + old_bitnslots := zbitnslots(old_size) + mut field := []u32{len: new_bitnslots} + for i := 0; i < old_bitnslots && i < new_bitnslots; i++ { + field[i] = instance.field[i] + } + instance.field = field.clone() + instance.size = new_size + if new_size < old_size && new_size % bitfield.slot_size != 0 { + instance.clear_tail() + } +} + +// rotate circular-shifts the bits by 'offset' positions (move +// 'offset' bit to 0, 'offset+1' bit to 1, and so on). +pub fn (instance BitField) rotate(offset int) BitField { + /* + * + * This function "cuts" the bitfield into two and swaps them. + * If the offset is positive, the cutting point is counted from the + * beginning of the bit array, otherwise from the end. + * + */ + size := instance.size + // removing extra rotations + mut offset_internal := offset % size + if offset_internal == 0 { + // nothing to shift + return instance + } + if offset_internal < 0 { + offset_internal = offset_internal + size + } + first_chunk := instance.slice(0, offset_internal) + second_chunk := instance.slice(offset_internal, size) + output := join(second_chunk, first_chunk) + return output +} + +// Internal functions +// clear_tail clears the extra bits that are not part of the bitfield, but yet are allocated +fn (mut instance BitField) clear_tail() { + tail := instance.size % bitfield.slot_size + if tail != 0 { + // create a mask for the tail + mask := u32((1 << tail) - 1) + // clear the extra bits + instance.field[zbitnslots(instance.size) - 1] = instance.field[zbitnslots(instance.size) - 1] & mask + } +} + +// bitmask is the bitmask needed to access a particular bit at offset bitnr +fn bitmask(bitnr int) u32 { + return u32(u32(1) << u32(bitnr % bitfield.slot_size)) +} + +// bitslot is the slot index (i.e. the integer) where a particular bit is located +fn bitslot(size int) int { + return size / bitfield.slot_size +} + +// min returns the minimum of 2 integers; it is here to avoid importing math just for that +fn min(input1 int, input2 int) int { + if input1 < input2 { + return input1 + } else { + return input2 + } +} + +// zbitnslots returns the minimum number of whole integers, needed to represent a bitfield of size length +fn zbitnslots(length int) int { + return (length - 1) / bitfield.slot_size + 1 +} diff --git a/v_windows/v/vlib/bitfield/bitfield_test.v b/v_windows/v/vlib/bitfield/bitfield_test.v new file mode 100644 index 0000000..ae61d38 --- /dev/null +++ b/v_windows/v/vlib/bitfield/bitfield_test.v @@ -0,0 +1,333 @@ +import bitfield +import rand + +fn test_bf_new_size() { + instance := bitfield.new(75) + assert instance.get_size() == 75 +} + +fn test_bf_set_clear_toggle_get() { + mut instance := bitfield.new(75) + instance.set_bit(47) + assert instance.get_bit(47) == 1 + instance.clear_bit(47) + assert instance.get_bit(47) == 0 + instance.toggle_bit(47) + assert instance.get_bit(47) == 1 +} + +fn test_bf_insert_extract() { + mut instance := bitfield.new(11) + instance.set_all() + instance.insert(2, 9, 3) + assert instance.extract(2, 1) == 0 + assert instance.extract(2, 8) == 1 + assert instance.extract(10, 1) == 1 + instance.set_all() + instance.insert_lowest_bits_first(2, 9, 3) + assert instance.extract_lowest_bits_first(2, 1) == 1 + assert instance.extract_lowest_bits_first(2, 8) == 3 + assert instance.extract_lowest_bits_first(10, 1) == 0 +} + +fn test_bf_and_not_or_xor() { + len := 80 + mut input1 := bitfield.new(len) + mut input2 := bitfield.new(len) + mut i := 0 + for i < len { + if rand.intn(2) == 1 { + input1.set_bit(i) + } + if rand.intn(2) == 1 { + input2.set_bit(i) + } + i++ + } + output1 := bitfield.bf_xor(input1, input2) + bf_and := bitfield.bf_and(input1, input2) + bf_or := bitfield.bf_or(input1, input2) + bf_not := bitfield.bf_not(bf_and) + output2 := bitfield.bf_and(bf_or, bf_not) + mut result := 1 + for i < len { + if output1.get_bit(i) != output2.get_bit(i) { + result = 0 + } + } + assert result == 1 +} + +fn test_clone_cmp() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + } + } + output := input.clone() + assert output.get_size() == len + assert input == output +} + +fn test_slice_join() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + } + } + mut result := 1 + for point := 1; point < (len - 1); point++ { + // divide a bitfield into two subfields + chunk1 := input.slice(0, point) + chunk2 := input.slice(point, input.get_size()) + // concatenate them back into one and compare to the original + output := bitfield.join(chunk1, chunk2) + if input != output { + result = 0 + } + } + assert result == 1 +} + +fn test_pop_count() { + len := 80 + mut count0 := 0 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + count0++ + } + } + count1 := input.pop_count() + assert count0 == count1 +} + +fn test_hamming() { + len := 80 + mut count := 0 + mut input1 := bitfield.new(len) + mut input2 := bitfield.new(len) + for i in 0 .. len { + match rand.intn(4) { + 0, 1 { + input1.set_bit(i) + count++ + } + 2 { + input2.set_bit(i) + count++ + } + 3 { + input1.set_bit(i) + input2.set_bit(i) + } + else {} + } + } + assert count == bitfield.hamming(input1, input2) +} + +fn test_bf_from_bytes() { + input := [byte(0x01), 0xF0, 0x0F, 0xF0, 0xFF] + output := bitfield.from_bytes(input).str() + assert output == '00000001' + '11110000' + '00001111' + '11110000' + '11111111' + newoutput := bitfield.from_str(output).str() + assert newoutput == output +} + +fn test_bf_from_bytes_lowest_bits_first() { + input := [byte(0x01), 0xF0] + output := bitfield.from_bytes_lowest_bits_first(input).str() + assert output == '10000000' + '00001111' + newoutput := bitfield.from_str(output).str() + assert newoutput == output +} + +fn test_bf_from_str() { + len := 80 + mut input := '' + for _ in 0 .. len { + if rand.intn(2) == 1 { + input = input + '1' + } else { + input = input + '0' + } + } + output := bitfield.from_str(input) + mut result := 1 + for i in 0 .. len { + if input[i] != output.get_bit(i) + 48 { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_bf2str() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + } + } + mut check := '' + for i in 0 .. len { + if input.get_bit(i) == 1 { + check = check + '1' + } else { + check = check + '0' + } + } + output := input.str() + mut result := 1 + for i in 0 .. len { + if check[i] != output[i] { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_set_all() { + len := 80 + mut input := bitfield.new(len) + input.set_all() + mut result := 1 + for i in 0 .. len { + if input.get_bit(i) != 1 { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_clear_all() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + } + } + input.clear_all() + mut result := 1 + for i in 0 .. len { + if input.get_bit(i) != 0 { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_reverse() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 1 { + input.set_bit(i) + } + } + check := input.clone() + output := input.reverse() + mut result := 1 + for i in 0 .. len { + if output.get_bit(i) != check.get_bit(len - i - 1) { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_resize() { + len := 80 + mut input := bitfield.new(rand.intn(len) + 1) + for _ in 0 .. 100 { + input.resize(rand.intn(len) + 1) + input.set_bit(input.get_size() - 1) + } + assert input.get_bit(input.get_size() - 1) == 1 +} + +fn test_bf_pos() { + /* + * + * set haystack size to 80 + * test different sizes of needle, from 1 to 80 + * test different positions of needle, from 0 to where it fits + * all haystacks here contain exactly one instanse of needle, + * so search should return non-negative-values + * + */ + len := 80 + mut result := 1 + for i := 1; i < len; i++ { // needle size + for j in 0 .. len - i { // needle position in the haystack + // create the needle + mut needle := bitfield.new(i) + // fill the needle with random values + for k in 0 .. i { + if rand.intn(2) == 1 { + needle.set_bit(k) + } + } + // make sure the needle contains at least one set bit, selected randomly + r := rand.intn(i) + needle.set_bit(r) + // create the haystack, make sure it contains the needle + mut haystack := needle.clone() + // if there is space between the start of the haystack and the sought needle, fill it with zeroes + if j > 0 { + start := bitfield.new(j) + tmp := bitfield.join(start, haystack) + haystack = tmp + } + // if there is space between the sought needle and the end of haystack, fill it with zeroes + if j + i < len { + end := bitfield.new(len - j - i) + tmp2 := bitfield.join(haystack, end) + haystack = tmp2 + } + // now let's test + // the result should be equal to j + if haystack.pos(needle) != j { + result = 0 + } + } + } + assert result == 1 +} + +fn test_bf_rotate() { + mut result := 1 + len := 80 + for i := 1; i < 80 && result == 1; i++ { + mut chunk1 := bitfield.new(i) + chunk2 := bitfield.new(len - i) + chunk1.set_all() + input := bitfield.join(chunk1, chunk2) + output := input.rotate(i) + if output.get_bit(len - i - 1) != 0 || output.get_bit(len - i) != 1 { + result = 0 + } + } + assert result == 1 +} + +fn test_bf_printing() { + len := 80 + mut input := bitfield.new(len) + for i in 0 .. len { + if rand.intn(2) == 0 { + input.set_bit(i) + } + } + // the following should convert the bitfield input into a string automatically + println(input) + assert true +} diff --git a/v_windows/v/vlib/builtin/array.c.v b/v_windows/v/vlib/builtin/array.c.v new file mode 100644 index 0000000..b15fe4b --- /dev/null +++ b/v_windows/v/vlib/builtin/array.c.v @@ -0,0 +1 @@ +module builtin diff --git a/v_windows/v/vlib/builtin/array.v b/v_windows/v/vlib/builtin/array.v new file mode 100644 index 0000000..0a6f4e0 --- /dev/null +++ b/v_windows/v/vlib/builtin/array.v @@ -0,0 +1,644 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +import strings + +// array is a struct used for denoting array types in V +pub struct array { +pub: + element_size int // size in bytes of one element in the array. +pub mut: + data voidptr + offset int // in bytes (should be `size_t`) + len int // length of the array. + cap int // capacity of the array. +} + +// array.data uses a void pointer, which allows implementing arrays without generics and without generating +// extra code for every type. +// Internal function, used by V (`nums := []int`) +fn __new_array(mylen int, cap int, elm_size int) array { + cap_ := if cap < mylen { mylen } else { cap } + arr := array{ + element_size: elm_size + data: vcalloc(cap_ * elm_size) + len: mylen + cap: cap_ + } + return arr +} + +fn __new_array_with_default(mylen int, cap int, elm_size int, val voidptr) array { + cap_ := if cap < mylen { mylen } else { cap } + mut arr := array{ + element_size: elm_size + data: vcalloc(cap_ * elm_size) + len: mylen + cap: cap_ + } + if val != 0 { + for i in 0 .. arr.len { + unsafe { arr.set_unsafe(i, val) } + } + } + return arr +} + +fn __new_array_with_array_default(mylen int, cap int, elm_size int, val array) array { + cap_ := if cap < mylen { mylen } else { cap } + mut arr := array{ + element_size: elm_size + data: vcalloc(cap_ * elm_size) + len: mylen + cap: cap_ + } + for i in 0 .. arr.len { + val_clone := unsafe { val.clone_to_depth(1) } + unsafe { arr.set_unsafe(i, &val_clone) } + } + return arr +} + +// Private function, used by V (`nums := [1, 2, 3]`) +fn new_array_from_c_array(len int, cap int, elm_size int, c_array voidptr) array { + cap_ := if cap < len { len } else { cap } + arr := array{ + element_size: elm_size + data: vcalloc(cap_ * elm_size) + len: len + cap: cap_ + } + // TODO Write all memory functions (like memcpy) in V + unsafe { vmemcpy(arr.data, c_array, len * elm_size) } + return arr +} + +// Private function, used by V (`nums := [1, 2, 3] !`) +fn new_array_from_c_array_no_alloc(len int, cap int, elm_size int, c_array voidptr) array { + arr := array{ + element_size: elm_size + data: c_array + len: len + cap: cap + } + return arr +} + +// Private function. Doubles array capacity if needed. +fn (mut a array) ensure_cap(required int) { + if required <= a.cap { + return + } + mut cap := if a.cap > 0 { a.cap } else { 2 } + for required > cap { + cap *= 2 + } + new_size := cap * a.element_size + new_data := vcalloc(new_size) + if a.data != voidptr(0) { + unsafe { vmemcpy(new_data, a.data, a.len * a.element_size) } + // TODO: the old data may be leaked when no GC is used (ref-counting?) + } + a.data = new_data + a.offset = 0 + a.cap = cap +} + +// repeat returns a new array with the given array elements repeated given times. +// `cgen` will replace this with an apropriate call to `repeat_to_depth()` + +// This is a dummy placeholder that will be overridden by `cgen` with an appropriate +// call to `repeat_to_depth()`. However the `checker` needs it here. +pub fn (a array) repeat(count int) array { + return unsafe { a.repeat_to_depth(count, 0) } +} + +// version of `repeat()` that handles multi dimensional arrays +// `unsafe` to call directly because `depth` is not checked +[unsafe] +pub fn (a array) repeat_to_depth(count int, depth int) array { + if count < 0 { + panic('array.repeat: count is negative: $count') + } + mut size := count * a.len * a.element_size + if size == 0 { + size = a.element_size + } + arr := array{ + element_size: a.element_size + data: vcalloc(size) + len: count * a.len + cap: count * a.len + } + if a.len > 0 { + for i in 0 .. count { + if depth > 0 { + ary_clone := unsafe { a.clone_to_depth(depth) } + unsafe { vmemcpy(arr.get_unsafe(i * a.len), &byte(ary_clone.data), a.len * a.element_size) } + } else { + unsafe { vmemcpy(arr.get_unsafe(i * a.len), &byte(a.data), a.len * a.element_size) } + } + } + } + return arr +} + +// sort_with_compare sorts array in-place using given `compare` function as comparator. +pub fn (mut a array) sort_with_compare(compare voidptr) { + $if freestanding { + panic('sort does not work with -freestanding') + } $else { + unsafe { vqsort(a.data, size_t(a.len), size_t(a.element_size), compare) } + } +} + +// insert inserts a value in the array at index `i` +pub fn (mut a array) insert(i int, val voidptr) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap(a.len + 1) + unsafe { + vmemmove(a.get_unsafe(i + 1), a.get_unsafe(i), (a.len - i) * a.element_size) + a.set_unsafe(i, val) + } + a.len++ +} + +// insert_many inserts many values into the array from index `i`. +[unsafe] +pub fn (mut a array) insert_many(i int, val voidptr, size int) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert_many: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap(a.len + size) + elem_size := a.element_size + unsafe { + iptr := a.get_unsafe(i) + vmemmove(a.get_unsafe(i + size), iptr, (a.len - i) * elem_size) + vmemcpy(iptr, val, size * elem_size) + } + a.len += size +} + +// prepend prepends one value to the array. +pub fn (mut a array) prepend(val voidptr) { + a.insert(0, val) +} + +// prepend_many prepends another array to this array. +[unsafe] +pub fn (mut a array) prepend_many(val voidptr, size int) { + unsafe { a.insert_many(0, val, size) } +} + +// delete deletes array element at index `i`. +pub fn (mut a array) delete(i int) { + a.delete_many(i, 1) +} + +// delete_many deletes `size` elements beginning with index `i` +pub fn (mut a array) delete_many(i int, size int) { + $if !no_bounds_checking ? { + if i < 0 || i + size > a.len { + endidx := if size > 1 { '..${i + size}' } else { '' } + panic('array.delete: index out of range (i == $i$endidx, a.len == $a.len)') + } + } + // NB: if a is [12,34], a.len = 2, a.delete(0) + // should move (2-0-1) elements = 1 element (the 34) forward + old_data := a.data + new_size := a.len - size + new_cap := if new_size == 0 { 1 } else { new_size } + a.data = vcalloc(new_cap * a.element_size) + unsafe { vmemcpy(a.data, old_data, i * a.element_size) } + unsafe { + vmemcpy(&byte(a.data) + i * a.element_size, &byte(old_data) + (i + size) * a.element_size, + (a.len - i - size) * a.element_size) + } + a.len = new_size + a.cap = new_cap +} + +// clear clears the array without deallocating the allocated data. +pub fn (mut a array) clear() { + a.len = 0 +} + +// trim trims the array length to "index" without modifying the allocated data. If "index" is greater +// than len nothing will be changed. +pub fn (mut a array) trim(index int) { + if index < a.len { + a.len = index + } +} + +// we manually inline this for single operations for performance without -prod +[inline; unsafe] +fn (a array) get_unsafe(i int) voidptr { + unsafe { + return &byte(a.data) + i * a.element_size + } +} + +// Private function. Used to implement array[] operator. +fn (a array) get(i int) voidptr { + $if !no_bounds_checking ? { + if i < 0 || i >= a.len { + panic('array.get: index out of range (i == $i, a.len == $a.len)') + } + } + unsafe { + return &byte(a.data) + i * a.element_size + } +} + +// Private function. Used to implement x = a[i] or { ... } +fn (a array) get_with_check(i int) voidptr { + if i < 0 || i >= a.len { + return 0 + } + unsafe { + return &byte(a.data) + i * a.element_size + } +} + +// first returns the first element of the array. +pub fn (a array) first() voidptr { + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.first: array is empty') + } + } + return a.data +} + +// last returns the last element of the array. +pub fn (a array) last() voidptr { + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.last: array is empty') + } + } + unsafe { + return &byte(a.data) + (a.len - 1) * a.element_size + } +} + +// pop returns the last element of the array, and removes it. +pub fn (mut a array) pop() voidptr { + // in a sense, this is the opposite of `a << x` + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.pop: array is empty') + } + } + new_len := a.len - 1 + last_elem := unsafe { &byte(a.data) + new_len * a.element_size } + a.len = new_len + // NB: a.cap is not changed here *on purpose*, so that + // further << ops on that array will be more efficient. + return unsafe { memdup(last_elem, a.element_size) } +} + +// delete_last efficiently deletes the last element of the array. +pub fn (mut a array) delete_last() { + // copy pasting code for performance + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.pop: array is empty') + } + } + a.len-- +} + +// slice returns an array using the same buffer as original array +// but starting from the `start` element and ending with the element before +// the `end` element of the original array with the length and capacity +// set to the number of the elements in the slice. +fn (a array) slice(start int, _end int) array { + mut end := _end + $if !no_bounds_checking ? { + if start > end { + panic('array.slice: invalid slice index ($start > $end)') + } + if end > a.len { + panic('array.slice: slice bounds out of range ($end >= $a.len)') + } + if start < 0 { + panic('array.slice: slice bounds out of range ($start < 0)') + } + } + offset := start * a.element_size + data := unsafe { &byte(a.data) + offset } + l := end - start + res := array{ + element_size: a.element_size + data: data + offset: a.offset + offset + len: l + cap: l + } + return res +} + +// used internally for [2..4] +fn (a array) slice2(start int, _end int, end_max bool) array { + end := if end_max { a.len } else { _end } + return a.slice(start, end) +} + +// `clone_static_to_depth()` returns an independent copy of a given array. +// Unlike `clone_to_depth()` it has a value receiver and is used internally +// for slice-clone expressions like `a[2..4].clone()` and in -autofree generated code. +fn (a array) clone_static_to_depth(depth int) array { + return unsafe { a.clone_to_depth(depth) } +} + +// clone returns an independent copy of a given array. +// this will be overwritten by `cgen` with an apropriate call to `.clone_to_depth()` +// However the `checker` needs it here. +pub fn (a &array) clone() array { + return unsafe { a.clone_to_depth(0) } +} + +// recursively clone given array - `unsafe` when called directly because depth is not checked +[unsafe] +pub fn (a &array) clone_to_depth(depth int) array { + mut size := a.cap * a.element_size + if size == 0 { + size++ + } + mut arr := array{ + element_size: a.element_size + data: vcalloc(size) + len: a.len + cap: a.cap + } + // Recursively clone-generated elements if array element is array type + if depth > 0 && a.element_size == sizeof(array) && a.len >= 0 && a.cap >= a.len { + for i in 0 .. a.len { + ar := array{} + unsafe { vmemcpy(&ar, a.get_unsafe(i), int(sizeof(array))) } + ar_clone := unsafe { ar.clone_to_depth(depth - 1) } + unsafe { arr.set_unsafe(i, &ar_clone) } + } + return arr + } else { + if !isnil(a.data) { + unsafe { vmemcpy(&byte(arr.data), a.data, a.cap * a.element_size) } + } + return arr + } +} + +// we manually inline this for single operations for performance without -prod +[inline; unsafe] +fn (mut a array) set_unsafe(i int, val voidptr) { + unsafe { vmemcpy(&byte(a.data) + a.element_size * i, val, a.element_size) } +} + +// Private function. Used to implement assigment to the array element. +fn (mut a array) set(i int, val voidptr) { + $if !no_bounds_checking ? { + if i < 0 || i >= a.len { + panic('array.set: index out of range (i == $i, a.len == $a.len)') + } + } + unsafe { vmemcpy(&byte(a.data) + a.element_size * i, val, a.element_size) } +} + +fn (mut a array) push(val voidptr) { + a.ensure_cap(a.len + 1) + unsafe { vmemmove(&byte(a.data) + a.element_size * a.len, val, a.element_size) } + a.len++ +} + +// push_many implements the functionality for pushing another array. +// `val` is array.data and user facing usage is `a << [1,2,3]` +[unsafe] +pub fn (mut a3 array) push_many(val voidptr, size int) { + if a3.data == val && !isnil(a3.data) { + // handle `arr << arr` + copy := a3.clone() + a3.ensure_cap(a3.len + size) + unsafe { + // vmemcpy(a.data, copy.data, copy.element_size * copy.len) + vmemcpy(a3.get_unsafe(a3.len), copy.data, a3.element_size * size) + } + } else { + a3.ensure_cap(a3.len + size) + if !isnil(a3.data) && !isnil(val) { + unsafe { vmemcpy(a3.get_unsafe(a3.len), val, a3.element_size * size) } + } + } + a3.len += size +} + +// reverse_in_place reverses existing array data, modifying original array. +pub fn (mut a array) reverse_in_place() { + if a.len < 2 { + return + } + unsafe { + mut tmp_value := malloc(a.element_size) + for i in 0 .. a.len / 2 { + vmemcpy(tmp_value, &byte(a.data) + i * a.element_size, a.element_size) + vmemcpy(&byte(a.data) + i * a.element_size, &byte(a.data) + + (a.len - 1 - i) * a.element_size, a.element_size) + vmemcpy(&byte(a.data) + (a.len - 1 - i) * a.element_size, tmp_value, a.element_size) + } + free(tmp_value) + } +} + +// reverse returns a new array with the elements of the original array in reverse order. +pub fn (a array) reverse() array { + if a.len < 2 { + return a + } + mut arr := array{ + element_size: a.element_size + data: vcalloc(a.cap * a.element_size) + len: a.len + cap: a.cap + } + for i in 0 .. a.len { + unsafe { arr.set_unsafe(i, a.get_unsafe(a.len - 1 - i)) } + } + return arr +} + +// pub fn (a []int) free() { +// free frees all memory occupied by the array. +[unsafe] +pub fn (a &array) free() { + $if prealloc { + return + } + // if a.is_slice { + // return + // } + unsafe { free(&byte(a.data) - a.offset) } +} + +// filter creates a new array with all elements that pass the test implemented by the provided function +pub fn (a array) filter(predicate fn (voidptr) bool) array + +struct ZZZTmp1 {} + +// any tests whether at least one element in the array passes the test implemented by the +// provided function. It returns true if, in the array, it finds an element for which the provided +// function returns true; otherwise it returns false. It doesn't modify the array +pub fn (a array) any(predicate fn (voidptr) bool) bool + +struct ZZZTmp2 {} + +// all tests whether all elements in the array pass the test implemented by the provided function +pub fn (a array) all(predicate fn (voidptr) bool) bool + +struct ZZZTmp3 {} + +// map creates a new array populated with the results of calling a provided function +// on every element in the calling array +pub fn (a array) map(callback fn (voidptr) voidptr) array + +struct ZZZTmp4 {} + +// sort sorts an array in place in ascending order. +pub fn (mut a array) sort(callback fn (voidptr, voidptr) int) + +struct ZZZTmp5 {} + +// contains determines whether an array includes a certain value among its entries +pub fn (a array) contains(val voidptr) bool + +struct ZZZTmp6 {} + +// index returns the first index at which a given element can be found in the array +// or -1 if the value is not found. +pub fn (a array) index(value voidptr) int + +[unsafe] +pub fn (mut a []string) free() { + $if prealloc { + return + } + for s in a { + unsafe { s.free() } + } + unsafe { free(a.data) } +} + +// str returns a string representation of the array of strings +// => '["a", "b", "c"]'. +[manualfree] +pub fn (a []string) str() string { + mut sb := strings.new_builder(a.len * 3) + sb.write_string('[') + for i in 0 .. a.len { + val := a[i] + sb.write_string("'") + sb.write_string(val) + sb.write_string("'") + if i < a.len - 1 { + sb.write_string(', ') + } + } + sb.write_string(']') + res := sb.str() + unsafe { sb.free() } + return res +} + +// hex returns a string with the hexadecimal representation +// of the byte elements of the array. +pub fn (b []byte) hex() string { + mut hex := unsafe { malloc(b.len * 2 + 1) } + mut dst_i := 0 + for i in b { + n0 := i >> 4 + unsafe { + hex[dst_i] = if n0 < 10 { n0 + `0` } else { n0 + byte(87) } + dst_i++ + } + n1 := i & 0xF + unsafe { + hex[dst_i] = if n1 < 10 { n1 + `0` } else { n1 + byte(87) } + dst_i++ + } + } + unsafe { + hex[dst_i] = 0 + return tos(hex, dst_i) + } +} + +// copy copies the `src` byte array elements to the `dst` byte array. +// The number of the elements copied is the minimum of the length of both arrays. +// Returns the number of elements copied. +// TODO: implement for all types +pub fn copy(dst []byte, src []byte) int { + min := if dst.len < src.len { dst.len } else { src.len } + if min > 0 { + unsafe { vmemcpy(&byte(dst.data), src.data, min) } + } + return min +} + +// reduce executes a given reducer function on each element of the array, +// resulting in a single output value. +pub fn (a []int) reduce(iter fn (int, int) int, accum_start int) int { + mut accum_ := accum_start + for i in a { + accum_ = iter(accum_, i) + } + return accum_ +} + +// grow_cap grows the array's capacity by `amount` elements. +pub fn (mut a array) grow_cap(amount int) { + a.ensure_cap(a.cap + amount) +} + +// grow_len ensures that an array has a.len + amount of length +[unsafe] +pub fn (mut a array) grow_len(amount int) { + a.ensure_cap(a.len + amount) + a.len += amount +} + +// pointers returns a new array, where each element +// is the address of the corresponding element in the array. +[unsafe] +pub fn (a array) pointers() []voidptr { + mut res := []voidptr{} + for i in 0 .. a.len { + unsafe { res << a.get_unsafe(i) } + } + return res +} + +// voidptr.vbytes() - makes a V []byte structure from a C style memory buffer. NB: the data is reused, NOT copied! +[unsafe] +pub fn (data voidptr) vbytes(len int) []byte { + res := array{ + element_size: 1 + data: data + len: len + cap: len + } + return res +} + +// byteptr.vbytes() - makes a V []byte structure from a C style memory buffer. NB: the data is reused, NOT copied! +[unsafe] +pub fn (data &byte) vbytes(len int) []byte { + return unsafe { voidptr(data).vbytes(len) } +} diff --git a/v_windows/v/vlib/builtin/array_d_gcboehm_opt.v b/v_windows/v/vlib/builtin/array_d_gcboehm_opt.v new file mode 100644 index 0000000..132ddc7 --- /dev/null +++ b/v_windows/v/vlib/builtin/array_d_gcboehm_opt.v @@ -0,0 +1,267 @@ +// non-pub versions of array functions +// that allocale new memory using `GC_MALLOC_ATOMIC()` +// when `-gc boehm_*_opt` is used. These memory areas are not +// scanned for pointers. + +module builtin + +fn __new_array_noscan(mylen int, cap int, elm_size int) array { + cap_ := if cap < mylen { mylen } else { cap } + arr := array{ + element_size: elm_size + data: vcalloc_noscan(cap_ * elm_size) + len: mylen + cap: cap_ + } + return arr +} + +fn __new_array_with_default_noscan(mylen int, cap int, elm_size int, val voidptr) array { + cap_ := if cap < mylen { mylen } else { cap } + mut arr := array{ + element_size: elm_size + data: vcalloc_noscan(cap_ * elm_size) + len: mylen + cap: cap_ + } + if val != 0 { + for i in 0 .. arr.len { + unsafe { arr.set_unsafe(i, val) } + } + } + return arr +} + +fn __new_array_with_array_default_noscan(mylen int, cap int, elm_size int, val array) array { + cap_ := if cap < mylen { mylen } else { cap } + mut arr := array{ + element_size: elm_size + data: vcalloc_noscan(cap_ * elm_size) + len: mylen + cap: cap_ + } + for i in 0 .. arr.len { + val_clone := val.clone() + unsafe { arr.set_unsafe(i, &val_clone) } + } + return arr +} + +// Private function, used by V (`nums := [1, 2, 3]`) +fn new_array_from_c_array_noscan(len int, cap int, elm_size int, c_array voidptr) array { + cap_ := if cap < len { len } else { cap } + arr := array{ + element_size: elm_size + data: vcalloc_noscan(cap_ * elm_size) + len: len + cap: cap_ + } + // TODO Write all memory functions (like memcpy) in V + unsafe { vmemcpy(arr.data, c_array, len * elm_size) } + return arr +} + +// Private function. Doubles array capacity if needed. +fn (mut a array) ensure_cap_noscan(required int) { + if required <= a.cap { + return + } + mut cap := if a.cap > 0 { a.cap } else { 2 } + for required > cap { + cap *= 2 + } + new_size := cap * a.element_size + new_data := vcalloc_noscan(new_size) + if a.data != voidptr(0) { + unsafe { vmemcpy(new_data, a.data, a.len * a.element_size) } + // TODO: the old data may be leaked when no GC is used (ref-counting?) + } + a.data = new_data + a.offset = 0 + a.cap = cap +} + +// repeat returns a new array with the given array elements repeated given times. +// `cgen` will replace this with an apropriate call to `repeat_to_depth()` + +// version of `repeat()` that handles multi dimensional arrays +// `unsafe` to call directly because `depth` is not checked +[unsafe] +fn (a array) repeat_to_depth_noscan(count int, depth int) array { + if count < 0 { + panic('array.repeat: count is negative: $count') + } + mut size := count * a.len * a.element_size + if size == 0 { + size = a.element_size + } + arr := array{ + element_size: a.element_size + data: if depth > 0 { vcalloc(size) } else { vcalloc_noscan(size) } + len: count * a.len + cap: count * a.len + } + if a.len > 0 { + for i in 0 .. count { + if depth > 0 { + ary_clone := unsafe { a.clone_to_depth_noscan(depth) } + unsafe { vmemcpy(arr.get_unsafe(i * a.len), &byte(ary_clone.data), a.len * a.element_size) } + } else { + unsafe { vmemcpy(arr.get_unsafe(i * a.len), &byte(a.data), a.len * a.element_size) } + } + } + } + return arr +} + +// insert inserts a value in the array at index `i` +fn (mut a array) insert_noscan(i int, val voidptr) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap_noscan(a.len + 1) + unsafe { + vmemmove(a.get_unsafe(i + 1), a.get_unsafe(i), (a.len - i) * a.element_size) + a.set_unsafe(i, val) + } + a.len++ +} + +// insert_many inserts many values into the array from index `i`. +[unsafe] +fn (mut a array) insert_many_noscan(i int, val voidptr, size int) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert_many: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap_noscan(a.len + size) + elem_size := a.element_size + unsafe { + iptr := a.get_unsafe(i) + vmemmove(a.get_unsafe(i + size), iptr, (a.len - i) * elem_size) + vmemcpy(iptr, val, size * elem_size) + } + a.len += size +} + +// prepend prepends one value to the array. +fn (mut a array) prepend_noscan(val voidptr) { + a.insert_noscan(0, val) +} + +// prepend_many prepends another array to this array. +[unsafe] +fn (mut a array) prepend_many_noscan(val voidptr, size int) { + unsafe { a.insert_many_noscan(0, val, size) } +} + +// pop returns the last element of the array, and removes it. +fn (mut a array) pop_noscan() voidptr { + // in a sense, this is the opposite of `a << x` + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.pop: array is empty') + } + } + new_len := a.len - 1 + last_elem := unsafe { &byte(a.data) + new_len * a.element_size } + a.len = new_len + // NB: a.cap is not changed here *on purpose*, so that + // further << ops on that array will be more efficient. + return unsafe { memdup_noscan(last_elem, a.element_size) } +} + +// `clone_static_to_depth_noscan()` returns an independent copy of a given array. +// Unlike `clone_to_depth_noscan()` it has a value receiver and is used internally +// for slice-clone expressions like `a[2..4].clone()` and in -autofree generated code. +fn (a array) clone_static_to_depth_noscan(depth int) array { + return unsafe { a.clone_to_depth_noscan(depth) } +} + +// recursively clone given array - `unsafe` when called directly because depth is not checked +[unsafe] +fn (a &array) clone_to_depth_noscan(depth int) array { + mut size := a.cap * a.element_size + if size == 0 { + size++ + } + mut arr := array{ + element_size: a.element_size + data: if depth == 0 { vcalloc_noscan(size) } else { vcalloc(size) } + len: a.len + cap: a.cap + } + // Recursively clone-generated elements if array element is array type + if depth > 0 { + for i in 0 .. a.len { + ar := array{} + unsafe { vmemcpy(&ar, a.get_unsafe(i), int(sizeof(array))) } + ar_clone := unsafe { ar.clone_to_depth_noscan(depth - 1) } + unsafe { arr.set_unsafe(i, &ar_clone) } + } + return arr + } else { + if !isnil(a.data) { + unsafe { vmemcpy(&byte(arr.data), a.data, a.cap * a.element_size) } + } + return arr + } +} + +fn (mut a array) push_noscan(val voidptr) { + a.ensure_cap_noscan(a.len + 1) + unsafe { vmemmove(&byte(a.data) + a.element_size * a.len, val, a.element_size) } + a.len++ +} + +// push_many implements the functionality for pushing another array. +// `val` is array.data and user facing usage is `a << [1,2,3]` +[unsafe] +fn (mut a3 array) push_many_noscan(val voidptr, size int) { + if a3.data == val && !isnil(a3.data) { + // handle `arr << arr` + copy := a3.clone() + a3.ensure_cap_noscan(a3.len + size) + unsafe { + vmemcpy(a3.get_unsafe(a3.len), copy.data, a3.element_size * size) + } + } else { + a3.ensure_cap_noscan(a3.len + size) + if !isnil(a3.data) && !isnil(val) { + unsafe { vmemcpy(a3.get_unsafe(a3.len), val, a3.element_size * size) } + } + } + a3.len += size +} + +// reverse returns a new array with the elements of the original array in reverse order. +fn (a array) reverse_noscan() array { + if a.len < 2 { + return a + } + mut arr := array{ + element_size: a.element_size + data: vcalloc_noscan(a.cap * a.element_size) + len: a.len + cap: a.cap + } + for i in 0 .. a.len { + unsafe { arr.set_unsafe(i, a.get_unsafe(a.len - 1 - i)) } + } + return arr +} + +// grow_cap grows the array's capacity by `amount` elements. +fn (mut a array) grow_cap_noscan(amount int) { + a.ensure_cap_noscan(a.cap + amount) +} + +// grow_len ensures that an array has a.len + amount of length +[unsafe] +fn (mut a array) grow_len_noscan(amount int) { + a.ensure_cap_noscan(a.len + amount) + a.len += amount +} diff --git a/v_windows/v/vlib/builtin/array_notd_gcboehm_opt.v b/v_windows/v/vlib/builtin/array_notd_gcboehm_opt.v new file mode 100644 index 0000000..1703167 --- /dev/null +++ b/v_windows/v/vlib/builtin/array_notd_gcboehm_opt.v @@ -0,0 +1,10 @@ +// dummy placeholder for functions from `array_d_gcboehm_opt.v` +// that might be needed for compile time +// `$if gcboehm_opt ? { ... } $else { ... }` + +module builtin + +// this is needed in `string.v` +fn __new_array_noscan(mylen int, cap int, elm_size int) array { + return array{} +} diff --git a/v_windows/v/vlib/builtin/array_test.v b/v_windows/v/vlib/builtin/array_test.v new file mode 100644 index 0000000..c27768a --- /dev/null +++ b/v_windows/v/vlib/builtin/array_test.v @@ -0,0 +1,1507 @@ +fn test_pointer() { + mut arr := []&int{} + a := 1 + b := 2 + c := 3 + arr << &a + arr << &b + arr << &c + assert *arr[0] == 1 + arr[1] = &c + assert *arr[1] == 3 + mut d_arr := [arr] // [][]&int + d_arr << arr + assert *d_arr[0][1] == 3 + println(*d_arr[0][1]) + assert *d_arr[1][0] == 1 +} + +fn test_assign() { + mut arr := [2, 4, 8, 16, 32, 64, 128] + arr[0] = 2 + arr[1] &= 255 + arr[2] |= 255 + arr[3] <<= 4 + arr[4] >>= 4 + arr[5] %= 5 + arr[6] ^= 3 + assert arr[0] == 2 + assert arr[1] == 4 & 255 + assert arr[2] == 8 | 255 + assert arr[3] == 16 << 4 + assert arr[4] == 32 >> 4 + assert arr[5] == 64 % 5 + assert arr[6] == 128 ^ 3 +} + +fn test_ints() { + mut a := [1, 5, 2, 3] + assert a.len == 4 + assert a[0] == 1 + assert a[2] == 2 + assert a.last() == 3 + a << 4 + assert a.len == 5 + assert a[4] == 4 + assert a.last() == 4 + s := a.str() + assert s == '[1, 5, 2, 3, 4]' + assert a[1] == 5 + assert a.last() == 4 +} + +fn test_deleting() { + mut a := [1, 5, 2, 3, 4] + assert a.len == 5 + assert a.str() == '[1, 5, 2, 3, 4]' + a.delete(0) + assert a.str() == '[5, 2, 3, 4]' + assert a.len == 4 + a.delete(1) + assert a.str() == '[5, 3, 4]' + assert a.len == 3 + a.delete(a.len - 1) + assert a.str() == '[5, 3]' + assert a.len == 2 +} + +fn test_slice_delete() { + mut a := [1.5, 2.5, 3.25, 4.5, 5.75] + b := a[2..4] + a.delete(0) + assert a == [2.5, 3.25, 4.5, 5.75] + assert b == [3.25, 4.5] + a = [3.75, 4.25, -1.5, 2.25, 6.0] + c := a[..3] + a.delete(2) + assert a == [3.75, 4.25, 2.25, 6.0] + assert c == [3.75, 4.25, -1.5] +} + +fn test_delete_many() { + mut a := [1, 2, 3, 4, 5, 6, 7, 8, 9] + b := a[2..6] + a.delete_many(4, 3) + assert a == [1, 2, 3, 4, 8, 9] + assert b == [3, 4, 5, 6] + c := a[..a.len] + a.delete_many(2, 0) // this should just clone + a[1] = 17 + assert a == [1, 17, 3, 4, 8, 9] + assert c == [1, 2, 3, 4, 8, 9] + a.delete_many(0, a.len) + assert a == []int{} +} + +fn test_short() { + a := [1, 2, 3] + assert a.len == 3 + assert a.cap == 3 + assert a[0] == 1 + assert a[1] == 2 + assert a[2] == 3 +} + +fn test_large() { + mut a := [0].repeat(0) + for i in 0 .. 10000 { + a << i + } + assert a.len == 10000 + assert a[234] == 234 +} + +struct Chunk { + val string +} + +struct Kkk { + q []Chunk +} + +fn test_empty() { + mut chunks := []Chunk{} + a := Chunk{} + assert chunks.len == 0 + chunks << a + assert chunks.len == 1 + chunks = [] + assert chunks.len == 0 + chunks << a + assert chunks.len == 1 +} + +fn test_push() { + mut a := []int{} + a << 1 + a << 3 + assert a[1] == 3 + assert a.str() == '[1, 3]' +} + +fn test_insert() { + mut a := [1, 2] + a.insert(0, 3) + assert a[0] == 3 + assert a[2] == 2 + assert a.len == 3 + a.insert(1, 4) + assert a[1] == 4 + assert a[2] == 1 + assert a.len == 4 + a.insert(4, 5) + assert a[4] == 5 + assert a[3] == 2 + assert a.len == 5 + mut b := []f64{} + assert b.len == 0 + b.insert(0, f64(1.1)) + assert b.len == 1 + assert b[0] == f64(1.1) +} + +fn test_insert_many() { + mut a := [3, 4] + a.insert(0, [1, 2]) + assert a == [1, 2, 3, 4] + b := [5, 6] + a.insert(1, b) + assert a == [1, 5, 6, 2, 3, 4] +} + +fn test_prepend() { + mut a := []int{} + assert a.len == 0 + a.prepend(1) + assert a.len == 1 + assert a[0] == 1 + mut b := []f64{} + assert b.len == 0 + b.prepend(f64(1.1)) + assert b.len == 1 + assert b[0] == f64(1.1) +} + +fn test_prepend_many() { + mut a := [3, 4] + a.prepend([1, 2]) + assert a == [1, 2, 3, 4] + b := [5, 6] + a.prepend(b) + assert a == [5, 6, 1, 2, 3, 4] +} + +fn test_strings() { + a := ['a', 'b', 'c'] + assert a.str() == "['a', 'b', 'c']" +} + +/* +fn test_compare_ints() { + assert compare_ints(1, 2) == -1 + assert compare_ints(2, 1) == 1 + assert compare_ints(0, 0) == 0 + + a := 1 + b := 2 + assert compare_ints(a, b) == -1 + assert compare_ints(b, a) == 1 + assert compare_ints(a, a) == 0 +} +*/ +fn test_repeat() { + { + a := [0].repeat(5) + assert a.len == 5 + assert a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0 + } + { + a := [1.1].repeat(10) + assert a[0] == 1.1 + assert a[5] == 1.1 + assert a[9] == 1.1 + } + { + a := [i64(-123)].repeat(10) + assert a[0] == -123 + assert a[5] == -123 + assert a[9] == -123 + } + { + a := [u64(123)].repeat(10) + assert a[0] == 123 + assert a[5] == 123 + assert a[9] == 123 + } + { + a := [1.1].repeat(10) + assert a[0] == 1.1 + assert a[5] == 1.1 + assert a[9] == 1.1 + } + { + a := [1, 2].repeat(2) + assert a[0] == 1 + assert a[1] == 2 + assert a[2] == 1 + assert a[3] == 2 + } + { + a := ['1', 'abc'].repeat(2) + assert a[0] == '1' + assert a[1] == 'abc' + assert a[2] == '1' + assert a[3] == 'abc' + } + { + mut a := ['1', 'abc'].repeat(0) + assert a.len == 0 + a << 'abc' + assert a[0] == 'abc' + } +} + +fn test_deep_repeat() { + mut a3 := [[[1, 1], [2, 2], [3, 3]], [[4, 4], [5, 5], [6, 6]]] + r := a3.repeat(3) + a3[1][1][0] = 17 + assert r == [ + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + ] + assert a3 == [[[1, 1], [2, 2], [3, 3]], [[4, 4], [17, 5], [6, 6]]] +} + +fn test_right() { + a := [1, 2, 3, 4] + c := a[1..a.len] + d := a[1..] + assert c[0] == 2 + assert c[1] == 3 + assert d[0] == 2 + assert d[1] == 3 +} + +fn test_left() { + a := [1, 2, 3] + c := a[0..2] + d := a[..2] + assert c[0] == 1 + assert c[1] == 2 + assert d[0] == 1 + assert d[1] == 2 +} + +fn test_slice() { + a := [1, 2, 3, 4] + b := a[2..4] + assert b.len == 2 + assert a[1..2].len == 1 + assert a.len == 4 +} + +fn test_push_many() { + mut a := [1, 2, 3] + b := [4, 5, 6] + a << b + assert a.len == 6 + assert a[0] == 1 + assert a[3] == 4 + assert a[5] == 6 +} + +fn test_reverse() { + a := [1, 2, 3, 4] + b := ['test', 'array', 'reverse'] + c := a.reverse() + println(c) + d := b.reverse() + for i, _ in c { + assert c[i] == a[a.len - i - 1] + } + for i, _ in d { + assert d[i] == b[b.len - i - 1] + } + e := []int{} + f := e.reverse() + assert f.len == 0 +} + +const ( + c_n = 5 +) + +struct Foooj { + a [5]int // c_n +} + +fn test_fixed() { + mut nums := [4]int{} + // x := nums[1..3] + // assert x.len == 2 + assert nums[0] == 0 + assert nums[1] == 0 + assert nums[2] == 0 + assert nums[3] == 0 + nums[1] = 7 + assert nums[1] == 7 + nums2 := [5]int{} // c_n + assert nums2[c_n - 1] == 0 +} + +fn modify(mut numbers []int) { + numbers[0] = 777 +} + +fn test_mut_slice() { + mut n := [1, 2, 3] + // modify(mut n) + modify(mut n[..2]) + assert n[0] == 777 + modify(mut n[2..]) + assert n[2] == 777 + println(n) +} + +fn double_up(mut a []int) { + for i := 0; i < a.len; i++ { + a[i] = a[i] * 2 + } +} + +fn double_up_v2(mut a []int) { + for i, _ in a { + a[i] = a[i] * 2 // or val*2, doesn't matter + } +} + +fn test_mut_arg() { + mut arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + double_up(mut arr) + assert arr.str() == '[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]' + arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + double_up_v2(mut arr) + assert arr.str() == '[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]' +} + +fn test_clone() { + nums := [1, 2, 3, 4, 100] + _ = nums + nums2 := nums.clone() + assert nums2.len == 5 + assert nums.str() == '[1, 2, 3, 4, 100]' + assert nums2.str() == '[1, 2, 3, 4, 100]' + assert nums[1..3].str() == '[2, 3]' +} + +/* +fn test_copy() { + a := [1, 2, 3] + b := a + assert b[0] == 1 + assert b[1] == 2 + assert b[2] == 3 +} +*/ +fn test_multi_array_clone() { + // 2d array_int + mut a2_1 := [[1, 2, 3], [4, 5, 6]] + mut a2_2 := a2_1.clone() + a2_1[0][1] = 0 + a2_2[1][0] = 0 + assert a2_1 == [[1, 0, 3], [4, 5, 6]] + assert a2_2 == [[1, 2, 3], [0, 5, 6]] + // 2d array_string + mut b2_1 := [['1', '2', '3'], ['4', '5', '6']] + mut b2_2 := b2_1.clone() + b2_1[0][1] = '0' + b2_2[1][0] = '0' + assert b2_1 == [['1', '0', '3'], ['4', '5', '6']] + assert b2_2 == [['1', '2', '3'], ['0', '5', '6']] + // 3d array_int + mut a3_1 := [[[1, 1], [2, 2], [3, 3]], [[4, 4], [5, 5], [6, 6]]] + mut a3_2 := a3_1.clone() + a3_1[0][0][1] = 0 + a3_2[0][1][0] = 0 + assert a3_1 == [[[1, 0], [2, 2], [3, 3]], [[4, 4], [5, 5], + [6, 6], + ]] + assert a3_2 == [[[1, 1], [0, 2], [3, 3]], [[4, 4], [5, 5], + [6, 6], + ]] + // 3d array_string + mut b3_1 := [[['1', '1'], ['2', '2'], ['3', '3']], [['4', '4'], + ['5', '5'], ['6', '6']]] + mut b3_2 := b3_1.clone() + b3_1[0][0][1] = '0' + b3_2[0][1][0] = '0' + assert b3_1 == [[['1', '0'], ['2', '2'], ['3', '3']], [['4', '4'], + ['5', '5'], ['6', '6']]] + assert b3_2 == [[['1', '1'], ['0', '2'], ['3', '3']], [['4', '4'], + ['5', '5'], ['6', '6']]] +} + +fn test_doubling() { + mut nums := [1, 2, 3, 4, 5] + for i in 0 .. nums.len { + nums[i] *= 2 + } + println(nums.str()) + assert nums.str() == '[2, 4, 6, 8, 10]' +} + +struct Test2 { + one int + two int +} + +struct Test { + a string +mut: + b []Test2 +} + +// TODO: default array/struct str methods +fn (ta []Test2) str() string { + mut s := '[' + for i, t in ta { + s += t.str() + if i < ta.len - 1 { + s += ', ' + } + } + s += ']' + return s +} + +fn (t Test2) str() string { + return '{$t.one $t.two}' +} + +fn (t Test) str() string { + return '{$t.a $t.b}' +} + +fn test_struct_print() { + mut a := Test{ + a: 'Test' + b: [] + } + b := Test2{ + one: 1 + two: 2 + } + a.b << b + a.b << b + assert a.str() == '{Test [{1 2}, {1 2}]}' + assert b.str() == '{1 2}' + assert a.b.str() == '[{1 2}, {1 2}]' +} + +fn test_single_element() { + mut a := [1] + a << 2 + assert a.len == 2 + assert a[0] == 1 + assert a[1] == 2 + println(a) +} + +fn test_find_index() { + // string + a := ['v', 'is', 'great'] + assert a.index('v') == 0 + assert a.index('is') == 1 + assert a.index('gre') == -1 + // int + b := [1, 2, 3, 4] + assert b.index(1) == 0 + assert b.index(4) == 3 + assert b.index(5) == -1 + // byte + c := [0x22, 0x33, 0x55] + assert c.index(0x22) == 0 + assert c.index(0x55) == 2 + assert c.index(0x99) == -1 + // char + d := [`a`, `b`, `c`] + assert d.index(`b`) == 1 + assert d.index(`c`) == 2 + assert d.index(`u`) == -1 +} + +fn test_multi() { + a := [[1, 2, 3], [4, 5, 6]] + assert a.len == 2 + assert a[0].len == 3 + assert a[0][0] == 1 + assert a[0][2] == 3 + assert a[1][2] == 6 + // TODO + // b := [ [[1,2,3],[4,5,6]], [[1,2]] ] + // assert b[0][0][0] == 1 +} + +fn test_in() { + a := [1, 2, 3] + assert 1 in a + assert 2 in a + assert 3 in a + assert 4 !in a + assert 0 !in a + assert 0 !in a + assert 4 !in a + b := [1, 4, 0] + c := [3, 6, 2, 0] + assert 0 in b + assert 0 in c +} + +fn sum(prev int, curr int) int { + return prev + curr +} + +fn sub(prev int, curr int) int { + return prev - curr +} + +fn test_reduce() { + a := [1, 2, 3, 4, 5] + b := a.reduce(sum, 0) + c := a.reduce(sum, 5) + d := a.reduce(sum, -1) + assert b == 15 + assert c == 20 + assert d == 14 + e := [1, 2, 3] + f := e.reduce(sub, 0) + g := e.reduce(sub, -1) + assert f == -6 + assert g == -7 +} + +fn filter_test_helper_1(a int) bool { + return a > 3 +} + +fn test_filter() { + a := [1, 2, 3, 4, 5, 6] + b := a.filter(it % 2 == 0) + assert b.len == 3 + assert b[0] == 2 + assert b[1] == 4 + assert b[2] == 6 + c := ['v', 'is', 'awesome'] + d := c.filter(it.len > 1) + assert d[0] == 'is' + assert d[1] == 'awesome' + //////// + arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + println(arr.filter(it % 2 == 0 || it % 3 == 0)) + assert true + assert [1, 2, 3].len == 3 + mut mut_arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + mut_arr = mut_arr.filter(it < 4) + assert mut_arr.len == 3 + assert a.filter(filter_test_helper_1) == [4, 5, 6] + assert [1, 5, 10].filter(filter_test_helper_1) == [5, 10] + // TODO + // assert arr.filter(arr % 2).len == 5 +} + +fn test_anon_fn_filter() { + filter_num := fn (i int) bool { + return i % 2 == 0 + } + assert [1, 2, 3, 4, 5].filter(filter_num) == [2, 4] +} + +fn test_anon_fn_arg_filter() { + a := [1, 2, 3, 4].filter(fn (i int) bool { + return i % 2 == 0 + }) + assert a == [2, 4] +} + +fn map_test_helper_1(i int) int { + return i * i +} + +fn map_test_helper_2(i int, b string) int { + return i + b.len +} + +fn map_test_helper_3(i int, b []string) int { + return i + b.map(it.len)[i % b.len] +} + +fn test_map() { + nums := [1, 2, 3, 4, 5, 6] + strs := ['v', 'is', 'awesome'] + // assert nums.map() == + // assert nums.map(it, 'excessive') == + // identity + assert nums.map(it) == [1, 2, 3, 4, 5, 6] + assert strs.map(it) == ['v', 'is', 'awesome'] + assert nums.map(it - it) == [0, 0, 0, 0, 0, 0] + assert nums.map(it - it)[0] == 0 + // type switch + assert nums.map(it * 10) == [10, 20, 30, 40, 50, 60] + assert nums.map(it * it) == [1, 4, 9, 16, 25, 36] + assert nums.map('$it') == ['1', '2', '3', '4', '5', '6'] + assert nums.map(it % 2 == 0) == [false, true, false, true, false, true] + assert strs.map(it.to_upper()) == ['V', 'IS', 'AWESOME'] + assert strs.map(it == 'awesome') == [false, false, true] + assert strs.map(it.len in nums) == [true, true, false] + assert strs.map(int(7)) == [7, 7, 7] + // external func + assert nums.map(map_test_helper_1(it)) == [1, 4, 9, 16, 25, 36] + assert nums.map(map_test_helper_2(it, 'bb')) == [3, 4, 5, 6, 7, 8] + assert nums.map(map_test_helper_3(it, strs)) == [3, 9, 4, 6, 12, 7] + // empty array as input + assert []int{len: 0}.map(it * 2) == [] + // nested maps (where it is of same type) + assert nums.map(strs.map(int(7)) == [7, 7, 7]) == [true, true, true, true, true, true] + assert nums.map('$it' + strs.map('a')[0]) == ['1a', '2a', '3a', '4a', '5a', '6a'] + assert nums.map(it + strs.map(int(7))[0]) == [8, 9, 10, 11, 12, 13] + assert nums.map(it + strs.map(it.len)[0]) == [2, 3, 4, 5, 6, 7] + assert strs.map(it.len + strs.map(it.len)[0]) == [2, 3, 8] + // nested (different it types) + assert strs.map(it[nums.map(it - it)[0]]) == [byte(`v`), `i`, `a`] + assert nums[0..3].map('$it' + strs.map(it)[it - 1]) == ['1v', '2is', '3awesome'] + assert nums.map(map_test_helper_1) == [1, 4, 9, 16, 25, 36] + assert [1, 5, 10].map(map_test_helper_1) == [1, 25, 100] + assert nums == [1, 2, 3, 4, 5, 6] + assert strs == ['v', 'is', 'awesome'] +} + +fn test_anon_fn_map() { + add_num := fn (i int) int { + return i + 1 + } + assert [1, 2, 3].map(add_num) == [2, 3, 4] +} + +fn test_multi_anon_fn_map() { + a := [1, 2, 3].map(fn (i int) int { + return i + 1 + }) + b := [1, 2, 3].map(fn (i int) int { + return i + 2 + }) + assert a == [2, 3, 4] + assert b == [3, 4, 5] +} + +fn test_anon_fn_arg_map() { + a := [1, 2, 3].map(fn (i int) int { + return i + 1 + }) + assert a == [2, 3, 4] +} + +fn test_anon_fn_arg_different_type_map() { + i_to_str := fn (i int) string { + return i.str() + } + a := [1, 2, 3].map(i_to_str) + assert a == ['1', '2', '3'] +} + +fn test_anon_fn_inline_different_type_map() { + a := [1, 2, 3].map(fn (i int) string { + return i.str() + }) + assert a == ['1', '2', '3'] +} + +fn test_array_str() { + numbers := [1, 2, 3] + assert numbers == [1, 2, 3] + numbers2 := [numbers, [4, 5, 6]] // dup str() bug + println(numbers2) + assert true + assert numbers.str() == '[1, 2, 3]' + // QTODO + // assert numbers2.str() == '[[1, 2, 3], [4, 5, 6]]' +} + +struct User { + age int + name string +} + +fn test_eq() { + assert [5, 6, 7] != [6, 7] + assert [`a`, `b`] == [`a`, `b`] + assert [User{ + age: 22 + name: 'bob' + }] == [User{ + age: 22 + name: 'bob' + }] + assert [{ + 'bob': 22 + }, { + 'tom': 33 + }] == [{ + 'bob': 22 + }, { + 'tom': 33 + }] + assert [[1, 2, 3], [4]] == [[1, 2, 3], [4]] +} + +fn test_fixed_array_eq() { + a1 := [1, 2, 3]! + assert a1 == [1, 2, 3]! + assert a1 != [2, 3, 4]! + + a2 := [[1, 2]!, [3, 4]!]! + assert a2 == [[1, 2]!, [3, 4]!]! + assert a2 != [[3, 4]!, [1, 2]!]! + + a3 := [[1, 2], [3, 4]]! + assert a3 == [[1, 2], [3, 4]]! + assert a3 != [[1, 1], [2, 2]]! + + a4 := [[`a`, `b`], [`c`, `d`]]! + assert a4 == [[`a`, `b`], [`c`, `d`]]! + assert a4 != [[`c`, `a`], [`a`, `b`]]! + + a5 := [['aaa', 'bbb'], ['ccc', 'ddd']]! + assert a5 == [['aaa', 'bbb'], ['ccc', 'ddd']]! + assert a5 != [['abc', 'def'], ['ccc', 'ddd']]! + + a6 := [['aaa', 'bbb']!, ['ccc', 'ddd']!]! + assert a6 == [['aaa', 'bbb']!, ['ccc', 'ddd']!]! + assert a6 != [['aaa', 'bbb']!, ['aaa', 'ddd']!]! + + a7 := [[1, 2]!, [3, 4]!] + assert a7 == [[1, 2]!, [3, 4]!] + assert a7 != [[2, 3]!, [1, 2]!] + + a8 := [['aaa', 'bbb']!, ['ccc', 'ddd']!] + assert a8 == [['aaa', 'bbb']!, ['ccc', 'ddd']!] + assert a8 != [['bbb', 'aaa']!, ['cccc', 'dddd']!] +} + +fn test_fixed_array_literal_eq() { + assert [1, 2, 3]! == [1, 2, 3]! + assert [1, 1, 1]! != [1, 2, 3]! + + assert [[1, 2], [3, 4]]! == [[1, 2], [3, 4]]! + assert [[1, 1], [2, 2]]! != [[1, 2], [3, 4]]! + + assert [[1, 1]!, [2, 2]!]! == [[1, 1]!, [2, 2]!]! + assert [[1, 1]!, [2, 2]!]! != [[1, 2]!, [2, 3]!]! + + assert [[1, 1]!, [2, 2]!] == [[1, 1]!, [2, 2]!] + assert [[1, 1]!, [2, 2]!] != [[1, 2]!, [2, 3]!] +} + +fn test_sort() { + mut a := ['hi', '1', '5', '3'] + a.sort() + assert a == ['1', '3', '5', 'hi'] + + mut nums := [67, -3, 108, 42, 7] + nums.sort() + assert nums == [-3, 7, 42, 67, 108] + + nums.sort(a < b) + assert nums == [-3, 7, 42, 67, 108] + + nums.sort(b < a) + assert nums == [108, 67, 42, 7, -3] + + mut users := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}] + users.sort(a.age < b.age) + assert users[0].age == 20 + assert users[1].age == 22 + assert users[2].age == 25 + assert users[0].name == 'Bob' + assert users[1].name == 'Peter' + assert users[2].name == 'Alice' + + users.sort(a.age > b.age) + assert users[0].age == 25 + assert users[1].age == 22 + assert users[2].age == 20 + + users.sort(b.age > a.age) + assert users[0].age == 20 + assert users[1].age == 22 + assert users[2].age == 25 + + users.sort(a.name < b.name) + assert users[0].name == 'Alice' + assert users[1].name == 'Bob' + assert users[2].name == 'Peter' +} + +fn test_rune_sort() { + mut bs := [`f`, `e`, `d`, `b`, `c`, `a`] + bs.sort() + println(bs) + assert bs == [`a`, `b`, `c`, `d`, `e`, `f`] + + bs.sort(a > b) + println(bs) + assert bs == [`f`, `e`, `d`, `c`, `b`, `a`] + + bs.sort(a < b) + println(bs) + assert bs == [`a`, `b`, `c`, `d`, `e`, `f`] +} + +fn test_sort_by_different_order_of_a_b() { + mut x := [1, 2, 3] + x.sort(a < b) + println(x) + assert x == [1, 2, 3] + + mut y := [1, 2, 3] + y.sort(b < a) + println(y) + assert y == [3, 2, 1] +} + +fn test_f32_sort() { + mut f := [f32(50.0), 15, 1, 79, 38, 0, 27] + f.sort() + assert f == [f32(0.0), 1, 15, 27, 38, 50, 79] + + f.sort(a < b) + assert f == [f32(0.0), 1, 15, 27, 38, 50, 79] + + f.sort(b > a) + assert f == [f32(0.0), 1, 15, 27, 38, 50, 79] + + f.sort(b < a) + assert f == [f32(79.0), 50, 38, 27, 15, 1, 0] + + f.sort(a > b) + assert f == [f32(79.0), 50, 38, 27, 15, 1, 0] +} + +fn test_f64_sort() { + mut f := [50.0, 15, 1, 79, 38, 0, 27] + f.sort() + assert f[0] == 0.0 + assert f[1] == 1.0 + assert f[6] == 79.0 +} + +fn test_i64_sort() { + mut f := [i64(50), 15, 1, 79, 38, 0, 27] + f.sort() + assert f[0] == 0 + assert f[1] == 1 + assert f[6] == 79 +} + +fn test_sort_index_expr() { + mut f := [[i64(50), 48], [i64(15)], [i64(1)], [i64(79)], [i64(38)], + [i64(0)], [i64(27)]] + // TODO This currently gives "indexing pointer" error without unsafe + unsafe { + f.sort(a[0] < b[0]) + } + assert f == [[i64(0)], [i64(1)], [i64(15)], [i64(27)], [i64(38)], + [i64(50), 48], [i64(79)]] +} + +fn test_a_b_paras_sort() { + mut arr_i := [1, 3, 2] + arr_i.sort(a < b) + println(arr_i) + assert arr_i == [1, 2, 3] + arr_i.sort(b < a) + println(arr_i) + assert arr_i == [3, 2, 1] + + mut arr_f := [1.1, 3.3, 2.2] + arr_f.sort(a < b) + println(arr_f) + assert arr_f == [1.1, 2.2, 3.3] + arr_f.sort(b < a) + println(arr_f) + assert arr_f == [3.3, 2.2, 1.1] +} + +/* +fn test_for_last() { + numbers := [1, 2, 3, 4] + mut s := '[' + for num in numbers { + s += '$num' + if !last { + s += ', ' + + } + } + s += ']' + assert s == '[1, 2, 3, 4]' +} +*/ +struct Foo { +mut: + bar []int +} + +fn test_in_struct() { + mut baz := Foo{ + bar: [0, 0, 0] + } + baz.bar[0] += 2 + baz.bar[0]++ + assert baz.bar[0] == 3 +} + +[direct_array_access] +fn test_direct_modification() { + mut foo := [2, 0, 5] + foo[1] = 3 + foo[0] *= 7 + foo[1]-- + foo[2] -= 2 + assert foo[0] == 14 + assert foo[1] == 2 + assert foo[2] == 3 +} + +fn test_bools() { + println('test b') + mut a := [true, false] + a << true + println(a) +} + +fn test_push_many_self() { + mut actual_arr := [1, 2, 3, 4] + actual_arr << actual_arr + expected_arr := [1, 2, 3, 4, 1, 2, 3, 4] + assert actual_arr.len == expected_arr.len + for i in 0 .. actual_arr.len { + assert actual_arr[i] == expected_arr[i] + } +} + +fn test_for() { + nums := [1, 2, 3] + mut sum := 0 + for num in nums { + sum += num + } + assert sum == 6 +} + +fn test_clear() { + mut arr := [1, 2, 3] + assert arr.len == 3 + arr.clear() + assert arr.len == 0 + arr << 3 + arr << 2 + arr << 1 + arr << 0 + assert arr.len == 4 + assert arr[0] == 3 + assert arr[1] == 2 + assert arr[2] == 1 + assert arr[3] == 0 + arr.clear() + assert arr.len == 0 +} + +fn test_trim() { + mut arr := [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert arr.len == 9 + arr.trim(9) + assert arr.len == 9 + assert arr.last() == 9 + arr.trim(7) + assert arr.len == 7 + assert arr.last() == 7 + arr.trim(2) + assert arr.len == 2 + assert arr.last() == 2 +} + +fn test_hex() { + // array hex + st := [byte(`V`), `L`, `A`, `N`, `G`] + assert st.hex() == '564c414e47' + assert st.hex().len == 10 + st1 := [byte(0x41)].repeat(100) + assert st1.hex() == '41'.repeat(100) +} + +fn test_left_shift_precendence() { + mut arr := []int{} + arr << 1 + 1 + arr << 1 - 1 + arr << 2 / 1 + arr << 2 * 1 + assert arr[0] == 2 + assert arr[1] == 0 + assert arr[2] == 2 + assert arr[3] == 2 +} + +fn test_array_with_cap() { + a4 := []int{len: 1, cap: 10} + assert a4.len == 1 + assert a4.cap == 10 + a5 := []int{len: 1, cap: 10} + assert a5.len == 1 + assert a5.cap == 10 +} + +fn test_multi_array_index() { + mut a := [][]int{len: 2, init: []int{len: 3, init: 0}} + a[0][0] = 1 + assert '$a' == '[[1, 0, 0], [0, 0, 0]]' + mut b := [[0].repeat(3)].repeat(2) + b[0][0] = 1 + assert '$b' == '[[1, 0, 0], [0, 0, 0]]' +} + +fn test_plus_assign_string() { + mut a := [''] + a[0] += 'abc' + assert a == ['abc'] +} + +fn mut_arr_with_eq_in_fn(mut a []int) { + if a == [1, 2, 3, 4] { + a[0] = 0 + } + if [0, 2, 3, 4] == a { + a[1] = 0 + } + if !(a != [0, 0, 3, 4]) { + a[2] = 0 + } + if !([0, 0, 0, 4] != a) { + a[3] = 0 + } +} + +fn test_mut_arr_with_eq_in_fn() { + mut a := [1, 2, 3, 4] + mut_arr_with_eq_in_fn(mut a) + assert a == [0, 0, 0, 0] +} + +fn array_in_mut(mut a []int) { + if 1 in a { + a[0] = 2 + } +} + +fn test_array_in_mut() { + mut a := [1, 2] + array_in_mut(mut a) + assert a == [2, 2] +} + +// test array delete in function with mut argument +fn delete_nums(mut arr []int) { + arr.delete(0) +} + +fn test_array_delete_in_mut() { + mut nums := [1, 2, 3] + delete_nums(mut nums) + assert nums == [2, 3] +} + +// test array add in function with mut argument +fn add_nums(mut arr []int) { + arr << 4 +} + +fn test_array_add_in_mut() { + mut nums := [1, 2, 3] + add_nums(mut nums) + assert nums == [1, 2, 3, 4] +} + +fn test_reverse_in_place() { + mut a := [1, 2, 3, 4] + a.reverse_in_place() + assert a == [4, 3, 2, 1] + mut b := ['a', 'b', 'c'] + b.reverse_in_place() + assert b == ['c', 'b', 'a'] + mut c := [[1, 2], [3, 4], [5, 6]] + c.reverse_in_place() + assert c == [[5, 6], [3, 4], [1, 2]] +} + +fn test_array_int_pop() { + mut a := [1, 2, 3, 4, 5] + assert a.len == 5 + x := a.last() + y := a.pop() + assert x == y + assert a.len == 4 + z := a.pop() + assert a.len == 3 + assert z == 4 + x1 := a.pop() + x2 := a.pop() + final := a.pop() + assert final == 1 +} + +fn test_array_string_pop() { + mut a := ['abc', 'def', 'xyz'] + assert a.len == 3 + assert a.pop() == 'xyz' + assert a.pop() == 'def' + assert a.pop() == 'abc' + assert a.len == 0 + assert a.cap == 3 +} + +fn test_array_first() { + a := [3] + assert a.first() == 3 + b := [1, 2, 3, 4] + assert b.first() == 1 + c := ['abc', 'def'] + assert c.first()[0] == `a` + s := [Chunk{'a'}] + assert s.first().val == 'a' +} + +fn test_array_last() { + a := [3] + assert a.last() == 3 + b := [1, 2, 3, 4] + assert b.last() == 4 + c := ['abc', 'def'] + assert c.last()[0] == `d` + s := [Chunk{'a'}] + assert s.last().val == 'a' +} + +[direct_array_access] +fn test_direct_array_access() { + mut a := [11, 22, 33, 44] + assert a[0] == 11 + assert a[2] == 33 + x := a[0] + a[0] = 21 + a[1] += 2 + a[2] = x + 3 + a[3] -= a[1] + assert a == [21, 24, 14, 20] +} + +[direct_array_access] +fn test_direct_array_access_via_ptr() { + mut b := [11, 22, 33, 44] + unsafe { + mut a := &b + assert a[0] == 11 + assert a[2] == 33 + x := a[0] + a[0] = 21 + a[1] += 2 + a[2] = x + 3 + a[3] -= a[1] + assert a == [21, 24, 14, 20] + } +} + +fn test_push_arr_string_free() { + mut lines := ['hi'] + s := 'a' + 'b' + lines << s + // make sure the data in the array is valid after freeing the string + unsafe { s.free() } + // + println(lines) + assert lines.len == 2 + assert lines[0] == 'hi' + assert lines[1] == 'ab' +} + +const ( + grid_size_1 = 2 + grid_size_2 = 3 + grid_size_3 = 4 + cell_value = 123 +) + +fn test_multidimensional_array_initialization_with_consts() { + mut data := [][][]int{len: grid_size_1, init: [][]int{len: grid_size_2, init: []int{len: grid_size_3, init: cell_value}}} + assert data.len == grid_size_1 + assert data[0].len == grid_size_2 + assert data[0][0].len == grid_size_3 + assert data[0][0][0] == cell_value + assert data[1][1][1] == cell_value +} + +fn test_byteptr_vbytes() { + unsafe { + bp := malloc(5) + bp[0] = 1 + bp[1] = 2 + bp[2] = 3 + bp[3] = 4 + bp[4] = 255 + bytes := bp.vbytes(5) + println(bytes) + assert bytes.len == 5 + assert bytes[0] == 1 + assert bytes[1] == 2 + assert bytes[2] == 3 + assert bytes[3] == 4 + assert bytes[4] == 255 + } +} + +fn test_voidptr_vbytes() { + unsafe { + bp := malloc(3) + bp[0] = 4 + bp[1] = 5 + bp[2] = 6 + bytes := voidptr(bp).vbytes(3) + assert bytes.len == 3 + assert bytes[0] == 4 + assert bytes[1] == 5 + assert bytes[2] == 6 + println(bytes) + } +} + +fn test_multi_array_prepend() { + mut a := [][]int{} + a.prepend([1, 2, 3]) + assert a == [[1, 2, 3]] + mut b := [][]int{} + b.prepend([[1, 2, 3]]) + assert b == [[1, 2, 3]] +} + +fn test_multi_array_insert() { + mut a := [][]int{} + a.insert(0, [1, 2, 3]) + assert a == [[1, 2, 3]] + mut b := [][]int{} + b.insert(0, [[1, 2, 3]]) + assert b == [[1, 2, 3]] +} + +fn test_multi_array_in() { + a := [[1]] + println([1] in a) + assert [1] in a +} + +fn test_any_type_array_contains() { + a := [true, false] + assert a.contains(true) + assert true in a + assert a.contains(false) + assert false in a + b := [i64(2), 3, 4] + assert b.contains(i64(3)) + assert 5 !in b + c := [[1], [2]] + assert c.contains([1]) + assert [2] in c + assert [3] !in c +} + +struct Person { + name string + nums []int + kv map[string]string +} + +fn test_struct_array_of_multi_type_in() { + ivan := Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + } + people := [Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + }, Person{ + name: 'bob' + nums: [2] + kv: { + 'bbb': '222' + } + }] + println(ivan in people) + assert ivan in people +} + +fn test_struct_array_of_multi_type_index() { + ivan := Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + } + people := [Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + }, Person{ + name: 'bob' + nums: [2] + kv: { + 'bbb': '222' + } + }] + println(people.index(ivan)) + assert people.index(ivan) == 0 +} + +struct Coord { + x int + y int + z int +} + +fn test_array_struct_contains() { + mut coords := []Coord{} + coord_1 := Coord{ + x: 1 + y: 2 + z: -1 + } + coords << coord_1 + exists := coord_1 in coords + not_exists := coord_1 !in coords + println('`exists`: $exists and `not exists`: $not_exists') + assert exists == true + assert not_exists == false +} + +fn test_array_struct_ref_contains() { + mut coords := []&Coord{} + coord_1 := &Coord{ + x: 1 + y: 2 + z: -1 + } + coords << coord_1 + exists := coord_1 in coords + println(exists) + assert exists == true +} + +fn test_array_struct_ref_index() { + mut coords := []&Coord{} + coord_1 := &Coord{ + x: 1 + y: 2 + z: -1 + } + coords << coord_1 + println(coords.index(coord_1)) + assert coords.index(coord_1) == 0 +} + +fn test_array_of_array_append() { + mut x := [][]int{len: 4} + println(x) // OK + x[2] << 123 // RTE + println(x) + assert '$x' == '[[], [], [123], []]' +} + +fn test_array_of_map_insert() { + mut x := []map[string]int{len: 4} + println(x) // OK + x[2]['123'] = 123 // RTE + println(x) + assert '$x' == "[{}, {}, {'123': 123}, {}]" +} + +fn test_multi_fixed_array_init() { + a := [3][3]int{} + assert '$a' == '[[0, 0, 0], [0, 0, 0], [0, 0, 0]]' +} + +struct Numbers { + odds []int + evens []int +} + +fn test_array_of_multi_filter() { + arr := [1, 2, 3, 4, 5] + nums := Numbers{ + odds: arr.filter(it % 2 == 1) + evens: arr.filter(it % 2 == 0) + } + println(nums) + assert nums.odds == [1, 3, 5] + assert nums.evens == [2, 4] +} + +fn test_array_of_multi_map() { + arr := [1, 3, 5] + nums := Numbers{ + odds: arr.map(it + 2) + evens: arr.map(it * 2) + } + println(nums) + assert nums.odds == [3, 5, 7] + assert nums.evens == [2, 6, 10] +} + +fn test_multi_fixed_array_with_default_init() { + a := [3][3]int{init: [3]int{init: 10}} + println(a) + assert a == [[10, 10, 10]!, [10, 10, 10]!, [10, 10, 10]!]! +} + +struct Abc { +mut: + x i64 + y i64 + z i64 +} + +fn test_clone_of_same_elem_size_array() { + mut arr := []Abc{} + arr << Abc{1, 2, 3} + arr << Abc{2, 3, 4} + arr2 := arr.clone() + println(arr2) + assert arr2 == [Abc{1, 2, 3}, Abc{2, 3, 4}] +} + +pub fn example(mut arr []T) []T { + return arr.clone() +} + +fn test_generic_mutable_arrays() { + mut arr := [1, 2, 3] + assert example(mut arr) == [1, 2, 3] +} diff --git a/v_windows/v/vlib/builtin/builtin.c.v b/v_windows/v/vlib/builtin/builtin.c.v new file mode 100644 index 0000000..0379c83 --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin.c.v @@ -0,0 +1,547 @@ +module builtin + +type FnExitCb = fn () + +fn C.atexit(f FnExitCb) int +fn C.strerror(int) &char + +[noreturn] +fn vhalt() { + for {} +} + +// exit terminates execution immediately and returns exit `code` to the shell. +[noreturn] +pub fn exit(code int) { + C.exit(code) +} + +fn vcommithash() string { + return unsafe { tos5(&char(C.V_CURRENT_COMMIT_HASH)) } +} + +// panic_debug private function that V uses for panics, -cg/-g is passed +// recent versions of tcc print nicer backtraces automatically +// NB: the duplication here is because tcc_backtrace should be called directly +// inside the panic functions. +[noreturn] +fn panic_debug(line_no int, file string, mod string, fn_name string, s string) { + // NB: the order here is important for a stabler test output + // module is less likely to change than function, etc... + // During edits, the line number will change most frequently, + // so it is last + $if freestanding { + bare_panic(s) + } $else { + eprintln('================ V panic ================') + eprintln(' module: $mod') + eprintln(' function: ${fn_name}()') + eprintln(' message: $s') + eprintln(' file: $file:$line_no') + eprintln(' v hash: $vcommithash()') + eprintln('=========================================') + $if exit_after_panic_message ? { + C.exit(1) + } $else $if no_backtrace ? { + C.exit(1) + } $else { + $if tinyc { + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } $else { + C.tcc_backtrace(c'Backtrace') + } + C.exit(1) + } + print_backtrace_skipping_top_frames(1) + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } + C.exit(1) + } + } + vhalt() +} + +[noreturn] +pub fn panic_optional_not_set(s string) { + panic('optional not set ($s)') +} + +// panic prints a nice error message, then exits the process with exit code of 1. +// It also shows a backtrace on most platforms. +[noreturn] +pub fn panic(s string) { + $if freestanding { + bare_panic(s) + } $else { + eprint('V panic: ') + eprintln(s) + eprintln('v hash: $vcommithash()') + $if exit_after_panic_message ? { + C.exit(1) + } $else $if no_backtrace ? { + C.exit(1) + } $else { + $if tinyc { + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } $else { + C.tcc_backtrace(c'Backtrace') + } + C.exit(1) + } + print_backtrace_skipping_top_frames(1) + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } + C.exit(1) + } + } + vhalt() +} + +// return a C-API error message matching to `errnum` +pub fn c_error_number_str(errnum int) string { + mut err_msg := '' + $if freestanding { + err_msg = 'error $errnum' + } $else { + $if !vinix { + c_msg := C.strerror(errnum) + err_msg = string{ + str: &byte(c_msg) + len: unsafe { C.strlen(c_msg) } + is_lit: 1 + } + } + } + return err_msg +} + +// panic with a C-API error message matching `errnum` +[noreturn] +pub fn panic_error_number(basestr string, errnum int) { + panic(basestr + c_error_number_str(errnum)) +} + +// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed. +pub fn eprintln(s string) { + if s.str == 0 { + eprintln('eprintln(NIL)') + return + } + $if freestanding { + // flushing is only a thing with C.FILE from stdio.h, not on the syscall level + bare_eprint(s.str, u64(s.len)) + bare_eprint(c'\n', 1) + } $else $if ios { + C.WrappedNSLog(s.str) + } $else { + C.fflush(C.stdout) + C.fflush(C.stderr) + // eprintln is used in panics, so it should not fail at all + $if android { + C.fprintf(C.stderr, c'%.*s\n', s.len, s.str) + } + _writeln_to_fd(2, s) + C.fflush(C.stderr) + } +} + +// eprint prints a message to stderr. Both stderr and stdout are flushed. +pub fn eprint(s string) { + if s.str == 0 { + eprint('eprint(NIL)') + return + } + $if freestanding { + // flushing is only a thing with C.FILE from stdio.h, not on the syscall level + bare_eprint(s.str, u64(s.len)) + } $else $if ios { + // TODO: Implement a buffer as NSLog doesn't have a "print" + C.WrappedNSLog(s.str) + } $else { + C.fflush(C.stdout) + C.fflush(C.stderr) + $if android { + C.fprintf(C.stderr, c'%.*s', s.len, s.str) + } + _write_buf_to_fd(2, s.str, s.len) + C.fflush(C.stderr) + } +} + +// print prints a message to stdout. Unlike `println` stdout is not automatically flushed. +// A call to `flush()` will flush the output buffer to stdout. +[manualfree] +pub fn print(s string) { + $if android { + C.fprintf(C.stdout, c'%.*s', s.len, s.str) // logcat + } + // no else if for android termux support + $if ios { + // TODO: Implement a buffer as NSLog doesn't have a "print" + C.WrappedNSLog(s.str) + } $else $if freestanding { + bare_print(s.str, u64(s.len)) + } $else { + _write_buf_to_fd(1, s.str, s.len) + } +} + +// println prints a message with a line end, to stdout. stdout is flushed. +[manualfree] +pub fn println(s string) { + if s.str == 0 { + println('println(NIL)') + return + } + $if android { + C.fprintf(C.stdout, c'%.*s\n', s.len, s.str) // logcat + return + } + // no else if for android termux support + $if ios { + C.WrappedNSLog(s.str) + return + } $else $if freestanding { + bare_print(s.str, u64(s.len)) + bare_print(c'\n', 1) + return + } $else { + _writeln_to_fd(1, s) + } +} + +[manualfree] +fn _writeln_to_fd(fd int, s string) { + unsafe { + buf_len := s.len + 1 // space for \n + mut buf := malloc(buf_len) + defer { + free(buf) + } + C.memcpy(buf, s.str, s.len) + buf[s.len] = `\n` + _write_buf_to_fd(fd, buf, buf_len) + } +} + +[manualfree] +fn _write_buf_to_fd(fd int, buf &byte, buf_len int) { + if buf_len <= 0 { + return + } + unsafe { + mut ptr := buf + mut remaining_bytes := buf_len + for remaining_bytes > 0 { + x := C.write(fd, ptr, remaining_bytes) + ptr += x + remaining_bytes -= x + } + } +} + +__global total_m = i64(0) +// malloc dynamically allocates a `n` bytes block of memory on the heap. +// malloc returns a `byteptr` pointing to the memory address of the allocated space. +// unlike the `calloc` family of functions - malloc will not zero the memory block. +[unsafe] +pub fn malloc(n int) &byte { + if n <= 0 { + panic('> V malloc(<=0)') + } + $if vplayground ? { + if n > 10000 { + panic('allocating more than 10 KB at once is not allowed in the V playground') + } + if total_m > 50 * 1024 * 1024 { + panic('allocating more than 50 MB is not allowed in the V playground') + } + } + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m) + // print_backtrace() + } + mut res := &byte(0) + $if prealloc { + return unsafe { prealloc_malloc(n) } + } $else $if gcboehm ? { + unsafe { + res = C.GC_MALLOC(n) + } + } $else $if freestanding { + mut e := Errno{} + res, e = mm_alloc(u64(n)) + if e != .enoerror { + eprint('malloc() failed: ') + eprintln(e.str()) + panic('malloc() failed') + } + } $else { + res = unsafe { C.malloc(n) } + } + if res == 0 { + panic('malloc($n) failed') + } + $if debug_malloc ? { + // Fill in the memory with something != 0, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x88, n) } + } + return res +} + +[unsafe] +pub fn malloc_noscan(n int) &byte { + if n <= 0 { + panic('> V malloc(<=0)') + } + $if vplayground ? { + if n > 10000 { + panic('allocating more than 10 KB at once is not allowed in the V playground') + } + if total_m > 50 * 1024 * 1024 { + panic('allocating more than 50 MB is not allowed in the V playground') + } + } + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m) + // print_backtrace() + } + mut res := &byte(0) + $if prealloc { + return unsafe { prealloc_malloc(n) } + } $else $if gcboehm ? { + $if gcboehm_opt ? { + unsafe { + res = C.GC_MALLOC_ATOMIC(n) + } + } $else { + unsafe { + res = C.GC_MALLOC(n) + } + } + } $else $if freestanding { + mut e := Errno{} + res, e = mm_alloc(u64(n)) + if e != .enoerror { + eprint('malloc() failed: ') + eprintln(e.str()) + panic('malloc() failed') + } + } $else { + res = unsafe { C.malloc(n) } + } + if res == 0 { + panic('malloc($n) failed') + } + $if debug_malloc ? { + // Fill in the memory with something != 0, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x88, n) } + } + return res +} + +// v_realloc resizes the memory block `b` with `n` bytes. +// The `b byteptr` must be a pointer to an existing memory block +// previously allocated with `malloc`, `v_calloc` or `vcalloc`. +// Please, see also realloc_data, and use it instead if possible. +[unsafe] +pub fn v_realloc(b &byte, n int) &byte { + $if trace_realloc ? { + C.fprintf(C.stderr, c'v_realloc %6d\n', n) + } + mut new_ptr := &byte(0) + $if prealloc { + unsafe { + new_ptr = malloc(n) + C.memcpy(new_ptr, b, n) + } + return new_ptr + } $else $if gcboehm ? { + new_ptr = unsafe { C.GC_REALLOC(b, n) } + } $else { + new_ptr = unsafe { C.realloc(b, n) } + } + if new_ptr == 0 { + panic('realloc($n) failed') + } + return new_ptr +} + +// realloc_data resizes the memory block pointed by `old_data` to `new_size` +// bytes. `old_data` must be a pointer to an existing memory block, previously +// allocated with `malloc`, `v_calloc` or `vcalloc`, of size `old_data`. +// realloc_data returns a pointer to the new location of the block. +// NB: if you know the old data size, it is preferable to call `realloc_data`, +// instead of `v_realloc`, at least during development, because `realloc_data` +// can make debugging easier, when you compile your program with +// `-d debug_realloc`. +[unsafe] +pub fn realloc_data(old_data &byte, old_size int, new_size int) &byte { + $if trace_realloc ? { + C.fprintf(C.stderr, c'realloc_data old_size: %6d new_size: %6d\n', old_size, new_size) + } + $if prealloc { + return unsafe { prealloc_realloc(old_data, old_size, new_size) } + } + $if debug_realloc ? { + // NB: this is slower, but helps debugging memory problems. + // The main idea is to always force reallocating: + // 1) allocate a new memory block + // 2) copy the old to the new + // 3) fill the old with 0x57 (`W`) + // 4) free the old block + // => if there is still a pointer to the old block somewhere + // it will point to memory that is now filled with 0x57. + unsafe { + new_ptr := malloc(new_size) + min_size := if old_size < new_size { old_size } else { new_size } + C.memcpy(new_ptr, old_data, min_size) + C.memset(old_data, 0x57, old_size) + free(old_data) + return new_ptr + } + } + mut nptr := &byte(0) + $if gcboehm ? { + nptr = unsafe { C.GC_REALLOC(old_data, new_size) } + } $else { + nptr = unsafe { C.realloc(old_data, new_size) } + } + if nptr == 0 { + panic('realloc_data($old_data, $old_size, $new_size) failed') + } + return nptr +} + +// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. +// vcalloc returns a `byteptr` pointing to the memory address of the allocated space. +// Unlike `v_calloc` vcalloc checks for negative values given in `n`. +pub fn vcalloc(n int) &byte { + if n < 0 { + panic('calloc(<0)') + } else if n == 0 { + return &byte(0) + } + $if trace_vcalloc ? { + total_m += n + C.fprintf(C.stderr, c'vcalloc %6d total %10d\n', n, total_m) + } + $if prealloc { + return unsafe { prealloc_calloc(n) } + } $else $if gcboehm ? { + return unsafe { &byte(C.GC_MALLOC(n)) } + } $else { + return unsafe { C.calloc(1, n) } + } +} + +// special versions of the above that allocate memory which is not scanned +// for pointers (but is collected) when the Boehm garbage collection is used +pub fn vcalloc_noscan(n int) &byte { + $if trace_vcalloc ? { + total_m += n + C.fprintf(C.stderr, c'vcalloc_noscan %6d total %10d\n', n, total_m) + } + $if prealloc { + return unsafe { prealloc_calloc(n) } + } $else $if gcboehm ? { + $if vplayground ? { + if n > 10000 { + panic('allocating more than 10 KB is not allowed in the playground') + } + } + if n < 0 { + panic('calloc(<0)') + } + return $if gcboehm_opt ? { + unsafe { &byte(C.memset(C.GC_MALLOC_ATOMIC(n), 0, n)) } + } $else { + unsafe { &byte(C.GC_MALLOC(n)) } + } + } $else { + return unsafe { vcalloc(n) } + } +} + +// free allows for manually freeing memory allocated at the address `ptr`. +[unsafe] +pub fn free(ptr voidptr) { + $if prealloc { + return + } $else $if gcboehm ? { + // It is generally better to leave it to Boehm's gc to free things. + // Calling C.GC_FREE(ptr) was tried initially, but does not work + // well with programs that do manual management themselves. + // + // The exception is doing leak detection for manual memory management: + $if gcboehm_leak ? { + unsafe { C.GC_FREE(ptr) } + } + } $else { + C.free(ptr) + } +} + +// memdup dynamically allocates a `sz` bytes block of memory on the heap +// memdup then copies the contents of `src` into the allocated space and +// returns a pointer to the newly allocated space. +[unsafe] +pub fn memdup(src voidptr, sz int) voidptr { + if sz == 0 { + return vcalloc(1) + } + unsafe { + mem := malloc(sz) + return C.memcpy(mem, src, sz) + } +} + +[unsafe] +pub fn memdup_noscan(src voidptr, sz int) voidptr { + if sz == 0 { + return vcalloc_noscan(1) + } + unsafe { + mem := vcalloc_noscan(sz) + return C.memcpy(mem, src, sz) + } +} + +[inline] +fn v_fixed_index(i int, len int) int { + $if !no_bounds_checking ? { + if i < 0 || i >= len { + s := 'fixed array index out of range (index: $i, len: $len)' + panic(s) + } + } + return i +} + +// print_backtrace shows a backtrace of the current call stack on stdout +pub fn print_backtrace() { + // At the time of backtrace_symbols_fd call, the C stack would look something like this: + // * print_backtrace_skipping_top_frames + // * print_backtrace itself + // * the rest of the backtrace frames + // => top 2 frames should be skipped, since they will not be informative to the developer + $if !no_backtrace ? { + $if freestanding { + println(bare_backtrace()) + } $else { + $if tinyc { + C.tcc_backtrace(c'Backtrace') + } $else { + print_backtrace_skipping_top_frames(2) + } + } + } +} diff --git a/v_windows/v/vlib/builtin/builtin.v b/v_windows/v/vlib/builtin/builtin.v new file mode 100644 index 0000000..188e28d --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin.v @@ -0,0 +1,128 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +// isnil returns true if an object is nil (only for C objects). +[inline] +pub fn isnil(v voidptr) bool { + return v == 0 +} + +/* +fn on_panic(f fn(int)int) { + // TODO +} +*/ + +struct VCastTypeIndexName { + tindex int + tname string +} + +// will be filled in cgen +__global as_cast_type_indexes []VCastTypeIndexName + +fn __as_cast(obj voidptr, obj_type int, expected_type int) voidptr { + if obj_type != expected_type { + mut obj_name := as_cast_type_indexes[0].tname.clone() + mut expected_name := as_cast_type_indexes[0].tname.clone() + for x in as_cast_type_indexes { + if x.tindex == obj_type { + obj_name = x.tname.clone() + } + if x.tindex == expected_type { + expected_name = x.tname.clone() + } + } + panic('as cast: cannot cast `$obj_name` to `$expected_name`') + } + return obj +} + +// VAssertMetaInfo is used during assertions. An instance of it +// is filled in by compile time generated code, when an assertion fails. +pub struct VAssertMetaInfo { +pub: + fpath string // the source file path of the assertion + line_nr int // the line number of the assertion + fn_name string // the function name in which the assertion is + src string // the actual source line of the assertion + op string // the operation of the assertion, i.e. '==', '<', 'call', etc ... + llabel string // the left side of the infix expressions as source + rlabel string // the right side of the infix expressions as source + lvalue string // the stringified *actual value* of the left side of a failed assertion + rvalue string // the stringified *actual value* of the right side of a failed assertion +} + +// free is used to free the memory occupied by the assertion meta data. +// It is called by cb_assertion_failed, and cb_assertion_ok in the preludes, +// once they are done with reporting/formatting the meta data. +[manualfree; unsafe] +pub fn (ami &VAssertMetaInfo) free() { + unsafe { + ami.fpath.free() + ami.fn_name.free() + ami.src.free() + ami.op.free() + ami.llabel.free() + ami.rlabel.free() + ami.lvalue.free() + ami.rvalue.free() + } +} + +fn __print_assert_failure(i &VAssertMetaInfo) { + eprintln('$i.fpath:${i.line_nr + 1}: FAIL: fn $i.fn_name: assert $i.src') + if i.op.len > 0 && i.op != 'call' { + eprintln(' left value: $i.llabel = $i.lvalue') + if i.rlabel == i.rvalue { + eprintln(' right value: $i.rlabel') + } else { + eprintln(' right value: $i.rlabel = $i.rvalue') + } + } +} + +// MethodArgs holds type information for function and/or method arguments. +pub struct MethodArgs { +pub: + typ int + name string +} + +// FunctionData holds information about a parsed function. +pub struct FunctionData { +pub: + name string + attrs []string + args []MethodArgs + return_type int + typ int +} + +// FieldData holds information about a field. Fields reside on structs. +pub struct FieldData { +pub: + name string + attrs []string + is_pub bool + is_mut bool + is_shared bool + typ int +} + +pub enum AttributeKind { + plain // [name] + string // ['name'] + number // [123] + comptime_define // [if name] +} + +pub struct StructAttribute { +pub: + name string + has_arg bool + arg string + kind AttributeKind +} diff --git a/v_windows/v/vlib/builtin/builtin_d_gcboehm.c.v b/v_windows/v/vlib/builtin/builtin_d_gcboehm.c.v new file mode 100644 index 0000000..befec1d --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin_d_gcboehm.c.v @@ -0,0 +1,91 @@ +module builtin + +#flag -DGC_THREADS=1 + +$if static_boehm ? { + $if macos { + #flag -I$first_existing("/opt/homebrew/include", "/usr/local/include") + #flag $first_existing("/opt/homebrew/lib/libgc.a", "/usr/local/lib/libgc.a") + } $else $if linux { + #flag -l:libgc.a + } $else $if openbsd { + #flag -I/usr/local/include + #flag /usr/local/lib/libgc.a + #flag -lpthread + } $else $if windows { + #flag -DGC_NOT_DLL=1 + $if tinyc { + #flag -I@VEXEROOT/thirdparty/libgc/include + #flag -L@VEXEROOT/thirdparty/libgc + #flag -lgc + } $else { + #flag -DGC_BUILTIN_ATOMIC=1 + #flag -I@VEXEROOT/thirdparty/libgc + #flag @VEXEROOT/thirdparty/libgc/gc.o + } + } $else { + #flag -lgc + } +} $else { + $if macos { + #pkgconfig bdw-gc + } $else $if openbsd || freebsd { + #flag -I/usr/local/include + #flag -L/usr/local/lib + } + $if windows { + $if tinyc { + #flag -I@VEXEROOT/thirdparty/libgc/include + #flag -L@VEXEROOT/thirdparty/libgc + #flag -lgc + } $else { + #flag -DGC_BUILTIN_ATOMIC=1 + #flag -I@VEXEROOT/thirdparty/libgc + #flag @VEXEROOT/thirdparty/libgc/gc.o + } + } $else { + #flag -lgc + } +} + +$if gcboehm_leak ? { + #flag -DGC_DEBUG=1 +} + +#include + +// replacements for `malloc()/calloc()`, `realloc()` and `free()` +// for use with Boehm-GC +// Do not use them manually. They are automatically chosen when +// compiled with `-gc boehm` or `-gc boehm_leak`. +fn C.GC_MALLOC(n size_t) voidptr + +fn C.GC_MALLOC_ATOMIC(n size_t) voidptr + +fn C.GC_MALLOC_UNCOLLECTABLE(n size_t) voidptr + +fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr + +fn C.GC_FREE(ptr voidptr) + +// explicitely perform garbage collection now! Garbage collections +// are done automatically when needed, so this function is hardly needed +fn C.GC_gcollect() + +// functions to temporarily suspend/resume garbage collection +fn C.GC_disable() + +fn C.GC_enable() + +// returns non-zero if GC is disabled +fn C.GC_is_disabled() int + +// protect memory block from being freed before this call +fn C.GC_reachable_here(voidptr) + +// for leak detection it is advisable to do explicit garbage collections +pub fn gc_check_leaks() { + $if gcboehm_leak ? { + C.GC_gcollect() + } +} diff --git a/v_windows/v/vlib/builtin/builtin_ios.c.v b/v_windows/v/vlib/builtin/builtin_ios.c.v new file mode 100644 index 0000000..f745b4d --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin_ios.c.v @@ -0,0 +1,6 @@ +module builtin + +// TODO: Remove this later, added to make sure v self works +$if ios { + #include "@VEXEROOT/thirdparty/ios/ios.m" +} diff --git a/v_windows/v/vlib/builtin/builtin_nix.c.v b/v_windows/v/vlib/builtin/builtin_nix.c.v new file mode 100644 index 0000000..0d79af4 --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin_nix.c.v @@ -0,0 +1,144 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +// pub fn vsyscall(id int +// + +/* +pub const ( + sys_write = 1 + sys_mkdir = 83 +) +const ( + stdin_value = 0 + stdout_value = 1 + stderr_value = 2 +) +*/ + +fn builtin_init() { + // Do nothing +} + +fn print_backtrace_skipping_top_frames(xskipframes int) bool { + $if no_backtrace ? { + return false + } $else { + skipframes := xskipframes + 2 + $if macos || freebsd || openbsd || netbsd { + return print_backtrace_skipping_top_frames_bsd(skipframes) + } $else $if linux { + return print_backtrace_skipping_top_frames_linux(skipframes) + } $else { + println('print_backtrace_skipping_top_frames is not implemented. skipframes: $skipframes') + } + } + return false +} + +// the functions below are not called outside this file, +// so there is no need to have their twins in builtin_windows.v +fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool { + $if no_backtrace ? { + return false + } $else { + $if macos || freebsd || openbsd || netbsd { + buffer := [100]voidptr{} + nr_ptrs := C.backtrace(&buffer[0], 100) + if nr_ptrs < 2 { + eprintln('C.backtrace returned less than 2 frames') + return false + } + C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2) + } + return true + } +} + +fn C.tcc_backtrace(fmt &char) int +fn print_backtrace_skipping_top_frames_linux(skipframes int) bool { + $if android { + eprintln('On Android no backtrace is available.') + return false + } + $if !glibc { + eprintln('backtrace_symbols is missing => printing backtraces is not available.') + eprintln('Some libc implementations like musl simply do not provide it.') + return false + } + $if no_backtrace ? { + return false + } $else { + $if linux && !freestanding { + $if tinyc { + C.tcc_backtrace(c'Backtrace') + return false + } + buffer := [100]voidptr{} + nr_ptrs := C.backtrace(&buffer[0], 100) + if nr_ptrs < 2 { + eprintln('C.backtrace returned less than 2 frames') + return false + } + nr_actual_frames := nr_ptrs - skipframes + mut sframes := []string{} + //////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames) + csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames) + for i in 0 .. nr_actual_frames { + sframes << unsafe { tos2(&byte(csymbols[i])) } + } + for sframe in sframes { + executable := sframe.all_before('(') + addr := sframe.all_after('[').all_before(']') + beforeaddr := sframe.all_before('[') + cmd := 'addr2line -e $executable $addr' + // taken from os, to avoid depending on the os module inside builtin.v + f := C.popen(&char(cmd.str), c'r') + if isnil(f) { + eprintln(sframe) + continue + } + buf := [1000]byte{} + mut output := '' + unsafe { + bp := &buf[0] + for C.fgets(&char(bp), 1000, f) != 0 { + output += tos(bp, vstrlen(bp)) + } + } + output = output.trim_space() + ':' + if C.pclose(f) != 0 { + eprintln(sframe) + continue + } + if output in ['??:0:', '??:?:'] { + output = '' + } + // See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators + // NB: it is shortened here to just d. , just so that it fits, and so + // that the common error file:lineno: line format is enforced. + output = output.replace(' (discriminator', ': (d.') + eprintln('${output:-55s} | ${addr:14s} | $beforeaddr') + } + } + } + return true +} + +fn break_if_debugger_attached() { + unsafe { + mut ptr := &voidptr(0) + *ptr = voidptr(0) + _ = ptr + } +} + +// These functions are Windows specific - provide dummys for *nix +pub fn winapi_lasterr_str() string { + return '' +} + +[noreturn] +pub fn panic_lasterr() {} diff --git a/v_windows/v/vlib/builtin/builtin_notd_gcboehm.c.v b/v_windows/v/vlib/builtin/builtin_notd_gcboehm.c.v new file mode 100644 index 0000000..4479072 --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin_notd_gcboehm.c.v @@ -0,0 +1,20 @@ +module builtin + +// Just define the C functions, so that V does not error because of the missing definitions. + +// NB: they will NOT be used, since calls to them are wrapped with `$if gcboehm ? { }` + +fn C.GC_MALLOC(n size_t) voidptr + +fn C.GC_MALLOC_ATOMIC(n size_t) voidptr + +fn C.GC_MALLOC_UNCOLLECTABLE(n size_t) voidptr + +fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr + +fn C.GC_FREE(ptr voidptr) + +// provide an empty function when manual memory management is used +// to simplify leak detection +// +pub fn gc_check_leaks() {} diff --git a/v_windows/v/vlib/builtin/builtin_windows.c.v b/v_windows/v/vlib/builtin/builtin_windows.c.v new file mode 100644 index 0000000..97592cf --- /dev/null +++ b/v_windows/v/vlib/builtin/builtin_windows.c.v @@ -0,0 +1,304 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +// dbghelp.h is already included in cheaders.v +#flag windows -l dbghelp +// SymbolInfo is used by print_backtrace_skipping_top_frames_msvc +pub struct SymbolInfo { +pub mut: + f_size_of_struct u32 // must be 88 to be recognised by SymFromAddr + f_type_index u32 // Type Index of symbol + f_reserved [2]u64 + f_index u32 + f_size u32 + f_mod_base u64 // Base Address of module comtaining this symbol + f_flags u32 + f_value u64 // Value of symbol, ValuePresent should be 1 + f_address u64 // Address of symbol including base address of module + f_register u32 // register holding value or pointer to value + f_scope u32 // scope of the symbol + f_tag u32 // pdb classification + f_name_len u32 // Actual length of name + f_max_name_len u32 // must be manually set + f_name byte // must be calloc(f_max_name_len) +} + +pub struct SymbolInfoContainer { +pub mut: + syminfo SymbolInfo + f_name_rest [254]char +} + +pub struct Line64 { +pub mut: + f_size_of_struct u32 + f_key voidptr + f_line_number u32 + f_file_name &byte + f_address u64 +} + +// returns the current options mask +fn C.SymSetOptions(symoptions u32) u32 + +// returns handle +fn C.GetCurrentProcess() voidptr + +fn C.SymInitialize(h_process voidptr, p_user_search_path &byte, b_invade_process int) int + +fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16 + +fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) int + +fn C.SymGetLineFromAddr64(h_process voidptr, address u64, p_displacement voidptr, p_line &Line64) int + +// Ref - https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions +const ( + symopt_undname = 0x00000002 + symopt_deferred_loads = 0x00000004 + symopt_no_cpp = 0x00000008 + symopt_load_lines = 0x00000010 + symopt_include_32bit_modules = 0x00002000 + symopt_allow_zero_address = 0x01000000 + symopt_debug = 0x80000000 +) + +// g_original_codepage - used to restore the original windows console code page when exiting +__global ( + g_original_codepage = u32(0) +) + +// utf8 to stdout needs C.SetConsoleOutputCP(C.CP_UTF8) +fn C.GetConsoleOutputCP() u32 + +fn C.SetConsoleOutputCP(wCodePageID u32) bool + +fn restore_codepage() { + C.SetConsoleOutputCP(g_original_codepage) +} + +fn is_terminal(fd int) int { + mut mode := u32(0) + osfh := voidptr(C._get_osfhandle(fd)) + C.GetConsoleMode(osfh, voidptr(&mode)) + return int(mode) +} + +fn builtin_init() { + g_original_codepage = C.GetConsoleOutputCP() + C.SetConsoleOutputCP(C.CP_UTF8) + C.atexit(restore_codepage) + if is_terminal(1) > 0 { + C.SetConsoleMode(C.GetStdHandle(C.STD_OUTPUT_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing + C.SetConsoleMode(C.GetStdHandle(C.STD_ERROR_HANDLE), C.ENABLE_PROCESSED_OUTPUT | C.ENABLE_WRAP_AT_EOL_OUTPUT | 0x0004) // enable_virtual_terminal_processing + unsafe { + C.setbuf(C.stdout, 0) + C.setbuf(C.stderr, 0) + } + } + $if !no_backtrace ? { + add_unhandled_exception_handler() + } +} + +fn print_backtrace_skipping_top_frames(skipframes int) bool { + $if msvc { + return print_backtrace_skipping_top_frames_msvc(skipframes) + } + $if tinyc { + return print_backtrace_skipping_top_frames_tcc(skipframes) + } + $if mingw { + return print_backtrace_skipping_top_frames_mingw(skipframes) + } + eprintln('print_backtrace_skipping_top_frames is not implemented') + return false +} + +fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool { + $if msvc { + mut offset := u64(0) + backtraces := [100]voidptr{} + sic := SymbolInfoContainer{} + mut si := &sic.syminfo + si.f_size_of_struct = sizeof(SymbolInfo) // Note: C.SYMBOL_INFO is 88 + si.f_max_name_len = sizeof(SymbolInfoContainer) - sizeof(SymbolInfo) - 1 + fname := &char(&si.f_name) + mut sline64 := Line64{ + f_file_name: &byte(0) + } + sline64.f_size_of_struct = sizeof(Line64) + + handle := C.GetCurrentProcess() + defer { + C.SymCleanup(handle) + } + + C.SymSetOptions(symopt_debug | symopt_load_lines | symopt_undname) + + syminitok := C.SymInitialize(handle, 0, 1) + if syminitok != 1 { + eprintln('Failed getting process: Aborting backtrace.\n') + return false + } + + frames := int(C.CaptureStackBackTrace(skipframes + 1, 100, &backtraces[0], 0)) + if frames < 2 { + eprintln('C.CaptureStackBackTrace returned less than 2 frames') + return false + } + for i in 0 .. frames { + frame_addr := backtraces[i] + if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 { + nframe := frames - i - 1 + mut lineinfo := '' + if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 { + file_name := unsafe { tos3(sline64.f_file_name) } + lnumber := sline64.f_line_number + lineinfo = '$file_name:$lnumber' + } else { + addr: + lineinfo = '?? : address = 0x${(&frame_addr):x}' + } + sfunc := unsafe { tos3(fname) } + eprintln('${nframe:-2d}: ${sfunc:-25s} $lineinfo') + } else { + // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes + cerr := int(C.GetLastError()) + if cerr == 87 { + eprintln('SymFromAddr failure: $cerr = The parameter is incorrect)') + } else if cerr == 487 { + // probably caused because the .pdb isn't in the executable folder + eprintln('SymFromAddr failure: $cerr = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)') + } else { + eprintln('SymFromAddr failure: $cerr (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)') + } + } + } + return true + } $else { + eprintln('print_backtrace_skipping_top_frames_msvc must be called only when the compiler is msvc') + return false + } +} + +fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool { + eprintln('print_backtrace_skipping_top_frames_mingw is not implemented') + return false +} + +fn C.tcc_backtrace(fmt &char) int + +fn print_backtrace_skipping_top_frames_tcc(skipframes int) bool { + $if tinyc { + $if no_backtrace ? { + eprintln('backtraces are disabled') + return false + } $else { + C.tcc_backtrace(c'Backtrace') + return true + } + } $else { + eprintln('print_backtrace_skipping_top_frames_tcc must be called only when the compiler is tcc') + return false + } + // Not reachable, but it looks like it's not detectable by V + return false +} + +// TODO copypaste from os +// we want to be able to use this here without having to `import os` +struct ExceptionRecord { +pub: + // status_ constants + code u32 + flags u32 + record &ExceptionRecord + address voidptr + param_count u32 + // params []voidptr +} + +struct ContextRecord { + // TODO +} + +struct ExceptionPointers { +pub: + exception_record &ExceptionRecord + context_record &ContextRecord +} + +type VectoredExceptionHandler = fn (&ExceptionPointers) int + +fn C.AddVectoredExceptionHandler(int, C.PVECTORED_EXCEPTION_HANDLER) + +fn add_vectored_exception_handler(handler VectoredExceptionHandler) { + C.AddVectoredExceptionHandler(1, C.PVECTORED_EXCEPTION_HANDLER(handler)) +} + +[windows_stdcall] +fn unhandled_exception_handler(e &ExceptionPointers) int { + match e.exception_record.code { + // These are 'used' by the backtrace printer + // so we dont want to catch them... + 0x4001000A, 0x40010006 { + return 0 + } + else { + println('Unhandled Exception 0x${e.exception_record.code:X}') + print_backtrace_skipping_top_frames(5) + } + } + + return 0 +} + +fn add_unhandled_exception_handler() { + add_vectored_exception_handler(VectoredExceptionHandler(voidptr(unhandled_exception_handler))) +} + +fn C.IsDebuggerPresent() bool + +fn C.__debugbreak() + +fn break_if_debugger_attached() { + $if tinyc { + unsafe { + mut ptr := &voidptr(0) + *ptr = voidptr(0) + _ = ptr + } + } $else { + if C.IsDebuggerPresent() { + C.__debugbreak() + } + } +} + +// return an error message generated from WinAPI's `LastError` +pub fn winapi_lasterr_str() string { + err_msg_id := C.GetLastError() + if err_msg_id == 8 { + // handle this case special since `FormatMessage()` might not work anymore + return 'insufficient memory' + } + mut msgbuf := &u16(0) + res := C.FormatMessage(C.FORMAT_MESSAGE_ALLOCATE_BUFFER | C.FORMAT_MESSAGE_FROM_SYSTEM | C.FORMAT_MESSAGE_IGNORE_INSERTS, + C.NULL, err_msg_id, C.MAKELANGID(C.LANG_NEUTRAL, C.SUBLANG_DEFAULT), &msgbuf, + 0, C.NULL) + err_msg := if res == 0 { + 'Win-API error $err_msg_id' + } else { + unsafe { string_from_wide(msgbuf) } + } + return err_msg +} + +// panic with an error message generated from WinAPI's `LastError` +[noreturn] +pub fn panic_lasterr(base string) { + panic(base + winapi_lasterr_str()) +} diff --git a/v_windows/v/vlib/builtin/byte_test.v b/v_windows/v/vlib/builtin/byte_test.v new file mode 100644 index 0000000..8b38eb8 --- /dev/null +++ b/v_windows/v/vlib/builtin/byte_test.v @@ -0,0 +1,22 @@ +fn test_clone() { + a := [byte(0), 1, 2] + b := a.clone() + assert b.len == 3 + assert b[0] == 0 + assert b[1] == 1 + assert b[2] == 2 + assert b[1].str() == '1' + xx := byte(35) + assert xx.str() == '35' + assert xx.ascii_str() == '#' + println(typeof(`A`).name) + assert typeof(`A`).name == 'rune' + x := rune(`A`) + assert x.str() == 'A' + assert typeof(x).name == 'rune' + // + y := `Z` + assert typeof(y).name == 'rune' + assert y.str() == 'Z' + // assert b[1].str() == '1' TODO +} diff --git a/v_windows/v/vlib/builtin/cfns.c.v b/v_windows/v/vlib/builtin/cfns.c.v new file mode 100644 index 0000000..a45686d --- /dev/null +++ b/v_windows/v/vlib/builtin/cfns.c.v @@ -0,0 +1,462 @@ +module builtin + +// +fn C.memcpy(dest voidptr, const_src voidptr, n size_t) voidptr + +fn C.memcmp(const_s1 voidptr, const_s2 voidptr, n size_t) int + +fn C.memmove(dest voidptr, const_src voidptr, n size_t) voidptr + +fn C.memset(str voidptr, c int, n size_t) voidptr + +[trusted] +fn C.calloc(int, int) &byte + +fn C.malloc(int) &byte + +fn C.realloc(a &byte, b int) &byte + +fn C.free(ptr voidptr) + +[noreturn; trusted] +fn C.exit(code int) + +fn C.qsort(base voidptr, items size_t, item_size size_t, cb C.qsort_callback_func) + +fn C.sprintf(a ...voidptr) int + +fn C.strlen(s &char) int + +fn C.sscanf(&byte, &byte, ...&byte) int + +[trusted] +fn C.isdigit(c int) bool + +// stdio.h +fn C.popen(c &char, t &char) voidptr + +// +fn C.backtrace(a &voidptr, size int) int + +fn C.backtrace_symbols(a &voidptr, size int) &&char + +fn C.backtrace_symbols_fd(a &voidptr, size int, fd int) + +// +pub fn proc_pidpath(int, voidptr, int) int + +fn C.realpath(&char, &char) &char + +// fn C.chmod(byteptr, mode_t) int +fn C.chmod(&char, u32) int + +fn C.printf(&char, ...voidptr) int + +fn C.puts(&char) int + +fn C.fputs(str &char, stream &C.FILE) int + +fn C.fflush(&C.FILE) int + +// TODO define args in these functions +fn C.fseek(stream &C.FILE, offset int, whence int) int + +fn C.fopen(filename &char, mode &char) &C.FILE + +fn C.fileno(&C.FILE) int + +fn C.fread(ptr voidptr, item_size size_t, items size_t, stream &C.FILE) size_t + +fn C.fwrite(ptr voidptr, item_size size_t, items size_t, stream &C.FILE) size_t + +fn C.fclose(stream &C.FILE) int + +fn C.pclose(stream &C.FILE) int + +// process execution, os.process: +[trusted] +fn C.getpid() int + +fn C.getuid() int + +fn C.geteuid() int + +fn C.system(cmd &char) int + +fn C.posix_spawn(child_pid &int, path &char, file_actions voidptr, attrp voidptr, argv &&char, envp &&char) int + +fn C.posix_spawnp(child_pid &int, exefile &char, file_actions voidptr, attrp voidptr, argv &&char, envp &&char) int + +fn C.execve(cmd_path &char, args voidptr, envs voidptr) int + +fn C.execvp(cmd_path &char, args &&char) int + +fn C._execve(cmd_path &char, args voidptr, envs voidptr) int + +fn C._execvp(cmd_path &char, args &&char) int + +[trusted] +fn C.fork() int + +fn C.wait(status &int) int + +fn C.waitpid(pid int, status &int, options int) int + +[trusted] +fn C.kill(pid int, sig int) int + +fn C.setenv(&char, &char, int) int + +fn C.unsetenv(&char) int + +fn C.access(path &char, amode int) int + +fn C.remove(filename &char) int + +fn C.rmdir(path &char) int + +fn C.chdir(path &char) int + +fn C.rewind(stream &C.FILE) int + +fn C.stat(&char, voidptr) int + +fn C.lstat(path &char, buf &C.stat) int + +fn C.rename(old_filename &char, new_filename &char) int + +fn C.fgets(str &char, n int, stream &C.FILE) int + +[trusted] +fn C.sigemptyset() int + +fn C.getcwd(buf &char, size size_t) &char + +[trusted] +fn C.mktime() int + +fn C.gettimeofday(tv &C.timeval, tz &C.timezone) int + +[trusted] +fn C.sleep(seconds u32) u32 + +// fn C.usleep(usec useconds_t) int +[trusted] +fn C.usleep(usec u32) int + +fn C.opendir(&char) voidptr + +fn C.closedir(dirp &C.DIR) int + +// fn C.mkdir(path &char, mode mode_t) int +fn C.mkdir(path &char, mode u32) int + +// C.rand returns a pseudorandom integer from 0 (inclusive) to C.RAND_MAX (exclusive) +[trusted] +fn C.rand() int + +// C.srand seeds the internal PRNG with the given value. +[trusted] +fn C.srand(seed u32) + +fn C.atof(str &char) f64 + +[trusted] +fn C.tolower(c int) int + +[trusted] +fn C.toupper(c int) int + +[trusted] +fn C.getchar() int + +[trusted] +fn C.strerror(int) &char + +fn C.snprintf(str &char, size size_t, format &char, opt ...voidptr) int + +fn C.fprintf(voidptr, &char, ...voidptr) + +[trusted] +fn C.WIFEXITED(status int) bool + +[trusted] +fn C.WEXITSTATUS(status int) int + +[trusted] +fn C.WIFSIGNALED(status int) bool + +[trusted] +fn C.WTERMSIG(status int) int + +[trusted] +fn C.isatty(fd int) int + +fn C.syscall(number int, va ...voidptr) int + +fn C.sysctl(name &int, namelen u32, oldp voidptr, oldlenp voidptr, newp voidptr, newlen size_t) int + +[trusted] +fn C._fileno(int) int + +fn C._get_osfhandle(fd int) C.intptr_t + +fn C.GetModuleFileName(hModule voidptr, lpFilename &u16, nSize u32) int + +fn C.GetModuleFileNameW(hModule voidptr, lpFilename &u16, nSize u32) u32 + +fn C.CreateFile(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) voidptr + +fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) u32 + +fn C.GetFinalPathNameByHandleW(hFile voidptr, lpFilePath &u16, nSize u32, dwFlags u32) int + +fn C.CreatePipe(hReadPipe &voidptr, hWritePipe &voidptr, lpPipeAttributes voidptr, nSize u32) bool + +fn C.SetHandleInformation(hObject voidptr, dwMask u32, dw_flags u32) bool + +fn C.ExpandEnvironmentStringsW(lpSrc &u16, lpDst &u16, nSize u32) u32 + +fn C.GetComputerNameW(&u16, &u32) bool + +fn C.GetUserNameW(&u16, &u32) bool + +[trusted] +fn C.SendMessageTimeout() u32 + +fn C.SendMessageTimeoutW(hWnd voidptr, msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) u32 + +fn C.CreateProcessW(lpApplicationName &u16, lpCommandLine &u16, lpProcessAttributes voidptr, lpThreadAttributes voidptr, bInheritHandles bool, dwCreationFlags u32, lpEnvironment voidptr, lpCurrentDirectory &u16, lpStartupInfo voidptr, lpProcessInformation voidptr) bool + +fn C.ReadFile(hFile voidptr, lpBuffer voidptr, nNumberOfBytesToRead u32, lpNumberOfBytesRead C.LPDWORD, lpOverlapped voidptr) bool + +fn C.GetFileAttributesW(lpFileName &byte) u32 + +fn C.RegQueryValueEx(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData &byte, lpcbData &u32) voidptr + +fn C.RegQueryValueExW(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData &byte, lpcbData &u32) int + +fn C.RegOpenKeyEx(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) voidptr + +fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) int + +fn C.RegSetValueEx() voidptr + +fn C.RegSetValueExW(hKey voidptr, lpValueName &u16, reserved u32, dwType u32, lpData &byte, lpcbData u32) int + +fn C.RegCloseKey(hKey voidptr) + +fn C.RemoveDirectory(lpPathName &u16) int + +// fn C.GetStdHandle() voidptr +fn C.GetStdHandle(u32) voidptr + +// fn C.SetConsoleMode() +fn C.SetConsoleMode(voidptr, u32) int + +// fn C.GetConsoleMode() int +fn C.GetConsoleMode(voidptr, &u32) int + +[trusted] +fn C.GetCurrentProcessId() int + +fn C.wprintf() + +// fn C.setbuf() +fn C.setbuf(voidptr, &char) + +fn C.SymCleanup(hProcess voidptr) + +fn C.MultiByteToWideChar(codePage u32, dwFlags u32, lpMultiMyteStr &char, cbMultiByte int, lpWideCharStr &u16, cchWideChar int) int + +fn C.wcslen(str &u16) int + +fn C.WideCharToMultiByte(codePage u32, dwFlags u32, lpWideCharStr &u16, cchWideChar int, lpMultiByteStr &char, cbMultiByte int, lpDefaultChar &char, lpUsedDefaultChar &int) int + +fn C._wstat(path &u16, buffer &C._stat) int + +fn C._wrename(oldname &u16, newname &u16) int + +fn C._wfopen(filename &u16, mode &u16) voidptr + +fn C._wpopen(command &u16, mode &u16) voidptr + +fn C._pclose(stream &C.FILE) int + +fn C._wsystem(command &u16) int + +fn C._wgetenv(varname &u16) voidptr + +fn C._putenv(envstring &char) int + +fn C._waccess(path &u16, mode int) int + +fn C._wremove(path &u16) int + +fn C.ReadConsole(in_input_handle voidptr, out_buffer voidptr, in_chars_to_read u32, out_read_chars &u32, in_input_control voidptr) bool + +fn C.WriteConsole() voidptr + +fn C.WriteFile() voidptr + +fn C._wchdir(dirname &u16) + +fn C._wgetcwd(buffer &u16, maxlen int) int + +fn C._fullpath() int + +fn C.GetFullPathName(voidptr, u32, voidptr, voidptr) u32 + +[trusted] +fn C.GetCommandLine() voidptr + +fn C.LocalFree() + +fn C.FindFirstFileW(lpFileName &u16, lpFindFileData voidptr) voidptr + +fn C.FindFirstFile(lpFileName &byte, lpFindFileData voidptr) voidptr + +fn C.FindNextFile(hFindFile voidptr, lpFindFileData voidptr) int + +fn C.FindClose(hFindFile voidptr) + +// macro +fn C.MAKELANGID(lgid voidptr, srtid voidptr) int + +fn C.FormatMessage(dwFlags u32, lpSource voidptr, dwMessageId u32, dwLanguageId u32, lpBuffer voidptr, nSize int, arguments ...voidptr) voidptr + +fn C.CloseHandle(voidptr) int + +fn C.GetExitCodeProcess(hProcess voidptr, lpExitCode &u32) + +[trusted] +fn C.GetTickCount() i64 + +[trusted] +fn C.Sleep(dwMilliseconds u32) + +fn C.WSAStartup(u16, &voidptr) int + +fn C.WSAGetLastError() int + +fn C.closesocket(int) int + +fn C.vschannel_init(&C.TlsContext) + +fn C.request(&C.TlsContext, int, &u16, &byte, &&byte) int + +fn C.vschannel_cleanup(&C.TlsContext) + +fn C.URLDownloadToFile(int, &u16, &u16, int, int) + +fn C.GetLastError() u32 + +fn C.CreateDirectory(&byte, int) bool + +// win crypto +fn C.BCryptGenRandom(int, voidptr, int, int) int + +// win synchronization +fn C.CreateMutex(int, bool, &byte) voidptr + +fn C.WaitForSingleObject(voidptr, int) int + +fn C.ReleaseMutex(voidptr) bool + +fn C.CreateEvent(int, bool, bool, &byte) voidptr + +fn C.SetEvent(voidptr) int + +fn C.CreateSemaphore(voidptr, int, int, voidptr) voidptr + +fn C.ReleaseSemaphore(voidptr, int, voidptr) voidptr + +fn C.InitializeSRWLock(voidptr) + +fn C.AcquireSRWLockShared(voidptr) + +fn C.AcquireSRWLockExclusive(voidptr) + +fn C.ReleaseSRWLockShared(voidptr) + +fn C.ReleaseSRWLockExclusive(voidptr) + +// pthread.h +fn C.pthread_mutex_init(voidptr, voidptr) int + +fn C.pthread_mutex_lock(voidptr) int + +fn C.pthread_mutex_unlock(voidptr) int + +fn C.pthread_mutex_destroy(voidptr) int + +fn C.pthread_rwlockattr_init(voidptr) int + +fn C.pthread_rwlockattr_setkind_np(voidptr, int) int + +fn C.pthread_rwlockattr_setpshared(voidptr, int) int + +fn C.pthread_rwlock_init(voidptr, voidptr) int + +fn C.pthread_rwlock_rdlock(voidptr) int + +fn C.pthread_rwlock_wrlock(voidptr) int + +fn C.pthread_rwlock_unlock(voidptr) int + +fn C.pthread_condattr_init(voidptr) int + +fn C.pthread_condattr_setpshared(voidptr, int) int + +fn C.pthread_condattr_destroy(voidptr) int + +fn C.pthread_cond_init(voidptr, voidptr) int + +fn C.pthread_cond_signal(voidptr) int + +fn C.pthread_cond_wait(voidptr, voidptr) int + +fn C.pthread_cond_timedwait(voidptr, voidptr, voidptr) int + +fn C.pthread_cond_destroy(voidptr) int + +fn C.sem_init(voidptr, int, u32) int + +fn C.sem_post(voidptr) int + +fn C.sem_wait(voidptr) int + +fn C.sem_trywait(voidptr) int + +fn C.sem_timedwait(voidptr, voidptr) int + +fn C.sem_destroy(voidptr) int + +// MacOS semaphore functions +fn C.dispatch_semaphore_create(i64) voidptr + +fn C.dispatch_semaphore_signal(voidptr) i64 + +fn C.dispatch_semaphore_wait(voidptr, u64) i64 + +fn C.dispatch_time(u64, i64) u64 + +fn C.dispatch_release(voidptr) + +// file descriptor based reading/writing +fn C.read(fd int, buf voidptr, count size_t) int + +fn C.write(fd int, buf voidptr, count size_t) int + +fn C.close(fd int) int + +// pipes +fn C.pipe(pipefds &int) int + +fn C.dup2(oldfd int, newfd int) int + +// used by gl, stbi, freetype +fn C.glTexImage2D() + +// used by ios for println +fn C.WrappedNSLog(str &byte) diff --git a/v_windows/v/vlib/builtin/cfns_wrapper.c.v b/v_windows/v/vlib/builtin/cfns_wrapper.c.v new file mode 100644 index 0000000..06f1348 --- /dev/null +++ b/v_windows/v/vlib/builtin/cfns_wrapper.c.v @@ -0,0 +1,72 @@ +module builtin + +// vstrlen returns the V length of the C string `s` (0 terminator is not counted). +// The C string is expected to be a &byte pointer. +[inline; unsafe] +pub fn vstrlen(s &byte) int { + return unsafe { C.strlen(&char(s)) } +} + +// vstrlen_char returns the V length of the C string `s` (0 terminator is not counted). +// The C string is expected to be a &char pointer. +[inline; unsafe] +pub fn vstrlen_char(s &char) int { + return unsafe { C.strlen(s) } +} + +// vmemcpy copies n bytes from memory area src to memory area dest. +// The memory areas *MUST NOT OVERLAP*. Use vmemmove, if the memory +// areas do overlap. vmemcpy returns a pointer to `dest`. +[inline; unsafe] +pub fn vmemcpy(dest voidptr, const_src voidptr, n int) voidptr { + unsafe { + return C.memcpy(dest, const_src, n) + } +} + +// vmemmove copies n bytes from memory area `src` to memory area `dest`. +// The memory areas *MAY* overlap: copying takes place as though the bytes +// in `src` are first copied into a temporary array that does not overlap +// `src` or `dest`, and the bytes are then copied from the temporary array +// to `dest`. vmemmove returns a pointer to `dest`. +[inline; unsafe] +pub fn vmemmove(dest voidptr, const_src voidptr, n int) voidptr { + unsafe { + return C.memmove(dest, const_src, n) + } +} + +// vmemcmp compares the first n bytes (each interpreted as unsigned char) +// of the memory areas s1 and s2. It returns an integer less than, equal to, +// or greater than zero, if the first n bytes of s1 is found, respectively, +// to be less than, to match, or be greater than the first n bytes of s2. +// For a nonzero return value, the sign is determined by the sign of the +// difference between the first pair of bytes (interpreted as unsigned char) +// that differ in s1 and s2. +// If n is zero, the return value is zero. +// Do NOT use vmemcmp to compare security critical data, such as cryptographic +// secrets, because the required CPU time depends on the number of equal bytes. +// You should use a function that performs comparisons in constant time for +// this. +[inline; unsafe] +pub fn vmemcmp(const_s1 voidptr, const_s2 voidptr, n int) int { + unsafe { + return C.memcmp(const_s1, const_s2, n) + } +} + +// vmemset fills the first `n` bytes of the memory area pointed to by `s`, +// with the constant byte `c`. It returns a pointer to the memory area `s`. +[inline; unsafe] +pub fn vmemset(s voidptr, c int, n int) voidptr { + unsafe { + return C.memset(s, c, n) + } +} + +type FnSortCB = fn (const_a voidptr, const_b voidptr) int + +[inline; unsafe] +fn vqsort(base voidptr, nmemb size_t, size size_t, sort_cb FnSortCB) { + C.qsort(base, nmemb, size, voidptr(sort_cb)) +} diff --git a/v_windows/v/vlib/builtin/chan.v b/v_windows/v/vlib/builtin/chan.v new file mode 100644 index 0000000..1692d5f --- /dev/null +++ b/v_windows/v/vlib/builtin/chan.v @@ -0,0 +1,32 @@ +module builtin + +// ChanState describes the result of an attempted channel transaction. +pub enum ChanState { + success + not_ready // push()/pop() would have to wait, but no_block was requested + closed +} + +/* +The following methods are only stubs. +The real implementation is in `vlib/sync/channels.v` +*/ + +// close closes the channel for further push transactions. +// closed channels cannot be pushed to, however they can be popped +// from as long as there is still objects available in the channel buffer. +pub fn (ch chan) close() {} + +// try_pop returns `ChanState.success` if an object is popped from the channel. +// try_pop effectively pops from the channel without waiting for objects to become available. +// Both the test and pop transaction is done atomically. +pub fn (ch chan) try_pop(obj voidptr) ChanState { + return .success +} + +// try_push returns `ChanState.success` if the object is pushed to the channel. +// try_push effectively both push and test if the transaction `ch <- a` succeeded. +// Both the test and push transaction is done atomically. +pub fn (ch chan) try_push(obj voidptr) ChanState { + return .success +} diff --git a/v_windows/v/vlib/builtin/float.c.v b/v_windows/v/vlib/builtin/float.c.v new file mode 100644 index 0000000..99fe8b8 --- /dev/null +++ b/v_windows/v/vlib/builtin/float.c.v @@ -0,0 +1,205 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module builtin + +import strconv + +#include +/* +----------------------------------- +----- f64 to string functions ----- +*/ +// str return a `f64` as `string` in suitable notation. +[inline] +pub fn (x f64) str() string { + unsafe { + f := strconv.Float64u{ + f: x + } + if f.u == strconv.double_minus_zero { + return '-0' + } + if f.u == strconv.double_plus_zero { + return '0' + } + } + abs_x := f64_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f64_to_str_l(x) + } else { + return strconv.ftoa_64(x) + } +} + +// strg return a `f64` as `string` in "g" printf format +[inline] +pub fn (x f64) strg() string { + if x == 0 { + return '0' + } + abs_x := f64_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f64_to_str_l_no_dot(x) + } else { + return strconv.ftoa_64(x) + } +} + +// str returns the value of the `float_literal` as a `string`. +[inline] +pub fn (d float_literal) str() string { + return f64(d).str() +} + +// strsci returns the `f64` as a `string` in scientific notation with `digit_num` decimals displayed, max 17 digits. +// Example: assert f64(1.234).strsci(3) == '1.234e+00' +[inline] +pub fn (x f64) strsci(digit_num int) string { + mut n_digit := digit_num + if n_digit < 1 { + n_digit = 1 + } else if n_digit > 17 { + n_digit = 17 + } + return strconv.f64_to_str(x, n_digit) +} + +// strlong returns a decimal notation of the `f64` as a `string`. +// Example: assert f64(1.23456).strlong() == '1.23456' +[inline] +pub fn (x f64) strlong() string { + return strconv.f64_to_str_l(x) +} + +/* +----------------------------------- +----- f32 to string functions ----- +*/ +// str returns a `f32` as `string` in suitable notation. +[inline] +pub fn (x f32) str() string { + unsafe { + f := strconv.Float32u{ + f: x + } + if f.u == strconv.single_minus_zero { + return '-0' + } + if f.u == strconv.single_plus_zero { + return '0' + } + } + abs_x := f32_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f32_to_str_l(x) + } else { + return strconv.ftoa_32(x) + } +} + +// strg return a `f32` as `string` in "g" printf format +[inline] +pub fn (x f32) strg() string { + if x == 0 { + return '0' + } + abs_x := f32_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f32_to_str_l_no_dot(x) + } else { + return strconv.ftoa_32(x) + } +} + +// strsci returns the `f32` as a `string` in scientific notation with `digit_num` deciamals displayed, max 8 digits. +// Example: assert f32(1.234).strsci(3) == '1.234e+00' +[inline] +pub fn (x f32) strsci(digit_num int) string { + mut n_digit := digit_num + if n_digit < 1 { + n_digit = 1 + } else if n_digit > 8 { + n_digit = 8 + } + return strconv.f32_to_str(x, n_digit) +} + +// strlong returns a decimal notation of the `f32` as a `string`. +[inline] +pub fn (x f32) strlong() string { + return strconv.f32_to_str_l(x) +} + +/* +----------------------- +----- C functions ----- +*/ +// f32_abs returns the absolute value of `a` as a `f32` value. +// Example: assert f32_abs(-2.0) == 2.0 +[inline] +pub fn f32_abs(a f32) f32 { + return if a < 0 { -a } else { a } +} + +// f64_abs returns the absolute value of `a` as a `f64` value. +// Example: assert f64_abs(-2.0) == f64(2.0) +[inline] +fn f64_abs(a f64) f64 { + return if a < 0 { -a } else { a } +} + +// f32_max returns the largest `f32` of input `a` and `b`. +// Example: assert f32_max(2.0,3.0) == 3.0 +[inline] +pub fn f32_max(a f32, b f32) f32 { + return if a > b { a } else { b } +} + +// f32_min returns the smallest `f32` of input `a` and `b`. +// Example: assert f32_min(2.0,3.0) == 2.0 +[inline] +pub fn f32_min(a f32, b f32) f32 { + return if a < b { a } else { b } +} + +// f64_max returns the largest `f64` of input `a` and `b`. +// Example: assert f64_max(2.0,3.0) == 3.0 +[inline] +pub fn f64_max(a f64, b f64) f64 { + return if a > b { a } else { b } +} + +// f64_min returns the smallest `f64` of input `a` and `b`. +// Example: assert f64_min(2.0,3.0) == 2.0 +[inline] +fn f64_min(a f64, b f64) f64 { + return if a < b { a } else { b } +} + +// eq_epsilon returns true if the `f32` is equal to input `b`. +// using an epsilon of typically 1E-5 or higher (backend/compiler dependent). +// Example: assert f32(2.0).eq_epsilon(2.0) +[inline] +pub fn (a f32) eq_epsilon(b f32) bool { + hi := f32_max(f32_abs(a), f32_abs(b)) + delta := f32_abs(a - b) + if hi > f32(1.0) { + return delta <= hi * (4 * f32(C.FLT_EPSILON)) + } else { + return (1 / (4 * f32(C.FLT_EPSILON))) * delta <= hi + } +} + +// eq_epsilon returns true if the `f64` is equal to input `b`. +// using an epsilon of typically 1E-9 or higher (backend/compiler dependent). +// Example: assert f64(2.0).eq_epsilon(2.0) +[inline] +pub fn (a f64) eq_epsilon(b f64) bool { + hi := f64_max(f64_abs(a), f64_abs(b)) + delta := f64_abs(a - b) + if hi > 1.0 { + return delta <= hi * (4 * f64(C.DBL_EPSILON)) + } else { + return (1 / (4 * f64(C.DBL_EPSILON))) * delta <= hi + } +} diff --git a/v_windows/v/vlib/builtin/float_test.v b/v_windows/v/vlib/builtin/float_test.v new file mode 100644 index 0000000..cad2799 --- /dev/null +++ b/v_windows/v/vlib/builtin/float_test.v @@ -0,0 +1,147 @@ +fn test_float_decl() { + // z := 1f + // assert z > 0 + x1 := 1e10 + x2 := -2e16 + x3 := 1e-15 + x4 := -9e-4 + assert typeof(x1).name == 'f64' + assert typeof(x2).name == 'f64' + assert typeof(x3).name == 'f64' + assert typeof(x4).name == 'f64' + x5 := 4e108 + x6 := -7e99 + x7 := 3e-205 + x8 := -6e-147 + assert typeof(x5).name == 'f64' + assert typeof(x6).name == 'f64' + assert typeof(x7).name == 'f64' + assert typeof(x8).name == 'f64' + x9 := 312874834.77 + x10 := -22399994.06 + x11 := 0.0000000019 + x12 := -0.00000000008 + assert typeof(x9).name == 'f64' + assert typeof(x10).name == 'f64' + assert typeof(x11).name == 'f64' + assert typeof(x12).name == 'f64' + x13 := 34234234809890890898903213154353453453253253243432413232228908902183918392183902432432438980380123021983901392183921389083913890389089031.0 + x14 := -39999999999999999999222212128182813294989082302832183928343325325233253242312331324392839238239829389038097438248932789371837218372837293.8 + x15 := 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002 + x16 := -0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004 + assert typeof(x13).name == 'f64' + assert typeof(x14).name == 'f64' + assert typeof(x15).name == 'f64' + assert typeof(x16).name == 'f64' +} + +fn test_f32_equal_operator() { + b := f32(1.0) + mut a := f32(1.0) + a += 0.0000019073486328125 + assert a != b + a -= 0.0000019073486328125 + assert a == b + assert -1 == 1 * -1 + assert -1.0 == 1.0 * -1.0 + a = 1 + a += 0.0000019073486328125 + a -= 0.0000019073486328125 + assert a == f32(1.0) + a += 0.000001 + assert !(a < f32(1)) + assert !(a <= f32(1)) + assert a > f32(1) + assert a >= 1 + assert a != 1 + f := 1.2 + ab := int(f) + assert ab == 1 + e := f32(-1.602176634e-19) + m := f32(9.1093837015e-31) + assert e < m + assert e <= m + assert e != m + assert !(e == m) + assert m >= e + assert m > e +} + +fn test_f64_equal_operator() { + b := 1.0 + mut a := 1.0 + a += 0.0000019073486328125 + assert a != b + a -= 0.0000019073486328125 + assert a == b + e := -1.602176634e-19 + m := 9.1093837015e-31 + assert e < m + assert e <= m + assert e != m + assert !(e == m) + assert m >= e + assert m > e +} + +fn test_f64_eq_epsilon() { + a := 1.662248544459347e308 + b := 1.662248544459348e308 + x := 1.662248544459352e308 + assert a != b + assert a.eq_epsilon(b) + assert b.eq_epsilon(a) + assert (-a).eq_epsilon(-b) + assert (-b).eq_epsilon(-a) + assert !a.eq_epsilon(x) + assert !x.eq_epsilon(a) + assert !a.eq_epsilon(-b) + assert !(-a).eq_epsilon(b) + c := 1.5367748374385438503 + d := -1.5367748374385447257 + z := 1.5367748378943546 + assert c != -d + assert c.eq_epsilon(-d) + assert d.eq_epsilon(-c) + assert !c.eq_epsilon(z) + assert !z.eq_epsilon(c) + e := 2.531434251587394233e-308 + f := 2.531434251587395675e-308 + y := 2.531434251587398934e-308 + assert e != f + assert e.eq_epsilon(f) + assert (-f).eq_epsilon(-e) + assert !e.eq_epsilon(y) + assert !(-y).eq_epsilon(-e) +} + +fn test_f32_eq_epsilon() { + a := f32(3.244331e38) + b := f32(3.244332e38) + x := f32(3.244338e38) + assert a != b + assert a.eq_epsilon(b) + assert b.eq_epsilon(a) + assert (-a).eq_epsilon(-b) + assert (-b).eq_epsilon(-a) + assert !a.eq_epsilon(x) + assert !(-x).eq_epsilon(-a) + assert !a.eq_epsilon(-b) + assert !(-a).eq_epsilon(b) + c := f32(0.9546742) + d := f32(-0.9546745) + z := f32(0.9546754) + assert c != -d + assert c.eq_epsilon(-d) + assert d.eq_epsilon(-c) + assert !c.eq_epsilon(z) + assert !z.eq_epsilon(c) + e := f32(-1.5004390e-38) + f := f32(-1.5004395e-38) + y := f32(-1.5004409e-38) + assert e != f + assert e.eq_epsilon(f) + assert (-f).eq_epsilon(-e) + assert !e.eq_epsilon(y) + assert !(-y).eq_epsilon(-e) +} diff --git a/v_windows/v/vlib/builtin/float_x64.v b/v_windows/v/vlib/builtin/float_x64.v new file mode 100644 index 0000000..4a491a0 --- /dev/null +++ b/v_windows/v/vlib/builtin/float_x64.v @@ -0,0 +1,6 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module builtin + +fn float_test() { +} diff --git a/v_windows/v/vlib/builtin/int.v b/v_windows/v/vlib/builtin/int.v new file mode 100644 index 0000000..24e658f --- /dev/null +++ b/v_windows/v/vlib/builtin/int.v @@ -0,0 +1,481 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +// +// ----- value to string functions ----- +// + +type u8 = byte + +// ptr_str returns the address of `ptr` as a `string`. +pub fn ptr_str(ptr voidptr) string { + buf1 := u64(ptr).hex() + return buf1 +} + +pub fn (x size_t) str() string { + return u64(x).str() +} + +pub fn (cptr &char) str() string { + return u64(cptr).hex() +} + +const ( + // digit pairs in reverse order + digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999' +) + +// This implementation is the quickest with gcc -O2 +// str_l returns the string representation of the integer nn with max chars. +[direct_array_access; inline] +fn (nn int) str_l(max int) string { + unsafe { + mut n := i64(nn) + mut d := 0 + if n == 0 { + return '0' + } + + mut is_neg := false + if n < 0 { + n = -n + is_neg = true + } + mut index := max + mut buf := malloc_noscan(max + 1) + buf[index] = 0 + index-- + + for n > 0 { + n1 := int(n / 100) + // calculate the digit_pairs start index + d = ((int(n) - (n1 * 100)) << 1) + n = n1 + buf[index] = digit_pairs.str[d] + index-- + d++ + buf[index] = digit_pairs.str[d] + index-- + } + index++ + // remove head zero + if d < 20 { + index++ + } + // Prepend - if it's negative + if is_neg { + index-- + buf[index] = `-` + } + diff := max - index + vmemmove(buf, buf + index, diff + 1) + /* + // === manual memory move for bare metal === + mut c:= 0 + for c < diff { + buf[c] = buf[c+index] + c++ + } + buf[c] = 0 + */ + return tos(buf, diff) + + // return tos(memdup(&buf[0] + index, (max - index)), (max - index)) + } +} + +// str returns the value of the `i8` as a `string`. +// Example: assert i8(-2).str() == '-2' +pub fn (n i8) str() string { + return int(n).str_l(5) +} + +// str returns the value of the `i16` as a `string`. +// Example: assert i16(-20).str() == '-20' +pub fn (n i16) str() string { + return int(n).str_l(7) +} + +// str returns the value of the `u16` as a `string`. +// Example: assert u16(20).str() == '20' +pub fn (n u16) str() string { + return int(n).str_l(7) +} + +// str returns the value of the `int` as a `string`. +// Example: assert int(-2020).str() == '-2020' +pub fn (n int) str() string { + return n.str_l(12) +} + +// str returns the value of the `u32` as a `string`. +// Example: assert u32(20000).str() == '20000' +[direct_array_access; inline] +pub fn (nn u32) str() string { + unsafe { + mut n := nn + mut d := u32(0) + if n == 0 { + return '0' + } + max := 12 + mut buf := malloc_noscan(max + 1) + mut index := max + buf[index] = 0 + index-- + for n > 0 { + n1 := n / u32(100) + d = ((n - (n1 * u32(100))) << u32(1)) + n = n1 + buf[index] = digit_pairs[d] + index-- + d++ + buf[index] = digit_pairs[d] + index-- + } + index++ + // remove head zero + if d < u32(20) { + index++ + } + diff := max - index + vmemmove(buf, buf + index, diff + 1) + return tos(buf, diff) + + // return tos(memdup(&buf[0] + index, (max - index)), (max - index)) + } +} + +// str returns the value of the `int_literal` as a `string`. +[inline] +pub fn (n int_literal) str() string { + return i64(n).str() +} + +// str returns the value of the `i64` as a `string`. +// Example: assert i64(-200000).str() == '-200000' +[direct_array_access; inline] +pub fn (nn i64) str() string { + unsafe { + mut n := nn + mut d := i64(0) + if n == 0 { + return '0' + } + max := 20 + mut buf := malloc_noscan(max + 1) + mut is_neg := false + if n < 0 { + n = -n + is_neg = true + } + mut index := max + buf[index] = 0 + index-- + for n > 0 { + n1 := n / i64(100) + d = ((n - (n1 * i64(100))) << i64(1)) + n = n1 + buf[index] = digit_pairs[d] + index-- + d++ + buf[index] = digit_pairs[d] + index-- + } + index++ + // remove head zero + if d < i64(20) { + index++ + } + // Prepend - if it's negative + if is_neg { + index-- + buf[index] = `-` + } + diff := max - index + vmemmove(buf, buf + index, diff + 1) + return tos(buf, diff) + // return tos(memdup(&buf[0] + index, (max - index)), (max - index)) + } +} + +// str returns the value of the `u64` as a `string`. +// Example: assert u64(2000000).str() == '2000000' +[direct_array_access; inline] +pub fn (nn u64) str() string { + unsafe { + mut n := nn + mut d := u64(0) + if n == 0 { + return '0' + } + max := 20 + mut buf := malloc_noscan(max + 1) + mut index := max + buf[index] = 0 + index-- + for n > 0 { + n1 := n / 100 + d = ((n - (n1 * 100)) << 1) + n = n1 + buf[index] = digit_pairs[d] + index-- + d++ + buf[index] = digit_pairs[d] + index-- + } + index++ + // remove head zero + if d < 20 { + index++ + } + diff := max - index + vmemmove(buf, buf + index, diff + 1) + return tos(buf, diff) + // return tos(memdup(&buf[0] + index, (max - index)), (max - index)) + } +} + +// str returns the value of the `bool` as a `string`. +// Example: assert (2 > 1).str() == 'true' +pub fn (b bool) str() string { + if b { + return 'true' + } + return 'false' +} + +// +// ----- value to hex string functions ----- +// + +// u64_to_hex converts the number `nn` to a (zero padded if necessary) hexadecimal `string`. +[direct_array_access; inline] +fn u64_to_hex(nn u64, len byte) string { + mut n := nn + mut buf := [17]byte{} + buf[len] = 0 + mut i := 0 + for i = len - 1; i >= 0; i-- { + d := byte(n & 0xF) + buf[i] = if d < 10 { d + `0` } else { d + 87 } + n = n >> 4 + } + return unsafe { tos(memdup(&buf[0], len + 1), len) } +} + +// u64_to_hex_no_leading_zeros converts the number `nn` to hexadecimal `string`. +[direct_array_access; inline] +fn u64_to_hex_no_leading_zeros(nn u64, len byte) string { + mut n := nn + mut buf := [17]byte{} + buf[len] = 0 + mut i := 0 + for i = len - 1; i >= 0; i-- { + d := byte(n & 0xF) + buf[i] = if d < 10 { d + `0` } else { d + 87 } + n = n >> 4 + if n == 0 { + break + } + } + res_len := len - i + return unsafe { tos(memdup(&buf[i], res_len + 1), res_len) } +} + +// hex returns the value of the `byte` as a hexadecimal `string`. +// Note that the output is zero padded for values below 16. +// Example: assert byte(2).hex() == '02' +// Example: assert byte(15).hex() == '0f' +// Example: assert byte(255).hex() == 'ff' +pub fn (nn byte) hex() string { + if nn == 0 { + return '00' + } + return u64_to_hex(nn, 2) +} + +// hex returns the value of the `i8` as a hexadecimal `string`. +// Note that the output is zero padded for values below 16. +// Example: assert i8(8).hex() == '08' +// Example: assert i8(10).hex() == '0a' +// Example: assert i8(15).hex() == '0f' +pub fn (nn i8) hex() string { + if nn == 0 { + return '00' + } + return u64_to_hex(u64(nn), 2) + //return byte(nn).hex() +} + +// hex returns the value of the `u16` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert u16(2).hex() == '2' +// Example: assert u16(200).hex() == 'c8' +pub fn (nn u16) hex() string { + if nn == 0 { + return '0' + } + return u64_to_hex_no_leading_zeros(nn, 4) +} + +// hex returns the value of the `i16` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert i16(2).hex() == '2' +// Example: assert i16(200).hex() == 'c8' +pub fn (nn i16) hex() string { + return u16(nn).hex() +} + +// hex returns the value of the `u32` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert u32(2).hex() == '2' +// Example: assert u32(200).hex() == 'c8' +pub fn (nn u32) hex() string { + if nn == 0 { + return '0' + } + return u64_to_hex_no_leading_zeros(nn, 8) +} + +// hex returns the value of the `int` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert int(2).hex() == '2' +// Example: assert int(200).hex() == 'c8' +pub fn (nn int) hex() string { + return u32(nn).hex() +} + +// hex2 returns the value of the `int` as a `0x`-prefixed hexadecimal `string`. +// Note that the output after `0x` is ***not*** zero padded. +// Example: assert int(8).hex2() == '0x8' +// Example: assert int(15).hex2() == '0xf' +// Example: assert int(18).hex2() == '0x12' +pub fn (n int) hex2() string { + return '0x' + n.hex() +} + +// hex returns the value of the `u64` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert u64(2).hex() == '2' +// Example: assert u64(2000).hex() == '7d0' +pub fn (nn u64) hex() string { + if nn == 0 { + return '0' + } + return u64_to_hex_no_leading_zeros(nn, 16) +} + +// hex returns the value of the `i64` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// Example: assert i64(2).hex() == '2' +// Example: assert i64(-200).hex() == 'ffffffffffffff38' +// Example: assert i64(2021).hex() == '7e5' +pub fn (nn i64) hex() string { + return u64(nn).hex() +} + +// hex returns the value of the `int_literal` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +pub fn (nn int_literal) hex() string { + return u64(nn).hex() +} + +// hex returns the value of the `voidptr` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +pub fn (nn voidptr) str() string { + return u64(nn).hex() +} + +// hex returns the value of the `byteptr` as a hexadecimal `string`. +// Note that the output is ***not*** zero padded. +// pub fn (nn byteptr) str() string { +pub fn (nn byteptr) str() string { + return u64(nn).hex() +} + +pub fn (nn byte) hex_full() string { + return u64_to_hex(u64(nn), 2) +} + +pub fn (nn i8) hex_full() string { + return u64_to_hex(u64(nn), 2) +} + +pub fn (nn u16) hex_full() string { + return u64_to_hex(u64(nn), 4) +} + +pub fn (nn i16) hex_full() string { + return u64_to_hex(u64(nn), 4) +} + +pub fn (nn u32) hex_full() string { + return u64_to_hex(u64(nn), 8) +} + +pub fn (nn int) hex_full() string { + return u64_to_hex(u64(nn), 8) +} + +pub fn (nn i64) hex_full() string { + return u64_to_hex(u64(nn), 16) +} + +pub fn (nn voidptr) hex_full() string { + return u64_to_hex(u64(nn), 16) +} + +pub fn (nn int_literal) hex_full() string { + return u64_to_hex(u64(nn), 16) +} + +// hex_full returns the value of the `u64` as a *full* 16-digit hexadecimal `string`. +// Example: assert u64(2).hex_full() == '0000000000000002' +// Example: assert u64(255).hex_full() == '00000000000000ff' +pub fn (nn u64) hex_full() string { + return u64_to_hex(nn, 16) +} + +// str returns the contents of `byte` as a zero terminated `string`. +// Example: assert byte(111).str() == '111' +pub fn (b byte) str() string { + return int(b).str_l(7) +} + +// ascii_str returns the contents of `byte` as a zero terminated ASCII `string` character. +// Example: assert byte(97).ascii_str() == 'a' +pub fn (b byte) ascii_str() string { + mut str := string{ + str: unsafe { malloc_noscan(2) } + len: 1 + } + unsafe { + str.str[0] = b + str.str[1] = 0 + } + // println(str) + return str +} + +// str_escaped returns the contents of `byte` as an escaped `string`. +// Example: assert byte(0).str_escaped() == r'`\0`' +pub fn (b byte) str_escaped() string { + str := match b { + 0 { r'`\0`' } + 7 { r'`\a`' } + 8 { r'`\b`' } + 9 { r'`\t`' } + 10 { r'`\n`' } + 11 { r'`\v`' } + 12 { r'`\f`' } + 13 { r'`\r`' } + 27 { r'`\e`' } + 32...126 { b.ascii_str() } + else { '0x' + b.hex() } + } + return str +} diff --git a/v_windows/v/vlib/builtin/int_test.v b/v_windows/v/vlib/builtin/int_test.v new file mode 100644 index 0000000..3f233f1 --- /dev/null +++ b/v_windows/v/vlib/builtin/int_test.v @@ -0,0 +1,241 @@ +const ( + a = 3 + u = u64(1) +) + +fn test_const() { + b := (true && true) || false + assert b == true + assert a == 3 + assert u == u64(1) + assert u == 1 // make sure this works without the cast +} + +fn test_str_methods() { + assert i8(1).str() == '1' + assert i8(-1).str() == '-1' + assert i16(1).str() == '1' + assert i16(-1).str() == '-1' + assert int(1).str() == '1' + assert int(-1).str() == '-1' + assert int(2147483647).str() == '2147483647' + assert int(2147483648).str() == '-2147483648' + assert int(-2147483648).str() == '-2147483648' + assert i64(1).str() == '1' + assert i64(-1).str() == '-1' + assert u16(1).str() == '1' + assert u16(-1).str() == '65535' + assert u32(1).str() == '1' + assert u32(-1).str() == '4294967295' + assert u64(1).str() == '1' + assert u64(-1).str() == '18446744073709551615' + assert voidptr(-1).str() == 'ffffffffffffffff' + assert voidptr(1).str() == '1' + assert (&byte(-1)).str() == 'ffffffffffffffff' + assert (&byte(1)).str() == '1' + assert byteptr(-1).str() == 'ffffffffffffffff' + assert byteptr(1).str() == '1' +} + +fn test_and_precendence() { + assert (2 & 0 == 0) == ((2 & 0) == 0) + assert (2 & 0 != 0) == ((2 & 0) != 0) + assert (0 & 0 >= 0) == ((0 & 0) >= 0) + assert (0 & 0 <= 0) == ((0 & 0) <= 0) + assert (0 & 0 < 1) == ((0 & 0) < 1) + assert (1 & 2 > 0) == ((1 & 2) > 0) +} + +fn test_or_precendence() { + assert (1 | 0 == 0) == ((1 | 0) == 0) + assert (1 | 0 != 1) == ((1 | 0) != 1) + assert (1 | 0 >= 2) == ((1 | 0) >= 2) + assert (1 | 0 <= 0) == ((1 | 0) <= 0) + assert (1 | 0 < 0) == ((1 | 0) < 0) + assert (1 | 0 > 1) == ((1 | 0) > 1) +} + +fn test_xor_precendence() { + assert (1 ^ 0 == 2) == ((1 ^ 0) == 2) + assert (1 ^ 0 != 2) == ((1 ^ 0) != 2) + assert (1 ^ 0 >= 0) == ((1 ^ 0) >= 0) + assert (1 ^ 0 <= 1) == ((1 ^ 0) <= 1) + assert (1 ^ 0 < 0) == ((1 ^ 0) < 0) + assert (1 ^ 0 > 1) == ((1 ^ 0) > 1) +} + +fn test_left_shift_precendence() { + assert (2 << 4 | 3) == ((2 << 4) | 3) + assert (2 << 4 | 3) != (2 << (4 | 3)) +} + +fn test_right_shift_precendence() { + assert (256 >> 4 | 3) == ((256 >> 4) | 3) + assert (256 >> 4 | 3) != (256 >> (4 | 3)) +} + +fn test_i8_print() { + b := i8(0) + println(b) + c := i16(7) + println(c) + d := u16(6) + println(d) + assert true +} + +/* +fn test_cmp() { + assert 1 ≠ 2 + assert 1 ⩽ 2 + assert 1 ⩾ 0 +} +*/ +type MyInt = int + +fn test_int_alias() { + i := MyInt(2) + assert i + 10 == 12 +} + +fn test_hex() { + x := u64(10) + assert x.hex() == 'a' + b := 1234 + assert b.hex() == '4d2' + b1 := -1 + assert b1.hex() == 'ffffffff' + // unsigned tests + assert u8(12).hex() == '0c' + assert u8(255).hex() == 'ff' + assert u16(65535).hex() == 'ffff' + assert u32(-1).hex() == 'ffffffff' + assert u64(-1).hex() == 'ffffffffffffffff' + // signed tests + assert i8(-1).hex() == 'ff' + assert i8(12).hex() == '0c' + assert i16(32767).hex() == '7fff' + assert int(2147483647).hex() == '7fffffff' + assert i64(9223372036854775807).hex() == '7fffffffffffffff' +} + +fn test_bin() { + x1 := 0b10 + assert x1 == 2 + x2 := 0b10101010 + assert x2 == 0xAA + x3 := -0b0000001 + assert x3 == -1 + x4 := 0b11111111 + assert x4 == 255 + x5 := byte(0b11111111) + assert x5 == 255 + x6 := char(0b11111111) + assert int(x6) == -1 + x7 := 0b0 + assert x7 == 0 + x8 := -0b0 + assert x8 == 0 +} + +fn test_oct() { + x1 := 0o12 + assert x1 == 10 + x2 := 00000o350 + assert x2 == 232 + x3 := 000o00073 + assert x3 == 59 + x4 := 00000000 + assert x4 == 0 + x5 := 00000195 + assert x5 == 195 + x6 := -0o744 + assert x6 == -484 + x7 := -000o000042 + assert x7 == -34 + x8 := -0000112 + assert x8 == -112 + x9 := -000 + assert x9 == 0 +} + +fn test_num_separator() { + // int + assert 100_000_0 == 1000000 + assert -2_23_4_6 == -22346 + + // bin + assert 0b0_11 == 3 + assert -0b0_100 == -4 + + // oct + assert 0o1_73 == 123 + assert -0o17_5 == -125 + assert -0o175 == -125 + + // hex + assert 0xFF == 255 + assert 0xF_F == 255 + + // f32 or f64 + assert 312_2.55 == 3122.55 + assert 312_2.55 == 3122.55 +} + +fn test_int_decl() { + x1 := 0 + x2 := 1333 + x3 := -88955 + x4 := 2000000000 + x5 := -1999999999 + assert typeof(x1).name == 'int' + assert typeof(x2).name == 'int' + assert typeof(x3).name == 'int' + assert typeof(x4).name == 'int' + assert typeof(x5).name == 'int' + x7 := u64(-321314588900011) + assert typeof(x7).name == 'u64' +} + +fn test_int_to_hex() { + // array hex + st := [byte(`V`), `L`, `A`, `N`, `G`] + assert st.hex() == '564c414e47' + assert st.hex().len == 10 + st1 := [byte(0x41)].repeat(100) + assert st1.hex() == '41'.repeat(100) + // --- int to hex tests + c0 := 12 + // 8Bit + assert byte(0).hex() == '00' + assert byte(c0).hex() == '0c' + assert i8(c0).hex() == '0c' + assert byte(127).hex() == '7f' + assert i8(127).hex() == '7f' + assert byte(255).hex() == 'ff' + assert byte(-1).hex() == 'ff' + // 16bit + assert u16(0).hex() == '0' + assert i16(c0).hex() == 'c' + assert u16(c0).hex() == 'c' + assert i16(32767).hex() == '7fff' + assert u16(32767).hex() == '7fff' + assert i16(-1).hex() == 'ffff' + assert u16(65535).hex() == 'ffff' + // 32bit + assert u32(0).hex() == '0' + assert c0.hex() == 'c' + assert u32(c0).hex() == 'c' + assert 2147483647.hex() == '7fffffff' + assert u32(2147483647).hex() == '7fffffff' + assert (-1).hex() == 'ffffffffffffffff' + assert u32(4294967295).hex() == 'ffffffff' + // 64 bit + assert u64(0).hex() == '0' + assert i64(c0).hex() == 'c' + assert u64(c0).hex() == 'c' + assert i64(9223372036854775807).hex() == '7fffffffffffffff' + assert u64(9223372036854775807).hex() == '7fffffffffffffff' + assert i64(-1).hex() == 'ffffffffffffffff' + assert u64(18446744073709551615).hex() == 'ffffffffffffffff' +} diff --git a/v_windows/v/vlib/builtin/isnil_test.v b/v_windows/v/vlib/builtin/isnil_test.v new file mode 100644 index 0000000..15df9f0 --- /dev/null +++ b/v_windows/v/vlib/builtin/isnil_test.v @@ -0,0 +1,19 @@ +fn test_isnil_byteptr() { + pb := &byte(0) + assert isnil(pb) +} + +fn test_isnil_voidptr() { + pv := voidptr(0) + assert isnil(pv) +} + +fn test_isnil_charptr() { + pc := &char(0) + assert isnil(pc) +} + +fn test_isnil_intptr() { + pi := &int(0) + assert isnil(pi) +} diff --git a/v_windows/v/vlib/builtin/js/array.js.v b/v_windows/v/vlib/builtin/js/array.js.v new file mode 100644 index 0000000..495e656 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/array.js.v @@ -0,0 +1,253 @@ +module builtin + +struct array { + arr JS.Array +pub: + len int + cap int +} + +#function flatIntoArray(target, source, sourceLength, targetIndex, depth) { +#"use strict"; +# +#for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) { +#if (sourceIndex in source) { +#var element = source[sourceIndex]; +#if (depth > 0 && Array.isArray(element)) +#targetIndex = flatIntoArray(target, element, element.length, targetIndex, depth - 1); +#else { +#target[targetIndex] = element; +#++targetIndex; +#} +#} +#} +#return targetIndex; +#} +#function flatArray(target,depth) { +#var array = target +#var length = array.length; +#var depthNum = 1; +# +#if (depth !== undefined) +#depthNum = +depth +# +#var result = [] +# +#flatIntoArray(result, array, length, 0, depthNum); +#return result; +#} + +[unsafe] +pub fn (a array) repeat_to_depth(count int, depth int) array { + if count < 0 { + panic('array.repeat: count is negative: $count') + } + mut arr := empty_array() + #let tmp = new Array(a.arr.length * +count); + #tmp.fill(a.arr); + # + #arr.arr = flatArray(tmp,depth+1); + + return arr +} + +// last returns the last element of the array. +pub fn (a array) last() voidptr { + mut res := voidptr(0) + #res = a.arr[a.len-1]; + + return res +} + +fn (a array) get(ix int) voidptr { + mut result := voidptr(0) + #result = a.arr[ix] + + return result +} + +pub fn (a array) repeat(count int) array { + unsafe { + return a.repeat_to_depth(count, 0) + } +} + +fn empty_array() array { + mut arr := array{} + #arr = new array([]) + + return arr +} + +fn (a &array) set_len(i int) { + #a.arr.length=i +} + +pub fn (mut a array) sort_with_compare(compare voidptr) { + #a.val.arr.sort(compare) +} + +pub fn (mut a array) sort() { + #a.val.arr.sort($sortComparator) +} + +pub fn (a array) index(v string) int { + for i in 0 .. a.len { + #if (a.arr[i].toString() == v.toString()) + + { + return i + } + } + return -1 +} + +pub fn (a array) slice(start int, end int) array { + mut result := a + #result = new array(a.arr.slice(start,end)) + + return result +} + +pub fn (mut a array) insert(i int, val voidptr) { + #a.val.arr.splice(i,0,val) +} + +pub fn (mut a array) insert_many(i int, val voidptr, size int) { + #a.val.arr.splice(i,0,...val.slice(0,+size)) +} + +pub fn (mut a array) join(separator string) string { + mut res := '' + #res = new builtin.string(a.val.arr.join(separator +'')); + + return res +} + +fn (a array) push(val voidptr) { + #a.arr.push(val) +} + +pub fn (a array) str() string { + mut res := '' + #res = new builtin.string(a + '') + + return res +} + +#array.prototype[Symbol.iterator] = function () { return this.arr[Symbol.iterator](); } +#array.prototype.entries = function () { let result = []; for (const [key,val] of this.arr.entries()) { result.push([new int(key), val]); } return result[Symbol.iterator](); } +#array.prototype.map = function(callback) { return new builtin.array(this.arr.map(callback)); } +#array.prototype.filter = function(callback) { return new array(this.arr.filter( function (it) { return (+callback(it)) != 0; } )); } +#Object.defineProperty(array.prototype,'cap',{ get: function () { return this.len; } }) +#array.prototype.any = function (value) { +#let val ;if (typeof value == 'function') { val = function (x) { return value(x); } } else { val = function (x) { return vEq(x,value); } } +#for (let i = 0;i < this.arr.length;i++) +#if (val(this.arr[i])) +#return true; +# +#return false; +#} + +#array.prototype.all = function (value) { +#let val ;if (typeof value == 'function') { val = function (x) { return value(x); } } else { val = function (x) { return vEq(x,value); } } +#for (let i = 0;i < this.arr.length;i++) +#if (!val(this.arr[i])) +#return false; +# +#return true; +#} +// delete deletes array element at index `i`. +pub fn (mut a array) delete(i int) { + a.delete_many(i, 1) +} + +// delete_many deletes `size` elements beginning with index `i` +pub fn (mut a array) delete_many(i int, size int) { + #a.val.arr.splice(i.valueOf(),size.valueOf()) +} + +// prepend prepends one value to the array. +pub fn (mut a array) prepend(val voidptr) { + a.insert(0, val) +} + +// prepend_many prepends another array to this array. +[unsafe] +pub fn (mut a array) prepend_many(val voidptr, size int) { + unsafe { a.insert_many(0, val, size) } +} + +pub fn (a array) reverse() array { + mut res := array{} + #res.arr = Array.from(a.arr).reverse() + + return res +} + +pub fn (mut a array) reverse_in_place() { + #a.val.arr.reverse() +} + +#array.prototype.$includes = function (elem) { return this.arr.find(function(e) { return vEq(elem,e); }) !== undefined;} + +// reduce executes a given reducer function on each element of the array, +// resulting in a single output value. +pub fn (a array) reduce(iter fn (int, int) int, accum_start int) int { + mut accum_ := accum_start + #for (let i = 0;i < a.arr.length;i++) { + #accum_ = iter(accum_, a.arr[i]) + #} + + return accum_ +} + +pub fn (mut a array) pop() voidptr { + mut res := voidptr(0) + #res = a.val.arr.pop() + + return res +} + +pub fn (a array) first() voidptr { + mut res := voidptr(0) + #res = a.arr[0] + + return res +} + +#array.prototype.toString = function () { +#let res = "[" +#for (let i = 0; i < this.arr.length;i++) { +#res += this.arr[i].toString(); +#if (i != this.arr.length-1) +#res += ', ' +#} +#res += ']' +#return res; +# +#} + +pub fn (a array) contains(key voidptr) bool { + #for (let i = 0; i < a.arr.length;i++) + #if (vEq(a.arr[i],key)) return new bool(true); + + return false +} + +// delete_last effectively removes last element of an array. +pub fn (mut a array) delete_last() { + #a.val.arr.pop(); +} + +[unsafe] +pub fn (a array) free() { +} + +// todo: once (a []byte) will work rewrite this +pub fn (a array) bytestr() string { + res := '' + #a.arr.forEach((item) => res.str += String.fromCharCode(+item)) + + return res +} diff --git a/v_windows/v/vlib/builtin/js/builtin.js.v b/v_windows/v/vlib/builtin/js/builtin.js.v new file mode 100644 index 0000000..f64da00 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/builtin.js.v @@ -0,0 +1,61 @@ +module builtin + +// used to generate JS throw statements. +pub fn js_throw(s any) { + #throw s +} + +pub fn println(s string) { + $if js_freestanding { + #print(s.str) + } $else { + #console.log(s.str) + } +} + +pub fn print(s string) { + $if js_node { + #$process.stdout.write(s.str) + } $else { + panic('Cannot `print` in a browser, use `println` instead') + } +} + +pub fn eprintln(s string) { + $if js_freestanding { + #print(s.str) + } $else { + #console.error(s.str) + } +} + +pub fn eprint(s string) { + $if js_node { + #$process.stderr.write(s.str) + } $else { + panic('Cannot `eprint` in a browser, use `println` instead') + } +} + +// Exits the process in node, and halts execution in the browser +// because `process.exit` is undefined. Workaround for not having +// a 'real' way to exit in the browser. +pub fn exit(c int) { + JS.process.exit(c) + js_throw('exit($c)') +} + +fn opt_ok(data voidptr, option Option) { + #option.state = 0 + #option.err = none__ + #option.data = data +} + +pub fn unwrap(opt string) string { + mut o := Option{} + #o = opt + if o.state != 0 { + js_throw(o.err) + } + return opt +} diff --git a/v_windows/v/vlib/builtin/js/builtin.v b/v_windows/v/vlib/builtin/js/builtin.v new file mode 100644 index 0000000..7c82d1a --- /dev/null +++ b/v_windows/v/vlib/builtin/js/builtin.v @@ -0,0 +1,84 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +fn (a any) toString() + +pub fn panic(s string) { + eprintln('V panic: $s') + exit(1) +} + +// IError holds information about an error instance +pub interface IError { + msg string + code int +} + +// Error is the default implementation of IError, that is returned by e.g. `error()` +pub struct Error { +pub: + msg string + code int +} + +struct None__ { + msg string + code int +} + +fn (_ None__) str() string { + return 'none' +} + +pub const none__ = IError(None__{'', 0}) + +pub struct Option { + state byte + err IError = none__ +} + +pub fn (err IError) str() string { + return match err { + None__ { 'none' } + Error { err.msg } + else { '$err.type_name(): $err.msg' } + } +} + +pub fn (o Option) str() string { + if o.state == 0 { + return 'Option{ ok }' + } + if o.state == 1 { + return 'Option{ none }' + } + return 'Option{ error: "$o.err" }' +} + +fn trace_error(x string) { + eprintln('> ${@FN} | $x') +} + +// error returns a default error instance containing the error given in `message`. +// Example: `if ouch { return error('an error occurred') }` +[inline] +pub fn error(message string) IError { + // trace_error(message) + return &Error{ + msg: message + } +} + +// error_with_code returns a default error instance containing the given `message` and error `code`. +// `if ouch { return error_with_code('an error occurred', 1) }` +[inline] +pub fn error_with_code(message string, code int) IError { + // trace_error('$message | code: $code') + return &Error{ + msg: message + code: code + } +} diff --git a/v_windows/v/vlib/builtin/js/byte.js.v b/v_windows/v/vlib/builtin/js/byte.js.v new file mode 100644 index 0000000..af1803b --- /dev/null +++ b/v_windows/v/vlib/builtin/js/byte.js.v @@ -0,0 +1,16 @@ +module builtin + +pub fn (b byte) is_space() bool { + mut result := false + #result.val = /^\s*$/.test(String.fromCharCode(b)) + + return result +} + +pub fn (c byte) is_letter() bool { + result := false + + #result.val = (c.val >= `a`.charCodeAt() && c.val <= `z`.charCodeAt()) || (c.val >= `A`.charCodeAt() && c.val <= `Z`.charCodeAt()) + + return result +} diff --git a/v_windows/v/vlib/builtin/js/int.js.v b/v_windows/v/vlib/builtin/js/int.js.v new file mode 100644 index 0000000..08e52a9 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/int.js.v @@ -0,0 +1,8 @@ +module builtin + +pub fn (i int) str() string { + mut res := '' + #res = new builtin.string( i ) + + return res +} diff --git a/v_windows/v/vlib/builtin/js/jsfns.js.v b/v_windows/v/vlib/builtin/js/jsfns.js.v new file mode 100644 index 0000000..277c702 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/jsfns.js.v @@ -0,0 +1,125 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// This file contains JS functions present in both node and the browser. +// They have been ported from their TypeScript definitions. + +module builtin + +pub struct JS.Number {} + +pub struct JS.String { + length JS.Number +} + +pub struct JS.Boolean {} + +pub struct JS.Array { + length JS.Number +} + +pub struct JS.Map {} + +// browser: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Error +// node: https://nodejs.org/api/errors.html#errors_class_error +pub struct JS.Error { +pub: + name string + message string + stack string +} + +// Type prototype functions +fn (v JS.String) toString() JS.String +fn (v JS.Number) toString() JS.String +fn (v JS.Boolean) toString() JS.String +fn (v JS.Array) toString() JS.String +fn (v JS.Map) toString() JS.String + +// Hack for "`[]JS.String` is not a struct" when returning arr.length or arr.len +// TODO: Fix []JS.String not a struct error +fn native_str_arr_len(arr []JS.String) int { + len := 0 + #len = arr.length + + return len +} + +// Top level functions +fn JS.eval(string) any +fn JS.parseInt(string, f64) JS.Number +fn JS.parseFloat(string) JS.Number +fn JS.isNaN(f64) bool +fn JS.isFinite(f64) bool +fn JS.decodeURI(string) string +fn JS.decodeURIComponent(string) string +fn JS.encodeURI(string) string + +type EncodeURIComponentArg = bool | f64 | string + +fn JS.encodeURIComponent(EncodeURIComponentArg) string +fn JS.escape(string) string +fn JS.unescape(string) string + +// console +fn JS.console.assert(bool, ...any) +fn JS.console.clear() +fn JS.console.count(string) +fn JS.console.countReset(string) +fn JS.console.debug(...any) +fn JS.console.dir(any, any) +fn JS.console.dirxml(...any) +fn JS.console.error(...any) +fn JS.console.exception(string, ...any) +fn JS.console.group(...any) +fn JS.console.groupCollapsed(...any) +fn JS.console.groupEnd() +fn JS.console.info(...any) +fn JS.console.log(...any) +fn JS.console.table(any, []string) +fn JS.console.time(string) +fn JS.console.timeEnd(string) +fn JS.console.timeLog(string, ...any) +fn JS.console.timeStamp(string) +fn JS.console.trace(...any) +fn JS.console.warn(...any) + +// Math +fn JS.Math.abs(f64) f64 +fn JS.Math.acos(f64) f64 +fn JS.Math.asin(f64) f64 +fn JS.Math.atan(f64) f64 +fn JS.Math.atan2(f64, f64) f64 +fn JS.Math.ceil(f64) f64 +fn JS.Math.cos(f64) f64 +fn JS.Math.exp(f64) f64 +fn JS.Math.floor(f64) f64 +fn JS.Math.log(f64) f64 +fn JS.Math.max(...f64) f64 +fn JS.Math.min(...f64) f64 +fn JS.Math.pow(f64, f64) f64 +fn JS.Math.random() f64 +fn JS.Math.round(f64) f64 +fn JS.Math.sin(f64) f64 +fn JS.Math.sqrt(f64) f64 +fn JS.Math.tan(f64) f64 + +// JSON +fn JS.JSON.stringify(any) string +fn JS.JSON.parse(string) any + +// String +fn (v JS.String) slice(a int, b int) JS.String +fn (v JS.String) split(dot JS.String) []JS.String +fn (s JS.String) indexOf(needle JS.String) int +fn (s JS.String) lastIndexOf(needle JS.String) int + +fn (s JS.String) charAt(i int) JS.String +fn (s JS.String) charCodeAt(i int) byte +fn (s JS.String) toUpperCase() JS.String +fn (s JS.String) toLowerCase() JS.String +fn (s JS.String) concat(a JS.String) JS.String +fn (s JS.String) includes(substr JS.String) bool +fn (s JS.String) endsWith(substr JS.String) bool +fn (s JS.String) startsWith(substr JS.String) bool diff --git a/v_windows/v/vlib/builtin/js/jsfns_browser.js.v b/v_windows/v/vlib/builtin/js/jsfns_browser.js.v new file mode 100644 index 0000000..72daec3 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/jsfns_browser.js.v @@ -0,0 +1,59 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// This file contains JS functions only present in the browser. +// They have been ported from their TypeScript definitions. + +module builtin + +// Window +fn JS.atob(string) string +fn JS.btoa(string) string +fn JS.clearInterval(int) +fn JS.clearTimeout(int) + +// fn JS.createImageBitmap(ImageBitmapSource, ImageBitmapOptions) Promise +// fn JS.createImageBitmap(ImageBitmapSource, int, int, int, int, ImageBitmapOptions) Promise + +// TODO: js async attribute +// [js_async] +// fn JS.fetch(RequestInfo, RequestInit) Promise +fn JS.queueMicrotask(fn ()) +fn JS.setInterval(any, int, ...any) int +fn JS.setTimeout(any, int, ...any) int + +fn JS.alert(any) +fn JS.blur() +fn JS.captureEvents() +fn JS.close() +fn JS.confirm(string) bool + +// fn JS.departFocus(NavigationReason, FocusNavigationOrigin) +fn JS.focus() + +// fn JS.getComputedStyle(Element, string | null) CSSStyleDeclaration +// fn JS.getMatchedCSSRules(Element, string | null) CSSRuleList +// fn JS.getSelection() Selection | null +// fn JS.matchMedia(string) MediaQueryList +fn JS.moveBy(int, int) +fn JS.moveTo(int, int) +fn JS.msWriteProfilerMark(string) + +// fn JS.open(string, string, string, bool) ?Window +// fn JS.postMessage(any, string, []Transferable) +fn JS.print() +fn JS.prompt(string, string) ?string +fn JS.releaseEvents() +fn JS.resizeBy(int, int) +fn JS.resizeTo(int, int) + +// fn JS.scroll(ScrollToOptions) +fn JS.scroll(int, int) + +// fn JS.scrollBy(ScrollToOptions) +fn JS.scrollBy(int, int) + +// fn JS.scrollTo(ScrollToOptions) +fn JS.scrollTo(int, int) +fn JS.stop() diff --git a/v_windows/v/vlib/builtin/js/jsfns_node.js.v b/v_windows/v/vlib/builtin/js/jsfns_node.js.v new file mode 100644 index 0000000..6f65629 --- /dev/null +++ b/v_windows/v/vlib/builtin/js/jsfns_node.js.v @@ -0,0 +1,31 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// This file contains JS functions only present in node.js. +// They have been ported from their TypeScript definitions. + +module builtin + +pub struct JS.node_process { +pub: + arch string + argsv []string + env []string + platform string + version string + // TODO: add all properties +} + +// hack to access process properties +pub fn js_node_process() JS.node_process { + #return process + + return JS.node_process{} +} + +fn JS.process.exit(int) +fn JS.process.stdout.write(string) bool +fn JS.process.stdout.writeln(string) bool +fn JS.process.stderr.write(string) bool +fn JS.process.stderr.writeln(string) bool diff --git a/v_windows/v/vlib/builtin/js/map.js.v b/v_windows/v/vlib/builtin/js/map.js.v new file mode 100644 index 0000000..033a5fd --- /dev/null +++ b/v_windows/v/vlib/builtin/js/map.js.v @@ -0,0 +1,26 @@ +module builtin + +struct map { + m JS.Map + len int +} + +// Removes the mapping of a particular key from the map. +[unsafe] +pub fn (mut m map) delete(key voidptr) { + #m.map.delete(key) +} + +pub fn (m &map) free() {} + +#map.prototype[Symbol.iterator] = function () { return this.map[Symbol.iterator](); } + +#map.prototype.toString = function () { +#function fmtKey(key) { return typeof key == 'string' ? '\'' + key + '\'' : key} +#let res = '{' +#for (const entry of this) { +#res += fmtKey(entry[0]) + ': ' + entry[0]; +#} +#res += '}' +#return res; +#} diff --git a/v_windows/v/vlib/builtin/js/string.js.v b/v_windows/v/vlib/builtin/js/string.js.v new file mode 100644 index 0000000..8b2933d --- /dev/null +++ b/v_windows/v/vlib/builtin/js/string.js.v @@ -0,0 +1,720 @@ +module builtin + +pub struct string { +pub: + str JS.String + len int +} + +pub fn (s string) slice(a int, b int) string { + return string(s.str.slice(a, b)) +} + +pub fn (s string) after(dot string) string { + return string(s.str.slice(s.str.lastIndexOf(dot.str) + 1, int(s.str.length))) +} + +pub fn (s string) after_char(dot byte) string { + // TODO: Implement after byte + return s +} + +pub fn (s string) all_after(dot string) string { + return string(s.str.slice(s.str.indexOf(dot.str) + 1, int(s.str.length))) +} + +// why does this exist? +pub fn (s string) all_after_last(dot string) string { + return s.after(dot) +} + +pub fn (s string) all_before(dot string) string { + return string(s.str.slice(0, s.str.indexOf(dot.str))) +} + +pub fn (s string) all_before_last(dot string) string { + return string(s.str.slice(0, s.str.lastIndexOf(dot.str))) +} + +pub fn (s string) bool() bool { + return s == 'true' +} + +pub fn (s string) split(dot string) []string { + mut arr := s.str.split(dot.str).map(string(it)) + #arr = new array(arr) + + return arr +} + +pub fn (s string) bytes() []byte { + sep := '' + mut arr := s.str.split(sep.str).map(it.charCodeAt(0)) + #arr = new array(arr) + + return arr +} + +pub fn (s string) capitalize() string { + part := string(s.str.slice(1, int(s.str.length))) + return string(s.str.charAt(0).toUpperCase().concat(part.str)) +} + +pub fn (s string) clone() string { + return string(s.str) +} + +pub fn (s string) contains(substr string) bool { + return s.str.includes(substr.str) +} + +pub fn (s string) contains_any(chars string) bool { + sep := '' + for x in chars.str.split(sep.str) { + if s.str.includes(x) { + return true + } + } + return false +} + +pub fn (s string) contains_any_substr(chars []string) bool { + for x in chars { + if s.str.includes(x.str) { + return true + } + } + return false +} + +pub fn (s string) count(substr string) int { + // TODO: "error: `[]JS.String` is not a struct" when returning arr.length or arr.len + arr := s.str.split(substr.str) + return native_str_arr_len(arr) +} + +pub fn (s string) ends_with(p string) bool { + mut res := false + #res.val = s.str.endsWith(p.str) + + return res +} + +pub fn (s string) starts_with(p string) bool { + return s.str.startsWith(p.str) +} + +pub fn (s string) fields() []string { + mut res := []string{} + mut word_start := 0 + mut word_len := 0 + mut is_in_word := false + mut is_space := false + for i, c in s { + is_space = c in [32, 9, 10] + if !is_space { + word_len++ + } + if !is_in_word && !is_space { + word_start = i + is_in_word = true + continue + } + if is_space && is_in_word { + res << s[word_start..word_start + word_len] + is_in_word = false + word_len = 0 + word_start = 0 + continue + } + } + if is_in_word && word_len > 0 { + // collect the remainder word at the end + res << s[word_start..s.len] + } + return res +} + +pub fn (s string) find_between(start string, end string) string { + return string(s.str.slice(s.str.indexOf(start.str) + 1, s.str.indexOf(end.str))) +} + +// unnecessary in the JS backend, implemented for api parity. +pub fn (s string) free() {} + +pub fn (s string) hash() int { + mut h := u32(0) + if h == 0 && s.len > 0 { + for c in s { + h = h * 31 + u32(c) + } + } + return int(h) +} + +// int returns the value of the string as an integer `'1'.int() == 1`. +pub fn (s string) int() int { + return int(JS.parseInt(s)) +} + +// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`. +pub fn (s string) i64() i64 { + return i64(JS.parseInt(s)) +} + +// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`. +pub fn (s string) i8() i8 { + return i8(JS.parseInt(s)) +} + +// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`. +pub fn (s string) i16() i16 { + return i16(JS.parseInt(s)) +} + +// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`. +pub fn (s string) f32() f32 { + // return C.atof(&char(s.str)) + return f32(JS.parseFloat(s)) +} + +// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`. +pub fn (s string) f64() f64 { + return f64(JS.parseFloat(s)) +} + +// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`. +pub fn (s string) u16() u16 { + return u16(JS.parseInt(s)) +} + +// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`. +pub fn (s string) u32() u32 { + return u32(JS.parseInt(s)) +} + +// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`. +pub fn (s string) u64() u64 { + return u64(JS.parseInt(s)) +} + +// trim_right strips any of the characters given in `cutset` from the right of the string. +// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V' +pub fn (s string) trim_right(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + + mut pos := s.len - 1 + + for pos >= 0 { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + } + } + if !found { + break + } + pos-- + } + + if pos < 0 { + return '' + } + + return s[..pos + 1] +} + +// trim_left strips any of the characters given in `cutset` from the left of the string. +// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer' +[direct_array_access] +pub fn (s string) trim_left(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + mut pos := 0 + for pos < s.len { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + break + } + } + if !found { + break + } + pos++ + } + return s[pos..] +} + +// trim_prefix strips `str` from the start of the string. +// Example: assert 'WorldHello V'.trim_prefix('World') == 'Hello V' +pub fn (s string) trim_prefix(str string) string { + if s.starts_with(str) { + return s[str.len..] + } + return s.clone() +} + +// trim_suffix strips `str` from the end of the string. +// Example: assert 'Hello VWorld'.trim_suffix('World') == 'Hello V' +pub fn (s string) trim_suffix(str string) string { + if s.ends_with(str) { + return s[..s.len - str.len] + } + return s.clone() +} + +// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`. +pub fn compare_strings(a &string, b &string) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +// compare_strings_reverse returns `1` if `a < b`, `-1` if `a > b` else `0`. +fn compare_strings_reverse(a &string, b &string) int { + if a < b { + return 1 + } + if a > b { + return -1 + } + return 0 +} + +// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`. +fn compare_strings_by_len(a &string, b &string) int { + if a.len < b.len { + return -1 + } + if a.len > b.len { + return 1 + } + return 0 +} + +// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing. +fn compare_lower_strings(a &string, b &string) int { + aa := a.to_lower() + bb := b.to_lower() + return compare_strings(&aa, &bb) +} + +// at returns the byte at index `idx`. +// Example: assert 'ABC'.at(1) == byte(`B`) +fn (s string) at(idx int) byte { + mut result := byte(0) + #result = new byte(s.str.charCodeAt(result)) + + return result +} + +pub fn (s string) to_lower() string { + mut result := '' + #let str = s.str.toLowerCase() + #result = new string(str) + + return result +} + +// TODO: check if that behaves the same as V's own string.replace(old_sub,new_sub): +pub fn (s string) replace(old_sub string, new_sub string) string { + mut result := '' + #result = new string( s.str.replaceAll(old_sub.str, new_sub.str) ) + + return result +} + +pub fn (s string) to_upper() string { + mut result := '' + #let str = s.str.toUpperCase() + #result = new string(str) + + return result +} + +// sort sorts the string array. +pub fn (mut s []string) sort() { + s.sort_with_compare(compare_strings) +} + +// sort_ignore_case sorts the string array using case insesitive comparing. +pub fn (mut s []string) sort_ignore_case() { + s.sort_with_compare(compare_lower_strings) +} + +// sort_by_len sorts the the string array by each string's `.len` length. +pub fn (mut s []string) sort_by_len() { + s.sort_with_compare(compare_strings_by_len) +} + +// str returns a copy of the string +pub fn (s string) str() string { + return s.clone() +} + +pub fn (s string) repeat(count int) string { + mut result := '' + #result = new string(s.str.repeat(count)) + + return result +} + +// TODO(playX): Use this iterator instead of using .split('').map(c => byte(c)) +#function string_iterator(string) { this.stringIteratorFieldIndex = 0; this.stringIteratorIteratedString = string.str; } +#string_iterator.prototype.next = function next() { +#var done = true; +#var value = undefined; +#var position = this.stringIteratorFieldIndex; +#if (position !== -1) { +#var string = this.stringIteratorIteratedString; +#var length = string.length >>> 0; +#if (position >= length) { +#this.stringIteratorFieldIndex = -1; +#} else { +#done = false; +#var first = string.charCodeAt(position); +#if (first < 0xD800 || first > 0xDBFF || position + 1 === length) +#value = new byte(string[position]); +#else { +#value = new byte(string[position]+string[position+1]) +#} +#this.stringIteratorFieldIndex = position + value.length; +#} +#} +#return { +#value, done +#} +#} +#string.prototype[Symbol.iterator] = function () { return new string_iterator(this) } + +// TODO: Make these functions actually work. +// strip_margin allows multi-line strings to be formatted in a way that removes white-space +// before a delimeter. by default `|` is used. +// Note: the delimiter has to be a byte at this time. That means surrounding +// the value in ``. +// +// Example: +// st := 'Hello there, +// |this is a string, +// | Everything before the first | is removed'.strip_margin() +// Returns: +// Hello there, +// this is a string, +// Everything before the first | is removed +pub fn (s string) strip_margin() string { + return s.strip_margin_custom(`|`) +} + +// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|` +[direct_array_access] +pub fn (s string) strip_margin_custom(del byte) string { + mut sep := del + if sep.is_space() { + eprintln('Warning: `strip_margin` cannot use white-space as a delimiter') + eprintln(' Defaulting to `|`') + sep = `|` + } + // don't know how much space the resulting string will be, but the max it + // can be is this big + mut ret := []byte{} + #ret = new array() + + mut count := 0 + for i := 0; i < s.len; i++ { + if s[i] in [10, 13] { + unsafe { + ret[count] = s[i] + } + count++ + // CRLF + if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 { + unsafe { + ret[count] = s[i + 1] + } + count++ + i++ + } + for s[i] != sep { + i++ + if i >= s.len { + break + } + } + } else { + unsafe { + ret[count] = s[i] + } + count++ + } + } + /* + unsafe { + ret[count] = 0 + return ret.vstring_with_len(count) + }*/ + mut result := '' + #for (let x of ret.arr) result.str += String.fromCharCode(x.val) + + return result +} + +// split_nth splits the string based on the passed `delim` substring. +// It returns the first Nth parts. When N=0, return all the splits. +// The last returned element has the remainder of the string, even if +// the remainder contains more `delim` substrings. +[direct_array_access] +pub fn (s string) split_nth(delim string, nth int) []string { + mut res := []string{} + mut i := 0 + + match delim.len { + 0 { + i = 1 + for ch in s { + if nth > 0 && i >= nth { + res << s[i..] + break + } + res << ch.str() + i++ + } + return res + } + 1 { + mut start := 0 + delim_byte := delim[0] + + for i < s.len { + if s[i] == delim_byte { + was_last := nth > 0 && res.len == nth - 1 + if was_last { + break + } + val := s[start..i] //.substr(start, i) + res << val + start = i + delim.len + i = start + } else { + i++ + } + } + + // Then the remaining right part of the string + if nth < 1 || res.len < nth { + res << s[start..] + } + return res + } + else { + mut start := 0 + // Take the left part for each delimiter occurence + for i <= s.len { + is_delim := i + delim.len <= s.len && s[i..i + delim.len] == delim + if is_delim { + was_last := nth > 0 && res.len == nth - 1 + if was_last { + break + } + val := s[start..i] // .substr(start, i) + res << val + start = i + delim.len + i = start + } else { + i++ + } + } + // Then the remaining right part of the string + if nth < 1 || res.len < nth { + res << s[start..] + } + return res + } + } +} + +struct RepIndex { + idx int + val_idx int +} + +// replace_each replaces all occurences of the string pairs given in `vals`. +// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC' +[direct_array_access] +pub fn (s string) replace_each(vals []string) string { + if s.len == 0 || vals.len == 0 { + return s.clone() + } + + if vals.len % 2 != 0 { + eprintln('string.replace_each(): odd number of strings') + return s.clone() + } + + // `rep` - string to replace + // `with_` - string to replace with_ + // Remember positions of all rep strings, and calculate the length + // of the new string to do just one allocation. + + mut idxs := []RepIndex{} + mut idx := 0 + mut new_len := s.len + s_ := s.clone() + #function setCharAt(str,index,chr) { + #if(index > str.length-1) return str; + #return str.substring(0,index) + chr + str.substring(index+1); + #} + + for rep_i := 0; rep_i < vals.len; rep_i = rep_i + 2 { + rep := vals[rep_i] + + mut with_ := vals[rep_i + 1] + with_ = with_ + + for { + idx = s_.index_after(rep, idx) + if idx == -1 { + break + } + + for i in 0 .. rep.len { + mut j_ := i + j_ = j_ + #s_.str = setCharAt(s_.str,idx + i, String.fromCharCode(127)) + } + + rep_idx := RepIndex{ + idx: 0 + val_idx: 0 + } + // todo: primitives should always be copied + #rep_idx.idx = idx.val + #rep_idx.val_idx = new int(rep_i.val) + idxs << rep_idx + idx += rep.len + new_len += with_.len - rep.len + } + } + + if idxs.len == 0 { + return s.clone() + } + + idxs.sort(a.idx < b.idx) + + mut b := '' + #for (let i = 0; i < new_len.val;i++) b.str += String.fromCharCode(127) + + new_len = new_len + mut idx_pos := 0 + mut cur_idx := idxs[idx_pos] + mut b_i := 0 + for i := 0; i < s.len; i++ { + if i == cur_idx.idx { + rep := vals[cur_idx.val_idx] + with_ := vals[cur_idx.val_idx + 1] + for j in 0 .. with_.len { + mut j_ := j + + j_ = j_ + #b.str = setCharAt(b.str,b_i, with_.str[j]) + //#b.str[b_i] = with_.str[j] + b_i++ + } + i += rep.len - 1 + idx_pos++ + if idx_pos < idxs.len { + cur_idx = idxs[idx_pos] + } + } else { + #b.str = setCharAt(b.str,b_i,s.str[i]) //b.str[b_i] = s.str[i] + b_i++ + } + } + + return b +} + +// last_index returns the position of the last occurence of the input string. +fn (s string) last_index_(p string) int { + if p.len > s.len || p.len == 0 { + return -1 + } + mut i := s.len - p.len + for i >= 0 { + mut j := 0 + for j < p.len && s[i + j] == p[j] { + j++ + } + if j == p.len { + return i + } + i-- + } + return -1 +} + +// last_index returns the position of the last occurence of the input string. +pub fn (s string) last_index(p string) ?int { + idx := s.last_index_(p) + if idx == -1 { + return none + } + return idx +} + +pub fn (s string) trim_space() string { + res := '' + #res.str = s.str.trim() + + return res +} + +pub fn (s string) index_after(p string, start int) int { + if p.len > s.len { + return -1 + } + + mut strt := start + if start < 0 { + strt = 0 + } + if start >= s.len { + return -1 + } + mut i := strt + + for i < s.len { + mut j := 0 + mut ii := i + for j < p.len && s[ii] == p[j] { + j++ + ii++ + } + + if j == p.len { + return i + } + i++ + } + return -1 +} + +pub fn (s string) split_into_lines() []string { + mut res := []string{} + #let i = 0 + #s.str.split('\n').forEach((str) => { + #res.arr[i] = new string(str); + #}) + + return res +} diff --git a/v_windows/v/vlib/builtin/linux_bare/libc_impl.v b/v_windows/v/vlib/builtin/linux_bare/libc_impl.v new file mode 100644 index 0000000..a8d6d63 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/libc_impl.v @@ -0,0 +1,162 @@ +module builtin + +[unsafe] +pub fn memcpy(dest &C.void, src &C.void, n size_t) &C.void { + dest_ := unsafe { &byte(dest) } + src_ := unsafe { &byte(src) } + unsafe { + for i in 0 .. int(n) { + dest_[i] = src_[i] + } + } + return unsafe { dest } +} + +[export: 'malloc'] +[unsafe] +fn __malloc(n size_t) &C.void { + return unsafe { malloc(int(n)) } +} + +[unsafe] +fn strlen(_s &C.void) size_t { + s := unsafe { &byte(_s) } + mut i := 0 + for ; unsafe { s[i] } != 0; i++ {} + return size_t(i) +} + +[unsafe] +fn realloc(old_area &C.void, new_size size_t) &C.void { + if old_area == 0 { + return unsafe { malloc(int(new_size)) } + } + if new_size == size_t(0) { + unsafe { free(old_area) } + return 0 + } + old_size := unsafe { *(&u64(old_area - sizeof(u64))) } + if u64(new_size) <= old_size { + return unsafe { old_area } + } else { + new_area := unsafe { malloc(int(new_size)) } + unsafe { memmove(new_area, old_area, size_t(old_size)) } + unsafe { free(old_area) } + return new_area + } +} + +[unsafe] +fn memset(s &C.void, c int, n size_t) &C.void { + mut s_ := unsafe { &char(s) } + for i in 0 .. int(n) { + unsafe { + s_[i] = char(c) + } + } + return unsafe { s } +} + +[unsafe] +fn memmove(dest &C.void, src &C.void, n size_t) &C.void { + dest_ := unsafe { &byte(dest) } + src_ := unsafe { &byte(src) } + mut temp_buf := unsafe { malloc(int(n)) } + for i in 0 .. int(n) { + unsafe { + temp_buf[i] = src_[i] + } + } + + for i in 0 .. int(n) { + unsafe { + dest_[i] = temp_buf[i] + } + } + unsafe { free(temp_buf) } + return unsafe { dest } +} + +[export: 'calloc'] +[unsafe] +fn __calloc(nmemb size_t, size size_t) &C.void { + new_area := unsafe { malloc(int(nmemb) * int(size)) } + unsafe { memset(new_area, 0, nmemb * size) } + return new_area +} + +fn getchar() int { + x := byte(0) + sys_read(0, &x, 1) + return int(x) +} + +fn memcmp(a &C.void, b &C.void, n size_t) int { + a_ := unsafe { &byte(a) } + b_ := unsafe { &byte(b) } + for i in 0 .. int(n) { + if unsafe { a_[i] != b_[i] } { + unsafe { + return a_[i] - b_[i] + } + } + } + return 0 +} + +[export: 'free'] +[unsafe] +fn __free(ptr &C.void) { + err := mm_free(ptr) + if err != .enoerror { + eprintln('free error:') + panic(err) + } +} + +fn vsprintf(str &char, format &char, ap &byte) int { + panic('vsprintf(): string interpolation is not supported in `-freestanding`') +} + +fn vsnprintf(str &char, size size_t, format &char, ap &byte) int { + panic('vsnprintf(): string interpolation is not supported in `-freestanding`') +} + +// not really needed +fn bare_read(buf &byte, count u64) (i64, Errno) { + return sys_read(0, buf, count) +} + +fn bare_print(buf &byte, len u64) { + sys_write(1, buf, len) +} + +fn bare_eprint(buf &byte, len u64) { + sys_write(2, buf, len) +} + +pub fn write(fd i64, buf &byte, count u64) i64 { + x, _ := sys_write(fd, buf, count) + return x +} + +[noreturn] +fn bare_panic(msg string) { + println('V panic' + msg) + exit(1) +} + +fn bare_backtrace() string { + return 'backtraces are not available with `-freestanding`' +} + +[export: 'exit'] +[noreturn] +fn __exit(code int) { + sys_exit(code) +} + +[export: 'qsort'] +fn __qsort(base voidptr, nmemb size_t, size size_t, sort_cb FnSortCB) { + panic('qsort() is not yet implemented in `-freestanding`') +} diff --git a/v_windows/v/vlib/builtin/linux_bare/linux_syscalls.v b/v_windows/v/vlib/builtin/linux_bare/linux_syscalls.v new file mode 100644 index 0000000..b0b2c47 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/linux_syscalls.v @@ -0,0 +1,433 @@ +module builtin + +enum SigIndex { + si_signo = 0x00 + si_code = 0x02 + si_pid = 0x04 + si_uid = 0x05 + si_status = 0x06 + si_size = 0x80 +} + +enum Signo { + sighup = 1 // Hangup. + sigint = 2 // Interactive attention signal. + sigquit = 3 // Quit. + sigill = 4 // Illegal instruction. + sigtrap = 5 // Trace/breakpoint trap. + sigabrt = 6 // Abnormal termination. + sigbus = 7 + sigfpe = 8 // Erroneous arithmetic operation. + sigkill = 9 // Killed. + sigusr1 = 10 + sigsegv = 11 // Invalid access to memory. + sigusr2 = 12 + sigpipe = 13 // Broken pipe. + sigalrm = 14 // Alarm clock. + sigterm = 15 // Termination request. + sigstkflt = 16 + sigchld = 17 + sigcont = 18 + sigstop = 19 + sigtstp = 20 + sigttin = 21 // Background read from control terminal. + sigttou = 22 // Background write to control terminal. + sigurg = 23 + sigxcpu = 24 // CPU time limit exceeded. + sigxfsz = 25 // File size limit exceeded. + sigvtalrm = 26 // Virtual timer expired. + sigprof = 27 // Profiling timer expired. + sigwinch = 28 + sigpoll = 29 + sigsys = 31 +} + +// List of all the errors returned by syscalls +enum Errno { + enoerror = 0x00000000 + eperm = 0x00000001 + enoent = 0x00000002 + esrch = 0x00000003 + eintr = 0x00000004 + eio = 0x00000005 + enxio = 0x00000006 + e2big = 0x00000007 + enoexec = 0x00000008 + ebadf = 0x00000009 + echild = 0x0000000a + eagain = 0x0000000b + enomem = 0x0000000c + eacces = 0x0000000d + efault = 0x0000000e + enotblk = 0x0000000f + ebusy = 0x00000010 + eexist = 0x00000011 + exdev = 0x00000012 + enodev = 0x00000013 + enotdir = 0x00000014 + eisdir = 0x00000015 + einval = 0x00000016 + enfile = 0x00000017 + emfile = 0x00000018 + enotty = 0x00000019 + etxtbsy = 0x0000001a + efbig = 0x0000001b + enospc = 0x0000001c + espipe = 0x0000001d + erofs = 0x0000001e + emlink = 0x0000001f + epipe = 0x00000020 + edom = 0x00000021 + erange = 0x00000022 +} + +enum MemProt { + prot_read = 0x1 + prot_write = 0x2 + prot_exec = 0x4 + prot_none = 0x0 + prot_growsdown = 0x01000000 + prot_growsup = 0x02000000 +} + +enum MapFlags { + map_shared = 0x01 + map_private = 0x02 + map_shared_validate = 0x03 + map_type = 0x0f + map_fixed = 0x10 + map_file = 0x00 + map_anonymous = 0x20 + map_huge_shift = 26 + map_huge_mask = 0x3f +} + +// const ( +// fcntlf_dupfd = 0x00000000 +// fcntlf_exlck = 0x00000004 +// fcntlf_getfd = 0x00000001 +// fcntlf_getfl = 0x00000003 +// fcntlf_getlk = 0x00000005 +// fcntlf_getlk64 = 0x0000000c +// fcntlf_getown = 0x00000009 +// fcntlf_getowner_uids = 0x00000011 +// fcntlf_getown_ex = 0x00000010 +// fcntlf_getsig = 0x0000000b +// fcntlf_ofd_getlk = 0x00000024 +// fcntlf_ofd_setlk = 0x00000025 +// fcntlf_ofd_setlkw = 0x00000026 +// fcntlf_owner_pgrp = 0x00000002 +// fcntlf_owner_pid = 0x00000001 +// fcntlf_owner_tid = 0x00000000 +// fcntlf_rdlck = 0x00000000 +// fcntlf_setfd = 0x00000002 +// fcntlf_setfl = 0x00000004 +// fcntlf_setlk = 0x00000006 +// fcntlf_setlk64 = 0x0000000d +// fcntlf_setlkw = 0x00000007 +// fcntlf_setlkw64 = 0x0000000e +// fcntlf_setown = 0x00000008 +// fcntlf_setown_ex = 0x0000000f +// fcntlf_setsig = 0x0000000a +// fcntlf_shlck = 0x00000008 +// fcntlf_unlck = 0x00000002 +// fcntlf_wrlck = 0x00000001 +// fcntllock_ex = 0x00000002 +// fcntllock_mand = 0x00000020 +// fcntllock_nb = 0x00000004 +// fcntllock_read = 0x00000040 +// fcntllock_rw = 0x000000c0 +// fcntllock_sh = 0x00000001 +// fcntllock_un = 0x00000008 +// fcntllock_write = 0x00000080 +// fcntlo_accmode = 0x00000003 +// fcntlo_append = 0x00000400 +// fcntlo_cloexec = 0x00080000 +// fcntlo_creat = 0x00000040 +// fcntlo_direct = 0x00004000 +// fcntlo_directory = 0x00010000 +// fcntlo_dsync = 0x00001000 +// fcntlo_excl = 0x00000080 +// fcntlo_largefile = 0x00008000 +// fcntlo_ndelay = 0x00000800 +// fcntlo_noatime = 0x00040000 +// fcntlo_noctty = 0x00000100 +// fcntlo_nofollow = 0x00020000 +// fcntlo_nonblock = 0x00000800 +// fcntlo_path = 0x00200000 +// fcntlo_rdonly = 0x00000000 +// fcntlo_rdwr = 0x00000002 +// fcntlo_trunc = 0x00000200 +// fcntlo_wronly = 0x00000001 +// ) + +/* +Paraphrased from "man 2 waitid" on Linux + + Upon successful return, waitid() fills in the + following fields of the siginfo_t structure + pointed to by infop: + + si_pid, offset 0x10, int index 0x04: + The process ID of the child. + + si_uid: offset 0x14, int index 0x05 + The real user ID of the child. + + si_signo: offset 0x00, int index 0x00 + Always set to SIGCHLD. + + si_status: ofset 0x18, int index 0x06 + 1 the exit status of the child, as given to _exit(2) + (or exit(3)) (sc_sys.cld_exited) + 2 the signal that caused the child to terminate, stop, + or continue. + 3 The si_code field can be used to determine how to + interpret this field. + + si_code, set to one of (enum Wi_si_code), offset 0x08, int index 0x02: + CLD_EXITED (child called _exit(2)); + CLD_KILLED (child killed by signal); + CLD_DUMPED (child killed by signal, and dumped core); + CLD_STOPPED (child stopped by signal); + CLD_TRAPPED (traced child has trapped); + CLD_CONTINUED (child continued by SIGCONT). +*/ + +const ( + wp_sys_wnohang = u64(0x00000001) + wp_sys_wuntraced = u64(0x00000002) + wp_sys_wstopped = u64(0x00000002) + wp_sys_wexited = u64(0x00000004) + wp_sys_wcontinued = u64(0x00000008) + wp_sys_wnowait = u64(0x01000000) // don't reap, just poll status. + wp_sys___wnothread = u64(0x20000000) // don't wait on children of other threads in this group + wp_sys___wall = u64(0x40000000) // wait on all children, regardless of type + wp_sys___wclone = u64(0x80000000) // wait only on non-sigchld children +) + +// First argument to waitid: +enum WiWhich { + p_all = 0 + p_pid = 1 + p_pgid = 2 +} + +enum WiSiCode { + cld_exited = 1 // child has exited + cld_killed = 2 // child was killed + cld_dumped = 3 // child terminated abnormally + cld_trapped = 4 // traced child has trapped + cld_stopped = 5 // child has stopped + cld_continued = 6 // stopped child has continued +} + +fn split_int_errno(rc_in u64) (i64, Errno) { + rc := i64(rc_in) + if rc < 0 { + return i64(-1), Errno(-rc) + } + return rc, Errno.enoerror +} + +// 0 sys_read +fn sys_read(fd i64, buf &byte, count u64) (i64, Errno) { + return split_int_errno(sys_call3(0, u64(fd), u64(buf), count)) +} + +// 1 sys_write +pub fn sys_write(fd i64, buf &byte, count u64) (i64, Errno) { + return split_int_errno(sys_call3(1, u64(fd), u64(buf), count)) +} + +// 2 sys_open +fn sys_open(filename &byte, flags i64, mode int) (i64, Errno) { + return split_int_errno(sys_call3(2, u64(filename), u64(flags), u64(mode))) +} + +// 3 sys_close +fn sys_close(fd i64) Errno { + return Errno(-i64(sys_call1(3, u64(fd)))) +} + +// 9 sys_mmap +fn sys_mmap(addr &byte, len u64, prot MemProt, flags MapFlags, fildes u64, off u64) (&byte, Errno) { + rc := sys_call6(9, u64(addr), len, u64(prot), u64(flags), fildes, off) + a, e := split_int_errno(rc) + return &byte(a), e +} + +// 11 sys_munmap +fn sys_munmap(addr voidptr, len u64) Errno { + return Errno(-sys_call2(11, u64(addr), len)) +} + +// 22 sys_pipe +fn sys_pipe(filedes &int) Errno { + return Errno(sys_call1(22, u64(filedes))) +} + +// 24 sys_sched_yield +fn sys_sched_yield() Errno { + return Errno(sys_call0(24)) +} + +// 28 sys_madvise +fn sys_madvise(addr voidptr, len u64, advice int) Errno { + return Errno(sys_call3(28, u64(addr), len, u64(advice))) +} + +// 39 sys_getpid +fn sys_getpid() int { + return int(sys_call0(39)) +} + +// 57 sys_fork +fn sys_fork() int { + return int(sys_call0(57)) +} + +// 58 sys_vfork +fn sys_vfork() int { + return int(sys_call0(58)) +} + +// 33 sys_dup2 +fn sys_dup2(oldfd int, newfd int) (i64, Errno) { + return split_int_errno(sys_call2(33, u64(oldfd), u64(newfd))) +} + +// 59 sys_execve +fn sys_execve(filename &byte, argv []&byte, envp []&byte) int { + return int(sys_call3(59, u64(filename), argv.data, envp.data)) +} + +// 60 sys_exit +[noreturn] +fn sys_exit(ec int) { + sys_call1(60, u64(ec)) + for {} +} + +// 102 sys_getuid +fn sys_getuid() int { + return int(sys_call0(102)) +} + +// 247 sys_waitid +fn sys_waitid(which WiWhich, pid int, infop &int, options int, ru voidptr) Errno { + return Errno(sys_call5(247, u64(which), u64(pid), u64(infop), u64(options), u64(ru))) +} + +fn sys_call0(scn u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + } + return res +} + +fn sys_call1(scn u64, arg1 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + } + return res +} + +fn sys_call2(scn u64, arg1 u64, arg2 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + } + return res +} + +fn sys_call3(scn u64, arg1 u64, arg2 u64, arg3 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + } + return res +} + +fn sys_call4(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + ; r10 + } + return res +} + +fn sys_call5(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + ; r10 + r8 + } + return res +} + +fn sys_call6(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64, arg6 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + mov r9, arg6 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + r (arg6) + ; r10 + r8 + r9 + } + return res +} + +asm amd64 { + .globl _start + _start: + call main + mov rax, 60 + xor rdi, rdi + syscall + ret +} diff --git a/v_windows/v/vlib/builtin/linux_bare/memory_managment.v b/v_windows/v/vlib/builtin/linux_bare/memory_managment.v new file mode 100644 index 0000000..9c23dcf --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/memory_managment.v @@ -0,0 +1,29 @@ +module builtin + +fn mm_alloc(size u64) (&byte, Errno) { + // BEGIN CONSTS + // the constants need to be here, since the initialization of other constants, + // which happen before these ones would, require malloc + mem_prot := MemProt(int(MemProt.prot_read) | int(MemProt.prot_write)) + map_flags := MapFlags(int(MapFlags.map_private) | int(MapFlags.map_anonymous)) + // END CONSTS + + a, e := sys_mmap(&byte(0), size + sizeof(u64), mem_prot, map_flags, -1, 0) + if e == .enoerror { + unsafe { + mut ap := &u64(a) + *ap = size + x2 := &byte(a + sizeof(u64)) + return x2, e + } + } + return &byte(0), e +} + +fn mm_free(addr &byte) Errno { + unsafe { + ap := &u64(addr - sizeof(u64)) + size := *ap + return sys_munmap(addr - sizeof(u64), size + sizeof(u64)) + } +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/.gitignore b/v_windows/v/vlib/builtin/linux_bare/old/.checks/.gitignore new file mode 100644 index 0000000..4132964 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/.gitignore @@ -0,0 +1,5 @@ +checks +linuxsys/linuxsys +string/string +consts/consts +structs/structs diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/checks.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/checks.v new file mode 100644 index 0000000..8fd3575 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/checks.v @@ -0,0 +1,32 @@ +module main + +import os + +fn failed (msg string) { + println ("!!! failed: $msg") +} + +fn passed (msg string) { + println (">>> passed: $msg") +} + + +fn vcheck(vfile string) { + run_check := "v -user_mod_path . -freestanding run " + if 0 == os.system("$run_check $vfile/${vfile}.v") { + passed(run_check) + } else { + failed(run_check) + } + os.system("ls -lh $vfile/$vfile") + os.system("rm -f $vfile/$vfile") +} + +fn main() { + vcheck("linuxsys") + vcheck("string") + vcheck("consts") + vcheck("structs") + exit(0) +} + diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/consts/consts.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/consts/consts.v new file mode 100644 index 0000000..eee2c61 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/consts/consts.v @@ -0,0 +1,22 @@ +module main +import forkedtest + +const ( + integer1 = 111 + integer2 = 222 + integer3 = integer1+integer2 + integer9 = integer3 * 3 + abc = "123" +) + +fn check_const_initialization() { + assert abc == "123" + assert integer9 == 999 +} + +fn main(){ + mut fails := 0 + fails += forkedtest.normal_run(check_const_initialization, "check_const_initialization") + assert fails == 0 + sys_exit(0) +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/forkedtest/forkedtest.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/forkedtest/forkedtest.v new file mode 100644 index 0000000..6d9272c --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/forkedtest/forkedtest.v @@ -0,0 +1,49 @@ +module forkedtest + +pub fn run (op fn(), label string, code Wi_si_code, status int) int { + child := sys_fork() + if child == 0 { + op() + sys_exit(0) + } + + siginfo := []int{len:int(Sig_index.si_size)} + + e := sys_waitid(.p_pid, child, intptr(&siginfo[0]), .wexited, 0) + + assert e == .enoerror + assert siginfo[int(Sig_index.si_pid)] == child + assert siginfo[int(Sig_index.si_signo)] == int(Signo.sigchld) + assert siginfo[int(Sig_index.si_uid)] == sys_getuid() + + r_code := siginfo[Sig_index.si_code] + r_status := siginfo[Sig_index.si_status] + + print("+++ ") + print(label) + if (int(code) == r_code) && (status == r_status) { + println(" PASSED") + return 0 + } + println(" FAILED") + + if int(code) != r_code { + print(">> Expecting si_code 0x") + println(i64_str(int(code),16)) + print(">> Got 0x") + println(i64_str(r_code,16)) + } + + if status != r_status { + print(">> Expecting status 0x") + println(i64_str(status,16)) + print(">> Got 0x") + println(i64_str(r_status,16)) + } + + return 1 +} + +pub fn normal_run (op fn(), label string) int { + return run (op, label, .cld_exited, 0) +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/linuxsys/linuxsys.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/linuxsys/linuxsys.v new file mode 100644 index 0000000..cca5cb6 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/linuxsys/linuxsys.v @@ -0,0 +1,300 @@ +module main +import forkedtest + +const ( + sample_text_file1 = "" +) + +fn check_fork_minimal () { + child := sys_fork() + ec := 100 + if child == 0 { + println("child") + sys_exit(ec) + } + siginfo := [ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0] + + e := sys_waitid(.p_pid, child, intptr(siginfo.data) , .wexited, 0) + + assert e == .enoerror + //println(i64_tos(buffer0,80,siginfo[Sig_index.si_code],16)) + assert siginfo[Sig_index.si_code] == int(Wi_si_code.cld_exited) + assert siginfo[Sig_index.si_pid] == child + assert siginfo[Sig_index.si_status] == ec + assert siginfo[Sig_index.si_signo] == int(Signo.sigchld) + assert siginfo[Sig_index.si_uid] == sys_getuid() +} + +fn check_read_write_pipe() { + // Checks the following system calls: + // sys_pipe + // sys_write + // sys_read + // sys_close + // + buffer0 := []byte{len:(128)} + buffer := byteptr(buffer0.data) + + fd := [-1, -1] + + assert fd[0] == -1 + assert fd[1] == -1 + + a := sys_pipe(intptr(&fd[0])) + + assert a == .enoerror + + assert fd[0] != -1 + assert fd[1] != -1 + + test_data := "test_data" + b := test_data.len + 1 + c1, e1 := sys_write (fd[1], test_data.str, u64(b)) + + assert e1 == .enoerror + assert c1 == b + + c2, e2 := sys_read(fd[0], buffer, u64(b)) + + assert e2 == .enoerror + assert c2 == b + + assert buffer[b-1] == 0 + + for i in 0..b { + assert test_data[i] == buffer[i] + } + + assert sys_close(fd[0]) == .enoerror + assert sys_close(fd[1]) == .enoerror + + assert sys_close(-1) == .ebadf +} + +fn check_read_file() { + /* + Checks the following system calls: + sys_read + sys_write + sys_close + sys_open + */ + buffer0 := []byte{len:(128)} + buffer := byteptr(buffer0.data) + + test_file := "sample_text1.txt" + sample_text := "Do not change this text.\n" + fd, ec := sys_open(test_file.str, .o_rdonly, 0) + assert fd > 0 + assert ec == .enoerror + n := sample_text.len + c, e := sys_read(fd, buffer, u64(n*2)) + assert e == .enoerror + assert c == n + for i in 0..n { + assert sample_text[i] == buffer[i] + } + assert sys_close(fd) == .enoerror +} + +fn check_open_file_fail() { + fd1, ec1 := sys_open("./nofilehere".str, .o_rdonly, 0) + assert fd1 == -1 + assert ec1 == .enoent +} + +/* +fn check_print() { + println ("checking print and println") + + a := sys_pipe(intptr(fd)) + assert a != -1 + assert fd[0] != -1 + assert fd[1] != -1 + + //sys_dup2 + println ("print and println passed") +} +*/ + +fn check_munmap_fail() { + ec := sys_munmap(-16384,8192) + assert ec == .einval +} + +fn check_mmap_one_page() { + mp := int(Mm_prot.prot_read) | int(Mm_prot.prot_write) + mf := int(Map_flags.map_private) | int(Map_flags.map_anonymous) + mut a, e := sys_mmap(0, u64(Linux_mem.page_size), Mm_prot(mp), Map_flags(mf), -1, 0) + + assert e == .enoerror + assert a != byteptr(-1) + + for i in 0..int(Linux_mem.page_size) { + b := i & 0xFF + a[i] = b + assert a[i] == b + } + + ec := sys_munmap(a, u64(Linux_mem.page_size)) + assert ec == .enoerror +} + +fn check_mm_pages() { + for i in 0 .. int(Linux_mem.page_size)-4 { + assert u32(1) == mm_pages(u64(i)) + } + for i in int(Linux_mem.page_size)-3 .. (int(Linux_mem.page_size)*2)-4 { + assert u32(2) == mm_pages(u64(i)) + } + for i in (int(Linux_mem.page_size)*2)-3 .. (int(Linux_mem.page_size)*3)-4 { + assert u32(3) == mm_pages(u64(i)) + } +} + +//pub fn mm_alloc(size u64) (voidptr, Errno) + +fn check_mm_alloc() { + for i in 1 .. 2000 { + size := u64(i*1000) + pages := mm_pages(size) + mut a, e := mm_alloc(size) + + assert e == .enoerror + ap := intptr(a-4) + assert *ap == int(pages) + assert e == .enoerror + assert !isnil(a) + + if (i%111) == 0 { + for j in 0 .. int(size) { + b := j & 0xFF + a[j] = b + assert b == int(a[j]) + } + } + + mfa := mm_free(a) + + assert mfa == .enoerror + } +} + +fn check_int_array_ro() { + a := [100,110,120,130] + assert a.len == 4 + assert a[0] == 100 + assert a[1] == 110 + assert a[2] == 120 + assert a[3] == 130 +} + +fn check_int_array_rw() { + mut a := [-10,-11,-12,-13] + assert a.len == 4 + assert a[0] == -10 + assert a[1] == -11 + assert a[2] == -12 + assert a[3] == -13 + for i in 0..a.len { + b := -a[i] * 10 + a[i] = b + assert a[i] == b + } + assert a[3] == 130 +} + +fn check_int64_array_ro() { + a := [i64(1000),1100,1200,1300,1400] + assert a.len == 5 + assert a[0] == 1000 + assert a[1] == 1100 + assert a[2] == 1200 + assert a[3] == 1300 + assert a[4] == 1400 +} + +fn check_voidptr_array_ro() { + a := [ + voidptr(10000), + voidptr(11000), + voidptr(12000), + voidptr(13000), + voidptr(14000), + voidptr(15000) + ] + assert a.len == 6 + assert a[0] == voidptr(10000) + assert a[1] == voidptr(11000) + assert a[2] == voidptr(12000) + assert a[3] == voidptr(13000) + assert a[4] == voidptr(14000) + assert a[5] == voidptr(15000) +} + +fn check_voidptr_array_rw() { + mut a := [ + voidptr(-1), + voidptr(-1), + voidptr(-1), + voidptr(-1), + voidptr(-1), + voidptr(-1) + ] + assert a.len == 6 + + assert a[0] == voidptr(-1) + assert a[1] == voidptr(-1) + assert a[2] == voidptr(-1) + assert a[3] == voidptr(-1) + assert a[4] == voidptr(-1) + assert a[5] == voidptr(-1) + + a[0] = voidptr(100000) + assert a[0] == voidptr(100000) + + a[1] = voidptr(110000) + assert a[1] == voidptr(110000) + + a[2] = voidptr(120000) + assert a[2] == voidptr(120000) + + a[3] = voidptr(130000) + assert a[3] == voidptr(130000) + + a[4] = voidptr(140000) + assert a[4] == voidptr(140000) + + a[5] = voidptr(150000) + assert a[5] == voidptr(150000) +} + + +fn main() { + mut fails := 0 + fails += forkedtest.normal_run(check_fork_minimal, "check_fork_minimal") + fails += forkedtest.normal_run(check_munmap_fail, "check_munmap_fail") + fails += forkedtest.normal_run(check_mmap_one_page, "check_mmap_one_page") + fails += forkedtest.normal_run(check_mm_pages, "check_mm_pages") + fails += forkedtest.normal_run(check_mm_alloc, "check_mm_alloc") + fails += forkedtest.normal_run(check_read_write_pipe, "check_read_write_pipe") + fails += forkedtest.normal_run(check_read_file, "check_read_file") + // check_print() + fails += forkedtest.normal_run(check_open_file_fail, "check_open_file_fail") + fails += forkedtest.normal_run(check_int_array_ro, "check_int_array_ro") + fails += forkedtest.normal_run(check_int_array_rw, "check_int_array_rw") + fails += forkedtest.normal_run(check_int64_array_ro, "check_int64_array_ro") + fails += forkedtest.normal_run(check_voidptr_array_ro, "check_voidptr_array_ro") + fails += forkedtest.normal_run(check_voidptr_array_rw, "check_voidptr_array_rw") + + assert fails == 0 + sys_exit(0) +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/readme.md b/v_windows/v/vlib/builtin/linux_bare/old/.checks/readme.md new file mode 100644 index 0000000..03c1b30 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/readme.md @@ -0,0 +1,5 @@ +In this directory: +``` +v run checks.v +``` + diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/sample_text1.txt b/v_windows/v/vlib/builtin/linux_bare/old/.checks/sample_text1.txt new file mode 100644 index 0000000..f06e75a --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/sample_text1.txt @@ -0,0 +1 @@ +Do not change this text. diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/string/string.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/string/string.v new file mode 100644 index 0000000..54c06e7 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/string/string.v @@ -0,0 +1,63 @@ +module main +import forkedtest + +fn check_string_eq () { + assert "monkey" != "rat" + some_animal := "a bird" + assert some_animal == "a bird" +} + +fn check_i64_tos() { + buffer0 := []byte{len:(128)} + buffer := byteptr(buffer0.data) + + s0 := i64_tos(buffer, 70, 140, 10) + assert s0 == "140" + + s1 := i64_tos(buffer, 70, -160, 10) + assert s1 == "-160" + + s2 := i64_tos(buffer, 70, 65537, 16) + assert s2 == "10001" + + s3 := i64_tos(buffer, 70, -160000, 10) + assert s3 == "-160000" +} + +fn check_i64_str() { + assert "141" == i64_str(141, 10) + assert "-161" == i64_str(-161, 10) + assert "10002" == i64_str(65538, 16) + assert "-160001" == i64_str(-160001, 10) +} + +fn check_str_clone() { + a := i64_str(1234,10) + b := a.clone() + assert a == b + c := i64_str(-6789,10).clone() + assert c == "-6789" +} + +fn check_string_add_works(){ + abc := 'abc' + combined := 'a' + 'b' + 'c' + assert abc.len == combined.len + assert abc[0] == combined[0] + assert abc[1] == combined[1] + assert abc[2] == combined[2] + assert abc[0] == `a` + assert abc == combined +} + +fn main () { + mut fails := 0 + fails += forkedtest.normal_run(check_string_eq, "check_string_eq") + fails += forkedtest.normal_run(check_i64_tos, "check_i64_tos") + fails += forkedtest.normal_run(check_i64_str, "check_i64_str") + fails += forkedtest.normal_run(check_str_clone, "check_str_clone") + fails += forkedtest.normal_run(check_string_add_works, "check_string_add_works") + assert fails == 0 + sys_exit(0) +} + diff --git a/v_windows/v/vlib/builtin/linux_bare/old/.checks/structs/structs.v b/v_windows/v/vlib/builtin/linux_bare/old/.checks/structs/structs.v new file mode 100644 index 0000000..f63285d --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/.checks/structs/structs.v @@ -0,0 +1,42 @@ +module main +import forkedtest + +struct SimpleEmptyStruct{ +} + +struct NonEmptyStruct{ + x int + y int + z int +} + +fn check_simple_empty_struct(){ + s := SimpleEmptyStruct{} + addr_s := &s + str_addr_s := ptr_str( addr_s ) + assert !isnil(addr_s) + assert str_addr_s.len > 3 + println(str_addr_s) +} + +fn check_non_empty_struct(){ + a := NonEmptyStruct{1,2,3} + b := NonEmptyStruct{4,5,6} + assert sizeof(NonEmptyStruct) > 0 + assert sizeof(SimpleEmptyStruct) < sizeof(NonEmptyStruct) + assert a.x == 1 + assert a.y == 2 + assert a.z == 3 + assert b.x + b.y + b.z == 15 + assert ptr_str(&a) != ptr_str(&b) + println('sizeof SimpleEmptyStruct:' + i64_str( sizeof(SimpleEmptyStruct) , 10 )) + println('sizeof NonEmptyStruct:' + i64_str( sizeof(NonEmptyStruct) , 10 )) +} + +fn main(){ + mut fails := 0 + fails += forkedtest.normal_run(check_simple_empty_struct, "check_simple_empty_struct") + fails += forkedtest.normal_run(check_non_empty_struct, "check_non_empty_struct") + assert fails == 0 + sys_exit(0) +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/array_bare.v b/v_windows/v/vlib/builtin/linux_bare/old/array_bare.v new file mode 100644 index 0000000..b92214f --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/array_bare.v @@ -0,0 +1,53 @@ +module builtin + +pub struct array { +pub: + data voidptr + len int + cap int + element_size int +} + +// for now off the stack +fn new_array_from_c_array(len int, cap int, elm_size int, c_array voidptr) array { + arr := array{ + len: len + cap: cap + element_size: elm_size + data: c_array + } + return arr +} + +// Private function. Used to implement array[] operator +fn (a array) get(i int) voidptr { + if i < 0 || i >= a.len { + panic('array.get: index out of range') // FIXME: (i == $i, a.len == $a.len)') + } + return a.data + i * a.element_size +} + +// Private function. Used to implement assigment to the array element. +fn (mut a array) set(i int, val voidptr) { + if i < 0 || i >= a.len { + panic('array.set: index out of range') // FIXME: (i == $i, a.len == $a.len)') + } + mem_copy(a.data + a.element_size * i, val, a.element_size) +} + +// array.repeat returns new array with the given array elements +// repeated `nr_repeat` times +pub fn (a array) repeat(nr_repeats int) array { + assert nr_repeats >= 0 + + arr := array{ + len: nr_repeats * a.len + cap: nr_repeats * a.len + element_size: a.element_size + data: malloc(nr_repeats * a.len * a.element_size) + } + for i in 0 .. nr_repeats { + mem_copy(arr.data + i * a.len * a.element_size, a.data, a.len * a.element_size) + } + return arr +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/builtin_bare.v b/v_windows/v/vlib/builtin/linux_bare/old/builtin_bare.v new file mode 100644 index 0000000..a7be853 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/builtin_bare.v @@ -0,0 +1,60 @@ +module builtin + +// called by the generated main/init +fn init() { +} + +pub fn isnil(p voidptr) bool { + return p == 0 +} + +pub fn print(s string) { + sys_write(1, s.str, u64(s.len)) +} + +pub fn println(s string) { + print(s) + print('\n') +} + +pub fn panic(s string) { + eprint('V panic: ') + eprintln(s) + sys_exit(1) +} + +// replaces panic when -debug arg is passed +fn panic_debug(line_no int, file string, mod string, fn_name string, s string) { + eprintln('================ V panic ================') + eprint(' module: ') + eprintln('mod') + eprint(' function: ') + eprint(fn_name) + eprintln('()') + eprintln(' file: ') + eprintln(file) + // println(' line: ${line_no}') + eprint(' message: ') + eprintln(s) + eprintln('=========================================') + sys_exit(1) +} + +pub fn eprint(s string) { + if isnil(s.str) { + panic('eprint(NIL)') + } + sys_write(2, s.str, u64(s.len)) +} + +pub fn eprint_ln(s string) { + eprint(s) + eprint('\n') +} + +pub fn eprintln(s string) { + if isnil(s.str) { + panic('eprintln(NIL)') + } + eprint_ln(s) +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/linuxsys_bare.v b/v_windows/v/vlib/builtin/linux_bare/old/linuxsys_bare.v new file mode 100644 index 0000000..ddec203 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/linuxsys_bare.v @@ -0,0 +1,759 @@ +module builtin + +pub enum Linux_mem { + page_size = 4096 +} + +pub const ( + wp_sys_wnohang = u64(0x00000001) + wp_sys_wuntraced = u64(0x00000002) + wp_sys_wstopped = u64(0x00000002) + wp_sys_wexited = u64(0x00000004) + wp_sys_wcontinued = u64(0x00000008) + wp_sys_wnowait = u64(0x01000000) // don't reap, just poll status. + wp_sys___wnothread = u64(0x20000000) // don't wait on children of other threads in this group + wp_sys___wall = u64(0x40000000) // wait on all children, regardless of type + wp_sys___wclone = u64(0x80000000) // wait only on non-sigchld children +) + +// First argument to waitid: +pub enum Wi_which { + p_all = 0 + p_pid = 1 + p_pgid = 2 +} + +pub enum Wi_si_code { + cld_exited = 1 // child has exited + cld_killed = 2 // child was killed + cld_dumped = 3 // child terminated abnormally + cld_trapped = 4 // traced child has trapped + cld_stopped = 5 // child has stopped + cld_continued = 6 // stopped child has continued +} + +/* +Paraphrased from "man 2 waitid" on Linux + + Upon successful return, waitid() fills in the + following fields of the siginfo_t structure + pointed to by infop: + + si_pid, offset 0x10, int index 0x04: + The process ID of the child. + + si_uid: offset 0x14, int index 0x05 + The real user ID of the child. + + si_signo: offset 0x00, int index 0x00 + Always set to SIGCHLD. + + si_status: ofset 0x18, int index 0x06 + 1 the exit status of the child, as given to _exit(2) + (or exit(3)) (sc_sys.cld_exited) + 2 the signal that caused the child to terminate, stop, + or continue. + 3 The si_code field can be used to determine how to + interpret this field. + + si_code, set to one of (enum Wi_si_code), offset 0x08, int index 0x02: + CLD_EXITED (child called _exit(2)); + CLD_KILLED (child killed by signal); + CLD_DUMPED (child killed by signal, and dumped core); + CLD_STOPPED (child stopped by signal); + CLD_TRAPPED (traced child has trapped); + CLD_CONTINUED (child continued by SIGCONT). +*/ + +pub enum Sig_index { + si_signo = 0x00 + si_code = 0x02 + si_pid = 0x04 + si_uid = 0x05 + si_status = 0x06 + si_size = 0x80 +} + +pub enum Signo { + sighup = 1 // Hangup. + sigint = 2 // Interactive attention signal. + sigquit = 3 // Quit. + sigill = 4 // Illegal instruction. + sigtrap = 5 // Trace/breakpoint trap. + sigabrt = 6 // Abnormal termination. + sigbus = 7 + sigfpe = 8 // Erroneous arithmetic operation. + sigkill = 9 // Killed. + sigusr1 = 10 + sigsegv = 11 // Invalid access to storage. + sigusr2 = 12 + sigpipe = 13 // Broken pipe. + sigalrm = 14 // Alarm clock. + sigterm = 15 // Termination request. + sigstkflt = 16 + sigchld = 17 + sigcont = 18 + sigstop = 19 + sigtstp = 20 + sigttin = 21 // Background read from control terminal. + sigttou = 22 // Background write to control terminal. + sigurg = 23 + sigxcpu = 24 // CPU time limit exceeded. + sigxfsz = 25 // File size limit exceeded. + sigvtalrm = 26 // Virtual timer expired. + sigprof = 27 // Profiling timer expired. + sigwinch = 28 + sigpoll = 29 + sigsys = 31 +} + +pub const ( + fcntlf_dupfd = 0x00000000 + fcntlf_exlck = 0x00000004 + fcntlf_getfd = 0x00000001 + fcntlf_getfl = 0x00000003 + fcntlf_getlk = 0x00000005 + fcntlf_getlk64 = 0x0000000c + fcntlf_getown = 0x00000009 + fcntlf_getowner_uids = 0x00000011 + fcntlf_getown_ex = 0x00000010 + fcntlf_getsig = 0x0000000b + fcntlf_ofd_getlk = 0x00000024 + fcntlf_ofd_setlk = 0x00000025 + fcntlf_ofd_setlkw = 0x00000026 + fcntlf_owner_pgrp = 0x00000002 + fcntlf_owner_pid = 0x00000001 + fcntlf_owner_tid = 0x00000000 + fcntlf_rdlck = 0x00000000 + fcntlf_setfd = 0x00000002 + fcntlf_setfl = 0x00000004 + fcntlf_setlk = 0x00000006 + fcntlf_setlk64 = 0x0000000d + fcntlf_setlkw = 0x00000007 + fcntlf_setlkw64 = 0x0000000e + fcntlf_setown = 0x00000008 + fcntlf_setown_ex = 0x0000000f + fcntlf_setsig = 0x0000000a + fcntlf_shlck = 0x00000008 + fcntlf_unlck = 0x00000002 + fcntlf_wrlck = 0x00000001 + fcntllock_ex = 0x00000002 + fcntllock_mand = 0x00000020 + fcntllock_nb = 0x00000004 + fcntllock_read = 0x00000040 + fcntllock_rw = 0x000000c0 + fcntllock_sh = 0x00000001 + fcntllock_un = 0x00000008 + fcntllock_write = 0x00000080 + fcntlo_accmode = 0x00000003 + fcntlo_append = 0x00000400 + fcntlo_cloexec = 0x00080000 + fcntlo_creat = 0x00000040 + fcntlo_direct = 0x00004000 + fcntlo_directory = 0x00010000 + fcntlo_dsync = 0x00001000 + fcntlo_excl = 0x00000080 + fcntlo_largefile = 0x00008000 + fcntlo_ndelay = 0x00000800 + fcntlo_noatime = 0x00040000 + fcntlo_noctty = 0x00000100 + fcntlo_nofollow = 0x00020000 + fcntlo_nonblock = 0x00000800 + fcntlo_path = 0x00200000 + fcntlo_rdonly = 0x00000000 + fcntlo_rdwr = 0x00000002 + fcntlo_trunc = 0x00000200 + fcntlo_wronly = 0x00000001 +) + +pub enum Errno { + enoerror = 0x00000000 + e2big = 0x00000007 + eacces = 0x0000000d + eagain = 0x0000000b + ebadf = 0x00000009 + ebusy = 0x00000010 + echild = 0x0000000a + edom = 0x00000021 + eexist = 0x00000011 + efault = 0x0000000e + efbig = 0x0000001b + eintr = 0x00000004 + einval = 0x00000016 + eio = 0x00000005 + eisdir = 0x00000015 + emfile = 0x00000018 + emlink = 0x0000001f + enfile = 0x00000017 + enodev = 0x00000013 + enoent = 0x00000002 + enoexec = 0x00000008 + enomem = 0x0000000c + enospc = 0x0000001c + enotblk = 0x0000000f + enotdir = 0x00000014 + enotty = 0x00000019 + enxio = 0x00000006 + eperm = 0x00000001 + epipe = 0x00000020 + erange = 0x00000022 + erofs = 0x0000001e + espipe = 0x0000001d + esrch = 0x00000003 + etxtbsy = 0x0000001a + exdev = 0x00000012 +} + +pub enum Mm_prot { + prot_read = 0x1 + prot_write = 0x2 + prot_exec = 0x4 + prot_none = 0x0 + prot_growsdown = 0x01000000 + prot_growsup = 0x02000000 +} + +pub enum Map_flags { + map_shared = 0x01 + map_private = 0x02 + map_shared_validate = 0x03 + map_type = 0x0f + map_fixed = 0x10 + map_file = 0x00 + map_anonymous = 0x20 + map_huge_shift = 26 + map_huge_mask = 0x3f +} + +fn sys_call0(scn u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + } + return res +} + +fn sys_call1(scn u64, arg1 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + } + return res +} + +fn sys_call2(scn u64, arg1 u64, arg2 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + } + return res +} + +fn sys_call3(scn u64, arg1 u64, arg2 u64, arg3 u64) u64 { + res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + } + return res +} + +fn sys_call4(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + ; r10 + } + return res +} + +fn sys_call5(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + ; r10 + r8 + } + return res +} + +fn sys_call6(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64, arg6 u64) u64 { + res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + mov r9, arg6 + syscall + ; =a (res) + ; a (scn) + D (arg1) + S (arg2) + d (arg3) + r (arg4) + r (arg5) + r (arg6) + ; r10 + r8 + r9 + } + return res +} + +fn split_int_errno(rc_in u64) (i64, Errno) { + rc := i64(rc_in) + if rc < 0 { + return i64(-1), Errno(-rc) + } + return rc, Errno.enoerror +} + +// 0 sys_read unsigned int fd char *buf size_t count +pub fn sys_read(fd i64, buf &byte, count u64) (i64, Errno) { + return split_int_errno(sys_call3(0, u64(fd), u64(buf), count)) +} + +// 1 sys_write unsigned int fd, const char *buf, size_t count +pub fn sys_write(fd i64, buf &byte, count u64) (i64, Errno) { + return split_int_errno(sys_call3(1, u64(fd), u64(buf), count)) +} + +pub fn sys_open(filename &byte, flags i64, mode int) (i64, Errno) { + // 2 sys_open const char *filename int flags int mode + return split_int_errno(sys_call3(2, u64(filename), u64(flags), u64(mode))) +} + +pub fn sys_close(fd i64) Errno { + // 3 sys_close unsigned int fd + return Errno(-i64(sys_call1(3, u64(fd)))) +} + +// 9 sys_mmap unsigned long addr unsigned long len unsigned long prot unsigned long flags unsigned long fd unsigned long off +pub fn sys_mmap(addr &byte, len u64, prot Mm_prot, flags Map_flags, fildes u64, off u64) (&byte, Errno) { + rc := sys_call6(9, u64(addr), len, u64(prot), u64(flags), fildes, off) + a, e := split_int_errno(rc) + return &byte(a), e +} + +pub fn sys_munmap(addr voidptr, len u64) Errno { + // 11 sys_munmap unsigned long addr size_t len + return Errno(-sys_call2(11, u64(addr), len)) +} + +// 22 sys_pipe int *filedes +pub fn sys_pipe(filedes &int) Errno { + return Errno(sys_call1(22, u64(filedes))) +} + +// 24 sys_sched_yield +pub fn sys_sched_yield() Errno { + return Errno(sys_call0(24)) +} + +pub fn sys_madvise(addr voidptr, len u64, advice int) Errno { + // 28 sys_madvise unsigned long start size_t len_in int behavior + return Errno(sys_call3(28, u64(addr), len, u64(advice))) +} + +// 39 sys_getpid +pub fn sys_getpid() int { + return int(sys_call0(39)) +} + +// 57 sys_fork +pub fn sys_fork() int { + return int(sys_call0(57)) +} + +// 58 sys_vfork +pub fn sys_vfork() int { + return int(sys_call0(58)) +} + +// 33 sys_dup2 unsigned int oldfd unsigned int newfd +pub fn sys_dup2(oldfd int, newfd int) (i64, Errno) { + return split_int_errno(sys_call2(33, u64(oldfd), u64(newfd))) +} + +// 59 sys_execve const char *filename const char *const argv[] const char *const envp[] +// pub fn sys_execve(filename byteptr, argv []byteptr, envp []byteptr) int { +// return sys_call3(59, filename, argv, envp) +//} + +// 60 sys_exit int error_code +pub fn sys_exit(ec int) { + sys_call1(60, u64(ec)) +} + +// 102 sys_getuid +pub fn sys_getuid() int { + return int(sys_call0(102)) +} + +// 247 sys_waitid int which pid_t upid struct siginfo *infop int options struct rusage *ru +pub fn sys_waitid(which Wi_which, pid int, infop &int, options int, ru voidptr) Errno { + return Errno(sys_call5(247, u64(which), u64(pid), u64(infop), u64(options), u64(ru))) +} + +/* +A few years old, but still relevant +https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ + +>0 sys_read unsigned int fd char *buf size_t count +>1 sys_write unsigned int fd const char *buf size_t count +>2 sys_open const char *filename int flags int mode +>3 sys_close unsigned int fd +4 sys_stat const char *filename struct stat *statbuf +5 sys_fstat unsigned int fd struct stat *statbuf +6 sys_lstat fconst char *filename struct stat *statbuf +7 sys_poll struct poll_fd *ufds unsigned int nfds long timeout_msecs +8 sys_lseek unsigned int fd off_t offset unsigned int origin +>9 sys_mmap unsigned long addr unsigned long len unsigned long prot unsigned long flags unsigned long fd unsigned long off +10 sys_mprotect unsigned long start size_t len unsigned long prot +>11 sys_munmap unsigned long addr size_t len +12 sys_brk unsigned long brk +13 sys_rt_sigaction int sig const struct sigaction *act struct sigaction *oact size_t sigsetsize +14 sys_rt_sigprocmask int how sigset_t *nset sigset_t *oset size_t sigsetsize +15 sys_rt_sigreturn unsigned long __unused +16 sys_ioctl unsigned int fd unsigned int cmd unsigned long arg +17 sys_pread64 unsigned long fd char *buf size_t count loff_t pos +18 sys_pwrite64 unsigned int fd const char *buf size_t count loff_t pos +19 sys_readv unsigned long fd const struct iovec *vec unsigned long vlen +20 sys_writev unsigned long fd const struct iovec *vec unsigned long vlen +21 sys_access const char *filename int mode +>22 sys_pipe int *filedes +23 sys_select int n fd_set *inp fd_set *outp fd_set*exp struct timeval *tvp +>24 sys_sched_yield +25 sys_mremap unsigned long addr unsigned long old_len unsigned long new_len unsigned long flags unsigned long new_addr +26 sys_msync unsigned long start size_t len int flags +27 sys_mincore unsigned long start size_t len unsigned char *vec +>28 sys_madvise unsigned long start size_t len_in int behavior +29 sys_shmget key_t key size_t size int shmflg +30 sys_shmat int shmid char *shmaddr int shmflg +31 sys_shmctl int shmid int cmd struct shmid_ds *buf +32 sys_dup unsigned int fildes +33 sys_dup2 unsigned int oldfd unsigned int newfd +34 sys_pause +35 sys_nanosleep struct timespec *rqtp struct timespec *rmtp +36 sys_getitimer int which struct itimerval *value +37 sys_alarm unsigned int seconds +38 sys_setitimer int which struct itimerval *value struct itimerval *ovalue +>39 sys_getpid +40 sys_sendfile int out_fd int in_fd off_t *offset size_t count +41 sys_socket int family int type int protocol +42 sys_connect int fd struct sockaddr *uservaddr int addrlen +43 sys_accept int fd struct sockaddr *upeer_sockaddr int *upeer_addrlen +44 sys_sendto int fd void *buff size_t len unsigned flags struct sockaddr *addr int addr_len +45 sys_recvfrom int fd void *ubuf size_t size unsigned flags struct sockaddr *addr int *addr_len +46 sys_sendmsg int fd struct msghdr *msg unsigned flags +47 sys_recvmsg int fd struct msghdr *msg unsigned int flags +48 sys_shutdown int fd int how +49 sys_bind int fd struct sokaddr *umyaddr int addrlen +50 sys_listen int fd int backlog +51 sys_getsockname int fd struct sockaddr *usockaddr int *usockaddr_len +52 sys_getpeername int fd struct sockaddr *usockaddr int *usockaddr_len +53 sys_socketpair int family int type int protocol int *usockvec +54 sys_setsockopt int fd int level int optname char *optval int optlen +55 sys_getsockopt int fd int level int optname char *optval int *optlen +56 sys_clone unsigned long clone_flags unsigned long newsp void *parent_tid void *child_tid +>57 sys_fork +>58 sys_vfork +>59 sys_execve const char *filename const char *const argv[] const char *const envp[] +>60 sys_exit int error_code +61 sys_wait4 pid_t upid int *stat_addr int options struct rusage *ru +62 sys_kill pid_t pid int sig +63 sys_uname struct old_utsname *name +64 sys_semget key_t key int nsems int semflg +65 sys_semop int semid struct sembuf *tsops unsigned nsops +66 sys_semctl int semid int semnum int cmd union semun arg +67 sys_shmdt char *shmaddr +68 sys_msgget key_t key int msgflg +69 sys_msgsnd int msqid struct msgbuf *msgp size_t msgsz int msgflg +70 sys_msgrcv int msqid struct msgbuf *msgp size_t msgsz long msgtyp int msgflg +71 sys_msgctl int msqid int cmd struct msqid_ds *buf +72 sys_fcntl unsigned int fd unsigned int cmd unsigned long arg +73 sys_flock unsigned int fd unsigned int cmd +74 sys_fsync unsigned int fd +75 sys_fdatasync unsigned int fd +76 sys_truncate const char *path long length +77 sys_ftruncate unsigned int fd unsigned long length +78 sys_getdents unsigned int fd struct linux_dirent *dirent unsigned int count +79 sys_getcwd char *buf unsigned long size +80 sys_chdir const char *filename +81 sys_fchdir unsigned int fd +82 sys_rename const char *oldname const char *newname +83 sys_mkdir const char *pathname int mode +84 sys_rmdir const char *pathname +85 sys_creat const char *pathname int mode +86 sys_link const char *oldname const char *newname +87 sys_unlink const char *pathname +88 sys_symlink const char *oldname const char *newname +89 sys_readlink const char *path char *buf int bufsiz +90 sys_chmod const char *filename mode_t mode +91 sys_fchmod unsigned int fd mode_t mode +92 sys_chown const char *filename uid_t user gid_t group +93 sys_fchown unsigned int fd uid_t user gid_t group +94 sys_lchown const char *filename uid_t user gid_t group +95 sys_umask int mask +96 sys_gettimeofday struct timeval *tv struct timezone *tz +97 sys_getrlimit unsigned int resource struct rlimit *rlim +98 sys_getrusage int who struct rusage *ru +99 sys_sysinfo struct sysinfo *info +100 sys_times struct sysinfo *info +101 sys_ptrace long request long pid unsigned long addr unsigned long data +>102 sys_getuid +103 sys_syslog int type char *buf int len +104 sys_getgid +105 sys_setuid uid_t uid +106 sys_setgid gid_t gid +107 sys_geteuid +108 sys_getegid +109 sys_setpgid pid_t pid pid_t pgid +110 sys_getppid +111 sys_getpgrp +112 sys_setsid +113 sys_setreuid uid_t ruid uid_t euid +114 sys_setregid gid_t rgid gid_t egid +115 sys_getgroups int gidsetsize gid_t *grouplist +116 sys_setgroups int gidsetsize gid_t *grouplist +117 sys_setresuid uid_t *ruid uid_t *euid uid_t *suid +118 sys_getresuid uid_t *ruid uid_t *euid uid_t *suid +119 sys_setresgid gid_t rgid gid_t egid gid_t sgid +120 sys_getresgid gid_t *rgid gid_t *egid gid_t *sgid +121 sys_getpgid pid_t pid +122 sys_setfsuid uid_t uid +123 sys_setfsgid gid_t gid +124 sys_getsid pid_t pid +125 sys_capget cap_user_header_t header cap_user_data_t dataptr +126 sys_capset cap_user_header_t header const cap_user_data_t data +127 sys_rt_sigpending sigset_t *set size_t sigsetsize +128 sys_rt_sigtimedwait const sigset_t *uthese siginfo_t *uinfo const struct timespec *uts size_t sigsetsize +129 sys_rt_sigqueueinfo pid_t pid int sig siginfo_t *uinfo +130 sys_rt_sigsuspend sigset_t *unewset size_t sigsetsize +131 sys_sigaltstack const stack_t *uss stack_t *uoss +132 sys_utime char *filename struct utimbuf *times +133 sys_mknod const char *filename umode_t mode unsigned dev +134 sys_uselib NOT IMPLEMENTED +135 sys_personality unsigned int personality +136 sys_ustat unsigned dev struct ustat *ubuf +137 sys_statfs const char *pathname struct statfs *buf +138 sys_fstatfs unsigned int fd struct statfs *buf +139 sys_sysfs int option unsigned long arg1 unsigned long arg2 +140 sys_getpriority int which int who +141 sys_setpriority int which int who int niceval +142 sys_sched_setparam pid_t pid struct sched_param *param +143 sys_sched_getparam pid_t pid struct sched_param *param +144 sys_sched_setscheduler pid_t pid int policy struct sched_param *param +145 sys_sched_getscheduler pid_t pid +146 sys_sched_get_priority_max int policy +147 sys_sched_get_priority_min int policy +148 sys_sched_rr_get_interval pid_t pid struct timespec *interval +149 sys_mlock unsigned long start size_t len +150 sys_munlock unsigned long start size_t len +151 sys_mlockall int flags +152 sys_munlockall +153 sys_vhangup +154 sys_modify_ldt int func void *ptr unsigned long bytecount +155 sys_pivot_root const char *new_root const char *put_old +156 sys__sysctl struct __sysctl_args *args +157 sys_prctl int option unsigned long arg2 unsigned long arg3 unsigned long arg4 unsigned long arg5 +158 sys_arch_prctl struct task_struct *task int code unsigned long *addr +159 sys_adjtimex struct timex *txc_p +160 sys_setrlimit unsigned int resource struct rlimit *rlim +161 sys_chroot const char *filename +162 sys_sync +163 sys_acct const char *name +164 sys_settimeofday struct timeval *tv struct timezone *tz +165 sys_mount char *dev_name char *dir_name char *type unsigned long flags void *data +166 sys_umount2 const char *target int flags +167 sys_swapon const char *specialfile int swap_flags +168 sys_swapoff const char *specialfile +169 sys_reboot int magic1 int magic2 unsigned int cmd void *arg +170 sys_sethostname char *name int len +171 sys_setdomainname char *name int len +172 sys_iopl unsigned int level struct pt_regs *regs +173 sys_ioperm unsigned long from unsigned long num int turn_on +174 sys_create_module REMOVED IN Linux 2.6 +175 sys_init_module void *umod unsigned long len const char *uargs +176 sys_delete_module const chat *name_user unsigned int flags +177 sys_get_kernel_syms REMOVED IN Linux 2.6 +178 sys_query_module REMOVED IN Linux 2.6 +179 sys_quotactl unsigned int cmd const char *special qid_t id void *addr +180 sys_nfsservctl NOT IMPLEMENTED +181 sys_getpmsg NOT IMPLEMENTED +182 sys_putpmsg NOT IMPLEMENTED +183 sys_afs_syscall NOT IMPLEMENTED +184 sys_tuxcall NOT IMPLEMENTED +185 sys_security NOT IMPLEMENTED +186 sys_gettid +187 sys_readahead int fd loff_t offset size_t count +188 sys_setxattr const char *pathname const char *name const void *value size_t size int flags +189 sys_lsetxattr const char *pathname const char *name const void *value size_t size int flags +190 sys_fsetxattr int fd const char *name const void *value size_t size int flags +191 sys_getxattr const char *pathname const char *name void *value size_t size +192 sys_lgetxattr const char *pathname const char *name void *value size_t size +193 sys_fgetxattr int fd const har *name void *value size_t size +194 sys_listxattr const char *pathname char *list size_t size +195 sys_llistxattr const char *pathname char *list size_t size +196 sys_flistxattr int fd char *list size_t size +197 sys_removexattr const char *pathname const char *name +198 sys_lremovexattr const char *pathname const char *name +199 sys_fremovexattr int fd const char *name +200 sys_tkill pid_t pid ing sig +201 sys_time time_t *tloc +202 sys_futex u32 *uaddr int op u32 val struct timespec *utime u32 *uaddr2 u32 val3 +203 sys_sched_setaffinity pid_t pid unsigned int len unsigned long *user_mask_ptr +204 sys_sched_getaffinity pid_t pid unsigned int len unsigned long *user_mask_ptr +205 sys_set_thread_area NOT IMPLEMENTED. Use arch_prctl +206 sys_io_setup unsigned nr_events aio_context_t *ctxp +207 sys_io_destroy aio_context_t ctx +208 sys_io_getevents aio_context_t ctx_id long min_nr long nr struct io_event *events +209 sys_io_submit aio_context_t ctx_id long nr struct iocb **iocbpp +210 sys_io_cancel aio_context_t ctx_id struct iocb *iocb struct io_event *result +211 sys_get_thread_area NOT IMPLEMENTED. Use arch_prctl +212 sys_lookup_dcookie u64 cookie64 long buf long len +213 sys_epoll_create int size +214 sys_epoll_ctl_old NOT IMPLEMENTED +215 sys_epoll_wait_old NOT IMPLEMENTED +216 sys_remap_file_pages unsigned long start unsigned long size unsigned long prot unsigned long pgoff unsigned long flags +217 sys_getdents64 unsigned int fd struct linux_dirent64 *dirent unsigned int count +218 sys_set_tid_address int *tidptr +219 sys_restart_syscall +220 sys_semtimedop int semid struct sembuf *tsops unsigned nsops const struct timespec *timeout +221 sys_fadvise64 int fd loff_t offset size_t len int advice +222 sys_timer_create const clockid_t which_clock struct sigevent *timer_event_spec timer_t *created_timer_id +223 sys_timer_settime timer_t timer_id int flags const struct itimerspec *new_setting struct itimerspec *old_setting +224 sys_timer_gettime timer_t timer_id struct itimerspec *setting +225 sys_timer_getoverrun timer_t timer_id +226 sys_timer_delete timer_t timer_id +227 sys_clock_settime const clockid_t which_clock const struct timespec *tp +228 sys_clock_gettime const clockid_t which_clock struct timespec *tp +229 sys_clock_getres const clockid_t which_clock struct timespec *tp +230 sys_clock_nanosleep const clockid_t which_clock int flags const struct timespec *rqtp struct timespec *rmtp +231 sys_exit_group int error_code +232 sys_epoll_wait int epfd struct epoll_event *events int maxevents int timeout +233 sys_epoll_ctl int epfd int op int fd struct epoll_event *event +234 sys_tgkill pid_t tgid pid_t pid int sig +235 sys_utimes char *filename struct timeval *utimes +236 sys_vserver NOT IMPLEMENTED +237 sys_mbind unsigned long start unsigned long len unsigned long mode unsigned long *nmask unsigned long maxnode unsigned flags +238 sys_set_mempolicy int mode unsigned long *nmask unsigned long maxnode +239 sys_get_mempolicy int *policy unsigned long *nmask unsigned long maxnode unsigned long addr unsigned long flags +240 sys_mq_open const char *u_name int oflag mode_t mode struct mq_attr *u_attr +241 sys_mq_unlink const char *u_name +242 sys_mq_timedsend mqd_t mqdes const char *u_msg_ptr size_t msg_len unsigned int msg_prio const stuct timespec *u_abs_timeout +243 sys_mq_timedreceive mqd_t mqdes char *u_msg_ptr size_t msg_len unsigned int *u_msg_prio const struct timespec *u_abs_timeout +244 sys_mq_notify mqd_t mqdes const struct sigevent *u_notification +245 sys_mq_getsetattr mqd_t mqdes const struct mq_attr *u_mqstat struct mq_attr *u_omqstat +246 sys_kexec_load unsigned long entry unsigned long nr_segments struct kexec_segment *segments unsigned long flags +>247 sys_waitid int which pid_t upid struct siginfo *infop int options struct rusage *ru +248 sys_add_key const char *_type const char *_description const void *_payload size_t plen +249 sys_request_key const char *_type const char *_description const char *_callout_info key_serial_t destringid +250 sys_keyctl int option unsigned long arg2 unsigned long arg3 unsigned long arg4 unsigned long arg5 +251 sys_ioprio_set int which int who int ioprio +252 sys_ioprio_get int which int who +253 sys_inotify_init +254 sys_inotify_add_watch int fd const char *pathname u32 mask +255 sys_inotify_rm_watch int fd __s32 wd +256 sys_migrate_pages pid_t pid unsigned long maxnode const unsigned long *old_nodes const unsigned long *new_nodes +257 sys_openat int dfd const char *filename int flags int mode +258 sys_mkdirat int dfd const char *pathname int mode +259 sys_mknodat int dfd const char *filename int mode unsigned dev +260 sys_fchownat int dfd const char *filename uid_t user gid_t group int flag +261 sys_futimesat int dfd const char *filename struct timeval *utimes +262 sys_newfstatat int dfd const char *filename struct stat *statbuf int flag +263 sys_unlinkat int dfd const char *pathname int flag +264 sys_renameat int oldfd const char *oldname int newfd const char *newname +265 sys_linkat int oldfd const char *oldname int newfd const char *newname int flags +266 sys_symlinkat const char *oldname int newfd const char *newname +267 sys_readlinkat int dfd const char *pathname char *buf int bufsiz +268 sys_fchmodat int dfd const char *filename mode_t mode +269 sys_faccessat int dfd const char *filename int mode +270 sys_pselect6 int n fd_set *inp fd_set *outp fd_set *exp struct timespec *tsp void *sig +271 sys_ppoll struct pollfd *ufds unsigned int nfds struct timespec *tsp const sigset_t *sigmask size_t sigsetsize +272 sys_unshare unsigned long unshare_flags +273 sys_set_robust_list struct robust_list_head *head size_t len +274 sys_get_robust_list int pid struct robust_list_head **head_ptr size_t *len_ptr +275 sys_splice int fd_in loff_t *off_in int fd_out loff_t *off_out size_t len unsigned int flags +276 sys_tee int fdin int fdout size_t len unsigned int flags +277 sys_sync_file_range long fd loff_t offset loff_t bytes long flags +278 sys_vmsplice int fd const struct iovec *iov unsigned long nr_segs unsigned int flags +279 sys_move_pages pid_t pid unsigned long nr_pages const void **pages const int *nodes int *status int flags +280 sys_utimensat int dfd const char *filename struct timespec *utimes int flags +281 sys_epoll_pwait int epfd struct epoll_event *events int maxevents int timeout const sigset_t *sigmask size_t sigsetsize +282 sys_signalfd int ufd sigset_t *user_mask size_t sizemask +283 sys_timerfd_create int clockid int flags +284 sys_eventfd unsigned int count +285 sys_fallocate long fd long mode loff_t offset loff_t len +286 sys_timerfd_settime int ufd int flags const struct itimerspec *utmr struct itimerspec *otmr +287 sys_timerfd_gettime int ufd struct itimerspec *otmr +288 sys_accept4 int fd struct sockaddr *upeer_sockaddr int *upeer_addrlen int flags +289 sys_signalfd4 int ufd sigset_t *user_mask size_t sizemask int flags +290 sys_eventfd2 unsigned int count int flags +291 sys_epoll_create1 int flags +292 sys_dup3 unsigned int oldfd unsigned int newfd int flags +293 sys_pipe2 int *filedes int flags +294 sys_inotify_init1 int flags +295 sys_preadv unsigned long fd const struct iovec *vec unsigned long vlen unsigned long pos_l unsigned long pos_h +296 sys_pwritev unsigned long fd const struct iovec *vec unsigned long vlen unsigned long pos_l unsigned long pos_h +297 sys_rt_tgsigqueueinfo pid_t tgid pid_t pid int sig siginfo_t *uinfo +298 sys_perf_event_open struct perf_event_attr *attr_uptr pid_t pid int cpu int group_fd unsigned long flags +299 sys_recvmmsg int fd struct msghdr *mmsg unsigned int vlen unsigned int flags struct timespec *timeout +300 sys_fanotify_init unsigned int flags unsigned int event_f_flags +301 sys_fanotify_mark long fanotify_fd long flags __u64 mask long dfd long pathname +302 sys_prlimit64 pid_t pid unsigned int resource const struct rlimit64 *new_rlim struct rlimit64 *old_rlim +303 sys_name_to_handle_at int dfd const char *name struct file_handle *handle int *mnt_id int flag +304 sys_open_by_handle_at int dfd const char *name struct file_handle *handle int *mnt_id int flags +305 sys_clock_adjtime clockid_t which_clock struct timex *tx +306 sys_syncfs int fd +307 sys_sendmmsg int fd struct mmsghdr *mmsg unsigned int vlen unsigned int flags +308 sys_setns int fd int nstype +309 sys_getcpu unsigned *cpup unsigned *nodep struct getcpu_cache *unused +310 sys_process_vm_readv pid_t pid const struct iovec *lvec unsigned long liovcnt const struct iovec *rvec unsigned long riovcnt unsigned long flags +311 sys_process_vm_writev pid_t pid const struct iovec *lvec unsigned long liovcnt const struct iovcc *rvec unsigned long riovcnt unsigned long flags +312 sys_kcmp pid_t pid1 pid_t pid2 int type unsigned long idx1 unsigned long idx2 +313 sys_finit_module int fd const char __user *uargs int flags +314 sys_sched_setattr pid_t pid struct sched_attr __user *attr unsigned int flags +315 sys_sched_getattr pid_t pid struct sched_attr __user *attr unsigned int size unsigned int flags +316 sys_renameat2 int olddfd const char __user *oldname int newdfd const char __user *newname unsigned int flags +317 sys_seccomp unsigned int op unsigned int flags const char __user *uargs +318 sys_getrandom char __user *buf size_t count unsigned int flags +319 sys_memfd_create const char __user *uname_ptr unsigned int flags +320 sys_kexec_file_load int kernel_fd int initrd_fd unsigned long cmdline_len const char __user *cmdline_ptr unsigned long flags +321 sys_bpf int cmd union bpf_attr *attr unsigned int size +322 stub_execveat int dfd const char __user *filename const char __user *const __user *argv const char __user *const __user *envp int flags +323 userfaultfd int flags +324 membarrier int cmd int flags +325 mlock2 unsigned long start size_t len int flags +326 copy_file_range int fd_in loff_t __user *off_in int fd_out loff_t __user * off_out size_t len unsigned int flags +327 preadv2 unsigned long fd const struct iovec __user *vec unsigned long vlen unsigned long pos_l unsigned long pos_h int flags +328 pwritev2 unsigned long fd const struct iovec __user *vec unsigned long vlen unsigned long pos_l unsigned long pos_h int flags +*/ diff --git a/v_windows/v/vlib/builtin/linux_bare/old/mm_bare.v b/v_windows/v/vlib/builtin/linux_bare/old/mm_bare.v new file mode 100644 index 0000000..cee5f99 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/mm_bare.v @@ -0,0 +1,58 @@ +module builtin + +const ( + mem_prot = Mm_prot(int(Mm_prot.prot_read) | int(Mm_prot.prot_write)) + mem_flags = Map_flags(int(Map_flags.map_private) | int(Map_flags.map_anonymous)) + page_size = u64(Linux_mem.page_size) +) + +pub fn mm_pages(size u64) u32 { + pages := (size + u64(4) + page_size) / page_size + return u32(pages) +} + +pub fn mm_alloc(size u64) (&byte, Errno) { + pages := mm_pages(size) + n_bytes := u64(pages * u32(Linux_mem.page_size)) + + a, e := sys_mmap(0, n_bytes, mem_prot, mem_flags, -1, 0) + if e == .enoerror { + mut ap := &int(a) + *ap = pages + return &byte(a + 4), e + } + return &byte(0), e +} + +pub fn mm_free(addr &byte) Errno { + ap := &int(addr - 4) + size := u64(*ap) * u64(Linux_mem.page_size) + + return sys_munmap(ap, size) +} + +pub fn mem_copy(dest0 voidptr, src0 voidptr, n int) voidptr { + mut dest := &byte(dest0) + src := &byte(src0) + for i in 0 .. n { + dest[i] = src[i] + } + return dest0 +} + +[unsafe] +pub fn malloc(n int) &byte { + if n < 0 { + panic('malloc(<0)') + } + + ptr, e := mm_alloc(u64(n)) + assert e == .enoerror + assert !isnil(ptr) + return ptr +} + +[unsafe] +pub fn free(ptr voidptr) { + assert mm_free(ptr) == .enoerror +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/string_bare.v b/v_windows/v/vlib/builtin/linux_bare/old/string_bare.v new file mode 100644 index 0000000..8f7edfc --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/string_bare.v @@ -0,0 +1,150 @@ +module builtin + +pub struct string { +pub: + str &byte + len int +} + +pub fn strlen(s &byte) int { + mut i := 0 + for ; s[i] != 0; i++ {} + return i +} + +pub fn tos(s &byte, len int) string { + if s == 0 { + panic('tos(): nil string') + } + return string{ + str: s + len: len + } +} + +fn (s string) add(a string) string { + new_len := a.len + s.len + mut res := string{ + len: new_len + str: malloc(new_len + 1) + } + for j in 0 .. s.len { + res[j] = s[j] + } + for j in 0 .. a.len { + res[s.len + j] = a[j] + } + res[new_len] = 0 // V strings are not null terminated, but just in case + return res +} + +/* +pub fn tos_clone(s byteptr) string { + if s == 0 { + panic('tos: nil string') + } + return tos2(s).clone() +} +*/ + +// Same as `tos`, but calculates the length. Called by `string(bytes)` casts. +// Used only internally. +pub fn tos2(s &byte) string { + if s == 0 { + panic('tos2: nil string') + } + return string{ + str: s + len: strlen(s) + } +} + +pub fn tos3(s &char) string { + if s == 0 { + panic('tos3: nil string') + } + return string{ + str: &byte(s) + len: strlen(&byte(s)) + } +} + +pub fn string_eq(s1 string, s2 string) bool { + if s1.len != s2.len { + return false + } + for i in 0 .. s1.len { + if s1[i] != s2[i] { + return false + } + } + return true +} + +pub fn string_ne(s1 string, s2 string) bool { + return !string_eq(s1, s2) +} + +pub fn i64_tos(buf &byte, len int, n0 i64, base int) string { + if base < 2 { + panic('base must be >= 2') + } + if base > 36 { + panic('base must be <= 36') + } + + mut b := tos(buf, len) + mut i := len - 1 + + mut n := n0 + neg := n < 0 + if neg { + n = -n + } + + b[i--] = 0 + + for { + c := (n % base) + 48 + b[i--] = if c > 57 { c + 7 } else { c } + if i < 0 { + panic('buffer to small') + } + n /= base + if n < 1 { + break + } + } + if neg { + if i < 0 { + panic('buffer to small') + } + b[i--] = 45 + } + offset := i + 1 + b.str = b.str + offset + b.len -= (offset + 1) + return b +} + +pub fn i64_str(n0 i64, base int) string { + buf := malloc(80) + return i64_tos(buf, 79, n0, base) +} + +pub fn ptr_str(ptr voidptr) string { + buf := [16]byte{} + hex := i64_tos(buf, 15, i64(ptr), 16) + res := '0x' + hex + return res +} + +pub fn (a string) clone() string { + mut b := string{ + len: a.len + str: malloc(a.len + 1) + } + mem_copy(b.str, a.str, a.len) + b[a.len] = 0 + return b +} diff --git a/v_windows/v/vlib/builtin/linux_bare/old/syscallwrapper_test.v b/v_windows/v/vlib/builtin/linux_bare/old/syscallwrapper_test.v new file mode 100644 index 0000000..1bd79c9 --- /dev/null +++ b/v_windows/v/vlib/builtin/linux_bare/old/syscallwrapper_test.v @@ -0,0 +1,27 @@ +import os + +fn test_syscallwrappers() { + if true { + return + } + $if linux { + $if x64 { + exe := os.executable() + vdir := os.dir(exe) + if vdir.len > 1 { + dot_checks := vdir + '/.checks' + assert os.is_dir(dot_checks) + + os.chdir(dot_checks) or {} + checks_v := 'checks.v' + assert os.exists(checks_v) + rc := os.execute_or_exit('v run $checks_v') + assert rc.exit_code == 0 + assert !rc.output.contains('V panic: An assertion failed.') + assert !rc.output.contains('failed') + } else { + panic("Can't find test directory") + } + } + } +} diff --git a/v_windows/v/vlib/builtin/map.c.v b/v_windows/v/vlib/builtin/map.c.v new file mode 100644 index 0000000..c0fc60c --- /dev/null +++ b/v_windows/v/vlib/builtin/map.c.v @@ -0,0 +1,79 @@ +module builtin + +fn C.wyhash(&byte, u64, u64, &u64) u64 + +fn C.wyhash64(u64, u64) u64 + +// fast_string_eq is intended to be fast when +// the strings are very likely to be equal +// TODO: add branch prediction hints +[inline] +fn fast_string_eq(a string, b string) bool { + if a.len != b.len { + return false + } + unsafe { + return C.memcmp(a.str, b.str, b.len) == 0 + } +} + +fn map_hash_string(pkey voidptr) u64 { + key := *unsafe { &string(pkey) } + return C.wyhash(key.str, u64(key.len), 0, &u64(C._wyp)) +} + +fn map_hash_int_1(pkey voidptr) u64 { + return C.wyhash64(*unsafe { &byte(pkey) }, 0) +} + +fn map_hash_int_2(pkey voidptr) u64 { + return C.wyhash64(*unsafe { &u16(pkey) }, 0) +} + +fn map_hash_int_4(pkey voidptr) u64 { + return C.wyhash64(*unsafe { &u32(pkey) }, 0) +} + +fn map_hash_int_8(pkey voidptr) u64 { + return C.wyhash64(*unsafe { &u64(pkey) }, 0) +} + +// Move all zeros to the end of the array and resize array +fn (mut d DenseArray) zeros_to_end() { + // TODO alloca? + mut tmp_value := unsafe { malloc(d.value_bytes) } + mut tmp_key := unsafe { malloc(d.key_bytes) } + mut count := 0 + for i in 0 .. d.len { + if d.has_index(i) { + // swap (TODO: optimize) + unsafe { + if count != i { + // Swap keys + C.memcpy(tmp_key, d.key(count), d.key_bytes) + C.memcpy(d.key(count), d.key(i), d.key_bytes) + C.memcpy(d.key(i), tmp_key, d.key_bytes) + // Swap values + C.memcpy(tmp_value, d.value(count), d.value_bytes) + C.memcpy(d.value(count), d.value(i), d.value_bytes) + C.memcpy(d.value(i), tmp_value, d.value_bytes) + } + } + count++ + } + } + unsafe { + free(tmp_value) + free(tmp_key) + d.deletes = 0 + // TODO: reallocate instead as more deletes are likely + free(d.all_deleted) + } + d.len = count + old_cap := d.cap + d.cap = if count < 8 { 8 } else { count } + unsafe { + d.values = realloc_data(d.values, d.value_bytes * old_cap, d.value_bytes * d.cap) + d.keys = realloc_data(d.keys, d.key_bytes * old_cap, d.key_bytes * d.cap) + } +} diff --git a/v_windows/v/vlib/builtin/map.v b/v_windows/v/vlib/builtin/map.v new file mode 100644 index 0000000..5e67d79 --- /dev/null +++ b/v_windows/v/vlib/builtin/map.v @@ -0,0 +1,716 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +/* +This is a highly optimized hashmap implementation. It has several traits that +in combination makes it very fast and memory efficient. Here is a short expl- +anation of each trait. After reading this you should have a basic understand- +ing of how it functions: + +1. Hash-function: Wyhash. Wyhash is the fastest hash-function for short keys +passing SMHasher, so it was an obvious choice. + +2. Open addressing: Robin Hood Hashing. With this method, a hash-collision is +resolved by probing. As opposed to linear probing, Robin Hood hashing has a +simple but clever twist: As new keys are inserted, old keys are shifted arou- +nd in a way such that all keys stay reasonably close to the slot they origin- +ally hash to. A new key may displace a key already inserted if its probe cou- +nt is larger than that of the key at the current position. + +3. Memory layout: key-value pairs are stored in a `DenseArray`. This is a dy- +namic array with a very low volume of unused memory, at the cost of more rea- +llocations when inserting elements. It also preserves the order of the key-v- +alues. This array is named `key_values`. Instead of probing a new key-value, +this map probes two 32-bit numbers collectively. The first number has its 8 +most significant bits reserved for the probe-count and the remaining 24 bits +are cached bits from the hash which are utilized for faster re-hashing. This +number is often referred to as `meta`. The other 32-bit number is the index +at which the key-value was pushed to in `key_values`. Both of these numbers +are stored in a sparse array `metas`. The `meta`s and `kv_index`s are stored +at even and odd indices, respectively: + +metas = [meta, kv_index, 0, 0, meta, kv_index, 0, 0, meta, kv_index, ...] +key_values = [kv, kv, kv, ...] + +4. The size of metas is a power of two. This enables the use of bitwise AND +to convert the 64-bit hash to a bucket/index that doesn't overflow metas. If +the size is power of two you can use "hash & (SIZE - 1)" instead of "hash % +SIZE". Modulo is extremely expensive so using '&' is a big performance impro- +vement. The general concern with this approach is that you only make use of +the lower bits of the hash which can cause more collisions. This is solved by +using a well-dispersed hash-function. + +5. The hashmap keeps track of the highest probe_count. The trick is to alloc- +ate `extra_metas` > max(probe_count), so you never have to do any bounds-che- +cking since the extra meta memory ensures that a meta will never go beyond +the last index. + +6. Cached rehashing. When the `load_factor` of the map exceeds the `max_load_ +factor` the size of metas is doubled and all the key-values are "rehashed" to +find the index for their meta's in the new array. Instead of rehashing compl- +etely, it simply uses the cached-hashbits stored in the meta, resulting in +much faster rehashing. +*/ +const ( + // Number of bits from the hash stored for each entry + hashbits = 24 + // Number of bits from the hash stored for rehashing + max_cached_hashbits = 16 + // Initial log-number of buckets in the hashtable + init_log_capicity = 5 + // Initial number of buckets in the hashtable + init_capicity = 1 << init_log_capicity + // Maximum load-factor (len / capacity) + max_load_factor = 0.8 + // Initial highest even index in metas + init_even_index = init_capicity - 2 + // Used for incrementing `extra_metas` when max + // probe count is too high, to avoid overflow + extra_metas_inc = 4 + // Bitmask to select all the hashbits + hash_mask = u32(0x00FFFFFF) + // Used for incrementing the probe-count + probe_inc = u32(0x01000000) +) + +// DenseArray represents a dynamic array with very low growth factor +struct DenseArray { + key_bytes int + value_bytes int +mut: + cap int + len int + deletes u32 // count + // array allocated (with `cap` bytes) on first deletion + // has non-zero element when key deleted + all_deleted &byte + values &byte + keys &byte +} + +[inline] +fn new_dense_array(key_bytes int, value_bytes int) DenseArray { + cap := 8 + return DenseArray{ + key_bytes: key_bytes + value_bytes: value_bytes + cap: cap + len: 0 + deletes: 0 + all_deleted: 0 + keys: unsafe { malloc(cap * key_bytes) } + values: unsafe { malloc(cap * value_bytes) } + } +} + +[inline] +fn (d &DenseArray) key(i int) voidptr { + return unsafe { d.keys + i * d.key_bytes } +} + +// for cgen +[inline] +fn (d &DenseArray) value(i int) voidptr { + return unsafe { d.values + i * d.value_bytes } +} + +[inline] +fn (d &DenseArray) has_index(i int) bool { + return d.deletes == 0 || unsafe { d.all_deleted[i] } == 0 +} + +// Make space to append an element and return index +// The growth-factor is roughly 1.125 `(x + (x >> 3))` +[inline] +fn (mut d DenseArray) expand() int { + old_cap := d.cap + old_value_size := d.value_bytes * old_cap + old_key_size := d.key_bytes * old_cap + if d.cap == d.len { + d.cap += d.cap >> 3 + unsafe { + d.keys = realloc_data(d.keys, old_key_size, d.key_bytes * d.cap) + d.values = realloc_data(d.values, old_value_size, d.value_bytes * d.cap) + if d.deletes != 0 { + d.all_deleted = realloc_data(d.all_deleted, old_cap, d.cap) + vmemset(d.all_deleted + d.len, 0, d.cap - d.len) + } + } + } + push_index := d.len + unsafe { + if d.deletes != 0 { + d.all_deleted[push_index] = 0 + } + } + d.len++ + return push_index +} + +type MapHashFn = fn (voidptr) u64 + +type MapEqFn = fn (voidptr, voidptr) bool + +type MapCloneFn = fn (voidptr, voidptr) + +type MapFreeFn = fn (voidptr) + +// map is the internal representation of a V `map` type. +pub struct map { + // Number of bytes of a key + key_bytes int + // Number of bytes of a value + value_bytes int +mut: + // Highest even index in the hashtable + even_index u32 + // Number of cached hashbits left for rehashing + cached_hashbits byte + // Used for right-shifting out used hashbits + shift byte + // Array storing key-values (ordered) + key_values DenseArray + // Pointer to meta-data: + // - Odd indices store kv_index. + // - Even indices store probe_count and hashbits. + metas &u32 + // Extra metas that allows for no ranging when incrementing + // index in the hashmap + extra_metas u32 + has_string_keys bool + hash_fn MapHashFn + key_eq_fn MapEqFn + clone_fn MapCloneFn + free_fn MapFreeFn +pub mut: + // Number of key-values currently in the hashmap + len int +} + +fn map_eq_string(a voidptr, b voidptr) bool { + return fast_string_eq(*unsafe { &string(a) }, *unsafe { &string(b) }) +} + +fn map_eq_int_1(a voidptr, b voidptr) bool { + return unsafe { *&byte(a) == *&byte(b) } +} + +fn map_eq_int_2(a voidptr, b voidptr) bool { + return unsafe { *&u16(a) == *&u16(b) } +} + +fn map_eq_int_4(a voidptr, b voidptr) bool { + return unsafe { *&u32(a) == *&u32(b) } +} + +fn map_eq_int_8(a voidptr, b voidptr) bool { + return unsafe { *&u64(a) == *&u64(b) } +} + +fn map_clone_string(dest voidptr, pkey voidptr) { + unsafe { + s := *&string(pkey) + (*&string(dest)) = s.clone() + } +} + +fn map_clone_int_1(dest voidptr, pkey voidptr) { + unsafe { + *&byte(dest) = *&byte(pkey) + } +} + +fn map_clone_int_2(dest voidptr, pkey voidptr) { + unsafe { + *&u16(dest) = *&u16(pkey) + } +} + +fn map_clone_int_4(dest voidptr, pkey voidptr) { + unsafe { + *&u32(dest) = *&u32(pkey) + } +} + +fn map_clone_int_8(dest voidptr, pkey voidptr) { + unsafe { + *&u64(dest) = *&u64(pkey) + } +} + +fn map_free_string(pkey voidptr) { + unsafe { + (*&string(pkey)).free() + } +} + +fn map_free_nop(_ voidptr) { +} + +fn new_map(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map { + metasize := int(sizeof(u32) * (init_capicity + extra_metas_inc)) + // for now assume anything bigger than a pointer is a string + has_string_keys := key_bytes > sizeof(voidptr) + return map{ + key_bytes: key_bytes + value_bytes: value_bytes + even_index: init_even_index + cached_hashbits: max_cached_hashbits + shift: init_log_capicity + key_values: new_dense_array(key_bytes, value_bytes) + metas: unsafe { &u32(vcalloc_noscan(metasize)) } + extra_metas: extra_metas_inc + len: 0 + has_string_keys: has_string_keys + hash_fn: hash_fn + key_eq_fn: key_eq_fn + clone_fn: clone_fn + free_fn: free_fn + } +} + +fn new_map_init(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map { + mut out := new_map(key_bytes, value_bytes, hash_fn, key_eq_fn, clone_fn, free_fn) + // TODO pre-allocate n slots + mut pkey := &byte(keys) + mut pval := &byte(values) + for _ in 0 .. n { + unsafe { + out.set(pkey, pval) + pkey = pkey + key_bytes + pval = pval + value_bytes + } + } + return out +} + +pub fn (mut m map) move() map { + r := *m + unsafe { + vmemset(m, 0, int(sizeof(map))) + } + return r +} + +[inline] +fn (m &map) key_to_index(pkey voidptr) (u32, u32) { + hash := m.hash_fn(pkey) + index := hash & m.even_index + meta := ((hash >> m.shift) & hash_mask) | probe_inc + return u32(index), u32(meta) +} + +[inline] +fn (m &map) meta_less(_index u32, _metas u32) (u32, u32) { + mut index := _index + mut meta := _metas + for meta < unsafe { m.metas[index] } { + index += 2 + meta += probe_inc + } + return index, meta +} + +[inline] +fn (mut m map) meta_greater(_index u32, _metas u32, kvi u32) { + mut meta := _metas + mut index := _index + mut kv_index := kvi + for unsafe { m.metas[index] } != 0 { + if meta > unsafe { m.metas[index] } { + unsafe { + tmp_meta := m.metas[index] + m.metas[index] = meta + meta = tmp_meta + tmp_index := m.metas[index + 1] + m.metas[index + 1] = kv_index + kv_index = tmp_index + } + } + index += 2 + meta += probe_inc + } + unsafe { + m.metas[index] = meta + m.metas[index + 1] = kv_index + } + probe_count := (meta >> hashbits) - 1 + m.ensure_extra_metas(probe_count) +} + +[inline] +fn (mut m map) ensure_extra_metas(probe_count u32) { + if (probe_count << 1) == m.extra_metas { + size_of_u32 := sizeof(u32) + old_mem_size := (m.even_index + 2 + m.extra_metas) + m.extra_metas += extra_metas_inc + mem_size := (m.even_index + 2 + m.extra_metas) + unsafe { + x := realloc_data(&byte(m.metas), int(size_of_u32 * old_mem_size), int(size_of_u32 * mem_size)) + m.metas = &u32(x) + vmemset(m.metas + mem_size - extra_metas_inc, 0, int(sizeof(u32) * extra_metas_inc)) + } + // Should almost never happen + if probe_count == 252 { + panic('Probe overflow') + } + } +} + +// Insert new element to the map. The element is inserted if its key is +// not equivalent to the key of any other element already in the container. +// If the key already exists, its value is changed to the value of the new element. +fn (mut m map) set(key voidptr, value voidptr) { + load_factor := f32(m.len << 1) / f32(m.even_index) + if load_factor > max_load_factor { + m.expand() + } + mut index, mut meta := m.key_to_index(key) + index, meta = m.meta_less(index, meta) + // While we might have a match + for meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + unsafe { + pval := m.key_values.value(kv_index) + vmemcpy(pval, value, m.value_bytes) + } + return + } + index += 2 + meta += probe_inc + } + kv_index := m.key_values.expand() + unsafe { + pkey := m.key_values.key(kv_index) + pvalue := m.key_values.value(kv_index) + m.clone_fn(pkey, key) + vmemcpy(&byte(pvalue), value, m.value_bytes) + } + m.meta_greater(index, meta, u32(kv_index)) + m.len++ +} + +// Doubles the size of the hashmap +fn (mut m map) expand() { + old_cap := m.even_index + m.even_index = ((m.even_index + 2) << 1) - 2 + // Check if any hashbits are left + if m.cached_hashbits == 0 { + m.shift += max_cached_hashbits + m.cached_hashbits = max_cached_hashbits + m.rehash() + } else { + m.cached_rehash(old_cap) + m.cached_hashbits-- + } +} + +// A rehash is the reconstruction of the hash table: +// All the elements in the container are rearranged according +// to their hash value into the newly sized key-value container. +// Rehashes are performed when the load_factor is going to surpass +// the max_load_factor in an operation. +fn (mut m map) rehash() { + meta_bytes := sizeof(u32) * (m.even_index + 2 + m.extra_metas) + unsafe { + // TODO: use realloc_data here too + x := v_realloc(&byte(m.metas), int(meta_bytes)) + m.metas = &u32(x) + vmemset(m.metas, 0, int(meta_bytes)) + } + for i := 0; i < m.key_values.len; i++ { + if !m.key_values.has_index(i) { + continue + } + pkey := unsafe { m.key_values.key(i) } + mut index, mut meta := m.key_to_index(pkey) + index, meta = m.meta_less(index, meta) + m.meta_greater(index, meta, u32(i)) + } +} + +// This method works like rehash. However, instead of rehashing the +// key completely, it uses the bits cached in `metas`. +fn (mut m map) cached_rehash(old_cap u32) { + old_metas := m.metas + metasize := int(sizeof(u32) * (m.even_index + 2 + m.extra_metas)) + m.metas = unsafe { &u32(vcalloc(metasize)) } + old_extra_metas := m.extra_metas + for i := u32(0); i <= old_cap + old_extra_metas; i += 2 { + if unsafe { old_metas[i] } == 0 { + continue + } + old_meta := unsafe { old_metas[i] } + old_probe_count := ((old_meta >> hashbits) - 1) << 1 + old_index := (i - old_probe_count) & (m.even_index >> 1) + mut index := (old_index | (old_meta << m.shift)) & m.even_index + mut meta := (old_meta & hash_mask) | probe_inc + index, meta = m.meta_less(index, meta) + kv_index := unsafe { old_metas[i + 1] } + m.meta_greater(index, meta, kv_index) + } + unsafe { free(old_metas) } +} + +// This method is used for assignment operators. If the argument-key +// does not exist in the map, it's added to the map along with the zero/default value. +// If the key exists, its respective value is returned. +fn (mut m map) get_and_set(key voidptr, zero voidptr) voidptr { + for { + mut index, mut meta := m.key_to_index(key) + for { + if meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + pval := unsafe { m.key_values.value(kv_index) } + return unsafe { &byte(pval) } + } + } + index += 2 + meta += probe_inc + if meta > unsafe { m.metas[index] } { + break + } + } + // Key not found, insert key with zero-value + m.set(key, zero) + } + assert false + return voidptr(0) +} + +// If `key` matches the key of an element in the container, +// the method returns a reference to its mapped value. +// If not, a zero/default value is returned. +fn (m &map) get(key voidptr, zero voidptr) voidptr { + mut index, mut meta := m.key_to_index(key) + for { + if meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + pval := unsafe { m.key_values.value(kv_index) } + return unsafe { &byte(pval) } + } + } + index += 2 + meta += probe_inc + if meta > unsafe { m.metas[index] } { + break + } + } + return zero +} + +// If `key` matches the key of an element in the container, +// the method returns a reference to its mapped value. +// If not, a zero pointer is returned. +// This is used in `x := m['key'] or { ... }` +fn (m &map) get_check(key voidptr) voidptr { + mut index, mut meta := m.key_to_index(key) + for { + if meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + pval := unsafe { m.key_values.value(kv_index) } + return unsafe { &byte(pval) } + } + } + index += 2 + meta += probe_inc + if meta > unsafe { m.metas[index] } { + break + } + } + return 0 +} + +// Checks whether a particular key exists in the map. +fn (m &map) exists(key voidptr) bool { + mut index, mut meta := m.key_to_index(key) + for { + if meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + return true + } + } + index += 2 + meta += probe_inc + if meta > unsafe { m.metas[index] } { + break + } + } + return false +} + +[inline] +fn (mut d DenseArray) delete(i int) { + if d.deletes == 0 { + d.all_deleted = vcalloc(d.cap) // sets to 0 + } + d.deletes++ + unsafe { + d.all_deleted[i] = 1 + } +} + +// Removes the mapping of a particular key from the map. +[unsafe] +pub fn (mut m map) delete(key voidptr) { + mut index, mut meta := m.key_to_index(key) + index, meta = m.meta_less(index, meta) + // Perform backwards shifting + for meta == unsafe { m.metas[index] } { + kv_index := int(unsafe { m.metas[index + 1] }) + pkey := unsafe { m.key_values.key(kv_index) } + if m.key_eq_fn(key, pkey) { + for (unsafe { m.metas[index + 2] } >> hashbits) > 1 { + unsafe { + m.metas[index] = m.metas[index + 2] - probe_inc + m.metas[index + 1] = m.metas[index + 3] + } + index += 2 + } + m.len-- + m.key_values.delete(kv_index) + unsafe { + m.metas[index] = 0 + m.free_fn(pkey) + // Mark key as deleted + vmemset(pkey, 0, m.key_bytes) + } + if m.key_values.len <= 32 { + return + } + // Clean up key_values if too many have been deleted + if m.key_values.deletes >= (m.key_values.len >> 1) { + m.key_values.zeros_to_end() + m.rehash() + } + return + } + index += 2 + meta += probe_inc + } +} + +// Returns all keys in the map. +fn (m &map) keys() array { + mut keys := __new_array(m.len, 0, m.key_bytes) + mut item := unsafe { &byte(keys.data) } + if m.key_values.deletes == 0 { + for i := 0; i < m.key_values.len; i++ { + unsafe { + pkey := m.key_values.key(i) + m.clone_fn(item, pkey) + item = item + m.key_bytes + } + } + return keys + } + for i := 0; i < m.key_values.len; i++ { + if !m.key_values.has_index(i) { + continue + } + unsafe { + pkey := m.key_values.key(i) + m.clone_fn(item, pkey) + item = item + m.key_bytes + } + } + return keys +} + +// warning: only copies keys, does not clone +[unsafe] +fn (d &DenseArray) clone() DenseArray { + res := DenseArray{ + key_bytes: d.key_bytes + value_bytes: d.value_bytes + cap: d.cap + len: d.len + deletes: d.deletes + all_deleted: 0 + values: 0 + keys: 0 + } + unsafe { + if d.deletes != 0 { + res.all_deleted = memdup(d.all_deleted, d.cap) + } + res.keys = memdup(d.keys, d.cap * d.key_bytes) + res.values = memdup(d.values, d.cap * d.value_bytes) + } + return res +} + +// clone returns a clone of the `map`. +[unsafe] +pub fn (m &map) clone() map { + metasize := int(sizeof(u32) * (m.even_index + 2 + m.extra_metas)) + res := map{ + key_bytes: m.key_bytes + value_bytes: m.value_bytes + even_index: m.even_index + cached_hashbits: m.cached_hashbits + shift: m.shift + key_values: unsafe { m.key_values.clone() } + metas: unsafe { &u32(malloc(metasize)) } + extra_metas: m.extra_metas + len: m.len + has_string_keys: m.has_string_keys + hash_fn: m.hash_fn + key_eq_fn: m.key_eq_fn + clone_fn: m.clone_fn + free_fn: m.free_fn + } + unsafe { vmemcpy(res.metas, m.metas, metasize) } + if !m.has_string_keys { + return res + } + // clone keys + for i in 0 .. m.key_values.len { + if !m.key_values.has_index(i) { + continue + } + m.clone_fn(res.key_values.key(i), m.key_values.key(i)) + } + return res +} + +// free releases all memory resources occupied by the `map`. +[unsafe] +pub fn (m &map) free() { + unsafe { free(m.metas) } + if m.key_values.deletes == 0 { + for i := 0; i < m.key_values.len; i++ { + unsafe { + pkey := m.key_values.key(i) + m.free_fn(pkey) + } + } + } else { + for i := 0; i < m.key_values.len; i++ { + if !m.key_values.has_index(i) { + continue + } + unsafe { + pkey := m.key_values.key(i) + m.free_fn(pkey) + } + } + unsafe { free(m.key_values.all_deleted) } + } + unsafe { + free(m.key_values.keys) + free(m.key_values.values) + } +} diff --git a/v_windows/v/vlib/builtin/map_d_gcboehm_opt.v b/v_windows/v/vlib/builtin/map_d_gcboehm_opt.v new file mode 100644 index 0000000..93049b7 --- /dev/null +++ b/v_windows/v/vlib/builtin/map_d_gcboehm_opt.v @@ -0,0 +1,146 @@ +// "noscan" versions of `map` initialization routines +// +// They are used when the compiler can proof that either the keys or the values or both +// do not contain any pointers. Such objects can be placed in a memory area that is not +// scanned by the garbage collector + +module builtin + +[inline] +fn new_dense_array_noscan(key_bytes int, key_noscan bool, value_bytes int, value_noscan bool) DenseArray { + cap := 8 + keys := if key_noscan { + unsafe { malloc_noscan(cap * key_bytes) } + } else { + unsafe { malloc(cap * key_bytes) } + } + values := if value_noscan { + unsafe { malloc_noscan(cap * value_bytes) } + } else { + unsafe { malloc(cap * value_bytes) } + } + return DenseArray{ + key_bytes: key_bytes + value_bytes: value_bytes + cap: cap + len: 0 + deletes: 0 + all_deleted: 0 + keys: keys + values: values + } +} + +fn new_map_noscan_key(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map { + metasize := int(sizeof(u32) * (init_capicity + extra_metas_inc)) + // for now assume anything bigger than a pointer is a string + has_string_keys := key_bytes > sizeof(voidptr) + return map{ + key_bytes: key_bytes + value_bytes: value_bytes + even_index: init_even_index + cached_hashbits: max_cached_hashbits + shift: init_log_capicity + key_values: new_dense_array_noscan(key_bytes, true, value_bytes, false) + metas: unsafe { &u32(vcalloc_noscan(metasize)) } + extra_metas: extra_metas_inc + len: 0 + has_string_keys: has_string_keys + hash_fn: hash_fn + key_eq_fn: key_eq_fn + clone_fn: clone_fn + free_fn: free_fn + } +} + +fn new_map_noscan_value(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map { + metasize := int(sizeof(u32) * (init_capicity + extra_metas_inc)) + // for now assume anything bigger than a pointer is a string + has_string_keys := key_bytes > sizeof(voidptr) + return map{ + key_bytes: key_bytes + value_bytes: value_bytes + even_index: init_even_index + cached_hashbits: max_cached_hashbits + shift: init_log_capicity + key_values: new_dense_array_noscan(key_bytes, false, value_bytes, true) + metas: unsafe { &u32(vcalloc_noscan(metasize)) } + extra_metas: extra_metas_inc + len: 0 + has_string_keys: has_string_keys + hash_fn: hash_fn + key_eq_fn: key_eq_fn + clone_fn: clone_fn + free_fn: free_fn + } +} + +fn new_map_noscan_key_value(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map { + metasize := int(sizeof(u32) * (init_capicity + extra_metas_inc)) + // for now assume anything bigger than a pointer is a string + has_string_keys := key_bytes > sizeof(voidptr) + return map{ + key_bytes: key_bytes + value_bytes: value_bytes + even_index: init_even_index + cached_hashbits: max_cached_hashbits + shift: init_log_capicity + key_values: new_dense_array_noscan(key_bytes, true, value_bytes, true) + metas: unsafe { &u32(vcalloc_noscan(metasize)) } + extra_metas: extra_metas_inc + len: 0 + has_string_keys: has_string_keys + hash_fn: hash_fn + key_eq_fn: key_eq_fn + clone_fn: clone_fn + free_fn: free_fn + } +} + +fn new_map_init_noscan_key(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map { + mut out := new_map_noscan_key(key_bytes, value_bytes, hash_fn, key_eq_fn, clone_fn, + free_fn) + // TODO pre-allocate n slots + mut pkey := &byte(keys) + mut pval := &byte(values) + for _ in 0 .. n { + unsafe { + out.set(pkey, pval) + pkey = pkey + key_bytes + pval = pval + value_bytes + } + } + return out +} + +fn new_map_init_noscan_value(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map { + mut out := new_map_noscan_value(key_bytes, value_bytes, hash_fn, key_eq_fn, clone_fn, + free_fn) + // TODO pre-allocate n slots + mut pkey := &byte(keys) + mut pval := &byte(values) + for _ in 0 .. n { + unsafe { + out.set(pkey, pval) + pkey = pkey + key_bytes + pval = pval + value_bytes + } + } + return out +} + +fn new_map_init_noscan_key_value(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map { + mut out := new_map_noscan_key_value(key_bytes, value_bytes, hash_fn, key_eq_fn, clone_fn, + free_fn) + // TODO pre-allocate n slots + mut pkey := &byte(keys) + mut pval := &byte(values) + for _ in 0 .. n { + unsafe { + out.set(pkey, pval) + pkey = pkey + key_bytes + pval = pval + value_bytes + } + } + return out +} diff --git a/v_windows/v/vlib/builtin/map_of_floats_test.v b/v_windows/v/vlib/builtin/map_of_floats_test.v new file mode 100644 index 0000000..7481107 --- /dev/null +++ b/v_windows/v/vlib/builtin/map_of_floats_test.v @@ -0,0 +1,27 @@ +fn test_map_of_f32() { + mut m32 := map[f32]string{} + m32[1.0] = 'one' + println(m32) + assert '$m32' == r"{1.: 'one'}" + for k, v in m32 { + assert typeof(k).name == 'f32' + assert typeof(v).name == 'string' + assert k == 1.0 + assert v == 'one' + } +} + +fn test_map_of_f64() { + mut m64 := { + 3.14: 'pi' + } + m64[1.0] = 'one' + println(m64) + assert '$m64' == r"{3.14: 'pi', 1.: 'one'}" + for k, v in m64 { + assert typeof(k).name == 'f64' + assert typeof(v).name == 'string' + assert k in [1.0, 3.14] + assert v in ['pi', 'one'] + } +} diff --git a/v_windows/v/vlib/builtin/map_test.v b/v_windows/v/vlib/builtin/map_test.v new file mode 100644 index 0000000..f186dd5 --- /dev/null +++ b/v_windows/v/vlib/builtin/map_test.v @@ -0,0 +1,947 @@ +import rand + +const ( + strings = unique_strings(20000, 10) +) + +fn unique_strings(arr_len int, str_len int) []string { + mut arr := []string{cap: arr_len} + for arr.len < arr_len { + str := rand.string(str_len) + if str !in arr { + arr << str + } + } + return arr +} + +fn test_get_and_set_many() { + mut m := map[string]int{} + for i, s in strings { + m[s] = i + assert m[s] == i + assert m.len == i + 1 + } + for i, s in strings { + assert m[s] == i + } + assert m.len == strings.len +} + +fn test_for_in_many() { + mut m := map[string]int{} + for i, s in strings { + m[s] = i + } + for k, v in m { + assert m[k] == v + } +} + +fn test_keys_many() { + mut m := map[string]int{} + for i, s in strings { + m[s] = i + } + keys := m.keys() + assert keys.len == strings.len + assert keys.len == m.len + assert keys == strings +} + +fn test_deletes_many() { + mut m := map[string]int{} + for i, s in strings { + m[s] = i + } + for i, s in strings { + m.delete(s) + assert m[s] == 0 + assert m.len == strings.len - (i + 1) + } + assert m.len == 0 + assert m.keys().len == 0 +} + +struct User { +mut: + name string +} + +struct Aaa { +mut: + m map[string]int + users map[string]User +} + +fn (mut a Aaa) set(key string, val int) { + a.m[key] = val +} + +fn test_map() { + mut m := map[string]int{} + assert m.len == 0 + m['hi'] = 80 + m['hello'] = 101 + assert m['hi'] == 80 + assert m['hello'] == 101 + assert m.len == 2 + assert 'hi' in m + mut sum := 0 + // Test `for in` + for _, val in m { + sum += val + } + assert sum == 80 + 101 + // Test `.keys()` + keys := m.keys() + assert keys.len == 2 + assert 'hi' in keys + assert 'hello' in keys + m.delete('hi') + assert m.len == 1 + m.delete('aloha') + assert m.len == 1 + assert m['hi'] == 0 + assert m.keys().len == 1 + assert m.keys()[0] == 'hello' + // // + mut users := map[string]User{} + users['1'] = User{'Peter'} + peter := users['1'] + assert peter.name == 'Peter' + mut a := Aaa{ + m: map[string]int{} + users: map[string]User{} + } + a.users['Bob'] = User{'Bob'} + q := a.users['Bob'] + assert q.name == 'Bob' + // test struct field change + a.users['Bob'].name = 'bob' + q2 := a.users['Bob'] + assert q2.name == 'bob' + a.m['one'] = 1 + a.set('two', 2) + assert a.m['one'] == 1 + assert a.m['two'] == 2 +} + +fn test_map_init() { + one := 'one' + three := 'three' + m := { + one: 1 + 'two': 2 + three: 1 + 2 + } + assert m['one'] == 1 + assert m['two'] == 2 + assert m['three'] == 3 + assert m['unknown'] == 0 +} + +fn test_string_map() { + // m := map[string]Fn +} + +fn test_large_map() { + // ticks := time.ticks() + mut nums := map[string]int{} + n := 30 * 1000 + for i in 0 .. n { + key := i.str() + nums[key] = i + } + assert nums['1'] == 1 + assert nums['999'] == 999 + assert nums['1000000'] == 0 + // println(time.ticks() - ticks) +} + +fn test_various_map_value() { + mut m1 := map[string]int{} + m1['test'] = 1 + assert m1['test'] == 1 + mut m2 := map[string]string{} + m2['test'] = 'test' + assert m2['test'] == 'test' + mut m3 := map[string]i8{} + m3['test'] = i8(0) + assert m3['test'] == i8(0) + mut m4 := map[string]i16{} + m4['test'] = i16(0) + assert m4['test'] == i16(0) + mut m7 := map[string]u16{} + m7['test'] = u16(0) + assert m7['test'] == u16(0) + mut m8 := map[string]u32{} + m8['test'] = u32(0) + assert m8['test'] == u32(0) + mut m9 := map[string]bool{} + m9['test'] = true + assert m9['test'] == true + mut m10 := map[string]byte{} + m10['test'] = byte(0) + assert m10['test'] == byte(0) + mut m11 := map[string]f32{} + m11['test'] = f32(0.0) + assert m11['test'] == f32(0.0) + mut m12 := map[string]f64{} + m12['test'] = f64(0.0) + assert m12['test'] == f64(0.0) + // mut m13 := map[string]rune + // m13['test'] = rune(0) + // assert m13['test'] == rune(0) + mut m14 := map[string]voidptr{} + m14['test'] = voidptr(0) + assert m14['test'] == voidptr(0) + mut m15 := map[string]&byte{} + m15['test'] = &byte(0) + assert m15['test'] == &byte(0) + mut m16 := map[string]i64{} + m16['test'] = i64(0) + assert m16['test'] == i64(0) + mut m17 := map[string]u64{} + m17['test'] = u64(0) + assert m17['test'] == u64(0) + mut m18 := map[string]&int{} + m18['test'] = &int(0) + assert m18['test'] == &int(0) +} + +fn test_string_arr() { + mut m := map[string][]string{} + m['a'] = ['one', 'two'] + assert m['a'].len == 2 + assert m['a'][0] == 'one' + assert m['a'][1] == 'two' +} + +fn mut_map(mut m map[string]int) { + m['a'] = 10 +} + +fn test_mut_arg() { + mut m := map[string]int{} + mut_map(mut m) + a := m['a'] + assert a == 10 +} + +fn test_delete() { + mut m := map[string]int{} + m['one'] = 1 + m['two'] = 2 + println(m['two']) // => "2" + m.delete('two') + println(m['two'].str()) // => 0 + assert ('two' in m) == false + println('two' in m) // => true, on Linux and Windows <-- wrong ! +} + +fn test_delete_size() { + arr := ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + mut m := map[string]int{} + for _ in 0 .. 10 { + for i in 0 .. 10 { + m[arr[i]] = i + } + assert m.len == 10 + println(m.len) + for i in 0 .. 10 { + m.delete(arr[i]) + } + } +} + +fn test_nested_for_in() { + mut m := map[string]int{} + for i in 0 .. 1000 { + m[i.str()] = i + } + mut i := 0 + for key1, _ in m { + assert key1 == i.str() + i++ + mut j := 0 + for key2, _ in m { + assert key2 == j.str() + j++ + } + } +} + +fn test_delete_in_for_in() { + mut m := map[string]string{} + for i in 0 .. 1000 { + m[i.str()] = i.str() + } + mut i := 0 + for key, _ in m { + assert key == i.str() + m.delete(key) + i++ + } + assert m.str() == '{}' + assert m.len == 0 +} + +fn test_set_in_for_in() { + mut m := map[string]string{} + for i in 0 .. 10 { + m[i.str()] = i.str() + } + mut last_key := '' + mut i := 0 + for key, _ in m { + m['10'] = '10' + assert key == i.str() + last_key = key + i++ + } + assert last_key == '10' +} + +fn test_delete_and_set_in_for_in() { + mut m := map[string]string{} + for i in 0 .. 1000 { + m[i.str()] = i.str() + } + mut i := 0 + for key, _ in m { + assert key == i.str() + m.delete(key) + m[key] = i.str() + if i == 999 { + break + } + i++ + } + assert m.len == 1000 + i = 0 + for key, _ in m { + assert m[key] == i.str() + i++ + } + assert i == 1000 +} + +struct Mstruct1 { +pub mut: + mymap map[string]int +} + +struct Mstruct2 { +pub mut: + mymap map[string]f64 +} + +struct Mstruct3 { +pub mut: + mymap map[string]u16 +} + +fn test_map_assign() { + mut a := map[string]f64{} + mut b := map[string]int{} + mut c := map[string]u16{} + a = { + 'x': 12.4 + 'y': 3 + } + b = { + 'u': -13 + 'v': 12 + } + c = { + 's': u16(5) + 't': 3 + } + _ := Mstruct1{{ + 'p': 12 + }} + _ := Mstruct2{{ + 'q': 1.7 + }} + _ := Mstruct3{{ + 'r': u16(6) + 's': 5 + }} +} + +fn test_postfix_op_directly() { + mut a := map[string]int{} + a['aaa']++ + assert a['aaa'] == 1 + a['aaa']++ + assert a['aaa'] == 2 + a['bbb']-- + assert a['bbb'] == -1 + a['bbb']-- + assert a['bbb'] == -2 +} + +fn test_map_push_directly() { + mut a := map[string][]string{} + a['aaa'] << ['a', 'b', 'c'] + assert a['aaa'].len == 3 + assert a['aaa'] == ['a', 'b', 'c'] +} + +fn test_assign_directly() { + mut a := map[string]int{} + a['aaa'] += 4 + assert a['aaa'] == 4 + a['aaa'] -= 2 + assert a['aaa'] == 2 +} + +fn test_map_in_directly() { + for k, v in { + 'aa': 1 + } { + assert k == 'aa' + assert v == 1 + } +} + +fn test_plus_assign_string() { + mut m := { + 'one': '' + } + m['one'] += '1' + assert m.len == 1 + assert m['one'] == '1' +} + +fn test_map_keys_to_array() { + m := { + 'a': 'b' + 'c': 'd' + } + mut arr := []string{} + for k, _ in m { + arr << k + } + sarr := arr.str() + println(sarr) + assert sarr == "['a', 'c']" +} + +fn map_in_mut(mut m map[string]int) { + if 'one' in m { + m['one'] = 2 + } +} + +fn test_map_in_mut() { + mut m := { + 'one': 1 + } + map_in_mut(mut m) + assert m['one'] == 2 +} + +fn test_map_in() { + m := { + 'Foo': 'bar' + } + if 'foo'.capitalize() in m { + println('ok') + } else { + assert false + } +} + +fn mut_map_with_relation_op_in_fn(mut m map[string]int) { + if m['one'] == 1 { + m['three'] = 3 + } + if m['two'] != 1 { + m['four'] = 4 + } + if m['one'] > 0 { + m['five'] = 5 + } + if m['one'] < 2 { + m['six'] = 6 + } + if m['two'] >= 2 { + m['seven'] = 7 + } + if m['two'] <= 2 { + m['eight'] = 8 + } +} + +fn test_mut_map_with_relation_op_in_fn() { + mut m := { + 'one': 1 + 'two': 2 + } + mut_map_with_relation_op_in_fn(mut m) + assert 'three' in m + assert 'four' in m + assert 'five' in m + assert 'six' in m + assert 'seven' in m + assert 'eight' in m +} + +fn test_map_str_after_delete() { + mut m := { + 'first': 1 + 'second': 2 + 'third': 3 + } + osm := '$m' + m.delete('second') + nsm := '$m' + println('m: $m') + assert osm == "{'first': 1, 'second': 2, 'third': 3}" + assert nsm == "{'first': 1, 'third': 3}" +} + +fn test_modify_map_value() { + mut m1 := { + 'foo': 3 + 'bar': -7 + } + m1['foo'] += 5 + m1['bar'] *= -2 + assert m1['foo'] == 8 + assert m1['bar'] == 14 +} + +fn test_map_clone() { + mut nums := { + 'foo': 1 + 'bar': 2 + } + mut nums2 := nums.clone() + nums2['foo']++ + nums2['bar'] *= 4 + assert nums['foo'] == 1 + assert nums['bar'] == 2 + assert nums2['foo'] == 2 + assert nums2['bar'] == 8 +} + +struct MValue { + name string + misc map[string]string +} + +fn test_map_default_zero() { + m := map[string]MValue{} + v := m['unknown'] + x := v.misc['x'] + println(x) + assert x == '' +} + +fn test_map_or() { + m := { + 'first': 1 + 'second': 2 + 'third': 3 + } + _ = m + // num := m['first'] or { return } +} + +fn test_int_keys() { + mut m := map[int]int{} + m[3] = 9 + m[4] = 16 + assert m.len == 2 + assert m[3] == 9 + assert m[4] == 16 + m[5] += 24 + m[5]++ + assert m[5] == 25 + mut m2 := { + 3: 9 + 4: 16 + 5: 25 + } + + four := 4 + m2.delete(3) + m2.delete(four) + m2.delete(5) + assert m2.len == 0 + assert m2[3] == 0 + assert m2[4] == 0 + assert m2[5] == 0 + assert m2.keys() == [] + + m2 = { + 3: 9 + 4: 16 + 5: 25 + } + + assert m2.len == 3 + // clone + mc := m.clone() + same := mc == m + assert same + assert mc.len == 3 + assert mc.keys() == [3, 4, 5] + mut all := []int{} + for k, v in mc { + assert m[k] == v + all << k + all << v + } + assert all == [3, 9, 4, 16, 5, 25] + + mut m3 := { + 1: 'one' + 2: 'two' + } + assert m3[1] == 'one' + m3.delete(1) +} + +enum Color { + red + green + blue +} + +type ColorAlias = Color + +fn test_alias_enum() { + mut m := map[ColorAlias]string{} + m[Color.red] = 'hi' + assert m[Color.red] == 'hi' +} + +fn test_enum_in_map() { + mut m := map[Color]string{} + m[Color.red] = 'hi' + assert Color.red in m + assert Color.green !in m + assert Color.blue !in m +} + +fn test_voidptr_keys() { + mut m := map[voidptr]string{} + v := 5 + m[&v] = 'var' + m[&m] = 'map' + assert m[&v] == 'var' + assert m[&m] == 'map' + assert m.len == 2 +} + +fn test_rune_keys() { + mut m := { + `!`: 2 + `%`: 3 + } + assert typeof(m).name == 'map[rune]int' + assert m[`!`] == 2 + m[`@`] = 7 + assert m.len == 3 + println(m) + assert '$m' == '{`!`: 2, `%`: 3, `@`: 7}' + + mut a := []rune{} + for k, v in m { + a << k + a << rune(v) + `0` + } + assert a == [`!`, `2`, `%`, `3`, `@`, `7`] +} + +fn test_eq() { + a := { + 'a': 1 + 'b': 2 + } + assert a == { + 'a': 1 + 'b': 2 + } + b := { + 'a': [[1]] + 'b': [[2]] + } + assert b == { + 'a': [[1]] + 'b': [[2]] + } + c := { + 'a': { + '11': 1 + } + 'b': { + '22': 2 + } + } + assert c == { + 'a': { + '11': 1 + } + 'b': { + '22': 2 + } + } + d := { + 'a': MValue{ + name: 'aa' + misc: { + '11': '1' + } + } + 'b': MValue{ + name: 'bb' + misc: { + '22': '2' + } + } + } + assert d == { + 'a': MValue{ + name: 'aa' + misc: { + '11': '1' + } + } + 'b': MValue{ + name: 'bb' + misc: { + '22': '2' + } + } + } +} + +fn test_non_string_key_map_str() { + assert { + 23: 4 + }.str() == '{23: 4}' + assert { + `a`: 12 + `b`: 13 + }.str() == '{`a`: 12, `b`: 13}' + assert { + 23: 'foo' + 25: 'bar' + }.str() == "{23: 'foo', 25: 'bar'}" +} + +fn test_map_assign_empty_map_init() { + mut a := { + 'one': 1 + } + a = {} + println(a) + assert a == map[string]int{} + assert '$a' == '{}' +} + +fn test_in_map_literal() { + assert 1 in { + 1: 'one' + } +} + +fn test_byte_keys() { + mut m := map[byte]byte{} + byte_max := byte(255) + for i in byte(0) .. byte_max { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in byte(0) .. 100 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == byte_max + keys := m.keys() + for i in byte(0) .. byte_max { + assert keys[i] == i + } + for i in byte(0) .. byte_max { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_i16_keys() { + mut m := map[i16]i16{} + end := i16(1000) + for i in i16(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in i16(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in i16(0) .. end { + assert keys[i] == i + } + for i in i16(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_u16_keys() { + mut m := map[u16]u16{} + end := u16(1000) + for i in u16(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in u16(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in u16(0) .. end { + assert keys[i] == i + } + for i in u16(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_u32_keys() { + mut m := map[u32]u32{} + end := u32(1000) + for i in u32(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in u32(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in u32(0) .. end { + assert keys[i] == i + } + for i in u32(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_int_keys2() { + mut m := map[int]int{} + end := 1000 + for i in int(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in int(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in int(0) .. end { + assert keys[i] == i + } + for i in int(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_i64_keys() { + mut m := map[i64]i64{} + end := i64(1000) + for i in i64(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in i64(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in i64(0) .. end { + assert keys[i] == i + } + for i in i64(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_u64_keys() { + mut m := map[u64]u64{} + end := u64(1000) + for i in u64(0) .. end { + m[i] = i + assert m[i] == i + } + for k, v in m { + assert k == v + } + for i in u64(0) .. 500 { + m[i]++ + assert m[i] == i + 1 + } + assert m.len == end + keys := m.keys() + for i in u64(0) .. end { + assert keys[i] == i + } + for i in u64(0) .. end { + m.delete(i) + assert m[i] == 0 + } + assert m.len == 0 +} + +fn test_map_set_fixed_array_variable() { + mut m := map[string][2]f64{} + m['A'] = [1.1, 2.2]! + println(m) + assert '$m' == "{'A': [1.1, 2.2]}" + + mut m2 := map[string][2]f64{} + arr := [1.1, 2.2]! + m2['A'] = arr + println(m2) + assert '$m2' == "{'A': [1.1, 2.2]}" +} diff --git a/v_windows/v/vlib/builtin/option.c.v b/v_windows/v/vlib/builtin/option.c.v new file mode 100644 index 0000000..464cf87 --- /dev/null +++ b/v_windows/v/vlib/builtin/option.c.v @@ -0,0 +1,15 @@ +module builtin + +[typedef] +struct C.IError { + _object voidptr +} + +[unsafe] +pub fn (ie &IError) free() { + unsafe { + ie.msg.free() + cie := &C.IError(ie) + free(cie._object) + } +} diff --git a/v_windows/v/vlib/builtin/option.v b/v_windows/v/vlib/builtin/option.v new file mode 100644 index 0000000..1f1bdd9 --- /dev/null +++ b/v_windows/v/vlib/builtin/option.v @@ -0,0 +1,89 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +// IError holds information about an error instance +pub interface IError { + msg string + code int +} + +// Error is the default implementation of IError, that is returned by e.g. `error()` +pub struct Error { +pub: + msg string + code int +} + +pub fn (err IError) str() string { + return match err { + None__ { 'none' } + Error { err.msg } + else { '$err.type_name(): $err.msg' } + } +} + +const none__ = IError(&None__{}) + +struct None__ { + msg string + code int +} + +fn (_ None__) str() string { + return 'none' +} + +[if trace_error ?] +fn trace_error(x string) { + eprintln('> ${@FN} | $x') +} + +// error returns a default error instance containing the error given in `message`. +// Example: `if ouch { return error('an error occurred') }` +[inline] +pub fn error(message string) IError { + trace_error(message) + return &Error{ + msg: message + } +} + +// error_with_code returns a default error instance containing the given `message` and error `code`. +// `if ouch { return error_with_code('an error occurred', 1) }` +[inline] +pub fn error_with_code(message string, code int) IError { + trace_error('$message | code: $code') + return &Error{ + msg: message + code: code + } +} + +// Option is the base of V's internal optional return system. +struct Option { + state byte + err IError = none__ + // Data is trailing after err + // and is not included in here but in the + // derived Option_xxx types +} + +fn opt_ok(data voidptr, mut option Option, size int) { + unsafe { + *option = Option{} + // use err to get the end of OptionBase and then memcpy into it + vmemcpy(&byte(&option.err) + sizeof(IError), data, size) + } +} + +[unsafe] +pub fn (e &Error) free() { + unsafe { e.msg.free() } +} + +[unsafe] +pub fn (n &None__) free() { + unsafe { n.msg.free() } +} diff --git a/v_windows/v/vlib/builtin/prealloc.c.v b/v_windows/v/vlib/builtin/prealloc.c.v new file mode 100644 index 0000000..0ef66fc --- /dev/null +++ b/v_windows/v/vlib/builtin/prealloc.c.v @@ -0,0 +1,114 @@ +module builtin + +// With -prealloc, V calls libc's malloc to get chunks, each at least 16MB +// in size, as needed. Once a chunk is available, all malloc() calls within +// V code, that can fit inside the chunk, will use it instead, each bumping a +// pointer, till the chunk is filled. Once a chunk is filled, a new chunk will +// be allocated by calling libc's malloc, and the process continues. +// Each new chunk has a pointer to the old one, and at the end of the program, +// the entire linked list of chunks is freed. +// The goal of all this is to amortize the cost of calling libc's malloc, +// trading higher memory usage for a compiler (or any single threaded batch +// mode program), for a ~8-10% speed increase. +// NB: `-prealloc` is NOT safe to be used for multithreaded programs! + +// size of the preallocated chunk +const prealloc_block_size = 16 * 1024 * 1024 + +__global g_memory_block &VMemoryBlock +[heap] +struct VMemoryBlock { +mut: + id int + cap int + start &byte = 0 + previous &VMemoryBlock = 0 + remaining int + current &byte = 0 + mallocs int +} + +[unsafe] +fn vmemory_block_new(prev &VMemoryBlock, at_least int) &VMemoryBlock { + mut v := unsafe { &VMemoryBlock(C.calloc(1, sizeof(VMemoryBlock))) } + if prev != 0 { + v.id = prev.id + 1 + } + v.previous = prev + block_size := if at_least < prealloc_block_size { prealloc_block_size } else { at_least } + v.start = unsafe { C.malloc(block_size) } + v.cap = block_size + v.remaining = block_size + v.current = v.start + return v +} + +[unsafe] +fn vmemory_block_malloc(n int) &byte { + unsafe { + if g_memory_block.remaining < n { + g_memory_block = vmemory_block_new(g_memory_block, n) + } + mut res := &byte(0) + res = g_memory_block.current + g_memory_block.remaining -= n + g_memory_block.mallocs++ + g_memory_block.current += n + return res + } +} + +///////////////////////////////////////////////// + +[unsafe] +fn prealloc_vinit() { + unsafe { + g_memory_block = vmemory_block_new(voidptr(0), prealloc_block_size) + $if !freestanding { + C.atexit(prealloc_vcleanup) + } + } +} + +[unsafe] +fn prealloc_vcleanup() { + $if prealloc_stats ? { + // NB: we do 2 loops here, because string interpolation + // in the first loop may still use g_memory_block + // The second loop however should *not* allocate at all. + mut nr_mallocs := i64(0) + mut mb := g_memory_block + for mb != 0 { + nr_mallocs += mb.mallocs + eprintln('> freeing mb.id: ${mb.id:3} | cap: ${mb.cap:7} | rem: ${mb.remaining:7} | start: ${voidptr(mb.start)} | current: ${voidptr(mb.current)} | diff: ${u64(mb.current) - u64(mb.start):7} bytes | mallocs: $mb.mallocs') + mb = mb.previous + } + eprintln('> nr_mallocs: $nr_mallocs') + } + unsafe { + for g_memory_block != 0 { + C.free(g_memory_block.start) + g_memory_block = g_memory_block.previous + } + } +} + +[unsafe] +fn prealloc_malloc(n int) &byte { + return unsafe { vmemory_block_malloc(n) } +} + +[unsafe] +fn prealloc_realloc(old_data &byte, old_size int, new_size int) &byte { + new_ptr := unsafe { vmemory_block_malloc(new_size) } + min_size := if old_size < new_size { old_size } else { new_size } + unsafe { C.memcpy(new_ptr, old_data, min_size) } + return new_ptr +} + +[unsafe] +fn prealloc_calloc(n int) &byte { + new_ptr := unsafe { vmemory_block_malloc(n) } + unsafe { C.memset(new_ptr, 0, n) } + return new_ptr +} diff --git a/v_windows/v/vlib/builtin/rune.v b/v_windows/v/vlib/builtin/rune.v new file mode 100644 index 0000000..3364d90 --- /dev/null +++ b/v_windows/v/vlib/builtin/rune.v @@ -0,0 +1,65 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module builtin + +import strings + +// This was never working correctly, the issue is now +// fixed however the type checks in checker need to be +// updated. if you uncomment it you will see the issue +// type rune = int + +pub fn (c rune) str() string { + return utf32_to_str(u32(c)) + /* + unsafe { + fst_byte := int(c)>>8 * 3 & 0xff + len := utf8_char_len(byte(fst_byte)) + println('len=$len') + mut str := string{ + len: len + str: malloc(len + 1) + } + for i in 0..len { + str.str[i] = byte(int(c)>>8 * (3 - i) & 0xff) + } + str.str[len] = `\0` + println(str) + return str + } + */ +} + +// string converts a rune array to a string +[manualfree] +pub fn (ra []rune) string() string { + mut sb := strings.new_builder(ra.len) + sb.write_runes(ra) + res := sb.str() + unsafe { sb.free() } + return res +} + +// Define this on byte as well, so that we can do `s[0].is_capital()` +pub fn (c byte) is_capital() bool { + return c >= `A` && c <= `Z` +} + +pub fn (b []byte) clone() []byte { + mut res := []byte{len: b.len} + // mut res := make([]byte, {repeat:b.len}) + for i in 0 .. b.len { + res[i] = b[i] + } + return res +} + +// TODO: remove this once runes are implemented +pub fn (b []byte) bytestr() string { + unsafe { + buf := malloc_noscan(b.len + 1) + vmemcpy(buf, b.data, b.len) + buf[b.len] = 0 + return tos(buf, b.len) + } +} diff --git a/v_windows/v/vlib/builtin/sorted_map.v b/v_windows/v/vlib/builtin/sorted_map.v new file mode 100644 index 0000000..0f31424 --- /dev/null +++ b/v_windows/v/vlib/builtin/sorted_map.v @@ -0,0 +1,457 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +// import strings + +// B-trees are balanced search trees with all leaves at +// the same level. B-trees are generally faster than +// binary search trees due to the better locality of +// reference, since multiple keys are stored in one node. + +// The number for `degree` has been picked through vigor- +// ous benchmarking but can be changed to any number > 1. +// `degree` determines the maximum length of each node. +const ( + degree = 6 + mid_index = degree - 1 + max_len = 2 * degree - 1 + children_bytes = sizeof(voidptr) * (max_len + 1) +) + +pub struct SortedMap { + value_bytes int +mut: + root &mapnode +pub mut: + len int +} + +struct mapnode { +mut: + children &voidptr + len int + keys [11]string // TODO: Should use `max_len` + values [11]voidptr // TODO: Should use `max_len` +} + +fn new_sorted_map(n int, value_bytes int) SortedMap { // TODO: Remove `n` + return SortedMap{ + value_bytes: value_bytes + root: new_node() + len: 0 + } +} + +fn new_sorted_map_init(n int, value_bytes int, keys &string, values voidptr) SortedMap { + mut out := new_sorted_map(n, value_bytes) + for i in 0 .. n { + unsafe { + out.set(keys[i], &byte(values) + i * value_bytes) + } + } + return out +} + +// The tree is initialized with an empty node as root to +// avoid having to check whether the root is null for +// each insertion. +fn new_node() &mapnode { + return &mapnode{ + children: 0 + len: 0 + } +} + +// This implementation does proactive insertion, meaning +// that splits are done top-down and not bottom-up. +fn (mut m SortedMap) set(key string, value voidptr) { + mut node := m.root + mut child_index := 0 + mut parent := &mapnode(0) + for { + if node.len == max_len { + if isnil(parent) { + parent = new_node() + m.root = parent + } + parent.split_child(child_index, mut node) + if key == parent.keys[child_index] { + unsafe { + vmemcpy(parent.values[child_index], value, m.value_bytes) + } + return + } + if key < parent.keys[child_index] { + node = unsafe { &mapnode(parent.children[child_index]) } + } else { + node = unsafe { &mapnode(parent.children[child_index + 1]) } + } + } + mut i := 0 + for i < node.len && key > node.keys[i] { + i++ + } + if i != node.len && key == node.keys[i] { + unsafe { + vmemcpy(node.values[i], value, m.value_bytes) + } + return + } + if isnil(node.children) { + mut j := node.len - 1 + for j >= 0 && key < node.keys[j] { + node.keys[j + 1] = node.keys[j] + node.values[j + 1] = node.values[j] + j-- + } + node.keys[j + 1] = key + unsafe { + node.values[j + 1] = malloc(m.value_bytes) + vmemcpy(node.values[j + 1], value, m.value_bytes) + } + node.len++ + m.len++ + return + } + parent = node + child_index = i + node = unsafe { &mapnode(node.children[child_index]) } + } +} + +fn (mut n mapnode) split_child(child_index int, mut y mapnode) { + mut z := new_node() + z.len = mid_index + y.len = mid_index + for j := mid_index - 1; j >= 0; j-- { + z.keys[j] = y.keys[j + degree] + z.values[j] = y.values[j + degree] + } + if !isnil(y.children) { + z.children = unsafe { &voidptr(malloc(int(children_bytes))) } + for jj := degree - 1; jj >= 0; jj-- { + unsafe { + z.children[jj] = y.children[jj + degree] + } + } + } + unsafe { + if isnil(n.children) { + n.children = &voidptr(malloc(int(children_bytes))) + } + n.children[n.len + 1] = n.children[n.len] + } + for j := n.len; j > child_index; j-- { + n.keys[j] = n.keys[j - 1] + n.values[j] = n.values[j - 1] + unsafe { + n.children[j] = n.children[j - 1] + } + } + n.keys[child_index] = y.keys[mid_index] + n.values[child_index] = y.values[mid_index] + unsafe { + n.children[child_index] = voidptr(y) + n.children[child_index + 1] = voidptr(z) + } + n.len++ +} + +fn (m SortedMap) get(key string, out voidptr) bool { + mut node := m.root + for { + mut i := node.len - 1 + for i >= 0 && key < node.keys[i] { + i-- + } + if i != -1 && key == node.keys[i] { + unsafe { + vmemcpy(out, node.values[i], m.value_bytes) + } + return true + } + if isnil(node.children) { + break + } + node = unsafe { &mapnode(node.children[i + 1]) } + } + return false +} + +fn (m SortedMap) exists(key string) bool { + if isnil(m.root) { // TODO: find out why root can be nil + return false + } + mut node := m.root + for { + mut i := node.len - 1 + for i >= 0 && key < node.keys[i] { + i-- + } + if i != -1 && key == node.keys[i] { + return true + } + if isnil(node.children) { + break + } + node = unsafe { &mapnode(node.children[i + 1]) } + } + return false +} + +fn (n &mapnode) find_key(k string) int { + mut idx := 0 + for idx < n.len && n.keys[idx] < k { + idx++ + } + return idx +} + +fn (mut n mapnode) remove_key(k string) bool { + idx := n.find_key(k) + if idx < n.len && n.keys[idx] == k { + if isnil(n.children) { + n.remove_from_leaf(idx) + } else { + n.remove_from_non_leaf(idx) + } + return true + } else { + if isnil(n.children) { + return false + } + flag := if idx == n.len { true } else { false } + if unsafe { &mapnode(n.children[idx]) }.len < degree { + n.fill(idx) + } + + mut node := &mapnode(0) + if flag && idx > n.len { + node = unsafe { &mapnode(n.children[idx - 1]) } + } else { + node = unsafe { &mapnode(n.children[idx]) } + } + return node.remove_key(k) + } +} + +fn (mut n mapnode) remove_from_leaf(idx int) { + for i := idx + 1; i < n.len; i++ { + n.keys[i - 1] = n.keys[i] + n.values[i - 1] = n.values[i] + } + n.len-- +} + +fn (mut n mapnode) remove_from_non_leaf(idx int) { + k := n.keys[idx] + if unsafe { &mapnode(n.children[idx]) }.len >= degree { + mut current := unsafe { &mapnode(n.children[idx]) } + for !isnil(current.children) { + current = unsafe { &mapnode(current.children[current.len]) } + } + predecessor := current.keys[current.len - 1] + n.keys[idx] = predecessor + n.values[idx] = current.values[current.len - 1] + mut node := unsafe { &mapnode(n.children[idx]) } + node.remove_key(predecessor) + } else if unsafe { &mapnode(n.children[idx + 1]) }.len >= degree { + mut current := unsafe { &mapnode(n.children[idx + 1]) } + for !isnil(current.children) { + current = unsafe { &mapnode(current.children[0]) } + } + successor := current.keys[0] + n.keys[idx] = successor + n.values[idx] = current.values[0] + mut node := unsafe { &mapnode(n.children[idx + 1]) } + node.remove_key(successor) + } else { + n.merge(idx) + mut node := unsafe { &mapnode(n.children[idx]) } + node.remove_key(k) + } +} + +fn (mut n mapnode) fill(idx int) { + if idx != 0 && unsafe { &mapnode(n.children[idx - 1]) }.len >= degree { + n.borrow_from_prev(idx) + } else if idx != n.len && unsafe { &mapnode(n.children[idx + 1]) }.len >= degree { + n.borrow_from_next(idx) + } else if idx != n.len { + n.merge(idx) + } else { + n.merge(idx - 1) + } +} + +fn (mut n mapnode) borrow_from_prev(idx int) { + mut child := unsafe { &mapnode(n.children[idx]) } + mut sibling := unsafe { &mapnode(n.children[idx - 1]) } + for i := child.len - 1; i >= 0; i-- { + child.keys[i + 1] = child.keys[i] + child.values[i + 1] = child.values[i] + } + if !isnil(child.children) { + for i := child.len; i >= 0; i-- { + unsafe { + child.children[i + 1] = child.children[i] + } + } + } + child.keys[0] = n.keys[idx - 1] + child.values[0] = n.values[idx - 1] + if !isnil(child.children) { + unsafe { + child.children[0] = sibling.children[sibling.len] + } + } + n.keys[idx - 1] = sibling.keys[sibling.len - 1] + n.values[idx - 1] = sibling.values[sibling.len - 1] + child.len++ + sibling.len-- +} + +fn (mut n mapnode) borrow_from_next(idx int) { + mut child := unsafe { &mapnode(n.children[idx]) } + mut sibling := unsafe { &mapnode(n.children[idx + 1]) } + child.keys[child.len] = n.keys[idx] + child.values[child.len] = n.values[idx] + if !isnil(child.children) { + unsafe { + child.children[child.len + 1] = sibling.children[0] + } + } + n.keys[idx] = sibling.keys[0] + n.values[idx] = sibling.values[0] + for i := 1; i < sibling.len; i++ { + sibling.keys[i - 1] = sibling.keys[i] + sibling.values[i - 1] = sibling.values[i] + } + if !isnil(sibling.children) { + for i := 1; i <= sibling.len; i++ { + unsafe { + sibling.children[i - 1] = sibling.children[i] + } + } + } + child.len++ + sibling.len-- +} + +fn (mut n mapnode) merge(idx int) { + mut child := unsafe { &mapnode(n.children[idx]) } + sibling := unsafe { &mapnode(n.children[idx + 1]) } + child.keys[mid_index] = n.keys[idx] + child.values[mid_index] = n.values[idx] + for i in 0 .. sibling.len { + child.keys[i + degree] = sibling.keys[i] + child.values[i + degree] = sibling.values[i] + } + if !isnil(child.children) { + for i := 0; i <= sibling.len; i++ { + unsafe { + child.children[i + degree] = sibling.children[i] + } + } + } + for i := idx + 1; i < n.len; i++ { + n.keys[i - 1] = n.keys[i] + n.values[i - 1] = n.values[i] + } + for i := idx + 2; i <= n.len; i++ { + unsafe { + n.children[i - 1] = n.children[i] + } + } + child.len += sibling.len + 1 + n.len-- + // free(sibling) +} + +pub fn (mut m SortedMap) delete(key string) { + if m.root.len == 0 { + return + } + + removed := m.root.remove_key(key) + if removed { + m.len-- + } + + if m.root.len == 0 { + // tmp := t.root + if isnil(m.root.children) { + return + } else { + m.root = unsafe { &mapnode(m.root.children[0]) } + } + // free(tmp) + } +} + +// Insert all keys of the subtree into array `keys` +// starting at `at`. Keys are inserted in order. +fn (n &mapnode) subkeys(mut keys []string, at int) int { + mut position := at + if !isnil(n.children) { + // Traverse children and insert + // keys inbetween children + for i in 0 .. n.len { + child := unsafe { &mapnode(n.children[i]) } + position += child.subkeys(mut keys, position) + keys[position] = n.keys[i] + position++ + } + // Insert the keys of the last child + child := unsafe { &mapnode(n.children[n.len]) } + position += child.subkeys(mut keys, position) + } else { + // If leaf, insert keys + for i in 0 .. n.len { + keys[position + i] = n.keys[i] + } + position += n.len + } + // Return # of added keys + return position - at +} + +pub fn (m &SortedMap) keys() []string { + mut keys := []string{len: m.len} + if isnil(m.root) || m.root.len == 0 { + return keys + } + m.root.subkeys(mut keys, 0) + return keys +} + +fn (mut n mapnode) free() { + println('TODO') +} + +pub fn (mut m SortedMap) free() { + if isnil(m.root) { + return + } + m.root.free() +} + +pub fn (m SortedMap) print() { + println('TODO') +} + +// pub fn (m map_string) str() string { +// if m.len == 0 { +// return '{}' +// } +// mut sb := strings.new_builder(50) +// sb.writeln('{') +// for key, val in m { +// sb.writeln(' "$key" => "$val"') +// } +// sb.writeln('}') +// return sb.str() +// } diff --git a/v_windows/v/vlib/builtin/sorting_test.v b/v_windows/v/vlib/builtin/sorting_test.v new file mode 100644 index 0000000..4d0ff9f --- /dev/null +++ b/v_windows/v/vlib/builtin/sorting_test.v @@ -0,0 +1,84 @@ +const ( + unsorted = [2, 30, 10, 20, 1] + sorted_asc = [1, 2, 10, 20, 30] + sorted_desc = [30, 20, 10, 2, 1] +) + +fn test_sorting_simple() { + mut a := unsorted.clone() + a.sort() + eprintln(' a: $a') + assert a == sorted_asc +} + +fn test_sorting_with_condition_expression() { + mut a := unsorted.clone() + a.sort(a > b) + eprintln(' a: $a') + assert a == sorted_desc +} + +fn test_sorting_primitives_with_condition_expression() { + mut x := ['9', '87', '3210', '654'] + x.sort(a.len < b.len) + assert x == ['9', '87', '654', '3210'] +} + +// fn get_score(word string) int { +// mut total := 0 +// for letter in word { +// total += int(letter) - 97 +// } +// return total +// } + +// fn test_sorting_with_fn_call_in_condition_expression() { +// mut words := ['aaaa', 'a', 'b', 'foo', 'bar'] +// words.sort(get_score(a) < get_score(b)) +// } + +fn mysort(mut a []int) { + a.sort() +} + +fn test_sorting_by_passing_a_mut_array_to_a_function() { + mut a := unsorted.clone() + mysort(mut a) + eprintln(' a: $a') + assert a == sorted_asc +} + +/* +fn test_sorting_by_passing_an_anonymous_sorting_function() { + mut a := unsorted + a.sort(fn(a &int, b &int) int { return *b - *a }) + eprintln(' a: $a') + assert a == sort_desc +} +*/ +fn test_sorting_u64s() { + mut a := [u64(3), 2, 1, 9, 0, 8] + a.sort() + eprintln(' a: $a') + assert a == [u64(0), 1, 2, 3, 8, 9] + a.sort(a > b) + eprintln(' a: $a') + assert a == [u64(9), 8, 3, 2, 1, 0] +} + +struct User { + age int + name string +} + +fn g(mut users []User) { + users.sort(a.name > b.name) +} + +fn f(mut users []User) { + users.sort(a.name < b.name) +} + +fn z(mut users []User) { + users.sort(a.name < b.name) +} diff --git a/v_windows/v/vlib/builtin/string.v b/v_windows/v/vlib/builtin/string.v new file mode 100644 index 0000000..1a4815d --- /dev/null +++ b/v_windows/v/vlib/builtin/string.v @@ -0,0 +1,1604 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +import strconv + +/* +NB: A V string should be/is immutable from the point of view of + V user programs after it is first created. A V string is + also slightly larger than the equivalent C string because + the V string also has an integer length attached. + + This tradeoff is made, since V strings are created just *once*, + but potentially used *many times* over their lifetime. + + The V string implementation uses a struct, that has a .str field, + which points to a C style 0 terminated memory block. Although not + strictly necessary from the V point of view, that additional 0 + is *very useful for C interoperability*. + + The V string implementation also has an integer .len field, + containing the length of the .str field, excluding the + terminating 0 (just like the C's strlen(s) would do). + + The 0 ending of .str, and the .len field, mean that in practice: + a) a V string s can be used very easily, wherever a + C string is needed, just by passing s.str, + without a need for further conversion/copying. + + b) where strlen(s) is needed, you can just pass s.len, + without having to constantly recompute the length of s + *over and over again* like some C programs do. This is because + V strings are immutable and so their length does not change. + + Ordinary V code *does not need* to be concerned with the + additional 0 in the .str field. The 0 *must* be put there by the + low level string creating functions inside this module. + + Failing to do this will lead to programs that work most of the + time, when used with pure V functions, but fail in strange ways, + when used with modules using C functions (for example os and so on). +*/ +pub struct string { +pub: + str &byte = 0 // points to a C style 0 terminated string of bytes. + len int // the length of the .str field, excluding the ending 0 byte. It is always equal to strlen(.str). + // NB string.is_lit is an enumeration of the following: + // .is_lit == 0 => a fresh string, should be freed by autofree + // .is_lit == 1 => a literal string from .rodata, should NOT be freed + // .is_lit == -98761234 => already freed string, protects against double frees. + // ---------> ^^^^^^^^^ calling free on these is a bug. + // Any other value means that the string has been corrupted. +mut: + is_lit int +} + +pub fn (s string) runes() []rune { + mut runes := []rune{cap: s.len} + for i := 0; i < s.len; i++ { + char_len := utf8_char_len(unsafe { s.str[i] }) + if char_len > 1 { + end := if s.len - 1 >= i + char_len { i + char_len } else { s.len } + mut r := unsafe { s[i..end] } + runes << r.utf32_code() + i += char_len - 1 + } else { + runes << unsafe { s.str[i] } + } + } + return runes +} + +// tos converts a C string to a V string. +// String data is reused, not copied. +[unsafe] +pub fn tos(s &byte, len int) string { + // This should never happen. + if s == 0 { + panic('tos(): nil string') + } + return string{ + str: unsafe { s } + len: len + } +} + +// tos_clone returns a copy of `s`. +[unsafe] +pub fn tos_clone(s &byte) string { + return unsafe { tos2(s) }.clone() +} + +// tos2 does the same as `tos`, but also calculates the length. Called by `string(bytes)` casts. +// Used only internally. +[unsafe] +pub fn tos2(s &byte) string { + if s == 0 { + panic('tos2: nil string') + } + return string{ + str: unsafe { s } + len: unsafe { vstrlen(s) } + } +} + +// tos3 does the same as `tos2`, but for char*, to avoid warnings. +[unsafe] +pub fn tos3(s &char) string { + if s == 0 { + panic('tos3: nil string') + } + return string{ + str: &byte(s) + len: unsafe { vstrlen_char(s) } + } +} + +// tos4 does the same as `tos2`, but returns an empty string on nil ptr. +[unsafe] +pub fn tos4(s &byte) string { + if s == 0 { + return '' + } + return unsafe { tos2(s) } +} + +// tos5 does the same as `tos4`, but for char*, to avoid warnings. +[unsafe] +pub fn tos5(s &char) string { + if s == 0 { + return '' + } + return unsafe { tos3(s) } +} + +// vstring converts a C style string to a V string. NB: the string data is reused, NOT copied. +// strings returned from this function will be normal V strings beside that (i.e. they would be +// freed by V's -autofree mechanism, when they are no longer used). +[unsafe] +pub fn (bp &byte) vstring() string { + return string{ + str: unsafe { bp } + len: unsafe { vstrlen(bp) } + } +} + +// vstring_with_len converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (bp &byte) vstring_with_len(len int) string { + return string{ + str: unsafe { bp } + len: len + is_lit: 0 + } +} + +// vstring converts C char* to V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp &char) vstring() string { + return string{ + str: &byte(cp) + len: unsafe { vstrlen_char(cp) } + is_lit: 0 + } +} + +// vstring_with_len converts C char* to V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp &char) vstring_with_len(len int) string { + return string{ + str: &byte(cp) + len: len + is_lit: 0 + } +} + +// vstring_literal converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +// NB2: unlike vstring, vstring_literal will mark the string +// as a literal, so it will not be freed by autofree. +// This is suitable for readonly strings, C string literals etc, +// that can be read by the V program, but that should not be +// managed by it, for example `os.args` is implemented using it. +[unsafe] +pub fn (bp &byte) vstring_literal() string { + return string{ + str: unsafe { bp } + len: unsafe { vstrlen(bp) } + is_lit: 1 + } +} + +// vstring_with_len converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (bp &byte) vstring_literal_with_len(len int) string { + return string{ + str: unsafe { bp } + len: len + is_lit: 1 + } +} + +// vstring_literal converts C char* to V string. +// See also vstring_literal defined on byteptr for more details. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp &char) vstring_literal() string { + return string{ + str: &byte(cp) + len: unsafe { vstrlen_char(cp) } + is_lit: 1 + } +} + +// vstring_literal_with_len converts C char* to V string. +// See also vstring_literal_with_len defined on byteptr. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp &char) vstring_literal_with_len(len int) string { + return string{ + str: &byte(cp) + len: len + is_lit: 1 + } +} + +// clone_static returns an independent copy of a given array. +// It should be used only in -autofree generated code. +fn (a string) clone_static() string { + return a.clone() +} + +// clone returns a copy of the V string `a`. +pub fn (a string) clone() string { + if a.len == 0 { + return '' + } + mut b := string{ + str: unsafe { malloc_noscan(a.len + 1) } + len: a.len + } + unsafe { + vmemcpy(b.str, a.str, a.len) + b.str[a.len] = 0 + } + return b +} + +// cstring_to_vstring creates a copy of cstr and turns it into a v string. +[unsafe] +pub fn cstring_to_vstring(cstr &char) string { + return unsafe { tos_clone(&byte(cstr)) } +} + +// replace_once replaces the first occurence of `rep` with the string passed in `with`. +pub fn (s string) replace_once(rep string, with string) string { + idx := s.index_(rep) + if idx == -1 { + return s.clone() + } + return s.substr(0, idx) + with + s.substr(idx + rep.len, s.len) +} + +// replace replaces all occurences of `rep` with the string passed in `with`. +[direct_array_access] +pub fn (s string) replace(rep string, with string) string { + if s.len == 0 || rep.len == 0 || rep.len > s.len { + return s.clone() + } + if !s.contains(rep) { + return s.clone() + } + // TODO PERF Allocating ints is expensive. Should be a stack array + // Get locations of all reps within this string + mut idxs := []int{cap: s.len / rep.len} + defer { + unsafe { idxs.free() } + } + mut idx := 0 + for { + idx = s.index_after(rep, idx) + if idx == -1 { + break + } + idxs << idx + idx += rep.len + } + // Dont change the string if there's nothing to replace + if idxs.len == 0 { + return s.clone() + } + // Now we know the number of replacements we need to do and we can calc the len of the new string + new_len := s.len + idxs.len * (with.len - rep.len) + mut b := unsafe { malloc_noscan(new_len + 1) } // add space for the null byte at the end + // Fill the new string + mut b_i := 0 + mut s_idx := 0 + for _, rep_pos in idxs { + for i in s_idx .. rep_pos { // copy everything up to piece being replaced + unsafe { + b[b_i] = s[i] + } + b_i++ + } + s_idx = rep_pos + rep.len // move string index past replacement + for i in 0 .. with.len { // copy replacement piece + unsafe { + b[b_i] = with[i] + } + b_i++ + } + } + if s_idx < s.len { // if any original after last replacement, copy it + for i in s_idx .. s.len { + unsafe { + b[b_i] = s[i] + } + b_i++ + } + } + unsafe { + b[new_len] = 0 + return tos(b, new_len) + } +} + +struct RepIndex { + idx int + val_idx int +} + +// replace_each replaces all occurences of the string pairs given in `vals`. +// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC' +[direct_array_access] +pub fn (s string) replace_each(vals []string) string { + if s.len == 0 || vals.len == 0 { + return s.clone() + } + if vals.len % 2 != 0 { + eprintln('string.replace_each(): odd number of strings') + return s.clone() + } + // `rep` - string to replace + // `with` - string to replace with + // Remember positions of all rep strings, and calculate the length + // of the new string to do just one allocation. + mut new_len := s.len + mut idxs := []RepIndex{} + mut idx := 0 + s_ := s.clone() + for rep_i := 0; rep_i < vals.len; rep_i += 2 { + // vals: ['rep1, 'with1', 'rep2', 'with2'] + rep := vals[rep_i] + with := vals[rep_i + 1] + + for { + idx = s_.index_after(rep, idx) + if idx == -1 { + break + } + // The string already found is set to `/del`, to avoid duplicate searches. + for i in 0 .. rep.len { + unsafe { + s_.str[idx + i] = 127 + } + } + // We need to remember both the position in the string, + // and which rep/with pair it refers to. + + idxs << RepIndex{ + idx: idx + val_idx: rep_i + } + + idx += rep.len + new_len += with.len - rep.len + } + } + + // Dont change the string if there's nothing to replace + if idxs.len == 0 { + return s.clone() + } + idxs.sort(a.idx < b.idx) + mut b := unsafe { malloc_noscan(new_len + 1) } // add space for 0 terminator + // Fill the new string + mut idx_pos := 0 + mut cur_idx := idxs[idx_pos] + mut b_i := 0 + for i := 0; i < s.len; i++ { + if i == cur_idx.idx { + // Reached the location of rep, replace it with "with" + rep := vals[cur_idx.val_idx] + with := vals[cur_idx.val_idx + 1] + for j in 0 .. with.len { + unsafe { + b[b_i] = with[j] + } + b_i++ + } + // Skip the length of rep, since we just replaced it with "with" + i += rep.len - 1 + // Go to the next index + idx_pos++ + if idx_pos < idxs.len { + cur_idx = idxs[idx_pos] + } + } else { + // Rep doesnt start here, just copy + unsafe { + b[b_i] = s.str[i] + } + b_i++ + } + } + unsafe { + b[new_len] = 0 + return tos(b, new_len) + } +} + +// bool returns `true` if the string equals the word "true" it will return `false` otherwise. +pub fn (s string) bool() bool { + return s == 'true' || s == 't' // TODO t for pg, remove +} + +// int returns the value of the string as an integer `'1'.int() == 1`. +pub fn (s string) int() int { + return int(strconv.common_parse_int(s, 0, 32, false, false) or { 0 }) +} + +// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`. +pub fn (s string) i64() i64 { + return strconv.common_parse_int(s, 0, 64, false, false) or { 0 } +} + +// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`. +pub fn (s string) i8() i8 { + return i8(strconv.common_parse_int(s, 0, 8, false, false) or { 0 }) +} + +// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`. +pub fn (s string) i16() i16 { + return i16(strconv.common_parse_int(s, 0, 16, false, false) or { 0 }) +} + +// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`. +pub fn (s string) f32() f32 { + return f32(strconv.atof64(s)) +} + +// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`. +pub fn (s string) f64() f64 { + return strconv.atof64(s) +} + +// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`. +pub fn (s string) u16() u16 { + return u16(strconv.common_parse_uint(s, 0, 16, false, false) or { 0 }) +} + +// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`. +pub fn (s string) u32() u32 { + return u32(strconv.common_parse_uint(s, 0, 32, false, false) or { 0 }) +} + +// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`. +pub fn (s string) u64() u64 { + return strconv.common_parse_uint(s, 0, 64, false, false) or { 0 } +} + +[direct_array_access] +fn (s string) == (a string) bool { + if s.str == 0 { + // should never happen + panic('string.eq(): nil string') + } + if s.len != a.len { + return false + } + if s.len > 0 { + last_idx := s.len - 1 + if s[last_idx] != a[last_idx] { + return false + } + } + unsafe { + return vmemcmp(s.str, a.str, a.len) == 0 + } +} + +fn (s string) < (a string) bool { + for i in 0 .. s.len { + if i >= a.len || s[i] > a[i] { + return false + } else if s[i] < a[i] { + return true + } + } + if s.len < a.len { + return true + } + return false +} + +fn (s string) + (a string) string { + new_len := a.len + s.len + mut res := string{ + str: unsafe { malloc_noscan(new_len + 1) } + len: new_len + } + for j in 0 .. s.len { + unsafe { + res.str[j] = s.str[j] + } + } + for j in 0 .. a.len { + unsafe { + res.str[s.len + j] = a.str[j] + } + } + unsafe { + res.str[new_len] = 0 // V strings are not null terminated, but just in case + } + return res +} + +// split splits the string to an array by `delim`. +// Example: assert 'A B C'.split(' ') == ['A','B','C'] +// If `delim` is empty the string is split by it's characters. +// Example: assert 'DEF'.split('') == ['D','E','F'] +pub fn (s string) split(delim string) []string { + return s.split_nth(delim, 0) +} + +// split_nth splits the string based on the passed `delim` substring. +// It returns the first Nth parts. When N=0, return all the splits. +// The last returned element has the remainder of the string, even if +// the remainder contains more `delim` substrings. +[direct_array_access] +pub fn (s string) split_nth(delim string, nth int) []string { + mut res := []string{} + mut i := 0 + + match delim.len { + 0 { + i = 1 + for ch in s { + if nth > 0 && i >= nth { + res << s[i..] + break + } + res << ch.ascii_str() + i++ + } + return res + } + 1 { + mut start := 0 + delim_byte := delim[0] + + for i < s.len { + if s[i] == delim_byte { + was_last := nth > 0 && res.len == nth - 1 + if was_last { + break + } + val := s.substr(start, i) + res << val + start = i + delim.len + i = start + } else { + i++ + } + } + + // Then the remaining right part of the string + if nth < 1 || res.len < nth { + res << s[start..] + } + return res + } + else { + mut start := 0 + // Take the left part for each delimiter occurence + for i <= s.len { + is_delim := i + delim.len <= s.len && s.substr(i, i + delim.len) == delim + if is_delim { + was_last := nth > 0 && res.len == nth - 1 + if was_last { + break + } + val := s.substr(start, i) + res << val + start = i + delim.len + i = start + } else { + i++ + } + } + // Then the remaining right part of the string + if nth < 1 || res.len < nth { + res << s[start..] + } + return res + } + } +} + +// split_into_lines splits the string by newline characters. +// newlines are stripped. +// Both `\n` and `\r\n` newline endings are supported. +[direct_array_access] +pub fn (s string) split_into_lines() []string { + mut res := []string{} + if s.len == 0 { + return res + } + mut start := 0 + mut end := 0 + for i := 0; i < s.len; i++ { + if s[i] == 10 { + end = if i > 0 && s[i - 1] == 13 { i - 1 } else { i } + res << if start == end { '' } else { s[start..end] } + start = i + 1 + } + } + if start < s.len { + res << s[start..] + } + return res +} + +// used internally for [2..4] +fn (s string) substr2(start int, _end int, end_max bool) string { + end := if end_max { s.len } else { _end } + return s.substr(start, end) +} + +// substr returns the string between index positions `start` and `end`. +// Example: assert 'ABCD'.substr(1,3) == 'BC' +pub fn (s string) substr(start int, end int) string { + $if !no_bounds_checking ? { + if start > end || start > s.len || end > s.len || start < 0 || end < 0 { + panic('substr($start, $end) out of bounds (len=$s.len)') + } + } + len := end - start + if len == s.len { + return s.clone() + } + mut res := string{ + str: unsafe { malloc_noscan(len + 1) } + len: len + } + for i in 0 .. len { + unsafe { + res.str[i] = s.str[start + i] + } + } + unsafe { + res.str[len] = 0 + } + return res +} + +// index returns the position of the first character of the input string. +// It will return `-1` if the input string can't be found. +fn (s string) index_(p string) int { + if p.len > s.len || p.len == 0 { + return -1 + } + if p.len > 2 { + return s.index_kmp(p) + } + mut i := 0 + for i < s.len { + mut j := 0 + for j < p.len && unsafe { s.str[i + j] == p.str[j] } { + j++ + } + if j == p.len { + return i + } + i++ + } + return -1 +} + +// index returns the position of the first character of the input string. +// It will return `none` if the input string can't be found. +pub fn (s string) index(p string) ?int { + idx := s.index_(p) + if idx == -1 { + return none + } + return idx +} + +// index_kmp does KMP search. +[direct_array_access; manualfree] +fn (s string) index_kmp(p string) int { + if p.len > s.len { + return -1 + } + mut prefix := []int{len: p.len} + defer { + unsafe { prefix.free() } + } + mut j := 0 + for i := 1; i < p.len; i++ { + for unsafe { p.str[j] != p.str[i] } && j > 0 { + j = prefix[j - 1] + } + if unsafe { p.str[j] == p.str[i] } { + j++ + } + prefix[i] = j + } + j = 0 + for i in 0 .. s.len { + for unsafe { p.str[j] != s.str[i] } && j > 0 { + j = prefix[j - 1] + } + if unsafe { p.str[j] == s.str[i] } { + j++ + } + if j == p.len { + return i - p.len + 1 + } + } + return -1 +} + +// index_any returns the position of any of the characters in the input string - if found. +pub fn (s string) index_any(chars string) int { + for c in chars { + idx := s.index_(c.ascii_str()) + if idx == -1 { + continue + } + return idx + } + return -1 +} + +// last_index returns the position of the last occurence of the input string. +fn (s string) last_index_(p string) int { + if p.len > s.len || p.len == 0 { + return -1 + } + mut i := s.len - p.len + for i >= 0 { + mut j := 0 + for j < p.len && unsafe { s.str[i + j] == p.str[j] } { + j++ + } + if j == p.len { + return i + } + i-- + } + return -1 +} + +// last_index returns the position of the last occurence of the input string. +pub fn (s string) last_index(p string) ?int { + idx := s.last_index_(p) + if idx == -1 { + return none + } + return idx +} + +// index_after returns the position of the input string, starting search from `start` position. +pub fn (s string) index_after(p string, start int) int { + if p.len > s.len { + return -1 + } + mut strt := start + if start < 0 { + strt = 0 + } + if start >= s.len { + return -1 + } + mut i := strt + for i < s.len { + mut j := 0 + mut ii := i + for j < p.len && unsafe { s.str[ii] == p.str[j] } { + j++ + ii++ + } + if j == p.len { + return i + } + i++ + } + return -1 +} + +// index_byte returns the index of byte `c` if found in the string. +// index_byte returns -1 if the byte can not be found. +pub fn (s string) index_byte(c byte) int { + for i in 0 .. s.len { + if unsafe { s.str[i] } == c { + return i + } + } + return -1 +} + +// last_index_byte returns the index of the last occurence of byte `c` if found in the string. +// last_index_byte returns -1 if the byte is not found. +pub fn (s string) last_index_byte(c byte) int { + for i := s.len - 1; i >= 0; i-- { + if unsafe { s.str[i] == c } { + return i + } + } + return -1 +} + +// count returns the number of occurrences of `substr` in the string. +// count returns -1 if no `substr` could be found. +pub fn (s string) count(substr string) int { + if s.len == 0 || substr.len == 0 { + return 0 + } + if substr.len > s.len { + return 0 + } + + mut n := 0 + + if substr.len == 1 { + target := substr[0] + + for letter in s { + if letter == target { + n++ + } + } + + return n + } + + mut i := 0 + for { + i = s.index_after(substr, i) + if i == -1 { + return n + } + i += substr.len + n++ + } + return 0 // TODO can never get here - v doesn't know that +} + +// contains returns `true` if the string contains `substr`. +pub fn (s string) contains(substr string) bool { + if substr.len == 0 { + return true + } + if s.index_(substr) == -1 { + return false + } + return true +} + +// contains_any returns `true` if the string contains any chars in `chars`. +pub fn (s string) contains_any(chars string) bool { + for c in chars { + if s.contains(c.ascii_str()) { + return true + } + } + return false +} + +// contains_any_substr returns `true` if the string contains any of the strings in `substrs`. +pub fn (s string) contains_any_substr(substrs []string) bool { + if substrs.len == 0 { + return true + } + for sub in substrs { + if s.contains(sub) { + return true + } + } + return false +} + +// starts_with returns `true` if the string starts with `p`. +pub fn (s string) starts_with(p string) bool { + if p.len > s.len { + return false + } + for i in 0 .. p.len { + if unsafe { s.str[i] != p.str[i] } { + return false + } + } + return true +} + +// ends_with returns `true` if the string ends with `p`. +pub fn (s string) ends_with(p string) bool { + if p.len > s.len { + return false + } + for i in 0 .. p.len { + if unsafe { p.str[i] != s.str[s.len - p.len + i] } { + return false + } + } + return true +} + +// to_lower returns the string in all lowercase characters. +// TODO only works with ASCII +pub fn (s string) to_lower() string { + unsafe { + mut b := malloc_noscan(s.len + 1) + for i in 0 .. s.len { + if s.str[i] >= `A` && s.str[i] <= `Z` { + b[i] = s.str[i] + 32 + } else { + b[i] = s.str[i] + } + } + b[s.len] = 0 + return tos(b, s.len) + } +} + +// is_lower returns `true` if all characters in the string is lowercase. +// Example: assert 'hello developer'.is_lower() == true +[direct_array_access] +pub fn (s string) is_lower() bool { + for i in 0 .. s.len { + if s[i] >= `A` && s[i] <= `Z` { + return false + } + } + return true +} + +// to_upper returns the string in all uppercase characters. +// Example: assert 'Hello V'.to_upper() == 'HELLO V' +pub fn (s string) to_upper() string { + unsafe { + mut b := malloc_noscan(s.len + 1) + for i in 0 .. s.len { + if s.str[i] >= `a` && s.str[i] <= `z` { + b[i] = s.str[i] - 32 + } else { + b[i] = s.str[i] + } + } + b[s.len] = 0 + return tos(b, s.len) + } +} + +// is_upper returns `true` if all characters in the string is uppercase. +// Example: assert 'HELLO V'.is_upper() == true +[direct_array_access] +pub fn (s string) is_upper() bool { + for i in 0 .. s.len { + if s[i] >= `a` && s[i] <= `z` { + return false + } + } + return true +} + +// capitalize returns the string with the first character capitalized. +// Example: assert 'hello'.capitalize() == 'Hello' +[direct_array_access] +pub fn (s string) capitalize() string { + if s.len == 0 { + return '' + } + s0 := s[0] + letter := s0.ascii_str() + uletter := letter.to_upper() + if s.len == 1 { + return uletter + } + srest := s[1..] + res := uletter + srest + return res +} + +// is_capital returns `true` if the first character in the string is a capital letter. +// Example: assert 'Hello'.is_capital() == true +[direct_array_access] +pub fn (s string) is_capital() bool { + if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) { + return false + } + for i in 1 .. s.len { + if s[i] >= `A` && s[i] <= `Z` { + return false + } + } + return true +} + +// title returns the string with each word capitalized. +// Example: assert 'hello v developer'.title() == 'Hello V Developer' +pub fn (s string) title() string { + words := s.split(' ') + mut tit := []string{} + for word in words { + tit << word.capitalize() + } + title := tit.join(' ') + return title +} + +// is_title returns true if all words of the string is capitalized. +// Example: assert 'Hello V Developer'.is_title() == true +pub fn (s string) is_title() bool { + words := s.split(' ') + for word in words { + if !word.is_capital() { + return false + } + } + return true +} + +// find_between returns the string found between `start` string and `end` string. +// Example: assert 'hey [man] how you doin'.find_between('[', ']') == 'man' +pub fn (s string) find_between(start string, end string) string { + start_pos := s.index_(start) + if start_pos == -1 { + return '' + } + // First get everything to the right of 'start' + val := s[start_pos + start.len..] + end_pos := val.index_(end) + if end_pos == -1 { + return val + } + return val[..end_pos] +} + +// trim_space strips any of ` `, `\n`, `\t`, `\v`, `\f`, `\r` from the start and end of the string. +// Example: assert ' Hello V '.trim_space() == 'Hello V' +pub fn (s string) trim_space() string { + return s.trim(' \n\t\v\f\r') +} + +// trim strips any of the characters given in `cutset` from the start and end of the string. +// Example: assert ' ffHello V ffff'.trim(' f') == 'Hello V' +[direct_array_access] +pub fn (s string) trim(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + mut pos_left := 0 + mut pos_right := s.len - 1 + mut cs_match := true + for pos_left <= s.len && pos_right >= -1 && cs_match { + cs_match = false + for cs in cutset { + if s[pos_left] == cs { + pos_left++ + cs_match = true + break + } + } + for cs in cutset { + if s[pos_right] == cs { + pos_right-- + cs_match = true + break + } + } + if pos_left > pos_right { + return '' + } + } + return s.substr(pos_left, pos_right + 1) +} + +// trim_left strips any of the characters given in `cutset` from the left of the string. +// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer' +[direct_array_access] +pub fn (s string) trim_left(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + mut pos := 0 + for pos < s.len { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + break + } + } + if !found { + break + } + pos++ + } + return s[pos..] +} + +// trim_right strips any of the characters given in `cutset` from the right of the string. +// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V' +[direct_array_access] +pub fn (s string) trim_right(cutset string) string { + if s.len < 1 || cutset.len < 1 { + return s.clone() + } + mut pos := s.len - 1 + for pos >= 0 { + mut found := false + for cs in cutset { + if s[pos] == cs { + found = true + } + } + if !found { + break + } + pos-- + } + if pos < 0 { + return '' + } + return s[..pos + 1] +} + +// trim_prefix strips `str` from the start of the string. +// Example: assert 'WorldHello V'.trim_prefix('World') == 'Hello V' +pub fn (s string) trim_prefix(str string) string { + if s.starts_with(str) { + return s[str.len..] + } + return s.clone() +} + +// trim_suffix strips `str` from the end of the string. +// Example: assert 'Hello VWorld'.trim_suffix('World') == 'Hello V' +pub fn (s string) trim_suffix(str string) string { + if s.ends_with(str) { + return s[..s.len - str.len] + } + return s.clone() +} + +// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`. +pub fn compare_strings(a &string, b &string) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`. +fn compare_strings_by_len(a &string, b &string) int { + if a.len < b.len { + return -1 + } + if a.len > b.len { + return 1 + } + return 0 +} + +// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing. +fn compare_lower_strings(a &string, b &string) int { + aa := a.to_lower() + bb := b.to_lower() + return compare_strings(&aa, &bb) +} + +// sort_ignore_case sorts the string array using case insesitive comparing. +pub fn (mut s []string) sort_ignore_case() { + s.sort_with_compare(compare_lower_strings) +} + +// sort_by_len sorts the the string array by each string's `.len` length. +pub fn (mut s []string) sort_by_len() { + s.sort_with_compare(compare_strings_by_len) +} + +// str returns a copy of the string +pub fn (s string) str() string { + return s.clone() +} + +// at returns the byte at index `idx`. +// Example: assert 'ABC'.at(1) == byte(`B`) +fn (s string) at(idx int) byte { + $if !no_bounds_checking ? { + if idx < 0 || idx >= s.len { + panic('string index out of range: $idx / $s.len') + } + } + unsafe { + return s.str[idx] + } +} + +// version of `at()` that is used in `a[i] or {` +// return an error when the index is out of range +fn (s string) at_with_check(idx int) ?byte { + if idx < 0 || idx >= s.len { + return error('string index out of range') + } + unsafe { + return s.str[idx] + } +} + +// is_space returns `true` if the byte is a white space character. +// The following list is considered white space characters: ` `, `\t`, `\n`, `\v`, `\f`, `\r`, 0x85, 0xa0 +// Example: assert byte(` `).is_space() == true +[inline] +pub fn (c byte) is_space() bool { + // 0x85 is NEXT LINE (NEL) + // 0xa0 is NO-BREAK SPACE + return c == 32 || (c > 8 && c < 14) || (c == 0x85) || (c == 0xa0) +} + +// is_digit returns `true` if the byte is in range 0-9 and `false` otherwise. +// Example: assert byte(`9`) == true +[inline] +pub fn (c byte) is_digit() bool { + return c >= `0` && c <= `9` +} + +// is_hex_digit returns `true` if the byte is either in range 0-9, a-f or A-F and `false` otherwise. +// Example: assert byte(`F`) == true +[inline] +pub fn (c byte) is_hex_digit() bool { + return c.is_digit() || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`) +} + +// is_oct_digit returns `true` if the byte is in range 0-7 and `false` otherwise. +// Example: assert byte(`7`) == true +[inline] +pub fn (c byte) is_oct_digit() bool { + return c >= `0` && c <= `7` +} + +// is_bin_digit returns `true` if the byte is a binary digit (0 or 1) and `false` otherwise. +// Example: assert byte(`0`) == true +[inline] +pub fn (c byte) is_bin_digit() bool { + return c == `0` || c == `1` +} + +// is_letter returns `true` if the byte is in range a-z or A-Z and `false` otherwise. +// Example: assert byte(`V`) == true +[inline] +pub fn (c byte) is_letter() bool { + return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) +} + +// free allows for manually freeing the memory occupied by the string +[manualfree; unsafe] +pub fn (s &string) free() { + $if prealloc { + return + } + if s.is_lit == -98761234 { + double_free_msg := unsafe { &byte(c'double string.free() detected\n') } + double_free_msg_len := unsafe { vstrlen(double_free_msg) } + $if freestanding { + bare_eprint(double_free_msg, u64(double_free_msg_len)) + } $else { + _write_buf_to_fd(1, double_free_msg, double_free_msg_len) + } + return + } + if s.is_lit == 1 || s.str == 0 { + return + } + unsafe { + free(s.str) + } + s.is_lit = -98761234 +} + +// before returns the contents before `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.before('.') == '23:34:45' +// Example: assert 'abcd'.before('.') == 'abcd' +// TODO: deprecate and remove either .before or .all_before +pub fn (s string) before(sub string) string { + pos := s.index_(sub) + if pos == -1 { + return s.clone() + } + return s[..pos] +} + +// all_before returns the contents before `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.all_before('.') == '23:34:45' +// Example: assert 'abcd'.all_before('.') == 'abcd' +pub fn (s string) all_before(sub string) string { + // TODO remove dup method + pos := s.index_(sub) + if pos == -1 { + return s.clone() + } + return s[..pos] +} + +// all_before_last returns the contents before the last occurence of `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.all_before_last(':') == '23:34' +// Example: assert 'abcd'.all_before_last('.') == 'abcd' +pub fn (s string) all_before_last(sub string) string { + pos := s.last_index_(sub) + if pos == -1 { + return s.clone() + } + return s[..pos] +} + +// all_after returns the contents after `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.all_after('.') == '234' +// Example: assert 'abcd'.all_after('z') == 'abcd' +pub fn (s string) all_after(sub string) string { + pos := s.index_(sub) + if pos == -1 { + return s.clone() + } + return s[pos + sub.len..] +} + +// all_after_last returns the contents after the last occurence of `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.all_after_last(':') == '45.234' +// Example: assert 'abcd'.all_after_last('z') == 'abcd' +pub fn (s string) all_after_last(sub string) string { + pos := s.last_index_(sub) + if pos == -1 { + return s.clone() + } + return s[pos + sub.len..] +} + +// after returns the contents after the last occurence of `sub` in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.after(':') == '45.234' +// Example: assert 'abcd'.after('z') == 'abcd' +// TODO: deprecate either .all_after_last or .after +pub fn (s string) after(sub string) string { + return s.all_after_last(sub) +} + +// after_char returns the contents after the first occurence of `sub` character in the string. +// If the substring is not found, it returns the full input string. +// Example: assert '23:34:45.234'.after_char(`:`) == '34:45.234' +// Example: assert 'abcd'.after_char(`:`) == 'abcd' +pub fn (s string) after_char(sub byte) string { + mut pos := -1 + for i, c in s { + if c == sub { + pos = i + break + } + } + if pos == -1 { + return s.clone() + } + return s[pos + 1..] +} + +// join joins a string array into a string using `sep` separator. +// Example: assert ['Hello','V'].join(' ') == 'Hello V' +pub fn (a []string) join(sep string) string { + if a.len == 0 { + return '' + } + mut len := 0 + for val in a { + len += val.len + sep.len + } + len -= sep.len + // Allocate enough memory + mut res := string{ + str: unsafe { malloc_noscan(len + 1) } + len: len + } + mut idx := 0 + for i, val in a { + unsafe { + vmemcpy(res.str + idx, val.str, val.len) + idx += val.len + } + // Add sep if it's not last + if i != a.len - 1 { + unsafe { + vmemcpy(res.str + idx, sep.str, sep.len) + idx += sep.len + } + } + } + unsafe { + res.str[res.len] = 0 + } + return res +} + +// join joins a string array into a string using a `\n` newline delimiter. +pub fn (s []string) join_lines() string { + return s.join('\n') +} + +// reverse returns a reversed string. +// Example: assert 'Hello V'.reverse() == 'V olleH' +pub fn (s string) reverse() string { + if s.len == 0 || s.len == 1 { + return s.clone() + } + mut res := string{ + str: unsafe { malloc_noscan(s.len + 1) } + len: s.len + } + for i := s.len - 1; i >= 0; i-- { + unsafe { + res.str[s.len - i - 1] = s[i] + } + } + unsafe { + res.str[res.len] = 0 + } + return res +} + +// limit returns a portion of the string, starting at `0` and extending for a given number of characters afterward. +// 'hello'.limit(2) => 'he' +// 'hi'.limit(10) => 'hi' +pub fn (s string) limit(max int) string { + u := s.runes() + if u.len <= max { + return s.clone() + } + return u[0..max].string() +} + +// hash returns an integer hash of the string. +pub fn (s string) hash() int { + mut h := u32(0) + if h == 0 && s.len > 0 { + for c in s { + h = h * 31 + u32(c) + } + } + return int(h) +} + +// bytes returns the string converted to a byte array. +pub fn (s string) bytes() []byte { + if s.len == 0 { + return [] + } + mut buf := []byte{len: s.len} + unsafe { vmemcpy(buf.data, s.str, s.len) } + return buf +} + +// repeat returns a new string with `count` number of copies of the string it was called on. +pub fn (s string) repeat(count int) string { + if count < 0 { + panic('string.repeat: count is negative: $count') + } else if count == 0 { + return '' + } else if count == 1 { + return s.clone() + } + mut ret := unsafe { malloc_noscan(s.len * count + 1) } + for i in 0 .. count { + for j in 0 .. s.len { + unsafe { + ret[i * s.len + j] = s[j] + } + } + } + new_len := s.len * count + unsafe { + ret[new_len] = 0 + } + return unsafe { ret.vstring_with_len(new_len) } +} + +// fields returns a string array of the string split by `\t` and ` ` +// Example: assert '\t\tv = v'.fields() == ['v', '=', 'v'] +// Example: assert ' sss ssss'.fields() == ['sss', 'ssss'] +pub fn (s string) fields() []string { + mut res := []string{} + mut word_start := 0 + mut word_len := 0 + mut is_in_word := false + mut is_space := false + for i, c in s { + is_space = c in [32, 9, 10] + if !is_space { + word_len++ + } + if !is_in_word && !is_space { + word_start = i + is_in_word = true + continue + } + if is_space && is_in_word { + res << s[word_start..word_start + word_len] + is_in_word = false + word_len = 0 + word_start = 0 + continue + } + } + if is_in_word && word_len > 0 { + // collect the remainder word at the end + res << s[word_start..s.len] + } + return res +} + +// strip_margin allows multi-line strings to be formatted in a way that removes white-space +// before a delimeter. by default `|` is used. +// Note: the delimiter has to be a byte at this time. That means surrounding +// the value in ``. +// +// Example: +// st := 'Hello there, +// |this is a string, +// | Everything before the first | is removed'.strip_margin() +// Returns: +// Hello there, +// this is a string, +// Everything before the first | is removed +pub fn (s string) strip_margin() string { + return s.strip_margin_custom(`|`) +} + +// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|` +[direct_array_access] +pub fn (s string) strip_margin_custom(del byte) string { + mut sep := del + if sep.is_space() { + eprintln('Warning: `strip_margin` cannot use white-space as a delimiter') + eprintln(' Defaulting to `|`') + sep = `|` + } + // don't know how much space the resulting string will be, but the max it + // can be is this big + mut ret := unsafe { malloc_noscan(s.len + 1) } + mut count := 0 + for i := 0; i < s.len; i++ { + if s[i] in [10, 13] { + unsafe { + ret[count] = s[i] + } + count++ + // CRLF + if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 { + unsafe { + ret[count] = s[i + 1] + } + count++ + i++ + } + for s[i] != sep { + i++ + if i >= s.len { + break + } + } + } else { + unsafe { + ret[count] = s[i] + } + count++ + } + } + unsafe { + ret[count] = 0 + return ret.vstring_with_len(count) + } +} diff --git a/v_windows/v/vlib/builtin/string_charptr_byteptr_helpers.v b/v_windows/v/vlib/builtin/string_charptr_byteptr_helpers.v new file mode 100644 index 0000000..5b81b39 --- /dev/null +++ b/v_windows/v/vlib/builtin/string_charptr_byteptr_helpers.v @@ -0,0 +1,104 @@ +module builtin + +// NB: this file will be removed soon + +// byteptr.vbytes() - makes a V []byte structure from a C style memory buffer. NB: the data is reused, NOT copied! +[unsafe] +pub fn (data byteptr) vbytes(len int) []byte { + return unsafe { voidptr(data).vbytes(len) } +} + +// vstring converts a C style string to a V string. NB: the string data is reused, NOT copied. +// strings returned from this function will be normal V strings beside that (i.e. they would be +// freed by V's -autofree mechanism, when they are no longer used). +[unsafe] +pub fn (bp byteptr) vstring() string { + return string{ + str: bp + len: unsafe { vstrlen(bp) } + } +} + +// vstring_with_len converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (bp byteptr) vstring_with_len(len int) string { + return string{ + str: bp + len: len + is_lit: 0 + } +} + +// vstring converts C char* to V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp charptr) vstring() string { + return string{ + str: byteptr(cp) + len: unsafe { vstrlen_char(cp) } + is_lit: 0 + } +} + +// vstring_with_len converts C char* to V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp charptr) vstring_with_len(len int) string { + return string{ + str: byteptr(cp) + len: len + is_lit: 0 + } +} + +// vstring_literal converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +// NB2: unlike vstring, vstring_literal will mark the string +// as a literal, so it will not be freed by autofree. +// This is suitable for readonly strings, C string literals etc, +// that can be read by the V program, but that should not be +// managed by it, for example `os.args` is implemented using it. +[unsafe] +pub fn (bp byteptr) vstring_literal() string { + return string{ + str: bp + len: unsafe { vstrlen(bp) } + is_lit: 1 + } +} + +// vstring_with_len converts a C style string to a V string. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (bp byteptr) vstring_literal_with_len(len int) string { + return string{ + str: bp + len: len + is_lit: 1 + } +} + +// vstring_literal converts C char* to V string. +// See also vstring_literal defined on byteptr for more details. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp charptr) vstring_literal() string { + return string{ + str: byteptr(cp) + len: unsafe { vstrlen_char(cp) } + is_lit: 1 + } +} + +// vstring_literal_with_len converts C char* to V string. +// See also vstring_literal_with_len defined on byteptr. +// NB: the string data is reused, NOT copied. +[unsafe] +pub fn (cp charptr) vstring_literal_with_len(len int) string { + return string{ + str: byteptr(cp) + len: len + is_lit: 1 + } +} diff --git a/v_windows/v/vlib/builtin/string_int_test.v b/v_windows/v/vlib/builtin/string_int_test.v new file mode 100644 index 0000000..a17563d --- /dev/null +++ b/v_windows/v/vlib/builtin/string_int_test.v @@ -0,0 +1,221 @@ +import strconv + +fn test_common_atoi() { + // test common cases + assert '70zzz'.int() == 70 + assert '2901issue'.int() == 2901 + assert '234232w'.int() == 234232 + assert '-9009x'.int() == -9009 + assert '0y'.int() == 0 + + // test lead zeros + assert '0000012'.int() == 12 + assert '-0000012'.int() == -12 + assert '0x001F'.int() == 31 + assert '-0x001F'.int() == -31 + assert '0x001f'.int() == 31 + assert '0o00011'.int() == 9 + assert '0b00001001'.int() == 9 + + // test underscore in string + assert '-10_000'.int() == -10000 + assert '-0x00_0_f_ff'.int() == -0xfff + assert '10_000_000'.int() == 10000000 + + for n in -10000 .. 100000 { + s := n.str() + 'z' + assert s.int() == n + } +} + +fn test_unsigned_cast() { + // tests for u16 + + // test common cases + assert '70zzz'.u16() == 70 + assert '2901issue'.u16() == 2901 + assert '0y'.u16() == 0 + + // test lead zeros + assert '0000012'.u16() == 12 + assert '0x001F'.u16() == 31 + assert '0x001f'.u16() == 31 + assert '0o00011'.u16() == 9 + assert '0b00001001'.u16() == 9 + + // tests for u32 + + // test common cases + assert '70zzz'.u32() == 70 + assert '2901issue'.u32() == 2901 + assert '234232w'.u32() == 234232 + assert '-9009x'.u32() == 0 + assert '0y'.u32() == 0 + + // test lead zeros + assert '0000012'.u32() == 12 + assert '-0000012'.u32() == 0 + assert '0x001F'.u32() == 31 + assert '-0x001F'.u32() == 0 + assert '0x001f'.u32() == 31 + assert '0o00011'.u32() == 9 + assert '0b00001001'.u32() == 9 + + // test underscore in string + assert '-10_000'.u32() == 0 + assert '-0x00_0_f_ff'.u32() == 0 + assert '10_000_000'.u32() == 10000000 + + for n in 0 .. u32(100) { + s := n.str() + 'z' + assert s.u32() == n + } + + // tests for u64 + + // test common cases + assert '70zzz'.u64() == 70 + assert '2901issue'.u64() == 2901 + assert '234232w'.u64() == 234232 + assert '-9009x'.u64() == 0 + assert '0y'.u64() == 0 + + // test lead zeros + assert '0000012'.u64() == 12 + assert '-0000012'.u64() == 0 + assert '0x001F'.u64() == 31 + assert '-0x001F'.u64() == 0 + assert '0x001f'.u64() == 31 + assert '0o00011'.u64() == 9 + assert '0b00001001'.u64() == 9 + + // test underscore in string + assert '-10_000'.u64() == 0 + assert '-0x00_0_f_ff'.u64() == 0 + assert '10_000_000'.u64() == 10000000 + + for n in 0 .. u64(10000) { + s := n.str() + 'z' + assert s.u64() == n + } +} + +fn test_signed_cast() { + // tests for i64 + + // test common cases + assert '70zzz'.i64() == 70 + assert '2901issue'.i64() == 2901 + assert '234232w'.i64() == 234232 + assert '-9009x'.i64() == -9009 + assert '0y'.i64() == 0 + + // test lead zeros + assert '0000012'.i64() == 12 + assert '-0000012'.i64() == -12 + assert '0x001F'.i64() == 31 + assert '-0x001F'.i64() == -31 + assert '0x001f'.i64() == 31 + assert '0o00011'.i64() == 9 + assert '0b00001001'.i64() == 9 + + // test underscore in string + assert '-10_000'.i64() == -10000 + assert '-0x00_0_f_ff'.i64() == -0xfff + assert '10_000_000'.i64() == 10000000 + + for n in -10000 .. 100000 { + s := n.str() + 'z' + assert s.i64() == n + } + + // tests for i8 + + // test common cases + assert '70zzz'.i8() == 70 + assert '29issue'.i8() == 29 + assert '22w'.i8() == 22 + assert '-90x'.i8() == -90 + assert '0y'.i8() == 0 + + // test lead zeros + assert '0000012'.i8() == 12 + assert '-0000012'.i8() == -12 + assert '0x001F'.i8() == 31 + assert '-0x001F'.i8() == -31 + assert '0x001f'.i8() == 31 + assert '0o00011'.i8() == 9 + assert '0b000011'.i8() == 3 + + // test underscore in string + assert '-10_0'.i8() == -100 + assert '-0x0_0_f'.i8() == -0xf + assert '10_0'.i8() == 100 + + for n in -10 .. 100 { + s := n.str() + 'z' + assert s.i8() == n + } + + // tests for i16 + + // test common cases + assert '70zzz'.i16() == 70 + assert '2901issue'.i16() == 2901 + assert '2342w'.i16() == 2342 + assert '-9009x'.i16() == -9009 + assert '0y'.i16() == 0 + + // test lead zeros + assert '0000012'.i16() == 12 + assert '-0000012'.i16() == -12 + assert '0x001F'.i16() == 31 + assert '-0x001F'.i16() == -31 + assert '0x001f'.i16() == 31 + assert '0o00011'.i16() == 9 + assert '0b00001001'.i16() == 9 + + // test underscore in string + assert '-10_0'.i16() == -100 + assert '-0x00_0_fff'.i16() == -0xfff + assert '10_0'.i16() == 100 + + for n in -100 .. 100 { + s := n.str() + 'z' + assert s.i16() == n + } + + // test g format + unsafe { + mut u := strconv.Float64u{ + u: strconv.double_plus_zero + } + assert '${u.f:g}' == '0' + assert '${u.f:G}' == '0' + u.u = strconv.double_minus_zero + assert '${u.f:g}' == '0' + assert '${u.f:G}' == '0' + u.u = strconv.double_plus_infinity + assert '${u.f:g}' == '+inf' + assert '${u.f:G}' == '+INF' + u.u = strconv.double_minus_infinity + assert '${u.f:g}' == '-inf' + assert '${u.f:G}' == '-INF' + } + unsafe { + mut u := strconv.Float32u{ + u: strconv.single_plus_zero + } + assert '${u.f:g}' == '0' + assert '${u.f:G}' == '0' + u.u = strconv.single_minus_zero + assert '${u.f:g}' == '0' + assert '${u.f:G}' == '0' + u.u = strconv.single_plus_infinity + assert '${u.f:g}' == '+inf' + assert '${u.f:G}' == '+INF' + u.u = strconv.single_minus_infinity + assert '${u.f:g}' == '-inf' + assert '${u.f:G}' == '-INF' + } +} diff --git a/v_windows/v/vlib/builtin/string_interpolation.v b/v_windows/v/vlib/builtin/string_interpolation.v new file mode 100644 index 0000000..10064ac --- /dev/null +++ b/v_windows/v/vlib/builtin/string_interpolation.v @@ -0,0 +1,713 @@ +module builtin + +import strconv +import strings + +/*============================================================================= +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains string interpolation V functions +=============================================================================*/ + +//============================================================================= +// Enum format types max 0x1F => 32 types +//============================================================================= +pub enum StrIntpType { + si_no_str = 0 // no parameter to print only fix string + si_c + si_u8 + si_i8 + si_u16 + si_i16 + si_u32 + si_i32 + si_u64 + si_i64 + si_e32 + si_e64 + si_f32 + si_f64 + si_g32 + si_g64 + si_s + si_p + si_vp +} + +pub fn (x StrIntpType) str() string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'c' } + .si_u8 { return 'u8' } + .si_i8 { return 'i8' } + .si_u16 { return 'u16' } + .si_i16 { return 'i16' } + .si_u32 { return 'u32' } + .si_i32 { return 'i32' } + .si_u64 { return 'u64' } + .si_i64 { return 'i64' } + .si_f32 { return 'f32' } + .si_f64 { return 'f64' } + .si_g32 { return 'f32' } // g32 format use f32 data + .si_g64 { return 'f64' } // g64 format use f64 data + .si_e32 { return 'f32' } // e32 format use f32 data + .si_e64 { return 'f64' } // e64 format use f64 data + .si_s { return 's' } + .si_p { return 'p' } + .si_vp { return 'vp' } + } +} + +//============================================================================= +// Union data +//============================================================================= +pub union StrIntpMem { +pub mut: + d_c u32 + d_u8 byte + d_i8 i8 + d_u16 u16 + d_i16 i16 + d_u32 u32 + d_i32 int + d_u64 u64 + d_i64 i64 + d_f32 f32 + d_f64 f64 + d_s string + d_p voidptr + d_vp voidptr +} + +[inline] +fn fabs32(x f32) f32 { + return if x < 0 { -x } else { x } +} + +[inline] +fn fabs64(x f64) f64 { + return if x < 0 { -x } else { x } +} + +[inline] +fn abs64(x i64) u64 { + return if x < 0 { u64(-x) } else { u64(x) } +} + +//========================================= +// +// u32/u64 bit compact format +// +//___ 32 24 16 8 +//___ | | | | +//_3333333333222222222211111111110000000000 +//_9876543210987654321098765432109876543210 +//_nPPPPPPPPBBBBWWWWWWWWWWTDDDDDDDSUAA===== +// = data type 5 bit max 32 data type +// A allign 2 bit Note: for now only 1 used! +// U uppercase 1 bit 0 do nothing, 1 do to_upper() +// S sign 1 bit show the sign if positive +// D decimals 7 bit number of decimals digit to show +// T tail zeros 1 bit 1 remove tail zeros, 0 do nothing +// W Width 10 bit number of char for padding and indentation +// B num base 4 bit start from 2, 0 for base 10 +// P pad char 1/8 bit padding char (in u32 format reduced to 1 bit as flag for `0` padding) +// -------------- +// TOTAL: 39/32 bit +//========================================= + +// convert from data format to compact u64 +pub fn get_str_intp_u64_format(fmt_type StrIntpType, in_width int, in_precision int, in_tail_zeros bool, in_sign bool, in_pad_ch byte, in_base int, in_upper_case bool) u64 { + width := if in_width != 0 { abs64(in_width) } else { u64(0) } + allign := if in_width > 0 { u64(1 << 5) } else { u64(0) } // two bit 0 .left 1 .rigth, for now we use only one + upper_case := if in_upper_case { u64(1 << 7) } else { u64(0) } + sign := if in_sign { u64(1 << 8) } else { u64(0) } + precision := if in_precision != 987698 { + (u64(in_precision & 0x7F) << 9) + } else { + u64(0x7F) << 9 + } + tail_zeros := if in_tail_zeros { u32(1) << 16 } else { u32(0) } + base := u64((in_base & 0xf) << 27) + res := u64((u64(fmt_type) & 0x1F) | allign | upper_case | sign | precision | tail_zeros | (u64(width & 0x3FF) << 17) | base | (u64(in_pad_ch) << 31)) + return res +} + +// convert from data format to compact u32 +pub fn get_str_intp_u32_format(fmt_type StrIntpType, in_width int, in_precision int, in_tail_zeros bool, in_sign bool, in_pad_ch byte, in_base int, in_upper_case bool) u32 { + width := if in_width != 0 { abs64(in_width) } else { u32(0) } + allign := if in_width > 0 { u32(1 << 5) } else { u32(0) } // two bit 0 .left 1 .rigth, for now we use only one + upper_case := if in_upper_case { u32(1 << 7) } else { u32(0) } + sign := if in_sign { u32(1 << 8) } else { u32(0) } + precision := if in_precision != 987698 { + (u32(in_precision & 0x7F) << 9) + } else { + u32(0x7F) << 9 + } + tail_zeros := if in_tail_zeros { u32(1) << 16 } else { u32(0) } + base := u32((in_base & 0xf) << 27) + res := u32((u32(fmt_type) & 0x1F) | allign | upper_case | sign | precision | tail_zeros | (u32(width & 0x3FF) << 17) | base | (u32(in_pad_ch & 1) << 31)) + return res +} + +// convert from struct to formated string +[manualfree] +fn (data StrIntpData) get_fmt_format(mut sb strings.Builder) { + x := data.fmt + typ := StrIntpType(x & 0x1F) + allign := int((x >> 5) & 0x01) + upper_case := if ((x >> 7) & 0x01) > 0 { true } else { false } + sign := int((x >> 8) & 0x01) + precision := int((x >> 9) & 0x7F) + tail_zeros := if ((x >> 16) & 0x01) > 0 { true } else { false } + width := int(i16((x >> 17) & 0x3FF)) + mut base := int(x >> 27) & 0xF + fmt_pad_ch := byte((x >> 31) & 0xFF) + + // no string interpolation is needed, return empty string + if typ == .si_no_str { + return + } + + // if width > 0 { println("${x.hex()} Type: ${x & 0x7F} Width: ${width} Precision: ${precision} allign:${allign}") } + + // manage base if any + if base > 0 { + base += 2 // we start from 2, 0 == base 10 + } + + // mange pad char, for now only 0 allowed + mut pad_ch := byte(` `) + if fmt_pad_ch > 0 { + // pad_ch = fmt_pad_ch + pad_ch = `0` + } + + len0_set := if width > 0 { width } else { -1 } + len1_set := if precision == 0x7F { -1 } else { precision } + sign_set := if sign == 1 { true } else { false } + + mut bf := strconv.BF_param{ + pad_ch: pad_ch // padding char + len0: len0_set // default len for whole the number or string + len1: len1_set // number of decimal digits, if needed + positive: true // mandatory: the sign of the number passed + sign_flag: sign_set // flag for print sign as prefix in padding + allign: .left // alignment of the string + rm_tail_zero: tail_zeros // false // remove the tail zeros from floats + } + + // allign + if fmt_pad_ch == 0 { + match allign { + 0 { bf.allign = .left } + 1 { bf.allign = .right } + // 2 { bf.allign = .center } + else { bf.allign = .left } + } + } else { + bf.allign = .right + } + + unsafe { + // strings + if typ == .si_s { + mut s := '' + if upper_case { + s = data.d.d_s.to_upper() + } else { + s = data.d.d_s.clone() + } + if width == 0 { + sb.write_string(s) + } else { + strconv.format_str_sb(s, bf, mut sb) + } + s.free() + return + } + + // signed int + if typ in [.si_i8, .si_i16, .si_i32, .si_i64] { + mut d := data.d.d_i64 + if typ == .si_i8 { + d = i64(data.d.d_i8) + } else if typ == .si_i16 { + d = i64(data.d.d_i16) + } else if typ == .si_i32 { + d = i64(data.d.d_i32) + } + + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + if d < 0 { + bf.positive = false + } + strconv.format_dec_sb(abs64(d), bf, mut sb) + } else { + mut hx := strconv.format_int(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // unsigned int and pointers + if typ in [.si_u8, .si_u16, .si_u32, .si_u64] { + mut d := data.d.d_u64 + if typ == .si_u8 { + d = u64(data.d.d_u8) + } else if typ == .si_u16 { + d = u64(data.d.d_u16) + } else if typ == .si_u32 { + d = u64(data.d.d_u32) + } + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + strconv.format_dec_sb(d, bf, mut sb) + } else { + mut hx := strconv.format_uint(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // pointers + if typ == .si_p { + mut d := data.d.d_u64 + base = 16 // TODO: **** decide the behaviour of this flag! **** + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + strconv.format_dec_sb(d, bf, mut sb) + } else { + mut hx := strconv.format_uint(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // default settings for floats + mut use_default_str := false + if width == 0 && precision == 0x7F { + bf.len1 = 3 + use_default_str = true + } + if bf.len1 < 0 { + bf.len1 = 3 + } + + match typ { + // floating point + .si_f32 { + // println("HERE: f32") + if use_default_str { + mut f := data.d.d_f32.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + // println("HERE: f32 format") + // println(data.d.d_f32) + if data.d.d_f32 < 0 { + bf.positive = false + } + mut f := strconv.format_fl(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_f64 { + // println("HERE: f64") + if use_default_str { + mut f := data.d.d_f64.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f64 < 0 { + bf.positive = false + } + f_union := strconv.Float64u{ + f: data.d.d_f64 + } + if f_union.u == strconv.double_minus_zero { + bf.positive = false + } + + mut f := strconv.format_fl(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_g32 { + // println("HERE: g32") + if use_default_str { + mut f := data.d.d_f32.strg() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + // Manage +/-0 + if data.d.d_f32 == strconv.single_plus_zero { + tmp_str := '0' + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + return + } + if data.d.d_f32 == strconv.single_minus_zero { + tmp_str := '-0' + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + return + } + // Manage +/-INF + if data.d.d_f32 == strconv.single_plus_infinity { + mut tmp_str := '+inf' + if upper_case { + tmp_str = '+INF' + } + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + } + if data.d.d_f32 == strconv.single_minus_infinity { + mut tmp_str := '-inf' + if upper_case { + tmp_str = '-INF' + } + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + } + + if data.d.d_f32 < 0 { + bf.positive = false + } + d := fabs32(data.d.d_f32) + if d < 999_999.0 && d >= 0.00001 { + mut f := strconv.format_fl(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + return + } + mut f := strconv.format_es(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_g64 { + // println("HERE: g64") + if use_default_str { + mut f := data.d.d_f64.strg() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + // Manage +/-0 + if data.d.d_f64 == strconv.double_plus_zero { + tmp_str := '0' + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + return + } + if data.d.d_f64 == strconv.double_minus_zero { + tmp_str := '-0' + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + return + } + // Manage +/-INF + if data.d.d_f64 == strconv.double_plus_infinity { + mut tmp_str := '+inf' + if upper_case { + tmp_str = '+INF' + } + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + } + if data.d.d_f64 == strconv.double_minus_infinity { + mut tmp_str := '-inf' + if upper_case { + tmp_str = '-INF' + } + strconv.format_str_sb(tmp_str, bf, mut sb) + tmp_str.free() + } + + if data.d.d_f64 < 0 { + bf.positive = false + } + d := fabs64(data.d.d_f64) + if d < 999_999.0 && d >= 0.00001 { + mut f := strconv.format_fl(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + return + } + mut f := strconv.format_es(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_e32 { + // println("HERE: e32") + bf.len1 = 6 + if use_default_str { + mut f := data.d.d_f32.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f32 < 0 { + bf.positive = false + } + mut f := strconv.format_es(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_e64 { + // println("HERE: e64") + bf.len1 = 6 + if use_default_str { + mut f := data.d.d_f64.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f64 < 0 { + bf.positive = false + } + mut f := strconv.format_es(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + // runes + .si_c { + ss := utf32_to_str(data.d.d_c) + sb.write_string(ss) + ss.free() + } + // v pointers + .si_vp { + ss := u64(data.d.d_vp).hex() + sb.write_string(ss) + ss.free() + } + else { + sb.write_string('***ERROR!***') + } + } + } +} + +//==================================================================================== + +// storing struct used by cgen +pub struct StrIntpCgenData { +pub: + str string + fmt string + d string +} + +// NOTE: LOW LEVEL struct +// storing struct passed to V in the C code +pub struct StrIntpData { +pub: + str string + // fmt u64 // expanded version for future use, 64 bit + fmt u32 + d StrIntpMem +} + +// interpolation function +[manualfree] +pub fn str_intp(data_len int, in_data voidptr) string { + mut res := strings.new_builder(256) + unsafe { + mut i := 0 + for i < data_len { + data := &StrIntpData(&byte(in_data) + (int(sizeof(StrIntpData)) * i)) + // avoid empty strings + if data.str.len != 0 { + res.write_string(data.str) + } + // skip empty data + if data.fmt != 0 { + data.get_fmt_format(mut &res) + } + i++ + } + } + ret := res.str() + unsafe { res.free() } + return ret +} + +//==================================================================================== +// Utility for the compiler "auto_str_methods.v" +//==================================================================================== + +// substitute old _STR calls + +pub const ( + // BUG: this const is not released from the memory! use a const for now + // si_s_code = "0x" + int(StrIntpType.si_s).hex() // code for a simple string + si_s_code = '0xfe10' + si_g32_code = '0xfe0e' + si_g64_code = '0xfe0f' +) + +[inline] +pub fn str_intp_sq(in_str string) string { + return 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("\'"), $si_s_code, {.d_s = $in_str}},{_SLIT("\'"), 0, {.d_c = 0 }}}))' +} + +[inline] +pub fn str_intp_rune(in_str string) string { + return 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("\`"), $si_s_code, {.d_s = $in_str}},{_SLIT("\`"), 0, {.d_c = 0 }}}))' +} + +[inline] +pub fn str_intp_g32(in_str string) string { + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, $si_g32_code, {.d_f32 = $in_str }}}))' +} + +[inline] +pub fn str_intp_g64(in_str string) string { + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, $si_g64_code, {.d_f64 = $in_str }}}))' +} + +// replace %% with the in_str +[manualfree] +pub fn str_intp_sub(base_str string, in_str string) string { + index := base_str.index('%%') or { + eprintln('No strin interpolation %% parameteres') + exit(1) + } + // return base_str[..index] + in_str + base_str[index+2..] + + unsafe { + st_str := base_str[..index] + if index + 2 < base_str.len { + en_str := base_str[index + 2..] + res_str := 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("$st_str"), $si_s_code, {.d_s = $in_str }},{_SLIT("$en_str"), 0, {.d_c = 0}}}))' + st_str.free() + en_str.free() + return res_str + } + res2_str := 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("$st_str"), $si_s_code, {.d_s = $in_str }}}))' + st_str.free() + return res2_str + } +} diff --git a/v_windows/v/vlib/builtin/string_strip_margin_test.v b/v_windows/v/vlib/builtin/string_strip_margin_test.v new file mode 100644 index 0000000..372b8af --- /dev/null +++ b/v_windows/v/vlib/builtin/string_strip_margin_test.v @@ -0,0 +1,95 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +fn test_strip_margins_no_tabs() { + no_tabs := ['Hello there', 'This is a string', 'With multiple lines'].join('\n') + no_tabs_stripped := 'Hello there + |This is a string + |With multiple lines'.strip_margin() + assert no_tabs == no_tabs_stripped +} + +fn test_strip_margins_text_before() { + text_before := ['There is text', 'before the delimiter', 'that should be removed as well'].join('\n') + text_before_stripped := 'There is text + f lasj asldfj j lksjdf |before the delimiter + Which is removed hello |that should be removed as well'.strip_margin() + assert text_before_stripped == text_before +} + +fn test_strip_margins_white_space_after_delim() { + tabs := [' Tab', ' spaces', ' another tab'].join('\n') + tabs_stripped := ' Tab + | spaces + | another tab'.strip_margin() + assert tabs == tabs_stripped +} + +fn test_strip_margins_alternate_delim() { + alternate_delimiter := ['This has a different delim,', 'but that is ok', + 'because everything works', + ].join('\n') + alternate_delimiter_stripped := 'This has a different delim, + #but that is ok + #because everything works'.strip_margin_custom(`#`) + assert alternate_delimiter_stripped == alternate_delimiter +} + +fn test_strip_margins_multiple_delims_after_first() { + delim_after_first_instance := ['The delimiter used', + 'only matters the |||| First time it is seen', 'not any | other | times'].join('\n') + delim_after_first_instance_stripped := 'The delimiter used + |only matters the |||| First time it is seen + |not any | other | times'.strip_margin() + assert delim_after_first_instance_stripped == delim_after_first_instance +} + +fn test_strip_margins_uneven_delims() { + uneven_delims := ["It doesn't matter if the delims are uneven,", + 'The text will still be delimited correctly.', 'Maybe not everything needs 3 lines?', + 'Let us go for 4 then', + ].join('\n') + uneven_delims_stripped := "It doesn't matter if the delims are uneven, + |The text will still be delimited correctly. + |Maybe not everything needs 3 lines? + |Let us go for 4 then".strip_margin() + assert uneven_delims_stripped == uneven_delims +} + +fn test_strip_margins_multiple_blank_lines() { + multi_blank_lines := ['Multiple blank lines will be removed.', + ' I actually consider this a feature.', + ].join('\n') + multi_blank_lines_stripped := 'Multiple blank lines will be removed. + + + + | I actually consider this a feature.'.strip_margin() + assert multi_blank_lines == multi_blank_lines_stripped +} + +fn test_strip_margins_end_newline() { + end_with_newline := ['This line will end with a newline', 'Something cool or something.', ''].join('\n') + end_with_newline_stripped := 'This line will end with a newline + |Something cool or something. + + '.strip_margin() + assert end_with_newline_stripped == end_with_newline +} + +fn test_strip_margins_space_delimiter() { + space_delimiter := ['Using a white-space char will', 'revert back to default behavior.'].join('\n') + space_delimiter_stripped := 'Using a white-space char will + |revert back to default behavior.'.strip_margin_custom(`\n`) + assert space_delimiter == space_delimiter_stripped +} + +fn test_strip_margins_crlf() { + crlf := ["This string's line endings have CR as well as LFs.", 'This should pass', 'Definitely'].join('\r\n') + crlf_stripped := "This string's line endings have CR as well as LFs.\r + |This should pass\r + |Definitely".strip_margin() + + assert crlf == crlf_stripped +} diff --git a/v_windows/v/vlib/builtin/string_test.v b/v_windows/v/vlib/builtin/string_test.v new file mode 100644 index 0000000..5936aff --- /dev/null +++ b/v_windows/v/vlib/builtin/string_test.v @@ -0,0 +1,912 @@ +import strings + +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +struct Foo { + bar int +mut: + str string +} + +fn test_add() { + mut a := 'a' + a += 'b' + assert a == ('ab') + a = 'a' + for i := 1; i < 1000; i++ { + a += 'b' + } + assert a.len == 1000 + assert a.ends_with('bbbbb') + a += '123' + assert a.ends_with('3') +} + +fn test_ends_with() { + a := 'browser.v' + assert a.ends_with('.v') + + s := 'V Programming Language' + assert s.ends_with('guage') == true + assert s.ends_with('Language') == true + assert s.ends_with('Programming Language') == true + assert s.ends_with('V') == false +} + +fn test_between() { + s := 'hello [man] how you doing' + assert s.find_between('[', ']') == 'man' +} + +fn test_compare() { + a := 'Music' + b := 'src' + assert b >= a +} + +fn test_lt() { + a := '' + b := 'a' + c := 'a' + d := 'b' + e := 'aa' + f := 'ab' + assert a < b + assert !(b < c) + assert c < d + assert !(d < e) + assert c < e + assert e < f +} + +fn test_ge() { + a := 'aa' + b := 'aa' + c := 'ab' + d := 'abc' + e := 'aaa' + assert b >= a + assert c >= b + assert d >= c + assert !(c >= d) + assert e >= a +} + +fn test_compare_strings() { + a := 'aa' + b := 'aa' + c := 'ab' + d := 'abc' + e := 'aaa' + assert compare_strings(a, b) == 0 + assert compare_strings(b, c) == -1 + assert compare_strings(c, d) == -1 + assert compare_strings(d, e) == 1 + assert compare_strings(a, e) == -1 + assert compare_strings(e, a) == 1 +} + +fn test_sort() { + mut vals := [ + 'arr', + 'an', + 'a', + 'any', + ] + len := vals.len + vals.sort() + assert len == vals.len + assert vals[0] == 'a' + assert vals[1] == 'an' + assert vals[2] == 'any' + assert vals[3] == 'arr' +} + +fn test_sort_reverse() { + mut vals := [ + 'arr', + 'an', + 'a', + 'any', + ] + len := vals.len + vals.sort(b > a) + assert len == vals.len + assert vals[0] == 'a' + assert vals[1] == 'an' + assert vals[2] == 'any' + assert vals[3] == 'arr' +} + +fn test_split_nth() { + a := '1,2,3' + assert a.split(',').len == 3 + assert a.split_nth(',', -1).len == 3 + assert a.split_nth(',', 0).len == 3 + assert a.split_nth(',', 1).len == 1 + assert a.split_nth(',', 2).len == 2 + assert a.split_nth(',', 10).len == 3 + b := '1::2::3' + assert b.split('::').len == 3 + assert b.split_nth('::', -1).len == 3 + assert b.split_nth('::', 0).len == 3 + assert b.split_nth('::', 1).len == 1 + assert b.split_nth('::', 2).len == 2 + assert b.split_nth('::', 10).len == 3 + c := 'ABCDEF' + println(c.split('').len) + assert c.split('').len == 6 + assert c.split_nth('', 3).len == 3 + assert c.split_nth('BC', -1).len == 2 + d := ',' + assert d.split(',').len == 2 + assert d.split_nth('', 3).len == 1 + assert d.split_nth(',', -1).len == 2 + assert d.split_nth(',', 3).len == 2 + e := ',,,0,,,,,a,,b,' + assert e.split(',,').len == 5 + assert e.split_nth(',,', 3).len == 3 + assert e.split_nth(',', -1).len == 12 + assert e.split_nth(',', 3).len == 3 +} + +fn test_split_nth_values() { + line := 'CMD=eprintln(phase=1)' + + a0 := line.split_nth('=', 0) + assert a0.len == 3 + assert a0[0] == 'CMD' + assert a0[1] == 'eprintln(phase' + assert a0[2] == '1)' + + a1 := line.split_nth('=', 1) + assert a1.len == 1 + assert a1[0] == 'CMD=eprintln(phase=1)' + + a2 := line.split_nth('=', 2) + assert a2.len == 2 + assert a2[0] == 'CMD' + assert a2[1] == 'eprintln(phase=1)' + + a3 := line.split_nth('=', 3) + assert a3.len == 3 + assert a3[0] == 'CMD' + assert a3[1] == 'eprintln(phase' + assert a3[2] == '1)' + + a4 := line.split_nth('=', 4) + assert a4.len == 3 + assert a4[0] == 'CMD' + assert a4[1] == 'eprintln(phase' + assert a4[2] == '1)' +} + +fn test_split() { + mut s := 'volt/twitch.v:34' + mut vals := s.split(':') + assert vals.len == 2 + assert vals[0] == 'volt/twitch.v' + assert vals[1] == '34' + // ///////// + s = '2018-01-01z13:01:02' + vals = s.split('z') + assert vals.len == 2 + assert vals[0] == '2018-01-01' + assert vals[1] == '13:01:02' + // ////////// + s = '4627a862c3dec29fb3182a06b8965e0025759e18___1530207969___blue' + vals = s.split('___') + assert vals.len == 3 + assert vals[0] == '4627a862c3dec29fb3182a06b8965e0025759e18' + assert vals[1] == '1530207969' + assert vals[2] == 'blue' + // ///////// + s = 'lalala' + vals = s.split('a') + assert vals.len == 4 + assert vals[0] == 'l' + assert vals[1] == 'l' + assert vals[2] == 'l' + assert vals[3] == '' + // ///////// + s = 'awesome' + a := s.split('') + assert a.len == 7 + assert a[0] == 'a' + assert a[1] == 'w' + assert a[2] == 'e' + assert a[3] == 's' + assert a[4] == 'o' + assert a[5] == 'm' + assert a[6] == 'e' + // ///////// + s = 'wavy turquoise bags' + vals = s.split(' bags') + assert vals.len == 2 + assert vals[0] == 'wavy turquoise' + assert vals[1] == '' +} + +fn test_trim_space() { + a := ' a ' + assert a.trim_space() == 'a' + code := ' + +fn main() { + println(2) +} + +' + code_clean := 'fn main() { + println(2) +}' + assert code.trim_space() == code_clean +} + +fn test_join() { + mut strings := ['a', 'b', 'c'] + mut s := strings.join(' ') + assert s == 'a b c' + strings = [ + 'one +two ', + 'three! +four!', + ] + s = strings.join(' ') + assert s.contains('one') && s.contains('two ') && s.contains('four') + empty := []string{len: 0} + assert empty.join('A') == '' +} + +fn test_clone() { + mut a := 'a' + a += 'a' + a += 'a' + b := a + c := a.clone() + assert c == a + assert c == 'aaa' + assert b == 'aaa' +} + +fn test_replace() { + a := 'hello man!' + mut b := a.replace('man', 'world') + assert b == ('hello world!') + b = b.replace('!', '') + assert b == ('hello world') + b = b.replace('h', 'H') + assert b == ('Hello world') + b = b.replace('foo', 'bar') + assert b == ('Hello world') + s := 'hey man how are you' + assert s.replace('man ', '') == 'hey how are you' + lol := 'lol lol lol' + assert lol.replace('lol', 'LOL') == 'LOL LOL LOL' + b = 'oneBtwoBBthree' + assert b.replace('B', '') == 'onetwothree' + b = '*charptr' + assert b.replace('charptr', 'byteptr') == '*byteptr' + c := 'abc' + assert c.replace('', '-') == c + v := 'a b c d' + assert v.replace(' ', ' ') == 'a b c d' +} + +fn test_replace_each() { + s := 'hello man man :)' + q := s.replace_each([ + 'man', + 'dude', + 'hello', + 'hey', + ]) + assert q == 'hey dude dude :)' + bb := '[b]bold[/b] [code]code[/code]' + assert bb.replace_each([ + '[b]', + '', + '[/b]', + '', + '[code]', + '', + '[/code]', + '', + ]) == 'bold code' + bb2 := '[b]cool[/b]' + assert bb2.replace_each([ + '[b]', + '', + '[/b]', + '', + ]) == 'cool' + t := 'aaaaaaaa' + y := t.replace_each([ + 'aa', + 'b', + ]) + assert y == 'bbbb' + s2 := 'hello_world hello' + assert s2.replace_each(['hello_world', 'aaa', 'hello', 'bbb']) == 'aaa bbb' +} + +fn test_itoa() { + num := 777 + assert num.str() == '777' + big := 7779998 + assert big.str() == '7779998' + a := 3 + assert a.str() == '3' + b := 5555 + assert b.str() == '5555' + zero := 0 + assert zero.str() == '0' + neg := -7 + assert neg.str() == '-7' +} + +fn test_reassign() { + a := 'hi' + mut b := a + b += '!' + assert a == 'hi' + assert b == 'hi!' +} + +fn test_runes() { + s := 'привет' + assert s.len == 12 + s2 := 'privet' + assert s2.len == 6 + u := s.runes() + assert u.len == 6 + assert s2.substr(1, 4).len == 3 + assert s2.substr(1, 4) == 'riv' + assert s2[1..4].len == 3 + assert s2[1..4] == 'riv' + assert s2[..4].len == 4 + assert s2[..4] == 'priv' + assert s2[2..].len == 4 + assert s2[2..] == 'ivet' + assert u[1..4].string().len == 6 + assert u[1..4].string() == 'рив' + assert s2.substr(1, 2) == 'r' + assert u[1..2].string() == 'р' + assert s2.runes()[1] == `r` + assert u[1] == `р` + first := u[0] + last := u[u.len - 1] + assert first.str().len == 2 + assert last.str().len == 2 +} + +fn test_contains() { + s := 'view.v' + assert s.contains('vi') + assert !s.contains('random') + assert ''.contains('') + assert 'abc'.contains('') +} + +fn test_contains_any() { + assert !'team'.contains_any('i') + assert 'fail'.contains_any('ui') + assert 'ure'.contains_any('ui') + assert 'failure'.contains_any('ui') + assert !'foo'.contains_any('') + assert !''.contains_any('') +} + +fn test_contains_any_substr() { + s := 'Some random text' + assert s.contains_any_substr(['false', 'not', 'rand']) + assert !s.contains_any_substr(['ABC', 'invalid']) + assert ''.contains_any_substr([]) + assert 'abc'.contains_any_substr(['']) +} + +fn test_arr_contains() { + a := ['a', 'b', 'c'] + assert a.contains('b') + ints := [1, 2, 3] + assert ints.contains(2) +} + +fn test_to_num() { + s := '7' + assert s.int() == 7 + assert s.u64() == 7 + f := '71.5 hasdf' + // QTODO + assert f.f32() == 71.5 + vals := ['9'] + assert vals[0].int() == 9 + big := '93993993939322' + assert big.u64() == 93993993939322 + assert big.i64() == 93993993939322 +} + +fn test_inter_format_string() { + float_num := 1.52345 + float_num_string := '-${float_num:.3f}-' + assert float_num_string == '-1.523-' + int_num := 7 + int_num_string := '-${int_num:03d}-' + assert int_num_string == '-007-' + ch := `a` + ch_string := '-${ch:c}-' + assert ch_string == '-a-' + hex_n := 192 + hex_n_string := '-${hex_n:x}-' + assert hex_n_string == '-c0-' + oct_n := 192 + oct_n_string := '-${oct_n:o}-' + assert oct_n_string == '-300-' + str := 'abc' + str_string := '-${str:s}-' + assert str_string == '-abc-' +} + +fn test_hash() { + s := '10000' + assert s.hash() == 46730161 + s2 := '24640' + assert s2.hash() == 47778736 + s3 := 'Content-Type' + assert s3.hash() == 949037134 + s4 := 'bad_key' + assert s4.hash() == -346636507 + s5 := '24640' + // From a map collision test + assert s5.hash() % ((1 << 20) - 1) == s.hash() % ((1 << 20) - 1) + assert s5.hash() % ((1 << 20) - 1) == 592861 +} + +fn test_trim() { + assert 'banana'.trim('bna') == '' + assert 'abc'.trim('ac') == 'b' + assert 'aaabccc'.trim('ac') == 'b' +} + +fn test_trim_left() { + mut s := 'module main' + assert s.trim_left(' ') == 'module main' + s = ' module main' + assert s.trim_left(' ') == 'module main' + // test cutset + s = 'banana' + assert s.trim_left('ba') == 'nana' + assert s.trim_left('ban') == '' +} + +fn test_trim_right() { + mut s := 'module main' + assert s.trim_right(' ') == 'module main' + s = 'module main ' + assert s.trim_right(' ') == 'module main' + // test cutset + s = 'banana' + assert s.trim_right('na') == 'b' + assert s.trim_right('ban') == '' +} + +fn test_all_before() { + s := 'fn hello fn' + assert s.all_before(' ') == 'fn' + assert s.all_before('2') == s + assert s.all_before('') == s +} + +fn test_all_before_last() { + s := 'fn hello fn' + assert s.all_before_last(' ') == 'fn hello' + assert s.all_before_last('2') == s + assert s.all_before_last('') == s +} + +fn test_all_after() { + s := 'fn hello' + assert s.all_after('fn ') == 'hello' + assert s.all_after('test') == s + assert s.all_after('') == s + assert s.after('e') == 'llo' + x := s.after('e') + assert x == 'llo' +} + +fn test_reverse() { + assert 'hello'.reverse() == 'olleh' + assert ''.reverse() == '' + assert 'a'.reverse() == 'a' +} + +fn test_bytes_to_string() { + mut buf := vcalloc(10) + unsafe { + buf[0] = `h` + buf[1] = `e` + buf[2] = `l` + buf[3] = `l` + buf[4] = `o` + } + assert unsafe { buf.vstring() } == 'hello' + assert unsafe { buf.vstring_with_len(2) } == 'he' + bytes := [byte(`h`), `e`, `l`, `l`, `o`] + assert bytes.bytestr() == 'hello' +} + +fn test_charptr() { + foo := &char('VLANG'.str) + println(typeof(foo).name) + assert typeof(foo).name == '&char' + assert unsafe { foo.vstring() } == 'VLANG' + assert unsafe { foo.vstring_with_len(3) } == 'VLA' +} + +fn test_count() { + assert ''.count('') == 0 + assert ''.count('a') == 0 + assert 'a'.count('') == 0 + assert 'aa'.count('a') == 2 + assert 'aa'.count('aa') == 1 + assert 'aabbaa'.count('aa') == 2 + assert 'bbaabb'.count('aa') == 1 +} + +fn test_lower() { + mut s := 'A' + assert !s.is_lower() + assert s.to_lower() == 'a' + assert s.to_lower().len == 1 + s = 'HELLO' + assert !s.is_lower() + assert s.to_lower() == 'hello' + assert s.to_lower().len == 5 + s = 'Aloha' + assert !s.is_lower() + assert s.to_lower() == 'aloha' + s = 'Have A nice Day!' + assert !s.is_lower() + assert s.to_lower() == 'have a nice day!' + s = 'hi' + assert s.is_lower() + assert s.to_lower() == 'hi' + assert 'aloha!'[0] == `a` + assert 'aloha!'[5] == `!` +} + +fn test_upper() { + mut s := 'a' + assert !s.is_upper() + assert s.to_upper() == 'A' + assert s.to_upper().len == 1 + s = 'hello' + assert !s.is_upper() + assert s.to_upper() == 'HELLO' + assert s.to_upper().len == 5 + s = 'Aloha' + assert !s.is_upper() + assert s.to_upper() == 'ALOHA' + s = 'have a nice day!' + assert !s.is_upper() + assert s.to_upper() == 'HAVE A NICE DAY!' + s = 'HI' + assert s.is_upper() + assert s.to_upper() == 'HI' +} + +fn test_capitalize() { + mut s := 'hello' + assert !s.is_capital() + assert s.capitalize() == 'Hello' + s = 'test' + assert !s.is_capital() + assert s.capitalize() == 'Test' + s = 'i am ray' + assert !s.is_capital() + assert s.capitalize() == 'I am ray' + s = '' + assert !s.is_capital() + assert s.capitalize() == '' + s = 'TEST IT' + assert !s.is_capital() + assert s.capitalize() == 'TEST IT' + s = 'Test it' + assert s.is_capital() + assert s.capitalize() == 'Test it' + assert 'GameMission_t'.capitalize() == 'GameMission_t' +} + +fn test_title() { + mut s := 'hello world' + assert !s.is_title() + assert s.title() == 'Hello World' + s = 'HELLO WORLD' + assert !s.is_title() + assert s.title() == 'HELLO WORLD' + s = 'Hello World' + assert s.is_title() + assert s.title() == 'Hello World' +} + +fn test_for_loop() { + mut i := 0 + s := 'abcd' + + for c in s { + assert c == s[i] + i++ + } +} + +fn test_for_loop_two() { + s := 'abcd' + + for i, c in s { + assert c == s[i] + } +} + +fn test_quote() { + a := `'` + println('testing double quotes') + b := 'hi' + assert b == 'hi' + assert a.str() == "'" +} + +fn test_limit() { + s := 'hello' + assert s.limit(2) == 'he' + assert s.limit(9) == s + assert s.limit(0) == '' + // assert s.limit(-1) == '' +} + +fn test_repeat() { + s1 := 'V! ' + assert s1.repeat(5) == 'V! V! V! V! V! ' + assert s1.repeat(1) == s1 + assert s1.repeat(0) == '' + s2 := '' + assert s2.repeat(5) == s2 + assert s2.repeat(1) == s2 + assert s2.repeat(0) == s2 + // TODO Add test for negative values +} + +fn test_starts_with() { + s := 'V Programming Language' + assert s.starts_with('V') == true + assert s.starts_with('V Programming') == true + assert s.starts_with('Language') == false +} + +fn test_trim_prefix() { + s := 'V Programming Language' + assert s.trim_prefix('V ') == 'Programming Language' + assert s.trim_prefix('V Programming ') == 'Language' + assert s.trim_prefix('Language') == s + + s2 := 'TestTestTest' + assert s2.trim_prefix('Test') == 'TestTest' + assert s2.trim_prefix('TestTest') == 'Test' + + s3 := '123Test123Test' + assert s3.trim_prefix('123') == 'Test123Test' + assert s3.trim_prefix('123Test') == '123Test' +} + +fn test_trim_suffix() { + s := 'V Programming Language' + assert s.trim_suffix(' Language') == 'V Programming' + assert s.trim_suffix(' Programming Language') == 'V' + assert s.trim_suffix('V') == s + + s2 := 'TestTestTest' + assert s2.trim_suffix('Test') == 'TestTest' + assert s2.trim_suffix('TestTest') == 'Test' + + s3 := '123Test123Test' + assert s3.trim_suffix('123') == s3 + assert s3.trim_suffix('123Test') == '123Test' +} + +fn test_raw() { + raw := r'raw\nstring' + lines := raw.split('\n') + println(lines) + assert lines.len == 1 + println('raw string: "$raw"') + + raw2 := r'Hello V\0' + assert raw2[7] == `\\` + assert raw2[8] == `0` + + raw3 := r'Hello V\x00' + assert raw3[7] == `\\` + assert raw3[8] == `x` + assert raw3[9] == `0` + assert raw3[10] == `0` +} + +fn test_raw_with_quotes() { + raw := r"some'" + r'"thing' // " should be escaped in the generated C code + assert raw[0] == `s` + assert raw[5] == `"` + assert raw[6] == `t` +} + +fn test_escape() { + a := 10 + println("\"$a") + assert "\"$a" == '"10' +} + +fn test_atoi() { + assert '234232'.int() == 234232 + assert '-9009'.int() == -9009 + assert '0'.int() == 0 + for n in -10000 .. 100000 { + s := n.str() + assert s.int() == n + } +} + +fn test_raw_inter() { + world := 'world' + println(world) + s := r'hello\n$world' + assert s == r'hello\n$world' + assert s.contains('$') +} + +fn test_c_r() { + // This used to break because of r'' and c'' + c := 42 + println('$c') + r := 50 + println('$r') +} + +fn test_inter_before_comp_if() { + s := '123' + // This used to break ('123 $....') + $if linux { + println(s) + } + assert s == '123' +} + +fn test_double_quote_inter() { + a := 1 + b := 2 + println('$a $b') + assert '$a $b' == '1 2' + assert '$a $b' == '1 2' +} + +fn foo(b byte) byte { + return b - 10 +} + +fn filter(b byte) bool { + return b != `a` +} + +fn test_split_into_lines() { + line_content := 'Line' + text_crlf := '$line_content\r\n$line_content\r\n$line_content' + lines_crlf := text_crlf.split_into_lines() + + assert lines_crlf.len == 3 + for line in lines_crlf { + assert line == line_content + } + + text_lf := '$line_content\n$line_content\n$line_content' + lines_lf := text_lf.split_into_lines() + + assert lines_lf.len == 3 + for line in lines_lf { + assert line == line_content + } +} + +fn test_string_literal_with_backslash() { + a := 'HelloWorld' + assert a == 'HelloWorld' + + b := 'OneTwoThree' + assert b == 'OneTwoThree' +} + +/* +type MyString = string + +fn test_string_alias() { + s := MyString('hi') + ss := s + '!' +} +*/ + +// sort an array of structs, by their string field values + +struct Ka { + s string + i int +} + +fn test_sorter() { + mut arr := [ + Ka{ + s: 'bbb' + i: 100 + }, + Ka{ + s: 'aaa' + i: 101 + }, + Ka{ + s: 'ccc' + i: 102 + }, + ] + cmp := fn (a &Ka, b &Ka) int { + return compare_strings(a.s, b.s) + } + arr.sort_with_compare(cmp) + assert arr[0].s == 'aaa' + assert arr[0].i == 101 + assert arr[1].s == 'bbb' + assert arr[1].i == 100 + assert arr[2].s == 'ccc' + assert arr[2].i == 102 +} + +fn test_fields() { + assert 'a bcde'.fields() == ['a', 'bcde'] + assert ' sss \t ssss '.fields() == ['sss', 'ssss'] + assert '\n xyz \t abc def'.fields() == ['xyz', 'abc', 'def'] + assert 'hello'.fields() == ['hello'] + assert ''.fields() == [] +} + +fn test_interpolation_after_quoted_variable_still_works() { + rr := 'abc' + tt := 'xyz' + + // Basic interpolation, no internal quotes + yy := 'Replacing $rr with $tt' + assert yy == 'Replacing abc with xyz' + + // Interpolation after quoted variable ending with 'r'quote + // that may be mistaken with the start of a raw string, + // ensure that it is not. + ss := 'Replacing "$rr" with "$tt"' + assert ss == 'Replacing "abc" with "xyz"' + zz := "Replacing '$rr' with '$tt'" + assert zz == "Replacing 'abc' with 'xyz'" + + // Interpolation after quoted variable ending with 'c'quote + // may be mistaken with the start of a c string, so + // check it is not. + cc := 'abc' + ccc := "Replacing '$cc' with '$tt'" + assert ccc == "Replacing 'abc' with 'xyz'" + cccq := 'Replacing "$cc" with "$tt"' + assert cccq == 'Replacing "abc" with "xyz"' +} + +fn test_emoji_to_runes() { + x := '👋' + assert x.runes()[0] == `👋` +} + +fn test_string_to_rune() { + x := 'Hello World 👋' + assert x.runes().len == 13 +} diff --git a/v_windows/v/vlib/builtin/utf8.c.v b/v_windows/v/vlib/builtin/utf8.c.v new file mode 100644 index 0000000..f2ead3a --- /dev/null +++ b/v_windows/v/vlib/builtin/utf8.c.v @@ -0,0 +1,79 @@ +module builtin + +const ( + cp_utf8 = 65001 +) + +pub fn (_str string) to_wide() &u16 { + $if windows { + unsafe { + num_chars := (C.MultiByteToWideChar(cp_utf8, 0, &char(_str.str), _str.len, + 0, 0)) + mut wstr := &u16(malloc_noscan((num_chars + 1) * 2)) // sizeof(wchar_t) + if wstr != 0 { + C.MultiByteToWideChar(cp_utf8, 0, &char(_str.str), _str.len, wstr, num_chars) + C.memset(&byte(wstr) + num_chars * 2, 0, 2) + } + return wstr + } + } $else { + return 0 + } +} + +[unsafe] +pub fn string_from_wide(_wstr &u16) string { + $if windows { + unsafe { + wstr_len := C.wcslen(_wstr) + return string_from_wide2(_wstr, wstr_len) + } + } $else { + return '' + } +} + +[unsafe] +pub fn string_from_wide2(_wstr &u16, len int) string { + $if windows { + unsafe { + num_chars := C.WideCharToMultiByte(cp_utf8, 0, _wstr, len, 0, 0, 0, 0) + mut str_to := malloc_noscan(num_chars + 1) + if str_to != 0 { + C.WideCharToMultiByte(cp_utf8, 0, _wstr, len, &char(str_to), num_chars, + 0, 0) + C.memset(str_to + num_chars, 0, 1) + } + return tos2(str_to) + } + } $else { + return '' + } +} + +// Reads an utf8 character from standard input +pub fn utf8_getchar() int { + c := C.getchar() + len := utf8_len(byte(~c)) + if c < 0 { + return 0 + } else if len == 0 { + return c + } else if len == 1 { + return -1 + } else { + mut uc := c & ((1 << (7 - len)) - 1) + for i := 0; i + 1 < len; i++ { + c2 := C.getchar() + if c2 != -1 && (c2 >> 6) == 2 { + uc <<= 6 + uc |= (c2 & 63) + } else if c2 == -1 { + return 0 + } else { + return -1 + } + } + return uc + } +} diff --git a/v_windows/v/vlib/builtin/utf8.v b/v_windows/v/vlib/builtin/utf8.v new file mode 100644 index 0000000..839c249 --- /dev/null +++ b/v_windows/v/vlib/builtin/utf8.v @@ -0,0 +1,191 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builtin + +pub fn utf8_char_len(b byte) int { + return ((0xe5000000 >> ((b >> 3) & 0x1e)) & 3) + 1 +} + +// Convert utf32 to utf8 +// utf32 == Codepoint +pub fn utf32_to_str(code u32) string { + unsafe { + mut buffer := malloc_noscan(5) + res := utf32_to_str_no_malloc(code, buffer) + if res.len == 0 { + // the buffer was not used at all + free(buffer) + } + return res + } +} + +[manualfree; unsafe] +pub fn utf32_to_str_no_malloc(code u32, buf &byte) string { + unsafe { + icode := int(code) // Prevents doing casts everywhere + mut buffer := &byte(buf) + if icode <= 127 { + // 0x7F + buffer[0] = byte(icode) + buffer[1] = 0 + return tos(buffer, 1) + } else if icode <= 2047 { + // 0x7FF + buffer[0] = 192 | byte(icode >> 6) // 0xC0 - 110xxxxx + buffer[1] = 128 | byte(icode & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[2] = 0 + return tos(buffer, 2) + } else if icode <= 65535 { + // 0xFFFF + buffer[0] = 224 | byte(icode >> 12) // 0xE0 - 1110xxxx + buffer[1] = 128 | (byte(icode >> 6) & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[2] = 128 | byte(icode & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[3] = 0 + return tos(buffer, 3) + } + // 0x10FFFF + else if icode <= 1114111 { + buffer[0] = 240 | byte(icode >> 18) // 0xF0 - 11110xxx + buffer[1] = 128 | (byte(icode >> 12) & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[2] = 128 | (byte(icode >> 6) & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[3] = 128 | byte(icode & 63) // 0x80 - 0x3F - 10xxxxxx + buffer[4] = 0 + return tos(buffer, 4) + } + } + return '' +} + +// Convert utf8 to utf32 +pub fn (_rune string) utf32_code() int { + if _rune.len == 0 { + return 0 + } + // save ASC symbol as is + if _rune.len == 1 { + return int(_rune[0]) + } + mut b := byte(int(_rune[0])) + // TODO should be + // res := int( rune[0] << rune.len) + b = b << _rune.len + mut res := int(b) + mut shift := 6 - _rune.len + for i := 1; i < _rune.len; i++ { + c := int(_rune[i]) + res = res << shift + res |= c & 63 // 0x3f + shift = 6 + } + return res +} + +// Calculate length to read from the first byte +fn utf8_len(c byte) int { + mut b := 0 + mut x := c + if (x & 240) != 0 { + // 0xF0 + x >>= 4 + } else { + b += 4 + } + if (x & 12) != 0 { + // 0x0C + x >>= 2 + } else { + b += 2 + } + if (x & 2) == 0 { + // 0x02 + b++ + } + return b +} + +// Calculate string length for in number of codepoints +pub fn utf8_str_len(s string) int { + mut l := 0 + mut i := 0 + for i < s.len { + l++ + i += ((0xe5000000 >> ((unsafe { s.str[i] } >> 3) & 0x1e)) & 3) + 1 + } + return l +} + +// Calculate string length for formatting, i.e. number of "characters" +// This is simplified implementation. if you need specification compliant width, +// use utf8.east_asian.display_width. +pub fn utf8_str_visible_length(s string) int { + mut l := 0 + mut ul := 1 + for i := 0; i < s.len; i += ul { + c := unsafe { s.str[i] } + ul = ((0xe5000000 >> ((unsafe { s.str[i] } >> 3) & 0x1e)) & 3) + 1 + if i + ul > s.len { // incomplete UTF-8 sequence + return l + } + l++ + // avoid the match if not needed + if ul == 1 { + continue + } + // recognize combining characters and wide characters + match ul { + 2 { + r := u64((u16(c) << 8) | unsafe { s.str[i + 1] }) + if r >= 0xcc80 && r < 0xcdb0 { + // diacritical marks + l-- + } + } + 3 { + r := u64((u32(c) << 16) | unsafe { (u32(s.str[i + 1]) << 8) | s.str[i + 2] }) + // diacritical marks extended + // diacritical marks supplement + // diacritical marks for symbols + if (r >= 0xe1aab0 && r <= 0xe1ac7f) + || (r >= 0xe1b780 && r <= 0xe1b87f) + || (r >= 0xe28390 && r <= 0xe2847f) + || (r >= 0xefb8a0 && r <= 0xefb8af) { + // diacritical marks + l-- + } + // Hangru + // CJK Unified Ideographics + // Hangru + // CJK + else if (r >= 0xe18480 && r <= 0xe1859f) + || (r >= 0xe2ba80 && r <= 0xe2bf95) + || (r >= 0xe38080 && r <= 0xe4b77f) + || (r >= 0xe4b880 && r <= 0xea807f) + || (r >= 0xeaa5a0 && r <= 0xeaa79f) + || (r >= 0xeab080 && r <= 0xed9eaf) + || (r >= 0xefa480 && r <= 0xefac7f) + || (r >= 0xefb8b8 && r <= 0xefb9af) { + // half marks + l++ + } + } + 4 { + r := u64((u32(c) << 24) | unsafe { + (u32(s.str[i + 1]) << 16) | (u32(s.str[i + 2]) << 8) | s.str[i + 3] + }) + // Enclosed Ideographic Supplement + // Emoji + // CJK Unified Ideographs Extension B-G + if (r >= 0x0f9f8880 && r <= 0xf09f8a8f) + || (r >= 0xf09f8c80 && r <= 0xf09f9c90) + || (r >= 0xf09fa490 && r <= 0xf09fa7af) + || (r >= 0xf0a08080 && r <= 0xf180807f) { + l++ + } + } + else {} + } + } + return l +} diff --git a/v_windows/v/vlib/builtin/utf8_test.v b/v_windows/v/vlib/builtin/utf8_test.v new file mode 100644 index 0000000..46983f1 --- /dev/null +++ b/v_windows/v/vlib/builtin/utf8_test.v @@ -0,0 +1,28 @@ +fn test_utf8_char_len() { + assert utf8_char_len(`a`) == 1 + println(utf8_char_len(`a`)) + s := 'п' + assert utf8_char_len(s[0]) == 2 +} + +fn test_utf8_wide_char() { + $if msvc { + // TODO: make this test pass msvc too + return + } + r := `✔` + s := '✔' + println('r: $r') + println('s: $s') + rstr := r.str() + println('rstr: $rstr') + assert utf8_char_len(r) == 1 + assert utf8_char_len(s[0]) == 3 + assert s == rstr + val := rstr.str + unsafe { + assert val[0].hex() == 'e2' + assert val[1].hex() == '9c' + assert val[2].hex() == '94' + } +} diff --git a/v_windows/v/vlib/cli/README.md b/v_windows/v/vlib/cli/README.md new file mode 100644 index 0000000..93d8d62 --- /dev/null +++ b/v_windows/v/vlib/cli/README.md @@ -0,0 +1,30 @@ +Usage example: + +```v +module main + +import os +import cli + +fn main() { + mut app := cli.Command{ + name: 'example-app' + description: 'example-app' + execute: fn (cmd cli.Command) ? { + println('hello app') + return + } + commands: [ + cli.Command{ + name: 'sub' + execute: fn (cmd cli.Command) ? { + println('hello subcommand') + return + } + }, + ] + } + app.setup() + app.parse(os.args) +} +``` diff --git a/v_windows/v/vlib/cli/command.v b/v_windows/v/vlib/cli/command.v new file mode 100644 index 0000000..d242e00 --- /dev/null +++ b/v_windows/v/vlib/cli/command.v @@ -0,0 +1,307 @@ +module cli + +type FnCommandCallback = fn (cmd Command) ? + +// str returns the `string` representation of the callback. +pub fn (f FnCommandCallback) str() string { + return 'FnCommandCallback=>' + ptr_str(f) +} + +// Command is a structured representation of a single command +// or chain of commands. +pub struct Command { +pub mut: + name string + usage string + description string + version string + pre_execute FnCommandCallback + execute FnCommandCallback + post_execute FnCommandCallback + disable_help bool + disable_version bool + disable_flags bool + sort_flags bool + sort_commands bool + parent &Command = 0 + commands []Command + flags []Flag + required_args int + args []string + posix_mode bool +} + +// str returns the `string` representation of the `Command`. +pub fn (cmd Command) str() string { + mut res := []string{} + res << 'Command{' + res << ' name: "$cmd.name"' + res << ' usage: "$cmd.usage"' + res << ' version: "$cmd.version"' + res << ' description: "$cmd.description"' + res << ' disable_help: $cmd.disable_help' + res << ' disable_flags: $cmd.disable_flags' + res << ' disable_version: $cmd.disable_version' + res << ' sort_flags: $cmd.sort_flags' + res << ' sort_commands: $cmd.sort_commands' + res << ' cb execute: $cmd.execute' + res << ' cb pre_execute: $cmd.pre_execute' + res << ' cb post_execute: $cmd.post_execute' + if cmd.parent == 0 { + res << ' parent: &Command(0)' + } else { + res << ' parent: &Command{$cmd.parent.name ...}' + } + res << ' commands: $cmd.commands' + res << ' flags: $cmd.flags' + res << ' required_args: $cmd.required_args' + res << ' args: $cmd.args' + res << '}' + return res.join('\n') +} + +// is_root returns `true` if this `Command` has no parents. +pub fn (cmd Command) is_root() bool { + return isnil(cmd.parent) +} + +// root returns the root `Command` of the command chain. +pub fn (cmd Command) root() Command { + if cmd.is_root() { + return cmd + } + return cmd.parent.root() +} + +// full_name returns the full `string` representation of all commands int the chain. +pub fn (cmd Command) full_name() string { + if cmd.is_root() { + return cmd.name + } + return cmd.parent.full_name() + ' $cmd.name' +} + +// add_commands adds the `commands` array of `Command`s as sub-commands. +pub fn (mut cmd Command) add_commands(commands []Command) { + for command in commands { + cmd.add_command(command) + } +} + +// add_command adds `command` as a sub-command of this `Command`. +pub fn (mut cmd Command) add_command(command Command) { + mut subcmd := command + if cmd.commands.contains(subcmd.name) { + println('Command with the name `$subcmd.name` already exists') + exit(1) + } + subcmd.parent = unsafe { cmd } + cmd.commands << subcmd +} + +// setup ensures that all sub-commands of this `Command` +// is linked as a chain. +pub fn (mut cmd Command) setup() { + for mut subcmd in cmd.commands { + subcmd.parent = unsafe { cmd } + subcmd.posix_mode = cmd.posix_mode + subcmd.setup() + } +} + +// add_flags adds the array `flags` to this `Command`. +pub fn (mut cmd Command) add_flags(flags []Flag) { + for flag in flags { + cmd.add_flag(flag) + } +} + +// add_flag adds `flag` to this `Command`. +pub fn (mut cmd Command) add_flag(flag Flag) { + if cmd.flags.contains(flag.name) { + println('Flag with the name `$flag.name` already exists') + exit(1) + } + cmd.flags << flag +} + +// parse parses `args` into this structured `Command`. +pub fn (mut cmd Command) parse(args []string) { + if !cmd.disable_flags { + cmd.add_default_flags() + } + cmd.add_default_commands() + if cmd.sort_flags { + cmd.flags.sort(a.name < b.name) + } + if cmd.sort_commands { + cmd.commands.sort(a.name < b.name) + } + cmd.args = args[1..] + if !cmd.disable_flags { + cmd.parse_flags() + } + cmd.parse_commands() +} + +// add_default_flags adds the commonly used `-h`/`--help` and +// `-v`/`--version` flags to the `Command`. +fn (mut cmd Command) add_default_flags() { + if !cmd.disable_help && !cmd.flags.contains('help') { + use_help_abbrev := !cmd.flags.contains('h') && cmd.posix_mode + cmd.add_flag(help_flag(use_help_abbrev)) + } + if !cmd.disable_version && cmd.version != '' && !cmd.flags.contains('version') { + use_version_abbrev := !cmd.flags.contains('v') && cmd.posix_mode + cmd.add_flag(version_flag(use_version_abbrev)) + } +} + +// add_default_commands adds the command functions of the +// commonly used `help` and `version` flags to the `Command`. +fn (mut cmd Command) add_default_commands() { + if !cmd.disable_help && !cmd.commands.contains('help') && cmd.is_root() { + cmd.add_command(help_cmd()) + } + if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') { + cmd.add_command(version_cmd()) + } +} + +fn (mut cmd Command) parse_flags() { + for { + if cmd.args.len < 1 || !cmd.args[0].starts_with('-') { + break + } + mut found := false + for i in 0 .. cmd.flags.len { + unsafe { + mut flag := &cmd.flags[i] + if flag.matches(cmd.args, cmd.posix_mode) { + found = true + flag.found = true + cmd.args = flag.parse(cmd.args, cmd.posix_mode) or { + println('Failed to parse flag `${cmd.args[0]}`: $err') + exit(1) + } + break + } + } + } + if !found { + println('Command `$cmd.name` has no flag `${cmd.args[0]}`') + exit(1) + } + } +} + +fn (mut cmd Command) parse_commands() { + global_flags := cmd.flags.filter(it.global) + cmd.check_help_flag() + cmd.check_version_flag() + for i in 0 .. cmd.args.len { + arg := cmd.args[i] + for j in 0 .. cmd.commands.len { + mut command := cmd.commands[j] + if command.name == arg { + for flag in global_flags { + command.add_flag(flag) + } + command.parse(cmd.args[i..]) + return + } + } + } + if cmd.is_root() && isnil(cmd.execute) { + if !cmd.disable_help { + cmd.execute_help() + return + } + } + // if no further command was found, execute current command + if cmd.required_args > 0 { + if cmd.required_args > cmd.args.len { + eprintln('Command `$cmd.name` needs at least $cmd.required_args arguments') + exit(1) + } + } + cmd.check_required_flags() + if !isnil(cmd.pre_execute) { + cmd.pre_execute(*cmd) or { + eprintln('cli preexecution error: $err') + exit(1) + } + } + if !isnil(cmd.execute) { + cmd.execute(*cmd) or { + eprintln('cli execution error: $err') + exit(1) + } + } + if !isnil(cmd.post_execute) { + cmd.post_execute(*cmd) or { + eprintln('cli postexecution error: $err') + exit(1) + } + } +} + +fn (cmd Command) check_help_flag() { + if !cmd.disable_help && cmd.flags.contains('help') { + help_flag := cmd.flags.get_bool('help') or { return } // ignore error and handle command normally + if help_flag { + cmd.execute_help() + exit(0) + } + } +} + +fn (cmd Command) check_version_flag() { + if !cmd.disable_version && cmd.version != '' && cmd.flags.contains('version') { + version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally + if version_flag { + version_cmd := cmd.commands.get('version') or { return } // ignore error and handle command normally + version_cmd.execute(version_cmd) or { panic(err) } + exit(0) + } + } +} + +fn (cmd Command) check_required_flags() { + for flag in cmd.flags { + if flag.required && flag.value.len == 0 { + full_name := cmd.full_name() + println('Flag `$flag.name` is required by `$full_name`') + exit(1) + } + } +} + +// execute_help executes the callback registered +// for the `-h`/`--help` flag option. +pub fn (cmd Command) execute_help() { + if cmd.commands.contains('help') { + help_cmd := cmd.commands.get('help') or { return } // ignore error and handle command normally + help_cmd.execute(help_cmd) or { panic(err) } + } else { + print(cmd.help_message()) + } +} + +fn (cmds []Command) get(name string) ?Command { + for cmd in cmds { + if cmd.name == name { + return cmd + } + } + return error('Command `$name` not found in $cmds') +} + +fn (cmds []Command) contains(name string) bool { + for cmd in cmds { + if cmd.name == name { + return true + } + } + return false +} diff --git a/v_windows/v/vlib/cli/command_test.v b/v_windows/v/vlib/cli/command_test.v new file mode 100644 index 0000000..e3c69b1 --- /dev/null +++ b/v_windows/v/vlib/cli/command_test.v @@ -0,0 +1,222 @@ +import cli + +fn test_if_command_parses_empty_args() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + cmd.parse(['command']) + assert cmd.name == 'command' && compare_arrays(cmd.args, []) +} + +fn test_if_command_parses_args() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + cmd.parse(['command', 'arg0', 'arg1']) + assert cmd.name == 'command' && compare_arrays(cmd.args, ['arg0', 'arg1']) +} + +fn test_if_subcommands_parse_args() { + mut cmd := cli.Command{ + name: 'command' + } + subcmd := cli.Command{ + name: 'subcommand' + execute: if_subcommands_parse_args_func + } + cmd.add_command(subcmd) + cmd.parse(['command', 'subcommand', 'arg0', 'arg1']) +} + +fn if_subcommands_parse_args_func(cmd cli.Command) ? { + assert cmd.name == 'subcommand' && compare_arrays(cmd.args, ['arg0', 'arg1']) +} + +fn test_if_command_has_default_help_subcommand() { + mut cmd := cli.Command{ + name: 'command' + } + cmd.parse(['command']) + assert has_command(cmd, 'help') +} + +fn test_if_command_has_default_version_subcommand_if_version_is_set() { + mut cmd := cli.Command{ + name: 'command' + version: '1.0.0' + } + cmd.parse(['command']) + assert has_command(cmd, 'version') +} + +fn flag_should_be_set(cmd cli.Command) ? { + flag := cmd.flags.get_string('flag') ? + assert flag == 'value' +} + +fn test_if_flag_gets_set() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_be_set + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.parse(['command', '-flag', 'value']) +} + +fn test_if_flag_gets_set_with_abbrev() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_be_set + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + abbrev: 'f' + }) + cmd.parse(['command', '-f', 'value']) +} + +fn test_if_flag_gets_set_with_long_arg() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_be_set + posix_mode: true + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + abbrev: 'f' + }) + cmd.parse(['command', '--flag', 'value']) +} + +fn flag_should_have_value_of_42(cmd cli.Command) ? { + flag := cmd.flags.get_string('flag') ? + assert flag == 'value' + value := cmd.flags.get_int('value') ? + assert value == 42 +} + +fn test_if_multiple_flags_get_set() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_have_value_of_42 + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.add_flag(cli.Flag{ + flag: .int + name: 'value' + }) + cmd.parse(['command', '-flag', 'value', '-value', '42']) +} + +fn test_if_required_flags_get_set() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_have_value_of_42 + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.add_flag(cli.Flag{ + flag: .int + name: 'value' + required: true + }) + cmd.parse(['command', '-flag', 'value', '-value', '42']) +} + +fn flag_is_set_in_subcommand(cmd cli.Command) ? { + flag := cmd.flags.get_string('flag') or { panic(err) } + assert flag == 'value' +} + +fn test_if_flag_gets_set_in_subcommand() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + mut subcmd := cli.Command{ + name: 'subcommand' + execute: flag_is_set_in_subcommand + } + subcmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.add_command(subcmd) + cmd.parse(['command', 'subcommand', '-flag', 'value']) +} + +fn test_if_global_flag_gets_set_in_subcommand() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + global: true + }) + subcmd := cli.Command{ + name: 'subcommand' + execute: flag_is_set_in_subcommand + } + cmd.add_command(subcmd) + cmd.parse(['command', '-flag', 'value', 'subcommand']) +} + +fn test_command_setup() { + mut cmd := cli.Command{ + name: 'root' + commands: [ + cli.Command{ + name: 'child' + commands: [ + cli.Command{ + name: 'child-child' + }, + ] + }, + ] + } + assert isnil(cmd.commands[0].parent) + assert isnil(cmd.commands[0].commands[0].parent) + cmd.setup() + assert cmd.commands[0].parent.name == 'root' + assert cmd.commands[0].commands[0].parent.name == 'child' +} + +// helper functions +fn empty_func(cmd cli.Command) ? { +} + +fn has_command(cmd cli.Command, name string) bool { + for subcmd in cmd.commands { + if subcmd.name == name { + return true + } + } + return false +} + +fn compare_arrays(array0 []string, array1 []string) bool { + if array0.len != array1.len { + return false + } + for i in 0 .. array0.len { + if array0[i] != array1[i] { + return false + } + } + return true +} diff --git a/v_windows/v/vlib/cli/flag.v b/v_windows/v/vlib/cli/flag.v new file mode 100644 index 0000000..fb8fa09 --- /dev/null +++ b/v_windows/v/vlib/cli/flag.v @@ -0,0 +1,296 @@ +module cli + +pub enum FlagType { + bool + int + float + string + // If flag can set multiple time, use array type + int_array + float_array + string_array +} + +// Flag holds information for a command line flag. +// (flags are also commonly referred to as "options" or "switches") +// These are typically denoted in the shell by a short form `-f` and/or a long form `--flag` +pub struct Flag { +pub mut: + flag FlagType + // Name of flag + name string + // Like short option + abbrev string + // Desciption of flag + description string + global bool + // If flag is requierd + required bool + // Default value if no value provide by command line + default_value []string = [] +mut: + // Set true if flag found. + found bool + // Value of flag + value []string = [] +} + +// get_all_found returns an array of all `Flag`s found in the command parameters +pub fn (flags []Flag) get_all_found() []Flag { + return flags.filter(it.found) +} + +// get_bool returns `true` if the flag is set. +// get_bool returns an error if the `FlagType` is not boolean. +pub fn (flag Flag) get_bool() ?bool { + if flag.flag != .bool { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `bool`') + } + + val := flag.get_value_or_default_value() + + return val.len > 0 && val[0] == 'true' +} + +// get_bool returns `true` if the flag specified in `name` is set. +// get_bool returns an error if the `FlagType` is not boolean. +pub fn (flags []Flag) get_bool(name string) ?bool { + flag := flags.get(name) ? + return flag.get_bool() +} + +// get_int returns the `int` value argument of the flag. +// get_int returns an error if the `FlagType` is not integer. +pub fn (flag Flag) get_int() ?int { + if flag.flag != .int { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `int`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return 0 + } else { + return val[0].int() + } +} + +// get_ints returns the array of `int` value argument of the flag specified in `name`. +// get_ints returns an error if the `FlagType` is not integer. +pub fn (flag Flag) get_ints() ?[]int { + if flag.flag != .int_array { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `int_array`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return []int{} + } else { + mut values := []int{} + + for f in val { + values << f.int() + } + + return values + } +} + +// get_int returns the `int` value argument of the flag specified in `name`. +// get_int returns an error if the `FlagType` is not integer. +pub fn (flags []Flag) get_int(name string) ?int { + flag := flags.get(name) ? + return flag.get_int() +} + +// get_ints returns the array of `int` value argument of the flag specified in `name`. +// get_ints returns an error if the `FlagType` is not integer. +pub fn (flags []Flag) get_ints(name string) ?[]int { + flag := flags.get(name) ? + return flag.get_ints() +} + +// get_float returns the `f64` value argument of the flag. +// get_float returns an error if the `FlagType` is not floating point. +pub fn (flag Flag) get_float() ?f64 { + if flag.flag != .float { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `float`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return 0.0 + } else { + return val[0].f64() + } +} + +// get_floats returns the `f64` value argument of the flag. +// get_floats returns an error if the `FlagType` is not floating point. +pub fn (flag Flag) get_floats() ?[]f64 { + if flag.flag != .float_array { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `float_array`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return []f64{} + } else { + mut values := []f64{} + + for f in val { + values << f.f64() + } + + return values + } +} + +// get_float returns the `f64` value argument of the flag specified in `name`. +// get_float returns an error if the `FlagType` is not floating point. +pub fn (flags []Flag) get_float(name string) ?f64 { + flag := flags.get(name) ? + return flag.get_float() +} + +// get_floats returns the array of `f64` value argument of the flag specified in `name`. +// get_floats returns an error if the `FlagType` is not floating point. +pub fn (flags []Flag) get_floats(name string) ?[]f64 { + flag := flags.get(name) ? + return flag.get_floats() +} + +// get_string returns the `string` value argument of the flag. +// get_string returns an error if the `FlagType` is not string. +pub fn (flag Flag) get_string() ?string { + if flag.flag != .string { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `string`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return '' + } else { + return val[0] + } +} + +// get_strings returns the array of `string` value argument of the flag. +// get_strings returns an error if the `FlagType` is not string. +pub fn (flag Flag) get_strings() ?[]string { + if flag.flag != .string_array { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `string_array`') + } + + val := flag.get_value_or_default_value() + + if val.len == 0 { + return []string{} + } else { + return val + } +} + +// get_string returns the `string` value argument of the flag specified in `name`. +// get_string returns an error if the `FlagType` is not string. +pub fn (flags []Flag) get_string(name string) ?string { + flag := flags.get(name) ? + return flag.get_string() +} + +// get_strings returns the `string` value argument of the flag specified in `name`. +// get_strings returns an error if the `FlagType` is not string. +pub fn (flags []Flag) get_strings(name string) ?[]string { + flag := flags.get(name) ? + return flag.get_strings() +} + +// parse parses flag values from arguments and return +// an array of arguments with all consumed elements removed. +fn (mut flag Flag) parse(args []string, posix_mode bool) ?[]string { + if flag.matches(args, posix_mode) { + if flag.flag == .bool { + new_args := flag.parse_bool(args) ? + return new_args + } else { + if flag.value.len > 0 && flag.flag != .int_array && flag.flag != .float_array + && flag.flag != .string_array { + return error('The argument `$flag.name` accept only one value!') + } + + new_args := flag.parse_raw(args) ? + return new_args + } + } else { + return args + } +} + +// matches returns `true` if first arg in `args` matches this flag. +fn (mut flag Flag) matches(args []string, posix_mode bool) bool { + prefix := if posix_mode { '--' } else { '-' } + return (flag.name != '' && args[0] == '$prefix$flag.name') + || (flag.name != '' && args[0].starts_with('$prefix$flag.name=')) + || (flag.abbrev != '' && args[0] == '-$flag.abbrev') + || (flag.abbrev != '' && args[0].starts_with('-$flag.abbrev=')) +} + +fn (mut flag Flag) parse_raw(args []string) ?[]string { + if args[0].len > flag.name.len && args[0].contains('=') { + flag.value << args[0].split('=')[1] + return args[1..] + } else if args.len >= 2 { + flag.value << args[1] + return args[2..] + } + return error('Missing argument for `$flag.name`') +} + +fn (mut flag Flag) parse_bool(args []string) ?[]string { + if args[0].len > flag.name.len && args[0].contains('=') { + flag.value = [args[0].split('=')[1]] + return args[1..] + } else if args.len >= 2 { + if args[1] in ['true', 'false'] { + flag.value = [args[1]] + return args[2..] + } + } + // In fact bool cannot be multiple + flag.value = ['true'] + return args[1..] +} + +// get returns the `Flag` matching `name` or an error +// if it can't be found. +fn (flags []Flag) get(name string) ?Flag { + for flag in flags { + if flag.name == name { + return flag + } + } + return error('Flag `$name` not found in $flags') +} + +fn (flags []Flag) contains(name string) bool { + for flag in flags { + if flag.name == name || flag.abbrev == name { + return true + } + } + return false +} + +// Check if value is set by command line option. If not, return default value. +fn (flag Flag) get_value_or_default_value() []string { + if flag.value.len == 0 && flag.default_value.len > 0 { + // If default value is set and no value provide, use default value. + return flag.default_value + } else { + return flag.value + } +} diff --git a/v_windows/v/vlib/cli/flag_test.v b/v_windows/v/vlib/cli/flag_test.v new file mode 100644 index 0000000..a40a7c0 --- /dev/null +++ b/v_windows/v/vlib/cli/flag_test.v @@ -0,0 +1,216 @@ +import cli + +fn test_if_string_flag_parses() { + mut flag := cli.Flag{ + flag: .string + name: 'flag' + } + flag.parse(['-flag', 'value1'], false) or { panic(err) } + mut value := flag.get_string() or { panic(err) } + assert value == 'value1' + + flag = cli.Flag{ + flag: .string + name: 'flag' + } + flag.parse(['-flag=value2'], false) or { panic(err) } + value = flag.get_string() or { panic(err) } + assert value == 'value2' + + flag = cli.Flag{ + flag: .string_array + name: 'flag' + } + flag.parse(['-flag=value1'], false) or { panic(err) } + flag.parse(['-flag=value2'], false) or { panic(err) } + mut values := flag.get_strings() or { panic(err) } + assert values == ['value1', 'value2'] + + flags := [ + cli.Flag{ + flag: .string_array + name: 'flag' + value: ['a', 'b', 'c'] + }, + cli.Flag{ + flag: .string + name: 'flag2' + }, + ] + + values = flags.get_strings('flag') or { panic(err) } + assert values == ['a', 'b', 'c'] +} + +fn test_if_bool_flag_parses() { + mut flag := cli.Flag{ + flag: .bool + name: 'flag' + } + mut value := false + flag.parse(['-flag'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true + flag.parse(['-flag', 'false'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == false + flag.parse(['-flag', 'true'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true + flag.parse(['-flag=false'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == false + flag.parse(['-flag=true'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true +} + +fn test_if_int_flag_parses() { + mut flag := cli.Flag{ + flag: .int + name: 'flag' + } + + mut value := 0 + flag.parse(['-flag', '42'], false) or { panic(err) } + value = flag.get_int() or { panic(err) } + assert value == 42 + + flag = cli.Flag{ + flag: .int + name: 'flag' + } + + flag.parse(['-flag=45'], false) or { panic(err) } + value = flag.get_int() or { panic(err) } + assert value == 45 + + flag = cli.Flag{ + flag: .int_array + name: 'flag' + } + + flag.parse(['-flag=42'], false) or { panic(err) } + flag.parse(['-flag=45'], false) or { panic(err) } + mut values := flag.get_ints() or { panic(err) } + assert values == [42, 45] + + flags := [ + cli.Flag{ + flag: .int_array + name: 'flag' + value: ['1', '2', '3'] + }, + cli.Flag{ + flag: .int + name: 'flag2' + }, + ] + + values = flags.get_ints('flag') or { panic(err) } + assert values == [1, 2, 3] +} + +fn test_if_float_flag_parses() { + mut flag := cli.Flag{ + flag: .float + name: 'flag' + } + mut value := f64(0) + flag.parse(['-flag', '3.14158'], false) or { panic(err) } + value = flag.get_float() or { panic(err) } + assert value == 3.14158 + + flag = cli.Flag{ + flag: .float + name: 'flag' + } + + flag.parse(['-flag=3.14159'], false) or { panic(err) } + value = flag.get_float() or { panic(err) } + assert value == 3.14159 + + flag = cli.Flag{ + flag: .float_array + name: 'flag' + } + + flag.parse(['-flag=3.1'], false) or { panic(err) } + flag.parse(['-flag=1.3'], false) or { panic(err) } + mut values := flag.get_floats() or { panic(err) } + assert values == [3.1, 1.3] + + flags := [ + cli.Flag{ + flag: .float_array + name: 'flag' + value: ['1.1', '2.2', '3.3'] + }, + cli.Flag{ + flag: .float + name: 'flag2' + }, + ] + + values = flags.get_floats('flag') or { panic(err) } + assert values == [1.1, 2.2, 3.3] +} + +fn test_if_flag_parses_with_abbrev() { + mut flag := cli.Flag{ + flag: .bool + name: 'flag' + abbrev: 'f' + } + mut value := false + flag.parse(['--flag'], true) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true + + value = false + flag = cli.Flag{ + flag: .bool + name: 'flag' + abbrev: 'f' + } + flag.parse(['-f'], true) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true +} + +fn test_if_multiple_value_on_single_value() { + mut flag := cli.Flag{ + flag: .float + name: 'flag' + } + + flag.parse(['-flag', '3.14158'], false) or { panic(err) } + + if _ := flag.parse(['-flag', '3.222'], false) { + panic("No multiple value flag don't raise an error!") + } else { + assert true + } +} + +fn test_default_value() { + mut flag := cli.Flag{ + flag: .float + name: 'flag' + default_value: ['1.234'] + } + + flag.parse(['-flag', '3.14158'], false) or { panic(err) } + mut value := flag.get_float() or { panic(err) } + assert value == 3.14158 + + flag = cli.Flag{ + flag: .float + name: 'flag' + default_value: ['1.234'] + } + + flag.parse([''], false) or { panic(err) } + value = flag.get_float() or { panic(err) } + assert value == 1.234 +} diff --git a/v_windows/v/vlib/cli/help.v b/v_windows/v/vlib/cli/help.v new file mode 100644 index 0000000..f47f90e --- /dev/null +++ b/v_windows/v/vlib/cli/help.v @@ -0,0 +1,176 @@ +module cli + +import term +import strings + +const ( + base_indent_len = 2 + min_description_indent_len = 20 + spacing = 2 +) + +fn help_flag(with_abbrev bool) Flag { + sabbrev := if with_abbrev { 'h' } else { '' } + return Flag{ + flag: .bool + name: 'help' + abbrev: sabbrev + description: 'Prints help information.' + } +} + +fn help_cmd() Command { + return Command{ + name: 'help' + usage: '' + description: 'Prints help information.' + execute: print_help_for_command + } +} + +fn print_help_for_command(help_cmd Command) ? { + if help_cmd.args.len > 0 { + mut cmd := help_cmd.parent + for arg in help_cmd.args { + mut found := false + for sub_cmd in cmd.commands { + if sub_cmd.name == arg { + cmd = unsafe { &sub_cmd } + found = true + break + } + } + if !found { + args := help_cmd.args.join(' ') + println('Invalid command: $args') + return + } + } + print(cmd.help_message()) + } else { + if help_cmd.parent != 0 { + print(help_cmd.parent.help_message()) + } + } +} + +fn (cmd Command) help_message() string { + mut help := '' + help += 'Usage: $cmd.full_name()' + if cmd.flags.len > 0 { + help += ' [flags]' + } + if cmd.commands.len > 0 { + help += ' [commands]' + } + if cmd.usage.len > 0 { + help += ' $cmd.usage' + } else { + for i in 0 .. cmd.required_args { + help += ' ' + } + } + help += '\n' + if cmd.description != '' { + help += '\n$cmd.description\n' + } + mut abbrev_len := 0 + mut name_len := cli.min_description_indent_len + if cmd.posix_mode { + for flag in cmd.flags { + if flag.abbrev != '' { + abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front + } + name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 2) // + 2 for '--' in front + } + for command in cmd.commands { + name_len = max(name_len, command.name.len + cli.spacing) + } + } else { + for flag in cmd.flags { + if flag.abbrev != '' { + abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front + } + name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 1) // + 1 for '-' in front + } + for command in cmd.commands { + name_len = max(name_len, command.name.len + cli.spacing) + } + } + if cmd.flags.len > 0 { + help += '\nFlags:\n' + for flag in cmd.flags { + mut flag_name := '' + prefix := if cmd.posix_mode { '--' } else { '-' } + if flag.abbrev != '' { + abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front + flag_name = '-$flag.abbrev$abbrev_indent$prefix$flag.name' + } else { + abbrev_indent := ' '.repeat(abbrev_len) + flag_name = '$abbrev_indent$prefix$flag.name' + } + mut required := '' + if flag.required { + required = ' (required)' + } + base_indent := ' '.repeat(cli.base_indent_len) + description_indent := ' '.repeat(name_len - flag_name.len) + help += '$base_indent$flag_name$description_indent' + + pretty_description(flag.description + required, cli.base_indent_len + name_len) + + '\n' + } + } + if cmd.commands.len > 0 { + help += '\nCommands:\n' + for command in cmd.commands { + base_indent := ' '.repeat(cli.base_indent_len) + description_indent := ' '.repeat(name_len - command.name.len) + help += '$base_indent$command.name$description_indent' + + pretty_description(command.description, name_len) + '\n' + } + } + return help +} + +// pretty_description resizes description text depending on terminal width. +// Essentially, smart wrap-around +fn pretty_description(s string, indent_len int) string { + width, _ := term.get_terminal_size() + // Don't prettify if the terminal is that small, it won't be pretty anyway. + if indent_len > width { + return s + } + indent := ' '.repeat(indent_len) + chars_per_line := width - indent_len + // Give us enough room, better a little bigger than smaller + mut acc := strings.new_builder(((s.len / chars_per_line) + 1) * (width + 1)) + for k, line in s.split('\n') { + if k != 0 { + acc.write_string('\n$indent') + } + mut i := chars_per_line - 2 + mut j := 0 + for ; i < line.len; i += chars_per_line - 2 { + for line[i] != ` ` { + i-- + } + // indent was already done the first iteration + if j != 0 { + acc.write_string(indent) + } + acc.writeln(line[j..i].trim_space()) + j = i + } + // We need this even though it should never happen + if j != 0 { + acc.write_string(indent) + } + acc.write_string(line[j..].trim_space()) + } + return acc.str() +} + +fn max(a int, b int) int { + res := if a > b { a } else { b } + return res +} diff --git a/v_windows/v/vlib/cli/help_test.v b/v_windows/v/vlib/cli/help_test.v new file mode 100644 index 0000000..62cb37e --- /dev/null +++ b/v_windows/v/vlib/cli/help_test.v @@ -0,0 +1,65 @@ +module cli + +fn test_help_message() { + mut cmd := Command{ + name: 'command' + description: 'description' + commands: [ + Command{ + name: 'sub' + description: 'subcommand' + }, + Command{ + name: 'sub2' + description: 'another subcommand' + }, + ] + flags: [ + Flag{ + flag: .string + name: 'str' + description: 'str flag' + }, + Flag{ + flag: .bool + name: 'bool' + description: 'bool flag' + abbrev: 'b' + }, + Flag{ + flag: .string + name: 'required' + abbrev: 'r' + required: true + }, + ] + } + assert cmd.help_message() == r'Usage: command [flags] [commands] + +description + +Flags: + -str str flag + -b -bool bool flag + -r -required (required) + +Commands: + sub subcommand + sub2 another subcommand +' + + cmd.posix_mode = true + assert cmd.help_message() == r'Usage: command [flags] [commands] + +description + +Flags: + --str str flag + -b --bool bool flag + -r --required (required) + +Commands: + sub subcommand + sub2 another subcommand +' +} diff --git a/v_windows/v/vlib/cli/version.v b/v_windows/v/vlib/cli/version.v new file mode 100644 index 0000000..0f3f583 --- /dev/null +++ b/v_windows/v/vlib/cli/version.v @@ -0,0 +1,25 @@ +module cli + +fn version_flag(with_abbrev bool) Flag { + sabbrev := if with_abbrev { 'v' } else { '' } + return Flag{ + flag: .bool + name: 'version' + abbrev: sabbrev + description: 'Prints version information.' + } +} + +fn version_cmd() Command { + return Command{ + name: 'version' + description: 'Prints version information.' + execute: version_func + } +} + +fn version_func(version_cmd Command) ? { + cmd := version_cmd.parent + version := '$cmd.name version $cmd.version' + println(version) +} diff --git a/v_windows/v/vlib/clipboard/clipboard.v b/v_windows/v/vlib/clipboard/clipboard.v new file mode 100644 index 0000000..d82289b --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard.v @@ -0,0 +1,37 @@ +module clipboard + +// new returns a new `Clipboard` instance allocated on the heap. +// The `Clipboard` resources can be released with `free()` +pub fn new() &Clipboard { + return new_clipboard() +} + +// copy copies `text` into the clipboard. +pub fn (mut cb Clipboard) copy(text string) bool { + return cb.set_text(text) +} + +// paste returns current entry as a `string` from the clipboard. +pub fn (mut cb Clipboard) paste() string { + return cb.get_text() +} + +// clear_all clears the clipboard. +pub fn (mut cb Clipboard) clear_all() { + cb.clear() +} + +// destroy destroys the clipboard and free it's resources. +pub fn (mut cb Clipboard) destroy() { + cb.free() +} + +// check_ownership returns `true` if the `Clipboard` has the content ownership. +pub fn (cb Clipboard) check_ownership() bool { + return cb.has_ownership() +} + +// is_available returns `true` if the clipboard is available for use. +pub fn (cb &Clipboard) is_available() bool { + return cb.check_availability() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_android.c.v b/v_windows/v/vlib/clipboard/clipboard_android.c.v new file mode 100644 index 0000000..eb1e6b8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_android.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.dummy + +pub type Clipboard = dummy.Clipboard + +fn new_clipboard() &Clipboard { + return dummy.new_clipboard() +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + return dummy.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_darwin.c.v b/v_windows/v/vlib/clipboard/clipboard_darwin.c.v new file mode 100644 index 0000000..13c9ee8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_darwin.c.v @@ -0,0 +1,70 @@ +module clipboard + +#include +#include +#flag -framework Cocoa +#include "@VEXEROOT/vlib/clipboard/clipboard_darwin.m" + +pub struct Clipboard { + pb voidptr + last_cb_serial i64 +mut: + foo int // TODO remove, for mut hack +} + +fn C.darwin_new_pasteboard() voidptr + +fn C.darwin_get_pasteboard_text(voidptr) &byte + +fn C.darwin_set_pasteboard_text(voidptr, string) bool + +fn new_clipboard() &Clipboard { + cb := &Clipboard{ + pb: C.darwin_new_pasteboard() // pb + } + return cb +} + +pub fn (cb &Clipboard) check_availability() bool { + return cb.pb != C.NULL +} + +pub fn (mut cb Clipboard) clear() { + cb.foo = 0 + cb.set_text('') + //#[cb->pb clearContents]; +} + +pub fn (mut cb Clipboard) free() { + cb.foo = 0 + // nothing to free +} + +pub fn (cb &Clipboard) has_ownership() bool { + if cb.last_cb_serial == 0 { + return false + } + //#return [cb->pb changeCount] == cb->last_cb_serial; + return false +} + +fn C.OSAtomicCompareAndSwapLong() + +pub fn (mut cb Clipboard) set_text(text string) bool { + return C.darwin_set_pasteboard_text(cb.pb, text) +} + +pub fn (mut cb Clipboard) get_text() string { + cb.foo = 0 + if isnil(cb.pb) { + return '' + } + utf8_clip := C.darwin_get_pasteboard_text(cb.pb) + return unsafe { tos_clone(&byte(utf8_clip)) } +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + panic('Primary clipboard is not supported on non-Linux systems.') +} diff --git a/v_windows/v/vlib/clipboard/clipboard_darwin.m b/v_windows/v/vlib/clipboard/clipboard_darwin.m new file mode 100644 index 0000000..cabc744 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_darwin.m @@ -0,0 +1,23 @@ +//NSPasteboard* darwin_new_pasteboard() { +void* darwin_new_pasteboard() { + return (__bridge void*) [NSPasteboard generalPasteboard]; +} + +char* darwin_get_pasteboard_text(void* pb) { + NSString *ns_clip = [((__bridge NSPasteboard*)pb) stringForType:NSStringPboardType]; //NSPasteboardTypeString + if (ns_clip == nil) { + return ""; + } + return [ns_clip UTF8String]; +} + +bool darwin_set_pasteboard_text(void* _pb, string text) { + NSPasteboard* pb = (__bridge NSPasteboard*) _pb; + NSString *ns_clip = [[ NSString alloc ] initWithBytesNoCopy:text.str length:text.len encoding:NSUTF8StringEncoding freeWhenDone: false]; + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + bool ret = [pb setString:ns_clip forType:NSStringPboardType]; + //[ns_clip release]; + int serial = [pb changeCount]; + //OSAtomicCompareAndSwapLong(cb.last_cb_serial, serial, &cb.last_cb_serial); + return ret; +} diff --git a/v_windows/v/vlib/clipboard/clipboard_default.c.v b/v_windows/v/vlib/clipboard/clipboard_default.c.v new file mode 100644 index 0000000..d8f65ce --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_default.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.x11 + +pub type Clipboard = x11.Clipboard + +fn new_clipboard() &Clipboard { + return x11.new_clipboard() +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + return x11.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_solaris.c.v b/v_windows/v/vlib/clipboard/clipboard_solaris.c.v new file mode 100644 index 0000000..eb1e6b8 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_solaris.c.v @@ -0,0 +1,15 @@ +module clipboard + +import clipboard.dummy + +pub type Clipboard = dummy.Clipboard + +fn new_clipboard() &Clipboard { + return dummy.new_clipboard() +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + return dummy.new_primary() +} diff --git a/v_windows/v/vlib/clipboard/clipboard_test.v b/v_windows/v/vlib/clipboard/clipboard_test.v new file mode 100644 index 0000000..6c97f44 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_test.v @@ -0,0 +1,29 @@ +import clipboard + +fn run_test(is_primary bool) { + mut cb := if is_primary { clipboard.new_primary() } else { clipboard.new() } + if !cb.is_available() { + return + } + assert cb.check_ownership() == false + assert cb.copy('I am a good boy!') == true + // assert cb.check_ownership() == true TODO + assert cb.paste() == 'I am a good boy!' + cb.clear_all() + assert cb.paste().len <= 0 + cb.destroy() +} + +fn test_primary() { + $if linux || freebsd { + // run_test(true) + return + } +} + +fn test_clipboard() { + $if linux || freebsd { + return + } + run_test(false) +} diff --git a/v_windows/v/vlib/clipboard/clipboard_windows.c.v b/v_windows/v/vlib/clipboard/clipboard_windows.c.v new file mode 100644 index 0000000..9ae42d1 --- /dev/null +++ b/v_windows/v/vlib/clipboard/clipboard_windows.c.v @@ -0,0 +1,186 @@ +module clipboard + +import time + +#include +#flag -luser32 + +struct WndClassEx { + cb_size u32 + style u32 + lpfn_wnd_proc voidptr + cb_cls_extra int + cb_wnd_extra int + h_instance C.HINSTANCE + h_icon C.HICON + h_cursor C.HCURSOR + hbr_background C.HBRUSH + lpsz_menu_name &u16 // LPCWSTR + lpsz_class_name &u16 + h_icon_sm &u16 +} + +fn C.RegisterClassEx(class &WndClassEx) int + +fn C.GetClipboardOwner() &C.HWND + +fn C.CreateWindowEx(dwExStyle i64, lpClassName &u16, lpWindowName &u16, dwStyle i64, x int, y int, nWidth int, nHeight int, hWndParent i64, hMenu voidptr, h_instance voidptr, lpParam voidptr) &C.HWND + +// fn C.MultiByteToWideChar(CodePage u32, dw_flags u16, lpMultiByteStr byteptr, cbMultiByte int, lpWideCharStr u16, cchWideChar int) int +fn C.EmptyClipboard() + +fn C.CloseClipboard() + +fn C.GlobalAlloc(uFlag u32, size i64) C.HGLOBAL + +fn C.GlobalFree(buf C.HGLOBAL) + +fn C.GlobalLock(buf C.HGLOBAL) voidptr + +fn C.GlobalUnlock(buf C.HGLOBAL) bool + +fn C.SetClipboardData(uFormat u32, data voidptr) C.HANDLE + +fn C.GetClipboardData(uFormat u32) C.HANDLE + +fn C.DefWindowProc(hwnd C.HWND, msg u32, wParam C.WPARAM, lParam C.LPARAM) C.LRESULT + +fn C.SetLastError(error i64) + +fn C.OpenClipboard(hwnd C.HWND) int + +fn C.DestroyWindow(hwnd C.HWND) + +struct Clipboard { + max_retries int + retry_delay int +mut: + hwnd C.HWND + foo int // TODO remove +} + +fn (cb &Clipboard) get_clipboard_lock() bool { + mut retries := cb.max_retries + mut last_error := u32(0) + for { + retries-- + if retries < 0 { + break + } + last_error = C.GetLastError() + if C.OpenClipboard(cb.hwnd) > 0 { + return true + } else if last_error != u32(C.ERROR_ACCESS_DENIED) { + return false + } + time.sleep(cb.retry_delay * time.second) + } + C.SetLastError(last_error) + return false +} + +fn new_clipboard() &Clipboard { + mut cb := &Clipboard{ + max_retries: 5 + retry_delay: 5 + } + class_name := 'clipboard' + wndclass := WndClassEx{ + cb_size: sizeof(WndClassEx) + lpfn_wnd_proc: voidptr(&C.DefWindowProc) + lpsz_class_name: class_name.to_wide() + lpsz_menu_name: 0 + h_icon_sm: 0 + } + if C.RegisterClassEx(&wndclass) == 0 && C.GetLastError() != u32(C.ERROR_CLASS_ALREADY_EXISTS) { + println('Failed registering class.') + } + hwnd := C.CreateWindowEx(0, wndclass.lpsz_class_name, wndclass.lpsz_class_name, 0, + 0, 0, 0, 0, C.HWND_MESSAGE, C.NULL, C.NULL, C.NULL) + if hwnd == C.NULL { + println('Error creating window!') + } + cb.hwnd = hwnd + return cb +} + +pub fn (cb &Clipboard) check_availability() bool { + return cb.hwnd != C.HWND(C.NULL) +} + +pub fn (cb &Clipboard) has_ownership() bool { + return C.GetClipboardOwner() == cb.hwnd +} + +pub fn (mut cb Clipboard) clear() { + if !cb.get_clipboard_lock() { + return + } + C.EmptyClipboard() + C.CloseClipboard() + cb.foo = 0 +} + +pub fn (mut cb Clipboard) free() { + C.DestroyWindow(cb.hwnd) + cb.foo = 0 +} + +// the string.to_wide doesn't work with SetClipboardData, don't know why +fn to_wide(text string) C.HGLOBAL { + len_required := C.MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, + text.len + 1, C.NULL, 0) + buf := C.GlobalAlloc(C.GMEM_MOVEABLE, i64(sizeof(u16)) * len_required) + if buf != C.HGLOBAL(C.NULL) { + mut locked := &u16(C.GlobalLock(buf)) + C.MultiByteToWideChar(C.CP_UTF8, C.MB_ERR_INVALID_CHARS, text.str, text.len + 1, + locked, len_required) + unsafe { + locked[len_required - 1] = u16(0) + } + C.GlobalUnlock(buf) + } + return buf +} + +pub fn (mut cb Clipboard) set_text(text string) bool { + cb.foo = 0 + buf := to_wide(text) + if !cb.get_clipboard_lock() { + C.GlobalFree(buf) + return false + } else { + // EmptyClipboard must be called to properly update clipboard ownership + C.EmptyClipboard() + if C.SetClipboardData(C.CF_UNICODETEXT, buf) == C.HANDLE(C.NULL) { + println('SetClipboardData: Failed.') + C.CloseClipboard() + C.GlobalFree(buf) + return false + } + } + // CloseClipboard appears to change the sequence number... + C.CloseClipboard() + return true +} + +pub fn (mut cb Clipboard) get_text() string { + cb.foo = 0 + if !cb.get_clipboard_lock() { + return '' + } + h_data := C.GetClipboardData(C.CF_UNICODETEXT) + if h_data == C.HANDLE(C.NULL) { + C.CloseClipboard() + return '' + } + str := unsafe { string_from_wide(&u16(C.GlobalLock(C.HGLOBAL(h_data)))) } + C.GlobalUnlock(C.HGLOBAL(h_data)) + return str +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + panic('Primary clipboard is not supported on non-Linux systems.') +} diff --git a/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v b/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v new file mode 100644 index 0000000..a3f4f35 --- /dev/null +++ b/v_windows/v/vlib/clipboard/dummy/dummy_clipboard.v @@ -0,0 +1,49 @@ +module dummy + +pub struct Clipboard { +mut: + text string // text data sent or received + got_text bool // used to confirm that we have got the text + is_owner bool // to save selection owner state +} + +// new_clipboard returns a new `Clipboard` instance allocated on the heap. +// The `Clipboard` resources can be released with `free()` +pub fn new_clipboard() &Clipboard { + return &Clipboard{} +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + return &Clipboard{} +} + +pub fn (mut cb Clipboard) set_text(text string) bool { + cb.text = text + cb.is_owner = true + cb.got_text = true + return true +} + +pub fn (mut cb Clipboard) get_text() string { + return cb.text +} + +pub fn (mut cb Clipboard) clear() { + cb.text = '' + cb.is_owner = false +} + +pub fn (mut cb Clipboard) free() { +} + +pub fn (cb &Clipboard) has_ownership() bool { + return cb.is_owner +} + +pub fn (cb &Clipboard) check_availability() bool { + // This is a dummy clipboard implementation, + // which can be always used, although it does not do much... + return true +} diff --git a/v_windows/v/vlib/clipboard/x11/clipboard.c.v b/v_windows/v/vlib/clipboard/x11/clipboard.c.v new file mode 100644 index 0000000..d5a4656 --- /dev/null +++ b/v_windows/v/vlib/clipboard/x11/clipboard.c.v @@ -0,0 +1,501 @@ +// Currently there is only X11 Selections support and no way to handle Wayland +// but since Wayland isn't extremely adopted, we are covering almost all Linux distros. +module x11 + +import time +import sync +import math + +$if freebsd { + #flag -I/usr/local/include + #flag -L/usr/local/lib +} $else $if openbsd { + #flag -I/usr/X11R6/include + #flag -L/usr/X11R6/lib +} +#flag -lX11 + +#include # Please install a package with the X11 development headers, for example: `apt-get install libx11-dev` +// X11 +[typedef] +struct C.Display { +} + +type Window = u64 +type Atom = u64 + +fn C.XInitThreads() int + +fn C.XCloseDisplay(d &C.Display) + +fn C.XFlush(d &C.Display) + +fn C.XDestroyWindow(d &C.Display, w Window) + +fn C.XNextEvent(d &C.Display, e &C.XEvent) + +fn C.XSetSelectionOwner(d &C.Display, a Atom, w Window, time int) + +fn C.XGetSelectionOwner(d &C.Display, a Atom) Window + +fn C.XChangeProperty(d &C.Display, requestor Window, property Atom, typ Atom, format int, mode int, data voidptr, nelements int) int + +fn C.XSendEvent(d &C.Display, requestor Window, propogate int, mask i64, event &C.XEvent) + +fn C.XInternAtom(d &C.Display, typ &byte, only_if_exists int) Atom + +fn C.XCreateSimpleWindow(d &C.Display, root Window, x int, y int, width u32, height u32, border_width u32, border u64, background u64) Window + +fn C.XOpenDisplay(name &byte) &C.Display + +fn C.XConvertSelection(d &C.Display, selection Atom, target Atom, property Atom, requestor Window, time int) int + +fn C.XSync(d &C.Display, discard int) int + +fn C.XGetWindowProperty(d &C.Display, w Window, property Atom, offset i64, length i64, delete int, req_type Atom, actual_type_return &Atom, actual_format_return &int, nitems &u64, bytes_after_return &u64, prop_return &&byte) int + +fn C.XDeleteProperty(d &C.Display, w Window, property Atom) int + +fn C.DefaultScreen(display &C.Display) int + +fn C.RootWindow(display &C.Display, screen_number int) Window + +fn C.BlackPixel(display &C.Display, screen_number int) u32 + +fn C.WhitePixel(display &C.Display, screen_number int) u32 + +fn C.XFree(data voidptr) + +fn todo_del() {} + +[typedef] +struct C.XSelectionRequestEvent { +mut: + display &C.Display // Display the event was read from + owner Window + requestor Window + selection Atom + target Atom + property Atom + time int +} + +[typedef] +struct C.XSelectionEvent { +mut: + @type int + display &C.Display // Display the event was read from + requestor Window + selection Atom + target Atom + property Atom + time int +} + +[typedef] +struct C.XSelectionClearEvent { +mut: + window Window + selection Atom +} + +[typedef] +struct C.XDestroyWindowEvent { +mut: + window Window +} + +[typedef] +union C.XEvent { +mut: + @type int + xdestroywindow C.XDestroyWindowEvent + xselectionclear C.XSelectionClearEvent + xselectionrequest C.XSelectionRequestEvent + xselection C.XSelectionEvent +} + +const ( + atom_names = ['TARGETS', 'CLIPBOARD', 'PRIMARY', 'SECONDARY', 'TEXT', 'UTF8_STRING', 'text/plain', + 'text/html', + ] +) + +// UNSUPPORTED TYPES: MULTIPLE, INCR, TIMESTAMP, image/bmp, image/jpeg, image/tiff, image/png +// all the atom types we need +// currently we only support text +// in the future, maybe we can extend this +// to support other mime types +enum AtomType { + xa_atom = 0 // value 4 + xa_string = 1 // value 31 + targets = 2 + clipboard = 3 + primary = 4 + secondary = 5 + text = 6 + utf8_string = 7 + text_plain = 8 + text_html = 9 +} + +pub struct Clipboard { + display &C.Display +mut: + selection Atom // the selection atom + window Window + atoms []Atom + mutex &sync.Mutex + text string // text data sent or received + got_text bool // used to confirm that we have got the text + is_owner bool // to save selection owner state +} + +struct Property { + actual_type Atom + actual_format int + nitems u64 + data &byte +} + +// new_clipboard returns a new `Clipboard` instance allocated on the heap. +// The `Clipboard` resources can be released with `free()` +pub fn new_clipboard() &Clipboard { + return new_x11_clipboard(.clipboard) +} + +// new_x11_clipboard initializes a new clipboard of the given selection type. +// Multiple clipboard instance types can be initialized and used separately. +fn new_x11_clipboard(selection AtomType) &Clipboard { + if selection !in [.clipboard, .primary, .secondary] { + panic('Wrong AtomType. Must be one of .primary, .secondary or .clipboard.') + } + // init x11 thread support + status := C.XInitThreads() + if status == 0 { + println('WARN: this system does not support threads; clipboard will cause the program to lock.') + } + + display := new_display() + + if display == C.NULL { + println('ERROR: No X Server running. Clipboard cannot be used.') + return &Clipboard{ + display: 0 + mutex: sync.new_mutex() + } + } + + mut cb := &Clipboard{ + display: display + window: create_xwindow(display) + mutex: sync.new_mutex() + } + cb.intern_atoms() + cb.selection = cb.get_atom(selection) + // start the listener on another thread or + // we will be locked and will have to hard exit + go cb.start_listener() + return cb +} + +pub fn (cb &Clipboard) check_availability() bool { + return cb.display != C.NULL +} + +pub fn (mut cb Clipboard) free() { + C.XDestroyWindow(cb.display, cb.window) + cb.window = Window(0) + // FIX ME: program hangs when closing display + // XCloseDisplay(cb.display) +} + +pub fn (mut cb Clipboard) clear() { + cb.mutex.@lock() + C.XSetSelectionOwner(cb.display, cb.selection, Window(0), C.CurrentTime) + C.XFlush(cb.display) + cb.is_owner = false + cb.text = '' + cb.mutex.unlock() +} + +pub fn (cb &Clipboard) has_ownership() bool { + return cb.is_owner +} + +fn (cb &Clipboard) take_ownership() { + C.XSetSelectionOwner(cb.display, cb.selection, cb.window, C.CurrentTime) + C.XFlush(cb.display) +} + +// set_text stores `text` in the system clipboard. +pub fn (mut cb Clipboard) set_text(text string) bool { + if cb.window == Window(0) { + return false + } + cb.mutex.@lock() + cb.text = text + cb.is_owner = true + cb.take_ownership() + C.XFlush(cb.display) + cb.mutex.unlock() + // sleep a little bit + time.sleep(1 * time.millisecond) + return cb.is_owner +} + +pub fn (mut cb Clipboard) get_text() string { + if cb.window == Window(0) { + return '' + } + if cb.is_owner { + return cb.text + } + cb.got_text = false + + // Request a list of possible conversions, if we're pasting. + C.XConvertSelection(cb.display, cb.selection, cb.get_atom(.targets), cb.selection, + cb.window, C.CurrentTime) + + // wait for the text to arrive + mut retries := 5 + for { + if cb.got_text || retries == 0 { + break + } + time.sleep(50 * time.millisecond) + retries-- + } + return cb.text +} + +// transmit_selection is crucial to handling all the different data types. +// If we ever support other mimetypes they should be handled here. +fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool { + if xse.target == cb.get_atom(.targets) { + targets := cb.get_supported_targets() + C.XChangeProperty(xse.display, xse.requestor, xse.property, cb.get_atom(.xa_atom), + 32, C.PropModeReplace, targets.data, targets.len) + } else if cb.is_supported_target(xse.target) && cb.is_owner && cb.text != '' { + cb.mutex.@lock() + C.XChangeProperty(xse.display, xse.requestor, xse.property, xse.target, 8, C.PropModeReplace, + cb.text.str, cb.text.len) + cb.mutex.unlock() + } else { + return false + } + return true +} + +fn (mut cb Clipboard) start_listener() { + event := C.XEvent{} + mut sent_request := false + mut to_be_requested := Atom(0) + for { + C.XNextEvent(cb.display, &event) + if unsafe { event.@type == 0 } { + println('error') + continue + } + match unsafe { event.@type } { + C.DestroyNotify { + if unsafe { event.xdestroywindow.window == cb.window } { + // we are done + return + } + } + C.SelectionClear { + if unsafe { event.xselectionclear.window == cb.window } && unsafe { + event.xselectionclear.selection == cb.selection + } { + cb.mutex.@lock() + cb.is_owner = false + cb.text = '' + cb.mutex.unlock() + } + } + C.SelectionRequest { + if unsafe { event.xselectionrequest.selection == cb.selection } { + mut xsre := &C.XSelectionRequestEvent{ + display: 0 + } + xsre = unsafe { &event.xselectionrequest } + + mut xse := C.XSelectionEvent{ + @type: C.SelectionNotify // 31 + display: xsre.display + requestor: xsre.requestor + selection: xsre.selection + time: xsre.time + target: xsre.target + property: xsre.property + } + if !cb.transmit_selection(&xse) { + xse.property = new_atom(0) + } + C.XSendEvent(cb.display, xse.requestor, 0, C.PropertyChangeMask, voidptr(&xse)) + C.XFlush(cb.display) + } + } + C.SelectionNotify { + if unsafe { + event.xselection.selection == cb.selection + && event.xselection.property != Atom(0) + } { + if unsafe { event.xselection.target == cb.get_atom(.targets) && !sent_request } { + sent_request = true + prop := read_property(cb.display, cb.window, cb.selection) + to_be_requested = cb.pick_target(prop) + if to_be_requested != Atom(0) { + C.XConvertSelection(cb.display, cb.selection, to_be_requested, + cb.selection, cb.window, C.CurrentTime) + } + } else if unsafe { event.xselection.target == to_be_requested } { + sent_request = false + to_be_requested = Atom(0) + cb.mutex.@lock() + prop := unsafe { + read_property(event.xselection.display, event.xselection.requestor, + event.xselection.property) + } + unsafe { + C.XDeleteProperty(event.xselection.display, event.xselection.requestor, + event.xselection.property) + } + if cb.is_supported_target(prop.actual_type) { + cb.got_text = true + unsafe { + cb.text = prop.data.vstring() // TODO: return byteptr to support other mimetypes + } + } + cb.mutex.unlock() + } + } + } + C.PropertyNotify {} + else {} + } + } +} + +/* +* Helpers +*/ +// intern_atoms initializes all the atoms we need. +fn (mut cb Clipboard) intern_atoms() { + cb.atoms << Atom(4) // XA_ATOM + cb.atoms << Atom(31) // XA_STRING + for i, name in x11.atom_names { + only_if_exists := if i == int(AtomType.utf8_string) { 1 } else { 0 } + cb.atoms << C.XInternAtom(cb.display, &char(name.str), only_if_exists) + if i == int(AtomType.utf8_string) && cb.atoms[i] == Atom(0) { + cb.atoms[i] = cb.get_atom(.xa_string) + } + } +} + +fn read_property(d &C.Display, w Window, p Atom) Property { + actual_type := Atom(0) + actual_format := 0 + nitems := u64(0) + bytes_after := u64(0) + ret := &byte(0) + mut read_bytes := 1024 + for { + if ret != 0 { + C.XFree(ret) + } + C.XGetWindowProperty(d, w, p, 0, read_bytes, 0, 0, &actual_type, &actual_format, + &nitems, &bytes_after, &ret) + read_bytes *= 2 + if bytes_after == 0 { + break + } + } + return Property{actual_type, actual_format, nitems, ret} +} + +// pick_target finds the best target given a local copy of a property. +fn (cb &Clipboard) pick_target(prop Property) Atom { + // The list of targets is a list of atoms, so it should have type XA_ATOM + // but it may have the type TARGETS instead. + if (prop.actual_type != cb.get_atom(.xa_atom) && prop.actual_type != cb.get_atom(.targets)) + || prop.actual_format != 32 { + // This would be really broken. Targets have to be an atom list + // and applications should support this. Nevertheless, some + // seem broken (MATLAB 7, for instance), so ask for STRING + // next instead as the lowest common denominator + return cb.get_atom(.xa_string) + } else { + atom_list := &Atom(voidptr(prop.data)) + + mut to_be_requested := Atom(0) + + // This is higher than the maximum priority. + mut priority := math.max_i32 + + for i in 0 .. prop.nitems { + // See if this data type is allowed and of higher priority (closer to zero) + // than the present one. + + target := unsafe { atom_list[i] } + if cb.is_supported_target(target) { + index := cb.get_target_index(target) + if priority > index && index >= 0 { + priority = index + to_be_requested = target + } + } + } + return to_be_requested + } +} + +fn (cb &Clipboard) get_atoms(types ...AtomType) []Atom { + mut atoms := []Atom{} + for typ in types { + atoms << cb.atoms[typ] + } + return atoms +} + +fn (cb &Clipboard) get_atom(typ AtomType) Atom { + return cb.atoms[typ] +} + +fn (cb &Clipboard) is_supported_target(target Atom) bool { + return cb.get_target_index(target) >= 0 +} + +fn (cb &Clipboard) get_target_index(target Atom) int { + for i, atom in cb.get_supported_targets() { + if atom == target { + return i + } + } + return -1 +} + +fn (cb &Clipboard) get_supported_targets() []Atom { + return cb.get_atoms(AtomType.utf8_string, .xa_string, .text, .text_plain, .text_html) +} + +fn new_atom(value int) &Atom { + return unsafe { &Atom(&u64(u64(value))) } +} + +fn create_xwindow(display &C.Display) Window { + n := C.DefaultScreen(display) + return C.XCreateSimpleWindow(display, C.RootWindow(display, n), 0, 0, 1, 1, 0, C.BlackPixel(display, + n), C.WhitePixel(display, n)) +} + +fn new_display() &C.Display { + return C.XOpenDisplay(C.NULL) +} + +// new_primary returns a new X11 `PRIMARY` type `Clipboard` instance allocated on the heap. +// Please note: new_primary only works on X11 based systems. +pub fn new_primary() &Clipboard { + return new_x11_clipboard(.primary) +} diff --git a/v_windows/v/vlib/context/README.md b/v_windows/v/vlib/context/README.md new file mode 100644 index 0000000..7fbf18b --- /dev/null +++ b/v_windows/v/vlib/context/README.md @@ -0,0 +1,166 @@ +# Context + +This module defines the Context type, which carries deadlines, cancellation signals, +and other request-scoped values across API boundaries and between processes. + +Incoming requests to a server should create a Context, and outgoing calls to servers +should accept a Context. The chain of function calls between them must propagate the +Context, optionally replacing it with a derived Context created using with_cancel, +with_deadline, with_timeout, or with_value. When a Context is canceled, all Contexts +derived from it are also canceled. + +The with_cancel, with_deadline, and with_timeout functions take a Context (the parent) +and return a derived Context (the child). Calling the cancel function +cancels the child and its children, removes the parent's reference to the child, +and stops any associated timers. + +Programs that use Contexts should follow these rules to keep interfaces consistent +across different modules. + +Do not store Contexts inside a struct type; instead, pass a Context explicitly +to each function that needs it. The Context should be the first parameter, +typically named ctx, just to make it more consistent. + +## Examples + +In this section you can see some usage examples for this module + +### Context With Cancellation + +```v +import context + +// This example demonstrates the use of a cancelable context to prevent a +// routine leak. By the end of the example function, the routine started +// by gen will return without leaking. +fn example_with_cancel() { + // gen generates integers in a separate routine and + // sends them to the returned channel. + // The callers of gen need to cancel the context once + // they are done consuming generated integers not to leak + // the internal routine started by gen. + gen := fn (ctx context.Context) chan int { + dst := chan int{} + go fn (ctx context.Context, dst chan int) { + mut v := 0 + ch := ctx.done() + for { + select { + _ := <-ch { + // returning not to leak the routine + return + } + dst <- v { + v++ + } + } + } + }(ctx, dst) + return dst + } + + ctx := context.with_cancel(context.background()) + defer { + context.cancel(ctx) + } + + ch := gen(ctx) + for i in 0 .. 5 { + v := <-ch + assert i == v + } +} +``` + +### Context With Deadline + +```v +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// This example passes a context with an arbitrary deadline to tell a blocking +// function that it should abandon its work as soon as it gets to it. +fn example_with_deadline() { + dur := time.now().add(short_duration) + ctx := context.with_deadline(context.background(), dur) + + defer { + // Even though ctx will be expired, it is good practice to call its + // cancellation function in any case. Failure to do so may keep the + // context and its parent alive longer than necessary. + context.cancel(ctx) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} +``` + +### Context With Timeout + +```v +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// This example passes a context with a timeout to tell a blocking function that +// it should abandon its work after the timeout elapses. +fn example_with_timeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx := context.with_timeout(context.background(), short_duration) + defer { + context.cancel(ctx) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} +``` + +### Context With Value + +```v +import context + +type ValueContextKey = string + +// This example demonstrates how a value can be passed to the context +// and also how to retrieve it if it exists. +fn example_with_value() { + f := fn (ctx context.Context, key ValueContextKey) string { + if value := ctx.value(key) { + if !isnil(value) { + return *(&string(value)) + } + } + return 'key not found' + } + + key := ValueContextKey('language') + value := 'VAL' + ctx := context.with_value(context.background(), key, &value) + + assert value == f(ctx, key) + assert 'key not found' == f(ctx, ValueContextKey('color')) +} +``` diff --git a/v_windows/v/vlib/context/_context.v b/v_windows/v/vlib/context/_context.v new file mode 100644 index 0000000..5d4e2d1 --- /dev/null +++ b/v_windows/v/vlib/context/_context.v @@ -0,0 +1,81 @@ +// This module defines the Context type, which carries deadlines, cancellation signals, +// and other request-scoped values across API boundaries and between processes. +// Based off: https://github.com/golang/go/tree/master/src/context +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import time + +const ( + background = EmptyContext(0) + todo = EmptyContext(1) + + cancel_context_key = 'context.CancelContext' + + // canceled is the error returned by Context.err when the context is canceled. + canceled = error('context canceled') + + // deadline_exceeded is the error returned by Context.err when the context's + // deadline passes. + deadline_exceeded = error('context deadline exceeded') +) + +pub interface Context { + // deadline returns the time when work done on behalf of this context + // should be canceled. deadline returns none when no deadline is + // set. Successive calls to deadline return the same results. + deadline() ?time.Time + // done returns a channel that's closed when work done on behalf of this + // context should be canceled. done may return nil if this context can + // never be canceled. Successive calls to done return the same value. + // The close of the done channel may happen asynchronously, + // after the cancel function returns. + // + // with_cancel arranges for done to be closed when cancel is called; + // with_deadline arranges for done to be closed when the deadline + // expires; with_timeout arranges for done to be closed when the timeout + // elapses. + done() chan int + // If done is not yet closed, err returns nil. + // If done is closed, err returns a non-nil error explaining why: + // canceled if the context was canceled + // or deadline_exceeded if the context's deadline passed. + // After err returns a non-nil error, successive calls to err return the same error. + err() IError + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.with_value and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + value(key string) ?voidptr + str() string +} + +// background returns an empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +pub fn background() Context { + return context.background +} + +// todo returns an empty Context. Code should use todo when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). +pub fn todo() Context { + return context.todo +} + +fn context_name(ctx Context) string { + return typeof(ctx) +} diff --git a/v_windows/v/vlib/context/cancel.v b/v_windows/v/vlib/context/cancel.v new file mode 100644 index 0000000..1a0ae57 --- /dev/null +++ b/v_windows/v/vlib/context/cancel.v @@ -0,0 +1,181 @@ +// This module defines the Context type, which carries deadlines, cancellation signals, +// and other request-scoped values across API boundaries and between processes. +// Based off: https://github.com/golang/go/tree/master/src/context +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import rand +import sync +import time + +pub interface Canceler { + id string + cancel(remove_from_parent bool, err IError) + done() chan int +} + +pub fn cancel(ctx Context) { + match mut ctx { + CancelContext { + ctx.cancel(true, canceled) + } + TimerContext { + ctx.cancel(true, canceled) + } + else {} + } +} + +// A CancelContext can be canceled. When canceled, it also cancels any children +// that implement Canceler. +pub struct CancelContext { + id string +mut: + context Context + mutex &sync.Mutex + done chan int + children map[string]Canceler + err IError +} + +// with_cancel returns a copy of parent with a new done channel. The returned +// context's done channel is closed when the returned cancel function is called +// or when the parent context's done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +pub fn with_cancel(parent Context) Context { + mut c := new_cancel_context(parent) + propagate_cancel(parent, mut c) + return Context(c) +} + +// new_cancel_context returns an initialized CancelContext. +fn new_cancel_context(parent Context) &CancelContext { + return &CancelContext{ + id: rand.uuid_v4() + context: parent + mutex: sync.new_mutex() + done: chan int{cap: 2} + err: none + } +} + +pub fn (ctx CancelContext) deadline() ?time.Time { + return none +} + +pub fn (mut ctx CancelContext) done() chan int { + ctx.mutex.@lock() + done := ctx.done + ctx.mutex.unlock() + return done +} + +pub fn (mut ctx CancelContext) err() IError { + ctx.mutex.@lock() + err := ctx.err + ctx.mutex.unlock() + return err +} + +pub fn (ctx CancelContext) value(key string) ?voidptr { + if key == cancel_context_key { + return voidptr(unsafe { &ctx }) + } + return ctx.context.value(key) +} + +pub fn (ctx CancelContext) str() string { + return context_name(ctx.context) + '.with_cancel' +} + +fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) { + if err is none { + panic('context: internal error: missing cancel error') + } + + ctx.mutex.@lock() + if ctx.err !is none { + ctx.mutex.unlock() + // already canceled + return + } + + ctx.err = err + + if !ctx.done.closed { + ctx.done <- 0 + ctx.done.close() + } + + for _, child in ctx.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + + ctx.children = map[string]Canceler{} + ctx.mutex.unlock() + + if remove_from_parent { + remove_child(ctx.context, ctx) + } +} + +fn propagate_cancel(parent Context, mut child Canceler) { + done := parent.done() + select { + _ := <-done { + // parent is already canceled + child.cancel(false, parent.err()) + return + } + } + mut p := parent_cancel_context(parent) or { + go fn (parent Context, mut child Canceler) { + pdone := parent.done() + select { + _ := <-pdone { + child.cancel(false, parent.err()) + } + } + }(parent, mut child) + return + } + + if p.err is none { + p.children[child.id] = *child + } else { + // parent has already been canceled + child.cancel(false, p.err) + } +} + +// parent_cancel_context returns the underlying CancelContext for parent. +// It does this by looking up parent.value(&cancel_context_key) to find +// the innermost enclosing CancelContext and then checking whether +// parent.done() matches that CancelContext. (If not, the CancelContext +// has been wrapped in a custom implementation providing a +// different done channel, in which case we should not bypass it.) +fn parent_cancel_context(parent Context) ?CancelContext { + done := parent.done() + if done.closed { + return none + } + if p_ptr := parent.value(cancel_context_key) { + if !isnil(p_ptr) { + mut p := &CancelContext(p_ptr) + pdone := p.done() + if done == pdone { + return *p + } + } + } + return none +} + +// remove_child removes a context from its parent. +fn remove_child(parent Context, child Canceler) { + mut p := parent_cancel_context(parent) or { return } + p.children.delete(child.id) +} diff --git a/v_windows/v/vlib/context/cancel_test.v b/v_windows/v/vlib/context/cancel_test.v new file mode 100644 index 0000000..6b9fdaa --- /dev/null +++ b/v_windows/v/vlib/context/cancel_test.v @@ -0,0 +1,42 @@ +import context + +// This example demonstrates the use of a cancelable context to prevent a +// routine leak. By the end of the example function, the routine started +// by gen will return without leaking. +fn test_with_cancel() { + // gen generates integers in a separate routine and + // sends them to the returned channel. + // The callers of gen need to cancel the context once + // they are done consuming generated integers not to leak + // the internal routine started by gen. + gen := fn (ctx context.Context) chan int { + dst := chan int{} + go fn (ctx context.Context, dst chan int) { + mut v := 0 + ch := ctx.done() + for { + select { + _ := <-ch { + // returning not to leak the routine + return + } + dst <- v { + v++ + } + } + } + }(ctx, dst) + return dst + } + + ctx := context.with_cancel(context.background()) + defer { + context.cancel(ctx) + } + + ch := gen(ctx) + for i in 0 .. 5 { + v := <-ch + assert i == v + } +} diff --git a/v_windows/v/vlib/context/deadline.v b/v_windows/v/vlib/context/deadline.v new file mode 100644 index 0000000..43fd056 --- /dev/null +++ b/v_windows/v/vlib/context/deadline.v @@ -0,0 +1,94 @@ +// This module defines the Context type, which carries deadlines, cancellation signals, +// and other request-scoped values across API boundaries and between processes. +// Based off: https://github.com/golang/go/tree/master/src/context +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import rand +import time + +// A TimerContext carries a timer and a deadline. It embeds a CancelContext to +// implement done and err. It implements cancel by stopping its timer then +// delegating to CancelContext.cancel +pub struct TimerContext { + id string +mut: + cancel_ctx CancelContext + deadline time.Time +} + +// with_deadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// with_deadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +pub fn with_deadline(parent Context, d time.Time) Context { + id := rand.uuid_v4() + if cur := parent.deadline() { + if cur < d { + // The current deadline is already sooner than the new one. + return with_cancel(parent) + } + } + cancel_ctx := new_cancel_context(parent) + mut ctx := &TimerContext{ + cancel_ctx: cancel_ctx + deadline: d + id: id + } + propagate_cancel(parent, mut ctx) + dur := d - time.now() + if dur.nanoseconds() <= 0 { + ctx.cancel(true, deadline_exceeded) // deadline has already passed + return Context(ctx) + } + + if ctx.err() is none { + go fn (mut ctx TimerContext, dur time.Duration) { + time.sleep(dur) + ctx.cancel(true, deadline_exceeded) + }(mut ctx, dur) + } + return Context(ctx) +} + +// with_timeout returns with_deadline(parent, time.now().add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete +pub fn with_timeout(parent Context, timeout time.Duration) Context { + return with_deadline(parent, time.now().add(timeout)) +} + +pub fn (ctx TimerContext) deadline() ?time.Time { + return ctx.deadline +} + +pub fn (mut ctx TimerContext) done() chan int { + return ctx.cancel_ctx.done() +} + +pub fn (mut ctx TimerContext) err() IError { + return ctx.cancel_ctx.err() +} + +pub fn (ctx TimerContext) value(key string) ?voidptr { + return ctx.cancel_ctx.value(key) +} + +pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) { + ctx.cancel_ctx.cancel(false, err) + if remove_from_parent { + // Remove this TimerContext from its parent CancelContext's children. + remove_child(ctx.cancel_ctx.context, ctx) + } +} + +pub fn (ctx TimerContext) str() string { + return context_name(ctx.cancel_ctx.context) + '.with_deadline(' + ctx.deadline.str() + ' [' + + (time.now() - ctx.deadline).str() + '])' +} diff --git a/v_windows/v/vlib/context/deadline_test.v b/v_windows/v/vlib/context/deadline_test.v new file mode 100644 index 0000000..e4d7280 --- /dev/null +++ b/v_windows/v/vlib/context/deadline_test.v @@ -0,0 +1,48 @@ +import context +import time + +const ( + // a reasonable duration to block in an example + short_duration = 1 * time.millisecond +) + +// This example passes a context with an arbitrary deadline to tell a blocking +// function that it should abandon its work as soon as it gets to it. +fn test_with_deadline() { + dur := time.now().add(short_duration) + ctx := context.with_deadline(context.background(), dur) + + defer { + // Even though ctx will be expired, it is good practice to call its + // cancellation function in any case. Failure to do so may keep the + // context and its parent alive longer than necessary. + context.cancel(ctx) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} + +// This example passes a context with a timeout to tell a blocking function that +// it should abandon its work after the timeout elapses. +fn test_with_timeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx := context.with_timeout(context.background(), short_duration) + defer { + context.cancel(ctx) + } + + ctx_ch := ctx.done() + select { + _ := <-ctx_ch {} + 1 * time.second { + panic('This should not happen') + } + } +} diff --git a/v_windows/v/vlib/context/empty.v b/v_windows/v/vlib/context/empty.v new file mode 100644 index 0000000..335369a --- /dev/null +++ b/v_windows/v/vlib/context/empty.v @@ -0,0 +1,42 @@ +// This module defines the Context type, which carries deadlines, cancellation signals, +// and other request-scoped values across API boundaries and between processes. +// Based off: https://github.com/golang/go/tree/master/src/context +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import time + +// An EmptyContext is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +pub type EmptyContext = int + +pub fn (ctx EmptyContext) deadline() ?time.Time { + return none +} + +pub fn (ctx EmptyContext) done() chan int { + ch := chan int{} + defer { + ch.close() + } + return ch +} + +pub fn (ctx EmptyContext) err() IError { + // TODO: Change this to `none` + return none_ +} + +pub fn (ctx EmptyContext) value(key string) ?voidptr { + return none +} + +pub fn (ctx EmptyContext) str() string { + if ctx == background { + return 'context.Background' + } + if ctx == todo { + return 'context.TODO' + } + return 'unknown empty Context' +} diff --git a/v_windows/v/vlib/context/empty_test.v b/v_windows/v/vlib/context/empty_test.v new file mode 100644 index 0000000..433d8c8 --- /dev/null +++ b/v_windows/v/vlib/context/empty_test.v @@ -0,0 +1,17 @@ +module context + +fn test_background() { + ctx := background() + assert 'context.Background' == ctx.str() + if _ := ctx.value('') { + panic('This should never happen') + } +} + +fn test_todo() { + ctx := todo() + assert 'context.TODO' == ctx.str() + if _ := ctx.value('') { + panic('This should never happen') + } +} diff --git a/v_windows/v/vlib/context/err.v b/v_windows/v/vlib/context/err.v new file mode 100644 index 0000000..23cfe56 --- /dev/null +++ b/v_windows/v/vlib/context/err.v @@ -0,0 +1,12 @@ +module context + +const none_ = IError(&None{}) + +struct None { + msg string + code int +} + +fn (_ None) str() string { + return 'none' +} diff --git a/v_windows/v/vlib/context/value.v b/v_windows/v/vlib/context/value.v new file mode 100644 index 0000000..19a2289 --- /dev/null +++ b/v_windows/v/vlib/context/value.v @@ -0,0 +1,57 @@ +// This module defines the Context type, which carries deadlines, cancellation signals, +// and other request-scoped values across API boundaries and between processes. +// Based off: https://github.com/golang/go/tree/master/src/context +// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2 +module context + +import time + +// A ValueContext carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +pub struct ValueContext { + key string + value voidptr +mut: + context Context +} + +// with_value returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The provided key must be comparable and should not be of type +// string or any other built-in type to avoid collisions between +// packages using context. Users of with_value should define their own +// types for keys +pub fn with_value(parent Context, key string, value voidptr) Context { + return &ValueContext{ + context: parent + key: key + value: value + } +} + +pub fn (ctx ValueContext) deadline() ?time.Time { + return ctx.context.deadline() +} + +pub fn (ctx ValueContext) done() chan int { + return ctx.context.done() +} + +pub fn (ctx ValueContext) err() IError { + return ctx.context.err() +} + +pub fn (ctx ValueContext) value(key string) ?voidptr { + if ctx.key == key { + return ctx.value + } + return ctx.context.value(key) +} + +pub fn (ctx ValueContext) str() string { + return context_name(ctx.context) + '.with_value' +} diff --git a/v_windows/v/vlib/context/value_test.v b/v_windows/v/vlib/context/value_test.v new file mode 100644 index 0000000..a8ed5b5 --- /dev/null +++ b/v_windows/v/vlib/context/value_test.v @@ -0,0 +1,23 @@ +import context + +type ValueContextKey = string + +// This example demonstrates how a value can be passed to the context +// and also how to retrieve it if it exists. +fn test_with_value() { + f := fn (ctx context.Context, key ValueContextKey) string { + if value := ctx.value(key) { + if !isnil(value) { + return *(&string(value)) + } + } + return 'key not found' + } + + key := ValueContextKey('language') + value := 'VAL' + ctx := context.with_value(context.background(), key, &value) + + assert value == f(ctx, key) + assert 'key not found' == f(ctx, ValueContextKey('color')) +} diff --git a/v_windows/v/vlib/crypto/aes/aes.v b/v_windows/v/vlib/crypto/aes/aes.v new file mode 100644 index 0000000..f7e2cec --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/aes.v @@ -0,0 +1,77 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Based off: https://github.com/golang/go/blob/master/src/crypto/aes +// Last commit: https://github.com/golang/go/commit/691a2d457ab1bf03bd46d4b69e0f93b8993c0055 +module aes + +import crypto.internal.subtle + +pub const ( + // The AES block size in bytes. + block_size = 16 +) + +// AesCipher represents an AES encryption using a particular key. +struct AesCipher { +mut: + enc []u32 + dec []u32 +} + +// new_cipher creates and returns a new `AesCipher`. +// The key argument should be the AES key, +// either 16, 24, or 32 bytes to select +// AES-128, AES-192, or AES-256. +pub fn new_cipher(key []byte) AesCipher { + k := key.len + match k { + 16, 24, 32 { + // break + } + else { + panic('crypto.aes: invalid key size ' + k.str()) + // return error('crypto.aes: invalid key size ' + k.str()) + } + } + // for now use generic version + return new_cipher_generic(key) +} + +// block_size returns the block size of the checksum in bytes. +pub fn (c &AesCipher) block_size() int { + return aes.block_size +} + +// encrypt encrypts the blocks in `src` to `dst`. +// Please note: `dst` and `src` are both mutable for performance reasons. +pub fn (c &AesCipher) encrypt(mut dst []byte, mut src []byte) { + if src.len < aes.block_size { + panic('crypto.aes: input not full block') + } + if dst.len < aes.block_size { + panic('crypto.aes: output not full block') + } + // if subtle.inexact_overlap(dst[:block_size], src[:block_size]) { + if subtle.inexact_overlap((*dst)[..aes.block_size], (*src)[..aes.block_size]) { + panic('crypto.aes: invalid buffer overlap') + } + // for now use generic version + encrypt_block_generic(c.enc, mut dst, src) +} + +// decrypt decrypts the blocks in `src` to `dst`. +// Please note: `dst` and `src` are both mutable for performance reasons. +pub fn (c &AesCipher) decrypt(mut dst []byte, mut src []byte) { + if src.len < aes.block_size { + panic('crypto.aes: input not full block') + } + if dst.len < aes.block_size { + panic('crypto.aes: output not full block') + } + if subtle.inexact_overlap((*dst)[..aes.block_size], (*src)[..aes.block_size]) { + panic('crypto.aes: invalid buffer overlap') + } + // for now use generic version + decrypt_block_generic(c.dec, mut dst, src) +} diff --git a/v_windows/v/vlib/crypto/aes/aes_cbc.v b/v_windows/v/vlib/crypto/aes/aes_cbc.v new file mode 100644 index 0000000..253f79c --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/aes_cbc.v @@ -0,0 +1,126 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Cipher block chaining (CBC) mode. +// CBC provides confidentiality by xoring (chaining) each plaintext block +// with the previous ciphertext block before applying the block cipher. +// See NIST SP 800-38A, pp 10-11 +// NOTE this will be moved to crypto.cipher interface (joe-c) +module aes + +import crypto.cipher +import crypto.internal.subtle + +struct AesCbc { +mut: + b AesCipher + block_size int + iv []byte + tmp []byte +} + +// internal +fn new_aes_cbc(b AesCipher, iv []byte) AesCbc { + return AesCbc{ + b: b + block_size: b.block_size() + iv: iv.clone() + tmp: []byte{len: (b.block_size())} + } +} + +// new_cbc returns a `AesCbc` which encrypts in cipher block chaining +// mode, using the given Block. The length of iv must be the same as the +// Block's block size. +pub fn new_cbc(b AesCipher, iv []byte) AesCbc { + if iv.len != b.block_size() { + panic('crypto.cipher.new_cbc_encrypter: IV length must equal block size') + } + return new_aes_cbc(b, iv) +} + +// block_size returns the block size of the checksum in bytes. +pub fn (x &AesCbc) block_size() int { + return x.block_size +} + +// encrypt_blocks encrypts the blocks in `src_` to `dst_`. +// Please note: `dst_` is mutable for performance reasons. +pub fn (x &AesCbc) encrypt_blocks(mut dst_ []byte, src_ []byte) { + unsafe { + mut dst := *dst_ + mut src := src_ + if src.len % x.block_size != 0 { + panic('crypto.cipher: input not full blocks') + } + if dst.len < src.len { + panic('crypto.cipher: output smaller than input') + } + if subtle.inexact_overlap(dst[..src.len], src_) { + panic('crypto.cipher: invalid buffer overlap') + } + mut iv := x.iv + for src.len > 0 { + // Write the xor to dst, then encrypt in place. + cipher.xor_bytes(mut dst[..x.block_size], src[..x.block_size], iv) + x.b.encrypt(mut dst[..x.block_size], mut dst[..x.block_size]) + // Move to the next block with this block as the next iv. + iv = dst[..x.block_size] + if x.block_size >= src.len { + src = [] + } else { + src = src[x.block_size..] + } + dst = dst[x.block_size..] + } + // Save the iv for the next crypt_blocks call. + copy(x.iv, iv) + } +} + +// decrypt_blocks decrypts the blocks in `src` to `dst`. +// Please note: `dst` is mutable for performance reasons. +pub fn (mut x AesCbc) decrypt_blocks(mut dst []byte, src []byte) { + if src.len % x.block_size != 0 { + panic('crypto.cipher: input not full blocks') + } + if dst.len < src.len { + panic('crypto.cipher: output smaller than input') + } + if subtle.inexact_overlap((*dst)[..src.len], src) { + panic('crypto.cipher: invalid buffer overlap') + } + if src.len == 0 { + return + } + // For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv). + // To avoid making a copy each time, we loop over the blocks BACKWARDS. + mut end := src.len + mut start := end - x.block_size + mut prev := start - x.block_size + // Copy the last block of ciphertext in preparation as the new iv. + copy(x.tmp, src[start..end]) + // Loop over all but the first block. + for start > 0 { + mut src_chunk := src[start..end] + x.b.decrypt(mut (*dst)[start..end], mut src_chunk) + cipher.xor_bytes(mut (*dst)[start..end], (*dst)[start..end], src[prev..start]) + end = start + start = prev + prev -= x.block_size + } + // The first block is special because it uses the saved iv. + mut src_chunk := src[start..end] + x.b.decrypt(mut (*dst)[start..end], mut src_chunk) + cipher.xor_bytes(mut (*dst)[start..end], (*dst)[start..end], x.iv) + // Set the new iv to the first block we copied earlier. + x.iv = x.tmp + x.tmp = x.iv +} + +fn (x &AesCbc) set_iv(iv []byte) { + if iv.len != x.iv.len { + panic('cipher: incorrect length IV') + } + copy(x.iv, iv) +} diff --git a/v_windows/v/vlib/crypto/aes/aes_test.v b/v_windows/v/vlib/crypto/aes/aes_test.v new file mode 100644 index 0000000..ca85d06 --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/aes_test.v @@ -0,0 +1,27 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.aes + +fn test_crypto_aes() { + // TEST CBC + key := '6368616e676520746869732070617373'.bytes() + mut ciphertext := '73c86d43a9d700a253a96c85b0f6b03ac9792e0e757f869cca306bd3cba1c62b'.bytes() + block := aes.new_cipher(key) + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if ciphertext.len < aes.block_size { + panic('ciphertext too short') + } + iv := ciphertext[..aes.block_size] + ciphertext = ciphertext[aes.block_size..] + // CBC mode always works in whole blocks. + if ciphertext.len % aes.block_size != 0 { + panic('ciphertext is not a multiple of the block size') + } + mode := aes.new_cbc(block, iv) + cipher_clone := ciphertext.clone() + mode.encrypt_blocks(mut ciphertext, cipher_clone) + assert ciphertext.hex() == 'c210459b514668ddc44674885e4979215265a6c44431a248421254ef357a8c2a308a8bddf5623af9df91737562041cf1' + println('ok') +} diff --git a/v_windows/v/vlib/crypto/aes/block_generic.v b/v_windows/v/vlib/crypto/aes/block_generic.v new file mode 100644 index 0000000..5da938e --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/block_generic.v @@ -0,0 +1,183 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// This implementation is derived from the golang implementation +// which itself is derived in part from the reference +// ANSI C implementation, which carries the following notice: +// +// rijndael-alg-fst.c +// +// @version 3.0 (December 2000) +// +// Optimised ANSI C code for the Rijndael cipher (now AES) +// +// @author Vincent Rijmen +// @author Antoon Bosselaers +// @author Paulo Barreto +// +// This code is hereby placed in the public domain. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS +// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// See FIPS 197 for specification, and see Daemen and Rijmen's Rijndael submission +// for implementation details. +// https://csrc.nist.gov/csrc/media/publications/fips/197/final/documents/fips-197.pdf +// https://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf +module aes + +import encoding.binary + +// Encrypt one block from src into dst, using the expanded key xk. +fn encrypt_block_generic(xk []u32, mut dst []byte, src []byte) { + _ = src[15] // early bounds check + mut s0 := binary.big_endian_u32(src[..4]) + mut s1 := binary.big_endian_u32(src[4..8]) + mut s2 := binary.big_endian_u32(src[8..12]) + mut s3 := binary.big_endian_u32(src[12..16]) + // First round just XORs input with key. + s0 ^= xk[0] + s1 ^= xk[1] + s2 ^= xk[2] + s3 ^= xk[3] + // Middle rounds shuffle using tables. + // Number of rounds is set by length of expanded key. + nr := xk.len / 4 - 2 // - 2: one above, one more below + mut k := 4 + mut t0 := u32(0) + mut t1 := u32(0) + mut t2 := u32(0) + mut t3 := u32(0) + for _ in 0 .. nr { + t0 = xk[k + 0] ^ te0[byte(s0 >> 24)] ^ te1[byte(s1 >> 16)] ^ te2[byte(s2 >> 8)] ^ u32(te3[byte(s3)]) + t1 = xk[k + 1] ^ te0[byte(s1 >> 24)] ^ te1[byte(s2 >> 16)] ^ te2[byte(s3 >> 8)] ^ u32(te3[byte(s0)]) + t2 = xk[k + 2] ^ te0[byte(s2 >> 24)] ^ te1[byte(s3 >> 16)] ^ te2[byte(s0 >> 8)] ^ u32(te3[byte(s1)]) + t3 = xk[k + 3] ^ te0[byte(s3 >> 24)] ^ te1[byte(s0 >> 16)] ^ te2[byte(s1 >> 8)] ^ u32(te3[byte(s2)]) + k += 4 + s0 = t0 + s1 = t1 + s2 = t2 + s3 = t3 + } + // Last round uses s-box directly and XORs to produce output. + s0 = s_box0[t0 >> 24] << 24 | s_box0[t1 >> 16 & 0xff] << 16 | u32(s_box0[t2 >> 8 & 0xff] << 8) | s_box0[t3 & u32(0xff)] + s1 = s_box0[t1 >> 24] << 24 | s_box0[t2 >> 16 & 0xff] << 16 | u32(s_box0[t3 >> 8 & 0xff] << 8) | s_box0[t0 & u32(0xff)] + s2 = s_box0[t2 >> 24] << 24 | s_box0[t3 >> 16 & 0xff] << 16 | u32(s_box0[t0 >> 8 & 0xff] << 8) | s_box0[t1 & u32(0xff)] + s3 = s_box0[t3 >> 24] << 24 | s_box0[t0 >> 16 & 0xff] << 16 | u32(s_box0[t1 >> 8 & 0xff] << 8) | s_box0[t2 & u32(0xff)] + s0 ^= xk[k + 0] + s1 ^= xk[k + 1] + s2 ^= xk[k + 2] + s3 ^= xk[k + 3] + _ := dst[15] // early bounds check + binary.big_endian_put_u32(mut (*dst)[0..4], s0) + binary.big_endian_put_u32(mut (*dst)[4..8], s1) + binary.big_endian_put_u32(mut (*dst)[8..12], s2) + binary.big_endian_put_u32(mut (*dst)[12..16], s3) +} + +// Decrypt one block from src into dst, using the expanded key xk. +fn decrypt_block_generic(xk []u32, mut dst []byte, src []byte) { + _ = src[15] // early bounds check + mut s0 := binary.big_endian_u32(src[0..4]) + mut s1 := binary.big_endian_u32(src[4..8]) + mut s2 := binary.big_endian_u32(src[8..12]) + mut s3 := binary.big_endian_u32(src[12..16]) + // First round just XORs input with key. + s0 ^= xk[0] + s1 ^= xk[1] + s2 ^= xk[2] + s3 ^= xk[3] + // Middle rounds shuffle using tables. + // Number of rounds is set by length of expanded key. + nr := xk.len / 4 - 2 // - 2: one above, one more below + mut k := 4 + mut t0 := u32(0) + mut t1 := u32(0) + mut t2 := u32(0) + mut t3 := u32(0) + for _ in 0 .. nr { + t0 = xk[k + 0] ^ td0[byte(s0 >> 24)] ^ td1[byte(s3 >> 16)] ^ td2[byte(s2 >> 8)] ^ u32(td3[byte(s1)]) + t1 = xk[k + 1] ^ td0[byte(s1 >> 24)] ^ td1[byte(s0 >> 16)] ^ td2[byte(s3 >> 8)] ^ u32(td3[byte(s2)]) + t2 = xk[k + 2] ^ td0[byte(s2 >> 24)] ^ td1[byte(s1 >> 16)] ^ td2[byte(s0 >> 8)] ^ u32(td3[byte(s3)]) + t3 = xk[k + 3] ^ td0[byte(s3 >> 24)] ^ td1[byte(s2 >> 16)] ^ td2[byte(s1 >> 8)] ^ u32(td3[byte(s0)]) + k += 4 + s0 = t0 + s1 = t1 + s2 = t2 + s3 = t3 + } + // Last round uses s-box directly and XORs to produce output. + s0 = u32(s_box1[t0 >> 24]) << 24 | u32(s_box1[t3 >> 16 & 0xff]) << 16 | u32(s_box1[t2 >> 8 & 0xff] << 8) | u32(s_box1[t1 & u32(0xff)]) + s1 = u32(s_box1[t1 >> 24]) << 24 | u32(s_box1[t0 >> 16 & 0xff]) << 16 | u32(s_box1[t3 >> 8 & 0xff] << 8) | u32(s_box1[t2 & u32(0xff)]) + s2 = u32(s_box1[t2 >> 24]) << 24 | u32(s_box1[t1 >> 16 & 0xff]) << 16 | u32(s_box1[t0 >> 8 & 0xff] << 8) | u32(s_box1[t3 & u32(0xff)]) + s3 = u32(s_box1[t3 >> 24]) << 24 | u32(s_box1[t2 >> 16 & 0xff]) << 16 | u32(s_box1[t1 >> 8 & 0xff] << 8) | u32(s_box1[t0 & u32(0xff)]) + s0 ^= xk[k + 0] + s1 ^= xk[k + 1] + s2 ^= xk[k + 2] + s3 ^= xk[k + 3] + _ = dst[15] // early bounds check + binary.big_endian_put_u32(mut (*dst)[..4], s0) + binary.big_endian_put_u32(mut (*dst)[4..8], s1) + binary.big_endian_put_u32(mut (*dst)[8..12], s2) + binary.big_endian_put_u32(mut (*dst)[12..16], s3) +} + +// Apply s_box0 to each byte in w. +fn subw(w u32) u32 { + return u32(s_box0[w >> 24]) << 24 | u32(s_box0[w >> 16 & 0xff] << 16) | u32(s_box0[w >> 8 & 0xff] << 8) | u32(s_box0[w & u32(0xff)]) +} + +// Rotate +fn rotw(w u32) u32 { + return (w << 8) | (w >> 24) +} + +// Key expansion algorithm. See FIPS-197, Figure 11. +// Their rcon[i] is our powx[i-1] << 24. +fn expand_key_generic(key []byte, mut enc []u32, mut dec []u32) { + // Encryption key setup. + mut i := 0 + nk := key.len / 4 + for i = 0; i < nk; i++ { + if 4 * i >= key.len { + break + } + enc[i] = binary.big_endian_u32(key[4 * i..]) + } + for i < enc.len { + mut t := enc[i - 1] + if i % nk == 0 { + t = subw(rotw(t)) ^ u32(pow_x[i / nk - 1]) << 24 + } else if nk > 6 && i % nk == 4 { + t = subw(t) + } + enc[i] = enc[i - nk] ^ t + i++ + } + // Derive decryption key from encryption key. + // Reverse the 4-word round key sets from enc to produce dec. + // All sets but the first and last get the MixColumn transform applied. + if dec.len == 0 { + return + } + n := enc.len + for i = 0; i < n; i += 4 { + ei := n - i - 4 + for j in 0 .. 4 { + mut x := enc[ei + j] + if i > 0 && i + 4 < n { + x = td0[s_box0[x >> 24]] ^ td1[s_box0[x >> 16 & 0xff]] ^ td2[s_box0[x >> 8 & 0xff]] ^ td3[s_box0[x & u32(0xff)]] + } + dec[i + j] = x + } + } +} diff --git a/v_windows/v/vlib/crypto/aes/const.v b/v_windows/v/vlib/crypto/aes/const.v new file mode 100644 index 0000000..1fe3357 --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/const.v @@ -0,0 +1,374 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// Package aes implements AES encryption (formerly Rijndael), as defined in +// U.S. Federal Information Processing Standards Publication 197. +// +// The AES operations in this package are not implemented using constant-time algorithms. +// An exception is when running on systems with enabled hardware support for AES +// that makes these operations constant-time. Examples include amd64 systems using AES-NI +// extensions and s390x systems using Message-Security-Assist extensions. +// On such systems, when the result of NewCipher is passed to cipher.NewGCM, +// the GHASH operation used by GCM is also constant-time. +module aes + +// This file contains AES constants - 8720 bytes of initialized data. + +// https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + +// AES is based on the mathematical behavior of binary polynomials +// (polynomials over GF(2)) modulo the irreducible polynomial x⁸ + x⁴ + x³ + x + 1. +// Addition of these binary polynomials corresponds to binary xor. +// Reducing mod poly corresponds to binary xor with poly every +// time a 0x100 bit appears. +const ( + poly = (1<<8) | (1<<4) | (1<<3) | (1<<1) | (1<<0) // x⁸ + x⁴ + x³ + x + 1 +) + +// Powers of x mod poly in GF(2). +const ( + pow_x = [ + byte(0x01), + 0x02, + 0x04, + 0x08, + 0x10, + 0x20, + 0x40, + 0x80, + 0x1b, + 0x36, + 0x6c, + 0xd8, + 0xab, + 0x4d, + 0x9a, + 0x2f, + ] +) + +// FIPS-197 Figure 7. S-box substitution values in hexadecimal format. +const ( + s_box0 = [ + byte(0x63), 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, + ] +) + +// FIPS-197 Figure 14. Inverse S-box substitution values in hexadecimal format. +const ( + s_box1 = [ + byte(0x52), 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, + ] +) + +// Lookup tables for encryption. + +const ( + te0 = [ + u32(0xc66363a5), 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, + 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, + ] + te1 = [ + u32(0xa5c66363), 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, + 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, + 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, + 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, + 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, + 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, + 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, + 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, + 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, + 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, + 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, + 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, + 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, + 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, + 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, + 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, + 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, + 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, + 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, + 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, + 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, + 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, + ] + te2 = [ + u32(0x63a5c663), 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, + 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, + 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, + 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, + 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, + 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, + 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, + 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, + 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, + 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, + 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, + 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, + 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, + 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, + 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, + 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, + 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, + 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, + 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, + 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, + 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, + 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, + ] + te3 = [ + u32(0x6363a5c6), 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, + 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, + 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, + 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, + 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, + 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, + 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, + 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, + 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, + 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, + 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, + 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, + 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, + 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, + 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, + 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, + 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, + 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, + 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, + 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, + 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, + 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, + ] +) + +// Lookup tables for decryption. +const ( + td0 = [ + u32(0x51f4a750), 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, + 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, + 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, + 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, + 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, + 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, + 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, + 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, + 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, + 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, + 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, + 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, + 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, + 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, + 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, + 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, + 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, + 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, + 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, + 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, + ] + td1 = [ + u32(0x5051f4a7), 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, + 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, + 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, + 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, + 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, + 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, + 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, + 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, + 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, + 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, + 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, + 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, + 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, + 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, + 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, + 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, + 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, + 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, + 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, + 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, + 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, + 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, + ] + td2 = [ + u32(0xa75051f4), 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, + 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, + 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, + 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, + 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, + 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, + 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, + 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, + 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, + 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, + 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, + 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, + 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, + 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, + 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, + 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, + 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, + 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, + 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, + 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, + ] + td3 = [ + u32(0xf4a75051), 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, + 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, + 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, + 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, + 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, + 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, + 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, + 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, + 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, + 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, + 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, + 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, + 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, + 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, + 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, + 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, + 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, + 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, + 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, + 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, + 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, + 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, + ] +) diff --git a/v_windows/v/vlib/crypto/aes/cypher_generic.v b/v_windows/v/vlib/crypto/aes/cypher_generic.v new file mode 100644 index 0000000..fb8bb08 --- /dev/null +++ b/v_windows/v/vlib/crypto/aes/cypher_generic.v @@ -0,0 +1,16 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module aes + +// new_cipher_generic creates and returns a new cipher.Block +// this is the generiv v version, no arch optimisations +fn new_cipher_generic(key []byte) AesCipher { + n := key.len + 28 + mut c := AesCipher{ + enc: []u32{len: n} + dec: []u32{len: n} + } + expand_key_generic(key, mut c.enc, mut c.dec) + return c +} diff --git a/v_windows/v/vlib/crypto/cipher/xor_generic.v b/v_windows/v/vlib/crypto/cipher/xor_generic.v new file mode 100644 index 0000000..bbec30b --- /dev/null +++ b/v_windows/v/vlib/crypto/cipher/xor_generic.v @@ -0,0 +1,33 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module cipher + +// NOTE: Implement other versions (joe-c) +// xor_bytes xors the bytes in a and b. The destination should have enough +// space, otherwise xor_bytes will panic. Returns the number of bytes xor'd. +pub fn xor_bytes(mut dst []byte, a []byte, b []byte) int { + mut n := a.len + if b.len < n { + n = b.len + } + if n == 0 { + return 0 + } + safe_xor_bytes(mut dst, a, b, n) + return n +} + +// safe_xor_bytes XORs the bytes in `a` and `b` into `dst` it does so `n` times. +// Please note: `n` needs to be smaller or equal than the length of `a` and `b`. +pub fn safe_xor_bytes(mut dst []byte, a []byte, b []byte, n int) { + for i in 0 .. n { + dst[i] = a[i] ^ b[i] + } +} + +// xor_words XORs multiples of 4 or 8 bytes (depending on architecture.) +// The slice arguments `a` and `b` are assumed to be of equal length. +pub fn xor_words(mut dst []byte, a []byte, b []byte) { + safe_xor_bytes(mut dst, a, b, b.len) +} diff --git a/v_windows/v/vlib/crypto/crypto.v b/v_windows/v/vlib/crypto/crypto.v new file mode 100644 index 0000000..12a092f --- /dev/null +++ b/v_windows/v/vlib/crypto/crypto.v @@ -0,0 +1,23 @@ +module crypto + +pub enum Hash { + md4 + md5 + sha1 + sha224 + sha256 + sha384 + sha512 + md5sha1 + ripemd160 + sha3_224 + sha3_256 + sha3_384 + sha3_512 + sha512_224 + sha512_256 + blake2s_256 + blake2b_256 + blake2b_384 + blake2b_512 +} diff --git a/v_windows/v/vlib/crypto/hmac/hmac.v b/v_windows/v/vlib/crypto/hmac/hmac.v new file mode 100644 index 0000000..3f45467 --- /dev/null +++ b/v_windows/v/vlib/crypto/hmac/hmac.v @@ -0,0 +1,44 @@ +// HMAC: Keyed-Hashing for Message Authentication implemented in v +// implementation based on https://tools.ietf.org/html/rfc2104 +module hmac + +import crypto.internal.subtle + +const ( + ipad = []byte{len: 256, init: 0x36} // TODO is 256 enough?? + opad = []byte{len: 256, init: 0x5C} + npad = []byte{len: 256, init: 0} +) + +// new returns a HMAC byte array, depending on the hash algorithm used. +pub fn new(key []byte, data []byte, hash_func fn ([]byte) []byte, blocksize int) []byte { + mut b_key := []byte{} + if key.len <= blocksize { + b_key = key.clone() // TODO: remove .clone() once https://github.com/vlang/v/issues/6604 gets fixed + } else { + b_key = hash_func(key) + } + if b_key.len < blocksize { + b_key << hmac.npad[..blocksize - b_key.len] + } + mut inner := []byte{} + for i, b in hmac.ipad[..blocksize] { + inner << b_key[i] ^ b + } + inner << data + inner_hash := hash_func(inner) + mut outer := []byte{cap: b_key.len} + for i, b in hmac.opad[..blocksize] { + outer << b_key[i] ^ b + } + outer << inner_hash + digest := hash_func(outer) + return digest +} + +// equal compares 2 MACs for equality, without leaking timing info. +// NB: if the lengths of the 2 MACs are different, probably a completely different +// hash function was used to generate them => no useful timing information. +pub fn equal(mac1 []byte, mac2 []byte) bool { + return subtle.constant_time_compare(mac1, mac2) == 1 +} diff --git a/v_windows/v/vlib/crypto/hmac/hmac_test.v b/v_windows/v/vlib/crypto/hmac/hmac_test.v new file mode 100644 index 0000000..3b78420 --- /dev/null +++ b/v_windows/v/vlib/crypto/hmac/hmac_test.v @@ -0,0 +1,226 @@ +// tests are taken from https://tools.ietf.org/html/rfc2202 +module hmac + +import crypto.hmac +import crypto.md5 +import crypto.sha1 +import crypto.sha256 +import crypto.sha512 + +// not yet supported +// import crypto.md4 +// import crypto.md5sha1 +// import crypto.ripemd160 +// import crypto.sha3_224 +// import crypto.sha3_256 +// import crypto.sha3_384 +// import crypto.sha3_512 +// import crypto.blake2s_256 +// import crypto.blake2b_256 +// import crypto.blake2b_384 +// import crypto.blake2b_512 +const ( + keys = [[byte(0xb), 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb], + 'Jefe'.bytes(), + [byte(0xAA), 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA], + [byte(0x01), 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19], + [byte(0x0c), 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c], + [byte(0xaa), 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + [byte(0xaa), 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], + ] + data = ['Hi There'.bytes(), 'what do ya want for nothing?'.bytes(), + [byte(0xDD), 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD], + [byte(0xcd), 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd], + 'Test With Truncation'.bytes(), + 'Test Using Larger Than Block-Size Key - Hash Key First'.bytes(), + 'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data'.bytes(), + ] +) + +fn test_hmac_md5() { + md5_expected_results := [ + '9294727a3638bb1c13f48ef8158bfc9d', + '750c783e6ab0b503eaa86e310a5db738', + '56be34521d144c88dbb8c733f0e8b3f6', + '697eaf0aca3a3aea3a75164746ffaa79', + '56461ef2342edc00f9bab995690efd4c', + '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd', + '6f630fad67cda0ee1fb1f562db3aa53e', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], md5.sum, md5.block_size).hex() + assert result == md5_expected_results[i] + } +} + +fn test_hmac_sha1() { + sha1_expected_results := [ + '675b0b3a1b4ddf4e124872da6c2f632bfed957e9', + 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79', + 'd730594d167e35d5956fd8003d0db3d3f46dc7bb', + '4c9007f4026250c6bc8414f9bf50c86c2d7235da', + '37268b7e21e84da5720c53c4ba03ad1104039fa7', + 'aa4ae5e15272d00e95705637ce8a3b55ed402112', + 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], sha1.sum, sha1.block_size).hex() + assert result == sha1_expected_results[i] + } +} + +fn test_hmac_sha224() { + sha224_expected_results := [ + '4e841ce7a4ae83fbcf71e3cd64bfbf277f73a14680aae8c518ac7861', + 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', + 'cbff7c2716bbaa7c77bed4f491d3e8456cb6c574e92f672b291acf5b', + '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', + 'd812c97a5e1412f2eb08dc4d95548117780f2930fa4e0e553d985c68', + '9ed2eebc0ed23576efc815e9b5bc0d9257e36d13e4dd5d5f0c809b38', + '7358939e58683a448ac5065196d33191a1c1d33d4b8b0304dc60f5e0', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], sha256.sum224, sha256.block_size).hex() + assert result == sha224_expected_results[i] + } +} + +fn test_hmac_sha256() { + sha256_expected_results := [ + '492ce020fe2534a5789dc3848806c78f4f6711397f08e7e7a12ca5a4483c8aa6', + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843', + '7dda3cc169743a6484649f94f0eda0f9f2ff496a9733fb796ed5adb40a44c3c1', + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b', + '2282475faa2def6936685d9c06566f2d782307ace7a27ada2037e6285efcb008', + '6953025ed96f0c09f80a96f78e6538dbe2e7b820e3dd970e7ddd39091b32352f', + '6355ac22e890d0a3c8481a5ca4825bc884d3e7a1ff98a2fc2ac7d8e064c3b2e6', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], sha256.sum, sha256.block_size).hex() + assert result == sha256_expected_results[i] + } +} + +fn test_hmac_sha384() { + sha384_expected_results := [ + '7afaa633e20d379b02395915fbc385ff8dc27dcd3885e1068ab942eeab52ec1f20ad382a92370d8b2e0ac8b83c4d53bf', + 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649', + '1383e82e28286b91f4cc7afbd13d5b5c6f887c05e7c4542484043a37a5fe45802a9470fb663bd7b6570fe2f503fc92f5', + '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb', + '10e0150a42d0ae6f9d3f55da7a8261c383b024c8d81b40e95d120acfd53fb018af5e77846ad99451059f0579cb9a718b', + '69d2e2f55de9f09878f04d23d8670d49cb734825cdb9cd9e72e446171a43540b90e17cf086e6fa3a599382a286c61340', + '34f065bdedc2487c30a634d9a49cf42116f78bb386ea4d498aea05c0077f05373cfdaa9b59a7b0481bced9e3f55016a9', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], sha512.sum384, sha512.block_size).hex() + assert result == sha384_expected_results[i] + } +} + +fn test_hmac_sha512() { + sha512_expected_results := [ + '7641c48a3b4aa8f887c07b3e83f96affb89c978fed8c96fcbbf4ad596eebfe496f9f16da6cd080ba393c6f365ad72b50d15c71bfb1d6b81f66a911786c6ce932', + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737', + 'ad9b5c7de72693737cd5e9d9f41170d18841fec1201c1c1b02e05cae116718009f771cad9946ddbf7e3cde3e818d9ae85d91b2badae94172d096a44a79c91e86', + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd', + 'da2c03a1f8d34ce536b246c9dc47281d7052d3f82a7b4f6dfe9ee9f5accdae02dd72f9b89324f25f9b8276a2e3d31c0a87b8b6c1dcefd7602cc881a7d120e3fd', + '132c9ebc32531071f6c4d9e8842291e9403e5940f813170a3ba3a0dd6c055c8b8ca587b24c56c47f3c1f2fb8ee8f9fbc8d92deed0f83426be3e8a2e9056778b3', + '09441cda584ed2f4d2f5b519c71baf3c79cce19dfc89a548e73b3bb382a9124d6e792b77bf57903ff5858e5d111d15f45d6fd118eea023f28d2eb234ebe62f85', + ] + mut result := '' + for i, key in hmac.keys { + result = hmac.new(key, hmac.data[i], sha512.sum512, sha512.block_size).hex() + assert result == sha512_expected_results[i] + } +} + +fn test_hmac_equal() { + mac1_1 := '7641c48a3b4aa8f887c07b3e83f96affb89c978fed8c96fcbbf4ad596eebfe496f9f16da6cd080ba393c6f365ad72b50d15c71bfb1d6b81f66a911786c6ce932'.bytes() + mac1_2 := '7641c48a3b4aa8f887c07b3e83f96affb89c978fed8c96fcbbf4ad596eebfe496f9f16da6cd080ba393c6f365ad72b50d15c71bfb1d6b81f66a911786c6ce932'.bytes() + mac2_1 := '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'.bytes() + mac2_2 := '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'.bytes() + assert hmac.equal(mac1_1, mac1_2) + assert hmac.equal(mac2_1, mac2_2) + assert !hmac.equal(mac1_1, mac2_1) + assert !hmac.equal(mac1_1, mac2_2) +} + +// not yet supported by crypto module +// sha3_224_expected_results := [ +// 'f68da7f7bf577de799bb1224b7acfef9e8de015a63475ed5904a4693' +// '7fdb8dd88bd2f60d1b798634ad386811c2cfc85bfaf5d52bbace5e66' +// '3c9b90dbbd88c2af888fb1b43ec9d424c7fbf0d2b9d0140952b110b5' +// 'a9d7685a19c4e0dbd9df2556cc8a7d2a7733b67625ce594c78270eeb' +// 'f865c4fe082e4dd1873a9d83e1ca3bf827c3256d91274574a8b66f13' +// '852c3fb04b18a04df20c007e608027c44230fdd440cf7a50a0bc4fd9' +// '14db797c7f4c69fd1d4c0ababeb9f90971fc62622cc7852dee156265' +// ] +// sha3_256_expected_results := [ +// '874d1d4e6e8302439bf707052e5d787d92bffcf0715853784e30da740a81e198' +// 'c7d4072e788877ae3596bbb0da73b887c9171f93095b294ae857fbe2645e1ba5' +// 'b55008323817b4df9398f32fd09d3ce624a3ac2a4f329c3b750c47647990de2a' +// '57366a45e2305321a4bc5aa5fe2ef8a921f6af8273d7fe7be6cfedb3f0aea6d7' +// 'a0cd54f140b61480cd22120d600e30c2508c4ae0d335fd69770f2b4ddc80cd19' +// '016a1a59d67944c350d992a9bc1e8e7f6d1ace9c9ff6be92eda103961fe897ab' +// '415c2b5cde6b2aecd637fa2384aa87e5a0b0c5bc20d53550bbac5474b18769bf' +// ] +// sha3_384_expected_results := [ +// 'b34fdb255dc7fb7f0c4bb2c1caeb0379b81ece60ec1b3cb2c5ec509141fcb77ca16d1e06f93049734be4948e24b932e3' +// 'f1101f8cbf9766fd6764d2ed61903f21ca9b18f57cf3e1a23ca13508a93243ce48c045dc007f26a21b3f5e0e9df4c20a' +// '5bd8a0b98f9f4201eaec41d01fd1e274c266a2517527c1879b0460a692e1a430aefb82f0c9aea33406582ffeeef0bba6' +// '3a5d7a879702c086bc96d1dd8aa15d9c46446b95521311c606fdc4e308f4b984da2d0f9449b3ba8425ec7fb8c31bc136' +// '0cdfc206fd95ca1f27e8e8bd443164814460ca50f8d34d776b18f9eb300231a3d5bace731f694a59faa84c2e4ae7e235' +// '7172a2a2bb002c22669a2f85b8faaacfcc4e8a19d47ef5ee7a97f79bf21e1d89403ab3768b43929f12eded01e3ddd604' +// '45081e207f796f372aff5a098249f52d045e350ed5c805b3445a79ad0d4931c4b86d41bd1bb2ac935d1b32c344d56709' +// ] +// sha3_512_expected_results := [ +// 'd2d9588c7e7886b08e09b56a7ac9d7e30a4badf13b37a041f5dfde34d87c086b5db1a7ec679bcfce81fa2eee982573c01dfb8d988e302f78d7b20d7d7ac2dfd7' +// '5a4bfeab6166427c7a3647b747292b8384537cdb89afb3bf5665e4c5e709350b287baec921fd7ca0ee7a0c31d022a95e1fc92ba9d77df883960275beb4e62024' +// 'f25055024a17dfe15a25d6c40b00f45e8548f641844f2288170430ba0b7889bfaf04d9398121d165375300fe813f3cb6db9639921dcfb712b9177b8f5261d474' +// 'b27eab1d6e8d87461c29f7f5739dd58e98aa35f8e823ad38c5492a2088fa0281993bbfff9a0e9c6bf121ae9ec9bb09d84a5ebac817182ea974673fb133ca0d1d' +// '69e9553223ede3637f08f9cc01ea9ded8f3b4202b5cc1feb60071e195a942f0ca0fa1cd70d3f1f9f24b2e18057b3001e7d5160e61eb6099f75ea4e0d6b849bd2' +// 'eea495d39d9a07154b1266b028e233b9fd84de884ac8f0578e679f095ef14da96d0a355ed4738565884aec755c1b3f5ff09a918b437f6526e17dd8e77f425b95' +// '1488670c683959b5304fa17c172bea81724a249b44981a3eb52cfc66ff0758b7cd1204745131b8adbc714db7fc4550ce26af5f2326067ad1e699f05cae8bb792' +// ] +// blake2b_expected_results := [ +// 'be2c398cbd5eef033031f9cee2caba52b280604f4afabf86de21973398c821fd120de5277f08955234182989c68b640af7dfa8cb9228eef4b48ffe768ef595eb' +// '6ff884f8ddc2a6586b3c98a4cd6ebdf14ec10204b6710073eb5865ade37a2643b8807c1335d107ecdb9ffeaeb6828c4625ba172c66379efcd222c2de11727ab4' +// '548fc7c3d5bd5a0ac74c5fe582037a259c774b81fb791d6c86e675d03b361939e21ea0fb9c6401df22170ebf8017d908675bbcf65911025a1ab5d271a372f43f' +// 'e5dbb6de2fee42a1caa06e4e7b84ce408ffa5c4a9de2632eca769cde8875014c72d0720feaf53f76e6a180357f528d7bf484fa3a14e8cc1f0f3bada717b43491' +// '057f3bcc3511aa9627d96ab2ad02ec3dc86741514d19a3c9b10e539b0ca7c2587d9d04118636a67e18871633ecf8705a3ef6697cf6b64339f71b8cdffab89e34' +// '368aba23ca42648157771936b436a0ecb3d83e5fe21e020fef2a08dc9e59739ea919a8d0f46c45b99491f426f1e7c62352d9d67c066571d74e191b69bedaf718' +// 'f1c9b64e121330c512dc31e0d4a2fc84b7ca5be64e08934a7fc4640c4a1f5cc3c1f34d811c8079cc2df65a4e5d68baf833a1ec558546abeaa7d564840618db7b' +// ] +// blake2s_expected_results := [ +// '139cd736b926dae4853aab90655120e0305c476fde978166e472c7c8698c21b1' +// '464434dcbece095d456a1d62d6ec56f898e625a39e5c52bdf94daf111bad83aa' +// '90b6281e2f3038c9056af0b4a7e763cae6fe5d9eb4386a0ec95237890c104ff0' +// '92394bc2486bea31db01c74be46332edd1499e9700ea41df0670df79fcc0f7c6' +// '97a771b4e4e2fd4d4d0fd8aca2a0663ad8ad4a463cabbf603bf837dd84dec050' +// '41202b7be1fd03f84658f4c18a08b43ddbbd73eb012750c8d1ebc8601f1e064c' +// '467201ef5997a3442932b318083488cf9aa1d89bef2146154b4816d34863e33d' +// ] diff --git a/v_windows/v/vlib/crypto/internal/subtle/aliasing.v b/v_windows/v/vlib/crypto/internal/subtle/aliasing.v new file mode 100644 index 0000000..e09748e --- /dev/null +++ b/v_windows/v/vlib/crypto/internal/subtle/aliasing.v @@ -0,0 +1,29 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package subtle implements functions that are often useful in cryptographic +// code but require careful thought to use correctly. +module subtle + +// NOTE: require unsafe in future +// any_overlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +pub fn any_overlap(x []byte, y []byte) bool { + // NOTE: Remember to come back to this (joe-c) + return x.len > 0 && y.len > 0 && // &x.data[0] <= &y.data[y.len-1] && + // &y.data[0] <= &x.data[x.len-1] + unsafe { &x[0] <= &y[y.len - 1] && &y[0] <= &x[x.len - 1] } +} + +// inexact_overlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// inexact_overlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +pub fn inexact_overlap(x []byte, y []byte) bool { + if x.len == 0 || y.len == 0 || unsafe { &x[0] == &y[0] } { + return false + } + return any_overlap(x, y) +} diff --git a/v_windows/v/vlib/crypto/internal/subtle/comparison.v b/v_windows/v/vlib/crypto/internal/subtle/comparison.v new file mode 100644 index 0000000..e7dd162 --- /dev/null +++ b/v_windows/v/vlib/crypto/internal/subtle/comparison.v @@ -0,0 +1,53 @@ +module subtle + +// constant_time_byte_eq returns 1 when x == y. +pub fn constant_time_byte_eq(x byte, y byte) int { + return int((u32(x ^ y) - 1) >> 31) +} + +// constant_time_eq returns 1 when x == y. +pub fn constant_time_eq(x int, y int) int { + return int((u64(u32(x ^ y)) - 1) >> 63) +} + +// constant_time_select returns x when v == 1, and y when v == 0. +// it is undefined when v is any other value +pub fn constant_time_select(v int, x int, y int) int { + return (~(v - 1) & x) | ((v - 1) & y) +} + +// constant_time_compare returns 1 when x and y have equal contents. +// The runtime of this function is proportional of the length of x and y. +// It is *NOT* dependent on their content. +pub fn constant_time_compare(x []byte, y []byte) int { + if x.len != y.len { + return 0 + } + mut v := byte(0) + for i in 0 .. x.len { + v |= x[i] ^ y[i] + } + return constant_time_byte_eq(v, 0) +} + +// constant_time_copy copies the contents of y into x, when v == 1. +// When v == 0, x is left unchanged. this function is undefined, when +// v takes any other value +pub fn constant_time_copy(v int, mut x []byte, y []byte) { + if x.len != y.len { + panic('subtle: arrays have different lengths') + } + xmask := byte(v - 1) + ymask := byte(~(v - 1)) + for i := 0; i < x.len; i++ { + x[i] = x[i] & xmask | y[i] & ymask + } +} + +// constant_time_less_or_eq returns 1 if x <= y, and 0 otherwise. +// it is undefined when x or y are negative, or > (2^32 - 1) +pub fn constant_time_less_or_eq(x int, y int) int { + x32 := int(x) + y32 := int(y) + return int(((x32 - y32 - 1) >> 31) & 1) +} diff --git a/v_windows/v/vlib/crypto/internal/subtle/comparison_test.v b/v_windows/v/vlib/crypto/internal/subtle/comparison_test.v new file mode 100644 index 0000000..b339746 --- /dev/null +++ b/v_windows/v/vlib/crypto/internal/subtle/comparison_test.v @@ -0,0 +1,65 @@ +module subtle + +fn test_constant_time_byte_eq() { + assert constant_time_byte_eq(0, 0) == 1 + assert constant_time_byte_eq(1, 1) == 1 + assert constant_time_byte_eq(255, 255) == 1 + assert constant_time_byte_eq(255, 1) == 0 + assert constant_time_byte_eq(1, 255) == 0 + assert constant_time_byte_eq(2, 1) == 0 +} + +fn test_constant_time_eq() { + assert constant_time_eq(0, 0) == 1 + assert constant_time_eq(255, 255) == 1 + assert constant_time_eq(65536, 65536) == 1 + assert constant_time_eq(-1, -1) == 1 + assert constant_time_eq(-256, -256) == 1 + assert constant_time_eq(0, 1) == 0 +} + +fn test_constant_time_select() { + assert constant_time_select(1, 1, 0) == 1 + assert constant_time_select(1, 1, 255) == 1 + assert constant_time_select(1, 1, 255 * 255) == 1 + assert constant_time_select(1, 2, 0) == 2 + assert constant_time_select(1, 2, 255) == 2 + assert constant_time_select(1, 2, 255 * 255) == 2 + // + assert constant_time_select(0, 1, 0) == 0 + assert constant_time_select(0, 1, 255) == 255 + assert constant_time_select(0, 1, 255 * 255) == 255 * 255 + assert constant_time_select(0, 2, 0) == 0 + assert constant_time_select(0, 2, 255) == 255 + assert constant_time_select(0, 2, 255 * 255) == 255 * 255 +} + +fn test_constant_time_compare() { + assert constant_time_compare([byte(1), 2, 3], [byte(1), 2, 3]) == 1 + assert constant_time_compare([byte(1), 2, 3], [byte(1), 2, 9]) == 0 + assert constant_time_compare([byte(1), 2, 3], [byte(1), 2, 3, 4]) == 0 + assert constant_time_compare([byte(1), 2, 3], [byte(1), 2]) == 0 +} + +fn test_constant_time_copy() { + y := [byte(3), 4, 5] + mut x := [byte(0), 0, 0] + constant_time_copy(0, mut x, y) + assert x == [byte(0), 0, 0] + constant_time_copy(1, mut x, y) + assert x == y + assert x == [byte(3), 4, 5] +} + +fn test_constant_time_less_or_eq() { + assert constant_time_less_or_eq(1, 1) == 1 + assert constant_time_less_or_eq(1, 2) == 1 + assert constant_time_less_or_eq(1, 3) == 1 + assert constant_time_less_or_eq(255, 255) == 1 + assert constant_time_less_or_eq(255, 256) == 1 + assert constant_time_less_or_eq(255, 257) == 1 + assert constant_time_less_or_eq(1, 0) == 0 + assert constant_time_less_or_eq(2, 1) == 0 + assert constant_time_less_or_eq(3, 2) == 0 + assert constant_time_less_or_eq(255, 3) == 0 +} diff --git a/v_windows/v/vlib/crypto/md5/md5.v b/v_windows/v/vlib/crypto/md5/md5.v new file mode 100644 index 0000000..17796b1 --- /dev/null +++ b/v_windows/v/vlib/crypto/md5/md5.v @@ -0,0 +1,154 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package md5 implements the MD5 hash algorithm as defined in RFC 1321. +// MD5 is cryptographically broken and should not be used for secure +// applications. +// Based off: https://github.com/golang/go/blob/master/src/crypto/md5 +// Last commit: https://github.com/golang/go/commit/ed7f323c8f4f6bc61a75146bf34f5b8f73063a17 +module md5 + +import encoding.binary + +pub const ( + // The size of an MD5 checksum in bytes. + size = 16 + // The blocksize of MD5 in bytes. + block_size = 64 +) + +const ( + init0 = 0x67452301 + init1 = 0xEFCDAB89 + init2 = 0x98BADCFE + init3 = 0x10325476 +) + +// Digest represents the partial evaluation of a checksum. +struct Digest { +mut: + s []u32 + x []byte + nx int + len u64 +} + +fn (mut d Digest) reset() { + d.s = []u32{len: (4)} + d.x = []byte{len: md5.block_size} + d.s[0] = u32(md5.init0) + d.s[1] = u32(md5.init1) + d.s[2] = u32(md5.init2) + d.s[3] = u32(md5.init3) + d.nx = 0 + d.len = 0 +} + +// new returns a new Digest (implementing hash.Hash) computing the MD5 checksum. +pub fn new() &Digest { + mut d := &Digest{} + d.reset() + return d +} + +// write writes the contents of `p_` to the internal hash representation. +pub fn (mut d Digest) write(p_ []byte) ?int { + unsafe { + mut p := p_ + nn := p.len + d.len += u64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx..], p) + d.nx += n + if d.nx == md5.block_size { + block(mut d, d.x) + d.nx = 0 + } + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len >= md5.block_size { + n := p.len & ~(md5.block_size - 1) + block(mut d, p[..n]) + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len > 0 { + d.nx = copy(d.x, p) + } + return nn + } +} + +// sum returns the md5 sum of the bytes in `b_in`. +pub fn (d &Digest) sum(b_in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + mut d0 := *d + hash := d0.checksum() + mut b_out := b_in.clone() + for b in hash { + b_out << b + } + return b_out +} + +// checksum returns the byte checksum of the `Digest`. +pub fn (mut d Digest) checksum() []byte { + // Append 0x80 to the end of the message and then append zeros + // until the length is a multiple of 56 bytes. Finally append + // 8 bytes representing the message length in bits. + // + // 1 byte end marker :: 0-63 padding bytes :: 8 byte length + // tmp := [1 + 63 + 8]byte{0x80} + mut tmp := []byte{len: (1 + 63 + 8)} + tmp[0] = 0x80 + pad := ((55 - d.len) % 64) // calculate number of padding bytes + binary.little_endian_put_u64(mut tmp[1 + pad..], d.len << 3) // append length in bits + d.write(tmp[..1 + pad + 8]) or { panic(err) } + // The previous write ensures that a whole number of + // blocks (i.e. a multiple of 64 bytes) have been hashed. + if d.nx != 0 { + panic('d.nx != 0') + } + mut digest := []byte{len: md5.size} + binary.little_endian_put_u32(mut digest, d.s[0]) + binary.little_endian_put_u32(mut digest[4..], d.s[1]) + binary.little_endian_put_u32(mut digest[8..], d.s[2]) + binary.little_endian_put_u32(mut digest[12..], d.s[3]) + return digest +} + +// sum returns the MD5 checksum of the data. +pub fn sum(data []byte) []byte { + mut d := new() + d.write(data) or { panic(err) } + return d.checksum() +} + +fn block(mut dig Digest, p []byte) { + // For now just use block_generic until we have specific + // architecture optimized versions + block_generic(mut dig, p) +} + +// size returns the size of the checksum in bytes. +pub fn (d &Digest) size() int { + return md5.size +} + +// block_size returns the block size of the checksum in bytes. +pub fn (d &Digest) block_size() int { + return md5.block_size +} + +// hexhash returns a hexadecimal MD5 hash sum `string` of `s`. +// Example: assert md5.hexhash('V') == '5206560a306a2e085a437fd258eb57ce' +pub fn hexhash(s string) string { + return sum(s.bytes()).hex() +} diff --git a/v_windows/v/vlib/crypto/md5/md5_test.v b/v_windows/v/vlib/crypto/md5/md5_test.v new file mode 100644 index 0000000..fd43c1c --- /dev/null +++ b/v_windows/v/vlib/crypto/md5/md5_test.v @@ -0,0 +1,8 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.md5 + +fn test_crypto_md5() { + assert md5.sum('this is a md5 checksum.'.bytes()).hex() == '6fb421ff99036547655984da12973431' +} diff --git a/v_windows/v/vlib/crypto/md5/md5block_generic.v b/v_windows/v/vlib/crypto/md5/md5block_generic.v new file mode 100644 index 0000000..ebc4a8d --- /dev/null +++ b/v_windows/v/vlib/crypto/md5/md5block_generic.v @@ -0,0 +1,132 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// This is the generic version with no architecture optimizations. +// In its own file so that an architecture +// optimized verision can be substituted + +module md5 + +import math.bits +import encoding.binary + +fn block_generic(mut dig Digest, p []byte) { + // load state + mut a := dig.s[0] + mut b := dig.s[1] + mut c := dig.s[2] + mut d := dig.s[3] + + for i := 0; i <= p.len - block_size; i += block_size { + mut q := p[i..] + q = q[..block_size] + // save current state + aa := a + bb := b + cc := c + dd := d + + // load input block + x0 := binary.little_endian_u32(q[4 * 0x0..]) + x1 := binary.little_endian_u32(q[4 * 0x1..]) + x2 := binary.little_endian_u32(q[4 * 0x2..]) + x3 := binary.little_endian_u32(q[4 * 0x3..]) + x4 := binary.little_endian_u32(q[4 * 0x4..]) + x5 := binary.little_endian_u32(q[4 * 0x5..]) + x6 := binary.little_endian_u32(q[4 * 0x6..]) + x7 := binary.little_endian_u32(q[4 * 0x7..]) + x8 := binary.little_endian_u32(q[4 * 0x8..]) + x9 := binary.little_endian_u32(q[4 * 0x9..]) + xa := binary.little_endian_u32(q[4 * 0xa..]) + xb := binary.little_endian_u32(q[4 * 0xb..]) + xc := binary.little_endian_u32(q[4 * 0xc..]) + xd := binary.little_endian_u32(q[4 * 0xd..]) + xe := binary.little_endian_u32(q[4 * 0xe..]) + xf := binary.little_endian_u32(q[4 * 0xf..]) + + // round 1 + a = b + bits.rotate_left_32((((c ^ d) & b) ^ d) + a + x0 + u32(0xd76aa478), 7) + d = a + bits.rotate_left_32((((b ^ c) & a) ^ c) + d + x1 + u32(0xe8c7b756), 12) + c = d + bits.rotate_left_32((((a ^ b) & d) ^ b) + c + x2 + u32(0x242070db), 17) + b = c + bits.rotate_left_32((((d ^ a) & c) ^ a) + b + x3 + u32(0xc1bdceee), 22) + a = b + bits.rotate_left_32((((c ^ d) & b) ^ d) + a + x4 + u32(0xf57c0faf), 7) + d = a + bits.rotate_left_32((((b ^ c) & a) ^ c) + d + x5 + u32(0x4787c62a), 12) + c = d + bits.rotate_left_32((((a ^ b) & d) ^ b) + c + x6 + u32(0xa8304613), 17) + b = c + bits.rotate_left_32((((d ^ a) & c) ^ a) + b + x7 + u32(0xfd469501), 22) + a = b + bits.rotate_left_32((((c ^ d) & b) ^ d) + a + x8 + u32(0x698098d8), 7) + d = a + bits.rotate_left_32((((b ^ c) & a) ^ c) + d + x9 + u32(0x8b44f7af), 12) + c = d + bits.rotate_left_32((((a ^ b) & d) ^ b) + c + xa + u32(0xffff5bb1), 17) + b = c + bits.rotate_left_32((((d ^ a) & c) ^ a) + b + xb + u32(0x895cd7be), 22) + a = b + bits.rotate_left_32((((c ^ d) & b) ^ d) + a + xc + u32(0x6b901122), 7) + d = a + bits.rotate_left_32((((b ^ c) & a) ^ c) + d + xd + u32(0xfd987193), 12) + c = d + bits.rotate_left_32((((a ^ b) & d) ^ b) + c + xe + u32(0xa679438e), 17) + b = c + bits.rotate_left_32((((d ^ a) & c) ^ a) + b + xf + u32(0x49b40821), 22) + + // round 2 + a = b + bits.rotate_left_32((((b ^ c) & d) ^ c) + a + x1 + u32(0xf61e2562), 5) + d = a + bits.rotate_left_32((((a ^ b) & c) ^ b) + d + x6 + u32(0xc040b340), 9) + c = d + bits.rotate_left_32((((d ^ a) & b) ^ a) + c + xb + u32(0x265e5a51), 14) + b = c + bits.rotate_left_32((((c ^ d) & a) ^ d) + b + x0 + u32(0xe9b6c7aa), 20) + a = b + bits.rotate_left_32((((b ^ c) & d) ^ c) + a + x5 + u32(0xd62f105d), 5) + d = a + bits.rotate_left_32((((a ^ b) & c) ^ b) + d + xa + u32(0x02441453), 9) + c = d + bits.rotate_left_32((((d ^ a) & b) ^ a) + c + xf + u32(0xd8a1e681), 14) + b = c + bits.rotate_left_32((((c ^ d) & a) ^ d) + b + x4 + u32(0xe7d3fbc8), 20) + a = b + bits.rotate_left_32((((b ^ c) & d) ^ c) + a + x9 + u32(0x21e1cde6), 5) + d = a + bits.rotate_left_32((((a ^ b) & c) ^ b) + d + xe + u32(0xc33707d6), 9) + c = d + bits.rotate_left_32((((d ^ a) & b) ^ a) + c + x3 + u32(0xf4d50d87), 14) + b = c + bits.rotate_left_32((((c ^ d) & a) ^ d) + b + x8 + u32(0x455a14ed), 20) + a = b + bits.rotate_left_32((((b ^ c) & d) ^ c) + a + xd + u32(0xa9e3e905), 5) + d = a + bits.rotate_left_32((((a ^ b) & c) ^ b) + d + x2 + u32(0xfcefa3f8), 9) + c = d + bits.rotate_left_32((((d ^ a) & b) ^ a) + c + x7 + u32(0x676f02d9), 14) + b = c + bits.rotate_left_32((((c ^ d) & a) ^ d) + b + xc + u32(0x8d2a4c8a), 20) + + // round 3 + a = b + bits.rotate_left_32((b ^ c ^ d) + a + x5 + u32(0xfffa3942), 4) + d = a + bits.rotate_left_32((a ^ b ^ c) + d + x8 + u32(0x8771f681), 11) + c = d + bits.rotate_left_32((d ^ a ^ b) + c + xb + u32(0x6d9d6122), 16) + b = c + bits.rotate_left_32((c ^ d ^ a) + b + xe + u32(0xfde5380c), 23) + a = b + bits.rotate_left_32((b ^ c ^ d) + a + x1 + u32(0xa4beea44), 4) + d = a + bits.rotate_left_32((a ^ b ^ c) + d + x4 + u32(0x4bdecfa9), 11) + c = d + bits.rotate_left_32((d ^ a ^ b) + c + x7 + u32(0xf6bb4b60), 16) + b = c + bits.rotate_left_32((c ^ d ^ a) + b + xa + u32(0xbebfbc70), 23) + a = b + bits.rotate_left_32((b ^ c ^ d) + a + xd + u32(0x289b7ec6), 4) + d = a + bits.rotate_left_32((a ^ b ^ c) + d + x0 + u32(0xeaa127fa), 11) + c = d + bits.rotate_left_32((d ^ a ^ b) + c + x3 + u32(0xd4ef3085), 16) + b = c + bits.rotate_left_32((c ^ d ^ a) + b + x6 + u32(0x04881d05), 23) + a = b + bits.rotate_left_32((b ^ c ^ d) + a + x9 + u32(0xd9d4d039), 4) + d = a + bits.rotate_left_32((a ^ b ^ c) + d + xc + u32(0xe6db99e5), 11) + c = d + bits.rotate_left_32((d ^ a ^ b) + c + xf + u32(0x1fa27cf8), 16) + b = c + bits.rotate_left_32((c ^ d ^ a) + b + x2 + u32(0xc4ac5665), 23) + + // round 4 + a = b + bits.rotate_left_32((c ^ (b | ~d)) + a + x0 + u32(0xf4292244), 6) + d = a + bits.rotate_left_32((b ^ (a | ~c)) + d + x7 + u32(0x432aff97), 10) + c = d + bits.rotate_left_32((a ^ (d | ~b)) + c + xe + u32(0xab9423a7), 15) + b = c + bits.rotate_left_32((d ^ (c | ~a)) + b + x5 + u32(0xfc93a039), 21) + a = b + bits.rotate_left_32((c ^ (b | ~d)) + a + xc + u32(0x655b59c3), 6) + d = a + bits.rotate_left_32((b ^ (a | ~c)) + d + x3 + u32(0x8f0ccc92), 10) + c = d + bits.rotate_left_32((a ^ (d | ~b)) + c + xa + u32(0xffeff47d), 15) + b = c + bits.rotate_left_32((d ^ (c | ~a)) + b + x1 + u32(0x85845dd1), 21) + a = b + bits.rotate_left_32((c ^ (b | ~d)) + a + x8 + u32(0x6fa87e4f), 6) + d = a + bits.rotate_left_32((b ^ (a | ~c)) + d + xf + u32(0xfe2ce6e0), 10) + c = d + bits.rotate_left_32((a ^ (d | ~b)) + c + x6 + u32(0xa3014314), 15) + b = c + bits.rotate_left_32((d ^ (c | ~a)) + b + xd + u32(0x4e0811a1), 21) + a = b + bits.rotate_left_32((c ^ (b | ~d)) + a + x4 + u32(0xf7537e82), 6) + d = a + bits.rotate_left_32((b ^ (a | ~c)) + d + xb + u32(0xbd3af235), 10) + c = d + bits.rotate_left_32((a ^ (d | ~b)) + c + x2 + u32(0x2ad7d2bb), 15) + b = c + bits.rotate_left_32((d ^ (c | ~a)) + b + x9 + u32(0xeb86d391), 21) + + // add saved state + a += aa + b += bb + c += cc + d += dd + } + + // save state + dig.s[0] = a + dig.s[1] = b + dig.s[2] = c + dig.s[3] = d +} diff --git a/v_windows/v/vlib/crypto/rand/crypto_rand_read_test.v b/v_windows/v/vlib/crypto/rand/crypto_rand_read_test.v new file mode 100644 index 0000000..824923d --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/crypto_rand_read_test.v @@ -0,0 +1,15 @@ +import crypto.rand + +fn test_reading() ? { + a := rand.read(32) ? + // dump(a.hex()) + assert a.len == 32 + mut histogram := [256]int{} + for b in a { + histogram[b]++ + } + // dump(histogram) + for h in histogram { + assert h < 10 + } +} diff --git a/v_windows/v/vlib/crypto/rand/rand.v b/v_windows/v/vlib/crypto/rand/rand.v new file mode 100644 index 0000000..0703558 --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand.v @@ -0,0 +1,10 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module rand + +struct ReadError { + msg string = 'crypto.rand.read() error reading random bytes' + code int +} diff --git a/v_windows/v/vlib/crypto/rand/rand_darwin.c.v b/v_windows/v/vlib/crypto/rand/rand_darwin.c.v new file mode 100644 index 0000000..a53198b --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand_darwin.c.v @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module rand + +#include + +#flag darwin -framework Security + +fn C.SecRandomCopyBytes(rnd C.SecRandomRef, count size_t, bytes voidptr) int + +// read returns an array of `bytes_needed` random bytes read from the OS. +pub fn read(bytes_needed int) ?[]byte { + mut buffer := []byte{len: bytes_needed} + status := C.SecRandomCopyBytes(C.SecRandomRef(0), bytes_needed, buffer.data) + if status != 0 { + return IError(&ReadError{}) + } + return buffer +} diff --git a/v_windows/v/vlib/crypto/rand/rand_default.c.v b/v_windows/v/vlib/crypto/rand/rand_default.c.v new file mode 100644 index 0000000..2e1e823 --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand_default.c.v @@ -0,0 +1,9 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module rand + +// read returns an array of `bytes_needed` random bytes read from the OS. +pub fn read(bytes_needed int) ?[]byte { + return error('rand.read is not implemented on this platform') +} diff --git a/v_windows/v/vlib/crypto/rand/rand_linux.c.v b/v_windows/v/vlib/crypto/rand/rand_linux.c.v new file mode 100644 index 0000000..52ff3da --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand_linux.c.v @@ -0,0 +1,39 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module rand + +#include + +const ( + read_batch_size = 256 +) + +// read returns an array of `bytes_needed` random bytes read from the OS. +pub fn read(bytes_needed int) ?[]byte { + mut buffer := unsafe { malloc_noscan(bytes_needed) } + mut bytes_read := 0 + mut remaining_bytes := bytes_needed + // getrandom syscall wont block if requesting <= 256 bytes + for bytes_read < bytes_needed { + batch_size := if remaining_bytes > rand.read_batch_size { + rand.read_batch_size + } else { + remaining_bytes + } + rbytes := unsafe { getrandom(batch_size, buffer + bytes_read) } + if rbytes == -1 { + unsafe { free(buffer) } + return IError(&ReadError{}) + } + bytes_read += rbytes + } + return unsafe { buffer.vbytes(bytes_needed) } +} + +fn getrandom(bytes_needed int, buffer voidptr) int { + if bytes_needed > rand.read_batch_size { + panic('getrandom() dont request more than $rand.read_batch_size bytes at once.') + } + return unsafe { C.syscall(C.SYS_getrandom, buffer, bytes_needed, 0) } +} diff --git a/v_windows/v/vlib/crypto/rand/rand_solaris.c.v b/v_windows/v/vlib/crypto/rand/rand_solaris.c.v new file mode 100644 index 0000000..8d0ba0c --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand_solaris.c.v @@ -0,0 +1,42 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module rand + +#include + +fn C.getrandom(p &byte, n size_t, flags u32) int + +const ( + read_batch_size = 256 +) + +// read returns an array of `bytes_needed` random bytes read from the OS. +pub fn read(bytes_needed int) ?[]byte { + mut buffer := unsafe { malloc_noscan(bytes_needed) } + mut bytes_read := 0 + mut remaining_bytes := bytes_needed + // getrandom syscall wont block if requesting <= 256 bytes + for bytes_read < bytes_needed { + batch_size := if remaining_bytes > rand.read_batch_size { + rand.read_batch_size + } else { + remaining_bytes + } + rbytes := unsafe { getrandom(batch_size, buffer + bytes_read) } + if rbytes == -1 { + unsafe { free(buffer) } + return IError(&ReadError{}) + } + bytes_read += rbytes + } + return unsafe { buffer.vbytes(bytes_needed) } +} + +fn v_getrandom(bytes_needed int, buffer voidptr) int { + if bytes_needed > rand.read_batch_size { + panic('getrandom() dont request more than $rand.read_batch_size bytes at once.') + } + return C.getrandom(buffer, bytes_needed, 0) +} diff --git a/v_windows/v/vlib/crypto/rand/rand_windows.c.v b/v_windows/v/vlib/crypto/rand/rand_windows.c.v new file mode 100644 index 0000000..075a3ed --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/rand_windows.c.v @@ -0,0 +1,25 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module rand + +#flag windows -Llibraries/bcrypt +#flag windows -lbcrypt +#include + +const ( + status_success = 0x00000000 + bcrypt_use_system_preferred_rng = 0x00000002 +) + +// read returns an array of `bytes_needed` random bytes read from the OS. +pub fn read(bytes_needed int) ?[]byte { + mut buffer := []byte{len: bytes_needed} + // use bcrypt_use_system_preferred_rng because we passed null as algo + status := C.BCryptGenRandom(0, buffer.data, bytes_needed, rand.bcrypt_use_system_preferred_rng) + if status != rand.status_success { + return IError(&ReadError{}) + } + return buffer +} diff --git a/v_windows/v/vlib/crypto/rand/utils.v b/v_windows/v/vlib/crypto/rand/utils.v new file mode 100644 index 0000000..31eaf2d --- /dev/null +++ b/v_windows/v/vlib/crypto/rand/utils.v @@ -0,0 +1,55 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module rand + +import math.bits +import encoding.binary + +// int_u64 returns a random unsigned 64-bit integer `u64` read from a real OS source of entropy. +pub fn int_u64(max u64) ?u64 { + bitlen := bits.len_64(max) + if bitlen == 0 { + return u64(0) + } + k := (bitlen + 7) / 8 + mut b := u64(bitlen % 8) + if b == u64(0) { + b = u64(8) + } + mut n := u64(0) + for { + mut bytes := read(k) ? + bytes[0] &= byte(int(u64(1) << b) - 1) + x := bytes_to_u64(bytes) + n = x[0] + // NOTE: maybe until we have bigint could do it another way? + // if x.len > 1 { + // n = u64(u32(x[1])<= ws; k++ { + z[k] = binary.big_endian_u64(b[i - ws..i]) + i -= ws + } + if i > 0 { + mut d := u64(0) + for s := u64(0); i > 0; s += u64(8) { + d |= u64(b[i - 1]) << s + i-- + } + z[z.len - 1] = d + } + return z +} diff --git a/v_windows/v/vlib/crypto/rc4/rc4.v b/v_windows/v/vlib/crypto/rc4/rc4.v new file mode 100644 index 0000000..97b2be4 --- /dev/null +++ b/v_windows/v/vlib/crypto/rc4/rc4.v @@ -0,0 +1,79 @@ +module rc4 + +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package rc4 implements RC4 encryption, as defined in Bruce Schneier's +// Applied Cryptography. +// +// RC4 is cryptographically broken and should not be used for secure +// applications. +// Based off: https://github.com/golang/go/blob/master/src/crypto/rc4 +// Last commit: https://github.com/golang/go/commit/b35dacaac57b039205d9b07ea24098e2c3fcb12e +import crypto.internal.subtle + +// A Cipher is an instance of RC4 using a particular key. +struct Cipher { +mut: + s []u32 + i byte + j byte +} + +// new_cipher creates and returns a new Cipher. The key argument should be the +// RC4 key, at least 1 byte and at most 256 bytes. +pub fn new_cipher(key []byte) ?Cipher { + if key.len < 1 || key.len > 256 { + return error('crypto.rc4: invalid key size ' + key.len.str()) + } + mut c := Cipher{ + s: []u32{len: (256)} + } + for i in 0 .. 256 { + c.s[i] = u32(i) + } + mut j := byte(0) + for i in 0 .. 256 { + j += byte(c.s[i]) + key[i % key.len] + tmp := c.s[i] + c.s[i] = c.s[j] + c.s[j] = tmp + } + return c +} + +// reset zeros the key data and makes the Cipher unusable.good to com +// +// Deprecated: Reset can't guarantee that the key will be entirely removed from +// the process's memory. +pub fn (mut c Cipher) reset() { + for i in c.s { + c.s[i] = 0 + } + c.i = 0 + c.j = 0 +} + +// xor_key_stream sets dst to the result of XORing src with the key stream. +// Dst and src must overlap entirely or not at all. +pub fn (mut c Cipher) xor_key_stream(mut dst []byte, mut src []byte) { + if src.len == 0 { + return + } + if subtle.inexact_overlap(dst, src) { + panic('crypto.rc4: invalid buffer overlap') + } + mut i := c.i + mut j := c.j + for k, v in src { + i += byte(1) + x := c.s[i] + j += byte(x) + y := c.s[j] + c.s[i] = y + c.s[j] = x + dst[k] = v ^ byte(c.s[byte(x + y)]) + } + c.i = i + c.j = j +} diff --git a/v_windows/v/vlib/crypto/rc4/rc4_test.v b/v_windows/v/vlib/crypto/rc4/rc4_test.v new file mode 100644 index 0000000..dd9ac0f --- /dev/null +++ b/v_windows/v/vlib/crypto/rc4/rc4_test.v @@ -0,0 +1,22 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.rc4 + +fn test_crypto_rc4() { + key := 'tthisisourrc4key'.bytes() + + mut c := rc4.new_cipher(key) or { + println(err) + return + } + + mut src := 'toencrypt'.bytes() + + // src & dst same, encrypt in place + c.xor_key_stream(mut src, mut src) // encrypt data + + c.reset() + + assert src.hex() == '189a39a91aea8afa65' +} diff --git a/v_windows/v/vlib/crypto/readme.txt b/v_windows/v/vlib/crypto/readme.txt new file mode 100644 index 0000000..93b4cdf --- /dev/null +++ b/v_windows/v/vlib/crypto/readme.txt @@ -0,0 +1,29 @@ +Based on Go's crypto packages. + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/v_windows/v/vlib/crypto/sha1/sha1.v b/v_windows/v/vlib/crypto/sha1/sha1.v new file mode 100644 index 0000000..d792eb1 --- /dev/null +++ b/v_windows/v/vlib/crypto/sha1/sha1.v @@ -0,0 +1,155 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package sha1 implements the SHA-1 hash algorithm as defined in RFC 3174. +// SHA-1 is cryptographically broken and should not be used for secure +// applications. +// Based off: https://github.com/golang/go/blob/master/src/crypto/sha1 +// Last commit: https://github.com/golang/go/commit/3ce865d7a0b88714cc433454ae2370a105210c01 +module sha1 + +import encoding.binary + +pub const ( + // The size of a SHA-1 checksum in bytes. + size = 20 + // The blocksize of SHA-1 in bytes. + block_size = 64 +) + +const ( + chunk = 64 + init0 = 0x67452301 + init1 = 0xEFCDAB89 + init2 = 0x98BADCFE + init3 = 0x10325476 + init4 = 0xC3D2E1F0 +) + +// digest represents the partial evaluation of a checksum. +struct Digest { +mut: + h []u32 + x []byte + nx int + len u64 +} + +fn (mut d Digest) reset() { + d.x = []byte{len: sha1.chunk} + d.h = []u32{len: (5)} + d.h[0] = u32(sha1.init0) + d.h[1] = u32(sha1.init1) + d.h[2] = u32(sha1.init2) + d.h[3] = u32(sha1.init3) + d.h[4] = u32(sha1.init4) + d.nx = 0 + d.len = 0 +} + +// new returns a new Digest (implementing hash.Hash) computing the SHA1 checksum. +pub fn new() &Digest { + mut d := &Digest{} + d.reset() + return d +} + +// write writes the contents of `p_` to the internal hash representation. +[manualfree] +pub fn (mut d Digest) write(p_ []byte) ?int { + nn := p_.len + unsafe { + mut p := p_ + d.len += u64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx..], p) + d.nx += n + if d.nx == sha1.chunk { + block(mut d, d.x) + d.nx = 0 + } + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len >= sha1.chunk { + n := p.len & ~(sha1.chunk - 1) + block(mut d, p[..n]) + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len > 0 { + d.nx = copy(d.x, p) + } + } + return nn +} + +// sum returns a copy of the generated sum of the bytes in `b_in`. +pub fn (d &Digest) sum(b_in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + mut d0 := *d + hash := d0.checksum() + mut b_out := b_in.clone() + for b in hash { + b_out << b + } + return b_out +} + +// checksum returns the byte checksum of the `Digest`. +fn (mut d Digest) checksum() []byte { + mut len := d.len + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + mut tmp := []byte{len: (64)} + tmp[0] = 0x80 + if int(len) % 64 < 56 { + d.write(tmp[..56 - int(len) % 64]) or { panic(err) } + } else { + d.write(tmp[..64 + 56 - int(len) % 64]) or { panic(err) } + } + // Length in bits. + len <<= 3 + binary.big_endian_put_u64(mut tmp, len) + d.write(tmp[..8]) or { panic(err) } + mut digest := []byte{len: sha1.size} + binary.big_endian_put_u32(mut digest, d.h[0]) + binary.big_endian_put_u32(mut digest[4..], d.h[1]) + binary.big_endian_put_u32(mut digest[8..], d.h[2]) + binary.big_endian_put_u32(mut digest[12..], d.h[3]) + binary.big_endian_put_u32(mut digest[16..], d.h[4]) + return digest +} + +// sum returns the SHA-1 checksum of the bytes passed in `data`. +pub fn sum(data []byte) []byte { + mut d := new() + d.write(data) or { panic(err) } + return d.checksum() +} + +fn block(mut dig Digest, p []byte) { + // For now just use block_generic until we have specific + // architecture optimized versions + block_generic(mut dig, p) +} + +// size returns the size of the checksum in bytes. +pub fn (d &Digest) size() int { + return sha1.size +} + +// block_size returns the block size of the checksum in bytes. +pub fn (d &Digest) block_size() int { + return sha1.block_size +} + +// hexhash returns a hexadecimal SHA1 hash sum `string` of `s`. +pub fn hexhash(s string) string { + return sum(s.bytes()).hex() +} diff --git a/v_windows/v/vlib/crypto/sha1/sha1_test.v b/v_windows/v/vlib/crypto/sha1/sha1_test.v new file mode 100644 index 0000000..b52ea06 --- /dev/null +++ b/v_windows/v/vlib/crypto/sha1/sha1_test.v @@ -0,0 +1,8 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.sha1 + +fn test_crypto_sha1() { + assert sha1.sum('This is a sha1 checksum.'.bytes()).hex() == 'e100d74442faa5dcd59463b808983c810a8eb5a1' +} diff --git a/v_windows/v/vlib/crypto/sha1/sha1block_generic.v b/v_windows/v/vlib/crypto/sha1/sha1block_generic.v new file mode 100644 index 0000000..a0dc92c --- /dev/null +++ b/v_windows/v/vlib/crypto/sha1/sha1block_generic.v @@ -0,0 +1,118 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// This is the generic version with no architecture optimizations. +// In its own file so that an architecture +// optimized verision can be substituted +module sha1 + +import math.bits + +const ( + _k0 = 0x5A827999 + _k1 = 0x6ED9EBA1 + _k2 = 0x8F1BBCDC + _k3 = 0xCA62C1D6 +) + +fn block_generic(mut dig Digest, p_ []byte) { + unsafe { + mut p := p_ + mut w := []u32{len: (16)} + mut h0 := dig.h[0] + mut h1 := dig.h[1] + mut h2 := dig.h[2] + mut h3 := dig.h[3] + mut h4 := dig.h[4] + for p.len >= chunk { + // Can interlace the computation of w with the + // rounds below if needed for speed. + for i in 0 .. 16 { + j := i * 4 + w[i] = u32(p[j] << 24) | u32(p[j + 1] << 16) | u32(p[j + 2] << 8) | u32(p[j + 3]) + } + mut a := h0 + mut b := h1 + mut c := h2 + mut d := h3 + mut e := h4 + // Each of the four 20-iteration rounds + // differs only in the computation of f and + // the choice of K (_k0, _k1, etc). + mut i := 0 + for i < 16 { + f := b & c | (~b) & d + t := bits.rotate_left_32(a, 5) + f + e + w[i & 0xf] + u32(sha1._k0) + e = d + d = c + c = bits.rotate_left_32(b, 30) + b = a + a = t + i++ + } + for i < 20 { + tmp := w[(i - 3) & 0xf] ^ w[(i - 8) & 0xf] ^ w[(i - 14) & 0xf] ^ w[i & 0xf] + w[i & 0xf] = (tmp << 1) | (tmp >> (32 - 1)) + f := b & c | (~b) & d + t := bits.rotate_left_32(a, 5) + f + e + w[i & 0xf] + u32(sha1._k0) + e = d + d = c + c = bits.rotate_left_32(b, 30) + b = a + a = t + i++ + } + for i < 40 { + tmp := w[(i - 3) & 0xf] ^ w[(i - 8) & 0xf] ^ w[(i - 14) & 0xf] ^ w[i & 0xf] + w[i & 0xf] = (tmp << 1) | (tmp >> (32 - 1)) + f := b ^ c ^ d + t := bits.rotate_left_32(a, 5) + f + e + w[i & 0xf] + u32(sha1._k1) + e = d + d = c + c = bits.rotate_left_32(b, 30) + b = a + a = t + i++ + } + for i < 60 { + tmp := w[(i - 3) & 0xf] ^ w[(i - 8) & 0xf] ^ w[(i - 14) & 0xf] ^ w[i & 0xf] + w[i & 0xf] = (tmp << 1) | (tmp >> (32 - 1)) + f := ((b | c) & d) | (b & c) + t := bits.rotate_left_32(a, 5) + f + e + w[i & 0xf] + u32(sha1._k2) + e = d + d = c + c = bits.rotate_left_32(b, 30) + b = a + a = t + i++ + } + for i < 80 { + tmp := w[(i - 3) & 0xf] ^ w[(i - 8) & 0xf] ^ w[(i - 14) & 0xf] ^ w[i & 0xf] + w[i & 0xf] = (tmp << 1) | (tmp >> (32 - 1)) + f := b ^ c ^ d + t := bits.rotate_left_32(a, 5) + f + e + w[i & 0xf] + u32(sha1._k3) + e = d + d = c + c = bits.rotate_left_32(b, 30) + b = a + a = t + i++ + } + h0 += a + h1 += b + h2 += c + h3 += d + h4 += e + if chunk >= p.len { + p = [] + } else { + p = p[chunk..] + } + } + dig.h[0] = h0 + dig.h[1] = h1 + dig.h[2] = h2 + dig.h[3] = h3 + dig.h[4] = h4 + } +} diff --git a/v_windows/v/vlib/crypto/sha256/sha256.v b/v_windows/v/vlib/crypto/sha256/sha256.v new file mode 100644 index 0000000..106df94 --- /dev/null +++ b/v_windows/v/vlib/crypto/sha256/sha256.v @@ -0,0 +1,226 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package sha256 implements the SHA224 and SHA256 hash algorithms as defined +// in FIPS 180-4. +// Based off: https://github.com/golang/go/tree/master/src/crypto/sha256 +// Last commit: https://github.com/golang/go/commit/3ce865d7a0b88714cc433454ae2370a105210c01 +module sha256 + +import encoding.binary + +pub const ( + // The size of a SHA256 checksum in bytes. + size = 32 + // The size of a SHA224 checksum in bytes. + size224 = 28 + // The blocksize of SHA256 and SHA224 in bytes. + block_size = 64 +) + +const ( + chunk = 64 + init0 = 0x6A09E667 + init1 = 0xBB67AE85 + init2 = 0x3C6EF372 + init3 = 0xA54FF53A + init4 = 0x510E527F + init5 = 0x9B05688C + init6 = 0x1F83D9AB + init7 = 0x5BE0CD19 + init0_224 = 0xC1059ED8 + init1_224 = 0x367CD507 + init2_224 = 0x3070DD17 + init3_224 = 0xF70E5939 + init4_224 = 0xFFC00B31 + init5_224 = 0x68581511 + init6_224 = 0x64F98FA7 + init7_224 = 0xBEFA4FA4 +) + +// digest represents the partial evaluation of a checksum. +struct Digest { +mut: + h []u32 + x []byte + nx int + len u64 + is224 bool // mark if this digest is SHA-224 +} + +fn (mut d Digest) reset() { + d.h = []u32{len: (8)} + d.x = []byte{len: sha256.chunk} + if !d.is224 { + d.h[0] = u32(sha256.init0) + d.h[1] = u32(sha256.init1) + d.h[2] = u32(sha256.init2) + d.h[3] = u32(sha256.init3) + d.h[4] = u32(sha256.init4) + d.h[5] = u32(sha256.init5) + d.h[6] = u32(sha256.init6) + d.h[7] = u32(sha256.init7) + } else { + d.h[0] = u32(sha256.init0_224) + d.h[1] = u32(sha256.init1_224) + d.h[2] = u32(sha256.init2_224) + d.h[3] = u32(sha256.init3_224) + d.h[4] = u32(sha256.init4_224) + d.h[5] = u32(sha256.init5_224) + d.h[6] = u32(sha256.init6_224) + d.h[7] = u32(sha256.init7_224) + } + d.nx = 0 + d.len = 0 +} + +// new returns a new Digest (implementing hash.Hash) computing the SHA256 checksum. +pub fn new() &Digest { + mut d := &Digest{} + d.reset() + return d +} + +// new224 returns a new Digest (implementing hash.Hash) computing the SHA224 checksum. +pub fn new224() &Digest { + mut d := &Digest{} + d.is224 = true + d.reset() + return d +} + +// write writes the contents of `p_` to the internal hash representation. +fn (mut d Digest) write(p_ []byte) ?int { + unsafe { + mut p := p_ + nn := p.len + d.len += u64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx..], p) + d.nx += n + if d.nx == sha256.chunk { + block(mut d, d.x) + d.nx = 0 + } + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len >= sha256.chunk { + n := p.len & ~(sha256.chunk - 1) + block(mut d, p[..n]) + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len > 0 { + d.nx = copy(d.x, p) + } + return nn + } +} + +pub fn (d &Digest) sum(b_in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + mut d0 := *d + hash := d0.checksum() + mut b_out := b_in.clone() + if d0.is224 { + for b in hash[..sha256.size224] { + b_out << b + } + } else { + for b in hash { + b_out << b + } + } + return b_out +} + +fn (mut d Digest) checksum() []byte { + mut len := d.len + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + mut tmp := []byte{len: (64)} + tmp[0] = 0x80 + if int(len) % 64 < 56 { + d.write(tmp[..56 - int(len) % 64]) or { panic(err) } + } else { + d.write(tmp[..64 + 56 - int(len) % 64]) or { panic(err) } + } + // Length in bits. + len <<= u64(3) + binary.big_endian_put_u64(mut tmp, len) + d.write(tmp[..8]) or { panic(err) } + if d.nx != 0 { + panic('d.nx != 0') + } + mut digest := []byte{len: sha256.size} + binary.big_endian_put_u32(mut digest, d.h[0]) + binary.big_endian_put_u32(mut digest[4..], d.h[1]) + binary.big_endian_put_u32(mut digest[8..], d.h[2]) + binary.big_endian_put_u32(mut digest[12..], d.h[3]) + binary.big_endian_put_u32(mut digest[16..], d.h[4]) + binary.big_endian_put_u32(mut digest[20..], d.h[5]) + binary.big_endian_put_u32(mut digest[24..], d.h[6]) + if !d.is224 { + binary.big_endian_put_u32(mut digest[28..], d.h[7]) + } + return digest +} + +// sum returns the SHA256 checksum of the bytes in `data`. +// Example: assert sha256.sum('V'.bytes()).len > 0 == true +pub fn sum(data []byte) []byte { + return sum256(data) +} + +// sum256 returns the SHA256 checksum of the data. +pub fn sum256(data []byte) []byte { + mut d := new() + d.write(data) or { panic(err) } + return d.checksum() +} + +// sum224 returns the SHA224 checksum of the data. +pub fn sum224(data []byte) []byte { + mut d := new224() + d.write(data) or { panic(err) } + sum := d.checksum() + sum224 := []byte{len: sha256.size224} + copy(sum224, sum[..sha256.size224]) + return sum224 +} + +fn block(mut dig Digest, p []byte) { + // For now just use block_generic until we have specific + // architecture optimized versions + block_generic(mut dig, p) +} + +// size returns the size of the checksum in bytes. +pub fn (d &Digest) size() int { + if !d.is224 { + return sha256.size + } + return sha256.size224 +} + +// block_size returns the block size of the checksum in bytes. +pub fn (d &Digest) block_size() int { + return sha256.block_size +} + +// hexhash returns a hexadecimal SHA256 hash sum `string` of `s`. +// Example: assert sha256.hexhash('V') == 'de5a6f78116eca62d7fc5ce159d23ae6b889b365a1739ad2cf36f925a140d0cc' +pub fn hexhash(s string) string { + return sum256(s.bytes()).hex() +} + +// hexhash_224 returns a hexadecimal SHA224 hash sum `string` of `s`. +pub fn hexhash_224(s string) string { + return sum224(s.bytes()).hex() +} diff --git a/v_windows/v/vlib/crypto/sha256/sha256_test.v b/v_windows/v/vlib/crypto/sha256/sha256_test.v new file mode 100644 index 0000000..5aeacdc --- /dev/null +++ b/v_windows/v/vlib/crypto/sha256/sha256_test.v @@ -0,0 +1,16 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.sha256 + +fn test_crypto_sha256() { + assert sha256.sum('This is a sha256 checksum.'.bytes()).hex() == 'dc7163299659529eae29683eb1ffec50d6c8fc7275ecb10c145fde0e125b8727' +} + +fn test_crypto_sha256_writer() { + mut digest := sha256.new() + digest.write('This is a'.bytes()) or { assert false } + digest.write(' sha256 checksum.'.bytes()) or { assert false } + sum := digest.sum([]) + assert sum.hex() == 'dc7163299659529eae29683eb1ffec50d6c8fc7275ecb10c145fde0e125b8727' +} diff --git a/v_windows/v/vlib/crypto/sha256/sha256block_generic.v b/v_windows/v/vlib/crypto/sha256/sha256block_generic.v new file mode 100644 index 0000000..e3989cc --- /dev/null +++ b/v_windows/v/vlib/crypto/sha256/sha256block_generic.v @@ -0,0 +1,154 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// SHA256 block step. +// This is the generic version with no architecture optimizations. +// In its own file so that an architecture +// optimized verision can be substituted +module sha256 + +import math.bits + +const ( + _k = [ + 0x428a2f98, + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, + ] +) + +fn block_generic(mut dig Digest, p_ []byte) { + unsafe { + mut p := p_ + mut w := []u32{len: (64)} + mut h0 := dig.h[0] + mut h1 := dig.h[1] + mut h2 := dig.h[2] + mut h3 := dig.h[3] + mut h4 := dig.h[4] + mut h5 := dig.h[5] + mut h6 := dig.h[6] + mut h7 := dig.h[7] + for p.len >= chunk { + // Can interlace the computation of w with the + // rounds below if needed for speed. + for i in 0 .. 16 { + j := i * 4 + w[i] = u32(p[j] << 24) | u32(p[j + 1] << 16) | u32(p[j + 2] << 8) | u32(p[j + 3]) + } + for i := 16; i < 64; i++ { + v1 := w[i - 2] + t1 := (bits.rotate_left_32(v1, -17)) ^ (bits.rotate_left_32(v1, -19)) ^ (v1 >> 10) + v2 := w[i - 15] + t2 := (bits.rotate_left_32(v2, -7)) ^ (bits.rotate_left_32(v2, -18)) ^ (v2 >> 3) + w[i] = t1 + w[i - 7] + t2 + w[i - 16] + } + mut a := h0 + mut b := h1 + mut c := h2 + mut d := h3 + mut e := h4 + mut f := h5 + mut g := h6 + mut h := h7 + for i in 0 .. 64 { + t1 := h + + ((bits.rotate_left_32(e, -6)) ^ (bits.rotate_left_32(e, -11)) ^ (bits.rotate_left_32(e, -25))) + + ((e & f) ^ (~e & g)) + u32(sha256._k[i]) + w[i] + t2 := + ((bits.rotate_left_32(a, -2)) ^ (bits.rotate_left_32(a, -13)) ^ (bits.rotate_left_32(a, -22))) + + ((a & b) ^ (a & c) ^ (b & c)) + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + } + h0 += a + h1 += b + h2 += c + h3 += d + h4 += e + h5 += f + h6 += g + h7 += h + if chunk >= p.len { + p = [] + } else { + p = p[chunk..] + } + } + dig.h[0] = h0 + dig.h[1] = h1 + dig.h[2] = h2 + dig.h[3] = h3 + dig.h[4] = h4 + dig.h[5] = h5 + dig.h[6] = h6 + dig.h[7] = h7 + } +} diff --git a/v_windows/v/vlib/crypto/sha512/sha512.v b/v_windows/v/vlib/crypto/sha512/sha512.v new file mode 100644 index 0000000..dd5d3a9 --- /dev/null +++ b/v_windows/v/vlib/crypto/sha512/sha512.v @@ -0,0 +1,324 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Package sha512 implements the SHA-384, SHA-512, SHA-512/224, and SHA-512/256 +// hash algorithms as defined in FIPS 180-4. +// Based off: https://github.com/golang/go/tree/master/src/crypto/sha512 +// Last commit: https://github.com/golang/go/commit/3ce865d7a0b88714cc433454ae2370a105210c01 +module sha512 + +import crypto +import encoding.binary + +pub const ( + // size is the size, in bytes, of a SHA-512 checksum. + size = 64 + // size224 is the size, in bytes, of a SHA-512/224 checksum. + size224 = 28 + // size256 is the size, in bytes, of a SHA-512/256 checksum. + size256 = 32 + // size384 is the size, in bytes, of a SHA-384 checksum. + size384 = 48 + // block_size is the block size, in bytes, of the SHA-512/224, + // SHA-512/256, SHA-384 and SHA-512 hash functions. + block_size = 128 +) + +const ( + chunk = 128 + init0 = u64(0x6a09e667f3bcc908) + init1 = u64(0xbb67ae8584caa73b) + init2 = u64(0x3c6ef372fe94f82b) + init3 = u64(0xa54ff53a5f1d36f1) + init4 = u64(0x510e527fade682d1) + init5 = u64(0x9b05688c2b3e6c1f) + init6 = u64(0x1f83d9abfb41bd6b) + init7 = u64(0x5be0cd19137e2179) + init0_224 = u64(0x8c3d37c819544da2) + init1_224 = u64(0x73e1996689dcd4d6) + init2_224 = u64(0x1dfab7ae32ff9c82) + init3_224 = u64(0x679dd514582f9fcf) + init4_224 = u64(0x0f6d2b697bd44da8) + init5_224 = u64(0x77e36f7304c48942) + init6_224 = u64(0x3f9d85a86a1d36c8) + init7_224 = u64(0x1112e6ad91d692a1) + init0_256 = u64(0x22312194fc2bf72c) + init1_256 = u64(0x9f555fa3c84c64c2) + init2_256 = u64(0x2393b86b6f53b151) + init3_256 = u64(0x963877195940eabd) + init4_256 = u64(0x96283ee2a88effe3) + init5_256 = u64(0xbe5e1e2553863992) + init6_256 = u64(0x2b0199fc2c85b8aa) + init7_256 = u64(0x0eb72ddc81c52ca2) + init0_384 = u64(0xcbbb9d5dc1059ed8) + init1_384 = u64(0x629a292a367cd507) + init2_384 = u64(0x9159015a3070dd17) + init3_384 = u64(0x152fecd8f70e5939) + init4_384 = u64(0x67332667ffc00b31) + init5_384 = u64(0x8eb44a8768581511) + init6_384 = u64(0xdb0c2e0d64f98fa7) + init7_384 = u64(0x47b5481dbefa4fa4) +) + +// Digest represents the partial evaluation of a checksum. +struct Digest { +mut: + h []u64 + x []byte + nx int + len u64 + function crypto.Hash +} + +fn (mut d Digest) reset() { + d.h = []u64{len: (8)} + d.x = []byte{len: sha512.chunk} + match d.function { + .sha384 { + d.h[0] = sha512.init0_384 + d.h[1] = sha512.init1_384 + d.h[2] = sha512.init2_384 + d.h[3] = sha512.init3_384 + d.h[4] = sha512.init4_384 + d.h[5] = sha512.init5_384 + d.h[6] = sha512.init6_384 + d.h[7] = sha512.init7_384 + } + .sha512_224 { + d.h[0] = sha512.init0_224 + d.h[1] = sha512.init1_224 + d.h[2] = sha512.init2_224 + d.h[3] = sha512.init3_224 + d.h[4] = sha512.init4_224 + d.h[5] = sha512.init5_224 + d.h[6] = sha512.init6_224 + d.h[7] = sha512.init7_224 + } + .sha512_256 { + d.h[0] = sha512.init0_256 + d.h[1] = sha512.init1_256 + d.h[2] = sha512.init2_256 + d.h[3] = sha512.init3_256 + d.h[4] = sha512.init4_256 + d.h[5] = sha512.init5_256 + d.h[6] = sha512.init6_256 + d.h[7] = sha512.init7_256 + } + else { + d.h[0] = sha512.init0 + d.h[1] = sha512.init1 + d.h[2] = sha512.init2 + d.h[3] = sha512.init3 + d.h[4] = sha512.init4 + d.h[5] = sha512.init5 + d.h[6] = sha512.init6 + d.h[7] = sha512.init7 + } + } + d.nx = 0 + d.len = 0 +} + +// internal +fn new_digest(hash crypto.Hash) &Digest { + mut d := &Digest{ + function: hash + } + d.reset() + return d +} + +// new returns a new Digest (implementing hash.Hash) computing the SHA-512 checksum. +pub fn new() &Digest { + return new_digest(.sha512) +} + +// new512_224 returns a new Digest (implementing hash.Hash) computing the SHA-512/224 checksum. +fn new512_224() &Digest { + return new_digest(.sha512_224) +} + +// new512_256 returns a new Digest (implementing hash.Hash) computing the SHA-512/256 checksum. +fn new512_256() &Digest { + return new_digest(.sha512_256) +} + +// new384 returns a new Digest (implementing hash.Hash) computing the SHA-384 checksum. +fn new384() &Digest { + return new_digest(.sha384) +} + +// write writes the contents of `p_` to the internal hash representation. +fn (mut d Digest) write(p_ []byte) ?int { + unsafe { + mut p := p_ + nn := p.len + d.len += u64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx..], p) + d.nx += n + if d.nx == sha512.chunk { + block(mut d, d.x) + d.nx = 0 + } + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len >= sha512.chunk { + n := p.len & ~(sha512.chunk - 1) + block(mut d, p[..n]) + if n >= p.len { + p = [] + } else { + p = p[n..] + } + } + if p.len > 0 { + d.nx = copy(d.x, p) + } + return nn + } +} + +fn (d &Digest) sum(b_in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + mut d0 := *d + hash := d0.checksum() + mut b_out := b_in.clone() + match d0.function { + .sha384 { + for b in hash[..sha512.size384] { + b_out << b + } + } + .sha512_224 { + for b in hash[..sha512.size224] { + b_out << b + } + } + .sha512_256 { + for b in hash[..sha512.size256] { + b_out << b + } + } + else { + for b in hash { + b_out << b + } + } + } + return b_out +} + +fn (mut d Digest) checksum() []byte { + // Padding. Add a 1 bit and 0 bits until 112 bytes mod 128. + mut len := d.len + mut tmp := []byte{len: (128)} + tmp[0] = 0x80 + if int(len) % 128 < 112 { + d.write(tmp[..112 - int(len) % 128]) or { panic(err) } + } else { + d.write(tmp[..128 + 112 - int(len) % 128]) or { panic(err) } + } + // Length in bits. + len <<= u64(3) + binary.big_endian_put_u64(mut tmp, u64(0)) // upper 64 bits are always zero, because len variable has type u64 + binary.big_endian_put_u64(mut tmp[8..], len) + d.write(tmp[..16]) or { panic(err) } + if d.nx != 0 { + panic('d.nx != 0') + } + mut digest := []byte{len: sha512.size} + binary.big_endian_put_u64(mut digest, d.h[0]) + binary.big_endian_put_u64(mut digest[8..], d.h[1]) + binary.big_endian_put_u64(mut digest[16..], d.h[2]) + binary.big_endian_put_u64(mut digest[24..], d.h[3]) + binary.big_endian_put_u64(mut digest[32..], d.h[4]) + binary.big_endian_put_u64(mut digest[40..], d.h[5]) + if d.function != .sha384 { + binary.big_endian_put_u64(mut digest[48..], d.h[6]) + binary.big_endian_put_u64(mut digest[56..], d.h[7]) + } + return digest +} + +// sum512 returns the SHA512 checksum of the data. +pub fn sum512(data []byte) []byte { + mut d := new_digest(.sha512) + d.write(data) or { panic(err) } + return d.checksum() +} + +// sum384 returns the SHA384 checksum of the data. +pub fn sum384(data []byte) []byte { + mut d := new_digest(.sha384) + d.write(data) or { panic(err) } + sum := d.checksum() + sum384 := []byte{len: sha512.size384} + copy(sum384, sum[..sha512.size384]) + return sum384 +} + +// sum512_224 returns the Sum512/224 checksum of the data. +pub fn sum512_224(data []byte) []byte { + mut d := new_digest(.sha512_224) + d.write(data) or { panic(err) } + sum := d.checksum() + sum224 := []byte{len: sha512.size224} + copy(sum224, sum[..sha512.size224]) + return sum224 +} + +// sum512_256 returns the Sum512/256 checksum of the data. +pub fn sum512_256(data []byte) []byte { + mut d := new_digest(.sha512_256) + d.write(data) or { panic(err) } + sum := d.checksum() + sum256 := []byte{len: sha512.size256} + copy(sum256, sum[..sha512.size256]) + return sum256 +} + +fn block(mut dig Digest, p []byte) { + // For now just use block_generic until we have specific + // architecture optimized versions + block_generic(mut dig, p) +} + +// size returns the size of the checksum in bytes. +pub fn (d &Digest) size() int { + match d.function { + .sha512_224 { return sha512.size224 } + .sha512_256 { return sha512.size256 } + .sha384 { return sha512.size384 } + else { return sha512.size } + } +} + +// block_size returns the block size of the checksum in bytes. +pub fn (d &Digest) block_size() int { + return sha512.block_size +} + +// hexhash returns a hexadecimal SHA512 hash sum `string` of `s`. +pub fn hexhash(s string) string { + return sum512(s.bytes()).hex() +} + +// hexhash_384 returns a hexadecimal SHA384 hash sum `string` of `s`. +pub fn hexhash_384(s string) string { + return sum384(s.bytes()).hex() +} + +// hexhash_512_224 returns a hexadecimal SHA512/224 hash sum `string` of `s`. +pub fn hexhash_512_224(s string) string { + return sum512_224(s.bytes()).hex() +} + +// hexhash_512_256 returns a hexadecimal 512/256 hash sum `string` of `s`. +pub fn hexhash_512_256(s string) string { + return sum512_256(s.bytes()).hex() +} diff --git a/v_windows/v/vlib/crypto/sha512/sha512_test.v b/v_windows/v/vlib/crypto/sha512/sha512_test.v new file mode 100644 index 0000000..50676d9 --- /dev/null +++ b/v_windows/v/vlib/crypto/sha512/sha512_test.v @@ -0,0 +1,8 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import crypto.sha512 + +fn test_crypto_sha512() { + assert sha512.sum512('This is a sha512 checksum.'.bytes()).hex() == '4143e55fcba7e39b20f62a1368e5eb28f64a8859458886117ac66027832e0f9f5263daec688c439d2d0fa07059334668d39e59543039703dbb7e03ec9da7f8d7' +} diff --git a/v_windows/v/vlib/crypto/sha512/sha512block_generic.v b/v_windows/v/vlib/crypto/sha512/sha512block_generic.v new file mode 100644 index 0000000..72a6f5e --- /dev/null +++ b/v_windows/v/vlib/crypto/sha512/sha512block_generic.v @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// SHA512 block step. +// This is the generic version with no architecture optimizations. +// In its own file so that an architecture +// optimized verision can be substituted +module sha512 + +import math.bits + +const ( + _k = [u64(0x428a2f98d728ae22), u64(0x7137449123ef65cd), u64(0xb5c0fbcfec4d3b2f), u64(0xe9b5dba58189dbbc), + u64(0x3956c25bf348b538), u64(0x59f111f1b605d019), u64(0x923f82a4af194f9b), u64(0xab1c5ed5da6d8118), + u64(0xd807aa98a3030242), u64(0x12835b0145706fbe), u64(0x243185be4ee4b28c), u64(0x550c7dc3d5ffb4e2), + u64(0x72be5d74f27b896f), u64(0x80deb1fe3b1696b1), u64(0x9bdc06a725c71235), u64(0xc19bf174cf692694), + u64(0xe49b69c19ef14ad2), u64(0xefbe4786384f25e3), u64(0x0fc19dc68b8cd5b5), u64(0x240ca1cc77ac9c65), + u64(0x2de92c6f592b0275), u64(0x4a7484aa6ea6e483), u64(0x5cb0a9dcbd41fbd4), u64(0x76f988da831153b5), + u64(0x983e5152ee66dfab), u64(0xa831c66d2db43210), u64(0xb00327c898fb213f), u64(0xbf597fc7beef0ee4), + u64(0xc6e00bf33da88fc2), u64(0xd5a79147930aa725), u64(0x06ca6351e003826f), u64(0x142929670a0e6e70), + u64(0x27b70a8546d22ffc), u64(0x2e1b21385c26c926), u64(0x4d2c6dfc5ac42aed), u64(0x53380d139d95b3df), + u64(0x650a73548baf63de), u64(0x766a0abb3c77b2a8), u64(0x81c2c92e47edaee6), u64(0x92722c851482353b), + u64(0xa2bfe8a14cf10364), u64(0xa81a664bbc423001), u64(0xc24b8b70d0f89791), u64(0xc76c51a30654be30), + u64(0xd192e819d6ef5218), u64(0xd69906245565a910), u64(0xf40e35855771202a), u64(0x106aa07032bbd1b8), + u64(0x19a4c116b8d2d0c8), u64(0x1e376c085141ab53), u64(0x2748774cdf8eeb99), u64(0x34b0bcb5e19b48a8), + u64(0x391c0cb3c5c95a63), u64(0x4ed8aa4ae3418acb), u64(0x5b9cca4f7763e373), u64(0x682e6ff3d6b2b8a3), + u64(0x748f82ee5defb2fc), u64(0x78a5636f43172f60), u64(0x84c87814a1f0ab72), u64(0x8cc702081a6439ec), + u64(0x90befffa23631e28), u64(0xa4506cebde82bde9), u64(0xbef9a3f7b2c67915), u64(0xc67178f2e372532b), + u64(0xca273eceea26619c), u64(0xd186b8c721c0c207), u64(0xeada7dd6cde0eb1e), u64(0xf57d4f7fee6ed178), + u64(0x06f067aa72176fba), u64(0x0a637dc5a2c898a6), u64(0x113f9804bef90dae), u64(0x1b710b35131c471b), + u64(0x28db77f523047d84), u64(0x32caab7b40c72493), u64(0x3c9ebe0a15c9bebc), u64(0x431d67c49c100d4c), + u64(0x4cc5d4becb3e42b6), u64(0x597f299cfc657e2a), u64(0x5fcb6fab3ad6faec), u64(0x6c44198c4a475817)] +) + +fn block_generic(mut dig Digest, p_ []byte) { + unsafe { + mut p := p_ + mut w := []u64{len: (80)} + mut h0 := dig.h[0] + mut h1 := dig.h[1] + mut h2 := dig.h[2] + mut h3 := dig.h[3] + mut h4 := dig.h[4] + mut h5 := dig.h[5] + mut h6 := dig.h[6] + mut h7 := dig.h[7] + for p.len >= chunk { + for i in 0 .. 16 { + j := i * 8 + w[i] = (u64(p[j]) << 56) | + (u64(p[j + 1]) << 48) | (u64(p[j + 2]) << 40) | + (u64(p[j + 3]) << 32) | (u64(p[j + 4]) << 24) | + (u64(p[j + 5]) << 16) | (u64(p[j + 6]) << 8) | u64(p[j + 7]) + } + for i := 16; i < 80; i++ { + v1 := w[i - 2] + t1 := bits.rotate_left_64(v1, -19) ^ bits.rotate_left_64(v1, -61) ^ (v1 >> 6) + v2 := w[i - 15] + t2 := bits.rotate_left_64(v2, -1) ^ bits.rotate_left_64(v2, -8) ^ (v2 >> 7) + w[i] = t1 + w[i - 7] + t2 + w[i - 16] + } + mut a := h0 + mut b := h1 + mut c := h2 + mut d := h3 + mut e := h4 + mut f := h5 + mut g := h6 + mut h := h7 + for i in 0 .. 80 { + t1 := h + + (bits.rotate_left_64(e, -14) ^ bits.rotate_left_64(e, -18) ^ bits.rotate_left_64(e, -41)) + + ((e & f) ^ (~e & g)) + _k[i] + w[i] + t2 := (bits.rotate_left_64(a, -28) ^ bits.rotate_left_64(a, -34) ^ bits.rotate_left_64(a, -39)) + + ((a & b) ^ (a & c) ^ (b & c)) + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + } + h0 += a + h1 += b + h2 += c + h3 += d + h4 += e + h5 += f + h6 += g + h7 += h + if chunk >= p.len { + p = [] + } else { + p = p[chunk..] + } + } + dig.h[0] = h0 + dig.h[1] = h1 + dig.h[2] = h2 + dig.h[3] = h3 + dig.h[4] = h4 + dig.h[5] = h5 + dig.h[6] = h6 + dig.h[7] = h7 + } +} diff --git a/v_windows/v/vlib/darwin/darwin.m b/v_windows/v/vlib/darwin/darwin.m new file mode 100644 index 0000000..5cab83a --- /dev/null +++ b/v_windows/v/vlib/darwin/darwin.m @@ -0,0 +1,9 @@ + +///void NSLog(id x); + +#include + +NSString* nsstring2(string s) { + return [ [ NSString alloc ] initWithBytesNoCopy:s.str length:s.len + encoding:NSUTF8StringEncoding freeWhenDone: false]; +} diff --git a/v_windows/v/vlib/darwin/darwin.v b/v_windows/v/vlib/darwin/darwin.v new file mode 100644 index 0000000..b86636e --- /dev/null +++ b/v_windows/v/vlib/darwin/darwin.v @@ -0,0 +1,57 @@ +module darwin + +#include +#include + +#flag -framework Cocoa +#flag -framework Carbon + +struct C.NSString {} + +#include "@VEXEROOT/vlib/darwin/darwin.m" + +fn C.nsstring2(s string) voidptr + +// macOS and iOS helpers +// pub fn nsstring(s string) *C.NSString { +pub fn nsstring(s string) voidptr { + return C.nsstring2(s) + // println('ns $s len=$s.len') + //# return [ [ NSString alloc ] initWithBytesNoCopy:s.str length:s.len + //# encoding:NSUTF8StringEncoding freeWhenDone: false]; + // return 0 + + // ns := C.alloc_NSString() + // return ns.initWithBytesNoCopy(s.str, length: s.len, + // encoding: NSUTF8StringEncoding, freeWhenDone: false) +} + +// returns absolute path to folder where your resources should / will reside +// for .app packages: .../my.app/Contents/Resources +// for cli: .../parent_folder/Resources + +fn C.CFBundleCopyResourcesDirectoryURL(bundle voidptr) &byte +fn C.CFBundleGetMainBundle() voidptr +fn C.CFURLGetFileSystemRepresentation(url &byte, resolve_against_base bool, buffer &byte, buffer_size int) int +fn C.CFRelease(url &byte) + +pub fn resource_path() string { + main_bundle := C.CFBundleGetMainBundle() + resource_dir_url := C.CFBundleCopyResourcesDirectoryURL(main_bundle) + if isnil(resource_dir_url) { + panic('CFBundleCopyResourcesDirectoryURL failed') + } + buffer_size := 4096 + mut buffer := unsafe { malloc_noscan(buffer_size) } + unsafe { + buffer[0] = 0 + } + conv_result := C.CFURLGetFileSystemRepresentation(resource_dir_url, true, buffer, + buffer_size) + if conv_result == 0 { + panic('CFURLGetFileSystemRepresentation failed') + } + result := unsafe { buffer.vstring() } + C.CFRelease(resource_dir_url) + return result +} diff --git a/v_windows/v/vlib/dl/dl.v b/v_windows/v/vlib/dl/dl.v new file mode 100644 index 0000000..04ad16b --- /dev/null +++ b/v_windows/v/vlib/dl/dl.v @@ -0,0 +1,48 @@ +module dl + +pub const ( + version = 1 + dl_ext = get_shared_library_extension() +) + +// get_shared_library_extension returns the platform dependent shared library extension +// i.e. .dll on windows, .so on most unixes, .dylib on macos. +[inline] +pub fn get_shared_library_extension() string { + return $if windows { + '.dll' + } $else $if macos { + '.dylib' + } $else { + '.so' + } +} + +// get_libname returns a library name with the operating system specific extension for +// shared libraries. +[inline] +pub fn get_libname(libname string) string { + return '$libname$dl.dl_ext' +} + +// open_opt - loads the dynamic shared object. +// Unlike open, open_opt return an option. +pub fn open_opt(filename string, flags int) ?voidptr { + shared_object_handle := open(filename, flags) + if shared_object_handle == 0 { + e := dlerror() + return error(e) + } + return shared_object_handle +} + +// sym_opt returns the address of a symbol in a given shared object, if found. +// Unlike sym, sym_opt returns an option. +pub fn sym_opt(shared_object_handle voidptr, symbol string) ?voidptr { + sym_handle := sym(shared_object_handle, symbol) + if sym_handle == 0 { + e := dlerror() + return error(e) + } + return sym_handle +} diff --git a/v_windows/v/vlib/dl/dl_nix.c.v b/v_windows/v/vlib/dl/dl_nix.c.v new file mode 100644 index 0000000..40f63b5 --- /dev/null +++ b/v_windows/v/vlib/dl/dl_nix.c.v @@ -0,0 +1,43 @@ +module dl + +#include + +$if linux { + #flag -ldl +} + +pub const ( + rtld_now = C.RTLD_NOW + rtld_lazy = C.RTLD_LAZY +) + +fn C.dlopen(filename &char, flags int) voidptr + +fn C.dlsym(handle voidptr, symbol &char) voidptr + +fn C.dlclose(handle voidptr) int + +fn C.dlerror() &char + +// open loads the dynamic shared object. +pub fn open(filename string, flags int) voidptr { + return C.dlopen(&char(filename.str), flags) +} + +// close frees a given shared object. +pub fn close(handle voidptr) bool { + return C.dlclose(handle) == 0 +} + +// sym returns an address of a symbol in a given shared object. +pub fn sym(handle voidptr, symbol string) voidptr { + return C.dlsym(handle, &char(symbol.str)) +} + +// dlerror provides a text error diagnostic message for functions in `dl` +// it returns a human-readable string, describing the most recent error +// that occurred from a call to one of the `dl` functions, since the last +// call to dlerror() +pub fn dlerror() string { + return unsafe { cstring_to_vstring(C.dlerror()) } +} diff --git a/v_windows/v/vlib/dl/dl_test.v b/v_windows/v/vlib/dl/dl_test.v new file mode 100644 index 0000000..c4f0824 --- /dev/null +++ b/v_windows/v/vlib/dl/dl_test.v @@ -0,0 +1,46 @@ +import dl + +fn test_dl() { + $if linux { + run_test_invalid_lib_linux() + return + } + $if windows { + run_test_invalid_lib_windows() + run_test_valid_lib_windows() + run_test_invalid_sym_windows() + run_test_valid_sym_windows() + return + } $else { + eprint('currently not implemented on this platform') + } +} + +fn run_test_invalid_lib_linux() { + // ensure a not-existing dl won't be loaded + h := dl.open('not-existing-dynamic-link-library', dl.rtld_now) + assert h == 0 +} + +fn run_test_invalid_lib_windows() { + // ensure a not-existing dl won't be loaded + h := dl.open('not-existing-dynamic-link-library', dl.rtld_now) + assert h == 0 +} + +fn run_test_valid_lib_windows() { + h := dl.open('shell32', dl.rtld_now) + assert h != 0 +} + +fn run_test_invalid_sym_windows() { + h := dl.open('shell32', dl.rtld_now) + proc := dl.sym(h, 'CommandLineToArgvW2') + assert proc == 0 +} + +fn run_test_valid_sym_windows() { + h := dl.open('shell32', dl.rtld_now) + proc := dl.sym(h, 'CommandLineToArgvW') + assert proc != 0 +} diff --git a/v_windows/v/vlib/dl/dl_windows.c.v b/v_windows/v/vlib/dl/dl_windows.c.v new file mode 100644 index 0000000..b6ae2fc --- /dev/null +++ b/v_windows/v/vlib/dl/dl_windows.c.v @@ -0,0 +1,39 @@ +module dl + +pub const ( + rtld_now = 0 + rtld_lazy = 0 +) + +fn C.LoadLibrary(libfilename &u16) voidptr + +fn C.GetProcAddress(handle voidptr, procname &byte) voidptr + +fn C.FreeLibrary(handle voidptr) bool + +// open loads a given module into the address space of the calling process. +pub fn open(filename string, flags int) voidptr { + res := C.LoadLibrary(filename.to_wide()) + return res +} + +// close frees the loaded a given module. +pub fn close(handle voidptr) bool { + return C.FreeLibrary(handle) +} + +// sym returns an address of an exported function or variable from a given module. +pub fn sym(handle voidptr, symbol string) voidptr { + return C.GetProcAddress(handle, symbol.str) +} + +// dlerror provides a text error diagnostic message for functions in `dl` +// it returns a human-readable string, describing the most recent error +// that occurred from a call to one of the `dl` functions, since the last +// call to dlerror() +pub fn dlerror() string { + // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + // Unlike dlerror(), GetLastError returns just an error code, that is function specific. + cerr := int(C.GetLastError()) + return 'error code $cerr' +} diff --git a/v_windows/v/vlib/encoding/base58/alphabet.v b/v_windows/v/vlib/encoding/base58/alphabet.v new file mode 100644 index 0000000..44d4fc3 --- /dev/null +++ b/v_windows/v/vlib/encoding/base58/alphabet.v @@ -0,0 +1,65 @@ +module base58 + +// alphabets is a map of common base58 alphabets +pub const alphabets = init_alphabets() + +// init_alphabet instantiates the preconfigured `Alphabet`s and returns them as `map[string]Alphabet`. +// This is a temporary function. Setting const alphabets to the value returned in this function +// causes a C error right now. +fn init_alphabets() map[string]Alphabet { + return { + 'btc': new_alphabet('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + 'flickr': new_alphabet('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + 'ripple': new_alphabet('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + } +} + +// Alphabet is the series of characters that an input +// will be encoded to and a decode table. +struct Alphabet { +mut: + decode []i8 = []i8{len: 128, init: -1} + encode []byte = []byte{len: 58} +} + +// str returns an Alphabet encode table byte array as a string +pub fn (alphabet Alphabet) str() string { + // i guess i had a brain fart here. Why would I actually use this code?! + // mut str := []byte{} + // for entry in alphabet.encode { + // str << entry + // } + // return str.bytestr() + return alphabet.encode.bytestr() +} + +// new_alphabet instantiates an Alphabet object based on +// the provided characters +pub fn new_alphabet(str string) ?Alphabet { + if str.len != 58 { + return error(@MOD + '.' + @FN + ': string must be 58 characters in length') + } + + mut ret := Alphabet{} + copy(ret.encode, str.bytes()) + + mut distinct := 0 + for i, b in ret.encode { + if ret.decode[b] == -1 { + distinct++ + } + ret.decode[b] = i8(i) + } + + if distinct != 58 { + return error(@MOD + '.' + @FN + ': string must not contain repeating characters') + } + + return ret +} diff --git a/v_windows/v/vlib/encoding/base58/base58.v b/v_windows/v/vlib/encoding/base58/base58.v new file mode 100644 index 0000000..fb2ff72 --- /dev/null +++ b/v_windows/v/vlib/encoding/base58/base58.v @@ -0,0 +1,181 @@ +// algorthim is adapted from https://github.com/mr-tron/base58 under the MIT license + +module base58 + +import math + +// encode_int encodes any integer type to base58 string with Bitcoin alphabet +pub fn encode_int(input int) ?string { + return encode_int_walpha(input, alphabets['btc']) +} + +// encode_int_walpha any integer type to base58 string with custom alphabet +pub fn encode_int_walpha(input int, alphabet Alphabet) ?string { + if input <= 0 { + return error(@MOD + '.' + @FN + ': input must be greater than zero') + } + + mut buffer := []byte{} + + mut i := input + for i > 0 { + remainder := i % 58 + buffer << alphabet.encode[i8(remainder)] + // This needs to be casted so byte inputs can + // be used. i8 because remainder will never be + // over 58. + i = i / 58 + } + + return buffer.reverse().bytestr() +} + +// encode encodes byte array to base58 with Bitcoin alphabet +pub fn encode(input string) string { + return encode_walpha(input, alphabets['btc']) +} + +// encode_walpha encodes byte array to base58 with custom aplhabet +pub fn encode_walpha(input string, alphabet Alphabet) string { + if input.len == 0 { + return '' + } + + bin := input.bytes() + mut sz := bin.len + + mut zcount := 0 + for zcount < sz && bin[zcount] == 0 { + zcount++ + } + + // It is crucial to make this as short as possible, especially for + // the usual case of Bitcoin addresses + sz = zcount + (sz - zcount) * 555 / 406 + 1 + // integer simplification of + // ceil(log(256)/log(58)) + + mut out := []byte{len: sz} + mut i := 0 + mut high := 0 + mut carry := u32(0) + + high = sz - 1 + for b in bin { + i = sz - 1 + for carry = u32(b); i > high || carry != 0; i-- { + carry = carry + 256 * u32(out[i]) + out[i] = byte(carry % 58) + carry /= 58 + } + high = 1 + } + + // determine additional "zero-gap" in the buffer, aside from zcount + for i = zcount; i < sz && out[i] == 0; i++ {} + + // now encode the values with actual alphabet in-place + val := out[i - zcount..] + sz = val.len + for i = 0; i < sz; i++ { + out[i] = alphabet.encode[val[i]] + } + + return out[..sz].bytestr() +} + +// decode_int decodes base58 string to an integer with Bitcoin alphabet +pub fn decode_int(input string) ?int { + return decode_int_walpha(input, alphabets['btc']) +} + +// decode_int_walpha decodes base58 string to an integer with custom alphabet +pub fn decode_int_walpha(input string, alphabet Alphabet) ?int { + mut total := 0 // to hold the results + b58 := input.reverse() + for i, ch in b58 { + ch_i := alphabet.encode.bytestr().index_byte(ch) + if ch_i == -1 { + return error(@MOD + '.' + @FN + + ': input string contains values not found in the provided alphabet') + } + + val := ch_i * math.pow(58, i) + + total += int(val) + } + + return total +} + +// decode decodes base58 string using the Bitcoin alphabet +pub fn decode(str string) ?string { + return decode_walpha(str, alphabets['btc']) +} + +// decode_walpha decodes base58 string using custom alphabet +pub fn decode_walpha(str string, alphabet Alphabet) ?string { + if str.len == 0 { + return '' + } + + zero := alphabet.encode[0] + b58sz := str.len + + mut zcount := 0 + for i := 0; i < b58sz && str[i] == zero; i++ { + zcount++ + } + + mut t := u64(0) + mut c := u64(0) + + // the 32-bit algorithm stretches the result up to 2x + mut binu := []byte{len: 2 * ((b58sz * 406 / 555) + 1)} + mut outi := []u32{len: (b58sz + 3) / 4} + + for _, r in str { + if r > 127 { + panic(@MOD + '.' + @FN + + ': high-bit set on invalid digit; outside of ascii range ($r). This should never happen.') + } + if alphabet.decode[r] == -1 { + return error(@MOD + '.' + @FN + ': invalid base58 digit ($r)') + } + + c = u64(alphabet.decode[r]) + + for j := outi.len - 1; j >= 0; j-- { + t = u64(outi[j]) * 58 + c + c = t >> 32 + outi[j] = u32(t & 0xffffffff) + } + } + + // initial mask depend on b58sz, on further loops it always starts at 24 bits + mut mask := (u32(b58sz % 4) * 8) + if mask == 0 { + mask = 32 + } + mask -= 8 + + mut out_len := 0 + for j := 0; j < outi.len; j++ { + for mask < 32 { + binu[out_len] = byte(outi[j] >> mask) + mask -= 8 + out_len++ + } + mask = 24 + } + + // find the most significant byte post-decode, if any + for msb := zcount; msb < binu.len; msb++ { // loop relies on u32 overflow + if binu[msb] > 0 { + return binu[msb - zcount..out_len].bytestr() + } + } + + // it's all zeroes + return binu[..out_len].bytestr() +} diff --git a/v_windows/v/vlib/encoding/base58/base58_test.v b/v_windows/v/vlib/encoding/base58/base58_test.v new file mode 100644 index 0000000..5cbd37b --- /dev/null +++ b/v_windows/v/vlib/encoding/base58/base58_test.v @@ -0,0 +1,89 @@ +module base58 + +fn main() { + test_encode_int() or {} + test_decode_int() or {} + test_encode_string() + test_fails() or {} +} + +fn test_encode_int() ? { + a := 0x24 // should be 'd' in base58 + assert encode_int(a) ? == 'd' + + test_encode_int_walpha() ? +} + +fn test_encode_int_walpha() ? { + // random alphabet + abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + a := 0x24 // should be '_' in base58 with our custom alphabet + assert encode_int_walpha(a, abc) ? == '_' +} + +fn test_decode_int() ? { + a := 'd' + assert decode_int(a) ? == 0x24 + + test_decode_int_walpha() ? +} + +fn test_decode_int_walpha() ? { + abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + a := '_' + assert decode_int_walpha(a, abc) ? == 0x24 +} + +fn test_encode_string() { + // should be 'TtaR6twpTGu8VpY' in base58 and '0P7yfPSL0pQh2L5' with our custom alphabet + a := 'lorem ipsum' + assert encode(a) == 'TtaR6twpTGu8VpY' + + abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + assert encode_walpha(a, abc) == '0P7yfPSL0pQh2L5' +} + +fn test_decode_string() ? { + a := 'TtaR6twpTGu8VpY' + assert decode(a) ? == 'lorem ipsum' + + abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or { + panic(@MOD + '.' + @FN + ': this should never happen') + } + b := '0P7yfPSL0pQh2L5' + assert decode_walpha(b, abc) ? == 'lorem ipsum' +} + +fn test_fails() ? { + a := -238 + b := 0 + if z := encode_int(a) { + return error(@MOD + '.' + @FN + ': expected encode_int to fail, got $z') + } + if z := encode_int(b) { + return error(@MOD + '.' + @FN + ': expected encode_int to fail, got $z') + } + + c := '!' + if z := decode_int(c) { + return error(@MOD + '.' + @FN + ': expected decode_int to fail, got $z') + } + if z := decode(c) { + return error(@MOD + '.' + @FN + ': expected decode to fail, got $z') + } + + // repeating character + if abc := new_alphabet('aaaaafghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') { + return error(@MOD + '.' + @FN + ': expected new_alphabet to fail, got $abc') + } + // more than 58 characters long + if abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUVWXYZ') { + return error(@MOD + '.' + @FN + ': expected new_alphabet to fail, got $abc') + } +} diff --git a/v_windows/v/vlib/encoding/base64/base64.v b/v_windows/v/vlib/encoding/base64/base64.v new file mode 100644 index 0000000..ad06722 --- /dev/null +++ b/v_windows/v/vlib/encoding/base64/base64.v @@ -0,0 +1,308 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Based off: https://github.com/golang/go/blob/master/src/encoding/base64/base64.go +// Last commit: https://github.com/golang/go/commit/9a93baf4d7d13d7d5c67388c93960d78abc8e11e +module base64 + +const ( + index = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]! + ending_table = [0, 2, 1]! + enc_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +) + +union B64_64_datablock { +mut: + data u64 + data_byte [8]byte +} + +union B64_32_datablock { +mut: + data u32 + data_byte [4]byte +} + +// decode decodes the base64 encoded `string` value passed in `data`. +// Please note: If you need to decode many strings repeatedly, take a look at `decode_in_buffer`. +// Example: assert base64.decode('ViBpbiBiYXNlIDY0') == 'V in base 64' +pub fn decode(data string) []byte { + mut size := i64(data.len) * 3 / 4 + if size <= 0 || data.len % 4 != 0 { + return [] + } + size = (size + 3) & ~0x03 // round to the next multiple of 4 (the decoding loop writes multiples of 4 bytes) + unsafe { + buffer := malloc(int(size)) + n := decode_in_buffer(data, buffer) + return buffer.vbytes(n) + } +} + +// decode_str is the string variant of decode +pub fn decode_str(data string) string { + size := data.len * 3 / 4 + if size <= 0 || data.len % 4 != 0 { + return '' + } + unsafe { + buffer := malloc_noscan(size + 1) + buffer[size] = 0 + return tos(buffer, decode_in_buffer(data, buffer)) + } +} + +// encode encodes the `[]byte` value passed in `data` to base64. +// Please note: base64 encoding returns a `string` that is ~ 4/3 larger than the input. +// Please note: If you need to encode many strings repeatedly, take a look at `encode_in_buffer`. +// Example: assert base64.encode('V in base 64') == 'ViBpbiBiYXNlIDY0' +pub fn encode(data []byte) string { + return alloc_and_encode(data.data, data.len) +} + +// encode_str is the string variant of encode +pub fn encode_str(data string) string { + return alloc_and_encode(data.str, data.len) +} + +// alloc_and_encode is a private function that allocates and encodes data into a string +// Used by encode and encode_str +fn alloc_and_encode(src &byte, len int) string { + size := 4 * ((len + 2) / 3) + if size <= 0 { + return '' + } + unsafe { + buffer := malloc_noscan(size + 1) + buffer[size] = 0 + return tos(buffer, encode_from_buffer(buffer, src, len)) + } +} + +// url_decode returns a decoded URL `string` version of +// the a base64 url encoded `string` passed in `data`. +pub fn url_decode(data string) []byte { + mut result := data.replace_each(['-', '+', '_', '/']) + match result.len % 4 { + // Pad with trailing '='s + 2 { result += '==' } // 2 pad chars + 3 { result += '=' } // 1 pad char + else {} // no padding + } + return decode(result) +} + +// url_decode_str is the string variant of url_decode +pub fn url_decode_str(data string) string { + mut result := data.replace_each(['-', '+', '_', '/']) + match result.len % 4 { + // Pad with trailing '='s + 2 { result += '==' } // 2 pad chars + 3 { result += '=' } // 1 pad char + else {} // no padding + } + return decode_str(result) +} + +// url_encode returns a base64 URL encoded `string` version +// of the value passed in `data`. +pub fn url_encode(data []byte) string { + return encode(data).replace_each(['+', '-', '/', '_', '=', '']) +} + +// url_encode_str is the string variant of url_encode +pub fn url_encode_str(data string) string { + return encode_str(data).replace_each(['+', '-', '/', '_', '=', '']) +} + +// assemble64 assembles 8 base64 digits into 6 bytes. +// Each digit comes from the decode map. +// Please note: Invalid base64 digits are not expected and not handled. +fn assemble64(n1 byte, n2 byte, n3 byte, n4 byte, n5 byte, n6 byte, n7 byte, n8 byte) u64 { + return u64(n1) << 58 | u64(n2) << 52 | u64(n3) << 46 | u64(n4) << 40 | u64(n5) << 34 | u64(n6) << 28 | u64(n7) << 22 | u64(n8) << 16 +} + +// assemble32 assembles 4 base64 digits into 3 bytes. +// Each digit comes from the decode map. +// Please note: Invalid base64 digits are not expected and not handled. +fn assemble32(n1 byte, n2 byte, n3 byte, n4 byte) u32 { + return u32(n1) << 26 | u32(n2) << 20 | u32(n3) << 14 | u32(n4) << 8 +} + +// decode_in_buffer decodes the base64 encoded `string` reference passed in `data` into `buffer`. +// decode_in_buffer returns the size of the decoded data in the buffer. +// Please note: The `buffer` should be large enough (i.e. 3/4 of the data.len, or larger) +// to hold the decoded data. +// Please note: This function does NOT allocate new memory, and is thus suitable for handling very large strings. +pub fn decode_in_buffer(data &string, buffer &byte) int { + return decode_from_buffer(buffer, data.str, data.len) +} + +// decode_from_buffer decodes the base64 encoded ASCII bytes from `data` into `buffer`. +// decode_from_buffer returns the size of the decoded data in the buffer. +// Please note: The `buffer` should be large enough (i.e. 3/4 of the data.len, or larger) +// to hold the decoded data. +// Please note: This function does NOT allocate new memory, and is thus suitable for handling very large strings. +pub fn decode_in_buffer_bytes(data []byte, buffer &byte) int { + return decode_from_buffer(buffer, data.data, data.len) +} + +// decode_from_buffer decodes the base64 encoded ASCII bytes from `src` into `dest`. +// decode_from_buffer returns the size of the decoded data in the buffer. +// Please note: The `dest` buffer should be large enough (i.e. 3/4 of the `src_len`, or larger) +// to hold the decoded data. +// Please note: This function does NOT allocate new memory, and is thus suitable for handling very large strings. +// Please note: This function is for internal base64 decoding +fn decode_from_buffer(dest &byte, src &byte, src_len int) int { + if src_len < 4 { + return 0 + } + + mut padding := 0 + if unsafe { src[src_len - 1] == `=` } { + if unsafe { src[src_len - 2] == `=` } { + padding = 2 + } else { + padding = 1 + } + } + + mut d := unsafe { src } + mut b := unsafe { dest } + + unsafe { + mut n_decoded_bytes := 0 // padding bytes are also counted towards this. + mut si := 0 + + mut datablock_64 := B64_64_datablock{ + data: 0 + } + mut datablock_32 := B64_32_datablock{ + data: 0 + } + + for src_len - si >= 8 { + // Converting 8 bytes of input into 6 bytes of output. Storing these in the upper bytes of an u64. + datablock_64.data = assemble64(byte(base64.index[d[si + 0]]), byte(base64.index[d[si + 1]]), + byte(base64.index[d[si + 2]]), byte(base64.index[d[si + 3]]), byte(base64.index[d[ + si + 4]]), byte(base64.index[d[si + 5]]), byte(base64.index[d[si + 6]]), + byte(base64.index[d[si + 7]])) + + // Reading out the individual bytes from the u64. Watch out with endianess. + $if little_endian { + b[n_decoded_bytes + 0] = datablock_64.data_byte[7] + b[n_decoded_bytes + 1] = datablock_64.data_byte[6] + b[n_decoded_bytes + 2] = datablock_64.data_byte[5] + b[n_decoded_bytes + 3] = datablock_64.data_byte[4] + b[n_decoded_bytes + 4] = datablock_64.data_byte[3] + b[n_decoded_bytes + 5] = datablock_64.data_byte[2] + } $else { + b[n_decoded_bytes + 0] = datablock_64.data_byte[0] + b[n_decoded_bytes + 1] = datablock_64.data_byte[1] + b[n_decoded_bytes + 2] = datablock_64.data_byte[2] + b[n_decoded_bytes + 3] = datablock_64.data_byte[3] + b[n_decoded_bytes + 4] = datablock_64.data_byte[4] + b[n_decoded_bytes + 5] = datablock_64.data_byte[5] + } + + n_decoded_bytes += 6 + si += 8 + } + + for src_len - si >= 4 { + datablock_32.data = assemble32(byte(base64.index[d[si + 0]]), byte(base64.index[d[si + 1]]), + byte(base64.index[d[si + 2]]), byte(base64.index[d[si + 3]])) + $if little_endian { + b[n_decoded_bytes + 0] = datablock_32.data_byte[3] + b[n_decoded_bytes + 1] = datablock_32.data_byte[2] + b[n_decoded_bytes + 2] = datablock_32.data_byte[1] + b[n_decoded_bytes + 3] = datablock_32.data_byte[0] + } $else { + b[n_decoded_bytes + 0] = datablock_32.data_byte[0] + b[n_decoded_bytes + 1] = datablock_32.data_byte[1] + b[n_decoded_bytes + 2] = datablock_32.data_byte[2] + b[n_decoded_bytes + 3] = datablock_32.data_byte[3] + } + + n_decoded_bytes += 3 + si += 4 + } + + return n_decoded_bytes - padding + } +} + +// encode_in_buffer base64 encodes the `[]byte` passed in `data` into `buffer`. +// encode_in_buffer returns the size of the encoded data in the buffer. +// Please note: The buffer should be large enough (i.e. 4/3 of the data.len, or larger) to hold the encoded data. +// Please note: The function does NOT allocate new memory, and is suitable for handling very large strings. +pub fn encode_in_buffer(data []byte, buffer &byte) int { + return encode_from_buffer(buffer, data.data, data.len) +} + +// encode_from_buffer will perform encoding from any type of src buffer +// and write the bytes into `dest`. +// Please note: The `dest` buffer should be large enough (i.e. 4/3 of the src_len, or larger) to hold the encoded data. +// Please note: This function is for internal base64 encoding +fn encode_from_buffer(dest &byte, src &byte, src_len int) int { + if src_len == 0 { + return 0 + } + output_length := 4 * ((src_len + 2) / 3) + + mut d := unsafe { src } + mut b := unsafe { dest } + etable := base64.enc_table.str + + mut di := 0 + mut si := 0 + n := (src_len / 3) * 3 + for si < n { + // Convert 3x 8bit source bytes into 4 bytes + unsafe { + val := u32(d[si + 0]) << 16 | u32(d[si + 1]) << 8 | u32(d[si + 2]) + + b[di + 0] = etable[val >> 18 & 0x3F] + b[di + 1] = etable[val >> 12 & 0x3F] + b[di + 2] = etable[val >> 6 & 0x3F] + b[di + 3] = etable[val & 0x3F] + } + si += 3 + di += 4 + } + + remain := src_len - si + if remain == 0 { + return output_length + } + + // Add the remaining small block and padding + unsafe { + mut val := u32(d[si + 0]) << 16 + if remain == 2 { + val |= u32(d[si + 1]) << 8 + } + + b[di + 0] = etable[val >> 18 & 0x3F] + b[di + 1] = etable[val >> 12 & 0x3F] + + match remain { + 2 { + b[di + 2] = etable[val >> 6 & 0x3F] + b[di + 3] = byte(`=`) + } + 1 { + b[di + 2] = byte(`=`) + b[di + 3] = byte(`=`) + } + else { + panic('base64: This case should never occur.') + } + } + } + return output_length +} diff --git a/v_windows/v/vlib/encoding/base64/base64_memory_test.v b/v_windows/v/vlib/encoding/base64/base64_memory_test.v new file mode 100644 index 0000000..be543af --- /dev/null +++ b/v_windows/v/vlib/encoding/base64/base64_memory_test.v @@ -0,0 +1,59 @@ +import encoding.base64 + +fn test_long_encoding() { + repeats := 1000 + input_size := 3000 + + s_original := []byte{len: input_size, init: `a`} + s_encoded := base64.encode(s_original) + s_encoded_bytes := s_encoded.bytes() + s_decoded := base64.decode(s_encoded) + + assert s_encoded.len > s_original.len + assert s_original == s_decoded + + ebuffer := unsafe { malloc(s_encoded.len) } + dbuffer := unsafe { malloc(s_decoded.len) } + defer { + unsafe { free(ebuffer) } + unsafe { free(dbuffer) } + } + // + encoded_size := base64.encode_in_buffer(s_original, ebuffer) + mut encoded_in_buf := []byte{len: encoded_size} + unsafe { C.memcpy(encoded_in_buf.data, ebuffer, encoded_size) } + assert input_size * 4 / 3 == encoded_size + assert encoded_in_buf[0] == `Y` + assert encoded_in_buf[1] == `W` + assert encoded_in_buf[2] == `F` + assert encoded_in_buf[3] == `h` + + assert encoded_in_buf[encoded_size - 4] == `Y` + assert encoded_in_buf[encoded_size - 3] == `W` + assert encoded_in_buf[encoded_size - 2] == `F` + assert encoded_in_buf[encoded_size - 1] == `h` + + assert encoded_in_buf == s_encoded_bytes + + decoded_size := base64.decode_in_buffer(s_encoded, dbuffer) + assert decoded_size == input_size + mut decoded_in_buf := []byte{len: decoded_size} + unsafe { C.memcpy(decoded_in_buf.data, dbuffer, decoded_size) } + assert decoded_in_buf == s_original + + mut s := 0 + for _ in 0 .. repeats { + resultsize := base64.encode_in_buffer(s_original, ebuffer) + s += resultsize + assert resultsize == s_encoded.len + } + + for _ in 0 .. repeats { + resultsize := base64.decode_in_buffer(s_encoded, dbuffer) + s += resultsize + assert resultsize == s_decoded.len + } + + println('Final s: $s') + // assert s == 39147008 +} diff --git a/v_windows/v/vlib/encoding/base64/base64_test.v b/v_windows/v/vlib/encoding/base64/base64_test.v new file mode 100644 index 0000000..8d08de2 --- /dev/null +++ b/v_windows/v/vlib/encoding/base64/base64_test.v @@ -0,0 +1,150 @@ +import encoding.base64 + +struct TestPair { + decoded string + encoded string +} + +const ( + pairs = [ + // RFC 3548 examples + TestPair{'\x14\xfb\x9c\x03\xd9\x7e', 'FPucA9l+'}, + TestPair{'\x14\xfb\x9c\x03\xd9', 'FPucA9k='}, + TestPair{'\x14\xfb\x9c\x03', 'FPucAw=='}, + // RFC 4648 examples + TestPair{'', ''}, + TestPair{'f', 'Zg=='}, + TestPair{'fo', 'Zm8='}, + TestPair{'foo', 'Zm9v'}, + TestPair{'foob', 'Zm9vYg=='}, + TestPair{'fooba', 'Zm9vYmE='}, + TestPair{'foobar', 'Zm9vYmFy'}, + // Wikipedia examples + TestPair{'sure.', 'c3VyZS4='}, + TestPair{'sure', 'c3VyZQ=='}, + TestPair{'sur', 'c3Vy'}, + TestPair{'su', 'c3U='}, + TestPair{'leasure.', 'bGVhc3VyZS4='}, + TestPair{'easure.', 'ZWFzdXJlLg=='}, + TestPair{'asure.', 'YXN1cmUu'}, + TestPair{'sure.', 'c3VyZS4='}, + ] + + man_pair = TestPair{'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.', 'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4='} +) + +fn test_decode() { + assert base64.decode(man_pair.encoded) == man_pair.decoded.bytes() + + // Test for incorrect padding. + assert base64.decode('aGk') == ''.bytes() + assert base64.decode('aGk=') == 'hi'.bytes() + assert base64.decode('aGk==') == ''.bytes() + + for i, p in pairs { + got := base64.decode(p.encoded) + if got != p.decoded.bytes() { + eprintln('pairs[$i]: expected = $p.decoded, got = $got') + assert false + } + } +} + +fn test_decode_str() { + assert base64.decode_str(man_pair.encoded) == man_pair.decoded + + // Test for incorrect padding. + assert base64.decode_str('aGk') == '' + assert base64.decode_str('aGk=') == 'hi' + assert base64.decode_str('aGk==') == '' + + for i, p in pairs { + got := base64.decode_str(p.encoded) + if got != p.decoded { + eprintln('pairs[$i]: expected = $p.decoded, got = $got') + assert false + } + } +} + +fn test_encode() { + assert base64.encode(man_pair.decoded.bytes()) == man_pair.encoded + + for i, p in pairs { + got := base64.encode(p.decoded.bytes()) + if got != p.encoded { + eprintln('pairs[$i]: expected = $p.encoded, got = $got') + assert false + } + } +} + +fn test_encode_str() { + assert base64.encode_str(man_pair.decoded) == man_pair.encoded + + for i, p in pairs { + got := base64.encode_str(p.decoded) + if got != p.encoded { + eprintln('pairs[$i]: expected = $p.encoded, got = $got') + assert false + } + } +} + +fn test_url_encode() { + test := base64.url_encode('Hello Base64Url encoding!'.bytes()) + assert test == 'SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ' +} + +fn test_url_encode_str() { + test := base64.url_encode_str('Hello Base64Url encoding!') + assert test == 'SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ' +} + +fn test_url_decode() { + test := base64.url_decode('SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ') + assert test == 'Hello Base64Url encoding!'.bytes() +} + +fn test_url_decode_str() { + test := base64.url_decode_str('SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ') + assert test == 'Hello Base64Url encoding!' +} + +fn test_encode_null_byte() { + assert base64.encode([byte(`A`), 0, `C`]) == 'QQBD' +} + +fn test_encode_null_byte_str() { + // While this works, bytestr() does a memcpy + s := [byte(`A`), 0, `C`].bytestr() + assert base64.encode_str(s) == 'QQBD' +} + +fn test_decode_null_byte() { + assert base64.decode('QQBD') == [byte(`A`), 0, `C`] +} + +fn test_decode_null_byte_str() { + // While this works, bytestr() does a memcpy + s := [byte(`A`), 0, `C`].bytestr() + assert base64.decode_str('QQBD') == s +} + +fn test_decode_in_buffer_bytes() { + rfc4648_pairs := [ + TestPair{'foob', 'Zm9vYg=='}, + TestPair{'fooba', 'Zm9vYmE='}, + TestPair{'foobar', 'Zm9vYmFy'}, + ] + mut src_dec_buf := []byte{len: 8} + mut src_enc_buf := []byte{len: 8} + mut out_buf := []byte{len: 8} + + for p in rfc4648_pairs { + src_dec_buf = p.decoded.bytes() + src_enc_buf = p.encoded.bytes() + n := base64.decode_in_buffer_bytes(src_enc_buf, out_buf.data) + assert src_dec_buf == out_buf[..n] + } +} diff --git a/v_windows/v/vlib/encoding/binary/binary.v b/v_windows/v/vlib/encoding/binary/binary.v new file mode 100644 index 0000000..d7fe298 --- /dev/null +++ b/v_windows/v/vlib/encoding/binary/binary.v @@ -0,0 +1,100 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module binary + +// Little Endian +[inline] +pub fn little_endian_u16(b []byte) u16 { + _ = b[1] // bounds check + return u16(b[0]) | (u16(b[1]) << u16(8)) +} + +[inline] +pub fn little_endian_put_u16(mut b []byte, v u16) { + _ = b[1] // bounds check + b[0] = byte(v) + b[1] = byte(v >> u16(8)) +} + +[inline] +pub fn little_endian_u32(b []byte) u32 { + _ = b[3] // bounds check + return u32(b[0]) | (u32(b[1]) << u32(8)) | (u32(b[2]) << u32(16)) | (u32(b[3]) << u32(24)) +} + +[inline] +pub fn little_endian_put_u32(mut b []byte, v u32) { + _ = b[3] // bounds check + b[0] = byte(v) + b[1] = byte(v >> u32(8)) + b[2] = byte(v >> u32(16)) + b[3] = byte(v >> u32(24)) +} + +[inline] +pub fn little_endian_u64(b []byte) u64 { + _ = b[7] // bounds check + return u64(b[0]) | (u64(b[1]) << u64(8)) | (u64(b[2]) << u64(16)) | (u64(b[3]) << u64(24)) | (u64(b[4]) << u64(32)) | (u64(b[5]) << u64(40)) | (u64(b[6]) << u64(48)) | (u64(b[7]) << u64(56)) +} + +[inline] +pub fn little_endian_put_u64(mut b []byte, v u64) { + _ = b[7] // bounds check + b[0] = byte(v) + b[1] = byte(v >> u64(8)) + b[2] = byte(v >> u64(16)) + b[3] = byte(v >> u64(24)) + b[4] = byte(v >> u64(32)) + b[5] = byte(v >> u64(40)) + b[6] = byte(v >> u64(48)) + b[7] = byte(v >> u64(56)) +} + +// Big Endian +[inline] +pub fn big_endian_u16(b []byte) u16 { + _ = b[1] // bounds check + return u16(b[1]) | (u16(b[0]) << u16(8)) +} + +[inline] +pub fn big_endian_put_u16(mut b []byte, v u16) { + _ = b[1] // bounds check + b[0] = byte(v >> u16(8)) + b[1] = byte(v) +} + +[inline] +pub fn big_endian_u32(b []byte) u32 { + _ = b[3] // bounds check + return u32(b[3]) | (u32(b[2]) << u32(8)) | (u32(b[1]) << u32(16)) | (u32(b[0]) << u32(24)) +} + +[inline] +pub fn big_endian_put_u32(mut b []byte, v u32) { + _ = b[3] // bounds check + b[0] = byte(v >> u32(24)) + b[1] = byte(v >> u32(16)) + b[2] = byte(v >> u32(8)) + b[3] = byte(v) +} + +[inline] +pub fn big_endian_u64(b []byte) u64 { + _ = b[7] // bounds check + return u64(b[7]) | (u64(b[6]) << u64(8)) | (u64(b[5]) << u64(16)) | (u64(b[4]) << u64(24)) | (u64(b[3]) << u64(32)) | (u64(b[2]) << u64(40)) | (u64(b[1]) << u64(48)) | (u64(b[0]) << u64(56)) +} + +[inline] +pub fn big_endian_put_u64(mut b []byte, v u64) { + _ = b[7] // bounds check + b[0] = byte(v >> u64(56)) + b[1] = byte(v >> u64(48)) + b[2] = byte(v >> u64(40)) + b[3] = byte(v >> u64(32)) + b[4] = byte(v >> u64(24)) + b[5] = byte(v >> u64(16)) + b[6] = byte(v >> u64(8)) + b[7] = byte(v) +} diff --git a/v_windows/v/vlib/encoding/csv/README.md b/v_windows/v/vlib/encoding/csv/README.md new file mode 100644 index 0000000..01f3e4e --- /dev/null +++ b/v_windows/v/vlib/encoding/csv/README.md @@ -0,0 +1,19 @@ +## Reader example + +```v +import encoding.csv + +data := 'x,y\na,b,c\n' +mut parser := csv.new_reader(data) +// read each line +for { + items := parser.read() or { break } + println(items) +} +``` + +It prints: +``` +['x', 'y'] +['a', 'b', 'c'] +``` diff --git a/v_windows/v/vlib/encoding/csv/reader.v b/v_windows/v/vlib/encoding/csv/reader.v new file mode 100644 index 0000000..dafd022 --- /dev/null +++ b/v_windows/v/vlib/encoding/csv/reader.v @@ -0,0 +1,196 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module csv + +// Once interfaces are further along the idea would be to have something similar to +// go's io.reader & bufio.reader rather than reading the whole file into string, this +// would then satisfy that interface. I designed it this way to be easily adapted. +struct ErrCommentIsDelimiter { + msg string = 'encoding.csv: comment cannot be the same as delimiter' + code int +} + +struct ErrInvalidDelimiter { + msg string = 'encoding.csv: invalid delimiter' + code int +} + +struct ErrEndOfFile { + msg string = 'encoding.csv: end of file' + code int +} + +struct ErrInvalidLineEnding { + msg string = 'encoding.csv: could not find any valid line endings' + code int +} + +struct Reader { + // not used yet + // has_header bool + // headings []string + data string +pub mut: + delimiter byte + comment byte + is_mac_pre_osx_le bool + row_pos int +} + +// new_reader initializes a Reader with string data to parse +pub fn new_reader(data string) &Reader { + return &Reader{ + delimiter: `,` + comment: `#` + data: data + } +} + +// read reads a row from the CSV data. +// If successful, the result holds an array of each column's data. +pub fn (mut r Reader) read() ?[]string { + l := r.read_record() ? + return l +} + +// Once we have multi dimensional array +// pub fn (mut r Reader) read_all() ?[][]string { +// mut records := []string{} +// for { +// record := r.read_record() or { +// if err.error == err_eof.error { +// return records +// } else { +// return err +// } +// } +// records << record +// } +// return records +// } +fn (mut r Reader) read_line() ?string { + // last record + if r.row_pos == r.data.len { + return IError(&ErrEndOfFile{}) + } + le := if r.is_mac_pre_osx_le { '\r' } else { '\n' } + mut i := r.data.index_after(le, r.row_pos) + if i == -1 { + if r.row_pos == 0 { + // check for pre osx mac line endings + i = r.data.index_after('\r', r.row_pos) + if i != -1 { + r.is_mac_pre_osx_le = true + } else { + // no valid line endings found + return IError(&ErrInvalidLineEnding{}) + } + } else { + // No line ending on file + i = r.data.len - 1 + } + } + mut line := r.data[r.row_pos..i] + r.row_pos = i + 1 + // normalize win line endings (remove extra \r) + if !r.is_mac_pre_osx_le && (line.len >= 1 && line[line.len - 1] == `\r`) { + line = line[..line.len - 1] + } + return line +} + +fn (mut r Reader) read_record() ?[]string { + if r.delimiter == r.comment { + return IError(&ErrCommentIsDelimiter{}) + } + if !valid_delim(r.delimiter) { + return IError(&ErrInvalidDelimiter{}) + } + mut need_read := true + mut keep_raw := false + mut line := '' + mut fields := []string{} + mut i := -1 + for { + if need_read { + l := r.read_line() ? + if l.len <= 0 { + if keep_raw { + line += '\n' + } + continue + } else if l[0] == r.comment { + if keep_raw { + line += '\n' + l + } + continue + } else { + if keep_raw { + line += '\n' + } + line += l + } + need_read = false + keep_raw = false + } + if line.len == 0 || line[0] != `"` { // not quoted + j := line.index(r.delimiter.ascii_str()) or { + // last + fields << line[..line.len] + break + } + i = j + fields << line[..i] + line = line[i + 1..] + continue + } else { // quoted + mut need_more := true + mut has_double_quotes := false + mut j := 0 + mut n := 1 + for n < line.len { + if line[n] == `"` { + if n == line.len - 1 || line[n + 1] != `"` { + need_more = false + j = n - 1 + break + } else { + has_double_quotes = true + n++ + } + } + n++ + } + if need_more { + need_read = true + keep_raw = true + continue + } + line = line[1..] + if j + 1 == line.len { + // last record + fields << if has_double_quotes { line[..j].replace('""', '"') } else { line[..j] } + break + } + next := line[j + 1] + if next == r.delimiter { + fields << if has_double_quotes { line[..j].replace('""', '"') } else { line[..j] } + if j + 2 == line.len { + line = '' + } else { + line = line[j + 2..] + } + continue + } + } + if i <= -1 && fields.len == 0 { + return IError(&ErrInvalidDelimiter{}) + } + } + return fields +} + +fn valid_delim(b byte) bool { + return b != 0 && b != `"` && b != `\r` && b != `\n` +} diff --git a/v_windows/v/vlib/encoding/csv/reader_test.v b/v_windows/v/vlib/encoding/csv/reader_test.v new file mode 100644 index 0000000..cd54827 --- /dev/null +++ b/v_windows/v/vlib/encoding/csv/reader_test.v @@ -0,0 +1,253 @@ +import encoding.csv + +fn test_encoding_csv_reader() { + data := 'name,email,phone,other\njoe,joe@blow.com,0400000000,test\nsam,sam@likesham.com,0433000000,"test quoted field"\n#chris,chris@nomail.com,94444444,"commented row"\n' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'email' + assert row[2] == 'phone' + assert row[3] == 'other' + } else if row_count == 2 { + assert row[0] == 'joe' + assert row[1] == 'joe@blow.com' + assert row[2] == '0400000000' + assert row[3] == 'test' + } else if row_count == 3 { + assert row[0] == 'sam' + assert row[1] == 'sam@likesham.com' + assert row[2] == '0433000000' + // quoted field + assert row[3] == 'test quoted field' + } + } + assert row_count == 3 +} + +fn test_line_break_lf() { + lf_data := 'name,email\njoe,joe@blow.com\n' + mut csv_reader := csv.new_reader(lf_data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'email' + } else if row_count == 2 { + assert row[0] == 'joe' + assert row[1] == 'joe@blow.com' + } + } + assert row_count == 2 +} + +fn test_line_break_cr() { + cr_data := 'name,email\rjoe,joe@blow.com\r' + mut csv_reader := csv.new_reader(cr_data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'email' + } else if row_count == 2 { + assert row[0] == 'joe' + assert row[1] == 'joe@blow.com' + } + } + assert row_count == 2 +} + +fn test_line_break_crlf() { + crlf_data := 'name,email\r\njoe,joe@blow.com\r\n' + mut csv_reader := csv.new_reader(crlf_data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'email' + } else if row_count == 2 { + assert row[0] == 'joe' + assert row[1] == 'joe@blow.com' + } + } + assert row_count == 2 +} + +fn test_no_line_ending() { + data := 'name,email,phone,other\njoe,joe@blow.com,0400000000,test' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + csv_reader.read() or { break } + row_count++ + } + assert row_count == 2 +} + +fn test_last_field_empty() { + data := '"name","description","value"\n"one","first","1"\n"two","second",\n' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'description' + assert row[2] == 'value' + } else if row_count == 2 { + assert row[0] == 'one' + assert row[1] == 'first' + assert row[2] == '1' + } else if row_count == 3 { + assert row[0] == 'two' + assert row[1] == 'second' + assert row[2] == '' + } + } + assert row_count == 3 +} + +fn test_empty_fields_no_quotes() { + data := '1,2,3,4\n,6,7,8\n9,,11,12\n13,14,,16\n17,18,19,\n' + + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == '1' + assert row[1] == '2' + assert row[2] == '3' + assert row[3] == '4' + } else if row_count == 2 { + assert row[0] == '' + assert row[1] == '6' + assert row[2] == '7' + assert row[3] == '8' + } else if row_count == 3 { + assert row[0] == '9' + assert row[1] == '' + assert row[2] == '11' + assert row[3] == '12' + } else if row_count == 4 { + assert row[0] == '13' + assert row[1] == '14' + assert row[2] == '' + assert row[3] == '16' + } else if row_count == 5 { + assert row[0] == '17' + assert row[1] == '18' + assert row[2] == '19' + assert row[3] == '' + } + } + assert row_count == 5 +} + +fn test_empty_line() { + data := '"name","description","value"\n\n\n"one","first","1"\n\n"two","second",\n' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'description' + assert row[2] == 'value' + } else if row_count == 2 { + assert row[0] == 'one' + assert row[1] == 'first' + assert row[2] == '1' + } else if row_count == 3 { + assert row[0] == 'two' + assert row[1] == 'second' + } + } + assert row_count == 3 +} + +fn test_field_multiple_line() { + data := '"name","multiple + + line","value"\n"one","first","1"\n' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'name' + assert row[1] == 'multiple\n\n line' + assert row[2] == 'value' + } else if row_count == 2 { + assert row[0] == 'one' + assert row[1] == 'first' + assert row[2] == '1' + } + } + assert row_count == 2 +} + +fn test_field_quotes_for_parts() { + data := 'a1,"b1",c1\n"a2",b2,c2\na3,b3,"c3"\na4,b4,c4\n' + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == 'a1' + assert row[1] == 'b1' + assert row[2] == 'c1' + } else if row_count == 2 { + assert row[0] == 'a2' + assert row[1] == 'b2' + assert row[2] == 'c2' + } else if row_count == 3 { + assert row[0] == 'a3' + assert row[1] == 'b3' + assert row[2] == 'c3' + } else if row_count == 4 { + assert row[0] == 'a4' + assert row[1] == 'b4' + assert row[2] == 'c4' + } + } + assert row_count == 4 +} + +fn test_field_double_quotes() { + row1 := '11,"12\n13"\n' + row2 := '21,"2""2""\n23"\n' + row3 := '"3""1""",32\n' + data := row1 + row2 + row3 + mut csv_reader := csv.new_reader(data) + mut row_count := 0 + for { + row := csv_reader.read() or { break } + row_count++ + if row_count == 1 { + assert row[0] == '11' + assert row[1] == '12\n13' + } else if row_count == 2 { + assert row[0] == '21' + assert row[1] == '2"2"\n23' + } else if row_count == 3 { + assert row[0] == '3"1"' + assert row[1] == '32' + } + } + assert row_count == 3 +} diff --git a/v_windows/v/vlib/encoding/csv/writer.v b/v_windows/v/vlib/encoding/csv/writer.v new file mode 100644 index 0000000..735ca20 --- /dev/null +++ b/v_windows/v/vlib/encoding/csv/writer.v @@ -0,0 +1,80 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module csv + +import strings + +struct Writer { +mut: + sb strings.Builder +pub mut: + use_crlf bool + delimiter byte +} + +pub fn new_writer() &Writer { + return &Writer{ + delimiter: `,` + sb: strings.new_builder(200) + } +} + +// write writes a single record +pub fn (mut w Writer) write(record []string) ?bool { + if !valid_delim(w.delimiter) { + return IError(&ErrInvalidDelimiter{}) + } + le := if w.use_crlf { '\r\n' } else { '\n' } + for n, field_ in record { + mut field := field_ + if n > 0 { + w.sb.write_string(w.delimiter.ascii_str()) + } + if !w.field_needs_quotes(field) { + w.sb.write_string(field) + continue + } + w.sb.write_string('"') + for field.len > 0 { + mut i := field.index_any('"\r\n') + if i < 0 { + i = field.len + } + w.sb.write_string(field[..i]) + field = field[i..] + if field.len > 0 { + z := field[0] + match z { + `"` { w.sb.write_string('""') } + `\r`, `\n` { w.sb.write_string(le) } + else {} + } + field = field[1..] + } + } + w.sb.write_string('"') + } + w.sb.write_string(le) + return true +} + +// Once we have multi dimensional array +// pub fn (w &Writer) write_all(records [][]string) { +// for _, record in records { +// w.write(record) +// } +// } +fn (w &Writer) field_needs_quotes(field string) bool { + if field == '' { + return false + } + if field.contains(w.delimiter.ascii_str()) || (field.index_any('"\r\n') != -1) { + return true + } + return false +} + +pub fn (mut w Writer) str() string { + return w.sb.str() +} diff --git a/v_windows/v/vlib/encoding/csv/writer_test.v b/v_windows/v/vlib/encoding/csv/writer_test.v new file mode 100644 index 0000000..92882dd --- /dev/null +++ b/v_windows/v/vlib/encoding/csv/writer_test.v @@ -0,0 +1,11 @@ +import encoding.csv + +fn test_encoding_csv_writer() { + mut csv_writer := csv.new_writer() + + csv_writer.write(['name', 'email', 'phone', 'other']) or {} + csv_writer.write(['joe', 'joe@blow.com', '0400000000', 'test']) or {} + csv_writer.write(['sam', 'sam@likesham.com', '0433000000', 'needs, quoting']) or {} + + assert csv_writer.str() == 'name,email,phone,other\njoe,joe@blow.com,0400000000,test\nsam,sam@likesham.com,0433000000,"needs, quoting"\n' +} diff --git a/v_windows/v/vlib/encoding/hex/hex.v b/v_windows/v/vlib/encoding/hex/hex.v new file mode 100644 index 0000000..99387f1 --- /dev/null +++ b/v_windows/v/vlib/encoding/hex/hex.v @@ -0,0 +1,62 @@ +module hex + +import strings + +// decode converts a hex string into an array of bytes. The expected +// input format is 2 ASCII characters for each output byte. If the provided +// string length is not a multiple of 2, an implicit `0` is prepended to it. +pub fn decode(s string) ?[]byte { + mut hex_str := s + if hex_str.len >= 2 { + if s[0] == `0` && (s[1] == `x` || s[1] == `X`) { + hex_str = s[2..] + } + } + if hex_str.len == 0 { + return []byte{} + } else if hex_str.len == 1 { + return [char2nibble(hex_str[0]) ?] + } else if hex_str.len == 2 { + n1 := char2nibble(hex_str[0]) ? + n0 := char2nibble(hex_str[1]) ? + return [(n1 << 4) | n0] + } + // calculate the first byte depending on if hex_str.len is odd + mut val := char2nibble(hex_str[0]) ? + if hex_str.len & 1 == 0 { + val = (val << 4) | char2nibble(hex_str[1]) ? + } + // set cap to hex_str.len/2 rounded up + mut bytes := []byte{len: 1, cap: (hex_str.len + 1) >> 1, init: val} + // iterate over every 2 bytes + // the start index depends on if hex_str.len is odd + for i := 2 - (hex_str.len & 1); i < hex_str.len; i += 2 { + n1 := char2nibble(hex_str[i]) ? + n0 := char2nibble(hex_str[i + 1]) ? + bytes << (n1 << 4) | n0 + } + return bytes +} + +// encode converts an array of bytes into a string of ASCII hex bytes. The +// output will always be a string with length a multiple of 2. +[manualfree] +pub fn encode(bytes []byte) string { + mut sb := strings.new_builder(bytes.len << 1) + for b in bytes { + sb.write_string(b.hex()) + } + res := sb.str() + unsafe { sb.free() } + return res +} + +// char2nibble converts an ASCII hex character to it's hex value +fn char2nibble(b byte) ?byte { + match b { + `0`...`9` { return b - byte(`0`) } + `A`...`F` { return b - byte(`A`) + 10 } + `a`...`f` { return b - byte(`a`) + 10 } + else { return error('invalid hex char $b.ascii_str()') } + } +} diff --git a/v_windows/v/vlib/encoding/hex/hex_test.v b/v_windows/v/vlib/encoding/hex/hex_test.v new file mode 100644 index 0000000..62501e9 --- /dev/null +++ b/v_windows/v/vlib/encoding/hex/hex_test.v @@ -0,0 +1,54 @@ +module hex + +fn test_decode() ? { + assert decode('') ? == [] + assert decode('0') ? == [byte(0x0)] + assert decode('f') ? == [byte(0xf)] + assert decode('0f') ? == [byte(0x0f)] + assert decode('ff') ? == [byte(0xff)] + assert decode('123') ? == [byte(0x1), 0x23] + assert decode('1234') ? == [byte(0x12), 0x34] + assert decode('12345') ? == [byte(0x1), 0x23, 0x45] + assert decode('0123456789abcdef') ? == [byte(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] + assert decode('123456789ABCDEF') ? == [byte(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] +} + +fn test_decode_fails() ? { + if x := decode('foo') { + return error('expected decode to fail, got $x') + } + if x := decode('g') { + return error('expected decode to fail, got $x') + } + if x := decode('000000000g') { + return error('expected decode to fail, got $x') + } + if x := decode('_') { + return error('expected decode to fail, got $x') + } + if x := decode('!') { + return error('expected decode to fail, got $x') + } +} + +fn test_encode() ? { + assert encode(decode('') ?) == '' + assert encode(decode('0') ?) == '00' + assert encode(decode('f') ?) == '0f' + assert encode(decode('0f') ?) == '0f' + assert encode(decode('ff') ?) == 'ff' + assert encode(decode('123') ?) == '0123' + assert encode(decode('1234') ?) == '1234' + assert encode(decode('12345') ?) == '012345' + assert encode(decode('abcdef') ?) == 'abcdef' + assert encode(decode('ABCDEF') ?) == 'abcdef' +} + +fn test_decode_0x() ? { + assert decode('0x') ? == [] + assert decode('0x0') ? == [byte(0x0)] + assert decode('0X1234') ? == [byte(0x12), 0x34] + assert decode('0x12345') ? == [byte(0x1), 0x23, 0x45] + assert decode('0x0123456789abcdef') ? == [byte(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] + assert decode('0X123456789ABCDEF') ? == [byte(0x01), 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef] +} diff --git a/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width.v b/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width.v new file mode 100644 index 0000000..d1ac547 --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width.v @@ -0,0 +1,1204 @@ +// Copyright (c) 2021 Takahiro Yaota, a.k.a. zakuro. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module east_asian + +import encoding.utf8 + +// EastAsianWidthType represents East_Asian_Width informative prorperty +pub enum EastAsianWidthProperty { + full + half + wide + narrow + ambiguous + neutral +} + +// display_width return the display width as number of unicode chars from a string. +pub fn display_width(s string, ambiguous_width int) int { + mut i, mut n := 0, 0 + for i < s.len { + c_len := utf8_char_len(s[i]) + n += match east_asian_width_property_at(s, i) { + .ambiguous { ambiguous_width } + .full, .wide { int(2) } + else { int(1) } + } + i += c_len + } + return n +} + +// width_property_at returns the East Asian Width properties at string[index] +pub fn east_asian_width_property_at(s string, index int) EastAsianWidthProperty { + codepoint := utf8.get_uchar(s, index) + mut left, mut right := 0, east_asian.east_asian_width_data.len - 1 + for left <= right { + middle := left + ((right - left) / 2) + entry := east_asian.east_asian_width_data[middle] + if codepoint < entry.point { + right = middle - 1 + continue + } + + last := entry.point + entry.len + if codepoint > last { + left = middle + 1 + continue + } + + return entry.property + } + return .neutral +} + +struct EAWEntry { + property EastAsianWidthProperty + point int + len int +} + +// EastAsianWidth-13.0.0.txt +const ( + east_asian_width_data = [ + EAWEntry{.neutral, 0x0000, 32}, + EAWEntry{.narrow, 0x0020, 95}, + EAWEntry{.neutral, 0x007f, 34}, + EAWEntry{.ambiguous, 0x00a1, 1}, + EAWEntry{.narrow, 0x00a2, 2}, + EAWEntry{.ambiguous, 0x00a4, 1}, + EAWEntry{.narrow, 0x00a5, 2}, + EAWEntry{.ambiguous, 0x00a7, 2}, + EAWEntry{.neutral, 0x00a9, 1}, + EAWEntry{.ambiguous, 0x00aa, 1}, + EAWEntry{.neutral, 0x00ab, 1}, + EAWEntry{.narrow, 0x00ac, 1}, + EAWEntry{.ambiguous, 0x00ad, 2}, + EAWEntry{.narrow, 0x00af, 1}, + EAWEntry{.ambiguous, 0x00b0, 5}, + EAWEntry{.neutral, 0x00b5, 1}, + EAWEntry{.ambiguous, 0x00b6, 5}, + EAWEntry{.neutral, 0x00bb, 1}, + EAWEntry{.ambiguous, 0x00bc, 4}, + EAWEntry{.neutral, 0x00c0, 6}, + EAWEntry{.ambiguous, 0x00c6, 1}, + EAWEntry{.neutral, 0x00c7, 9}, + EAWEntry{.ambiguous, 0x00d0, 1}, + EAWEntry{.neutral, 0x00d1, 6}, + EAWEntry{.ambiguous, 0x00d7, 2}, + EAWEntry{.neutral, 0x00d9, 5}, + EAWEntry{.ambiguous, 0x00de, 4}, + EAWEntry{.neutral, 0x00e2, 4}, + EAWEntry{.ambiguous, 0x00e6, 1}, + EAWEntry{.neutral, 0x00e7, 1}, + EAWEntry{.ambiguous, 0x00e8, 3}, + EAWEntry{.neutral, 0x00eb, 1}, + EAWEntry{.ambiguous, 0x00ec, 2}, + EAWEntry{.neutral, 0x00ee, 2}, + EAWEntry{.ambiguous, 0x00f0, 1}, + EAWEntry{.neutral, 0x00f1, 1}, + EAWEntry{.ambiguous, 0x00f2, 2}, + EAWEntry{.neutral, 0x00f4, 3}, + EAWEntry{.ambiguous, 0x00f7, 4}, + EAWEntry{.neutral, 0x00fb, 1}, + EAWEntry{.ambiguous, 0x00fc, 1}, + EAWEntry{.neutral, 0x00fd, 1}, + EAWEntry{.ambiguous, 0x00fe, 1}, + EAWEntry{.neutral, 0x00ff, 2}, + EAWEntry{.ambiguous, 0x0101, 1}, + EAWEntry{.neutral, 0x0102, 15}, + EAWEntry{.ambiguous, 0x0111, 1}, + EAWEntry{.neutral, 0x0112, 1}, + EAWEntry{.ambiguous, 0x0113, 1}, + EAWEntry{.neutral, 0x0114, 7}, + EAWEntry{.ambiguous, 0x011b, 1}, + EAWEntry{.neutral, 0x011c, 10}, + EAWEntry{.ambiguous, 0x0126, 2}, + EAWEntry{.neutral, 0x0128, 3}, + EAWEntry{.ambiguous, 0x012b, 1}, + EAWEntry{.neutral, 0x012c, 5}, + EAWEntry{.ambiguous, 0x0131, 3}, + EAWEntry{.neutral, 0x0134, 4}, + EAWEntry{.ambiguous, 0x0138, 1}, + EAWEntry{.neutral, 0x0139, 6}, + EAWEntry{.ambiguous, 0x013f, 4}, + EAWEntry{.neutral, 0x0143, 1}, + EAWEntry{.ambiguous, 0x0144, 1}, + EAWEntry{.neutral, 0x0145, 3}, + EAWEntry{.ambiguous, 0x0148, 4}, + EAWEntry{.neutral, 0x014c, 1}, + EAWEntry{.ambiguous, 0x014d, 1}, + EAWEntry{.neutral, 0x014e, 4}, + EAWEntry{.ambiguous, 0x0152, 2}, + EAWEntry{.neutral, 0x0154, 18}, + EAWEntry{.ambiguous, 0x0166, 2}, + EAWEntry{.neutral, 0x0168, 3}, + EAWEntry{.ambiguous, 0x016b, 1}, + EAWEntry{.neutral, 0x016c, 98}, + EAWEntry{.ambiguous, 0x01ce, 1}, + EAWEntry{.neutral, 0x01cf, 1}, + EAWEntry{.ambiguous, 0x01d0, 1}, + EAWEntry{.neutral, 0x01d1, 1}, + EAWEntry{.ambiguous, 0x01d2, 1}, + EAWEntry{.neutral, 0x01d3, 1}, + EAWEntry{.ambiguous, 0x01d4, 1}, + EAWEntry{.neutral, 0x01d5, 1}, + EAWEntry{.ambiguous, 0x01d6, 1}, + EAWEntry{.neutral, 0x01d7, 1}, + EAWEntry{.ambiguous, 0x01d8, 1}, + EAWEntry{.neutral, 0x01d9, 1}, + EAWEntry{.ambiguous, 0x01da, 1}, + EAWEntry{.neutral, 0x01db, 1}, + EAWEntry{.ambiguous, 0x01dc, 1}, + EAWEntry{.neutral, 0x01dd, 116}, + EAWEntry{.ambiguous, 0x0251, 1}, + EAWEntry{.neutral, 0x0252, 15}, + EAWEntry{.ambiguous, 0x0261, 1}, + EAWEntry{.neutral, 0x0262, 98}, + EAWEntry{.ambiguous, 0x02c4, 1}, + EAWEntry{.neutral, 0x02c5, 2}, + EAWEntry{.ambiguous, 0x02c7, 1}, + EAWEntry{.neutral, 0x02c8, 1}, + EAWEntry{.ambiguous, 0x02c9, 3}, + EAWEntry{.neutral, 0x02cc, 1}, + EAWEntry{.ambiguous, 0x02cd, 1}, + EAWEntry{.neutral, 0x02ce, 2}, + EAWEntry{.ambiguous, 0x02d0, 1}, + EAWEntry{.neutral, 0x02d1, 7}, + EAWEntry{.ambiguous, 0x02d8, 4}, + EAWEntry{.neutral, 0x02dc, 1}, + EAWEntry{.ambiguous, 0x02dd, 1}, + EAWEntry{.neutral, 0x02de, 1}, + EAWEntry{.ambiguous, 0x02df, 1}, + EAWEntry{.neutral, 0x02e0, 32}, + EAWEntry{.ambiguous, 0x0300, 112}, + EAWEntry{.neutral, 0x0370, 8}, + EAWEntry{.neutral, 0x037a, 6}, + EAWEntry{.neutral, 0x0384, 7}, + EAWEntry{.neutral, 0x038c, 1}, + EAWEntry{.neutral, 0x038e, 3}, + EAWEntry{.ambiguous, 0x0391, 17}, + EAWEntry{.ambiguous, 0x03a3, 7}, + EAWEntry{.neutral, 0x03aa, 7}, + EAWEntry{.ambiguous, 0x03b1, 17}, + EAWEntry{.neutral, 0x03c2, 1}, + EAWEntry{.ambiguous, 0x03c3, 7}, + EAWEntry{.neutral, 0x03ca, 55}, + EAWEntry{.ambiguous, 0x0401, 1}, + EAWEntry{.neutral, 0x0402, 14}, + EAWEntry{.ambiguous, 0x0410, 64}, + EAWEntry{.neutral, 0x0450, 1}, + EAWEntry{.ambiguous, 0x0451, 1}, + EAWEntry{.neutral, 0x0452, 222}, + EAWEntry{.neutral, 0x0531, 38}, + EAWEntry{.neutral, 0x0559, 50}, + EAWEntry{.neutral, 0x058d, 3}, + EAWEntry{.neutral, 0x0591, 55}, + EAWEntry{.neutral, 0x05d0, 27}, + EAWEntry{.neutral, 0x05ef, 6}, + EAWEntry{.neutral, 0x0600, 29}, + EAWEntry{.neutral, 0x061e, 240}, + EAWEntry{.neutral, 0x070f, 60}, + EAWEntry{.neutral, 0x074d, 101}, + EAWEntry{.neutral, 0x07c0, 59}, + EAWEntry{.neutral, 0x07fd, 49}, + EAWEntry{.neutral, 0x0830, 15}, + EAWEntry{.neutral, 0x0840, 28}, + EAWEntry{.neutral, 0x085e, 1}, + EAWEntry{.neutral, 0x0860, 11}, + EAWEntry{.neutral, 0x08a0, 21}, + EAWEntry{.neutral, 0x08b6, 18}, + EAWEntry{.neutral, 0x08d3, 177}, + EAWEntry{.neutral, 0x0985, 8}, + EAWEntry{.neutral, 0x098f, 2}, + EAWEntry{.neutral, 0x0993, 22}, + EAWEntry{.neutral, 0x09aa, 7}, + EAWEntry{.neutral, 0x09b2, 1}, + EAWEntry{.neutral, 0x09b6, 4}, + EAWEntry{.neutral, 0x09bc, 9}, + EAWEntry{.neutral, 0x09c7, 2}, + EAWEntry{.neutral, 0x09cb, 4}, + EAWEntry{.neutral, 0x09d7, 1}, + EAWEntry{.neutral, 0x09dc, 2}, + EAWEntry{.neutral, 0x09df, 5}, + EAWEntry{.neutral, 0x09e6, 25}, + EAWEntry{.neutral, 0x0a01, 3}, + EAWEntry{.neutral, 0x0a05, 6}, + EAWEntry{.neutral, 0x0a0f, 2}, + EAWEntry{.neutral, 0x0a13, 22}, + EAWEntry{.neutral, 0x0a2a, 7}, + EAWEntry{.neutral, 0x0a32, 2}, + EAWEntry{.neutral, 0x0a35, 2}, + EAWEntry{.neutral, 0x0a38, 2}, + EAWEntry{.neutral, 0x0a3c, 1}, + EAWEntry{.neutral, 0x0a3e, 5}, + EAWEntry{.neutral, 0x0a47, 2}, + EAWEntry{.neutral, 0x0a4b, 3}, + EAWEntry{.neutral, 0x0a51, 1}, + EAWEntry{.neutral, 0x0a59, 4}, + EAWEntry{.neutral, 0x0a5e, 1}, + EAWEntry{.neutral, 0x0a66, 17}, + EAWEntry{.neutral, 0x0a81, 3}, + EAWEntry{.neutral, 0x0a85, 9}, + EAWEntry{.neutral, 0x0a8f, 3}, + EAWEntry{.neutral, 0x0a93, 22}, + EAWEntry{.neutral, 0x0aaa, 7}, + EAWEntry{.neutral, 0x0ab2, 2}, + EAWEntry{.neutral, 0x0ab5, 5}, + EAWEntry{.neutral, 0x0abc, 10}, + EAWEntry{.neutral, 0x0ac7, 3}, + EAWEntry{.neutral, 0x0acb, 3}, + EAWEntry{.neutral, 0x0ad0, 1}, + EAWEntry{.neutral, 0x0ae0, 4}, + EAWEntry{.neutral, 0x0ae6, 12}, + EAWEntry{.neutral, 0x0af9, 7}, + EAWEntry{.neutral, 0x0b01, 3}, + EAWEntry{.neutral, 0x0b05, 8}, + EAWEntry{.neutral, 0x0b0f, 2}, + EAWEntry{.neutral, 0x0b13, 22}, + EAWEntry{.neutral, 0x0b2a, 7}, + EAWEntry{.neutral, 0x0b32, 2}, + EAWEntry{.neutral, 0x0b35, 5}, + EAWEntry{.neutral, 0x0b3c, 9}, + EAWEntry{.neutral, 0x0b47, 2}, + EAWEntry{.neutral, 0x0b4b, 3}, + EAWEntry{.neutral, 0x0b55, 3}, + EAWEntry{.neutral, 0x0b5c, 2}, + EAWEntry{.neutral, 0x0b5f, 5}, + EAWEntry{.neutral, 0x0b66, 18}, + EAWEntry{.neutral, 0x0b82, 2}, + EAWEntry{.neutral, 0x0b85, 6}, + EAWEntry{.neutral, 0x0b8e, 3}, + EAWEntry{.neutral, 0x0b92, 4}, + EAWEntry{.neutral, 0x0b99, 2}, + EAWEntry{.neutral, 0x0b9c, 1}, + EAWEntry{.neutral, 0x0b9e, 2}, + EAWEntry{.neutral, 0x0ba3, 2}, + EAWEntry{.neutral, 0x0ba8, 3}, + EAWEntry{.neutral, 0x0bae, 12}, + EAWEntry{.neutral, 0x0bbe, 5}, + EAWEntry{.neutral, 0x0bc6, 3}, + EAWEntry{.neutral, 0x0bca, 4}, + EAWEntry{.neutral, 0x0bd0, 1}, + EAWEntry{.neutral, 0x0bd7, 1}, + EAWEntry{.neutral, 0x0be6, 21}, + EAWEntry{.neutral, 0x0c00, 13}, + EAWEntry{.neutral, 0x0c0e, 3}, + EAWEntry{.neutral, 0x0c12, 23}, + EAWEntry{.neutral, 0x0c2a, 16}, + EAWEntry{.neutral, 0x0c3d, 8}, + EAWEntry{.neutral, 0x0c46, 3}, + EAWEntry{.neutral, 0x0c4a, 4}, + EAWEntry{.neutral, 0x0c55, 2}, + EAWEntry{.neutral, 0x0c58, 3}, + EAWEntry{.neutral, 0x0c60, 4}, + EAWEntry{.neutral, 0x0c66, 10}, + EAWEntry{.neutral, 0x0c77, 22}, + EAWEntry{.neutral, 0x0c8e, 3}, + EAWEntry{.neutral, 0x0c92, 23}, + EAWEntry{.neutral, 0x0caa, 10}, + EAWEntry{.neutral, 0x0cb5, 5}, + EAWEntry{.neutral, 0x0cbc, 9}, + EAWEntry{.neutral, 0x0cc6, 3}, + EAWEntry{.neutral, 0x0cca, 4}, + EAWEntry{.neutral, 0x0cd5, 2}, + EAWEntry{.neutral, 0x0cde, 1}, + EAWEntry{.neutral, 0x0ce0, 4}, + EAWEntry{.neutral, 0x0ce6, 10}, + EAWEntry{.neutral, 0x0cf1, 2}, + EAWEntry{.neutral, 0x0d00, 13}, + EAWEntry{.neutral, 0x0d0e, 3}, + EAWEntry{.neutral, 0x0d12, 51}, + EAWEntry{.neutral, 0x0d46, 3}, + EAWEntry{.neutral, 0x0d4a, 6}, + EAWEntry{.neutral, 0x0d54, 16}, + EAWEntry{.neutral, 0x0d66, 26}, + EAWEntry{.neutral, 0x0d81, 3}, + EAWEntry{.neutral, 0x0d85, 18}, + EAWEntry{.neutral, 0x0d9a, 24}, + EAWEntry{.neutral, 0x0db3, 9}, + EAWEntry{.neutral, 0x0dbd, 1}, + EAWEntry{.neutral, 0x0dc0, 7}, + EAWEntry{.neutral, 0x0dca, 1}, + EAWEntry{.neutral, 0x0dcf, 6}, + EAWEntry{.neutral, 0x0dd6, 1}, + EAWEntry{.neutral, 0x0dd8, 8}, + EAWEntry{.neutral, 0x0de6, 10}, + EAWEntry{.neutral, 0x0df2, 3}, + EAWEntry{.neutral, 0x0e01, 58}, + EAWEntry{.neutral, 0x0e3f, 29}, + EAWEntry{.neutral, 0x0e81, 2}, + EAWEntry{.neutral, 0x0e84, 1}, + EAWEntry{.neutral, 0x0e86, 5}, + EAWEntry{.neutral, 0x0e8c, 24}, + EAWEntry{.neutral, 0x0ea5, 1}, + EAWEntry{.neutral, 0x0ea7, 23}, + EAWEntry{.neutral, 0x0ec0, 5}, + EAWEntry{.neutral, 0x0ec6, 1}, + EAWEntry{.neutral, 0x0ec8, 6}, + EAWEntry{.neutral, 0x0ed0, 10}, + EAWEntry{.neutral, 0x0edc, 4}, + EAWEntry{.neutral, 0x0f00, 72}, + EAWEntry{.neutral, 0x0f49, 36}, + EAWEntry{.neutral, 0x0f71, 39}, + EAWEntry{.neutral, 0x0f99, 36}, + EAWEntry{.neutral, 0x0fbe, 15}, + EAWEntry{.neutral, 0x0fce, 13}, + EAWEntry{.neutral, 0x1000, 198}, + EAWEntry{.neutral, 0x10c7, 1}, + EAWEntry{.neutral, 0x10cd, 1}, + EAWEntry{.neutral, 0x10d0, 48}, + EAWEntry{.wide, 0x1100, 96}, + EAWEntry{.neutral, 0x1160, 233}, + EAWEntry{.neutral, 0x124a, 4}, + EAWEntry{.neutral, 0x1250, 7}, + EAWEntry{.neutral, 0x1258, 1}, + EAWEntry{.neutral, 0x125a, 4}, + EAWEntry{.neutral, 0x1260, 41}, + EAWEntry{.neutral, 0x128a, 4}, + EAWEntry{.neutral, 0x1290, 33}, + EAWEntry{.neutral, 0x12b2, 4}, + EAWEntry{.neutral, 0x12b8, 7}, + EAWEntry{.neutral, 0x12c0, 1}, + EAWEntry{.neutral, 0x12c2, 4}, + EAWEntry{.neutral, 0x12c8, 15}, + EAWEntry{.neutral, 0x12d8, 57}, + EAWEntry{.neutral, 0x1312, 4}, + EAWEntry{.neutral, 0x1318, 67}, + EAWEntry{.neutral, 0x135d, 32}, + EAWEntry{.neutral, 0x1380, 26}, + EAWEntry{.neutral, 0x13a0, 86}, + EAWEntry{.neutral, 0x13f8, 6}, + EAWEntry{.neutral, 0x1400, 669}, + EAWEntry{.neutral, 0x16a0, 89}, + EAWEntry{.neutral, 0x1700, 13}, + EAWEntry{.neutral, 0x170e, 7}, + EAWEntry{.neutral, 0x1720, 23}, + EAWEntry{.neutral, 0x1740, 20}, + EAWEntry{.neutral, 0x1760, 13}, + EAWEntry{.neutral, 0x176e, 3}, + EAWEntry{.neutral, 0x1772, 2}, + EAWEntry{.neutral, 0x1780, 94}, + EAWEntry{.neutral, 0x17e0, 10}, + EAWEntry{.neutral, 0x17f0, 10}, + EAWEntry{.neutral, 0x1800, 15}, + EAWEntry{.neutral, 0x1810, 10}, + EAWEntry{.neutral, 0x1820, 89}, + EAWEntry{.neutral, 0x1880, 43}, + EAWEntry{.neutral, 0x18b0, 70}, + EAWEntry{.neutral, 0x1900, 31}, + EAWEntry{.neutral, 0x1920, 12}, + EAWEntry{.neutral, 0x1930, 12}, + EAWEntry{.neutral, 0x1940, 1}, + EAWEntry{.neutral, 0x1944, 42}, + EAWEntry{.neutral, 0x1970, 5}, + EAWEntry{.neutral, 0x1980, 44}, + EAWEntry{.neutral, 0x19b0, 26}, + EAWEntry{.neutral, 0x19d0, 11}, + EAWEntry{.neutral, 0x19de, 62}, + EAWEntry{.neutral, 0x1a1e, 65}, + EAWEntry{.neutral, 0x1a60, 29}, + EAWEntry{.neutral, 0x1a7f, 11}, + EAWEntry{.neutral, 0x1a90, 10}, + EAWEntry{.neutral, 0x1aa0, 14}, + EAWEntry{.neutral, 0x1ab0, 17}, + EAWEntry{.neutral, 0x1b00, 76}, + EAWEntry{.neutral, 0x1b50, 45}, + EAWEntry{.neutral, 0x1b80, 116}, + EAWEntry{.neutral, 0x1bfc, 60}, + EAWEntry{.neutral, 0x1c3b, 15}, + EAWEntry{.neutral, 0x1c4d, 60}, + EAWEntry{.neutral, 0x1c90, 43}, + EAWEntry{.neutral, 0x1cbd, 11}, + EAWEntry{.neutral, 0x1cd0, 43}, + EAWEntry{.neutral, 0x1d00, 250}, + EAWEntry{.neutral, 0x1dfb, 283}, + EAWEntry{.neutral, 0x1f18, 6}, + EAWEntry{.neutral, 0x1f20, 38}, + EAWEntry{.neutral, 0x1f48, 6}, + EAWEntry{.neutral, 0x1f50, 8}, + EAWEntry{.neutral, 0x1f59, 1}, + EAWEntry{.neutral, 0x1f5b, 1}, + EAWEntry{.neutral, 0x1f5d, 1}, + EAWEntry{.neutral, 0x1f5f, 31}, + EAWEntry{.neutral, 0x1f80, 53}, + EAWEntry{.neutral, 0x1fb6, 15}, + EAWEntry{.neutral, 0x1fc6, 14}, + EAWEntry{.neutral, 0x1fd6, 6}, + EAWEntry{.neutral, 0x1fdd, 19}, + EAWEntry{.neutral, 0x1ff2, 3}, + EAWEntry{.neutral, 0x1ff6, 9}, + EAWEntry{.neutral, 0x2000, 16}, + EAWEntry{.ambiguous, 0x2010, 1}, + EAWEntry{.neutral, 0x2011, 2}, + EAWEntry{.ambiguous, 0x2013, 4}, + EAWEntry{.neutral, 0x2017, 1}, + EAWEntry{.ambiguous, 0x2018, 2}, + EAWEntry{.neutral, 0x201a, 2}, + EAWEntry{.ambiguous, 0x201c, 2}, + EAWEntry{.neutral, 0x201e, 2}, + EAWEntry{.ambiguous, 0x2020, 3}, + EAWEntry{.neutral, 0x2023, 1}, + EAWEntry{.ambiguous, 0x2024, 4}, + EAWEntry{.neutral, 0x2028, 8}, + EAWEntry{.ambiguous, 0x2030, 1}, + EAWEntry{.neutral, 0x2031, 1}, + EAWEntry{.ambiguous, 0x2032, 2}, + EAWEntry{.neutral, 0x2034, 1}, + EAWEntry{.ambiguous, 0x2035, 1}, + EAWEntry{.neutral, 0x2036, 5}, + EAWEntry{.ambiguous, 0x203b, 1}, + EAWEntry{.neutral, 0x203c, 2}, + EAWEntry{.ambiguous, 0x203e, 1}, + EAWEntry{.neutral, 0x203f, 38}, + EAWEntry{.neutral, 0x2066, 12}, + EAWEntry{.ambiguous, 0x2074, 1}, + EAWEntry{.neutral, 0x2075, 10}, + EAWEntry{.ambiguous, 0x207f, 1}, + EAWEntry{.neutral, 0x2080, 1}, + EAWEntry{.ambiguous, 0x2081, 4}, + EAWEntry{.neutral, 0x2085, 10}, + EAWEntry{.neutral, 0x2090, 13}, + EAWEntry{.neutral, 0x20a0, 9}, + EAWEntry{.half, 0x20a9, 1}, + EAWEntry{.neutral, 0x20aa, 2}, + EAWEntry{.ambiguous, 0x20ac, 1}, + EAWEntry{.neutral, 0x20ad, 19}, + EAWEntry{.neutral, 0x20d0, 33}, + EAWEntry{.neutral, 0x2100, 3}, + EAWEntry{.ambiguous, 0x2103, 1}, + EAWEntry{.neutral, 0x2104, 1}, + EAWEntry{.ambiguous, 0x2105, 1}, + EAWEntry{.neutral, 0x2106, 3}, + EAWEntry{.ambiguous, 0x2109, 1}, + EAWEntry{.neutral, 0x210a, 9}, + EAWEntry{.ambiguous, 0x2113, 1}, + EAWEntry{.neutral, 0x2114, 2}, + EAWEntry{.ambiguous, 0x2116, 1}, + EAWEntry{.neutral, 0x2117, 10}, + EAWEntry{.ambiguous, 0x2121, 2}, + EAWEntry{.neutral, 0x2123, 3}, + EAWEntry{.ambiguous, 0x2126, 1}, + EAWEntry{.neutral, 0x2127, 4}, + EAWEntry{.ambiguous, 0x212b, 1}, + EAWEntry{.neutral, 0x212c, 39}, + EAWEntry{.ambiguous, 0x2153, 2}, + EAWEntry{.neutral, 0x2155, 6}, + EAWEntry{.ambiguous, 0x215b, 4}, + EAWEntry{.neutral, 0x215f, 1}, + EAWEntry{.ambiguous, 0x2160, 12}, + EAWEntry{.neutral, 0x216c, 4}, + EAWEntry{.ambiguous, 0x2170, 10}, + EAWEntry{.neutral, 0x217a, 15}, + EAWEntry{.ambiguous, 0x2189, 1}, + EAWEntry{.neutral, 0x218a, 2}, + EAWEntry{.ambiguous, 0x2190, 10}, + EAWEntry{.neutral, 0x219a, 30}, + EAWEntry{.ambiguous, 0x21b8, 2}, + EAWEntry{.neutral, 0x21ba, 24}, + EAWEntry{.ambiguous, 0x21d2, 1}, + EAWEntry{.neutral, 0x21d3, 1}, + EAWEntry{.ambiguous, 0x21d4, 1}, + EAWEntry{.neutral, 0x21d5, 18}, + EAWEntry{.ambiguous, 0x21e7, 1}, + EAWEntry{.neutral, 0x21e8, 24}, + EAWEntry{.ambiguous, 0x2200, 1}, + EAWEntry{.neutral, 0x2201, 1}, + EAWEntry{.ambiguous, 0x2202, 2}, + EAWEntry{.neutral, 0x2204, 3}, + EAWEntry{.ambiguous, 0x2207, 2}, + EAWEntry{.neutral, 0x2209, 2}, + EAWEntry{.ambiguous, 0x220b, 1}, + EAWEntry{.neutral, 0x220c, 3}, + EAWEntry{.ambiguous, 0x220f, 1}, + EAWEntry{.neutral, 0x2210, 1}, + EAWEntry{.ambiguous, 0x2211, 1}, + EAWEntry{.neutral, 0x2212, 3}, + EAWEntry{.ambiguous, 0x2215, 1}, + EAWEntry{.neutral, 0x2216, 4}, + EAWEntry{.ambiguous, 0x221a, 1}, + EAWEntry{.neutral, 0x221b, 2}, + EAWEntry{.ambiguous, 0x221d, 4}, + EAWEntry{.neutral, 0x2221, 2}, + EAWEntry{.ambiguous, 0x2223, 1}, + EAWEntry{.neutral, 0x2224, 1}, + EAWEntry{.ambiguous, 0x2225, 1}, + EAWEntry{.neutral, 0x2226, 1}, + EAWEntry{.ambiguous, 0x2227, 6}, + EAWEntry{.neutral, 0x222d, 1}, + EAWEntry{.ambiguous, 0x222e, 1}, + EAWEntry{.neutral, 0x222f, 5}, + EAWEntry{.ambiguous, 0x2234, 4}, + EAWEntry{.neutral, 0x2238, 4}, + EAWEntry{.ambiguous, 0x223c, 2}, + EAWEntry{.neutral, 0x223e, 10}, + EAWEntry{.ambiguous, 0x2248, 1}, + EAWEntry{.neutral, 0x2249, 3}, + EAWEntry{.ambiguous, 0x224c, 1}, + EAWEntry{.neutral, 0x224d, 5}, + EAWEntry{.ambiguous, 0x2252, 1}, + EAWEntry{.neutral, 0x2253, 13}, + EAWEntry{.ambiguous, 0x2260, 2}, + EAWEntry{.neutral, 0x2262, 2}, + EAWEntry{.ambiguous, 0x2264, 4}, + EAWEntry{.neutral, 0x2268, 2}, + EAWEntry{.ambiguous, 0x226a, 2}, + EAWEntry{.neutral, 0x226c, 2}, + EAWEntry{.ambiguous, 0x226e, 2}, + EAWEntry{.neutral, 0x2270, 18}, + EAWEntry{.ambiguous, 0x2282, 2}, + EAWEntry{.neutral, 0x2284, 2}, + EAWEntry{.ambiguous, 0x2286, 2}, + EAWEntry{.neutral, 0x2288, 13}, + EAWEntry{.ambiguous, 0x2295, 1}, + EAWEntry{.neutral, 0x2296, 3}, + EAWEntry{.ambiguous, 0x2299, 1}, + EAWEntry{.neutral, 0x229a, 11}, + EAWEntry{.ambiguous, 0x22a5, 1}, + EAWEntry{.neutral, 0x22a6, 25}, + EAWEntry{.ambiguous, 0x22bf, 1}, + EAWEntry{.neutral, 0x22c0, 82}, + EAWEntry{.ambiguous, 0x2312, 1}, + EAWEntry{.neutral, 0x2313, 7}, + EAWEntry{.wide, 0x231a, 2}, + EAWEntry{.neutral, 0x231c, 13}, + EAWEntry{.wide, 0x2329, 2}, + EAWEntry{.neutral, 0x232b, 190}, + EAWEntry{.wide, 0x23e9, 4}, + EAWEntry{.neutral, 0x23ed, 3}, + EAWEntry{.wide, 0x23f0, 1}, + EAWEntry{.neutral, 0x23f1, 2}, + EAWEntry{.wide, 0x23f3, 1}, + EAWEntry{.neutral, 0x23f4, 51}, + EAWEntry{.neutral, 0x2440, 11}, + EAWEntry{.ambiguous, 0x2460, 138}, + EAWEntry{.neutral, 0x24ea, 1}, + EAWEntry{.ambiguous, 0x24eb, 97}, + EAWEntry{.neutral, 0x254c, 4}, + EAWEntry{.ambiguous, 0x2550, 36}, + EAWEntry{.neutral, 0x2574, 12}, + EAWEntry{.ambiguous, 0x2580, 16}, + EAWEntry{.neutral, 0x2590, 2}, + EAWEntry{.ambiguous, 0x2592, 4}, + EAWEntry{.neutral, 0x2596, 10}, + EAWEntry{.ambiguous, 0x25a0, 2}, + EAWEntry{.neutral, 0x25a2, 1}, + EAWEntry{.ambiguous, 0x25a3, 7}, + EAWEntry{.neutral, 0x25aa, 8}, + EAWEntry{.ambiguous, 0x25b2, 2}, + EAWEntry{.neutral, 0x25b4, 2}, + EAWEntry{.ambiguous, 0x25b6, 2}, + EAWEntry{.neutral, 0x25b8, 4}, + EAWEntry{.ambiguous, 0x25bc, 2}, + EAWEntry{.neutral, 0x25be, 2}, + EAWEntry{.ambiguous, 0x25c0, 2}, + EAWEntry{.neutral, 0x25c2, 4}, + EAWEntry{.ambiguous, 0x25c6, 3}, + EAWEntry{.neutral, 0x25c9, 2}, + EAWEntry{.ambiguous, 0x25cb, 1}, + EAWEntry{.neutral, 0x25cc, 2}, + EAWEntry{.ambiguous, 0x25ce, 4}, + EAWEntry{.neutral, 0x25d2, 16}, + EAWEntry{.ambiguous, 0x25e2, 4}, + EAWEntry{.neutral, 0x25e6, 9}, + EAWEntry{.ambiguous, 0x25ef, 1}, + EAWEntry{.neutral, 0x25f0, 13}, + EAWEntry{.wide, 0x25fd, 2}, + EAWEntry{.neutral, 0x25ff, 6}, + EAWEntry{.ambiguous, 0x2605, 2}, + EAWEntry{.neutral, 0x2607, 2}, + EAWEntry{.ambiguous, 0x2609, 1}, + EAWEntry{.neutral, 0x260a, 4}, + EAWEntry{.ambiguous, 0x260e, 2}, + EAWEntry{.neutral, 0x2610, 4}, + EAWEntry{.wide, 0x2614, 2}, + EAWEntry{.neutral, 0x2616, 6}, + EAWEntry{.ambiguous, 0x261c, 1}, + EAWEntry{.neutral, 0x261d, 1}, + EAWEntry{.ambiguous, 0x261e, 1}, + EAWEntry{.neutral, 0x261f, 33}, + EAWEntry{.ambiguous, 0x2640, 1}, + EAWEntry{.neutral, 0x2641, 1}, + EAWEntry{.ambiguous, 0x2642, 1}, + EAWEntry{.neutral, 0x2643, 5}, + EAWEntry{.wide, 0x2648, 12}, + EAWEntry{.neutral, 0x2654, 12}, + EAWEntry{.ambiguous, 0x2660, 2}, + EAWEntry{.neutral, 0x2662, 1}, + EAWEntry{.ambiguous, 0x2663, 3}, + EAWEntry{.neutral, 0x2666, 1}, + EAWEntry{.ambiguous, 0x2667, 4}, + EAWEntry{.neutral, 0x266b, 1}, + EAWEntry{.ambiguous, 0x266c, 2}, + EAWEntry{.neutral, 0x266e, 1}, + EAWEntry{.ambiguous, 0x266f, 1}, + EAWEntry{.neutral, 0x2670, 15}, + EAWEntry{.wide, 0x267f, 1}, + EAWEntry{.neutral, 0x2680, 19}, + EAWEntry{.wide, 0x2693, 1}, + EAWEntry{.neutral, 0x2694, 10}, + EAWEntry{.ambiguous, 0x269e, 2}, + EAWEntry{.neutral, 0x26a0, 1}, + EAWEntry{.wide, 0x26a1, 1}, + EAWEntry{.neutral, 0x26a2, 8}, + EAWEntry{.wide, 0x26aa, 2}, + EAWEntry{.neutral, 0x26ac, 17}, + EAWEntry{.wide, 0x26bd, 2}, + EAWEntry{.ambiguous, 0x26bf, 1}, + EAWEntry{.neutral, 0x26c0, 4}, + EAWEntry{.wide, 0x26c4, 2}, + EAWEntry{.ambiguous, 0x26c6, 8}, + EAWEntry{.wide, 0x26ce, 1}, + EAWEntry{.ambiguous, 0x26cf, 5}, + EAWEntry{.wide, 0x26d4, 1}, + EAWEntry{.ambiguous, 0x26d5, 13}, + EAWEntry{.neutral, 0x26e2, 1}, + EAWEntry{.ambiguous, 0x26e3, 1}, + EAWEntry{.neutral, 0x26e4, 4}, + EAWEntry{.ambiguous, 0x26e8, 2}, + EAWEntry{.wide, 0x26ea, 1}, + EAWEntry{.ambiguous, 0x26eb, 7}, + EAWEntry{.wide, 0x26f2, 2}, + EAWEntry{.ambiguous, 0x26f4, 1}, + EAWEntry{.wide, 0x26f5, 1}, + EAWEntry{.ambiguous, 0x26f6, 4}, + EAWEntry{.wide, 0x26fa, 1}, + EAWEntry{.ambiguous, 0x26fb, 2}, + EAWEntry{.wide, 0x26fd, 1}, + EAWEntry{.ambiguous, 0x26fe, 2}, + EAWEntry{.neutral, 0x2700, 5}, + EAWEntry{.wide, 0x2705, 1}, + EAWEntry{.neutral, 0x2706, 4}, + EAWEntry{.wide, 0x270a, 2}, + EAWEntry{.neutral, 0x270c, 28}, + EAWEntry{.wide, 0x2728, 1}, + EAWEntry{.neutral, 0x2729, 20}, + EAWEntry{.ambiguous, 0x273d, 1}, + EAWEntry{.neutral, 0x273e, 14}, + EAWEntry{.wide, 0x274c, 1}, + EAWEntry{.neutral, 0x274d, 1}, + EAWEntry{.wide, 0x274e, 1}, + EAWEntry{.neutral, 0x274f, 4}, + EAWEntry{.wide, 0x2753, 3}, + EAWEntry{.neutral, 0x2756, 1}, + EAWEntry{.wide, 0x2757, 1}, + EAWEntry{.neutral, 0x2758, 30}, + EAWEntry{.ambiguous, 0x2776, 10}, + EAWEntry{.neutral, 0x2780, 21}, + EAWEntry{.wide, 0x2795, 3}, + EAWEntry{.neutral, 0x2798, 24}, + EAWEntry{.wide, 0x27b0, 1}, + EAWEntry{.neutral, 0x27b1, 14}, + EAWEntry{.wide, 0x27bf, 1}, + EAWEntry{.neutral, 0x27c0, 38}, + EAWEntry{.narrow, 0x27e6, 8}, + EAWEntry{.neutral, 0x27ee, 407}, + EAWEntry{.narrow, 0x2985, 2}, + EAWEntry{.neutral, 0x2987, 404}, + EAWEntry{.wide, 0x2b1b, 2}, + EAWEntry{.neutral, 0x2b1d, 51}, + EAWEntry{.wide, 0x2b50, 1}, + EAWEntry{.neutral, 0x2b51, 4}, + EAWEntry{.wide, 0x2b55, 1}, + EAWEntry{.ambiguous, 0x2b56, 4}, + EAWEntry{.neutral, 0x2b5a, 26}, + EAWEntry{.neutral, 0x2b76, 32}, + EAWEntry{.neutral, 0x2b97, 152}, + EAWEntry{.neutral, 0x2c30, 47}, + EAWEntry{.neutral, 0x2c60, 148}, + EAWEntry{.neutral, 0x2cf9, 45}, + EAWEntry{.neutral, 0x2d27, 1}, + EAWEntry{.neutral, 0x2d2d, 1}, + EAWEntry{.neutral, 0x2d30, 56}, + EAWEntry{.neutral, 0x2d6f, 2}, + EAWEntry{.neutral, 0x2d7f, 24}, + EAWEntry{.neutral, 0x2da0, 7}, + EAWEntry{.neutral, 0x2da8, 7}, + EAWEntry{.neutral, 0x2db0, 7}, + EAWEntry{.neutral, 0x2db8, 7}, + EAWEntry{.neutral, 0x2dc0, 7}, + EAWEntry{.neutral, 0x2dc8, 7}, + EAWEntry{.neutral, 0x2dd0, 7}, + EAWEntry{.neutral, 0x2dd8, 7}, + EAWEntry{.neutral, 0x2de0, 115}, + EAWEntry{.wide, 0x2e80, 26}, + EAWEntry{.wide, 0x2e9b, 89}, + EAWEntry{.wide, 0x2f00, 214}, + EAWEntry{.wide, 0x2ff0, 12}, + EAWEntry{.full, 0x3000, 1}, + EAWEntry{.wide, 0x3001, 62}, + EAWEntry{.neutral, 0x303f, 1}, + EAWEntry{.wide, 0x3041, 86}, + EAWEntry{.wide, 0x3099, 103}, + EAWEntry{.wide, 0x3105, 43}, + EAWEntry{.wide, 0x3131, 94}, + EAWEntry{.wide, 0x3190, 84}, + EAWEntry{.wide, 0x31f0, 47}, + EAWEntry{.wide, 0x3220, 40}, + EAWEntry{.ambiguous, 0x3248, 8}, + EAWEntry{.wide, 0x3250, 7024}, + EAWEntry{.neutral, 0x4dc0, 64}, + EAWEntry{.wide, 0x4e00, 22157}, + EAWEntry{.wide, 0xa490, 55}, + EAWEntry{.neutral, 0xa4d0, 348}, + EAWEntry{.neutral, 0xa640, 184}, + EAWEntry{.neutral, 0xa700, 192}, + EAWEntry{.neutral, 0xa7c2, 9}, + EAWEntry{.neutral, 0xa7f5, 56}, + EAWEntry{.neutral, 0xa830, 10}, + EAWEntry{.neutral, 0xa840, 56}, + EAWEntry{.neutral, 0xa880, 70}, + EAWEntry{.neutral, 0xa8ce, 12}, + EAWEntry{.neutral, 0xa8e0, 116}, + EAWEntry{.neutral, 0xa95f, 1}, + EAWEntry{.wide, 0xa960, 29}, + EAWEntry{.neutral, 0xa980, 78}, + EAWEntry{.neutral, 0xa9cf, 11}, + EAWEntry{.neutral, 0xa9de, 33}, + EAWEntry{.neutral, 0xaa00, 55}, + EAWEntry{.neutral, 0xaa40, 14}, + EAWEntry{.neutral, 0xaa50, 10}, + EAWEntry{.neutral, 0xaa5c, 103}, + EAWEntry{.neutral, 0xaadb, 28}, + EAWEntry{.neutral, 0xab01, 6}, + EAWEntry{.neutral, 0xab09, 6}, + EAWEntry{.neutral, 0xab11, 6}, + EAWEntry{.neutral, 0xab20, 7}, + EAWEntry{.neutral, 0xab28, 7}, + EAWEntry{.neutral, 0xab30, 60}, + EAWEntry{.neutral, 0xab70, 126}, + EAWEntry{.neutral, 0xabf0, 10}, + EAWEntry{.wide, 0xac00, 11172}, + EAWEntry{.neutral, 0xd7b0, 23}, + EAWEntry{.neutral, 0xd7cb, 49}, + EAWEntry{.neutral, 0xd800, 2048}, + EAWEntry{.ambiguous, 0xe000, 6400}, + EAWEntry{.wide, 0xf900, 512}, + EAWEntry{.neutral, 0xfb00, 7}, + EAWEntry{.neutral, 0xfb13, 5}, + EAWEntry{.neutral, 0xfb1d, 26}, + EAWEntry{.neutral, 0xfb38, 5}, + EAWEntry{.neutral, 0xfb3e, 1}, + EAWEntry{.neutral, 0xfb40, 2}, + EAWEntry{.neutral, 0xfb43, 2}, + EAWEntry{.neutral, 0xfb46, 124}, + EAWEntry{.neutral, 0xfbd3, 365}, + EAWEntry{.neutral, 0xfd50, 64}, + EAWEntry{.neutral, 0xfd92, 54}, + EAWEntry{.neutral, 0xfdf0, 14}, + EAWEntry{.ambiguous, 0xfe00, 16}, + EAWEntry{.wide, 0xfe10, 10}, + EAWEntry{.neutral, 0xfe20, 16}, + EAWEntry{.wide, 0xfe30, 35}, + EAWEntry{.wide, 0xfe54, 19}, + EAWEntry{.wide, 0xfe68, 4}, + EAWEntry{.neutral, 0xfe70, 5}, + EAWEntry{.neutral, 0xfe76, 135}, + EAWEntry{.neutral, 0xfeff, 1}, + EAWEntry{.full, 0xff01, 96}, + EAWEntry{.half, 0xff61, 94}, + EAWEntry{.half, 0xffc2, 6}, + EAWEntry{.half, 0xffca, 6}, + EAWEntry{.half, 0xffd2, 6}, + EAWEntry{.half, 0xffda, 3}, + EAWEntry{.full, 0xffe0, 7}, + EAWEntry{.half, 0xffe8, 7}, + EAWEntry{.neutral, 0xfff9, 4}, + EAWEntry{.ambiguous, 0xfffd, 1}, + EAWEntry{.neutral, 0x10000, 12}, + EAWEntry{.neutral, 0x1000d, 26}, + EAWEntry{.neutral, 0x10028, 19}, + EAWEntry{.neutral, 0x1003c, 2}, + EAWEntry{.neutral, 0x1003f, 15}, + EAWEntry{.neutral, 0x10050, 14}, + EAWEntry{.neutral, 0x10080, 123}, + EAWEntry{.neutral, 0x10100, 3}, + EAWEntry{.neutral, 0x10107, 45}, + EAWEntry{.neutral, 0x10137, 88}, + EAWEntry{.neutral, 0x10190, 13}, + EAWEntry{.neutral, 0x101a0, 1}, + EAWEntry{.neutral, 0x101d0, 46}, + EAWEntry{.neutral, 0x10280, 29}, + EAWEntry{.neutral, 0x102a0, 49}, + EAWEntry{.neutral, 0x102e0, 28}, + EAWEntry{.neutral, 0x10300, 36}, + EAWEntry{.neutral, 0x1032d, 30}, + EAWEntry{.neutral, 0x10350, 43}, + EAWEntry{.neutral, 0x10380, 30}, + EAWEntry{.neutral, 0x1039f, 37}, + EAWEntry{.neutral, 0x103c8, 14}, + EAWEntry{.neutral, 0x10400, 158}, + EAWEntry{.neutral, 0x104a0, 10}, + EAWEntry{.neutral, 0x104b0, 36}, + EAWEntry{.neutral, 0x104d8, 36}, + EAWEntry{.neutral, 0x10500, 40}, + EAWEntry{.neutral, 0x10530, 52}, + EAWEntry{.neutral, 0x1056f, 1}, + EAWEntry{.neutral, 0x10600, 311}, + EAWEntry{.neutral, 0x10740, 22}, + EAWEntry{.neutral, 0x10760, 8}, + EAWEntry{.neutral, 0x10800, 6}, + EAWEntry{.neutral, 0x10808, 1}, + EAWEntry{.neutral, 0x1080a, 44}, + EAWEntry{.neutral, 0x10837, 2}, + EAWEntry{.neutral, 0x1083c, 1}, + EAWEntry{.neutral, 0x1083f, 23}, + EAWEntry{.neutral, 0x10857, 72}, + EAWEntry{.neutral, 0x108a7, 9}, + EAWEntry{.neutral, 0x108e0, 19}, + EAWEntry{.neutral, 0x108f4, 2}, + EAWEntry{.neutral, 0x108fb, 33}, + EAWEntry{.neutral, 0x1091f, 27}, + EAWEntry{.neutral, 0x1093f, 1}, + EAWEntry{.neutral, 0x10980, 56}, + EAWEntry{.neutral, 0x109bc, 20}, + EAWEntry{.neutral, 0x109d2, 50}, + EAWEntry{.neutral, 0x10a05, 2}, + EAWEntry{.neutral, 0x10a0c, 8}, + EAWEntry{.neutral, 0x10a15, 3}, + EAWEntry{.neutral, 0x10a19, 29}, + EAWEntry{.neutral, 0x10a38, 3}, + EAWEntry{.neutral, 0x10a3f, 10}, + EAWEntry{.neutral, 0x10a50, 9}, + EAWEntry{.neutral, 0x10a60, 64}, + EAWEntry{.neutral, 0x10ac0, 39}, + EAWEntry{.neutral, 0x10aeb, 12}, + EAWEntry{.neutral, 0x10b00, 54}, + EAWEntry{.neutral, 0x10b39, 29}, + EAWEntry{.neutral, 0x10b58, 27}, + EAWEntry{.neutral, 0x10b78, 26}, + EAWEntry{.neutral, 0x10b99, 4}, + EAWEntry{.neutral, 0x10ba9, 7}, + EAWEntry{.neutral, 0x10c00, 73}, + EAWEntry{.neutral, 0x10c80, 51}, + EAWEntry{.neutral, 0x10cc0, 51}, + EAWEntry{.neutral, 0x10cfa, 46}, + EAWEntry{.neutral, 0x10d30, 10}, + EAWEntry{.neutral, 0x10e60, 31}, + EAWEntry{.neutral, 0x10e80, 42}, + EAWEntry{.neutral, 0x10eab, 3}, + EAWEntry{.neutral, 0x10eb0, 2}, + EAWEntry{.neutral, 0x10f00, 40}, + EAWEntry{.neutral, 0x10f30, 42}, + EAWEntry{.neutral, 0x10fb0, 28}, + EAWEntry{.neutral, 0x10fe0, 23}, + EAWEntry{.neutral, 0x11000, 78}, + EAWEntry{.neutral, 0x11052, 30}, + EAWEntry{.neutral, 0x1107f, 67}, + EAWEntry{.neutral, 0x110cd, 1}, + EAWEntry{.neutral, 0x110d0, 25}, + EAWEntry{.neutral, 0x110f0, 10}, + EAWEntry{.neutral, 0x11100, 53}, + EAWEntry{.neutral, 0x11136, 18}, + EAWEntry{.neutral, 0x11150, 39}, + EAWEntry{.neutral, 0x11180, 96}, + EAWEntry{.neutral, 0x111e1, 20}, + EAWEntry{.neutral, 0x11200, 18}, + EAWEntry{.neutral, 0x11213, 44}, + EAWEntry{.neutral, 0x11280, 7}, + EAWEntry{.neutral, 0x11288, 1}, + EAWEntry{.neutral, 0x1128a, 4}, + EAWEntry{.neutral, 0x1128f, 15}, + EAWEntry{.neutral, 0x1129f, 11}, + EAWEntry{.neutral, 0x112b0, 59}, + EAWEntry{.neutral, 0x112f0, 10}, + EAWEntry{.neutral, 0x11300, 4}, + EAWEntry{.neutral, 0x11305, 8}, + EAWEntry{.neutral, 0x1130f, 2}, + EAWEntry{.neutral, 0x11313, 22}, + EAWEntry{.neutral, 0x1132a, 7}, + EAWEntry{.neutral, 0x11332, 2}, + EAWEntry{.neutral, 0x11335, 5}, + EAWEntry{.neutral, 0x1133b, 10}, + EAWEntry{.neutral, 0x11347, 2}, + EAWEntry{.neutral, 0x1134b, 3}, + EAWEntry{.neutral, 0x11350, 1}, + EAWEntry{.neutral, 0x11357, 1}, + EAWEntry{.neutral, 0x1135d, 7}, + EAWEntry{.neutral, 0x11366, 7}, + EAWEntry{.neutral, 0x11370, 5}, + EAWEntry{.neutral, 0x11400, 92}, + EAWEntry{.neutral, 0x1145d, 5}, + EAWEntry{.neutral, 0x11480, 72}, + EAWEntry{.neutral, 0x114d0, 10}, + EAWEntry{.neutral, 0x11580, 54}, + EAWEntry{.neutral, 0x115b8, 38}, + EAWEntry{.neutral, 0x11600, 69}, + EAWEntry{.neutral, 0x11650, 10}, + EAWEntry{.neutral, 0x11660, 13}, + EAWEntry{.neutral, 0x11680, 57}, + EAWEntry{.neutral, 0x116c0, 10}, + EAWEntry{.neutral, 0x11700, 27}, + EAWEntry{.neutral, 0x1171d, 15}, + EAWEntry{.neutral, 0x11730, 16}, + EAWEntry{.neutral, 0x11800, 60}, + EAWEntry{.neutral, 0x118a0, 83}, + EAWEntry{.neutral, 0x118ff, 8}, + EAWEntry{.neutral, 0x11909, 1}, + EAWEntry{.neutral, 0x1190c, 8}, + EAWEntry{.neutral, 0x11915, 2}, + EAWEntry{.neutral, 0x11918, 30}, + EAWEntry{.neutral, 0x11937, 2}, + EAWEntry{.neutral, 0x1193b, 12}, + EAWEntry{.neutral, 0x11950, 10}, + EAWEntry{.neutral, 0x119a0, 8}, + EAWEntry{.neutral, 0x119aa, 46}, + EAWEntry{.neutral, 0x119da, 11}, + EAWEntry{.neutral, 0x11a00, 72}, + EAWEntry{.neutral, 0x11a50, 83}, + EAWEntry{.neutral, 0x11ac0, 57}, + EAWEntry{.neutral, 0x11c00, 9}, + EAWEntry{.neutral, 0x11c0a, 45}, + EAWEntry{.neutral, 0x11c38, 14}, + EAWEntry{.neutral, 0x11c50, 29}, + EAWEntry{.neutral, 0x11c70, 32}, + EAWEntry{.neutral, 0x11c92, 22}, + EAWEntry{.neutral, 0x11ca9, 14}, + EAWEntry{.neutral, 0x11d00, 7}, + EAWEntry{.neutral, 0x11d08, 2}, + EAWEntry{.neutral, 0x11d0b, 44}, + EAWEntry{.neutral, 0x11d3a, 1}, + EAWEntry{.neutral, 0x11d3c, 2}, + EAWEntry{.neutral, 0x11d3f, 9}, + EAWEntry{.neutral, 0x11d50, 10}, + EAWEntry{.neutral, 0x11d60, 6}, + EAWEntry{.neutral, 0x11d67, 2}, + EAWEntry{.neutral, 0x11d6a, 37}, + EAWEntry{.neutral, 0x11d90, 2}, + EAWEntry{.neutral, 0x11d93, 6}, + EAWEntry{.neutral, 0x11da0, 10}, + EAWEntry{.neutral, 0x11ee0, 25}, + EAWEntry{.neutral, 0x11fb0, 1}, + EAWEntry{.neutral, 0x11fc0, 50}, + EAWEntry{.neutral, 0x11fff, 923}, + EAWEntry{.neutral, 0x12400, 111}, + EAWEntry{.neutral, 0x12470, 5}, + EAWEntry{.neutral, 0x12480, 196}, + EAWEntry{.neutral, 0x13000, 1071}, + EAWEntry{.neutral, 0x13430, 9}, + EAWEntry{.neutral, 0x14400, 583}, + EAWEntry{.neutral, 0x16800, 569}, + EAWEntry{.neutral, 0x16a40, 31}, + EAWEntry{.neutral, 0x16a60, 10}, + EAWEntry{.neutral, 0x16a6e, 2}, + EAWEntry{.neutral, 0x16ad0, 30}, + EAWEntry{.neutral, 0x16af0, 6}, + EAWEntry{.neutral, 0x16b00, 70}, + EAWEntry{.neutral, 0x16b50, 10}, + EAWEntry{.neutral, 0x16b5b, 7}, + EAWEntry{.neutral, 0x16b63, 21}, + EAWEntry{.neutral, 0x16b7d, 19}, + EAWEntry{.neutral, 0x16e40, 91}, + EAWEntry{.neutral, 0x16f00, 75}, + EAWEntry{.neutral, 0x16f4f, 57}, + EAWEntry{.neutral, 0x16f8f, 17}, + EAWEntry{.wide, 0x16fe0, 5}, + EAWEntry{.wide, 0x16ff0, 2}, + EAWEntry{.wide, 0x17000, 6136}, + EAWEntry{.wide, 0x18800, 1238}, + EAWEntry{.wide, 0x18d00, 9}, + EAWEntry{.wide, 0x1b000, 287}, + EAWEntry{.wide, 0x1b150, 3}, + EAWEntry{.wide, 0x1b164, 4}, + EAWEntry{.wide, 0x1b170, 396}, + EAWEntry{.neutral, 0x1bc00, 107}, + EAWEntry{.neutral, 0x1bc70, 13}, + EAWEntry{.neutral, 0x1bc80, 9}, + EAWEntry{.neutral, 0x1bc90, 10}, + EAWEntry{.neutral, 0x1bc9c, 8}, + EAWEntry{.neutral, 0x1d000, 246}, + EAWEntry{.neutral, 0x1d100, 39}, + EAWEntry{.neutral, 0x1d129, 192}, + EAWEntry{.neutral, 0x1d200, 70}, + EAWEntry{.neutral, 0x1d2e0, 20}, + EAWEntry{.neutral, 0x1d300, 87}, + EAWEntry{.neutral, 0x1d360, 25}, + EAWEntry{.neutral, 0x1d400, 85}, + EAWEntry{.neutral, 0x1d456, 71}, + EAWEntry{.neutral, 0x1d49e, 2}, + EAWEntry{.neutral, 0x1d4a2, 1}, + EAWEntry{.neutral, 0x1d4a5, 2}, + EAWEntry{.neutral, 0x1d4a9, 4}, + EAWEntry{.neutral, 0x1d4ae, 12}, + EAWEntry{.neutral, 0x1d4bb, 1}, + EAWEntry{.neutral, 0x1d4bd, 7}, + EAWEntry{.neutral, 0x1d4c5, 65}, + EAWEntry{.neutral, 0x1d507, 4}, + EAWEntry{.neutral, 0x1d50d, 8}, + EAWEntry{.neutral, 0x1d516, 7}, + EAWEntry{.neutral, 0x1d51e, 28}, + EAWEntry{.neutral, 0x1d53b, 4}, + EAWEntry{.neutral, 0x1d540, 5}, + EAWEntry{.neutral, 0x1d546, 1}, + EAWEntry{.neutral, 0x1d54a, 7}, + EAWEntry{.neutral, 0x1d552, 340}, + EAWEntry{.neutral, 0x1d6a8, 292}, + EAWEntry{.neutral, 0x1d7ce, 702}, + EAWEntry{.neutral, 0x1da9b, 5}, + EAWEntry{.neutral, 0x1daa1, 15}, + EAWEntry{.neutral, 0x1e000, 7}, + EAWEntry{.neutral, 0x1e008, 17}, + EAWEntry{.neutral, 0x1e01b, 7}, + EAWEntry{.neutral, 0x1e023, 2}, + EAWEntry{.neutral, 0x1e026, 5}, + EAWEntry{.neutral, 0x1e100, 45}, + EAWEntry{.neutral, 0x1e130, 14}, + EAWEntry{.neutral, 0x1e140, 10}, + EAWEntry{.neutral, 0x1e14e, 2}, + EAWEntry{.neutral, 0x1e2c0, 58}, + EAWEntry{.neutral, 0x1e2ff, 1}, + EAWEntry{.neutral, 0x1e800, 197}, + EAWEntry{.neutral, 0x1e8c7, 16}, + EAWEntry{.neutral, 0x1e900, 76}, + EAWEntry{.neutral, 0x1e950, 10}, + EAWEntry{.neutral, 0x1e95e, 2}, + EAWEntry{.neutral, 0x1ec71, 68}, + EAWEntry{.neutral, 0x1ed01, 61}, + EAWEntry{.neutral, 0x1ee00, 4}, + EAWEntry{.neutral, 0x1ee05, 27}, + EAWEntry{.neutral, 0x1ee21, 2}, + EAWEntry{.neutral, 0x1ee24, 1}, + EAWEntry{.neutral, 0x1ee27, 1}, + EAWEntry{.neutral, 0x1ee29, 10}, + EAWEntry{.neutral, 0x1ee34, 4}, + EAWEntry{.neutral, 0x1ee39, 1}, + EAWEntry{.neutral, 0x1ee3b, 1}, + EAWEntry{.neutral, 0x1ee42, 1}, + EAWEntry{.neutral, 0x1ee47, 1}, + EAWEntry{.neutral, 0x1ee49, 1}, + EAWEntry{.neutral, 0x1ee4b, 1}, + EAWEntry{.neutral, 0x1ee4d, 3}, + EAWEntry{.neutral, 0x1ee51, 2}, + EAWEntry{.neutral, 0x1ee54, 1}, + EAWEntry{.neutral, 0x1ee57, 1}, + EAWEntry{.neutral, 0x1ee59, 1}, + EAWEntry{.neutral, 0x1ee5b, 1}, + EAWEntry{.neutral, 0x1ee5d, 1}, + EAWEntry{.neutral, 0x1ee5f, 1}, + EAWEntry{.neutral, 0x1ee61, 2}, + EAWEntry{.neutral, 0x1ee64, 1}, + EAWEntry{.neutral, 0x1ee67, 4}, + EAWEntry{.neutral, 0x1ee6c, 7}, + EAWEntry{.neutral, 0x1ee74, 4}, + EAWEntry{.neutral, 0x1ee79, 4}, + EAWEntry{.neutral, 0x1ee7e, 1}, + EAWEntry{.neutral, 0x1ee80, 10}, + EAWEntry{.neutral, 0x1ee8b, 17}, + EAWEntry{.neutral, 0x1eea1, 3}, + EAWEntry{.neutral, 0x1eea5, 5}, + EAWEntry{.neutral, 0x1eeab, 17}, + EAWEntry{.neutral, 0x1eef0, 2}, + EAWEntry{.neutral, 0x1f000, 4}, + EAWEntry{.wide, 0x1f004, 1}, + EAWEntry{.neutral, 0x1f005, 39}, + EAWEntry{.neutral, 0x1f030, 100}, + EAWEntry{.neutral, 0x1f0a0, 15}, + EAWEntry{.neutral, 0x1f0b1, 15}, + EAWEntry{.neutral, 0x1f0c1, 14}, + EAWEntry{.wide, 0x1f0cf, 1}, + EAWEntry{.neutral, 0x1f0d1, 37}, + EAWEntry{.ambiguous, 0x1f100, 11}, + EAWEntry{.neutral, 0x1f10b, 5}, + EAWEntry{.ambiguous, 0x1f110, 30}, + EAWEntry{.neutral, 0x1f12e, 2}, + EAWEntry{.ambiguous, 0x1f130, 58}, + EAWEntry{.neutral, 0x1f16a, 6}, + EAWEntry{.ambiguous, 0x1f170, 30}, + EAWEntry{.wide, 0x1f18e, 1}, + EAWEntry{.ambiguous, 0x1f18f, 2}, + EAWEntry{.wide, 0x1f191, 10}, + EAWEntry{.ambiguous, 0x1f19b, 18}, + EAWEntry{.neutral, 0x1f1ad, 1}, + EAWEntry{.neutral, 0x1f1e6, 26}, + EAWEntry{.wide, 0x1f200, 3}, + EAWEntry{.wide, 0x1f210, 44}, + EAWEntry{.wide, 0x1f240, 9}, + EAWEntry{.wide, 0x1f250, 2}, + EAWEntry{.wide, 0x1f260, 6}, + EAWEntry{.wide, 0x1f300, 33}, + EAWEntry{.neutral, 0x1f321, 12}, + EAWEntry{.wide, 0x1f32d, 9}, + EAWEntry{.neutral, 0x1f336, 1}, + EAWEntry{.wide, 0x1f337, 70}, + EAWEntry{.neutral, 0x1f37d, 1}, + EAWEntry{.wide, 0x1f37e, 22}, + EAWEntry{.neutral, 0x1f394, 12}, + EAWEntry{.wide, 0x1f3a0, 43}, + EAWEntry{.neutral, 0x1f3cb, 4}, + EAWEntry{.wide, 0x1f3cf, 5}, + EAWEntry{.neutral, 0x1f3d4, 12}, + EAWEntry{.wide, 0x1f3e0, 17}, + EAWEntry{.neutral, 0x1f3f1, 3}, + EAWEntry{.wide, 0x1f3f4, 1}, + EAWEntry{.neutral, 0x1f3f5, 3}, + EAWEntry{.wide, 0x1f3f8, 71}, + EAWEntry{.neutral, 0x1f43f, 1}, + EAWEntry{.wide, 0x1f440, 1}, + EAWEntry{.neutral, 0x1f441, 1}, + EAWEntry{.wide, 0x1f442, 187}, + EAWEntry{.neutral, 0x1f4fd, 2}, + EAWEntry{.wide, 0x1f4ff, 63}, + EAWEntry{.neutral, 0x1f53e, 13}, + EAWEntry{.wide, 0x1f54b, 4}, + EAWEntry{.neutral, 0x1f54f, 1}, + EAWEntry{.wide, 0x1f550, 24}, + EAWEntry{.neutral, 0x1f568, 18}, + EAWEntry{.wide, 0x1f57a, 1}, + EAWEntry{.neutral, 0x1f57b, 26}, + EAWEntry{.wide, 0x1f595, 2}, + EAWEntry{.neutral, 0x1f597, 13}, + EAWEntry{.wide, 0x1f5a4, 1}, + EAWEntry{.neutral, 0x1f5a5, 86}, + EAWEntry{.wide, 0x1f5fb, 85}, + EAWEntry{.neutral, 0x1f650, 48}, + EAWEntry{.wide, 0x1f680, 70}, + EAWEntry{.neutral, 0x1f6c6, 6}, + EAWEntry{.wide, 0x1f6cc, 1}, + EAWEntry{.neutral, 0x1f6cd, 3}, + EAWEntry{.wide, 0x1f6d0, 3}, + EAWEntry{.neutral, 0x1f6d3, 2}, + EAWEntry{.wide, 0x1f6d5, 3}, + EAWEntry{.neutral, 0x1f6e0, 11}, + EAWEntry{.wide, 0x1f6eb, 2}, + EAWEntry{.neutral, 0x1f6f0, 4}, + EAWEntry{.wide, 0x1f6f4, 9}, + EAWEntry{.neutral, 0x1f700, 116}, + EAWEntry{.neutral, 0x1f780, 89}, + EAWEntry{.wide, 0x1f7e0, 12}, + EAWEntry{.neutral, 0x1f800, 12}, + EAWEntry{.neutral, 0x1f810, 56}, + EAWEntry{.neutral, 0x1f850, 10}, + EAWEntry{.neutral, 0x1f860, 40}, + EAWEntry{.neutral, 0x1f890, 30}, + EAWEntry{.neutral, 0x1f8b0, 2}, + EAWEntry{.neutral, 0x1f900, 12}, + EAWEntry{.wide, 0x1f90c, 47}, + EAWEntry{.neutral, 0x1f93b, 1}, + EAWEntry{.wide, 0x1f93c, 10}, + EAWEntry{.neutral, 0x1f946, 1}, + EAWEntry{.wide, 0x1f947, 50}, + EAWEntry{.wide, 0x1f97a, 82}, + EAWEntry{.wide, 0x1f9cd, 51}, + EAWEntry{.neutral, 0x1fa00, 84}, + EAWEntry{.neutral, 0x1fa60, 14}, + EAWEntry{.wide, 0x1fa70, 5}, + EAWEntry{.wide, 0x1fa78, 3}, + EAWEntry{.wide, 0x1fa80, 7}, + EAWEntry{.wide, 0x1fa90, 25}, + EAWEntry{.wide, 0x1fab0, 7}, + EAWEntry{.wide, 0x1fac0, 3}, + EAWEntry{.wide, 0x1fad0, 7}, + EAWEntry{.neutral, 0x1fb00, 147}, + EAWEntry{.neutral, 0x1fb94, 55}, + EAWEntry{.neutral, 0x1fbf0, 10}, + EAWEntry{.wide, 0x20000, 65534}, + EAWEntry{.wide, 0x30000, 65534}, + EAWEntry{.neutral, 0xe0001, 1}, + EAWEntry{.neutral, 0xe0020, 96}, + EAWEntry{.ambiguous, 0xe0100, 240}, + EAWEntry{.ambiguous, 0xf0000, 65534}, + EAWEntry{.ambiguous, 0x100000, 65534}, + ] +) diff --git a/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width_test.v b/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width_test.v new file mode 100644 index 0000000..a44a9f8 --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/east_asian/east_asian_width_test.v @@ -0,0 +1,23 @@ +module east_asian + +fn test_width() { + assert east_asian_width_property_at('A', 0) == .narrow + assert east_asian_width_property_at('A', 0) == .full + assert east_asian_width_property_at('ア', 0) == .half + assert east_asian_width_property_at('ア', 0) == .wide + assert east_asian_width_property_at('☆', 0) == .ambiguous + assert east_asian_width_property_at('ج', 0) == .neutral + assert display_width('abc', 1) == 3 + assert display_width('ひらがな', 1) == 8 + assert display_width('カタカナ', 1) == 8 + assert display_width('カタカナ', 1) == 4 + assert display_width('한글', 1) == 4 + assert display_width('한자', 1) == 4 + assert display_width('漢字', 1) == 4 + assert display_width('简体字', 1) == 6 + assert display_width('繁體字', 1) == 6 + assert display_width('अरबी लिपि', 1) == 9 + assert display_width('☆', 1) == 1 + assert display_width('☆', 2) == 2 + assert display_width('🐈👽📛', 1) == 6 +} diff --git a/v_windows/v/vlib/encoding/utf8/encoding_utf8_test.v b/v_windows/v/vlib/encoding/utf8/encoding_utf8_test.v new file mode 100644 index 0000000..ebea87c --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/encoding_utf8_test.v @@ -0,0 +1,9 @@ +import encoding.utf8 + +fn test_validate_str() { + assert utf8.validate_str('añçá') == true + assert utf8.validate_str('\x61\xC3\xB1\xC3\xA7\xC3\xA1') == true + assert utf8.validate_str('\xC0\xC1') == false + assert utf8.validate_str('\xF5\xFF') == false + assert utf8.validate_str('\xE0\xEF') == false +} diff --git a/v_windows/v/vlib/encoding/utf8/utf8.v b/v_windows/v/vlib/encoding/utf8/utf8.v new file mode 100644 index 0000000..88c598f --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/utf8.v @@ -0,0 +1,88 @@ +module utf8 + +struct Utf8State { +mut: + index int + subindex int + failed bool +} + +pub fn validate_str(str string) bool { + return validate(str.str, str.len) +} + +pub fn validate(data &byte, len int) bool { + mut state := Utf8State{} + for i := 0; i < len; i++ { + s := unsafe { data[i] } + if s == 0 { + break + } + state.next_state(s) + if state.failed { + return false + } + } + return !state.failed && state.subindex <= 0 +} + +fn (mut s Utf8State) seq(r0 bool, r1 bool, is_tail bool) bool { + if s.subindex == 0 || (s.index > 1 && s.subindex == 1) || (s.index >= 6 && s.subindex == 2) { + if (s.subindex == 0 && r0) || (s.subindex == 1 && r1) || (s.subindex == 2 && is_tail) { + s.subindex++ + return true + } + } else { + s.failed = true + if is_tail { + s.index = 0 + s.subindex = 0 + s.failed = false + } + return true + } + s.index++ + s.subindex = 0 + return false +} + +fn (mut s Utf8State) next_state(c byte) { + // sequence 1 + if s.index == 0 { + if (c >= 0x00 + 1 && c <= 0x7F) || c == 0x00 { + return + } + s.index++ + s.subindex = 0 + } + is_tail := c >= 0x80 && c <= 0xBF + // sequence 2 + if s.index == 1 && s.seq(c >= 0xC2 && c <= 0xDF, false, is_tail) { + return + } + // sequence 3 + if s.index == 2 && s.seq(c == 0xE0, c >= 0xA0 && c <= 0xBF, is_tail) { + return + } + if s.index == 3 && s.seq(c >= 0xE1 && c <= 0xEC, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + if s.index == 4 && s.seq(c == 0xED, c >= 0x80 && c <= 0x9F, is_tail) { + return + } + if s.index == 5 && s.seq(c >= 0xEE && c <= 0xEF, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + // sequence 4 + if s.index == 6 && s.seq(c == 0xF0, c >= 0x90 && c <= 0xBF, is_tail) { + return + } + if s.index == 7 && s.seq(c >= 0xF1 && c <= 0xF3, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + if s.index == 8 && s.seq(c == 0xF4, c >= 0x80 && c <= 0x8F, is_tail) { + return + } + // we should never reach here + s.failed = true +} diff --git a/v_windows/v/vlib/encoding/utf8/utf8_util.v b/v_windows/v/vlib/encoding/utf8/utf8_util.v new file mode 100644 index 0000000..2e3da0d --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/utf8_util.v @@ -0,0 +1,1161 @@ +/* +utf-8 util + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains utilities for utf8 strings +*/ +module utf8 + +/* +Utility functions +*/ + +// len return the length as number of unicode chars from a string +pub fn len(s string) int { + if s.len == 0 { + return 0 + } + + mut count := 0 + mut index := 0 + + for { + ch_len := utf8_char_len(s[index]) + index += ch_len + count++ + if index >= s.len { + break + } + } + return count +} + +// get_uchar convert a unicode glyph in string[index] into a int unicode char +pub fn get_uchar(s string, index int) int { + mut res := 0 + mut ch_len := 0 + if s.len > 0 { + ch_len = utf8_char_len(s[index]) + + if ch_len == 1 { + return u16(s[index]) + } + if ch_len > 1 && ch_len < 5 { + mut lword := 0 + for i := 0; i < ch_len; i++ { + lword = (lword << 8) | int(s[index + i]) + } + + // 2 byte utf-8 + // byte format: 110xxxxx 10xxxxxx + // + if ch_len == 2 { + res = (lword & 0x1f00) >> 2 | (lword & 0x3f) + } + // 3 byte utf-8 + // byte format: 1110xxxx 10xxxxxx 10xxxxxx + // + else if ch_len == 3 { + res = (lword & 0x0f0000) >> 4 | (lword & 0x3f00) >> 2 | (lword & 0x3f) + } + // 4 byte utf-8 + // byte format: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + else if ch_len == 4 { + res = ((lword & 0x07000000) >> 6) | ((lword & 0x003f0000) >> 4) | ((lword & 0x00003F00) >> 2) | (lword & 0x0000003f) + } + } + } + return res +} + +// raw_index - get the raw chracter from the string by the given index value. +// example: '我是V Lang'.raw_index(1) => '是' + +// raw_index - get the raw chracter from the string by the given index value. +// example: utf8.raw_index('我是V Lang', 1) => '是' +pub fn raw_index(s string, index int) string { + mut r := []rune{} + + for i := 0; i < s.len; i++ { + if r.len - 1 == index { + break + } + + b := s[i] + ch_len := ((0xe5000000 >> ((b >> 3) & 0x1e)) & 3) + + r << if ch_len > 0 { + i += ch_len + rune(get_uchar(s, i - ch_len)) + } else { + rune(b) + } + } + + return r[index].str() +} + +// reverse - returns a reversed string. +// example: utf8.reverse('你好世界hello world') => 'dlrow olleh界世好你'. +pub fn reverse(s string) string { + len_s := len(s) + if len_s == 0 || len_s == 1 { + return s.clone() + } + mut str_array := []string{} + for i in 0 .. len_s { + str_array << raw_index(s, i) + } + str_array = str_array.reverse() + return str_array.join('') +} + +/* +Conversion functions +*/ + +// to_upper return an uppercase string from a string +pub fn to_upper(s string) string { + return up_low(s, true) +} + +// to_lower return an lowercase string from a string +pub fn to_lower(s string) string { + return up_low(s, false) +} + +/* +Punctuation functions + +The "western" function search on a small table, that is quicker than +the global unicode table search. **Use only for western chars**. +*/ + +// +// Western +// + +// is_punct return true if the string[index] byte is the start of a unicode western punctuation +pub fn is_punct(s string, index int) bool { + return is_uchar_punct(get_uchar(s, index)) +} + +// is_uchar_punct return true if the input unicode is a western unicode punctuation +pub fn is_uchar_punct(uchar int) bool { + return find_punct_in_table(uchar, utf8.unicode_punct_western) != 0 +} + +// +// Global +// + +// is_global_punct return true if the string[index] byte of is the start of a global unicode punctuation +pub fn is_global_punct(s string, index int) bool { + return is_uchar_global_punct(get_uchar(s, index)) +} + +// is_uchar_global_punct return true if the input unicode is a global unicode punctuation +pub fn is_uchar_global_punct(uchar int) bool { + return find_punct_in_table(uchar, utf8.unicode_punct) != 0 +} + +/* +Private functions +*/ + +// Raw to_lower utf-8 function +fn utf8_to_lower(in_cp int) int { + mut cp := in_cp + if ((0x0041 <= cp) && (0x005a >= cp)) || ((0x00c0 <= cp) && (0x00d6 >= cp)) + || ((0x00d8 <= cp) && (0x00de >= cp)) || ((0x0391 <= cp) && (0x03a1 >= cp)) + || ((0x03a3 <= cp) && (0x03ab >= cp)) || ((0x0410 <= cp) && (0x042f >= cp)) { + cp += 32 + } else if (0x0400 <= cp) && (0x040f >= cp) { + cp += 80 + } else if ((0x0100 <= cp) && (0x012f >= cp)) || ((0x0132 <= cp) && (0x0137 >= cp)) + || ((0x014a <= cp) && (0x0177 >= cp)) || ((0x0182 <= cp) && (0x0185 >= cp)) + || ((0x01a0 <= cp) && (0x01a5 >= cp)) || ((0x01de <= cp) && (0x01ef >= cp)) + || ((0x01f8 <= cp) && (0x021f >= cp)) || ((0x0222 <= cp) && (0x0233 >= cp)) + || ((0x0246 <= cp) && (0x024f >= cp)) || ((0x03d8 <= cp) && (0x03ef >= cp)) + || ((0x0460 <= cp) && (0x0481 >= cp)) || ((0x048a <= cp) && (0x04ff >= cp)) { + cp |= 0x1 + } else if ((0x0139 <= cp) && (0x0148 >= cp)) || ((0x0179 <= cp) && (0x017e >= cp)) + || ((0x01af <= cp) && (0x01b0 >= cp)) || ((0x01b3 <= cp) && (0x01b6 >= cp)) + || ((0x01cd <= cp) && (0x01dc >= cp)) { + cp += 1 + cp &= ~0x1 + } else if ((0x0531 <= cp) && (0x0556 >= cp)) || ((0x10A0 <= cp) && (0x10C5 >= cp)) { + // ARMENIAN or GEORGIAN + cp += 0x30 + } else if (((0x1E00 <= cp) && (0x1E94 >= cp)) || ((0x1EA0 <= cp) && (0x1EF8 >= cp))) + && (cp & 1 == 0) { + // LATIN CAPITAL LETTER + cp += 1 + } else if (0x24B6 <= cp) && (0x24CF >= cp) { + // CIRCLED LATIN + cp += 0x1a + } else if (0xFF21 <= cp) && (0xFF3A >= cp) { + // FULLWIDTH LATIN CAPITAL + cp += 0x19 + } else if ((0x1F08 <= cp) && (0x1F0F >= cp)) || ((0x1F18 <= cp) && (0x1F1D >= cp)) + || ((0x1F28 <= cp) && (0x1F2F >= cp)) || ((0x1F38 <= cp) && (0x1F3F >= cp)) + || ((0x1F48 <= cp) && (0x1F4D >= cp)) || ((0x1F68 <= cp) && (0x1F6F >= cp)) + || ((0x1F88 <= cp) && (0x1F8F >= cp)) || ((0x1F98 <= cp) && (0x1F9F >= cp)) + || ((0x1FA8 <= cp) && (0x1FAF >= cp)) { + // GREEK + cp -= 8 + } else { + match cp { + 0x0178 { cp = 0x00ff } + 0x0243 { cp = 0x0180 } + 0x018e { cp = 0x01dd } + 0x023d { cp = 0x019a } + 0x0220 { cp = 0x019e } + 0x01b7 { cp = 0x0292 } + 0x01c4 { cp = 0x01c6 } + 0x01c7 { cp = 0x01c9 } + 0x01ca { cp = 0x01cc } + 0x01f1 { cp = 0x01f3 } + 0x01f7 { cp = 0x01bf } + 0x0187 { cp = 0x0188 } + 0x018b { cp = 0x018c } + 0x0191 { cp = 0x0192 } + 0x0198 { cp = 0x0199 } + 0x01a7 { cp = 0x01a8 } + 0x01ac { cp = 0x01ad } + 0x01af { cp = 0x01b0 } + 0x01b8 { cp = 0x01b9 } + 0x01bc { cp = 0x01bd } + 0x01f4 { cp = 0x01f5 } + 0x023b { cp = 0x023c } + 0x0241 { cp = 0x0242 } + 0x03fd { cp = 0x037b } + 0x03fe { cp = 0x037c } + 0x03ff { cp = 0x037d } + 0x037f { cp = 0x03f3 } + 0x0386 { cp = 0x03ac } + 0x0388 { cp = 0x03ad } + 0x0389 { cp = 0x03ae } + 0x038a { cp = 0x03af } + 0x038c { cp = 0x03cc } + 0x038e { cp = 0x03cd } + 0x038f { cp = 0x03ce } + 0x0370 { cp = 0x0371 } + 0x0372 { cp = 0x0373 } + 0x0376 { cp = 0x0377 } + 0x03f4 { cp = 0x03b8 } + 0x03cf { cp = 0x03d7 } + 0x03f9 { cp = 0x03f2 } + 0x03f7 { cp = 0x03f8 } + 0x03fa { cp = 0x03fb } + // GREEK + 0x1F59 { cp = 0x1F51 } + 0x1F5B { cp = 0x1F53 } + 0x1F5D { cp = 0x1F55 } + 0x1F5F { cp = 0x1F57 } + 0x1FB8 { cp = 0x1FB0 } + 0x1FB9 { cp = 0x1FB1 } + 0x1FD8 { cp = 0x1FD0 } + 0x1FD9 { cp = 0x1FD1 } + 0x1FE8 { cp = 0x1FE0 } + 0x1FE9 { cp = 0x1FE1 } + else {} + } + } + + return cp +} + +// Raw to_upper utf-8 function +fn utf8_to_upper(in_cp int) int { + mut cp := in_cp + if ((0x0061 <= cp) && (0x007a >= cp)) || ((0x00e0 <= cp) && (0x00f6 >= cp)) + || ((0x00f8 <= cp) && (0x00fe >= cp)) || ((0x03b1 <= cp) && (0x03c1 >= cp)) + || ((0x03c3 <= cp) && (0x03cb >= cp)) || ((0x0430 <= cp) && (0x044f >= cp)) { + cp -= 32 + } else if (0x0450 <= cp) && (0x045f >= cp) { + cp -= 80 + } else if ((0x0100 <= cp) && (0x012f >= cp)) || ((0x0132 <= cp) && (0x0137 >= cp)) + || ((0x014a <= cp) && (0x0177 >= cp)) || ((0x0182 <= cp) && (0x0185 >= cp)) + || ((0x01a0 <= cp) && (0x01a5 >= cp)) || ((0x01de <= cp) && (0x01ef >= cp)) + || ((0x01f8 <= cp) && (0x021f >= cp)) || ((0x0222 <= cp) && (0x0233 >= cp)) + || ((0x0246 <= cp) && (0x024f >= cp)) || ((0x03d8 <= cp) && (0x03ef >= cp)) + || ((0x0460 <= cp) && (0x0481 >= cp)) || ((0x048a <= cp) && (0x04ff >= cp)) { + cp &= ~0x1 + } else if ((0x0139 <= cp) && (0x0148 >= cp)) || ((0x0179 <= cp) && (0x017e >= cp)) + || ((0x01af <= cp) && (0x01b0 >= cp)) || ((0x01b3 <= cp) && (0x01b6 >= cp)) + || ((0x01cd <= cp) && (0x01dc >= cp)) { + cp -= 1 + cp |= 0x1 + } else if ((0x0561 <= cp) && (0x0586 >= cp)) || ((0x10D0 <= cp) && (0x10F5 >= cp)) { + // ARMENIAN or GEORGIAN + cp -= 0x30 + } else if (((0x1E01 <= cp) && (0x1E95 >= cp)) || ((0x1EA1 <= cp) && (0x1EF9 >= cp))) + && (cp & 1 == 1) { + // LATIN CAPITAL LETTER + cp -= 1 + } else if (0x24D0 <= cp) && (0x24E9 >= cp) { + // CIRCLED LATIN + cp -= 0x1a + } else if (0xFF41 <= cp) && (0xFF5A >= cp) { + // FULLWIDTH LATIN CAPITAL + cp -= 0x19 + } else if ((0x1F00 <= cp) && (0x1F07 >= cp)) || ((0x1F10 <= cp) && (0x1F15 >= cp)) + || ((0x1F20 <= cp) && (0x1F27 >= cp)) || ((0x1F30 <= cp) && (0x1F37 >= cp)) + || ((0x1F40 <= cp) && (0x1F45 >= cp)) || ((0x1F60 <= cp) && (0x1F67 >= cp)) + || ((0x1F80 <= cp) && (0x1F87 >= cp)) || ((0x1F90 <= cp) && (0x1F97 >= cp)) + || ((0x1FA0 <= cp) && (0x1FA7 >= cp)) { + // GREEK + cp += 8 + } else { + match cp { + 0x00ff { cp = 0x0178 } + 0x0180 { cp = 0x0243 } + 0x01dd { cp = 0x018e } + 0x019a { cp = 0x023d } + 0x019e { cp = 0x0220 } + 0x0292 { cp = 0x01b7 } + 0x01c6 { cp = 0x01c4 } + 0x01c9 { cp = 0x01c7 } + 0x01cc { cp = 0x01ca } + 0x01f3 { cp = 0x01f1 } + 0x01bf { cp = 0x01f7 } + 0x0188 { cp = 0x0187 } + 0x018c { cp = 0x018b } + 0x0192 { cp = 0x0191 } + 0x0199 { cp = 0x0198 } + 0x01a8 { cp = 0x01a7 } + 0x01ad { cp = 0x01ac } + 0x01b0 { cp = 0x01af } + 0x01b9 { cp = 0x01b8 } + 0x01bd { cp = 0x01bc } + 0x01f5 { cp = 0x01f4 } + 0x023c { cp = 0x023b } + 0x0242 { cp = 0x0241 } + 0x037b { cp = 0x03fd } + 0x037c { cp = 0x03fe } + 0x037d { cp = 0x03ff } + 0x03f3 { cp = 0x037f } + 0x03ac { cp = 0x0386 } + 0x03ad { cp = 0x0388 } + 0x03ae { cp = 0x0389 } + 0x03af { cp = 0x038a } + 0x03cc { cp = 0x038c } + 0x03cd { cp = 0x038e } + 0x03ce { cp = 0x038f } + 0x0371 { cp = 0x0370 } + 0x0373 { cp = 0x0372 } + 0x0377 { cp = 0x0376 } + 0x03d1 { cp = 0x0398 } + 0x03d7 { cp = 0x03cf } + 0x03f2 { cp = 0x03f9 } + 0x03f8 { cp = 0x03f7 } + 0x03fb { cp = 0x03fa } + // GREEK + 0x1F51 { cp = 0x1F59 } + 0x1F53 { cp = 0x1F5B } + 0x1F55 { cp = 0x1F5D } + 0x1F57 { cp = 0x1F5F } + 0x1FB0 { cp = 0x1FB8 } + 0x1FB1 { cp = 0x1FB9 } + 0x1FD0 { cp = 0x1FD8 } + 0x1FD1 { cp = 0x1FD9 } + 0x1FE0 { cp = 0x1FE8 } + 0x1FE1 { cp = 0x1FE9 } + else {} + } + } + + return cp +} + +// +// if upper_flag == true then make low ==> upper conversion +// if upper_flag == false then make upper ==> low conversion +// +// up_low make the dirt job +fn up_low(s string, upper_flag bool) string { + mut index := 0 + mut tab_char := 0 + mut str_res := unsafe { malloc_noscan(s.len + 1) } + + for { + ch_len := utf8_char_len(s[index]) + + if ch_len == 1 { + if upper_flag == true { + unsafe { + str_res[index] = byte(C.toupper(s.str[index])) + } + } else { + unsafe { + str_res[index] = byte(C.tolower(s.str[index])) + } + } + } else if ch_len > 1 && ch_len < 5 { + mut lword := 0 + + for i := 0; i < ch_len; i++ { + lword = (lword << 8) | int(s[index + i]) + } + + // println("#${index} ($lword)") + + mut res := 0 + + // 2 byte utf-8 + // byte format: 110xxxxx 10xxxxxx + // + if ch_len == 2 { + res = (lword & 0x1f00) >> 2 | (lword & 0x3f) + } + // 3 byte utf-8 + // byte format: 1110xxxx 10xxxxxx 10xxxxxx + // + else if ch_len == 3 { + res = (lword & 0x0f0000) >> 4 | (lword & 0x3f00) >> 2 | (lword & 0x3f) + } + // 4 byte utf-8 + // byte format: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + else if ch_len == 4 { + res = ((lword & 0x07000000) >> 6) | ((lword & 0x003f0000) >> 4) | ((lword & 0x00003F00) >> 2) | (lword & 0x0000003f) + } + + // println("res: ${res.hex():8}") + + if upper_flag == false { + tab_char = utf8_to_lower(res) + } else { + tab_char = utf8_to_upper(res) + } + + if ch_len == 2 { + ch0 := byte((tab_char >> 6) & 0x1f) | 0xc0 // 110x xxxx + ch1 := byte((tab_char >> 0) & 0x3f) | 0x80 // 10xx xxxx + // C.printf("[%02x%02x] \n",ch0,ch1) + + unsafe { + str_res[index + 0] = ch0 + str_res[index + 1] = ch1 + } + //**************************************************************** + // BUG: doesn't compile, workaround use shitf to right of 0 bit + //**************************************************************** + // str_res[index + 1 ] = byte( tab_char & 0xbf ) // 1011 1111 + } else if ch_len == 3 { + ch0 := byte((tab_char >> 12) & 0x0f) | 0xe0 // 1110 xxxx + ch1 := byte((tab_char >> 6) & 0x3f) | 0x80 // 10xx xxxx + ch2 := byte((tab_char >> 0) & 0x3f) | 0x80 // 10xx xxxx + // C.printf("[%02x%02x%02x] \n",ch0,ch1,ch2) + + unsafe { + str_res[index + 0] = ch0 + str_res[index + 1] = ch1 + str_res[index + 2] = ch2 + } + } + // TODO: write if needed + else if ch_len == 4 { + // place holder!! + // at the present time simply copy the utf8 char + for i in 0 .. ch_len { + unsafe { + str_res[index + i] = s[index + i] + } + } + } + } else { + // other cases, just copy the string + for i in 0 .. ch_len { + unsafe { + str_res[index + i] = s[index + i] + } + } + } + + index += ch_len + + // we are done, exit the loop + if index >= s.len { + break + } + } + + // for c compatibility set the ending 0 + unsafe { + str_res[index] = 0 + // C.printf("str_res: %s\n--------------\n",str_res) + return tos(str_res, s.len) + } +} + +// find punct in lockup table +fn find_punct_in_table(in_code int, in_table []int) int { + // + // We will use a simple binary search + // + + mut first_index := 0 + mut last_index := (in_table.len) + mut index := 0 + mut x := 0 + + for { + index = (first_index + last_index) >> 1 + x = in_table[index] + // C.printf("(%d..%d) index:%d base[%08x]==>[%08x]\n",first_index,last_index,index,in_code,x) + + if x == in_code { + return index + } else if x > in_code { + last_index = index + } else { + first_index = index + } + + if (last_index - first_index) <= 1 { + break + } + } + // C.printf("not found.\n") + return 0 +} + +/* +Unicode punctuation chars + +source: http://www.unicode.org/faq/punctuation_symbols.html +*/ +const ( + // Western punctuation mark + // Character Name Browser Image + unicode_punct_western = [ + 0x0021 /* EXCLAMATION MARK ! */, + 0x0022 /* QUOTATION MARK " */, + 0x0027 /* APOSTROPHE ' */, + 0x002A /* ASTERISK * */, + 0x002C /* COMMA , */, + 0x002E /* FULL STOP . */, + 0x002F /* SOLIDUS / */, + 0x003A /* COLON : */, + 0x003B /* SEMICOLON ; */, + 0x003F /* QUESTION MARK ? */, + 0x00A1 /* INVERTED EXCLAMATION MARK ¡ */, + 0x00A7 /* SECTION SIGN § */, + 0x00B6 /* PILCROW SIGN ¶ */, + 0x00B7 /* MIDDLE DOT · */, + 0x00BF /* INVERTED QUESTION MARK ¿ */, + 0x037E /* GREEK QUESTION MARK ; */, + 0x0387 /* GREEK ANO TELEIA · */, + 0x055A /* ARMENIAN APOSTROPHE ՚ */, + 0x055B /* ARMENIAN EMPHASIS MARK ՛ */, + 0x055C /* ARMENIAN EXCLAMATION MARK ՜ */, + 0x055D /* ARMENIAN COMMA ՝ */, + 0x055E /* ARMENIAN QUESTION MARK ՞ */, + 0x055F /* ARMENIAN ABBREVIATION MARK ՟ */, + 0x0589 /* ARMENIAN FULL STOP ։ */, + 0x05C0 /* HEBREW PUNCTUATION PASEQ ׀ */, + 0x05C3 /* HEBREW PUNCTUATION SOF PASUQ ׃ */, + 0x05C6 /* HEBREW PUNCTUATION NUN HAFUKHA ׆ */, + 0x05F3 /* HEBREW PUNCTUATION GERESH ׳ */, + 0x05F4 /* HEBREW PUNCTUATION GERSHAYIM ״ */, + ] + + // Unicode Characters in the 'Punctuation, Other' Category + // Character Name Browser Image + unicode_punct = [ + 0x0021 /* EXCLAMATION MARK ! */, + 0x0022 /* QUOTATION MARK " */, + 0x0023 /* NUMBER SIGN # */, + 0x0025 /* PERCENT SIGN % */, + 0x0026 /* AMPERSAND & */, + 0x0027 /* APOSTROPHE ' */, + 0x002A /* ASTERISK * */, + 0x002C /* COMMA , */, + 0x002E /* FULL STOP . */, + 0x002F /* SOLIDUS / */, + 0x003A /* COLON : */, + 0x003B /* SEMICOLON ; */, + 0x003F /* QUESTION MARK ? */, + 0x0040 /* COMMERCIAL AT @ */, + 0x005C /* REVERSE SOLIDUS \ */, + 0x00A1 /* INVERTED EXCLAMATION MARK ¡ */, + 0x00A7 /* SECTION SIGN § */, + 0x00B6 /* PILCROW SIGN ¶ */, + 0x00B7 /* MIDDLE DOT · */, + 0x00BF /* INVERTED QUESTION MARK ¿ */, + 0x037E /* GREEK QUESTION MARK ; */, + 0x0387 /* GREEK ANO TELEIA · */, + 0x055A /* ARMENIAN APOSTROPHE ՚ */, + 0x055B /* ARMENIAN EMPHASIS MARK ՛ */, + 0x055C /* ARMENIAN EXCLAMATION MARK ՜ */, + 0x055D /* ARMENIAN COMMA ՝ */, + 0x055E /* ARMENIAN QUESTION MARK ՞ */, + 0x055F /* ARMENIAN ABBREVIATION MARK ՟ */, + 0x0589 /* ARMENIAN FULL STOP ։ */, + 0x05C0 /* HEBREW PUNCTUATION PASEQ ׀ */, + 0x05C3 /* HEBREW PUNCTUATION SOF PASUQ ׃ */, + 0x05C6 /* HEBREW PUNCTUATION NUN HAFUKHA ׆ */, + 0x05F3 /* HEBREW PUNCTUATION GERESH ׳ */, + 0x05F4 /* HEBREW PUNCTUATION GERSHAYIM ״ */, + 0x0609 /* ARABIC-INDIC PER MILLE SIGN ؉ */, + 0x060A /* ARABIC-INDIC PER TEN THOUSAND SIGN ؊ */, + 0x060C /* ARABIC COMMA ، */, + 0x060D /* ARABIC DATE SEPARATOR ؍ */, + 0x061B /* ARABIC SEMICOLON ؛ */, + 0x061E /* ARABIC TRIPLE DOT PUNCTUATION MARK ؞ */, + 0x061F /* ARABIC QUESTION MARK ؟ */, + 0x066A /* ARABIC PERCENT SIGN ٪ */, + 0x066B /* ARABIC DECIMAL SEPARATOR ٫ */, + 0x066C /* ARABIC THOUSANDS SEPARATOR ٬ */, + 0x066D /* ARABIC FIVE POINTED STAR ٭ */, + 0x06D4 /* ARABIC FULL STOP ۔ */, + 0x0700 /* SYRIAC END OF PARAGRAPH ܀ */, + 0x0701 /* SYRIAC SUPRALINEAR FULL STOP ܁ */, + 0x0702 /* SYRIAC SUBLINEAR FULL STOP ܂ */, + 0x0703 /* SYRIAC SUPRALINEAR COLON ܃ */, + 0x0704 /* SYRIAC SUBLINEAR COLON ܄ */, + 0x0705 /* SYRIAC HORIZONTAL COLON ܅ */, + 0x0706 /* SYRIAC COLON SKEWED LEFT ܆ */, + 0x0707 /* SYRIAC COLON SKEWED RIGHT ܇ */, + 0x0708 /* SYRIAC SUPRALINEAR COLON SKEWED LEFT ܈ */, + 0x0709 /* SYRIAC SUBLINEAR COLON SKEWED RIGHT ܉ */, + 0x070A /* SYRIAC CONTRACTION ܊ */, + 0x070B /* SYRIAC HARKLEAN OBELUS ܋ */, + 0x070C /* SYRIAC HARKLEAN METOBELUS ܌ */, + 0x070D /* SYRIAC HARKLEAN ASTERISCUS ܍ */, + 0x07F7 /* NKO SYMBOL GBAKURUNEN ߷ */, + 0x07F8 /* NKO COMMA ߸ */, + 0x07F9 /* NKO EXCLAMATION MARK ߹ */, + 0x0830 /* SAMARITAN PUNCTUATION NEQUDAA ࠰ */, + 0x0831 /* SAMARITAN PUNCTUATION AFSAAQ ࠱ */, + 0x0832 /* SAMARITAN PUNCTUATION ANGED ࠲ */, + 0x0833 /* SAMARITAN PUNCTUATION BAU ࠳ */, + 0x0834 /* SAMARITAN PUNCTUATION ATMAAU ࠴ */, + 0x0835 /* SAMARITAN PUNCTUATION SHIYYAALAA ࠵ */, + 0x0836 /* SAMARITAN ABBREVIATION MARK ࠶ */, + 0x0837 /* SAMARITAN PUNCTUATION MELODIC QITSA ࠷ */, + 0x0838 /* SAMARITAN PUNCTUATION ZIQAA ࠸ */, + 0x0839 /* SAMARITAN PUNCTUATION QITSA ࠹ */, + 0x083A /* SAMARITAN PUNCTUATION ZAEF ࠺ */, + 0x083B /* SAMARITAN PUNCTUATION TURU ࠻ */, + 0x083C /* SAMARITAN PUNCTUATION ARKAANU ࠼ */, + 0x083D /* SAMARITAN PUNCTUATION SOF MASHFAAT ࠽ */, + 0x083E /* SAMARITAN PUNCTUATION ANNAAU ࠾ */, + 0x085E /* MANDAIC PUNCTUATION ࡞ */, + 0x0964 /* DEVANAGARI DANDA । */, + 0x0965 /* DEVANAGARI DOUBLE DANDA ॥ */, + 0x0970 /* DEVANAGARI ABBREVIATION SIGN ॰ */, + 0x09FD /* BENGALI ABBREVIATION SIGN ৽ */, + 0x0A76 /* GURMUKHI ABBREVIATION SIGN ੶ */, + 0x0AF0 /* GUJARATI ABBREVIATION SIGN ૰ */, + 0x0C77 /* TELUGU SIGN SIDDHAM ౷ */, + 0x0C84 /* KANNADA SIGN SIDDHAM ಄ */, + 0x0DF4 /* SINHALA PUNCTUATION KUNDDALIYA ෴ */, + 0x0E4F /* THAI CHARACTER FONGMAN ๏ */, + 0x0E5A /* THAI CHARACTER ANGKHANKHU ๚ */, + 0x0E5B /* THAI CHARACTER KHOMUT ๛ */, + 0x0F04 /* TIBETAN MARK INITIAL YIG MGO MDUN MA ༄ */, + 0x0F05 /* TIBETAN MARK CLOSING YIG MGO SGAB MA ༅ */, + 0x0F06 /* TIBETAN MARK CARET YIG MGO PHUR SHAD MA ༆ */, + 0x0F07 /* TIBETAN MARK YIG MGO TSHEG SHAD MA ༇ */, + 0x0F08 /* TIBETAN MARK SBRUL SHAD ༈ */, + 0x0F09 /* TIBETAN MARK BSKUR YIG MGO ༉ */, + 0x0F0A /* TIBETAN MARK BKA- SHOG YIG MGO ༊ */, + 0x0F0B /* TIBETAN MARK INTERSYLLABIC TSHEG ་ */, + 0x0F0C /* TIBETAN MARK DELIMITER TSHEG BSTAR ༌ */, + 0x0F0D /* TIBETAN MARK SHAD ། */, + 0x0F0E /* TIBETAN MARK NYIS SHAD ༎ */, + 0x0F0F /* TIBETAN MARK TSHEG SHAD ༏ */, + 0x0F10 /* TIBETAN MARK NYIS TSHEG SHAD ༐ */, + 0x0F11 /* TIBETAN MARK RIN CHEN SPUNGS SHAD ༑ */, + 0x0F12 /* TIBETAN MARK RGYA GRAM SHAD ༒ */, + 0x0F14 /* TIBETAN MARK GTER TSHEG ༔ */, + 0x0F85 /* TIBETAN MARK PALUTA ྅ */, + 0x0FD0 /* TIBETAN MARK BSKA- SHOG GI MGO RGYAN ࿐ */, + 0x0FD1 /* TIBETAN MARK MNYAM YIG GI MGO RGYAN ࿑ */, + 0x0FD2 /* TIBETAN MARK NYIS TSHEG ࿒ */, + 0x0FD3 /* TIBETAN MARK INITIAL BRDA RNYING YIG MGO MDUN MA ࿓ */, + 0x0FD4 /* TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA ࿔ */, + 0x0FD9 /* TIBETAN MARK LEADING MCHAN RTAGS ࿙ */, + 0x0FDA /* TIBETAN MARK TRAILING MCHAN RTAGS ࿚ */, + 0x104A /* MYANMAR SIGN LITTLE SECTION ၊ */, + 0x104B /* MYANMAR SIGN SECTION ။ */, + 0x104C /* MYANMAR SYMBOL LOCATIVE ၌ */, + 0x104D /* MYANMAR SYMBOL COMPLETED ၍ */, + 0x104E /* MYANMAR SYMBOL AFOREMENTIONED ၎ */, + 0x104F /* MYANMAR SYMBOL GENITIVE ၏ */, + 0x10FB /* GEORGIAN PARAGRAPH SEPARATOR ჻ */, + 0x1360 /* ETHIOPIC SECTION MARK ፠ */, + 0x1361 /* ETHIOPIC WORDSPACE ፡ */, + 0x1362 /* ETHIOPIC FULL STOP ። */, + 0x1363 /* ETHIOPIC COMMA ፣ */, + 0x1364 /* ETHIOPIC SEMICOLON ፤ */, + 0x1365 /* ETHIOPIC COLON ፥ */, + 0x1366 /* ETHIOPIC PREFACE COLON ፦ */, + 0x1367 /* ETHIOPIC QUESTION MARK ፧ */, + 0x1368 /* ETHIOPIC PARAGRAPH SEPARATOR ፨ */, + 0x166E /* CANADIAN SYLLABICS FULL STOP ᙮ */, + 0x16EB /* RUNIC SINGLE PUNCTUATION ᛫ */, + 0x16EC /* RUNIC MULTIPLE PUNCTUATION ᛬ */, + 0x16ED /* RUNIC CROSS PUNCTUATION ᛭ */, + 0x1735 /* PHILIPPINE SINGLE PUNCTUATION ᜵ */, + 0x1736 /* PHILIPPINE DOUBLE PUNCTUATION ᜶ */, + 0x17D4 /* KHMER SIGN KHAN ។ */, + 0x17D5 /* KHMER SIGN BARIYOOSAN ៕ */, + 0x17D6 /* KHMER SIGN CAMNUC PII KUUH ៖ */, + 0x17D8 /* KHMER SIGN BEYYAL ៘ */, + 0x17D9 /* KHMER SIGN PHNAEK MUAN ៙ */, + 0x17DA /* KHMER SIGN KOOMUUT ៚ */, + 0x1800 /* MONGOLIAN BIRGA ᠀ */, + 0x1801 /* MONGOLIAN ELLIPSIS ᠁ */, + 0x1802 /* MONGOLIAN COMMA ᠂ */, + 0x1803 /* MONGOLIAN FULL STOP ᠃ */, + 0x1804 /* MONGOLIAN COLON ᠄ */, + 0x1805 /* MONGOLIAN FOUR DOTS ᠅ */, + 0x1807 /* MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER ᠇ */, + 0x1808 /* MONGOLIAN MANCHU COMMA ᠈ */, + 0x1809 /* MONGOLIAN MANCHU FULL STOP ᠉ */, + 0x180A /* MONGOLIAN NIRUGU ᠊ */, + 0x1944 /* LIMBU EXCLAMATION MARK ᥄ */, + 0x1945 /* LIMBU QUESTION MARK ᥅ */, + 0x1A1E /* BUGINESE PALLAWA ᨞ */, + 0x1A1F /* BUGINESE END OF SECTION ᨟ */, + 0x1AA0 /* TAI THAM SIGN WIANG ᪠ */, + 0x1AA1 /* TAI THAM SIGN WIANGWAAK ᪡ */, + 0x1AA2 /* TAI THAM SIGN SAWAN ᪢ */, + 0x1AA3 /* TAI THAM SIGN KEOW ᪣ */, + 0x1AA4 /* TAI THAM SIGN HOY ᪤ */, + 0x1AA5 /* TAI THAM SIGN DOKMAI ᪥ */, + 0x1AA6 /* TAI THAM SIGN REVERSED ROTATED RANA ᪦ */, + 0x1AA8 /* TAI THAM SIGN KAAN ᪨ */, + 0x1AA9 /* TAI THAM SIGN KAANKUU ᪩ */, + 0x1AAA /* TAI THAM SIGN SATKAAN ᪪ */, + 0x1AAB /* TAI THAM SIGN SATKAANKUU ᪫ */, + 0x1AAC /* TAI THAM SIGN HANG ᪬ */, + 0x1AAD /* TAI THAM SIGN CAANG ᪭ */, + 0x1B5A /* BALINESE PANTI ᭚ */, + 0x1B5B /* BALINESE PAMADA ᭛ */, + 0x1B5C /* BALINESE WINDU ᭜ */, + 0x1B5D /* BALINESE CARIK PAMUNGKAH ᭝ */, + 0x1B5E /* BALINESE CARIK SIKI ᭞ */, + 0x1B5F /* BALINESE CARIK PAREREN ᭟ */, + 0x1B60 /* BALINESE PAMENENG ᭠ */, + 0x1BFC /* BATAK SYMBOL BINDU NA METEK ᯼ */, + 0x1BFD /* BATAK SYMBOL BINDU PINARBORAS ᯽ */, + 0x1BFE /* BATAK SYMBOL BINDU JUDUL ᯾ */, + 0x1BFF /* BATAK SYMBOL BINDU PANGOLAT ᯿ */, + 0x1C3B /* LEPCHA PUNCTUATION TA-ROL ᰻ */, + 0x1C3C /* LEPCHA PUNCTUATION NYET THYOOM TA-ROL ᰼ */, + 0x1C3D /* LEPCHA PUNCTUATION CER-WA ᰽ */, + 0x1C3E /* LEPCHA PUNCTUATION TSHOOK CER-WA ᰾ */, + 0x1C3F /* LEPCHA PUNCTUATION TSHOOK ᰿ */, + 0x1C7E /* OL CHIKI PUNCTUATION MUCAAD ᱾ */, + 0x1C7F /* OL CHIKI PUNCTUATION DOUBLE MUCAAD ᱿ */, + 0x1CC0 /* SUNDANESE PUNCTUATION BINDU SURYA ᳀ */, + 0x1CC1 /* SUNDANESE PUNCTUATION BINDU PANGLONG ᳁ */, + 0x1CC2 /* SUNDANESE PUNCTUATION BINDU PURNAMA ᳂ */, + 0x1CC3 /* SUNDANESE PUNCTUATION BINDU CAKRA ᳃ */, + 0x1CC4 /* SUNDANESE PUNCTUATION BINDU LEU SATANGA ᳄ */, + 0x1CC5 /* SUNDANESE PUNCTUATION BINDU KA SATANGA ᳅ */, + 0x1CC6 /* SUNDANESE PUNCTUATION BINDU DA SATANGA ᳆ */, + 0x1CC7 /* SUNDANESE PUNCTUATION BINDU BA SATANGA ᳇ */, + 0x1CD3 /* VEDIC SIGN NIHSHVASA ᳓ */, + 0x2016 /* DOUBLE VERTICAL LINE ‖ */, + 0x2017 /* DOUBLE LOW LINE ‗ */, + 0x2020 /* DAGGER † */, + 0x2021 /* DOUBLE DAGGER ‡ */, + 0x2022 /* BULLET • */, + 0x2023 /* TRIANGULAR BULLET ‣ */, + 0x2024 /* ONE DOT LEADER ․ */, + 0x2025 /* TWO DOT LEADER ‥ */, + 0x2026 /* HORIZONTAL ELLIPSIS … */, + 0x2027 /* HYPHENATION POINT ‧ */, + 0x2030 /* PER MILLE SIGN ‰ */, + 0x2031 /* PER TEN THOUSAND SIGN ‱ */, + 0x2032 /* PRIME ′ */, + 0x2033 /* DOUBLE PRIME ″ */, + 0x2034 /* TRIPLE PRIME ‴ */, + 0x2035 /* REVERSED PRIME ‵ */, + 0x2036 /* REVERSED DOUBLE PRIME ‶ */, + 0x2037 /* REVERSED TRIPLE PRIME ‷ */, + 0x2038 /* CARET ‸ */, + 0x203B /* REFERENCE MARK ※ */, + 0x203C /* DOUBLE EXCLAMATION MARK ‼ */, + 0x203D /* INTERROBANG ‽ */, + 0x203E /* OVERLINE ‾ */, + 0x2041 /* CARET INSERTION POINT ⁁ */, + 0x2042 /* ASTERISM ⁂ */, + 0x2043 /* HYPHEN BULLET ⁃ */, + 0x2047 /* DOUBLE QUESTION MARK ⁇ */, + 0x2048 /* QUESTION EXCLAMATION MARK ⁈ */, + 0x2049 /* EXCLAMATION QUESTION MARK ⁉ */, + 0x204A /* TIRONIAN SIGN ET ⁊ */, + 0x204B /* REVERSED PILCROW SIGN ⁋ */, + 0x204C /* BLACK LEFTWARDS BULLET ⁌ */, + 0x204D /* BLACK RIGHTWARDS BULLET ⁍ */, + 0x204E /* LOW ASTERISK ⁎ */, + 0x204F /* REVERSED SEMICOLON ⁏ */, + 0x2050 /* CLOSE UP ⁐ */, + 0x2051 /* TWO ASTERISKS ALIGNED VERTICALLY ⁑ */, + 0x2053 /* SWUNG DASH ⁓ */, + 0x2055 /* FLOWER PUNCTUATION MARK ⁕ */, + 0x2056 /* THREE DOT PUNCTUATION ⁖ */, + 0x2057 /* QUADRUPLE PRIME ⁗ */, + 0x2058 /* FOUR DOT PUNCTUATION ⁘ */, + 0x2059 /* FIVE DOT PUNCTUATION ⁙ */, + 0x205A /* TWO DOT PUNCTUATION ⁚ */, + 0x205B /* FOUR DOT MARK ⁛ */, + 0x205C /* DOTTED CROSS ⁜ */, + 0x205D /* TRICOLON ⁝ */, + 0x205E /* VERTICAL FOUR DOTS ⁞ */, + 0x2CF9 /* COPTIC OLD NUBIAN FULL STOP ⳹ */, + 0x2CFA /* COPTIC OLD NUBIAN DIRECT QUESTION MARK ⳺ */, + 0x2CFB /* COPTIC OLD NUBIAN INDIRECT QUESTION MARK ⳻ */, + 0x2CFC /* COPTIC OLD NUBIAN VERSE DIVIDER ⳼ */, + 0x2CFE /* COPTIC FULL STOP ⳾ */, + 0x2CFF /* COPTIC MORPHOLOGICAL DIVIDER ⳿ */, + 0x2D70 /* TIFINAGH SEPARATOR MARK ⵰ */, + 0x2E00 /* RIGHT ANGLE SUBSTITUTION MARKER ⸀ */, + 0x2E01 /* RIGHT ANGLE DOTTED SUBSTITUTION MARKER ⸁ */, + 0x2E06 /* RAISED INTERPOLATION MARKER ⸆ */, + 0x2E07 /* RAISED DOTTED INTERPOLATION MARKER ⸇ */, + 0x2E08 /* DOTTED TRANSPOSITION MARKER ⸈ */, + 0x2E0B /* RAISED SQUARE ⸋ */, + 0x2E0E /* EDITORIAL CORONIS ⸎ */, + 0x2E0F /* PARAGRAPHOS ⸏ */, + 0x2E10 /* FORKED PARAGRAPHOS ⸐ */, + 0x2E11 /* REVERSED FORKED PARAGRAPHOS ⸑ */, + 0x2E12 /* HYPODIASTOLE ⸒ */, + 0x2E13 /* DOTTED OBELOS ⸓ */, + 0x2E14 /* DOWNWARDS ANCORA ⸔ */, + 0x2E15 /* UPWARDS ANCORA ⸕ */, + 0x2E16 /* DOTTED RIGHT-POINTING ANGLE ⸖ */, + 0x2E18 /* INVERTED INTERROBANG ⸘ */, + 0x2E19 /* PALM BRANCH ⸙ */, + 0x2E1B /* TILDE WITH RING ABOVE ⸛ */, + 0x2E1E /* TILDE WITH DOT ABOVE ⸞ */, + 0x2E1F /* TILDE WITH DOT BELOW ⸟ */, + 0x2E2A /* TWO DOTS OVER ONE DOT PUNCTUATION ⸪ */, + 0x2E2B /* ONE DOT OVER TWO DOTS PUNCTUATION ⸫ */, + 0x2E2C /* SQUARED FOUR DOT PUNCTUATION ⸬ */, + 0x2E2D /* FIVE DOT MARK ⸭ */, + 0x2E2E /* REVERSED QUESTION MARK ⸮ */, + 0x2E30 /* RING POINT ⸰ */, + 0x2E31 /* WORD SEPARATOR MIDDLE DOT ⸱ */, + 0x2E32 /* TURNED COMMA ⸲ */, + 0x2E33 /* RAISED DOT ⸳ */, + 0x2E34 /* RAISED COMMA ⸴ */, + 0x2E35 /* TURNED SEMICOLON ⸵ */, + 0x2E36 /* DAGGER WITH LEFT GUARD ⸶ */, + 0x2E37 /* DAGGER WITH RIGHT GUARD ⸷ */, + 0x2E38 /* TURNED DAGGER ⸸ */, + 0x2E39 /* TOP HALF SECTION SIGN ⸹ */, + 0x2E3C /* STENOGRAPHIC FULL STOP ⸼ */, + 0x2E3D /* VERTICAL SIX DOTS ⸽ */, + 0x2E3E /* WIGGLY VERTICAL LINE ⸾ */, + 0x2E3F /* CAPITULUM ⸿ */, + 0x2E41 /* REVERSED COMMA ⹁ */, + 0x2E43 /* DASH WITH LEFT UPTURN ⹃ */, + 0x2E44 /* DOUBLE SUSPENSION MARK ⹄ */, + 0x2E45 /* INVERTED LOW KAVYKA ⹅ */, + 0x2E46 /* INVERTED LOW KAVYKA WITH KAVYKA ABOVE ⹆ */, + 0x2E47 /* LOW KAVYKA ⹇ */, + 0x2E48 /* LOW KAVYKA WITH DOT ⹈ */, + 0x2E49 /* DOUBLE STACKED COMMA ⹉ */, + 0x2E4A /* DOTTED SOLIDUS ⹊ */, + 0x2E4B /* TRIPLE DAGGER ⹋ */, + 0x2E4C /* MEDIEVAL COMMA ⹌ */, + 0x2E4D /* PARAGRAPHUS MARK ⹍ */, + 0x2E4E /* PUNCTUS ELEVATUS MARK ⹎ */, + 0x2E4F /* CORNISH VERSE DIVIDER ⹏ */, + 0x3001 /* IDEOGRAPHIC COMMA 、 */, + 0x3002 /* IDEOGRAPHIC FULL STOP 。 */, + 0x3003 /* DITTO MARK 〃 */, + 0x303D /* PART ALTERNATION MARK 〽 */, + 0x30FB /* KATAKANA MIDDLE DOT ・ */, + 0xA4FE /* LISU PUNCTUATION COMMA ꓾ */, + 0xA4FF /* LISU PUNCTUATION FULL STOP ꓿ */, + 0xA60D /* VAI COMMA ꘍ */, + 0xA60E /* VAI FULL STOP ꘎ */, + 0xA60F /* VAI QUESTION MARK ꘏ */, + 0xA673 /* SLAVONIC ASTERISK ꙳ */, + 0xA67E /* CYRILLIC KAVYKA ꙾ */, + 0xA6F2 /* BAMUM NJAEMLI ꛲ */, + 0xA6F3 /* BAMUM FULL STOP ꛳ */, + 0xA6F4 /* BAMUM COLON ꛴ */, + 0xA6F5 /* BAMUM COMMA ꛵ */, + 0xA6F6 /* BAMUM SEMICOLON ꛶ */, + 0xA6F7 /* BAMUM QUESTION MARK ꛷ */, + 0xA874 /* PHAGS-PA SINGLE HEAD MARK ꡴ */, + 0xA875 /* PHAGS-PA DOUBLE HEAD MARK ꡵ */, + 0xA876 /* PHAGS-PA MARK SHAD ꡶ */, + 0xA877 /* PHAGS-PA MARK DOUBLE SHAD ꡷ */, + 0xA8CE /* SAURASHTRA DANDA ꣎ */, + 0xA8CF /* SAURASHTRA DOUBLE DANDA ꣏ */, + 0xA8F8 /* DEVANAGARI SIGN PUSHPIKA ꣸ */, + 0xA8F9 /* DEVANAGARI GAP FILLER ꣹ */, + 0xA8FA /* DEVANAGARI CARET ꣺ */, + 0xA8FC /* DEVANAGARI SIGN SIDDHAM ꣼ */, + 0xA92E /* KAYAH LI SIGN CWI ꤮ */, + 0xA92F /* KAYAH LI SIGN SHYA ꤯ */, + 0xA95F /* REJANG SECTION MARK ꥟ */, + 0xA9C1 /* JAVANESE LEFT RERENGGAN ꧁ */, + 0xA9C2 /* JAVANESE RIGHT RERENGGAN ꧂ */, + 0xA9C3 /* JAVANESE PADA ANDAP ꧃ */, + 0xA9C4 /* JAVANESE PADA MADYA ꧄ */, + 0xA9C5 /* JAVANESE PADA LUHUR ꧅ */, + 0xA9C6 /* JAVANESE PADA WINDU ꧆ */, + 0xA9C7 /* JAVANESE PADA PANGKAT ꧇ */, + 0xA9C8 /* JAVANESE PADA LINGSA ꧈ */, + 0xA9C9 /* JAVANESE PADA LUNGSI ꧉ */, + 0xA9CA /* JAVANESE PADA ADEG ꧊ */, + 0xA9CB /* JAVANESE PADA ADEG ADEG ꧋ */, + 0xA9CC /* JAVANESE PADA PISELEH ꧌ */, + 0xA9CD /* JAVANESE TURNED PADA PISELEH ꧍ */, + 0xA9DE /* JAVANESE PADA TIRTA TUMETES ꧞ */, + 0xA9DF /* JAVANESE PADA ISEN-ISEN ꧟ */, + 0xAA5C /* CHAM PUNCTUATION SPIRAL ꩜ */, + 0xAA5D /* CHAM PUNCTUATION DANDA ꩝ */, + 0xAA5E /* CHAM PUNCTUATION DOUBLE DANDA ꩞ */, + 0xAA5F /* CHAM PUNCTUATION TRIPLE DANDA ꩟ */, + 0xAADE /* TAI VIET SYMBOL HO HOI ꫞ */, + 0xAADF /* TAI VIET SYMBOL KOI KOI ꫟ */, + 0xAAF0 /* MEETEI MAYEK CHEIKHAN ꫰ */, + 0xAAF1 /* MEETEI MAYEK AHANG KHUDAM ꫱ */, + 0xABEB /* MEETEI MAYEK CHEIKHEI ꯫ */, + 0xFE10 /* PRESENTATION FORM FOR VERTICAL COMMA ︐ */, + 0xFE11 /* PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA ︑ */, + 0xFE12 /* PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP ︒ */, + 0xFE13 /* PRESENTATION FORM FOR VERTICAL COLON ︓ */, + 0xFE14 /* PRESENTATION FORM FOR VERTICAL SEMICOLON ︔ */, + 0xFE15 /* PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK ︕ */, + 0xFE16 /* PRESENTATION FORM FOR VERTICAL QUESTION MARK ︖ */, + 0xFE19 /* PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS ︙ */, + 0xFE30 /* PRESENTATION FORM FOR VERTICAL TWO DOT LEADER ︰ */, + 0xFE45 /* SESAME DOT ﹅ */, + 0xFE46 /* WHITE SESAME DOT ﹆ */, + 0xFE49 /* DASHED OVERLINE ﹉ */, + 0xFE4A /* CENTRELINE OVERLINE ﹊ */, + 0xFE4B /* WAVY OVERLINE ﹋ */, + 0xFE4C /* DOUBLE WAVY OVERLINE ﹌ */, + 0xFE50 /* SMALL COMMA ﹐ */, + 0xFE51 /* SMALL IDEOGRAPHIC COMMA ﹑ */, + 0xFE52 /* SMALL FULL STOP ﹒ */, + 0xFE54 /* SMALL SEMICOLON ﹔ */, + 0xFE55 /* SMALL COLON ﹕ */, + 0xFE56 /* SMALL QUESTION MARK ﹖ */, + 0xFE57 /* SMALL EXCLAMATION MARK ﹗ */, + 0xFE5F /* SMALL NUMBER SIGN ﹟ */, + 0xFE60 /* SMALL AMPERSAND ﹠ */, + 0xFE61 /* SMALL ASTERISK ﹡ */, + 0xFE68 /* SMALL REVERSE SOLIDUS ﹨ */, + 0xFE6A /* SMALL PERCENT SIGN ﹪ */, + 0xFE6B /* SMALL COMMERCIAL AT ﹫ */, + 0xFF01 /* FULLWIDTH EXCLAMATION MARK ! */, + 0xFF02 /* FULLWIDTH QUOTATION MARK " */, + 0xFF03 /* FULLWIDTH NUMBER SIGN # */, + 0xFF05 /* FULLWIDTH PERCENT SIGN % */, + 0xFF06 /* FULLWIDTH AMPERSAND & */, + 0xFF07 /* FULLWIDTH APOSTROPHE ' */, + 0xFF0A /* FULLWIDTH ASTERISK * */, + 0xFF0C /* FULLWIDTH COMMA , */, + 0xFF0E /* FULLWIDTH FULL STOP . */, + 0xFF0F /* FULLWIDTH SOLIDUS / */, + 0xFF1A /* FULLWIDTH COLON : */, + 0xFF1B /* FULLWIDTH SEMICOLON ; */, + 0xFF1F /* FULLWIDTH QUESTION MARK ? */, + 0xFF20 /* FULLWIDTH COMMERCIAL AT @ */, + 0xFF3C /* FULLWIDTH REVERSE SOLIDUS \ */, + 0xFF61 /* HALFWIDTH IDEOGRAPHIC FULL STOP 。 */, + 0xFF64 /* HALFWIDTH IDEOGRAPHIC COMMA 、 */, + 0xFF65 /* HALFWIDTH KATAKANA MIDDLE DOT ・ */, + 0x10100 /* AEGEAN WORD SEPARATOR LINE 𐄀 */, + 0x10101 /* AEGEAN WORD SEPARATOR DOT 𐄁 */, + 0x10102 /* AEGEAN CHECK MARK 𐄂 */, + 0x1039F /* UGARITIC WORD DIVIDER 𐎟 */, + 0x103D0 /* OLD PERSIAN WORD DIVIDER 𐏐 */, + 0x1056F /* CAUCASIAN ALBANIAN CITATION MARK 𐕯 */, + 0x10857 /* IMPERIAL ARAMAIC SECTION SIGN 𐡗 */, + 0x1091F /* PHOENICIAN WORD SEPARATOR 𐤟 */, + 0x1093F /* LYDIAN TRIANGULAR MARK 𐤿 */, + 0x10A50 /* KHAROSHTHI PUNCTUATION DOT 𐩐 */, + 0x10A51 /* KHAROSHTHI PUNCTUATION SMALL CIRCLE 𐩑 */, + 0x10A52 /* KHAROSHTHI PUNCTUATION CIRCLE 𐩒 */, + 0x10A53 /* KHAROSHTHI PUNCTUATION CRESCENT BAR 𐩓 */, + 0x10A54 /* KHAROSHTHI PUNCTUATION MANGALAM 𐩔 */, + 0x10A55 /* KHAROSHTHI PUNCTUATION LOTUS 𐩕 */, + 0x10A56 /* KHAROSHTHI PUNCTUATION DANDA 𐩖 */, + 0x10A57 /* KHAROSHTHI PUNCTUATION DOUBLE DANDA 𐩗 */, + 0x10A58 /* KHAROSHTHI PUNCTUATION LINES 𐩘 */, + 0x10A7F /* OLD SOUTH ARABIAN NUMERIC INDICATOR 𐩿 */, + 0x10AF0 /* MANICHAEAN PUNCTUATION STAR 𐫰 */, + 0x10AF1 /* MANICHAEAN PUNCTUATION FLEURON 𐫱 */, + 0x10AF2 /* MANICHAEAN PUNCTUATION DOUBLE DOT WITHIN DOT 𐫲 */, + 0x10AF3 /* MANICHAEAN PUNCTUATION DOT WITHIN DOT 𐫳 */, + 0x10AF4 /* MANICHAEAN PUNCTUATION DOT 𐫴 */, + 0x10AF5 /* MANICHAEAN PUNCTUATION TWO DOTS 𐫵 */, + 0x10AF6 /* MANICHAEAN PUNCTUATION LINE FILLER 𐫶 */, + 0x10B39 /* AVESTAN ABBREVIATION MARK 𐬹 */, + 0x10B3A /* TINY TWO DOTS OVER ONE DOT PUNCTUATION 𐬺 */, + 0x10B3B /* SMALL TWO DOTS OVER ONE DOT PUNCTUATION 𐬻 */, + 0x10B3C /* LARGE TWO DOTS OVER ONE DOT PUNCTUATION 𐬼 */, + 0x10B3D /* LARGE ONE DOT OVER TWO DOTS PUNCTUATION 𐬽 */, + 0x10B3E /* LARGE TWO RINGS OVER ONE RING PUNCTUATION 𐬾 */, + 0x10B3F /* LARGE ONE RING OVER TWO RINGS PUNCTUATION 𐬿 */, + 0x10B99 /* PSALTER PAHLAVI SECTION MARK 𐮙 */, + 0x10B9A /* PSALTER PAHLAVI TURNED SECTION MARK 𐮚 */, + 0x10B9B /* PSALTER PAHLAVI FOUR DOTS WITH CROSS 𐮛 */, + 0x10B9C /* PSALTER PAHLAVI FOUR DOTS WITH DOT 𐮜 */, + 0x10F55 /* SOGDIAN PUNCTUATION TWO VERTICAL BARS 𐽕 */, + 0x10F56 /* SOGDIAN PUNCTUATION TWO VERTICAL BARS WITH DOTS 𐽖 */, + 0x10F57 /* SOGDIAN PUNCTUATION CIRCLE WITH DOT 𐽗 */, + 0x10F58 /* SOGDIAN PUNCTUATION TWO CIRCLES WITH DOTS 𐽘 */, + 0x10F59 /* SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT 𐽙 */, + 0x11047 /* BRAHMI DANDA 𑁇 */, + 0x11048 /* BRAHMI DOUBLE DANDA 𑁈 */, + 0x11049 /* BRAHMI PUNCTUATION DOT 𑁉 */, + 0x1104A /* BRAHMI PUNCTUATION DOUBLE DOT 𑁊 */, + 0x1104B /* BRAHMI PUNCTUATION LINE 𑁋 */, + 0x1104C /* BRAHMI PUNCTUATION CRESCENT BAR 𑁌 */, + 0x1104D /* BRAHMI PUNCTUATION LOTUS 𑁍 */, + 0x110BB /* KAITHI ABBREVIATION SIGN 𑂻 */, + 0x110BC /* KAITHI ENUMERATION SIGN 𑂼 */, + 0x110BE /* KAITHI SECTION MARK 𑂾 */, + 0x110BF /* KAITHI DOUBLE SECTION MARK 𑂿 */, + 0x110C0 /* KAITHI DANDA 𑃀 */, + 0x110C1 /* KAITHI DOUBLE DANDA 𑃁 */, + 0x11140 /* CHAKMA SECTION MARK 𑅀 */, + 0x11141 /* CHAKMA DANDA 𑅁 */, + 0x11142 /* CHAKMA DOUBLE DANDA 𑅂 */, + 0x11143 /* CHAKMA QUESTION MARK 𑅃 */, + 0x11174 /* MAHAJANI ABBREVIATION SIGN 𑅴 */, + 0x11175 /* MAHAJANI SECTION MARK 𑅵 */, + 0x111C5 /* SHARADA DANDA 𑇅 */, + 0x111C6 /* SHARADA DOUBLE DANDA 𑇆 */, + 0x111C7 /* SHARADA ABBREVIATION SIGN 𑇇 */, + 0x111C8 /* SHARADA SEPARATOR 𑇈 */, + 0x111CD /* SHARADA SUTRA MARK 𑇍 */, + 0x111DB /* SHARADA SIGN SIDDHAM 𑇛 */, + 0x111DD /* SHARADA CONTINUATION SIGN 𑇝 */, + 0x111DE /* SHARADA SECTION MARK-1 𑇞 */, + 0x111DF /* SHARADA SECTION MARK-2 𑇟 */, + 0x11238 /* KHOJKI DANDA 𑈸 */, + 0x11239 /* KHOJKI DOUBLE DANDA 𑈹 */, + 0x1123A /* KHOJKI WORD SEPARATOR 𑈺 */, + 0x1123B /* KHOJKI SECTION MARK 𑈻 */, + 0x1123C /* KHOJKI DOUBLE SECTION MARK 𑈼 */, + 0x1123D /* KHOJKI ABBREVIATION SIGN 𑈽 */, + 0x112A9 /* MULTANI SECTION MARK 𑊩 */, + 0x1144B /* NEWA DANDA 𑑋 */, + 0x1144C /* NEWA DOUBLE DANDA 𑑌 */, + 0x1144D /* NEWA COMMA 𑑍 */, + 0x1144E /* NEWA GAP FILLER 𑑎 */, + 0x1144F /* NEWA ABBREVIATION SIGN 𑑏 */, + 0x1145B /* NEWA PLACEHOLDER MARK 𑑛 */, + 0x1145D /* NEWA INSERTION SIGN 𑑝 */, + 0x114C6 /* TIRHUTA ABBREVIATION SIGN 𑓆 */, + 0x115C1 /* SIDDHAM SIGN SIDDHAM 𑗁 */, + 0x115C2 /* SIDDHAM DANDA 𑗂 */, + 0x115C3 /* SIDDHAM DOUBLE DANDA 𑗃 */, + 0x115C4 /* SIDDHAM SEPARATOR DOT 𑗄 */, + 0x115C5 /* SIDDHAM SEPARATOR BAR 𑗅 */, + 0x115C6 /* SIDDHAM REPETITION MARK-1 𑗆 */, + 0x115C7 /* SIDDHAM REPETITION MARK-2 𑗇 */, + 0x115C8 /* SIDDHAM REPETITION MARK-3 𑗈 */, + 0x115C9 /* SIDDHAM END OF TEXT MARK 𑗉 */, + 0x115CA /* SIDDHAM SECTION MARK WITH TRIDENT AND U-SHAPED ORNAMENTS 𑗊 */, + 0x115CB /* SIDDHAM SECTION MARK WITH TRIDENT AND DOTTED CRESCENTS 𑗋 */, + 0x115CC /* SIDDHAM SECTION MARK WITH RAYS AND DOTTED CRESCENTS 𑗌 */, + 0x115CD /* SIDDHAM SECTION MARK WITH RAYS AND DOTTED DOUBLE CRESCENTS 𑗍 */, + 0x115CE /* SIDDHAM SECTION MARK WITH RAYS AND DOTTED TRIPLE CRESCENTS 𑗎 */, + 0x115CF /* SIDDHAM SECTION MARK DOUBLE RING 𑗏 */, + 0x115D0 /* SIDDHAM SECTION MARK DOUBLE RING WITH RAYS 𑗐 */, + 0x115D1 /* SIDDHAM SECTION MARK WITH DOUBLE CRESCENTS 𑗑 */, + 0x115D2 /* SIDDHAM SECTION MARK WITH TRIPLE CRESCENTS 𑗒 */, + 0x115D3 /* SIDDHAM SECTION MARK WITH QUADRUPLE CRESCENTS 𑗓 */, + 0x115D4 /* SIDDHAM SECTION MARK WITH SEPTUPLE CRESCENTS 𑗔 */, + 0x115D5 /* SIDDHAM SECTION MARK WITH CIRCLES AND RAYS 𑗕 */, + 0x115D6 /* SIDDHAM SECTION MARK WITH CIRCLES AND TWO ENCLOSURES 𑗖 */, + 0x115D7 /* SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES 𑗗 */, + 0x11641 /* MODI DANDA 𑙁 */, + 0x11642 /* MODI DOUBLE DANDA 𑙂 */, + 0x11643 /* MODI ABBREVIATION SIGN 𑙃 */, + 0x11660 /* MONGOLIAN BIRGA WITH ORNAMENT 𑙠 */, + 0x11661 /* MONGOLIAN ROTATED BIRGA 𑙡 */, + 0x11662 /* MONGOLIAN DOUBLE BIRGA WITH ORNAMENT 𑙢 */, + 0x11663 /* MONGOLIAN TRIPLE BIRGA WITH ORNAMENT 𑙣 */, + 0x11664 /* MONGOLIAN BIRGA WITH DOUBLE ORNAMENT 𑙤 */, + 0x11665 /* MONGOLIAN ROTATED BIRGA WITH ORNAMENT 𑙥 */, + 0x11666 /* MONGOLIAN ROTATED BIRGA WITH DOUBLE ORNAMENT 𑙦 */, + 0x11667 /* MONGOLIAN INVERTED BIRGA 𑙧 */, + 0x11668 /* MONGOLIAN INVERTED BIRGA WITH DOUBLE ORNAMENT 𑙨 */, + 0x11669 /* MONGOLIAN SWIRL BIRGA 𑙩 */, + 0x1166A /* MONGOLIAN SWIRL BIRGA WITH ORNAMENT 𑙪 */, + 0x1166B /* MONGOLIAN SWIRL BIRGA WITH DOUBLE ORNAMENT 𑙫 */, + 0x1166C /* MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT 𑙬 */, + 0x1173C /* AHOM SIGN SMALL SECTION 𑜼 */, + 0x1173D /* AHOM SIGN SECTION 𑜽 */, + 0x1173E /* AHOM SIGN RULAI 𑜾 */, + 0x1183B /* DOGRA ABBREVIATION SIGN 𑠻 */, + 0x119E2 /* NANDINAGARI SIGN SIDDHAM 𑧢 */, + 0x11A3F /* ZANABAZAR SQUARE INITIAL HEAD MARK 𑨿 */, + 0x11A40 /* ZANABAZAR SQUARE CLOSING HEAD MARK 𑩀 */, + 0x11A41 /* ZANABAZAR SQUARE MARK TSHEG 𑩁 */, + 0x11A42 /* ZANABAZAR SQUARE MARK SHAD 𑩂 */, + 0x11A43 /* ZANABAZAR SQUARE MARK DOUBLE SHAD 𑩃 */, + 0x11A44 /* ZANABAZAR SQUARE MARK LONG TSHEG 𑩄 */, + 0x11A45 /* ZANABAZAR SQUARE INITIAL DOUBLE-LINED HEAD MARK 𑩅 */, + 0x11A46 /* ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK 𑩆 */, + 0x11A9A /* SOYOMBO MARK TSHEG 𑪚 */, + 0x11A9B /* SOYOMBO MARK SHAD 𑪛 */, + 0x11A9C /* SOYOMBO MARK DOUBLE SHAD 𑪜 */, + 0x11A9E /* SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME 𑪞 */, + 0x11A9F /* SOYOMBO HEAD MARK WITH MOON AND SUN AND FLAME 𑪟 */, + 0x11AA0 /* SOYOMBO HEAD MARK WITH MOON AND SUN 𑪠 */, + 0x11AA1 /* SOYOMBO TERMINAL MARK-1 𑪡 */, + 0x11AA2 /* SOYOMBO TERMINAL MARK-2 𑪢 */, + 0x11C41 /* BHAIKSUKI DANDA 𑱁 */, + 0x11C42 /* BHAIKSUKI DOUBLE DANDA 𑱂 */, + 0x11C43 /* BHAIKSUKI WORD SEPARATOR 𑱃 */, + 0x11C44 /* BHAIKSUKI GAP FILLER-1 𑱄 */, + 0x11C45 /* BHAIKSUKI GAP FILLER-2 𑱅 */, + 0x11C70 /* MARCHEN HEAD MARK 𑱰 */, + 0x11C71 /* MARCHEN MARK SHAD 𑱱 */, + 0x11EF7 /* MAKASAR PASSIMBANG 𑻷 */, + 0x11EF8 /* MAKASAR END OF SECTION 𑻸 */, + 0x11FFF /* TAMIL PUNCTUATION END OF TEXT 𑿿 */, + 0x12470 /* CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER 𒑰 */, + 0x12471 /* CUNEIFORM PUNCTUATION SIGN VERTICAL COLON 𒑱 */, + 0x12472 /* CUNEIFORM PUNCTUATION SIGN DIAGONAL COLON 𒑲 */, + 0x12473 /* CUNEIFORM PUNCTUATION SIGN DIAGONAL TRICOLON 𒑳 */, + 0x12474 /* CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON 𒑴 */, + 0x16A6E /* MRO DANDA 𖩮 */, + 0x16A6F /* MRO DOUBLE DANDA 𖩯 */, + 0x16AF5 /* BASSA VAH FULL STOP 𖫵 */, + 0x16B37 /* PAHAWH HMONG SIGN VOS THOM 𖬷 */, + 0x16B38 /* PAHAWH HMONG SIGN VOS TSHAB CEEB 𖬸 */, + 0x16B39 /* PAHAWH HMONG SIGN CIM CHEEM 𖬹 */, + 0x16B3A /* PAHAWH HMONG SIGN VOS THIAB 𖬺 */, + 0x16B3B /* PAHAWH HMONG SIGN VOS FEEM 𖬻 */, + 0x16B44 /* PAHAWH HMONG SIGN XAUS 𖭄 */, + 0x16E97 /* MEDEFAIDRIN COMMA 𖺗 */, + 0x16E98 /* MEDEFAIDRIN FULL STOP 𖺘 */, + 0x16E99 /* MEDEFAIDRIN SYMBOL AIVA 𖺙 */, + 0x16E9A /* MEDEFAIDRIN EXCLAMATION OH 𖺚 */, + 0x16FE2 /* OLD CHINESE HOOK MARK 𖿢 */, + 0x1BC9F /* DUPLOYAN PUNCTUATION CHINOOK FULL STOP 𛲟 */, + 0x1DA87 /* SIGNWRITING COMMA 𝪇 */, + 0x1DA88 /* SIGNWRITING FULL STOP 𝪈 */, + 0x1DA89 /* SIGNWRITING SEMICOLON 𝪉 */, + 0x1DA8A /* SIGNWRITING COLON 𝪊 */, + 0x1DA8B /* SIGNWRITING PARENTHESIS 𝪋 */, + 0x1E95E /* ADLAM INITIAL EXCLAMATION MARK 𞥞 */, + 0x1E95F /* ADLAM INITIAL QUESTION MARK */, + ] +) diff --git a/v_windows/v/vlib/encoding/utf8/utf8_util_test.v b/v_windows/v/vlib/encoding/utf8/utf8_util_test.v new file mode 100644 index 0000000..f09cb76 --- /dev/null +++ b/v_windows/v/vlib/encoding/utf8/utf8_util_test.v @@ -0,0 +1,66 @@ +import encoding.utf8 + +fn test_utf8_util() { + // string test + src := 'ăĂ ôÔ testo 怔' //_\u1E5A\u1E5B<=>\u1F49\u1F41<=>\u0128\u012a\u012c" // len 29 runes, raw 49 bytes + src_upper := 'ĂĂ ÔÔ TESTO Æ€”' //_\u1E5A\u1E5A<=>\u1F49\u1F49<=>\u0128\u012A\u012C" + src_lower := 'ăă ôô testo 怔' //_\u1E5B\u1E5B<=>\u1F41\u1F41<=>\u0129\u012B\u012D" + upper := utf8.to_upper(src) + lower := utf8.to_lower(src) + assert upper == src_upper + assert lower == src_lower + + assert utf8.to_upper('абвёabc12{') == 'АБВЁABC12{' + assert utf8.to_lower('АБВЁABC12{') == 'абвёabc12{' + + // test len function + assert utf8.len('') == 0 + assert utf8.len('pippo') == 5 + assert utf8.len(src) == 15 // 29 + assert src.len == 24 // 49 + + // western punctuation + a := '.abc?abcòàè.' + assert utf8.is_punct(a, 0) == true + assert utf8.is_punct('b', 0) == false + assert utf8.is_uchar_punct(0x002E) == true + assert utf8.is_punct(a, 4) == true // ? + assert utf8.is_punct(a, 14) == true // last . + assert utf8.is_punct(a, 12) == false // è + println('OK western') + + // global punctuation + b := '.ĂĂa. ÔÔ TESTO Æ€' + assert utf8.is_global_punct(b, 0) == true + assert utf8.is_global_punct('.', 0) == true + assert utf8.is_uchar_punct(0x002E) == true + assert utf8.is_global_punct(b, 6) == true // . + assert utf8.is_global_punct(b, 1) == false // a + + // test utility functions + assert utf8.get_uchar(b, 0) == 0x002E +} + +fn test_raw_indexing() { + a := '我是V Lang!' + + // test non ascii characters + assert utf8.raw_index(a, 0) == '我' + assert utf8.raw_index(a, 1) == '是' + + // test ascii characters + assert utf8.raw_index(a, 2) == 'V' + assert utf8.raw_index(a, 3) == ' ' + assert utf8.raw_index(a, 4) == 'L' + assert utf8.raw_index(a, 5) == 'a' + assert utf8.raw_index(a, 6) == 'n' + assert utf8.raw_index(a, 7) == 'g' + assert utf8.raw_index(a, 8) == '!' +} + +fn test_reversed() { + a := '我是V Lang!' + b := '你好世界hello world' + assert utf8.reverse(a) == '!gnaL V是我' + assert utf8.reverse(b) == 'dlrow olleh界世好你' +} diff --git a/v_windows/v/vlib/eventbus/README.md b/v_windows/v/vlib/eventbus/README.md new file mode 100644 index 0000000..93047db --- /dev/null +++ b/v_windows/v/vlib/eventbus/README.md @@ -0,0 +1,122 @@ +# Event Bus + +A module to provide eventing capabilities using pub/sub. + +## API + +1. `new()` - create a new `EventBus` + +### Structs: + +**EventBus:** + +1. `publish(name string, sender voidptr, args voidptr)` - publish an event with provided + Params & name +2. `clear_all()` - clear all subscribers +3. `has_subscriber(name string)` - check if a subscriber to an event exists + +**Subscriber:** + +1. `subscribe(name string, handler EventHandlerFn)` - subscribe to an event +2. `subscribe_once(name string, handler EventHandlerFn)` - subscribe only once to an event +3. `subscribe_method(name string, handler EventHandlerFn, receiver voidptr)` - subscribe to + an event and also set the `receiver` as a parameter. + Since it's not yet possible to send methods as parameters, this is a workaround. +4. `is_subscribed(name string)` - check if we are subscribed to an event +5. `unsubscribe(name string)` - unsubscribe from an event + +**Event Handler Signature:** + +The function given to `subscribe`, `subscribe_method` and `subscribe_once` must match this: + +```v oksyntax +fn cb(receiver voidptr, args voidptr, sender voidptr) { +} + +// Since V can map structs to voidptr, this also works +struct ClickEvent { + x int + y int +} + +// Example case where publisher sends ClickEvent as args. +fn on_press(receiver voidptr, e &ClickEvent, sender voidptr) { + println(e.x) + // your code here... +} +``` + +## Usage + +For **usage across modules** +[check the example](https://github.com/vlang/v/tree/master/examples/eventbus). + +_Note: As a general rule, you will need to **subscribe before publishing**._ + +**main.v** + +```v oksyntax +module main + +import eventbus + +// initialize it globally +const ( + eb = eventbus.new() +) + +fn main() { + // get a mutable reference to the subscriber + mut sub := eb.subscriber + // subscribe to the 'error' event + sub.subscribe('error', on_error) + // start the work + do_work() +} + +// the event handler +fn on_error(receiver voidptr, e &Error, work &Work) { + println('error occured on ${work.hours}. Error: $e.message') +} +``` + +**work.v** + +```v oksyntax +module main + +import eventbus + +const eb = eventbus.new() + +struct Work { + hours int +} + +struct AnError { + message string +} + +fn do_work() { + work := Work{20} + // get a mutable Params instance & put some data into it + error := &AnError{'Error: no internet connection.'} + // publish the event + eb.publish('error', work, error) +} +``` + +### Notes: + +1. Each `EventBus` instance has it's own registry (i.e. there is no global event registry + so you can't just subscribe to an event wherever you are. +2. Each `EventBus` has a `Subscriber` instance which will need to be either exposed or you can make + small public helper functions specific to your module like (`onPress`, `onError`) and etc. +3. The `eventbus` module has some helpers to ease getting/setting of Params + (since V doesn't support empty interfaces yet or reflection) so use them (see usage above). + +**The rationale behind separating Subscriber & Publisher:** + +This is mainly for security because if publisher & subscriber are both passed around, +a client can easily publish events acting as the server. +So a client should only be able to use the Subscriber methods. diff --git a/v_windows/v/vlib/eventbus/eventbus.v b/v_windows/v/vlib/eventbus/eventbus.v new file mode 100644 index 0000000..69cf118 --- /dev/null +++ b/v_windows/v/vlib/eventbus/eventbus.v @@ -0,0 +1,121 @@ +module eventbus + +pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr) + +pub struct Publisher { +mut: + registry &Registry +} + +pub struct Subscriber { +mut: + registry &Registry +} + +struct Registry { +mut: + events []EventHandler +} + +struct EventHandler { + name string + handler EventHandlerFn + receiver voidptr = voidptr(0) + once bool +} + +pub struct EventBus { +pub mut: + registry &Registry + publisher &Publisher + subscriber &Subscriber +} + +pub fn new() &EventBus { + registry := &Registry{ + events: [] + } + return &EventBus{registry, &Publisher{registry}, &Subscriber{registry}} +} + +// EventBus Methods +pub fn (eb &EventBus) publish(name string, sender voidptr, args voidptr) { + mut publisher := eb.publisher + publisher.publish(name, sender, args) +} + +pub fn (eb &EventBus) clear_all() { + mut publisher := eb.publisher + publisher.clear_all() +} + +pub fn (eb &EventBus) has_subscriber(name string) bool { + return eb.registry.check_subscriber(name) +} + +// Publisher Methods +fn (mut pb Publisher) publish(name string, sender voidptr, args voidptr) { + for event in pb.registry.events { + if event.name == name { + event.handler(event.receiver, args, sender) + } + } + pb.registry.events = pb.registry.events.filter(!(it.name == name && it.once)) +} + +fn (mut p Publisher) clear_all() { + p.registry.events.clear() +} + +// Subscriber Methods +pub fn (mut s Subscriber) subscribe(name string, handler EventHandlerFn) { + s.registry.events << EventHandler{ + name: name + handler: handler + } +} + +pub fn (mut s Subscriber) subscribe_method(name string, handler EventHandlerFn, receiver voidptr) { + s.registry.events << EventHandler{ + name: name + handler: handler + receiver: receiver + } +} + +// unsubscribe_method unsubscribe a receiver for only one method +pub fn (mut s Subscriber) unsubscribe_method(name string, receiver voidptr) { + s.registry.events = s.registry.events.filter(!(it.name == name && it.receiver == receiver)) +} + +// unsubscribe_receiver unsubscribes a receiver from all events +pub fn (mut s Subscriber) unsubscribe_receiver(receiver voidptr) { + s.registry.events = s.registry.events.filter(it.receiver != receiver) +} + +pub fn (mut s Subscriber) subscribe_once(name string, handler EventHandlerFn) { + s.registry.events << EventHandler{ + name: name + handler: handler + once: true + } +} + +pub fn (s &Subscriber) is_subscribed(name string) bool { + return s.registry.check_subscriber(name) +} + +// is_subscribed_method checks whether a receiver was already subscribed for any events +pub fn (s &Subscriber) is_subscribed_method(name string, receiver voidptr) bool { + return s.registry.events.any(it.name == name && it.receiver == receiver) +} + +pub fn (mut s Subscriber) unsubscribe(name string, handler EventHandlerFn) { + // v := voidptr(handler) + s.registry.events = s.registry.events.filter(!(it.name == name && it.handler == handler)) +} + +// Registry Methods +fn (r &Registry) check_subscriber(name string) bool { + return r.events.any(it.name == name) +} diff --git a/v_windows/v/vlib/eventbus/eventbus_test.v b/v_windows/v/vlib/eventbus/eventbus_test.v new file mode 100644 index 0000000..e27af88 --- /dev/null +++ b/v_windows/v/vlib/eventbus/eventbus_test.v @@ -0,0 +1,109 @@ +import eventbus + +struct EventData { + data string +} + +struct FakeReceiver { + ok bool +} + +fn test_eventbus() { + ev_data := &EventData{'hello'} + mut eb := eventbus.new() + eb.subscriber.subscribe_once('on_test', on_test) + assert eb.has_subscriber('on_test') + assert eb.subscriber.is_subscribed('on_test') + eb.publish('on_test', eb, ev_data) + assert !eb.has_subscriber('on_test') + assert !eb.subscriber.is_subscribed('on_test') + eb.subscriber.subscribe('on_test', on_test) + assert eb.has_subscriber('on_test') + assert eb.subscriber.is_subscribed('on_test') + eb.clear_all() + assert !eb.has_subscriber('on_test') + assert !eb.subscriber.is_subscribed('on_test') +} + +fn test_subscribe_method() { + // Does not really test subscribe_method idinvidually though + // given + mut eb := eventbus.new() + r := FakeReceiver{} + + assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r) + // when + eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r) + + // then + assert eb.subscriber.is_subscribed_method('on_test_with_receiver', r) +} + +fn test_unsubscribe_method() { + // given + mut eb := eventbus.new() + r := FakeReceiver{} + r2 := FakeReceiver{} + + // when + eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r) + eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r2) + eb.subscriber.unsubscribe_method('on_test_with_receiver', r) + + // then + assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r) + assert eb.subscriber.is_subscribed_method('on_test_with_receiver', r2) +} + +fn test_publish() { + // given + ev_data := &EventData{'hello'} + mut eb := eventbus.new() + + // when + eb.subscriber.subscribe_once('on_test', on_test) + eb.subscriber.subscribe_once('on_test', on_test) + eb.publish('on_test', eb, ev_data) + + // then + assert !eb.subscriber.is_subscribed('on_test') +} + +fn test_publish_with_receiver() { + // given + mut eb := eventbus.new() + ev_data := &EventData{'hello'} + r := FakeReceiver{} + + // when + eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r) + eb.publish('on_test_with_receiver', eb, ev_data) + + // then asserts are in on_test_with_receiver, don't know how to be sure + // that it has been properly called... +} + +fn test_unsubscribe_reveiver() { + // given + mut eb := eventbus.new() + r := &FakeReceiver{} + + // when + eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r) + eb.subscriber.subscribe_method('on_test', on_test, r) + eb.subscriber.unsubscribe_receiver(r) + assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r) + assert !eb.subscriber.is_subscribed_method('on_test', r) +} + +fn on_test(receiver voidptr, ev &EventData, sender voidptr) { + assert receiver == 0 + assert sender != 0 + assert ev.data == 'hello' +} + +fn on_test_with_receiver(receiver &FakeReceiver, ev &EventData, sender voidptr) { + assert receiver.ok == false + assert sender != 0 + assert ev.data == 'hello' +} diff --git a/v_windows/v/vlib/flag/README.md b/v_windows/v/vlib/flag/README.md new file mode 100644 index 0000000..1122f77 --- /dev/null +++ b/v_windows/v/vlib/flag/README.md @@ -0,0 +1,36 @@ +The `flag` module helps command-line flag parsing. +Main features are: +- parses flags like `-f` or '--flag' or '--stuff=things' or '--things stuff'. +- handles bool, int, float and string args. +- can print usage information listing all the declrared flags. +- handles unknown arguments as error. + +Usage example: + +```v +module main + +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.application('flag_example_tool') + fp.version('v0.0.1') + fp.limit_free_args(0, 0) // comment this, if you expect arbitrary texts after the options + fp.description('This tool is only designed to show how the flag lib is working') + fp.skip_executable() + an_int := fp.int('an_int', 0, 0o123, 'some int to define 0o123 is its default value') + a_bool := fp.bool('a_bool', 0, false, 'some boolean flag. --a_bool will set it to true.') + a_float := fp.float('a_float', 0, 1.0, 'some floating point value, by default 1.0 .') + a_string := fp.string('a_string', `a`, 'no text', 'finally, some text with ' + + ' `-a` as an abbreviation, so you can pass --a_string abc or just -a abc') + additional_args := fp.finalize() or { + eprintln(err) + println(fp.usage()) + return + } + println('an_int: $an_int | a_bool: $a_bool | a_float: $a_float | a_string: "$a_string" ') + println(additional_args.join_lines()) +} +``` diff --git a/v_windows/v/vlib/flag/default_flag_options_test.v b/v_windows/v/vlib/flag/default_flag_options_test.v new file mode 100644 index 0000000..7cf37d4 --- /dev/null +++ b/v_windows/v/vlib/flag/default_flag_options_test.v @@ -0,0 +1,35 @@ +import os + +const source = 'vlib/flag/testdata/simplest_flag_program.v' + +const simple_flag_app_executable = os.real_path(os.join_path(os.cache_dir(), 'simple_flag_app.exe')) + +fn testsuite_begin() { + os.chdir(@VMODROOT) or {} + os.rm(simple_flag_app_executable) or {} + res := os.execute('${@VEXE} -o $simple_flag_app_executable $source') + assert res.exit_code == 0 + assert os.execute(simple_flag_app_executable).exit_code == 0 +} + +fn testsuite_end() { + os.rm(simple_flag_app_executable) or {} + assert true +} + +fn check_program(opts string, extension string) { + result := source.replace('.v', extension) + res := os.execute('$simple_flag_app_executable $opts') + lines := os.read_lines(result) or { panic(err) } + assert res.exit_code == 0 + assert res.output.split_into_lines() == lines +} + +fn test_default_builtin_flag_options() { + check_program('', '.out') + check_program(' -- --help', '.dashdash.help.out') + check_program(' -- --version', '.dashdash.version.out') + check_program(' -h', '.help.out') + check_program(' --help', '.help.out') + check_program(' --version', '.version.out') +} diff --git a/v_windows/v/vlib/flag/flag.v b/v_windows/v/vlib/flag/flag.v new file mode 100644 index 0000000..85bb09d --- /dev/null +++ b/v_windows/v/vlib/flag/flag.v @@ -0,0 +1,624 @@ +module flag + +// data object storing information about a defined flag +pub struct Flag { +pub: + name string // name as it appears on command line + abbr byte // shortcut + usage string // help message + val_desc string // something like '' that appears in usage, + // and also the default value, when the flag is not given +} + +struct UnkownFlagError { + msg string + code int +} + +struct MinimumArgsCountError { + msg string + code int +} + +struct MaximumArgsCountError { + msg string + code int +} + +struct NoArgsExpectedError { + msg string + code int +} + +[unsafe] +fn (mut f Flag) free() { + unsafe { + f.name.free() + f.usage.free() + f.val_desc.free() + } +} + +pub fn (f Flag) str() string { + return '' + ' flag:\n' + ' name: $f.name\n' + + ' abbr: `$f.abbr.ascii_str()`\n' + ' usag: $f.usage\n' + + ' desc: $f.val_desc' +} + +pub fn (af []Flag) str() string { + mut res := []string{} + res << '\n []Flag = [' + for f in af { + res << f.str() + } + res << ' ]' + return res.join('\n') +} + +// +pub struct FlagParser { +pub: + original_args []string // the original arguments to be parsed + idx_dashdash int // the index of a `--`, -1 if there is not any + all_after_dashdash []string // all options after `--` are ignored, and will be passed to the application unmodified +pub mut: + usage_examples []string // when set, --help will print: + // Usage: $appname $usage_examples[0]` + // or: $appname $usage_examples[1]` + // etc + default_help_label string = 'display this help and exit' + default_version_label string = 'output version information and exit' + args []string // the current list of processed args + max_free_args int + flags []Flag // registered flags + application_name string + application_version string + application_description string + min_free_args int + args_description string + allow_unknown_args bool // whether passing undescribed arguments is allowed + footers []string // when set, --help will display all the collected footers at the bottom. +} + +[unsafe] +fn (mut f FlagParser) free() { + unsafe { + for a in f.args { + a.free() + } + f.args.free() + // + for flag in f.flags { + flag.free() + } + f.flags.free() + // + f.application_name.free() + f.application_version.free() + f.application_description.free() + f.args_description.free() + } +} + +pub const ( + // used for formating usage message + space = ' ' + underline = '-----------------------------------------------' + max_args_number = 4048 +) + +// create a new flag set for parsing command line arguments +pub fn new_flag_parser(args []string) &FlagParser { + original_args := args.clone() + idx_dashdash := args.index('--') + mut all_before_dashdash := args.clone() + mut all_after_dashdash := []string{} + if idx_dashdash >= 0 { + all_before_dashdash.trim(idx_dashdash) + if idx_dashdash < original_args.len { + all_after_dashdash = original_args[idx_dashdash + 1..] + } + } + return &FlagParser{ + original_args: original_args + idx_dashdash: idx_dashdash + all_after_dashdash: all_after_dashdash + args: all_before_dashdash + max_free_args: flag.max_args_number + } +} + +// usage_example - add an usage example +// All examples will be listed in the help screen. +// If you do not give any examples, then a default usage +// will be shown, based on whether the application takes +// options and expects additional parameters. +pub fn (mut fs FlagParser) usage_example(example string) { + fs.usage_examples << example +} + +// add_footer - add a footnote, that will be shown +// at the bottom of the help screen. +pub fn (mut fs FlagParser) footer(footer string) { + fs.footers << footer +} + +// change the application name to be used in 'usage' output +pub fn (mut fs FlagParser) application(name string) { + fs.application_name = name +} + +// change the application version to be used in 'usage' output +pub fn (mut fs FlagParser) version(vers string) { + fs.application_version = vers +} + +// description appends to the application description lines, shown +// in the help/usage screen +pub fn (mut fs FlagParser) description(desc string) { + if fs.application_description.len == 0 { + fs.application_description = desc + } else { + fs.application_description += '\n$desc' + } +} + +// in most cases you do not need the first argv for flag parsing +pub fn (mut fs FlagParser) skip_executable() { + fs.args.delete(0) +} + +// allow_unknown_args - if your program has sub commands, that have +// their own arguments, you can call .allow_unknown_args(), so that +// the subcommand arguments (which generally are not known to your +// parent program), will not cause the validation in .finalize() to fail. +pub fn (mut fs FlagParser) allow_unknown_args() { + fs.allow_unknown_args = true +} + +// private helper to register a flag +fn (mut fs FlagParser) add_flag(name string, abbr byte, usage string, desc string) { + fs.flags << Flag{ + name: name + abbr: abbr + usage: usage + val_desc: desc + } +} + +// private: general parsing a single argument +// - search args for existence +// if true +// extract the defined value as string +// else +// return an (dummy) error -> argument is not defined +// +// - the name, usage are registered +// - found arguments and corresponding values are removed from args list +[manualfree] +fn (mut fs FlagParser) parse_value(longhand string, shorthand byte) []string { + full := '--$longhand' + defer { + unsafe { full.free() } + } + mut found_entries := []string{} + mut to_delete := []int{} + defer { + unsafe { to_delete.free() } + } + mut should_skip_one := false + for i, arg in fs.args { + if should_skip_one { + should_skip_one = false + continue + } + if arg.len == 0 || arg[0] != `-` { + continue + } + if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { + if i + 1 >= fs.args.len { + return [] + } + nextarg := fs.args[i + 1] + if nextarg.len > 2 { + nextarg_rest := nextarg[..2] + if nextarg_rest == '--' { + // It could be end of input (--) or another argument (--abc). + // Both are invalid so die. + unsafe { nextarg_rest.free() } + return [] + } + unsafe { nextarg_rest.free() } + } + found_entries << fs.args[i + 1] + to_delete << i + to_delete << i + 1 + should_skip_one = true + continue + } + if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { + found_entries << arg[full.len + 1..] + to_delete << i + continue + } + } + for i, del in to_delete { + // i entrys are deleted so it's shifted left i times. + fs.args.delete(del - i) + } + return found_entries +} + +// special parsing for bool values +// see also: parse_value +// +// special: it is allowed to define bool flags without value +// -> '--flag' is parsed as true +// -> '--flag' is equal to '--flag=true' +fn (mut fs FlagParser) parse_bool_value(longhand string, shorthand byte) ?string { + { + full := '--$longhand' + for i, arg in fs.args { + if arg.len == 0 { + continue + } + if arg[0] != `-` { + continue + } + if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { + if fs.args.len > i + 1 && (fs.args[i + 1] in ['true', 'false']) { + val := fs.args[i + 1] + fs.args.delete(i + 1) + fs.args.delete(i) + return val + } else { + fs.args.delete(i) + return 'true' + } + } + if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { + // Flag abc=true + val := arg[full.len + 1..] + fs.args.delete(i) + return val + } + if arg.len > 1 && arg[0] == `-` && arg[1] != `-` && arg.index_byte(shorthand) != -1 { + // -abc is equivalent to -a -b -c + return 'true' + } + } + } + return error("parameter '$longhand' not found") +} + +// bool_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) bool_opt(name string, abbr byte, usage string) ?bool { + mut res := false + { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_bool_value(name, abbr) or { + return error("parameter '$name' not provided") + } + res = parsed == 'true' + } + return res +} + +// defining and parsing a bool flag +// if defined +// the value is returned (true/false) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to bool conversion +pub fn (mut fs FlagParser) bool(name string, abbr byte, bdefault bool, usage string) bool { + value := fs.bool_opt(name, abbr, usage) or { return bdefault } + return value +} + +// int_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) int_multi(name string, abbr byte, usage string) []int { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_value(name, abbr) + mut value := []int{} + for val in parsed { + value << val.int() + } + return value +} + +// int_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) int_opt(name string, abbr byte, usage string) ?int { + mut res := 0 + { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + parsed0 := parsed[0] + res = parsed0.int() + } + return res +} + +// defining and parsing an int flag +// if defined +// the value is returned (int) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to int conversion +pub fn (mut fs FlagParser) int(name string, abbr byte, idefault int, usage string) int { + value := fs.int_opt(name, abbr, usage) or { return idefault } + return value +} + +// float_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) float_multi(name string, abbr byte, usage string) []f64 { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_value(name, abbr) + mut value := []f64{} + for val in parsed { + value << val.f64() + } + return value +} + +// float_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) float_opt(name string, abbr byte, usage string) ?f64 { + mut res := 0.0 + { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + res = parsed[0].f64() + } + return res +} + +// defining and parsing a float flag +// if defined +// the value is returned (float) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to float conversion +pub fn (mut fs FlagParser) float(name string, abbr byte, fdefault f64, usage string) f64 { + value := fs.float_opt(name, abbr, usage) or { return fdefault } + return value +} + +// string_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) string_multi(name string, abbr byte, usage string) []string { + fs.add_flag(name, abbr, usage, '') + return fs.parse_value(name, abbr) +} + +// string_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) string_opt(name string, abbr byte, usage string) ?string { + mut res := '' + { + fs.add_flag(name, abbr, usage, '') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + res = parsed[0] + } + return res +} + +// defining and parsing a string flag +// if defined +// the value is returned (string) +// else +// the default value is returned +// version with abbr +pub fn (mut fs FlagParser) string(name string, abbr byte, sdefault string, usage string) string { + value := fs.string_opt(name, abbr, usage) or { return sdefault } + return value +} + +pub fn (mut fs FlagParser) limit_free_args_to_at_least(n int) { + if n > flag.max_args_number { + panic('flag.limit_free_args_to_at_least expect n to be smaller than $flag.max_args_number') + } + if n <= 0 { + panic('flag.limit_free_args_to_at_least expect n to be a positive number') + } + fs.min_free_args = n +} + +pub fn (mut fs FlagParser) limit_free_args_to_exactly(n int) { + if n > flag.max_args_number { + panic('flag.limit_free_args_to_exactly expect n to be smaller than $flag.max_args_number') + } + if n < 0 { + panic('flag.limit_free_args_to_exactly expect n to be a non negative number') + } + fs.min_free_args = n + fs.max_free_args = n +} + +// this will cause an error in finalize() if free args are out of range +// (min, ..., max) +pub fn (mut fs FlagParser) limit_free_args(min int, max int) { + if min > max { + panic('flag.limit_free_args expect min < max, got $min >= $max') + } + fs.min_free_args = min + fs.max_free_args = max +} + +pub fn (mut fs FlagParser) arguments_description(description string) { + fs.args_description = description +} + +// collect all given information and +pub fn (fs FlagParser) usage() string { + positive_min_arg := (fs.min_free_args > 0) + positive_max_arg := (fs.max_free_args > 0 && fs.max_free_args != flag.max_args_number) + no_arguments := (fs.min_free_args == 0 && fs.max_free_args == 0) + mut adesc := if fs.args_description.len > 0 { fs.args_description } else { '[ARGS]' } + if no_arguments { + adesc = '' + } + mut use := []string{} + if fs.application_version != '' { + use << '$fs.application_name $fs.application_version' + use << '$flag.underline' + } + if fs.usage_examples.len == 0 { + use << 'Usage: $fs.application_name [options] $adesc' + } else { + for i, example in fs.usage_examples { + if i == 0 { + use << 'Usage: $fs.application_name $example' + } else { + use << ' or: $fs.application_name $example' + } + } + } + use << '' + if fs.application_description != '' { + use << 'Description: $fs.application_description' + use << '' + } + // show a message about the [ARGS]: + if positive_min_arg || positive_max_arg || no_arguments { + if no_arguments { + use << 'This application does not expect any arguments' + use << '' + } else { + mut s := []string{} + if positive_min_arg { + s << 'at least $fs.min_free_args' + } + if positive_max_arg { + s << 'at most $fs.max_free_args' + } + if positive_min_arg && positive_max_arg && fs.min_free_args == fs.max_free_args { + s = ['exactly $fs.min_free_args'] + } + sargs := s.join(' and ') + use << 'The arguments should be $sargs in number.' + use << '' + } + } + if fs.flags.len > 0 { + use << 'Options:' + for f in fs.flags { + mut onames := []string{} + if f.abbr != 0 { + onames << '-$f.abbr.ascii_str()' + } + if f.name != '' { + if !f.val_desc.contains('') { + onames << '--$f.name $f.val_desc' + } else { + onames << '--$f.name' + } + } + option_names := ' ' + onames.join(', ') + mut xspace := '' + if option_names.len > flag.space.len - 2 { + xspace = '\n$flag.space' + } else { + xspace = flag.space[option_names.len..] + } + fdesc := '$option_names$xspace$f.usage' + use << fdesc + } + } + for footer in fs.footers { + use << footer + } + return use.join('\n').replace('- ,', ' ') +} + +fn (mut fs FlagParser) find_existing_flag(fname string) ?Flag { + for f in fs.flags { + if f.name == fname { + return f + } + } + return error('no such flag') +} + +fn (mut fs FlagParser) handle_builtin_options() { + mut show_version := false + mut show_help := false + fs.find_existing_flag('help') or { + show_help = fs.bool('help', `h`, false, fs.default_help_label) + } + fs.find_existing_flag('version') or { + show_version = fs.bool('version', 0, false, fs.default_version_label) + } + if show_help { + println(fs.usage()) + exit(0) + } + if show_version { + println('$fs.application_name $fs.application_version') + exit(0) + } +} + +// finalize - return all remaining arguments (non options). +// Call .finalize() after all arguments are defined. +// The remaining arguments are returned in the same order they are +// defined on the command line. If additional flags are found, i.e. +// (things starting with '--' or '-'), it returns an error. +pub fn (mut fs FlagParser) finalize() ?[]string { + fs.handle_builtin_options() + mut remaining := fs.args.clone() + if !fs.allow_unknown_args { + for a in remaining { + if (a.len >= 2 && a[..2] == '--') || (a.len == 2 && a[0] == `-`) { + return IError(&UnkownFlagError{ + msg: 'Unknown flag `$a`' + }) + } + } + } + if remaining.len < fs.min_free_args && fs.min_free_args > 0 { + return IError(&MinimumArgsCountError{ + msg: 'Expected at least $fs.min_free_args arguments, but given $remaining.len' + }) + } + if remaining.len > fs.max_free_args && fs.max_free_args > 0 { + return IError(&MaximumArgsCountError{ + msg: 'Expected at most $fs.max_free_args arguments, but given $remaining.len' + }) + } + if remaining.len > 0 && fs.max_free_args == 0 && fs.min_free_args == 0 { + return IError(&NoArgsExpectedError{ + msg: 'Expected no arguments, but given $remaining.len' + }) + } + remaining << fs.all_after_dashdash + return remaining +} + +// remaining_parameters will return all remaining parameters. +// Call .remaining_parameters() *AFTER* you have defined all options +// that your program needs. remaining_parameters will also print any +// parsing errors and stop the program. Use .finalize() instead, if +// you want more control over the error handling. +pub fn (mut fs FlagParser) remaining_parameters() []string { + return fs.finalize() or { + eprintln(err.msg) + println(fs.usage()) + exit(1) + } +} diff --git a/v_windows/v/vlib/flag/flag_test.v b/v_windows/v/vlib/flag/flag_test.v new file mode 100644 index 0000000..8326193 --- /dev/null +++ b/v_windows/v/vlib/flag/flag_test.v @@ -0,0 +1,412 @@ +import flag + +fn test_if_flag_not_given_return_default_values() { + mut fp := flag.new_flag_parser([]) + assert false == fp.bool('a_bool', 0, false, '') + assert 42 == fp.int('an_int', 0, 42, '') + assert 1.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'stuff', '') +} + +fn test_could_define_application_name_and_version() { + mut fp := flag.new_flag_parser([]) + fp.application('test app') + fp.version('0.0.42') + fp.description('some text') + assert fp.application_name == 'test app' + assert fp.application_version == '0.0.42' + assert fp.application_description == 'some text' +} + +fn test_bool_flags_do_not_need_an_value() { + mut fp := flag.new_flag_parser(['--a_bool']) + assert true == fp.bool('a_bool', 0, false, '') +} + +fn test_flags_could_be_defined_with_eq() { + mut fp := flag.new_flag_parser([ + '--an_int=42', + '--a_float=2.0', + '--bool_without', + '--a_string=stuff', + '--a_bool=true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_values_could_be_defined_without_eq() { + mut fp := flag.new_flag_parser([ + '--an_int', + '42', + '--a_float', + '2.0', + '--bool_without', + '--a_string', + 'stuff', + '--a_bool', + 'true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_values_could_be_defined_mixed() { + mut fp := flag.new_flag_parser([ + '--an_int', + '42', + '--a_float=2.0', + '--bool_without', + '--a_string', + 'stuff', + '--a_bool=true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_beaware_for_argument_names_with_same_prefix() { + mut fp := flag.new_flag_parser([ + '--short', + '5', + '--shorter=7', + ]) + assert 5 == fp.int('short', 0, 0o666, '') + assert 7 == fp.int('shorter', 0, 0o666, '') +} + +fn test_beaware_for_argument_names_with_same_prefix_inverse() { + mut fp := flag.new_flag_parser([ + '--shorter=7', + '--short', + '5', + ]) + assert 5 == fp.int('short', 0, 0o666, '') + assert 7 == fp.int('shorter', 0, 0o666, '') +} + +fn test_allow_to_skip_executable_path() { + mut fp := flag.new_flag_parser(['./path/to/execuable']) + fp.skip_executable() + args := fp.finalize() or { + assert false + return + } + assert !args.contains('./path/to/execuable') +} + +fn test_none_flag_arguments_are_allowed() { + mut fp := flag.new_flag_parser([ + 'file1', + '--an_int=2', + 'file2', + 'file3', + '--bool_without', + 'file4', + '--outfile', + 'outfile', + ]) + assert 2 == fp.int('an_int', 0, 0o666, '') + assert 'outfile' == fp.string('outfile', 0, 'bad', '') + assert true == fp.bool('bool_without', 0, false, '') +} + +fn test_finalize_returns_none_flag_arguments_ordered() { + mut fp := flag.new_flag_parser(['d', 'b', 'x', 'a', '--outfile', 'outfile']) + fp.string('outfile', 0, 'bad', '') + finalized := fp.finalize() or { + assert false + return + } + expected := ['d', 'b', 'x', 'a'] + for i, v in finalized { + assert v == expected[i] + } +} + +fn test_finalize_returns_error_for_unknown_flags_long() { + mut fp := flag.new_flag_parser(['--known', '--unknown']) + fp.bool('known', 0, false, '') + finalized := fp.finalize() or { + assert err.msg == 'Unknown flag `--unknown`' + return + } + assert finalized.len < 0 // expect error to be returned +} + +fn test_finalize_returns_error_for_unknown_flags_short() { + mut fp := flag.new_flag_parser(['--known', '-x']) + fp.bool('known', 0, false, '') + finalized := fp.finalize() or { + assert err.msg == 'Unknown flag `-x`' + return + } + assert finalized.len < 0 // expect error to be returned +} + +fn test_allow_to_build_usage_message() { + mut fp := flag.new_flag_parser([]) + fp.limit_free_args(1, 4) + fp.application('flag_tool') + fp.version('v0.0.0') + fp.description('some short information about this tool') + fp.int('an_int', 0, 0o666, 'some int to define') + fp.bool('a_bool', 0, false, 'some bool to define') + fp.bool('bool_without_but_really_big', 0, false, 'this should appear on the next line') + fp.float('a_float', 0, 1.0, 'some float as well') + fp.string('a_string', 0, 'not_stuff', 'your credit card number') + usage := fp.usage() + mut all_strings_found := true + for s in ['flag_tool', 'v0.0.0', 'an_int ', 'a_bool', 'bool_without', 'a_float ', + 'a_string ', 'some int to define', 'some bool to define', + 'this should appear on the next line', 'some float as well', 'your credit card number', + 'The arguments should be at least 1 and at most 4 in number.', 'Usage', 'Options:', + 'Description:', 'some short information about this tool'] { + if !usage.contains(s) { + eprintln(" missing '$s' in usage message") + all_strings_found = false + } + } + assert all_strings_found +} + +fn test_if_no_description_given_usage_message_does_not_contain_descpription() { + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') + fp.bool('a_bool', 0, false, '') + assert !fp.usage().contains('Description:') +} + +fn test_if_no_options_given_usage_message_does_not_contain_options() { + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') + assert !fp.usage().contains('Options:') +} + +fn test_free_args_could_be_limited() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(1, 4) + args := fp1.finalize() or { + assert false + return + } + assert args[0] == 'a' + assert args[1] == 'b' + assert args[2] == 'c' +} + +fn test_error_for_to_few_free_args() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(5, 6) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected at least 5 arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_error_for_to_much_free_args() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(1, 2) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected at most 2 arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_could_expect_no_free_args() { + mut fp1 := flag.new_flag_parser(['a']) + fp1.limit_free_args(0, 0) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected no arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_allow_abreviations() { + mut fp := flag.new_flag_parser(['-v', '-o', 'some_file', '-i', '42', '-f', '2.0']) + v := fp.bool('version', `v`, false, '') + o := fp.string('output', `o`, 'empty', '') + i := fp.int('count', `i`, 0, '') + f := fp.float('value', `f`, 0.0, '') + assert v == true + assert o == 'some_file' + assert i == 42 + assert f == 2.0 + u := fp.usage() + assert u.contains(' -v') + assert u.contains(' -o') + assert u.contains(' -i') + assert u.contains(' -f') + assert u.contains(' -o, --output ') + assert u.contains(' -i, --count ') + assert u.contains(' -f, --value ') +} + +fn test_allow_kebab_options() { + default_value := 'this_is_the_default_value_of_long_option' + long_option_value := 'this_is_a_long_option_value_as_argument' + mut fp := flag.new_flag_parser(['--my-long-flag', 'true', '--my-long-option', long_option_value]) + my_flag := fp.bool('my-long-flag', 0, false, 'flag with long-kebab-name') + my_option := fp.string('my-long-option', 0, default_value, 'string with long-kebab-name') + assert my_flag == true + assert my_option == long_option_value + u := fp.usage() + assert u.contains(' --my-long-flag') + assert u.contains(' --my-long-option') +} + +fn test_not_provided_option_is_not_returned() { + mut fp := flag.new_flag_parser([]) + fp.bool_opt('some-flag', `a`, '') or { + fp.int_opt('some-flag', `a`, '') or { + fp.float_opt('some-flag', `a`, '') or { + fp.string_opt('some-flag', `a`, '') or { + // Everything should not return + return + } + return + } + return + } + return + } + // If we reach here, one of them returned a value. + assert false +} + +fn test_provided_option_is_returned() { + mut fp := flag.new_flag_parser(['-a', '-b', '3', '-c', 'hello', '-d', '3.14']) + a := fp.bool_opt('some-flag', `a`, '') or { panic('bool_opt did not return a bool') } + b := fp.int_opt('some-flag', `b`, '') or { panic('int_opt did not return an int') } + c := fp.string_opt('some-flag', `c`, '') or { panic('string_opt did not return a string') } + d := fp.float_opt('some-flag', `d`, '') or { panic('float_opt did not return a float') } + assert true == a + assert b == 3 + assert c == 'hello' + assert d == 3.14 +} + +fn test_multiple_arguments() { + mut fp := flag.new_flag_parser([ + '-a', + '2', + '-a', + '3', + '-a', + '5', + '-b', + 'a', + '-b', + 'c', + '-b', + 'b', + '-c', + '1.23', + '-c', + '2.34', + '-c', + '3.45', + ]) + // TODO Move to array comparison once it's implemented + // assert fp.int_multi('some-flag', `a`, '') == [2, 3, 5] && + // fp.string_multi('some-flag', `b`, '') == ['a', 'c', 'b'] && + // fp.float_multi('some-flag', `c`, '') == [1.23, 2.34, 3.45] + a := fp.int_multi('some-flag', `a`, '') + b := fp.string_multi('some-flag', `b`, '') + c := fp.float_multi('some-flag', `c`, '') + assert a.len == 3 + assert b.len == 3 + assert c.len == 3 + assert a[0] == 2 + assert a[1] == 3 + assert a[2] == 5 + assert b[0] == 'a' + assert b[1] == 'c' + assert b[2] == 'b' + assert c[0] == 1.23 + assert c[1] == 2.34 + assert c[2] == 3.45 +} + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option() { + mut fp := flag.new_flag_parser([ + '--vabc', + '/abc', + ]) + verbose := fp.bool('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == false + assert vabc == '/abc' +} + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option_both_set() { + mut fp := flag.new_flag_parser([ + '-v', + '--vabc', + '/abc', + ]) + verbose := fp.bool('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == true + assert vabc == '/abc' +} + +fn test_single_dash() { + mut fp := flag.new_flag_parser([ + '-', + ]) + flag_update := fp.bool('update', `u`, false, 'Update tools') + assert flag_update == false +} + +fn test_optional_flags() { + mut fp := flag.new_flag_parser(['-a', '10', '-b']) + fp.int_opt('some-flag', `a`, '') or { + assert false + return + } + b := fp.string_opt('another-flag', `b`, '') or { 'some_default_value' } + assert b == 'some_default_value' +} + +fn test_dashdash_acts_as_parser_full_stop() ? { + mut fp := flag.new_flag_parser(['-b', '5', '--', '-d', '-x', '-b', '4', '-a', '-c', 'hello', + 'some', 'other', 'parameters']) + a := fp.bool_opt('a-bool-flag', `a`, '') or { false } + b := fp.int_opt('an-int-flag', `b`, '') or { -1 } + c := fp.string_opt('a-string-flag', `c`, '') or { 'default' } + assert a == false + assert b == 5 + assert c == 'default' + args := fp.finalize() ? + assert args.len > 0 + assert args[0] != '--' + assert args == ['-d', '-x', '-b', '4', '-a', '-c', 'hello', 'some', 'other', 'parameters'] +} + +fn test_dashdash_acts_as_parser_full_stop_dashdash_at_end() ? { + mut fp := flag.new_flag_parser(['-b', '5', '-b', '4', 'other', 'params', '--']) + b := fp.int_multi('an-int-flag', `b`, '') + assert b == [5, 4] + args := fp.finalize() ? + assert args.len > 0 +} + +fn test_empty_string_with_flag() { + mut fp := flag.new_flag_parser(['']) + s := fp.string('something', `s`, 'default', 'Hey parse me') +} diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.help.out b/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.help.out new file mode 100644 index 0000000..2529e9f --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.help.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--help'] diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.version.out b/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.version.out new file mode 100644 index 0000000..b0b4d65 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.dashdash.version.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--version'] diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.help.out b/v_windows/v/vlib/flag/testdata/simplest_flag_program.help.out new file mode 100644 index 0000000..9b5ab9e --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.help.out @@ -0,0 +1,7 @@ +abc 0.0.1 +----------------------------------------------- +Usage: abc [options] [ARGS] + +Options: + -h, --help display this help and exit + --version output version information and exit diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.out b/v_windows/v/vlib/flag/testdata/simplest_flag_program.out new file mode 100644 index 0000000..02b7cea --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: [] diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.v b/v_windows/v/vlib/flag/testdata/simplest_flag_program.v new file mode 100644 index 0000000..cee2ff7 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.v @@ -0,0 +1,14 @@ +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.application('abc') + fp.version('0.0.1') + fp.skip_executable() + rest_of_args := fp.finalize() or { + eprintln(err) + exit(1) + } + dump(rest_of_args) +} diff --git a/v_windows/v/vlib/flag/testdata/simplest_flag_program.version.out b/v_windows/v/vlib/flag/testdata/simplest_flag_program.version.out new file mode 100644 index 0000000..de52cb0 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/simplest_flag_program.version.out @@ -0,0 +1 @@ +abc 0.0.1 diff --git a/v_windows/v/vlib/flag/testdata/usage_example.help.out b/v_windows/v/vlib/flag/testdata/usage_example.help.out new file mode 100644 index 0000000..b40047b --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/usage_example.help.out @@ -0,0 +1,13 @@ +xyz 0.0.2 +----------------------------------------------- +Usage: xyz [NUMBER]... + or: xyz OPTION + +Description: description line 1 +description line 2 + +Options: + -h, --help display this help and exit + --version output version information and exit +footer 1 +footer 2 diff --git a/v_windows/v/vlib/flag/testdata/usage_example.out b/v_windows/v/vlib/flag/testdata/usage_example.out new file mode 100644 index 0000000..b2fd000 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/usage_example.out @@ -0,0 +1 @@ +[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def'] diff --git a/v_windows/v/vlib/flag/testdata/usage_example.v b/v_windows/v/vlib/flag/testdata/usage_example.v new file mode 100644 index 0000000..522b758 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/usage_example.v @@ -0,0 +1,17 @@ +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.skip_executable() + fp.application('xyz') + fp.version('0.0.2') + fp.usage_example('[NUMBER]...') + fp.usage_example('OPTION') + fp.description('description line 1') + fp.description('description line 2') + fp.footer('footer 1') + fp.footer('footer 2') + rest_of_args := fp.remaining_parameters() + dump(rest_of_args) +} diff --git a/v_windows/v/vlib/flag/testdata/usage_example.version.out b/v_windows/v/vlib/flag/testdata/usage_example.version.out new file mode 100644 index 0000000..9196894 --- /dev/null +++ b/v_windows/v/vlib/flag/testdata/usage_example.version.out @@ -0,0 +1 @@ +xyz 0.0.2 diff --git a/v_windows/v/vlib/flag/usage_example_test.v b/v_windows/v/vlib/flag/usage_example_test.v new file mode 100644 index 0000000..0f6c353 --- /dev/null +++ b/v_windows/v/vlib/flag/usage_example_test.v @@ -0,0 +1,35 @@ +import os + +const the_source = 'vlib/flag/testdata/usage_example.v' + +const the_executable = os.real_path(os.join_path(os.cache_dir(), 'flag_usage_example_app.exe')) + +fn testsuite_begin() { + os.chdir(@VMODROOT) or {} + os.rm(the_executable) or {} + res := os.execute('${@VEXE} -o $the_executable $the_source') + assert res.exit_code == 0 + assert os.execute(the_executable).exit_code == 0 + C.atexit(fn () { + os.rm(the_executable) or {} + }) +} + +fn normalise_lines(lines []string) string { + return '\n' + lines.join('\n') +} + +fn check_program(opts string, extension string) { + result := the_source.replace('.v', extension) + res := os.execute('$the_executable $opts') + assert res.exit_code == 0 + assert normalise_lines(res.output.split_into_lines()) == normalise_lines(os.read_lines(result) or { + panic(err) + }) +} + +fn test_normal_usage() { + check_program('abc def', '.out') + check_program(' --help', '.help.out') + check_program(' --version', '.version.out') +} diff --git a/v_windows/v/vlib/fontstash/a_d_use_freetype.v b/v_windows/v/vlib/fontstash/a_d_use_freetype.v new file mode 100644 index 0000000..199fe31 --- /dev/null +++ b/v_windows/v/vlib/fontstash/a_d_use_freetype.v @@ -0,0 +1,19 @@ +module fontstash + +#define FONS_USE_FREETYPE 1 +#flag windows -I @VEXEROOT/thirdparty/freetype/include +#flag windows -L @VEXEROOT/thirdparty/freetype/win64 +#flag linux -I/usr/include/freetype2 +#flag darwin -I/usr/local/include/freetype2 +// brew on m1 +#flag darwin -I/opt/homebrew/include/freetype2 +#flag darwin -L/opt/homebrew/lib +// MacPorts +#flag darwin -I/opt/local/include/freetype2 +#flag darwin -L/opt/local/lib +#flag freebsd -I/usr/local/include/freetype2 +#flag freebsd -Wl -L/usr/local/lib +#flag windows -lfreetype +#flag linux -lfreetype +#flag darwin -lfreetype +#flag darwin -lpng -lbz2 -lz diff --git a/v_windows/v/vlib/fontstash/fontstash.c.v b/v_windows/v/vlib/fontstash/fontstash.c.v new file mode 100644 index 0000000..6149a01 --- /dev/null +++ b/v_windows/v/vlib/fontstash/fontstash.c.v @@ -0,0 +1,172 @@ +module fontstash + +#flag -I @VEXEROOT/thirdparty/fontstash +#define FONTSTASH_IMPLEMENTATION +$if gcboehm ? { + #define FONTSTASH_MALLOC GC_MALLOC + #define FONTSTASH_REALLOC GC_REALLOC + #define FONTSTASH_FREE GC_FREE +} +#include "fontstash.h" +#flag -I /usr/local/Cellar/freetype/2.10.2/include/freetype2 + +$if windows { + $if tinyc { + #flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o + } +} $else { + #flag -lm +} + +//#flag -lfreetype +pub const ( + // TODO: fontstash.used_import is used to keep v from warning about unused imports + used_import = 1 +) + +// Contructor and destructor. +[inline] +pub fn create_internal(params &C.FONSparams) &C.FONScontext { + return C.fonsCreateInternal(params) +} + +[inline] +pub fn delete_internal(s &C.FONScontext) { + C.fonsDeleteInternal(s) +} + +[inline] +pub fn (s &C.FONScontext) set_error_callback(callback fn (voidptr, int, int), uptr voidptr) { + C.fonsSetErrorCallback(s, callback, uptr) +} + +// Returns current atlas size. +[inline] +pub fn (s &C.FONScontext) get_atlas_size(width &int, height &int) { + C.fonsGetAtlasSize(s, width, height) +} + +// Expands the atlas size. +[inline] +pub fn (s &C.FONScontext) expand_atlas(width int, height int) int { + return C.fonsExpandAtlas(s, width, height) +} + +// Resets the whole stash. +[inline] +pub fn (s &C.FONScontext) reset_atlas(width int, height int) int { + return C.fonsResetAtlas(s, width, height) +} + +// Add fonts +[inline] +pub fn (s &C.FONScontext) get_font_by_name(name &char) int { + return C.fonsGetFontByName(s, name) +} + +[inline] +pub fn (s &C.FONScontext) add_fallback_font(base int, fallback int) int { + return C.fonsAddFallbackFont(s, base, fallback) +} + +[inline] +pub fn (s &C.FONScontext) add_font_mem(name &char, data &byte, data_size int, free_data int) int { + return C.fonsAddFontMem(s, name, data, data_size, free_data) +} + +// State handling +[inline] +pub fn (s &C.FONScontext) push_state() { + C.fonsPushState(s) +} + +[inline] +pub fn (s &C.FONScontext) pop_state() { + C.fonsPopState(s) +} + +[inline] +pub fn (s &C.FONScontext) clear_state() { + C.fonsClearState(s) +} + +// State setting +[inline] +pub fn (s &C.FONScontext) set_size(size f32) { + C.fonsSetSize(s, size) +} + +[inline] +pub fn (s &C.FONScontext) set_color(color u32) { + C.fonsSetColor(s, color) +} + +[inline] +pub fn (s &C.FONScontext) set_spacing(spacing f32) { + C.fonsSetSpacing(s, spacing) +} + +[inline] +pub fn (s &C.FONScontext) set_blur(blur f32) { + C.fonsSetBlur(s, blur) +} + +[inline] +pub fn (s &C.FONScontext) set_align(align int) { + C.fonsSetAlign(s, align) +} + +[inline] +pub fn (s &C.FONScontext) set_font(font int) { + C.fonsSetFont(s, font) +} + +// Draw text +[inline] +pub fn (s &C.FONScontext) draw_text(x f32, y f32, str &char, end &char) f32 { + return C.fonsDrawText(s, x, y, str, end) +} + +// Measure text +[inline] +pub fn (s &C.FONScontext) text_bounds(x f32, y f32, str &char, end &char, bounds &f32) f32 { + return C.fonsTextBounds(s, x, y, str, end, bounds) +} + +[inline] +pub fn (s &C.FONScontext) line_bounds(y f32, miny &f32, maxy &f32) { + C.fonsLineBounds(s, y, miny, maxy) +} + +[inline] +pub fn (s &C.FONScontext) vert_metrics(ascender &f32, descender &f32, lineh &f32) { + C.fonsVertMetrics(s, ascender, descender, lineh) +} + +// Text iterator +[inline] +pub fn (s &C.FONScontext) text_iter_init(iter &C.FONStextIter, x f32, y f32, str &char, end &char) int { + return C.fonsTextIterInit(s, iter, x, y, str, end) +} + +[inline] +pub fn (s &C.FONScontext) text_iter_next(iter &C.FONStextIter, quad &C.FONSquad) int { + return C.fonsTextIterNext(s, iter, quad) +} + +// Pull texture changes +[inline] +pub fn (s &C.FONScontext) get_texture_data(width &int, height &int) &byte { + return &byte(C.fonsGetTextureData(s, width, height)) +} + +[inline] +pub fn (s &C.FONScontext) validate_texture(dirty &int) int { + return C.fonsValidateTexture(s, dirty) +} + +// Draws the stash texture for debugging +[inline] +pub fn (s &C.FONScontext) draw_debug(x f32, y f32) { + C.fonsDrawDebug(s, x, y) +} diff --git a/v_windows/v/vlib/fontstash/fontstash_funcs.c.v b/v_windows/v/vlib/fontstash/fontstash_funcs.c.v new file mode 100644 index 0000000..8e260e7 --- /dev/null +++ b/v_windows/v/vlib/fontstash/fontstash_funcs.c.v @@ -0,0 +1,53 @@ +module fontstash + +// Contructor and destructor. +fn C.fonsCreateInternal(params &C.FONSparams) &C.FONScontext +fn C.fonsDeleteInternal(s &C.FONScontext) + +fn C.fonsSetErrorCallback(s &C.FONScontext, callback fn (voidptr, int, int), uptr voidptr) + +// Returns current atlas size. +fn C.fonsGetAtlasSize(s &C.FONScontext, width &int, height &int) + +// Expands the atlas size. +fn C.fonsExpandAtlas(s &C.FONScontext, width int, height int) int + +// Resets the whole stash. +fn C.fonsResetAtlas(s &C.FONScontext, width int, height int) int + +// Add fonts +fn C.fonsGetFontByName(s &C.FONScontext, name &char) int +fn C.fonsAddFallbackFont(s &C.FONScontext, base int, fallback int) int +fn C.fonsAddFontMem(s &C.FONScontext, name &char, data &byte, dataSize int, freeData int) int + +// State handling +fn C.fonsPushState(s &C.FONScontext) +fn C.fonsPopState(s &C.FONScontext) +fn C.fonsClearState(s &C.FONScontext) + +// State setting +fn C.fonsSetSize(s &C.FONScontext, size f32) +fn C.fonsSetColor(s &C.FONScontext, color u32) +fn C.fonsSetSpacing(s &C.FONScontext, spacing f32) +fn C.fonsSetBlur(s &C.FONScontext, blur f32) +fn C.fonsSetAlign(s &C.FONScontext, align int) +fn C.fonsSetFont(s &C.FONScontext, font int) + +// Draw text +fn C.fonsDrawText(s &C.FONScontext, x f32, y f32, str &char, end &char) f32 + +// Measure text +fn C.fonsTextBounds(s &C.FONScontext, x f32, y f32, str &char, end &char, bounds &f32) f32 +fn C.fonsLineBounds(s &C.FONScontext, y f32, miny &f32, maxy &f32) +fn C.fonsVertMetrics(s &C.FONScontext, ascender &f32, descender &f32, lineh &f32) + +// Text iterator +fn C.fonsTextIterInit(s &C.FONScontext, iter &C.FONStextIter, x f32, y f32, str &char, end &char) int +fn C.fonsTextIterNext(s &C.FONScontext, iter &C.FONStextIter, quad &C.FONSquad) int + +// Pull texture changes +fn C.fonsGetTextureData(s &C.FONScontext, width &int, height &int) &char +fn C.fonsValidateTexture(s &C.FONScontext, dirty &int) int + +// Draws the stash texture for debugging +fn C.fonsDrawDebug(s &C.FONScontext, x f32, y f32) diff --git a/v_windows/v/vlib/fontstash/fontstash_structs.c.v b/v_windows/v/vlib/fontstash/fontstash_structs.c.v new file mode 100644 index 0000000..e34ef26 --- /dev/null +++ b/v_windows/v/vlib/fontstash/fontstash_structs.c.v @@ -0,0 +1,51 @@ +module fontstash + +pub struct C.FONSparams { + width int + height int + flags char + userPtr voidptr + // int (*renderCreate)(void* uptr, int width, int height) + renderCreate fn (uptr voidptr, width int, height int) int + // int (*renderResize)(void* uptr, int width, int height) + renderResize fn (uptr voidptr, width int, height int) int + // void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data) + renderUpdate fn (uptr voidptr, rect &int, data &byte) + // void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) + renderDraw fn (uptr voidptr, verts &f32, tcoords &f32, colors &u32, nverts int) + // void (*renderDelete)(void* uptr) + renderDelete fn (uptr voidptr) +} + +pub struct C.FONSquad { + x0 f32 + y0 f32 + s0 f32 + t0 f32 + x1 f32 + y1 f32 + s1 f32 + t1 f32 +} + +pub struct C.FONStextIter { + x f32 + y f32 + nextx f32 + nexty f32 + scale f32 + spacing f32 + codepoint u32 + isize i16 + iblur i16 + font &C.FONSfont + prevGlyphIndex int + str &byte + next &byte + end &byte + utf8state u32 +} + +pub struct C.FONSfont {} + +pub struct C.FONScontext {} diff --git a/v_windows/v/vlib/fontstash/fontstash_structs.v b/v_windows/v/vlib/fontstash/fontstash_structs.v new file mode 100644 index 0000000..8779bc0 --- /dev/null +++ b/v_windows/v/vlib/fontstash/fontstash_structs.v @@ -0,0 +1,29 @@ +module fontstash + +pub enum FonsFlags { + top_left = 1 + bottom_left = 2 +} + +pub enum FonsAlign { + // Horizontal align + left = 1 // Default + center = 2 + right = 4 + // Vertical align + top = 8 + middle = 16 + bottom = 32 + baseline = 64 // Default +} + +pub enum FonsErrorCode { + // Font atlas is full. + atlas_full = 1 + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + scratch_full = 2 + // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. + states_overflow = 3 + // Trying to pop too many states fonsPopState(). + states_underflow = 4 +} diff --git a/v_windows/v/vlib/gg/enums.v b/v_windows/v/vlib/gg/enums.v new file mode 100644 index 0000000..c87f986 --- /dev/null +++ b/v_windows/v/vlib/gg/enums.v @@ -0,0 +1,161 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +pub enum MouseButton { + left = 0 + right = 1 + middle = 2 + invalid = 256 +} + +// NB: unlike the MouseButton enum from above, +// the [flag]-ed enum here can have combined states, +// representing several pressed buttons at once. + +[flag] +pub enum MouseButtons { + left + right + middle +} + +[flag] +pub enum Modifier { + shift // (1<<0) + ctrl // (1<<1) + alt // (1<<2) + super // (1<<3) +} + +pub enum PenLineType { + solid + dashed + dotted +} + +pub enum KeyCode { + invalid = 0 + space = 32 + apostrophe = 39 //' + comma = 44 //, + minus = 45 //- + period = 46 //. + slash = 47 /// + _0 = 48 + _1 = 49 + _2 = 50 + _3 = 51 + _4 = 52 + _5 = 53 + _6 = 54 + _7 = 55 + _8 = 56 + _9 = 57 + semicolon = 59 //; + equal = 61 //= + a = 65 + b = 66 + c = 67 + d = 68 + e = 69 + f = 70 + g = 71 + h = 72 + i = 73 + j = 74 + k = 75 + l = 76 + m = 77 + n = 78 + o = 79 + p = 80 + q = 81 + r = 82 + s = 83 + t = 84 + u = 85 + v = 86 + w = 87 + x = 88 + y = 89 + z = 90 + left_bracket = 91 //[ + backslash = 92 //\ + right_bracket = 93 //] + grave_accent = 96 //` + world_1 = 161 // non-us #1 + world_2 = 162 // non-us #2 + escape = 256 + enter = 257 + tab = 258 + backspace = 259 + insert = 260 + delete = 261 + right = 262 + left = 263 + down = 264 + up = 265 + page_up = 266 + page_down = 267 + home = 268 + end = 269 + caps_lock = 280 + scroll_lock = 281 + num_lock = 282 + print_screen = 283 + pause = 284 + f1 = 290 + f2 = 291 + f3 = 292 + f4 = 293 + f5 = 294 + f6 = 295 + f7 = 296 + f8 = 297 + f9 = 298 + f10 = 299 + f11 = 300 + f12 = 301 + f13 = 302 + f14 = 303 + f15 = 304 + f16 = 305 + f17 = 306 + f18 = 307 + f19 = 308 + f20 = 309 + f21 = 310 + f22 = 311 + f23 = 312 + f24 = 313 + f25 = 314 + kp_0 = 320 + kp_1 = 321 + kp_2 = 322 + kp_3 = 323 + kp_4 = 324 + kp_5 = 325 + kp_6 = 326 + kp_7 = 327 + kp_8 = 328 + kp_9 = 329 + kp_decimal = 330 + kp_divide = 331 + kp_multiply = 332 + kp_subtract = 333 + kp_add = 334 + kp_enter = 335 + kp_equal = 336 + left_shift = 340 + left_control = 341 + left_alt = 342 + left_super = 343 + right_shift = 344 + right_control = 345 + right_alt = 346 + right_super = 347 + menu = 348 +} + +const key_code_max = 512 diff --git a/v_windows/v/vlib/gg/gg.c.v b/v_windows/v/vlib/gg/gg.c.v new file mode 100644 index 0000000..f6956e6 --- /dev/null +++ b/v_windows/v/vlib/gg/gg.c.v @@ -0,0 +1,278 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. + +module gg + +import os +import gx +import sokol +import sokol.sapp +import sokol.sgl +import sokol.gfx +import math + +pub struct Event { +pub mut: + frame_count u64 + typ sapp.EventType + key_code KeyCode + char_code u32 + key_repeat bool + modifiers u32 + mouse_button MouseButton + mouse_x f32 + mouse_y f32 + mouse_dx f32 + mouse_dy f32 + scroll_x f32 + scroll_y f32 + num_touches int + touches [8]C.sapp_touchpoint + window_width int + window_height int + framebuffer_width int + framebuffer_height int +} + +[heap] +pub struct Context { +mut: + render_text bool = true + // a cache with all images created by the user. used for sokol image init and to save space + // (so that the user can store image ids, not entire Image objects) + image_cache []Image + needs_refresh bool = true + ticks int // for ui mode only +pub: + native_rendering bool +pub mut: + scale f32 = 1.0 + // will get set to 2.0 for retina, will remain 1.0 for normal + width int + height int + clear_pass C.sg_pass_action + window C.sapp_desc + timage_pip C.sgl_pipeline + config Config + ft &FT + font_inited bool + ui_mode bool // do not redraw everything 60 times/second, but only when the user requests + frame u64 // the current frame counted from the start of the application; always increasing + // + mbtn_mask byte + mouse_buttons MouseButtons // typed version of mbtn_mask; easier to use for user programs + mouse_pos_x int + mouse_pos_y int + mouse_dx int + mouse_dy int + scroll_x int + scroll_y int + // + key_modifiers Modifier // the current key modifiers + key_repeat bool // whether the pressed key was an autorepeated one + pressed_keys [key_code_max]bool // an array representing all currently pressed keys + pressed_keys_edge [key_code_max]bool // true when the previous state of pressed_keys, + // *before* the current event was different +} + +fn gg_init_sokol_window(user_data voidptr) { + mut g := unsafe { &Context(user_data) } + desc := sapp.create_desc() + /* + desc := C.sg_desc{ + mtl_device: sapp.metal_get_device() + mtl_renderpass_descriptor_cb: sapp.metal_get_renderpass_descriptor + mtl_drawable_cb: sapp.metal_get_drawable + d3d11_device: sapp.d3d11_get_device() + d3d11_device_context: sapp.d3d11_get_device_context() + d3d11_render_target_view_cb: sapp.d3d11_get_render_target_view + d3d11_depth_stencil_view_cb: sapp.d3d11_get_depth_stencil_view + } + */ + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{} + sgl.setup(&sgl_desc) + g.scale = dpi_scale() + // is_high_dpi := sapp.high_dpi() + // fb_w := sapp.width() + // fb_h := sapp.height() + // println('g.scale=$g.scale is_high_dpi=$is_high_dpi fb_w=$fb_w fb_h=$fb_h') + // if g.config.init_text { + // `os.is_file()` won't work on Android if the font file is embedded into the APK + exists := $if !android { os.is_file(g.config.font_path) } $else { true } + if g.config.font_path != '' && !exists { + g.render_text = false + } else if g.config.font_path != '' && exists { + // t := time.ticks() + g.ft = new_ft( + font_path: g.config.font_path + custom_bold_font_path: g.config.custom_bold_font_path + scale: dpi_scale() + ) or { panic(err) } + // println('FT took ${time.ticks()-t} ms') + g.font_inited = true + } else { + if g.config.font_bytes_normal.len > 0 { + g.ft = new_ft( + bytes_normal: g.config.font_bytes_normal + bytes_bold: g.config.font_bytes_bold + bytes_mono: g.config.font_bytes_mono + bytes_italic: g.config.font_bytes_italic + scale: sapp.dpi_scale() + ) or { panic(err) } + g.font_inited = true + } else { + sfont := system_font_path() + if g.config.font_path != '' { + eprintln('font file "$g.config.font_path" does not exist, the system font ($sfont) was used instead.') + } + + g.ft = new_ft( + font_path: sfont + custom_bold_font_path: g.config.custom_bold_font_path + scale: sapp.dpi_scale() + ) or { panic(err) } + g.font_inited = true + } + } + // + mut pipdesc := C.sg_pipeline_desc{ + label: c'alpha_image' + } + unsafe { vmemset(&pipdesc, 0, int(sizeof(pipdesc))) } + + color_state := C.sg_color_state{ + blend: C.sg_blend_state{ + enabled: true + src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA) + dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA) + } + } + pipdesc.colors[0] = color_state + + g.timage_pip = sgl.make_pipeline(&pipdesc) + // + if g.config.init_fn != voidptr(0) { + g.config.init_fn(g.config.user_data) + } + // Create images now that we can do that after sg is inited + if g.native_rendering { + return + } + + for i in 0 .. g.image_cache.len { + if g.image_cache[i].simg.id == 0 { + g.image_cache[i].init_sokol_image() + } + } +} + +// +pub fn new_context(cfg Config) &Context { + mut g := &Context{ + width: cfg.width + height: cfg.height + config: cfg + ft: 0 + ui_mode: cfg.ui_mode + native_rendering: cfg.native_rendering + } + g.set_bg_color(cfg.bg_color) + // C.printf('new_context() %p\n', cfg.user_data) + window := C.sapp_desc{ + user_data: g + init_userdata_cb: gg_init_sokol_window + frame_userdata_cb: gg_frame_fn + event_userdata_cb: gg_event_fn + fail_userdata_cb: gg_fail_fn + cleanup_userdata_cb: gg_cleanup_fn + window_title: &char(cfg.window_title.str) + html5_canvas_name: &char(cfg.window_title.str) + width: cfg.width + height: cfg.height + sample_count: cfg.sample_count + high_dpi: true + fullscreen: cfg.fullscreen + __v_native_render: cfg.native_rendering + } + g.window = window + return g +} + +pub fn (ctx &Context) draw_circle_line(x f32, y f32, r int, segments int, c gx.Color) { + $if macos { + if ctx.native_rendering { + C.darwin_draw_circle(x - r + 1, ctx.height - (y + r + 3), r, c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + nr := r * ctx.scale + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + sgl.begin_line_strip() + for i := 0; i < segments + 1; i++ { + theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + xx = nr * math.cosf(theta) + yy = nr * math.sinf(theta) + sgl.v2f(xx + nx, yy + ny) + } + sgl.end() +} + +pub fn high_dpi() bool { + return C.sapp_high_dpi() +} + +pub fn screen_size() Size { + $if macos { + return C.gg_get_screen_size() + } + // TODO windows, linux, etc + return Size{} +} + +fn C.WaitMessage() + +/* +pub fn wait_events() { + unsafe { + $if macos { + #NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + #untilDate:[NSDate distantFuture] + #inMode:NSDefaultRunLoopMode + #dequeue:YES]; + #[NSApp sendEvent:event]; + } + $if windows { + C.WaitMessage() + } + } +} +*/ + +// TODO: Fix alpha +pub fn (ctx &Context) draw_rect(x f32, y f32, w f32, h f32, c gx.Color) { + $if macos { + if ctx.native_rendering { + C.darwin_draw_rect(x, ctx.height - (y + h), w, h, c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + h) * ctx.scale) + sgl.end() +} diff --git a/v_windows/v/vlib/gg/gg.v b/v_windows/v/vlib/gg/gg.v new file mode 100644 index 0000000..d1dbaa4 --- /dev/null +++ b/v_windows/v/vlib/gg/gg.v @@ -0,0 +1,750 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. + +module gg + +import gx +import sokol.sapp +import sokol.sgl +import sokol.gfx +import math + +pub type FNCb = fn (data voidptr) + +pub type FNEvent = fn (e &Event, data voidptr) + +pub type FNFail = fn (msg string, data voidptr) + +pub type FNKeyDown = fn (c KeyCode, m Modifier, data voidptr) + +pub type FNKeyUp = fn (c KeyCode, m Modifier, data voidptr) + +pub type FNMove = fn (x f32, y f32, data voidptr) + +pub type FNClick = fn (x f32, y f32, button MouseButton, data voidptr) + +pub type FNUnClick = fn (x f32, y f32, button MouseButton, data voidptr) + +pub type FNChar = fn (c u32, data voidptr) + +pub struct Config { +pub: + width int + height int + use_ortho bool // unused, still here just for backwards compatibility + retina bool + resizable bool + user_data voidptr + font_size int + create_window bool + // window_user_ptr voidptr + window_title string + borderless_window bool + always_on_top bool + bg_color gx.Color + init_fn FNCb = voidptr(0) + frame_fn FNCb = voidptr(0) + native_frame_fn FNCb = voidptr(0) + cleanup_fn FNCb = voidptr(0) + fail_fn FNFail = voidptr(0) + // + event_fn FNEvent = voidptr(0) + quit_fn FNEvent = voidptr(0) + // + keydown_fn FNKeyDown = voidptr(0) + keyup_fn FNKeyUp = voidptr(0) + char_fn FNChar = voidptr(0) + // + move_fn FNMove = voidptr(0) + click_fn FNClick = voidptr(0) + unclick_fn FNUnClick = voidptr(0) + leave_fn FNEvent = voidptr(0) + enter_fn FNEvent = voidptr(0) + resized_fn FNEvent = voidptr(0) + scroll_fn FNEvent = voidptr(0) + // wait_events bool // set this to true for UIs, to save power + fullscreen bool + scale f32 = 1.0 + sample_count int + // ved needs this + // init_text bool + font_path string + custom_bold_font_path string + ui_mode bool // refreshes only on events to save CPU usage + // font bytes for embedding + font_bytes_normal []byte + font_bytes_bold []byte + font_bytes_mono []byte + font_bytes_italic []byte + native_rendering bool // Cocoa on macOS/iOS, GDI+ on Windows +} + +pub struct PenConfig { + color gx.Color + line_type PenLineType = .solid + thickness int = 1 +} + +pub struct Size { +pub: + width int + height int +} + +fn gg_frame_fn(user_data voidptr) { + mut ctx := unsafe { &Context(user_data) } + ctx.frame++ + if ctx.config.frame_fn == voidptr(0) { + return + } + if ctx.native_rendering { + // return + } + + if ctx.ui_mode && !ctx.needs_refresh { + // Draw 3 more frames after the "stop refresh" command + ctx.ticks++ + if ctx.ticks > 3 { + return + } + } + ctx.config.frame_fn(ctx.config.user_data) + ctx.needs_refresh = false +} + +pub fn (mut ctx Context) refresh_ui() { + ctx.needs_refresh = true + ctx.ticks = 0 +} + +fn gg_event_fn(ce &C.sapp_event, user_data voidptr) { + // e := unsafe { &sapp.Event(ce) } + mut e := unsafe { &Event(ce) } + mut g := unsafe { &Context(user_data) } + if g.ui_mode { + g.refresh_ui() + } + if e.typ == .mouse_down { + bitplace := int(e.mouse_button) + g.mbtn_mask |= byte(1 << bitplace) + g.mouse_buttons = MouseButtons(g.mbtn_mask) + } + if e.typ == .mouse_up { + bitplace := int(e.mouse_button) + g.mbtn_mask &= ~(byte(1 << bitplace)) + g.mouse_buttons = MouseButtons(g.mbtn_mask) + } + if e.typ == .mouse_move && e.mouse_button == .invalid { + if g.mbtn_mask & 0x01 > 0 { + e.mouse_button = .left + } + if g.mbtn_mask & 0x02 > 0 { + e.mouse_button = .right + } + if g.mbtn_mask & 0x04 > 0 { + e.mouse_button = .middle + } + } + g.mouse_pos_x = int(e.mouse_x / g.scale) + g.mouse_pos_y = int(e.mouse_y / g.scale) + g.mouse_dx = int(e.mouse_dx / g.scale) + g.mouse_dy = int(e.mouse_dy / g.scale) + g.scroll_x = int(e.scroll_x / g.scale) + g.scroll_y = int(e.scroll_y / g.scale) + g.key_modifiers = Modifier(e.modifiers) + g.key_repeat = e.key_repeat + if e.typ in [.key_down, .key_up] { + key_idx := int(e.key_code) % key_code_max + prev := g.pressed_keys[key_idx] + next := e.typ == .key_down + g.pressed_keys[key_idx] = next + g.pressed_keys_edge[key_idx] = prev != next + } + if g.config.event_fn != voidptr(0) { + g.config.event_fn(e, g.config.user_data) + } + match e.typ { + .mouse_move { + if g.config.move_fn != voidptr(0) { + g.config.move_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, g.config.user_data) + } + } + .mouse_down { + if g.config.click_fn != voidptr(0) { + g.config.click_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, e.mouse_button, + g.config.user_data) + } + } + .mouse_up { + if g.config.unclick_fn != voidptr(0) { + g.config.unclick_fn(e.mouse_x / g.scale, e.mouse_y / g.scale, e.mouse_button, + g.config.user_data) + } + } + .mouse_leave { + if g.config.leave_fn != voidptr(0) { + g.config.leave_fn(e, g.config.user_data) + } + } + .mouse_enter { + if g.config.enter_fn != voidptr(0) { + g.config.enter_fn(e, g.config.user_data) + } + } + .mouse_scroll { + if g.config.scroll_fn != voidptr(0) { + g.config.scroll_fn(e, g.config.user_data) + } + } + .key_down { + if g.config.keydown_fn != voidptr(0) { + g.config.keydown_fn(e.key_code, Modifier(e.modifiers), g.config.user_data) + } + } + .key_up { + if g.config.keyup_fn != voidptr(0) { + g.config.keyup_fn(e.key_code, Modifier(e.modifiers), g.config.user_data) + } + } + .char { + if g.config.char_fn != voidptr(0) { + g.config.char_fn(e.char_code, g.config.user_data) + } + } + .resized { + if g.config.resized_fn != voidptr(0) { + g.config.resized_fn(e, g.config.user_data) + } + } + .quit_requested { + if g.config.quit_fn != voidptr(0) { + g.config.quit_fn(e, g.config.user_data) + } + } + else { + // dump(e) + } + } +} + +fn gg_cleanup_fn(user_data voidptr) { + mut g := unsafe { &Context(user_data) } + if g.config.cleanup_fn != voidptr(0) { + g.config.cleanup_fn(g.config.user_data) + } +} + +fn gg_fail_fn(msg &char, user_data voidptr) { + mut g := unsafe { &Context(user_data) } + vmsg := unsafe { tos3(msg) } + if g.config.fail_fn != voidptr(0) { + g.config.fail_fn(vmsg, g.config.user_data) + } else { + eprintln('gg error: $vmsg') + } +} + +pub fn (gg &Context) run() { + sapp.run(&gg.window) +} + +// quit closes the context window and exits the event loop for it +pub fn (ctx &Context) quit() { + sapp.request_quit() // does not require ctx right now, but sokol multi-window might in the future +} + +pub fn (mut ctx Context) set_bg_color(c gx.Color) { + ctx.clear_pass = gfx.create_clear_pass(f32(c.r) / 255.0, f32(c.g) / 255.0, f32(c.b) / 255.0, + f32(c.a) / 255.0) +} + +[inline] +pub fn (ctx &Context) draw_square(x f32, y f32, s f32, c gx.Color) { + ctx.draw_rect(x, y, s, s, c) +} + +[inline] +pub fn (ctx &Context) set_pixel(x f32, y f32, c gx.Color) { + ctx.draw_square(x, y, 1, c) +} + +pub fn (ctx &Context) set_pixels(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + for i in 0 .. len { + x, y := points[i * 2], points[i * 2 + 1] + + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + 1) * ctx.scale, y * ctx.scale) + sgl.v2f((x + 1) * ctx.scale, (y + 1) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + 1) * ctx.scale) + } + sgl.end() +} + +pub fn (ctx &Context) draw_triangle(x f32, y f32, x2 f32, y2 f32, x3 f32, y3 f32, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_quads() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) + sgl.v2f(x3 * ctx.scale, y3 * ctx.scale) + sgl.end() +} + +pub fn (ctx &Context) draw_empty_rect(x f32, y f32, w f32, h f32, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y - 1) * ctx.scale) + sgl.end() +} + +[inline] +pub fn (ctx &Context) draw_empty_square(x f32, y f32, s f32, c gx.Color) { + ctx.draw_empty_rect(x, y, s, s, c) +} + +pub fn (ctx &Context) draw_circle(x f32, y f32, r f32, c gx.Color) { + ctx.draw_circle_with_segments(x, y, r, 10, c) +} + +pub fn (ctx &Context) draw_circle_with_segments(x f32, y f32, r f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + nr := r * ctx.scale + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + sgl.begin_triangle_strip() + for i := 0; i < segments + 1; i++ { + theta = 2.0 * f32(math.pi) * f32(i) / f32(segments) + xx = nr * math.cosf(theta) + yy = nr * math.sinf(theta) + sgl.v2f(xx + nx, yy + ny) + sgl.v2f(nx, ny) + } + sgl.end() +} + +pub fn (ctx &Context) draw_arc_line(x f32, y f32, r int, start_angle f32, arc_angle f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + theta := f32(arc_angle / f32(segments)) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + nx := x * ctx.scale + ny := y * ctx.scale + mut xx := f32(r * math.cosf(start_angle)) + mut yy := f32(r * math.sinf(start_angle)) + sgl.begin_line_strip() + for i := 0; i < segments + 1; i++ { + sgl.v2f(xx + nx, yy + ny) + tx := -yy + ty := xx + xx += tx * tan_factor + yy += ty * tan_factor + xx *= rad_factor + yy *= rad_factor + } + sgl.end() +} + +pub fn (ctx &Context) draw_arc(x f32, y f32, r int, start_angle f32, arc_angle f32, segments int, c gx.Color) { + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + nx := x * ctx.scale + ny := y * ctx.scale + theta := f32(arc_angle / f32(segments)) + tan_factor := math.tanf(theta) + rad_factor := math.cosf(theta) + mut xx := f32(r * math.cosf(start_angle)) + mut yy := f32(r * math.sinf(start_angle)) + sgl.begin_triangle_strip() + for i := 0; i < segments + 1; i++ { + sgl.v2f(xx + nx, yy + ny) + sgl.v2f(nx, ny) + tx := -yy + ty := xx + xx += tx * tan_factor + yy += ty * tan_factor + xx *= rad_factor + yy *= rad_factor + } + sgl.end() +} + +pub fn (gg &Context) begin() { + if gg.render_text && gg.font_inited { + gg.ft.flush() + } + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) +} + +pub fn (gg &Context) end() { + gfx.begin_default_pass(gg.clear_pass, sapp.width(), sapp.height()) + sgl.draw() + gfx.end_pass() + gfx.commit() + /* + if gg.config.wait_events { + // println('gg: waiting') + wait_events() + } + */ +} + +// resize the context's Window +pub fn (mut ctx Context) resize(width int, height int) { + ctx.width = width + ctx.height = height +} + +// draw_line draws a line between the points provided +pub fn (ctx &Context) draw_line(x f32, y f32, x2 f32, y2 f32, c gx.Color) { + $if macos { + if ctx.native_rendering { + // Make the line more clear on hi dpi screens: draw a rectangle + mut width := math.abs(x2 - x) + mut height := math.abs(y2 - y) + if width == 0 { + width = 1 + } else if height == 0 { + height = 1 + } + ctx.draw_rect(x, y, f32(width), f32(height), c) + return + } + } + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.begin_line_strip() + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f(x2 * ctx.scale, y2 * ctx.scale) + sgl.end() +} + +// draw_line_with_config draws a line between the points provided with the PenConfig +pub fn (ctx &Context) draw_line_with_config(x f32, y f32, x2 f32, y2 f32, config PenConfig) { + if config.color.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + + if config.thickness <= 0 { + return + } + + nx := x * ctx.scale + ny := y * ctx.scale + nx2 := x2 * ctx.scale + ny2 := y2 * ctx.scale + + dx := nx2 - nx + dy := ny2 - ny + length := math.sqrtf(math.powf(x2 - x, 2) + math.powf(y2 - y, 2)) + theta := f32(math.atan2(dy, dx)) + + sgl.push_matrix() + + sgl.translate(nx, ny, 0) + sgl.rotate(theta, 0, 0, 1) + sgl.translate(-nx, -ny, 0) + + if config.line_type == .solid { + ctx.draw_rect(x, y, length, config.thickness, config.color) + } else { + size := if config.line_type == .dotted { config.thickness } else { config.thickness * 3 } + space := if size == 1 { 2 } else { size } + + mut available := length + mut start_x := x + + for i := 0; available > 0; i++ { + if i % 2 == 0 { + ctx.draw_rect(start_x, y, size, config.thickness, config.color) + available -= size + start_x += size + continue + } + + available -= space + start_x += space + } + } + + sgl.pop_matrix() +} + +pub fn (ctx &Context) draw_rounded_rect(x f32, y f32, w f32, h f32, radius f32, color gx.Color) { + sgl.c4b(color.r, color.g, color.b, color.a) + sgl.begin_triangle_strip() + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + r := radius * ctx.scale + nx := x * ctx.scale + ny := y * ctx.scale + width := w * ctx.scale + height := h * ctx.scale + segments := 2 * math.pi * r + segdiv := segments / 4 + rb := 0 + lb := int(rb + segdiv) + lt := int(lb + segdiv) + rt := int(lt + segdiv) + // left top + lx := nx + r + ly := ny + r + for i in lt .. rt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lx, yy + ly) + sgl.v2f(lx, ly) + } + // right top + mut rx := nx + width - r + mut ry := ny + r + for i in rt .. int(segments) { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rx, yy + ry) + sgl.v2f(rx, ry) + } + // right bottom + mut rbx := rx + mut rby := ny + height - r + for i in rb .. lb { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rbx, yy + rby) + sgl.v2f(rbx, rby) + } + // left bottom + mut lbx := lx + mut lby := ny + height - r + for i in lb .. lt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lbx, yy + lby) + sgl.v2f(lbx, lby) + } + sgl.v2f(lx + xx, ly) + sgl.v2f(lx, ly) + sgl.end() + sgl.begin_quads() + sgl.v2f(lx, ly) + sgl.v2f(rx, ry) + sgl.v2f(rbx, rby) + sgl.v2f(lbx, lby) + sgl.end() +} + +pub fn (ctx &Context) draw_empty_rounded_rect(x f32, y f32, w f32, h f32, radius f32, border_color gx.Color) { + mut theta := f32(0) + mut xx := f32(0) + mut yy := f32(0) + r := radius * ctx.scale + nx := x * ctx.scale + ny := y * ctx.scale + width := w * ctx.scale + height := h * ctx.scale + segments := 2 * math.pi * r + segdiv := segments / 4 + rb := 0 + lb := int(rb + segdiv) + lt := int(lb + segdiv) + rt := int(lt + segdiv) + sgl.c4b(border_color.r, border_color.g, border_color.b, border_color.a) + sgl.begin_line_strip() + // left top + lx := nx + r + ly := ny + r + for i in lt .. rt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lx, yy + ly) + } + // right top + mut rx := nx + width - r + mut ry := ny + r + for i in rt .. int(segments) { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rx, yy + ry) + } + // right bottom + mut rbx := rx + mut rby := ny + height - r + for i in rb .. lb { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + rbx, yy + rby) + } + // left bottom + mut lbx := lx + mut lby := ny + height - r + for i in lb .. lt { + theta = 2 * f32(math.pi) * f32(i) / segments + xx = r * math.cosf(theta) + yy = r * math.sinf(theta) + sgl.v2f(xx + lbx, yy + lby) + } + sgl.v2f(lx + xx, ly) + sgl.end() +} + +// draw_convex_poly draws a convex polygon, given an array of points, and a color. +// Note that the points must be given in clockwise order. +pub fn (ctx &Context) draw_convex_poly(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + assert len >= 3 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_triangle_strip() + x0 := points[0] * ctx.scale + y0 := points[1] * ctx.scale + for i in 1 .. (len / 2 + 1) { + sgl.v2f(x0, y0) + sgl.v2f(points[i * 4 - 2] * ctx.scale, points[i * 4 - 1] * ctx.scale) + sgl.v2f(points[i * 4] * ctx.scale, points[i * 4 + 1] * ctx.scale) + } + + if len % 2 == 0 { + sgl.v2f(points[2 * len - 2] * ctx.scale, points[2 * len - 1] * ctx.scale) + } + sgl.end() +} + +// draw_empty_poly - draws the borders of a polygon, given an array of points, and a color. +// Note that the points must be given in clockwise order. +pub fn (ctx &Context) draw_empty_poly(points []f32, c gx.Color) { + assert points.len % 2 == 0 + len := points.len / 2 + assert len >= 3 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_line_strip() + for i in 0 .. len { + sgl.v2f(points[2 * i] * ctx.scale, points[2 * i + 1] * ctx.scale) + } + sgl.v2f(points[0] * ctx.scale, points[1] * ctx.scale) + sgl.end() +} + +// draw_cubic_bezier draws a cubic Bézier curve, also known as a spline, from four points. +// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates). +// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`. +// Please see `draw_cubic_bezier_in_steps` to control the amount of steps (segments) used to draw the curve. +pub fn (ctx &Context) draw_cubic_bezier(points []f32, c gx.Color) { + ctx.draw_cubic_bezier_in_steps(points, u32(30 * ctx.scale), c) +} + +// draw_cubic_bezier_in_steps draws a cubic Bézier curve, also known as a spline, from four points. +// The smoothness of the curve can be controlled with the `steps` parameter. `steps` determines how many iterations is +// taken to draw the curve. +// The four points is provided as one `points` array which contains a stream of point pairs (x and y coordinates). +// Thus a cubic Bézier could be declared as: `points := [x1, y1, control_x1, control_y1, control_x2, control_y2, x2, y2]`. +pub fn (ctx &Context) draw_cubic_bezier_in_steps(points []f32, steps u32, c gx.Color) { + assert steps > 0 + assert points.len == 8 + + if c.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + sgl.c4b(c.r, c.g, c.b, c.a) + + sgl.begin_line_strip() + + p1_x, p1_y := points[0], points[1] + p2_x, p2_y := points[6], points[7] + + ctrl_p1_x, ctrl_p1_y := points[2], points[3] + ctrl_p2_x, ctrl_p2_y := points[4], points[5] + + // The constant 3 is actually points.len() - 1; + + step := f32(1.0) / steps + sgl.v2f(p1_x * ctx.scale, p1_y * ctx.scale) + for u := f32(0.0); u <= f32(1.0); u += step { + pow_2_u := u * u + pow_3_u := pow_2_u * u + + x := pow_3_u * (p2_x + 3 * (ctrl_p1_x - ctrl_p2_x) - p1_x) + + 3 * pow_2_u * (p1_x - 2 * ctrl_p1_x + ctrl_p2_x) + 3 * u * (ctrl_p1_x - p1_x) + p1_x + + y := pow_3_u * (p2_y + 3 * (ctrl_p1_y - ctrl_p2_y) - p1_y) + + 3 * pow_2_u * (p1_y - 2 * ctrl_p1_y + ctrl_p2_y) + 3 * u * (ctrl_p1_y - p1_y) + p1_y + + sgl.v2f(x * ctx.scale, y * ctx.scale) + } + sgl.v2f(p2_x * ctx.scale, p2_y * ctx.scale) + + sgl.end() +} + +// window_size returns the `Size` of the active window +pub fn window_size() Size { + s := dpi_scale() + return Size{int(sapp.width() / s), int(sapp.height() / s)} +} + +// window_size_real_pixels returns the `Size` of the active window without scale +pub fn window_size_real_pixels() Size { + return Size{sapp.width(), sapp.height()} +} + +pub fn dpi_scale() f32 { + mut s := sapp.dpi_scale() + $if android { + s *= android_dpi_scale() + } + // NB: on older X11, `Xft.dpi` from ~/.Xresources, that sokol uses, + // may not be set which leads to sapp.dpi_scale reporting incorrectly 0.0 + if s < 0.1 { + s = 1.0 + } + return s +} diff --git a/v_windows/v/vlib/gg/gg_android.c.v b/v_windows/v/vlib/gg/gg_android.c.v new file mode 100644 index 0000000..23450f2 --- /dev/null +++ b/v_windows/v/vlib/gg/gg_android.c.v @@ -0,0 +1,37 @@ +module gg + +import sokol.sapp + +#include +#include + +fn C.AConfiguration_new() voidptr +fn C.AConfiguration_fromAssetManager(voidptr, voidptr) +fn C.AConfiguration_getDensity(voidptr) u32 +fn C.AConfiguration_delete(voidptr) + +struct C.AAssetManager {} + +// See https://developer.android.com/ndk/reference/struct/a-native-activity for more info. +struct C.ANativeActivity { +pub: + assetManager voidptr // Pointer to the Asset Manager instance for the application. (AAssetManager *) + callbacks voidptr // Pointer to the callback function table of the native application. (struct ANativeActivityCallbacks *) + clazz voidptr // The NativeActivity object handle. + env voidptr // JNI context for the main thread of the app. + externalDataPath &char // Path to this application's external (removable/mountable) data directory. + instance voidptr // This is the native instance of the application. + internalDataPath &char // Path to this application's internal data directory. + obbPath &char // Available starting with Honeycomb: path to the directory containing the application's OBB files (if any). + sdkVersion int // The platform's SDK version code. + vm voidptr // The global handle on the process's Java VM +} + +pub fn android_dpi_scale() f32 { + config := C.AConfiguration_new() + activity := &C.ANativeActivity(sapp.android_get_native_activity()) + C.AConfiguration_fromAssetManager(config, activity.assetManager) + density := C.AConfiguration_getDensity(config) + C.AConfiguration_delete(config) + return f32(density) / 160 +} diff --git a/v_windows/v/vlib/gg/gg_darwin.c.v b/v_windows/v/vlib/gg/gg_darwin.c.v new file mode 100644 index 0000000..d95b8c4 --- /dev/null +++ b/v_windows/v/vlib/gg/gg_darwin.c.v @@ -0,0 +1,23 @@ +module gg + +#include "@VEXEROOT/vlib/gg/gg_darwin.m" + +fn C.gg_get_screen_size() Size + +fn C.darwin_draw_string(x int, y int, s string, cfg voidptr) + +fn C.darwin_text_width(s string) int + +fn C.darwin_text_width_runes(r []rune) int + +fn C.darwin_window_refresh() + +fn C.darwin_draw_rect(f32, f32, f32, f32, voidptr) + +fn C.darwin_create_image(path string) Image + +fn C.darwin_draw_image(f32, f32, f32, f32, &Image) + +fn C.darwin_draw_circle(f32, f32, f32, voidptr) + +//, gx.Color c) diff --git a/v_windows/v/vlib/gg/gg_darwin.m b/v_windows/v/vlib/gg/gg_darwin.m new file mode 100644 index 0000000..3ec883d --- /dev/null +++ b/v_windows/v/vlib/gg/gg_darwin.m @@ -0,0 +1,125 @@ +#include + +NSColor* nscolor(gx__Color c) { + float red= (float)c.r / 255.0f; + float green= (float)c.g / 255.0f; + float blue= (float)c.b / 255.0f; + return [NSColor colorWithDeviceRed:red green:green blue:blue alpha:1.0f]; +} + +NSString* nsstring(string s) { + return [ [ NSString alloc ] initWithBytesNoCopy:s.str length:s.len + encoding:NSUTF8StringEncoding freeWhenDone: false]; +} + + +gg__Size gg_get_screen_size() { + NSScreen *screen = [NSScreen mainScreen]; + NSDictionary *description = [screen deviceDescription]; + NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + CGSize displayPhysicalSize = CGDisplayScreenSize( + [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + gg__Size res; + res.width = displayPixelSize.width; + res.height = displayPixelSize.height; + return res; +} + +void darwin_draw_string(int x, int y, string s, gx__TextCfg cfg) { + NSFont* font = [NSFont userFontOfSize: 0]; //cfg.size]; + // # NSFont* font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size]; + if (cfg.mono) { + // # font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size]; + font = [NSFont fontWithName:@"Menlo" size:cfg.size-5]; + } +if (cfg.bold) { + font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask]; +} + + + NSDictionary* attr = @{ +NSForegroundColorAttributeName: nscolor(cfg.color), +//NSParagraphStyleAttributeName: paragraphStyle, +NSFontAttributeName: font, +}; + [nsstring(s) drawAtPoint:NSMakePoint(x,y-15) withAttributes:attr]; +} + +int darwin_text_width(string s) { + // println('text_width "$s" len=$s.len') + NSString* n = @""; + if (s.len == 1) { + // println('len=1') + n=[NSString stringWithFormat:@"%c" , s.str[0]]; + } + else { + n = nsstring(s); + } + /* + # if (!defaultFont){ + # defaultFont = [NSFont userFontOfSize: ui__DEFAULT_FONT_SIZE]; + # } + # NSDictionary *attrs = @{ + # NSFontAttributeName: defaultFont, + # }; + */ + NSSize size = [n sizeWithAttributes:nil]; + // # printf("!!!%f\n", ceil(size.width)); + return (int)(ceil(size.width)); +} + +void darwin_draw_rect(float x, float y, float width, float height, gx__Color c) { + NSColor* color = nscolor(c); + NSRect rect = NSMakeRect(x, y, width, height); + [color setFill]; + NSRectFill(rect); +} + + +void darwin_window_refresh() { + //[g_view setNeedsDisplay:YES]; + // update UI on the main thread TODO separate fn + + dispatch_async(dispatch_get_main_queue(), ^{ + [g_view setNeedsDisplay:YES]; + }); + + //puts("refresh"); + //[g_view drawRect:NSMakeRect(0,0,2000,2000)]; + //[[NSGraphicsContext currentContext] flushGraphics]; +} + +gg__Image darwin_create_image(string path_) { + // file = file.trim_space() + NSString* path = nsstring(path_); + NSImage* img = [[NSImage alloc] initWithContentsOfFile:path]; + if (img == 0) { + } + NSSize size = [img size]; + gg__Image res; + res.width = size.width; + res.height = size.height; + res.path = path_; + res.ok = true; + //printf("inited img width=%d\n", res.width) ; + // need __brige_retained so that the pointer is not freed by ARC + res.data = (__bridge_retained voidptr)(img); + return res; +} + +void darwin_draw_image(float x, float y, float w, float h, gg__Image* img) { + NSImage* i= (__bridge NSImage*)(img->data); + [i drawInRect:NSMakeRect(x,y,w,h)]; +} + +void darwin_draw_circle(float x, float y, float d, gx__Color color) { + NSColor* c = nscolor(color); + NSRect rect = NSMakeRect(x, y, d * 2, d * 2); + NSBezierPath* circlePath = [NSBezierPath bezierPath]; + [circlePath appendBezierPathWithOvalInRect: rect]; + [c setFill]; + // [circlePath stroke]; + [circlePath fill]; + // NSRectFill(rect); +} + diff --git a/v_windows/v/vlib/gg/image.c.v b/v_windows/v/vlib/gg/image.c.v new file mode 100644 index 0000000..352eac4 --- /dev/null +++ b/v_windows/v/vlib/gg/image.c.v @@ -0,0 +1,169 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import os +import stbi +import sokol.gfx + +fn C.sg_isvalid() bool + +// TODO return ?Image +pub fn (mut ctx Context) create_image(file string) Image { + // println('\ncreate_image("$file")') + if !os.exists(file) { + return Image{} + } + $if macos { + if ctx.native_rendering { + // return C.darwin_create_image(file) + mut img := C.darwin_create_image(file) + // println('created macos image: $img.path w=$img.width') + // C.printf('p = %p\n', img.data) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img + } + } + if !C.sg_isvalid() { + // Sokol is not initialized yet, add stbi object to a queue/cache + // ctx.image_queue << file + stb_img := stbi.load(file) or { return Image{} } + img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: false + data: stb_img.data + ext: stb_img.ext + path: file + id: ctx.image_cache.len + } + ctx.image_cache << img + return img + } + mut img := create_image(file) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img +} + +pub fn (mut img Image) init_sokol_image() &Image { + // println('\n init sokol image $img.path ok=$img.simg_ok') + mut img_desc := C.sg_image_desc{ + width: img.width + height: img.height + num_mipmaps: 0 + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: img.path.str + d3d11_texture: 0 + } + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: img.data + size: size_t(img.nr_channels * img.width * img.height) + } + img.simg = C.sg_make_image(&img_desc) + img.simg_ok = true + img.ok = true + return img +} + +// draw_image draws the provided image onto the screen +pub fn (ctx &Context) draw_image(x f32, y f32, width f32, height f32, img_ &Image) { + $if macos { + if img_.id >= ctx.image_cache.len { + eprintln('gg: draw_image() bad img id $img_.id (img cache len = $ctx.image_cache.len)') + return + } + if ctx.native_rendering { + if img_.width == 0 { + return + } + if !os.exists(img_.path) { + return + } + C.darwin_draw_image(x, ctx.height - (y + height), width, height, img_) + return + } + } + + ctx.draw_image_with_config( + img: img_ + img_rect: Rect{x, y, width, height} + part_rect: Rect{0, 0, img_.width, img_.height} + ) +} + +// new_streaming_image returns a cached `image_idx` of a special image, that +// can be updated *each frame* by calling: gg.update_pixel_data(image_idx, buf) +// ... where buf is a pointer to the actual pixel data for the image. +// NB: you still need to call app.gg.draw_image after that, to actually draw it. +pub fn (mut ctx Context) new_streaming_image(w int, h int, channels int, sicfg StreamingImageConfig) int { + mut img := Image{} + img.width = w + img.height = h + img.nr_channels = channels // 4 bytes per pixel for .rgba8, see pixel_format + mut img_desc := C.sg_image_desc{ + width: img.width + height: img.height + pixel_format: sicfg.pixel_format + num_slices: 1 + num_mipmaps: 1 + usage: .stream + wrap_u: sicfg.wrap_u + wrap_v: sicfg.wrap_v + min_filter: sicfg.min_filter + mag_filter: sicfg.mag_filter + label: img.path.str + } + // Sokol requires that streamed images have NO .ptr/.size initially: + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: 0 + size: size_t(0) + } + img.simg = C.sg_make_image(&img_desc) + img.simg_ok = true + img.ok = true + img_idx := ctx.cache_image(img) + return img_idx +} + +// update_pixel_data is a helper for working with image streams (i.e. images, +// that are updated dynamically by the CPU on each frame) +pub fn (mut ctx Context) update_pixel_data(cached_image_idx int, buf &byte) { + mut image := ctx.get_cached_image_by_idx(cached_image_idx) + image.update_pixel_data(buf) +} + +pub fn (mut img Image) update_pixel_data(buf &byte) { + mut data := C.sg_image_data{} + data.subimage[0][0].ptr = buf + data.subimage[0][0].size = size_t(img.width * img.height * img.nr_channels) + gfx.update_image(img.simg, &data) +} + +// TODO copypasta +pub fn (mut ctx Context) create_image_with_size(file string, width int, height int) Image { + if !C.sg_isvalid() { + // Sokol is not initialized yet, add stbi object to a queue/cache + // ctx.image_queue << file + stb_img := stbi.load(file) or { return Image{} } + img := Image{ + width: width + height: height + nr_channels: stb_img.nr_channels + ok: false + data: stb_img.data + ext: stb_img.ext + path: file + id: ctx.image_cache.len + } + ctx.image_cache << img + return img + } + mut img := create_image(file) + img.id = ctx.image_cache.len + ctx.image_cache << img + return img +} diff --git a/v_windows/v/vlib/gg/image.v b/v_windows/v/vlib/gg/image.v new file mode 100644 index 0000000..e4c2776 --- /dev/null +++ b/v_windows/v/vlib/gg/image.v @@ -0,0 +1,223 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +// import sokol.sapp +import gx +import sokol.gfx +import os +import sokol +import sokol.sgl +import stbi + +[heap] +pub struct Image { +pub mut: + id int + width int + height int + nr_channels int + ok bool + data voidptr + ext string + simg_ok bool + simg C.sg_image + path string +} + +// DrawImageConfig struct defines the various options +// that can be used to draw an image onto the screen +pub struct DrawImageConfig { +pub: + flip_x bool + flip_y bool + img &Image = voidptr(0) + img_id int + img_rect Rect // defines the size and position on image when rendering to the screen + part_rect Rect // defines the size and position of part of the image to use when rendering + rotate int // amount to rotate the image in degrees + z f32 + color gx.Color = gx.white +} + +pub struct Rect { +pub: + x f32 + y f32 + width f32 + height f32 +} + +// TODO remove this +fn create_image(file string) Image { + if !os.exists(file) { + println('gg.create_image(): file not found: $file') + return Image{} // none + } + stb_img := stbi.load(file) or { return Image{} } + mut img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: stb_img.ok + data: stb_img.data + ext: stb_img.ext + path: file + } + img.init_sokol_image() + return img +} + +pub fn (mut ctx Context) create_image_from_memory(buf &byte, bufsize int) Image { + stb_img := stbi.load_from_memory(buf, bufsize) or { return Image{} } + mut img := Image{ + width: stb_img.width + height: stb_img.height + nr_channels: stb_img.nr_channels + ok: stb_img.ok + data: stb_img.data + ext: stb_img.ext + id: ctx.image_cache.len + } + ctx.image_cache << img + return img +} + +pub fn (mut ctx Context) create_image_from_byte_array(b []byte) Image { + return ctx.create_image_from_memory(b.data, b.len) +} + +pub fn (mut ctx Context) cache_image(img Image) int { + ctx.image_cache << img + image_idx := ctx.image_cache.len - 1 + ctx.image_cache[image_idx].id = image_idx + return image_idx +} + +pub fn (mut ctx Context) get_cached_image_by_idx(image_idx int) &Image { + return &ctx.image_cache[image_idx] +} + +pub struct StreamingImageConfig { + pixel_format gfx.PixelFormat = .rgba8 + wrap_u gfx.Wrap = .clamp_to_edge + wrap_v gfx.Wrap = .clamp_to_edge + min_filter gfx.Filter = .linear + mag_filter gfx.Filter = .linear + num_mipmaps int = 1 + num_slices int = 1 +} + +// draw_image_with_config takes in a config that details how the +// provided image should be drawn onto the screen +pub fn (ctx &Context) draw_image_with_config(config DrawImageConfig) { + id := if !isnil(config.img) { config.img.id } else { config.img_id } + if id >= ctx.image_cache.len { + eprintln('gg: draw_image() bad img id $id (img cache len = $ctx.image_cache.len)') + return + } + + img := ctx.image_cache[id] + if !img.simg_ok { + return + } + + mut img_rect := config.img_rect + if img_rect.width == 0 && img_rect.height == 0 { + img_rect = Rect{img_rect.x, img_rect.y, img.width, img.height} + } + + mut part_rect := config.part_rect + if part_rect.width == 0 && part_rect.height == 0 { + part_rect = Rect{part_rect.x, part_rect.y, img.width, img.height} + } + + u0 := part_rect.x / img.width + v0 := part_rect.y / img.height + u1 := (part_rect.x + part_rect.width) / img.width + v1 := (part_rect.y + part_rect.height) / img.height + x0 := img_rect.x * ctx.scale + y0 := img_rect.y * ctx.scale + x1 := (img_rect.x + img_rect.width) * ctx.scale + mut y1 := (img_rect.y + img_rect.height) * ctx.scale + if img_rect.height == 0 { + scale := f32(img.width) / f32(img_rect.width) + y1 = f32(img_rect.y + int(f32(img.height) / scale)) * ctx.scale + } + + flip_x := config.flip_x + flip_y := config.flip_y + + mut u0f := if !flip_x { u0 } else { u1 } + mut u1f := if !flip_x { u1 } else { u0 } + mut v0f := if !flip_y { v0 } else { v1 } + mut v1f := if !flip_y { v1 } else { v0 } + + sgl.load_pipeline(ctx.timage_pip) + sgl.enable_texture() + sgl.texture(img.simg) + + if config.rotate != 0 { + width := img_rect.width * ctx.scale + height := (if img_rect.height > 0 { img_rect.height } else { img.height }) * ctx.scale + + sgl.push_matrix() + sgl.translate(x0 + (width / 2), y0 + (height / 2), 0) + sgl.rotate(sgl.rad(-config.rotate), 0, 0, 1) + sgl.translate(-x0 - (width / 2), -y0 - (height / 2), 0) + } + + sgl.begin_quads() + sgl.c4b(config.color.r, config.color.g, config.color.b, config.color.a) + sgl.v3f_t2f(x0, y0, config.z, u0f, v0f) + sgl.v3f_t2f(x1, y0, config.z, u1f, v0f) + sgl.v3f_t2f(x1, y1, config.z, u1f, v1f) + sgl.v3f_t2f(x0, y1, config.z, u0f, v1f) + sgl.end() + + if config.rotate != 0 { + sgl.pop_matrix() + } + + sgl.disable_texture() +} + +// Draw part of an image using uv coordinates +// img_rect is the size and position (in pixels on screen) of the displayed rectangle (ie the draw_image args) +// part_rect is the size and position (in absolute pixels in the image) of the wanted part +// eg. On a 600*600 context, to display only the first 400*400 pixels of a 2000*2000 image +// on the entire context surface, call : +// draw_image_part(Rect{0, 0, 600, 600}, Rect{0, 0, 400, 400}, img) +pub fn (ctx &Context) draw_image_part(img_rect Rect, part_rect Rect, img_ &Image) { + ctx.draw_image_with_config( + img: img_ + img_rect: img_rect + part_rect: part_rect + ) +} + +// draw_image_flipped draws the provided image flipped horizontally (use `draw_image_with_config` to flip vertically) +pub fn (ctx &Context) draw_image_flipped(x f32, y f32, width f32, height f32, img_ &Image) { + ctx.draw_image_with_config( + flip_x: true + img: img_ + img_rect: Rect{x, y, width, height} + ) +} + +// draw_image_by_id draws an image by its id +pub fn (ctx &Context) draw_image_by_id(x f32, y f32, width f32, height f32, id int) { + ctx.draw_image_with_config( + img_id: id + img_rect: Rect{x, y, width, height} + ) +} + +// draw_image_3d draws an image with a z depth +pub fn (ctx &Context) draw_image_3d(x f32, y f32, z f32, width f32, height f32, img_ &Image) { + ctx.draw_image_with_config( + img: img_ + img_rect: Rect{x, y, width, height} + z: z + ) +} diff --git a/v_windows/v/vlib/gg/m4/graphic.v b/v_windows/v/vlib/gg/m4/graphic.v new file mode 100644 index 0000000..e134e80 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/graphic.v @@ -0,0 +1,110 @@ +/********************************************************************** +* +* Simply vector/matrix graphic utility +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +module m4 + +import math + +// Translate degrees to radians +[inline] +pub fn rad(deg f32) f32 { + return (math.pi / 180.0) * deg +} + +// Translate radians to degrees +[inline] +pub fn deg(grad f32) f32 { + return (180.0 / math.pi) * grad +} + +// calculate the Orthographic projection matrix +pub fn ortho(left f32, right f32, bottom f32, top f32, z_near f32, z_far f32) Mat4 { + rml := right - left + rpl := right + left + tmb := top - bottom + tpb := top + bottom + fmn := z_far - z_near + fpn := z_far + z_near + if fmn != 0 { + return Mat4{ e: [ + 2 / rml, 0 , 0, -(rpl / rml), + 0 , 2 / tmb, 0, -(tpb / tmb), + 0 , 0, 2 / fmn, -(fpn / fmn), + 0 , 0, 0, 1, + ]! + } + } + return Mat4{ e: [ + 2 / rml, 0 , 0, -(rpl / rml), + 0 , 2 / tmb, 0, -(tpb / tmb), + 0 , 0, 0, 0, + 0 , 0, 0, 1, + ]! + } +} + +// Calculate the perspective matrix using (fov:fov, ar:aspect_ratio ,n:near_pane, f:far_plane) as parameters +pub fn perspective(fov f32, ar f32, n f32, f f32) Mat4 { + ctan := f32(1.0 / math.tan(fov * (f32(math.pi) / 360.0))) // for the FOV we use 360 instead 180 + return Mat4{ e: [ + ctan / ar, 0, 0, 0, + 0, ctan, 0, 0, + 0, 0, (n + f) / (n - f), -1.0, + 0, 0, (2.0 * n * f) / (n - f), 0, + ]! + } +} + +// Calculate the look-at matrix +pub fn look_at(eye Vec4, center Vec4, up Vec4) Mat4 { + f := (center - eye).normalize3() + s := (f % up).normalize3() + u := (s % f) + + return Mat4{ e: [ + /* [0][0] */ s.e[0], + /* [0][1] */ u.e[0], + /* [0][2] */ - f.e[0], + /* [0][3] */ 0, + + /* [1][1] */ s.e[1], + /* [1][1] */ u.e[1], + /* [1][2] */ - f.e[1], + /* [1][3] */ 0, + + /* [2][0] */ s.e[2], + /* [2][1] */ u.e[2], + /* [2][2] */ - f.e[2], + /* [2][3] */ 0, + + /* [3][0] */ - (s * eye), + /* [3][1] */ - (u * eye), + /* [3][2] */ f * eye, + /* [3][3] */ 1, + ]! + } +} + + +// Get the complete transformation matrix for GLSL demos +pub fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) Mat4 { + proj := perspective(60, w / h, 0.01, 10.0) + view := look_at(Vec4{ e: [f32(0.0), 1.5, 6, 0]! }, Vec4{ e: [f32(0), 0, 0, 0]! }, Vec4{ e: [f32(0), 1.0, 0, 0]! }) + view_proj := view * proj + + rxm := rotate(rad(rx), Vec4{ e: [f32(1), 0, 0, 0]! }) + rym := rotate(rad(ry), Vec4{ e: [f32(0), 1, 0, 0]! }) + + model := rym * rxm + scale_m := scale(Vec4{ e: [in_scale, in_scale, in_scale, 1]! }) + + res := (scale_m * model) * view_proj + return res +} diff --git a/v_windows/v/vlib/gg/m4/m4_test.v b/v_windows/v/vlib/gg/m4/m4_test.v new file mode 100644 index 0000000..90b4640 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/m4_test.v @@ -0,0 +1,235 @@ +import gg.m4 + +pub fn test_m4() { + unsafe { + // Test Mat4 + mut a := m4.Mat4{ e: [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + } + mut b := m4.Mat4{} + mut c := m4.Mat4{} + + // equal test + assert a.e == [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + + // copy test + b.copy(a) + assert a.e == b.e + + // test: transpose, scale + assert b.transpose().mul_scalar(2.0).mul_scalar(0.5).transpose().e == a.e + assert b.sum_all() == 120.0 + + // test rows/columns set/get + for i in 0 .. 4 { + b = m4.zero_m4() + b.set_row(i, m4.Vec4{ e: [f32(1.0), 2, 3, 4]! }) + assert b.get_f(0, i) == 1.0 + assert b.get_f(1, i) == 2.0 + assert b.get_f(2, i) == 3.0 + assert b.get_f(3, i) == 4.0 + // println(b) + c = m4.zero_m4() + c.set_col(i, m4.Vec4{ e: [f32(1.0), 2, 3, 4]! }) + assert c.get_f(i, 0) == 1.0 + assert c.get_f(i, 1) == 2.0 + assert c.get_f(i, 2) == 3.0 + assert c.get_f(i, 3) == 4.0 + // println(c) + } + } +} + +fn test_swap_col_row() { + unsafe { + // swap_col / swap_row + b := m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + b.swap_col(0, 2) + assert b.e == [ + f32(3), 2, 1, 4, + 7, 6, 5, 8, + 11, 10, 9, 12, + 15, 14, 13, 16, + ]! + b = m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + b.swap_row(0, 2) + assert b.e == [ + f32(9), 10, 11, 12, + 5, 6, 7, 8, + 1, 2, 3, 4, + 13, 14, 15, 16, + ]! + } +} + +fn test_sum_sub() { + unsafe { + // test sum/sub + b := m4.unit_m4() + c := m4.unit_m4() + assert m4.sub(m4.add(b, c), b).e == m4.unit_m4().e + assert (b + c - b).e == m4.unit_m4().e + } +} + +fn test_transpose() { + unsafe { + b := m4.Mat4{ e: [ + f32(0), 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + ]! + } + assert b.transpose().transpose().e == b.e + } +} + +fn test_multiplication() { + unsafe { + b := m4.Mat4{ e: [ + f32(1), 0, 0, 0, + 0, 2, 0, 0, + 0, 0, 3, 0, + 0, 0, 0, 4, + ]! + } + c := m4.Mat4{ e: [ + f32(1), 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + ]! + } + + assert (c * c).e == [ + f32(90),100,110,120, + 202,228,254,280, + 314,356,398,440, + 426,484,542,600, + ]! + + assert m4.mul(c, c).e == [ + f32(90),100,110,120, + 202,228,254,280, + 314,356,398,440, + 426,484,542,600, + ]! + + assert m4.mul(b, c).e == [ + f32(1), 2, 3, 4, + 10, 12, 14, 16, + 27, 30, 33, 36, + 52, 56, 60, 64, + ]! + + assert (b * c).e == [ + f32(1), 2, 3, 4, + 10, 12, 14, 16, + 27, 30, 33, 36, + 52, 56, 60, 64, + ]! + + assert m4.det(b) == 24 + } +} + +fn test_det() { + unsafe { + b := m4.Mat4{ e: [ + f32(5), 6, 6, 8, + 2, 2, 2, 8, + 6, 6, 2, 8, + 2, 3, 6, 7, + ]! + } + assert m4.det(b) == -8 + + c := m4.Mat4{ e: [ + f32(1), 8, 2, 3, + 8, 2, 3, 1, + 2, 3, 3, 2, + 3, 1, 2, 4, + ]! + } + // println("*** INVERSE ****") + // println(m4.mul(b.inverse(),b)) + // println(m4.clean_small(m4.mul(c.inverse(),c))) + // println("****************") + assert m4.mul(b.inverse(), b).e == m4.unit_m4().e + assert m4.mul(c.inverse(), c).is_equal(m4.unit_m4()) + } +} + +fn test_vec4() { + // Test Vec4 + // println("*** Vector4 ****") + assert m4.vec3(1,2,3) == m4.Vec4{[f32(1), 2, 3, 1]!} + mut v := m4.Vec4{[f32(1), 2, 3, 4]!} + assert v * v.inv() == 4 + assert v.mul_scalar(1.0 / v.mod()).mod() == 1 + assert v + m4.Vec4{ e: [f32(5), 6, 7, 8]! } == m4.Vec4{ e: [f32(6), 8, 10, 12]! } + assert v - m4.Vec4{ e: [f32(1), 2, 3, 4]! } == m4.Vec4{ e: [f32(0), 0, 0, 0]! } + assert v.mul_vec4(m4.Vec4{ e: [f32(2), 2, 2, 2]! }) == m4.Vec4{ e: [f32(2), 4, 6, 8]! } + assert f32_abs(v.normalize().mod() - 1) < m4.precision + v = m4.Vec4{[f32(1), 2, 3, 0]!} + assert f32_abs(v.normalize3().mod3() - 1) < m4.precision + assert f32_abs(v.normalize3().mod() - 1) < m4.precision + // cross product + // x y z + // 1 2 3 ==> -3 6 -3 0 + // 4 5 6 + // println(m4.Vec4{[f32(1),2,3,2]!} % m4.Vec4{[f32(4),5,6,2]!}) + assert m4.Vec4{[f32(1), 2, 3, 0]!} % m4.Vec4{[f32(4), 5, 6, 0]!} == m4.Vec4{[ f32(-3), 6, -3, 0, ]!} + assert m4.Vec4{[f32(1), 2, 3, 13]!} % m4.Vec4{[f32(4), 5, 6, 11]!} == m4.Vec4{[ f32(-3), 6, -3, 0, ]!} + // matrix * vector + a := m4.Mat4{ e: [ + f32(1),2,3,4 + 5,6,7,8 + 9,10,11,12 + 13,14,15,16 + ]! + } + assert m4.mul_vec(a, m4.Vec4{[f32(1), 2, 3, 4]!}) == m4.Vec4{[ f32(30), 70, 110,150, ]!} + // Rotation + // println("*** Rotation ****") + rotx := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(1.0), 0, 0, 0]! }).clean() + roty := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(0), 1.0, 0, 0]! }).clean() + rotz := m4.rotate(m4.rad(-90), m4.Vec4{ e: [f32(0), 0, 1, 0]! }).clean() + // println( rotx ) + // println( roty ) + // println( rotz ) + // println( m4.mul_vec(rotx, m4.Vec4{e:[f32(0),0,1,0]!}).clean()) + assert m4.mul_vec(roty, m4.Vec4{ e: [f32(1.0), 0.0, 0, 0]! }).clean() == m4.Vec4{ e: [f32(0), 0.0, -1, 0]! } + assert m4.mul_vec(rotz, m4.Vec4{ e: [f32(1.0), 0.0, 0, 0]! }).clean() == m4.Vec4{ e: [f32(0), 1, 0, 0]! } + assert m4.mul_vec(rotx, m4.Vec4{ e: [f32(0), 0, 1, 0]! }).clean() == m4.Vec4{ e: [f32(0), -1, 0, 0]! } + // println("****************") +} + +fn test_proj() { + ort := m4.ortho(0,300,0,200,0,0) + assert m4.mul_vec(ort, m4.Vec4{[ f32(150), 100, 0, 1]!}) == m4.Vec4{[ f32(0), 0, 0, 1]!} + assert m4.mul_vec(ort, m4.Vec4{[ f32(0), 0, 0, 1]!}) == m4.Vec4{[ f32(-1), -1, 0, 1]!} + assert m4.mul_vec(ort, m4.Vec4{[ f32(300), 200, 0, 1]!}) == m4.Vec4{[ f32(1), 1, 0, 1]!} +} diff --git a/v_windows/v/vlib/gg/m4/matrix.v b/v_windows/v/vlib/gg/m4/matrix.v new file mode 100644 index 0000000..c839b9d --- /dev/null +++ b/v_windows/v/vlib/gg/m4/matrix.v @@ -0,0 +1,595 @@ +/********************************************************************** +* +* Simply vector/matrix utility +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +module m4 + +import math + +pub union Mat4 { +pub mut: + e [16]f32 + f [4][4]f32 +} + +pub const precision = f32(10e-7) + +// default precision for the module + +/********************************************************************* +* +* Utility +* +*********************************************************************/ +// String representation of the matrix +pub fn (x Mat4) str() string { + unsafe { + return '|${x.e[0]:-6.3},${x.e[1]:-6.3},${x.e[2]:-6.3},${x.e[3]:-6.3}|\n' + + '|${x.e[4]:-6.3},${x.e[5]:-6.3},${x.e[6]:-6.3},${x.e[7]:-6.3}|\n' + + '|${x.e[8]:-6.3},${x.e[9]:-6.3},${x.e[10]:-6.3},${x.e[11]:-6.3}|\n' + + '|${x.e[12]:-6.3},${x.e[13]:-6.3},${x.e[14]:-6.3},${x.e[15]:-6.3}|' + } +} + +// Remove all the raw zeros +[direct_array_access] +pub fn (a Mat4) clean() Mat4 { + unsafe { + x := Mat4{} + for c, value in a.e { + if f32_abs(value) < m4.precision { + x.e[c] = 0 + } else { + x.e[c] = value + } + } + return x + } +} + +// Sum all the elements of the matrix +pub fn (x Mat4) sum_all() f32 { + mut res := f32(0) + for v in unsafe { x.e } { + res += v + } + return res +} + +// Check if two matrix are equal using module precision +[direct_array_access] +pub fn (x Mat4) is_equal(y Mat4) bool { + unsafe { + for c, value in x.e { + if f32_abs(value - y.e[c]) > m4.precision { + return false + } + } + return true + } +} + +//------------------------------------- +// Set/Get values +//------------------------------------- +// Get an element of the matrix using [0..15] indexes, one dimension +pub fn (x Mat4) get_e(elem_index int) f32 { + unsafe { + return x.e[elem_index] + } +} + +// Get an element of the matrix using [0..3][0..3] indexes, two dimension +pub fn (x Mat4) get_f(index_col int, index_row int) f32 { + unsafe { + return x.e[(index_row << 2) + index_col] + } +} + +// Set an element of the matrix using [0..15] indexes, one dimension +pub fn (mut x Mat4) set_e(index int, value f32) { + unsafe { + x.e[index] = value + } +} + +// Set an element of the matrix using [0..3][0..3] indexes, two dimension +pub fn (mut x Mat4) set_f(index_col int, index_row int, value f32) { + unsafe { + x.e[(index_row << 2) + index_col] = value + } +} + +// Copy a matrix elements from another matrix +pub fn (mut x Mat4) copy(y Mat4) { + unsafe { + x.e = [ + y.e[0 ], y.e[1 ], y.e[2 ], y.e[3 ], + y.e[4 ], y.e[5 ], y.e[6 ], y.e[7 ], + y.e[8 ], y.e[9 ], y.e[10], y.e[11], + y.e[12], y.e[13], y.e[14], y.e[15], + ]! + } +} + +// Set the trace of the matrix using a vec4 +pub fn (mut x Mat4) set_trace(v3 Vec4) { + unsafe { + x.e[0 ] = v3.e[0] + x.e[5 ] = v3.e[1] + x.e[10] = v3.e[2] + x.e[15] = v3.e[3] + } +} + +// Get the trace of the matrix +pub fn (x Mat4) get_trace() Vec4 { + unsafe { + return Vec4{ e: [ x.e[0], x.e[5], x.e[10], x.e[15], ]! } + } +} + +// Set all the matrix elements to value +pub fn (mut x Mat4) set_f32(value f32) { + unsafe { + x.e = [ + value, value, value, value, + value, value, value, value, + value, value, value, value, + value, value, value, value, + ]! + } +} + +//------------------------------------- +// Rows/Column access +//------------------------------------- +// Set the row as the input vec4 +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) set_row(row int, v3 Vec4) { + unsafe { + x.e[row * 4 + 0] = v3.e[0] + x.e[row * 4 + 1] = v3.e[1] + x.e[row * 4 + 2] = v3.e[2] + x.e[row * 4 + 3] = v3.e[3] + } +} + +// Get a row from a matrix +[direct_array_access] +[unsafe] +pub fn (x Mat4) get_row(row int) Vec4 { + unsafe { + return Vec4{ + e: [ + x.e[row * 4 + 0], + x.e[row * 4 + 1], + x.e[row * 4 + 2], + x.e[row * 4 + 3], + ]! + } + } +} + +// Set the column as the input vec4 +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) set_col(col int, v3 Vec4) { + unsafe { + x.e[col] = v3.e[0] + x.e[col + 4 ] = v3.e[1] + x.e[col + 8 ] = v3.e[2] + x.e[col + 12] = v3.e[3] + } +} + +// Get a column from a matrix +[direct_array_access] +[unsafe] +pub fn (x Mat4) get_col(col int) Vec4 { + unsafe { + return Vec4{ + e: [ + x.e[col], + x.e[col + 4 ], + x.e[col + 8 ], + x.e[col + 12], + ]! + } + } +} + +// Swap two columns in the matrix +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) swap_col(col1 int, col2 int) { + unsafe { + v0 := x.e[col1] + v1 := x.e[col1 + 4 ] + v2 := x.e[col1 + 8 ] + v3 := x.e[col1 + 12] + + x.e[col1] = x.e[col2] + x.e[col1 + 4 ] = x.e[col2 + 4 ] + x.e[col1 + 8 ] = x.e[col2 + 8 ] + x.e[col1 + 12] = x.e[col2 + 12] + + x.e[col2] = v0 + x.e[col2 + 4 ] = v1 + x.e[col2 + 8 ] = v2 + x.e[col2 + 12] = v3 + } +} + +// Swap two rows in the matrix +[direct_array_access] +[unsafe] +pub fn (mut x Mat4) swap_row(row1 int, row2 int) { + unsafe { + v0 := x.e[row1 * 4 + 0] + v1 := x.e[row1 * 4 + 1] + v2 := x.e[row1 * 4 + 2] + v3 := x.e[row1 * 4 + 3] + + x.e[row1 * 4 + 0] = x.e[row2 * 4 + 0] + x.e[row1 * 4 + 1] = x.e[row2 * 4 + 1] + x.e[row1 * 4 + 2] = x.e[row2 * 4 + 2] + x.e[row1 * 4 + 3] = x.e[row2 * 4 + 3] + + x.e[row2 * 4 + 0] = v0 + x.e[row2 * 4 + 1] = v1 + x.e[row2 * 4 + 2] = v2 + x.e[row2 * 4 + 3] = v3 + } +} + +//------------------------------------- +// Modify data +//------------------------------------- +// Transpose the matrix +pub fn (x Mat4) transpose() Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0 ], x.e[4 ], x.e[8 ], x.e[12], + x.e[1 ], x.e[5 ], x.e[9 ], x.e[13], + x.e[2 ], x.e[6 ], x.e[10], x.e[14], + x.e[3 ], x.e[7 ], x.e[11], x.e[15], + ]! + } + } +} + +// Multiply the all the elements of the matrix by a scalar +pub fn (x Mat4) mul_scalar(s f32) Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0 ] * s, x.e[1 ] * s, x.e[2 ] * s, x.e[3 ] * s, + x.e[4 ] * s, x.e[5 ] * s, x.e[6 ] * s, x.e[7 ] * s, + x.e[8 ] * s, x.e[9 ] * s, x.e[10] * s, x.e[11] * s, + x.e[12] * s, x.e[13] * s, x.e[14] * s, x.e[15] * s, + ]! + } + } +} + +/********************************************************************* +* +* Init/set +* +*********************************************************************/ +// Return a zero matrix +pub fn zero_m4() Mat4 { + return Mat4{ e: [ + f32(0), 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ]! + } +} + +// Return a unity matrix +pub fn unit_m4() Mat4 { + return Mat4{ e: [ + f32(1), 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ]! + } +} + +// Return a matrix initialized with value +pub fn set_m4(value f32) Mat4 { + return Mat4{ e: [ + value, value, value, value, + value, value, value, value, + value, value, value, value, + value, value, value, value, + ]! + } +} + +/********************************************************************* +* +* Math +* +*********************************************************************/ + +// Sum of matrix, operator + +pub fn (a Mat4) + (b Mat4) Mat4 { + unsafe { + return Mat4{ e: [ + a.e[0 ] + b.e[0 ], a.e[1 ] + b.e[1 ], a.e[2 ] + b.e[2 ], a.e[3 ] + b.e[3 ], + a.e[4 ] + b.e[4 ], a.e[5 ] + b.e[5 ], a.e[6 ] + b.e[6 ], a.e[7 ] + b.e[7 ], + a.e[8 ] + b.e[8 ], a.e[9 ] + b.e[9 ], a.e[10] + b.e[10], a.e[11] + b.e[11], + a.e[12] + b.e[12], a.e[13] + b.e[13], a.e[14] + b.e[14], a.e[15] + b.e[15], + ]! + } + } +} + +// Subtraction of matrix, operator - +pub fn (a Mat4) - (b Mat4) Mat4 { + unsafe { + return Mat4{ e: [ + a.e[0 ] - b.e[0 ], a.e[1 ] - b.e[1 ], a.e[2 ] - b.e[2 ], a.e[3 ] - b.e[3 ], + a.e[4 ] - b.e[4 ], a.e[5 ] - b.e[5 ], a.e[6 ] - b.e[6 ], a.e[7 ] - b.e[7 ], + a.e[8 ] - b.e[8 ], a.e[9 ] - b.e[9 ], a.e[10] - b.e[10], a.e[11] - b.e[11], + a.e[12] - b.e[12], a.e[13] - b.e[13], a.e[14] - b.e[14], a.e[15] - b.e[15], + ]! + } + } +} + +// Multiplication of matrix, operator * +pub fn (a Mat4) * (b Mat4) Mat4 { + unsafe { + return Mat4{ + e: [ + /* [0][0] */ a.f[0][0] * b.f[0][0] + a.f[0][1] * b.f[1][0] + a.f[0][2] * b.f[2][0] + a.f[0][3] * b.f[3][0] + /* [0][1] */, a.f[0][0] * b.f[0][1] + a.f[0][1] * b.f[1][1] + a.f[0][2] * b.f[2][1] + a.f[0][3] * b.f[3][1] + /* [0][2] */, a.f[0][0] * b.f[0][2] + a.f[0][1] * b.f[1][2] + a.f[0][2] * b.f[2][2] + a.f[0][3] * b.f[3][2] + /* [0][3] */, a.f[0][0] * b.f[0][3] + a.f[0][1] * b.f[1][3] + a.f[0][2] * b.f[2][3] + a.f[0][3] * b.f[3][3] + + /* [1][0] */, a.f[1][0] * b.f[0][0] + a.f[1][1] * b.f[1][0] + a.f[1][2] * b.f[2][0] + a.f[1][3] * b.f[3][0] + /* [1][1] */, a.f[1][0] * b.f[0][1] + a.f[1][1] * b.f[1][1] + a.f[1][2] * b.f[2][1] + a.f[1][3] * b.f[3][1] + /* [1][2] */, a.f[1][0] * b.f[0][2] + a.f[1][1] * b.f[1][2] + a.f[1][2] * b.f[2][2] + a.f[1][3] * b.f[3][2] + /* [1][3] */, a.f[1][0] * b.f[0][3] + a.f[1][1] * b.f[1][3] + a.f[1][2] * b.f[2][3] + a.f[1][3] * b.f[3][3] + + /* [2][0] */, a.f[2][0] * b.f[0][0] + a.f[2][1] * b.f[1][0] + a.f[2][2] * b.f[2][0] + a.f[2][3] * b.f[3][0] + /* [2][1] */, a.f[2][0] * b.f[0][1] + a.f[2][1] * b.f[1][1] + a.f[2][2] * b.f[2][1] + a.f[2][3] * b.f[3][1] + /* [2][2] */, a.f[2][0] * b.f[0][2] + a.f[2][1] * b.f[1][2] + a.f[2][2] * b.f[2][2] + a.f[2][3] * b.f[3][2] + /* [2][3] */, a.f[2][0] * b.f[0][3] + a.f[2][1] * b.f[1][3] + a.f[2][2] * b.f[2][3] + a.f[2][3] * b.f[3][3] + + /* [3][0] */, a.f[3][0] * b.f[0][0] + a.f[3][1] * b.f[1][0] + a.f[3][2] * b.f[2][0] + a.f[3][3] * b.f[3][0] + /* [3][1] */, a.f[3][0] * b.f[0][1] + a.f[3][1] * b.f[1][1] + a.f[3][2] * b.f[2][1] + a.f[3][3] * b.f[3][1] + /* [3][2] */, a.f[3][0] * b.f[0][2] + a.f[3][1] * b.f[1][2] + a.f[3][2] * b.f[2][2] + a.f[3][3] * b.f[3][2] + /* [3][3] */, a.f[3][0] * b.f[0][3] + a.f[3][1] * b.f[1][3] + a.f[3][2] * b.f[2][3] + a.f[3][3] * b.f[3][3], + ]! + } + } +} + +// Sum of matrix function +pub fn add(a Mat4, b Mat4) Mat4 { + unsafe { + return a + b + } +} + +// Subtraction of matrix function +pub fn sub(a Mat4, b Mat4) Mat4 { + unsafe { + return a - b + } +} + +// Multiplication of matrix function +pub fn mul(a Mat4, b Mat4) Mat4 { + unsafe { + return a * b + } +} + +// Multiply a Matrix by a vector +pub fn mul_vec(a Mat4, v Vec4) Vec4 { + unsafe { + return Vec4{ e: [ + a.e[0 ] * v.e[0] + a.e[1 ] * v.e[1] + a.e[2 ] * v.e[2] + a.e[3 ] * v.e[3], + a.e[4 ] * v.e[0] + a.e[5 ] * v.e[1] + a.e[6 ] * v.e[2] + a.e[7 ] * v.e[3], + a.e[8 ] * v.e[0] + a.e[9 ] * v.e[1] + a.e[10] * v.e[2] + a.e[11] * v.e[3], + a.e[12] * v.e[0] + a.e[13] * v.e[1] + a.e[14] * v.e[2] + a.e[15] * v.e[3], + ]! + } + } +} + +// Calculate the determinant of the Matrix +pub fn det(x Mat4) f32 { + unsafe { + mut t := [6]f32{} + x00 := x.f[0][0] + x10 := x.f[1][0] + x20 := x.f[2][0] + x30 := x.f[3][0] + x01 := x.f[0][1] + x11 := x.f[1][1] + x21 := x.f[2][1] + x31 := x.f[3][1] + x02 := x.f[0][2] + x12 := x.f[1][2] + x22 := x.f[2][2] + x32 := x.f[3][2] + x03 := x.f[0][3] + x13 := x.f[1][3] + x23 := x.f[2][3] + x33 := x.f[3][3] + + t[0] = x22 * x33 - x23 * x32 + t[1] = x12 * x33 - x13 * x32 + t[2] = x12 * x23 - x13 * x22 + t[3] = x02 * x33 - x03 * x32 + t[4] = x02 * x23 - x03 * x22 + t[5] = x02 * x13 - x03 * x12 + + return 0.0 + + x00 * (x11 * t[0] - x21 * t[1] + x31 * t[2]) - + x10 * (x01 * t[0] - x21 * t[3] + x31 * t[4]) + + x20 * (x01 * t[1] - x11 * t[3] + x31 * t[5]) - + x30 * (x01 * t[2] - x11 * t[4] + x21 * t[5]) + } +} + +// Calculate the inverse of the Matrix +pub fn (x Mat4) inverse() Mat4 { + unsafe { + mut t := [6]f32{} + mut det := f32(0) + + a := x.f[0][0] + b := x.f[1][0] + c := x.f[2][0] + d := x.f[3][0] + e := x.f[0][1] + f := x.f[1][1] + g := x.f[2][1] + h := x.f[3][1] + i := x.f[0][2] + j := x.f[1][2] + k := x.f[2][2] + l := x.f[3][2] + m := x.f[0][3] + n := x.f[1][3] + o := x.f[2][3] + p := x.f[3][3] + + t[0] = k * p - o * l + t[1] = j * p - n * l + t[2] = j * o - n * k + t[3] = i * p - m * l + t[4] = i * o - m * k + t[5] = i * n - m * j + + mut dest := Mat4{} + dest.f[0][0] = f * t[0] - g * t[1] + h * t[2] + dest.f[0][1] = -(e * t[0] - g * t[3] + h * t[4]) + dest.f[0][2] = e * t[1] - f * t[3] + h * t[5] + dest.f[0][3] = -(e * t[2] - f * t[4] + g * t[5]) + + dest.f[1][0] = -(b * t[0] - c * t[1] + d * t[2]) + dest.f[1][1] = a * t[0] - c * t[3] + d * t[4] + dest.f[1][2] = -(a * t[1] - b * t[3] + d * t[5]) + dest.f[1][3] = a * t[2] - b * t[4] + c * t[5] + + t[0] = g * p - o * h + t[1] = f * p - n * h + t[2] = f * o - n * g + t[3] = e * p - m * h + t[4] = e * o - m * g + t[5] = e * n - m * f + + dest.f[2][0] = b * t[0] - c * t[1] + d * t[2] + dest.f[2][1] = -(a * t[0] - c * t[3] + d * t[4]) + dest.f[2][2] = a * t[1] - b * t[3] + d * t[5] + dest.f[2][3] = -(a * t[2] - b * t[4] + c * t[5]) + + t[0] = g * l - k * h + t[1] = f * l - j * h + t[2] = f * k - j * g + t[3] = e * l - i * h + t[4] = e * k - i * g + t[5] = e * j - i * f + + dest.f[3][0] = -(b * t[0] - c * t[1] + d * t[2]) + dest.f[3][1] = a * t[0] - c * t[3] + d * t[4] + dest.f[3][2] = -(a * t[1] - b * t[3] + d * t[5]) + dest.f[3][3] = a * t[2] - b * t[4] + c * t[5] + + tmp := (a * dest.f[0][0] + b * dest.f[0][1] + c * dest.f[0][2] + d * dest.f[0][3]) + if tmp != 0 { + det = f32(1.0) / tmp + } + return dest.mul_scalar(det) + } +} + +/********************************************************************* +* +* Transformations +* +*********************************************************************/ + +// Get a rotation matrix using w as rotation axis vector, the angle is in radians +pub fn rotate(angle f32, w Vec4) Mat4 { + cs := f32(math.cos(angle)) + sn := f32(math.sin(angle)) + cv := f32(1.0) - cs + axis := w.normalize3() + unsafe { + ax := axis.e[0] + ay := axis.e[1] + az := axis.e[2] + + return Mat4{ e: [ + /* [0][0] */ (ax * ax * cv) + cs + /* [0][1] */, (ax * ay * cv) + az * sn + /* [0][2] */, (ax * az * cv) - ay * sn + /* [0][3] */, 0 + + /* [1][0] */, (ay * ax * cv) - az * sn + /* [1][1] */, (ay * ay * cv) + cs + /* [1][2] */, (ay * az * cv) + ax * sn + /* [1][3] */, 0 + + /* [2][0] */, (az * ax * cv) + ay * sn + /* [2][1] */, (az * ay * cv) - ax * sn + /* [2][2] */, (az * az * cv) + cs + /* [2][3] */, 0 + + /* [3][0] */, 0 + /* [3][1] */, 0 + /* [3][2] */, 0 + /* [3][3] */, 1, + ]! + } + } +} + +/********************************************************************* +* +* Graphic +* +*********************************************************************/ +// Get a matrix translated by a vector w +pub fn (x Mat4) translate(w Vec4) Mat4 { + unsafe { + return Mat4{ e: [ + x.e[0], x.e[1], x.e[2 ], x.e[3 ] , + x.e[4], x.e[5], x.e[6 ], x.e[7 ] , + x.e[8], x.e[9], x.e[10], x.e[11] , + x.e[12] + w.e[0], x.e[13] + w.e[1], x.e[14] + w.e[2], x.e[15], + ]! + } + } +} + +// Get a scale matrix, the scale vector is w, only xyz are evaluated. +pub fn scale(w Vec4) Mat4 { + unsafe { + return Mat4{ e: [ + w.e[0], 0, 0, 0, + 0, w.e[1], 0, 0, + 0, 0, w.e[2], 0, + 0, 0, 0, 1, + ]! + } + } +} diff --git a/v_windows/v/vlib/gg/m4/vector.v b/v_windows/v/vlib/gg/m4/vector.v new file mode 100644 index 0000000..52e4c78 --- /dev/null +++ b/v_windows/v/vlib/gg/m4/vector.v @@ -0,0 +1,230 @@ +/********************************************************************** +* +* Simply vector/matrix utility +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +module m4 + +import math + +pub struct Vec4 { +pub mut: + e [4]f32 +} + +/********************************************************************* +* +* Utility +* +*********************************************************************/ +pub fn (x Vec4) str() string { + return '|${x.e[0]:-6.3},${x.e[1]:-6.3},${x.e[2]:-6.3},${x.e[3]:-6.3}|' +} + +// create a Vec4 function passing x,y,z as parameteres. w is set to 1 +pub fn vec3(x f32, y f32, z f32) Vec4 { + return Vec4{ + e: [x, y, z, 1]! + } +} + +// Remove all the raw zeros +[direct_array_access] +pub fn (a Vec4) clean() Vec4 { + mut x := Vec4{} + for c, value in a.e { + if f32_abs(value) < precision { + x.e[c] = 0 + } else { + x.e[c] = value + } + } + return x +} + +// Set all elements to value +pub fn (mut x Vec4) copy(value f32) { + x.e = [value, value, value, value]! +} + +// Scale the vector using a scalar +pub fn (x Vec4) mul_scalar(value f32) Vec4 { + return Vec4{ + e: [x.e[0] * value, x.e[1] * value, x.e[2] * value, x.e[3] * value]! + } +} + +// Reciprocal of the vector +pub fn (x Vec4) inv() Vec4 { + return Vec4{ + e: [ + if x.e[0] != 0 { 1.0 / x.e[0] } else { f32(0) }, + if x.e[1] != 0 { 1.0 / x.e[1] } else { f32(0) }, + if x.e[2] != 0 { 1.0 / x.e[2] } else { f32(0) }, + if x.e[3] != 0 { 1.0 / x.e[3] } else { f32(0) }, + ]! + } +} + +// Normalize the vector +pub fn (x Vec4) normalize() Vec4 { + m := x.mod() + if m == 0 { + return zero_v4() + } + return Vec4{ + e: [ + x.e[0] * (1 / m), + x.e[1] * (1 / m), + x.e[2] * (1 / m), + x.e[3] * (1 / m), + ]! + } +} + +// Normalize only xyz, w set to 0 +pub fn (x Vec4) normalize3() Vec4 { + m := x.mod3() + if m == 0 { + return zero_v4() + } + return Vec4{ + e: [ + x.e[0] * (1 / m), + x.e[1] * (1 / m), + x.e[2] * (1 / m), + 0, + ]! + } +} + +// Module of the vector xyzw +pub fn (x Vec4) mod() f32 { + return f32(math.sqrt(x.e[0] * x.e[0] + x.e[1] * x.e[1] + x.e[2] * x.e[2] + x.e[3] * x.e[3])) +} + +// Module for 3d vector xyz, w ignored +pub fn (x Vec4) mod3() f32 { + return f32(math.sqrt(x.e[0] * x.e[0] + x.e[1] * x.e[1] + x.e[2] * x.e[2])) +} + +/********************************************************************* +* +* Math +* +*********************************************************************/ +// Return a zero vector +pub fn zero_v4() Vec4 { + return Vec4{ + e: [ + f32(0), + 0, + 0, + 0, + ]! + } +} + +// Return all one vector +pub fn one_v4() Vec4 { + return Vec4{ + e: [ + f32(1), + 1, + 1, + 1, + ]! + } +} + +// Return a blank vector +pub fn blank_v4() Vec4 { + return Vec4{ + e: [ + f32(0), + 0, + 0, + 1, + ]! + } +} + +// Set all elements to value +pub fn set_v4(value f32) Vec4 { + return Vec4{ + e: [ + value, + value, + value, + value, + ]! + } +} + +// Sum of all the elements +pub fn (x Vec4) sum() f32 { + return x.e[0] + x.e[1] + x.e[2] + x.e[3] +} + +/********************************************************************* +* +* Operators +* +*********************************************************************/ +// Addition +pub fn (a Vec4) + (b Vec4) Vec4 { + return Vec4{ + e: [ + a.e[0] + b.e[0], + a.e[1] + b.e[1], + a.e[2] + b.e[2], + a.e[3] + b.e[3], + ]! + } +} + +// Subtraction +pub fn (a Vec4) - (b Vec4) Vec4 { + return Vec4{ + e: [ + a.e[0] - b.e[0], + a.e[1] - b.e[1], + a.e[2] - b.e[2], + a.e[3] - b.e[3], + ]! + } +} + +// Dot product +pub fn (a Vec4) * (b Vec4) f32 { + return a.e[0] * b.e[0] + a.e[1] * b.e[1] + a.e[2] * b.e[2] + a.e[3] * b.e[3] +} + +// Cross product +pub fn (a Vec4) % (b Vec4) Vec4 { + return Vec4{ + e: [ + (a.e[1] * b.e[2]) - (a.e[2] * b.e[1]), + (a.e[2] * b.e[0]) - (a.e[0] * b.e[2]), + (a.e[0] * b.e[1]) - (a.e[1] * b.e[0]), + 0, + ]! + } +} + +// Components multiplication +pub fn (x Vec4) mul_vec4(y Vec4) Vec4 { + return Vec4{ + e: [ + x.e[0] * y.e[0], + x.e[1] * y.e[1], + x.e[2] * y.e[2], + x.e[3] * y.e[3], + ]! + } +} diff --git a/v_windows/v/vlib/gg/text_rendering.c.v b/v_windows/v/vlib/gg/text_rendering.c.v new file mode 100644 index 0000000..9530dd2 --- /dev/null +++ b/v_windows/v/vlib/gg/text_rendering.c.v @@ -0,0 +1,226 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import sokol.sfons +import sokol.sgl +import gx +import os + +struct FT { +pub: + fons &C.FONScontext + font_normal int + font_bold int + font_mono int + font_italic int + scale f32 = 1.0 +} + +fn new_ft(c FTConfig) ?&FT { + if c.font_path == '' { + if c.bytes_normal.len > 0 { + fons := sfons.create(512, 512, 1) + bytes_normal := c.bytes_normal + bytes_bold := if c.bytes_bold.len > 0 { + c.bytes_bold + } else { + debug_font_println('setting bold variant to normal') + bytes_normal + } + bytes_mono := if c.bytes_mono.len > 0 { + c.bytes_mono + } else { + debug_font_println('setting mono variant to normal') + bytes_normal + } + bytes_italic := if c.bytes_italic.len > 0 { + c.bytes_italic + } else { + debug_font_println('setting italic variant to normal') + bytes_normal + } + + return &FT{ + fons: fons + font_normal: C.fonsAddFontMem(fons, c'sans', bytes_normal.data, bytes_normal.len, + false) + font_bold: C.fonsAddFontMem(fons, c'sans', bytes_bold.data, bytes_bold.len, + false) + font_mono: C.fonsAddFontMem(fons, c'sans', bytes_mono.data, bytes_mono.len, + false) + font_italic: C.fonsAddFontMem(fons, c'sans', bytes_italic.data, bytes_italic.len, + false) + scale: c.scale + } + } else { + // Load default font + } + } + + if c.font_path == '' || !os.exists(c.font_path) { + $if !android { + println('failed to load font "$c.font_path"') + return none + } + } + + mut bytes := []byte{} + $if android { + // First try any filesystem paths + bytes = os.read_bytes(c.font_path) or { []byte{} } + if bytes.len == 0 { + // ... then try the APK asset path + bytes = os.read_apk_asset(c.font_path) or { + println('failed to load font "$c.font_path"') + return none + } + } + } $else { + bytes = os.read_bytes(c.font_path) or { + println('failed to load font "$c.font_path"') + return none + } + } + bold_path := if c.custom_bold_font_path != '' { + c.custom_bold_font_path + } else { + get_font_path_variant(c.font_path, .bold) + } + bytes_bold := os.read_bytes(bold_path) or { + debug_font_println('failed to load font "$bold_path"') + bytes + } + mono_path := get_font_path_variant(c.font_path, .mono) + bytes_mono := os.read_bytes(mono_path) or { + debug_font_println('failed to load font "$mono_path"') + bytes + } + italic_path := get_font_path_variant(c.font_path, .italic) + bytes_italic := os.read_bytes(italic_path) or { + debug_font_println('failed to load font "$italic_path"') + bytes + } + fons := sfons.create(512, 512, 1) + return &FT{ + fons: fons + font_normal: C.fonsAddFontMem(fons, c'sans', bytes.data, bytes.len, false) + font_bold: C.fonsAddFontMem(fons, c'sans', bytes_bold.data, bytes_bold.len, false) + font_mono: C.fonsAddFontMem(fons, c'sans', bytes_mono.data, bytes_mono.len, false) + font_italic: C.fonsAddFontMem(fons, c'sans', bytes_italic.data, bytes_italic.len, + false) + scale: c.scale + } +} + +pub fn (ctx &Context) set_cfg(cfg gx.TextCfg) { + if !ctx.font_inited { + return + } + if cfg.bold { + ctx.ft.fons.set_font(ctx.ft.font_bold) + } else if cfg.mono { + ctx.ft.fons.set_font(ctx.ft.font_mono) + } else if cfg.italic { + ctx.ft.fons.set_font(ctx.ft.font_italic) + } else { + ctx.ft.fons.set_font(ctx.ft.font_normal) + } + scale := if ctx.ft.scale == 0 { f32(1) } else { ctx.ft.scale } + size := if cfg.mono { cfg.size - 2 } else { cfg.size } + ctx.ft.fons.set_size(scale * f32(size)) + C.fonsSetAlign(ctx.ft.fons, int(cfg.align) | int(cfg.vertical_align)) + color := C.sfons_rgba(cfg.color.r, cfg.color.g, cfg.color.b, cfg.color.a) + if cfg.color.a != 255 { + sgl.load_pipeline(ctx.timage_pip) + } + C.fonsSetColor(ctx.ft.fons, color) + ascender := f32(0.0) + descender := f32(0.0) + lh := f32(0.0) + ctx.ft.fons.vert_metrics(&ascender, &descender, &lh) +} + +pub fn (ctx &Context) draw_text(x int, y int, text_ string, cfg gx.TextCfg) { + $if macos { + if ctx.native_rendering { + if cfg.align == gx.align_right { + width := ctx.text_width(text_) + C.darwin_draw_string(x - width, ctx.height - y, text_, cfg) + } else { + C.darwin_draw_string(x, ctx.height - y, text_, cfg) + } + return + } + } + if !ctx.font_inited { + eprintln('gg: draw_text(): font not initialized') + return + } + // text := text_.trim_space() // TODO remove/optimize + // mut text := text_ + // if text.contains('\t') { + // text = text.replace('\t', ' ') + // } + ctx.set_cfg(cfg) + scale := if ctx.ft.scale == 0 { f32(1) } else { ctx.ft.scale } + C.fonsDrawText(ctx.ft.fons, x * scale, y * scale, &char(text_.str), 0) // TODO: check offsets/alignment +} + +pub fn (ctx &Context) draw_text_def(x int, y int, text string) { + ctx.draw_text(x, y, text) +} + +/* +pub fn (mut gg FT) init_font() { +} +*/ +pub fn (ft &FT) flush() { + sfons.flush(ft.fons) +} + +pub fn (ctx &Context) text_width(s string) int { + $if macos { + if ctx.native_rendering { + return C.darwin_text_width(s) + } + } + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + if s.ends_with(' ') { + return int((buf[2] - buf[0]) / ctx.scale) + + ctx.text_width('i') // TODO fix this in fontstash? + } + res := int((buf[2] - buf[0]) / ctx.scale) + // println('TW "$s" = $res') + $if macos { + if ctx.native_rendering { + return res * 2 + } + } + return int((buf[2] - buf[0]) / ctx.scale) +} + +pub fn (ctx &Context) text_height(s string) int { + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + return int((buf[3] - buf[1]) / ctx.scale) +} + +pub fn (ctx &Context) text_size(s string) (int, int) { + // ctx.set_cfg(cfg) TODO + if !ctx.font_inited { + return 0, 0 + } + mut buf := [4]f32{} + C.fonsTextBounds(ctx.ft.fons, 0, 0, &char(s.str), 0, &buf[0]) + return int((buf[2] - buf[0]) / ctx.scale), int((buf[3] - buf[1]) / ctx.scale) +} diff --git a/v_windows/v/vlib/gg/text_rendering.v b/v_windows/v/vlib/gg/text_rendering.v new file mode 100644 index 0000000..a34bbbb --- /dev/null +++ b/v_windows/v/vlib/gg/text_rendering.v @@ -0,0 +1,150 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module gg + +import os +import gx + +enum FontVariant { + normal = 0 + bold + mono + italic +} + +struct FTConfig { + font_path string + custom_bold_font_path string + scale f32 = 1.0 + font_size int + bytes_normal []byte + bytes_bold []byte + bytes_mono []byte + bytes_italic []byte +} + +struct StringToRender { + x int + y int + text string + cfg gx.TextCfg +} + +pub fn system_font_path() string { + env_font := os.getenv('VUI_FONT') + if env_font != '' && os.exists(env_font) { + return env_font + } + $if windows { + return 'C:\\Windows\\Fonts\\arial.ttf' + } + mut fonts := ['Ubuntu-R.ttf', 'Arial.ttf', 'LiberationSans-Regular.ttf', 'NotoSans-Regular.ttf', + 'FreeSans.ttf', 'DejaVuSans.ttf'] + $if macos { + fonts = ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf', + '/Library/Fonts/Arial.ttf', + ] + for font in fonts { + if os.is_file(font) { + return font + } + } + } + $if android { + xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml', + '/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml', + '/etc/fallback_fonts.xml', + ] + font_locations := ['/system/fonts', '/data/fonts'] + for xml_file in xml_files { + if os.is_file(xml_file) && os.is_readable(xml_file) { + xml := os.read_file(xml_file) or { continue } + lines := xml.split('\n') + mut candidate_font := '' + for line in lines { + if line.contains('').all_before('<').trim(' \n\t\r') + if candidate_font.contains('.ttf') { + for location in font_locations { + candidate_path := os.join_path(location, candidate_font) + if os.is_file(candidate_path) && os.is_readable(candidate_path) { + return candidate_path + } + } + } + } + } + } + } + } + s := os.execute('fc-list') + if s.exit_code != 0 { + panic('failed to fetch system fonts') + } + system_fonts := s.output.split('\n') + for line in system_fonts { + for font in fonts { + if line.contains(font) && line.contains(':') { + res := line.all_before(':') + println('Using font $res') + return res + } + } + } + panic('failed to init the font') +} + +fn get_font_path_variant(font_path string, variant FontVariant) string { + // TODO: find some way to make this shorter and more eye-pleasant + // NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work + mut file := os.file_name(font_path) + mut fpath := font_path.replace(file, '') + file = file.replace('.ttf', '') + + match variant { + .normal {} + .bold { + if fpath.ends_with('-Regular') { + file = file.replace('-Regular', '-Bold') + } else if file.starts_with('DejaVuSans') { + file += '-Bold' + } else if file.to_lower().starts_with('arial') { + file += 'bd' + } else { + file += '-bold' + } + $if macos { + if os.exists('SFNS-bold') { + file = 'SFNS-bold' + } + } + } + .italic { + if file.ends_with('-Regular') { + file = file.replace('-Regular', '-Italic') + } else if file.starts_with('DejaVuSans') { + file += '-Oblique' + } else if file.to_lower().starts_with('arial') { + file += 'i' + } else { + file += 'Italic' + } + } + .mono { + if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') { + file = file.replace('-Regular', 'Mono-Regular') + } else if file.to_lower().starts_with('arial') { + // Arial has no mono variant + } else { + file += 'Mono' + } + } + } + return fpath + file + '.ttf' +} + +fn debug_font_println(s string) { + $if debug_font ? { + println(s) + } +} diff --git a/v_windows/v/vlib/glm/glm.v b/v_windows/v/vlib/glm/glm.v new file mode 100644 index 0000000..abd7981 --- /dev/null +++ b/v_windows/v/vlib/glm/glm.v @@ -0,0 +1,428 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module glm + +import math + +/* +#flag -lmyglm +# f32* myglm_ortho(f32, f32, f32, f32); +# f32* myglm_translate(f32, f32, f32); +*/ +// # f32* myglm_rotate(f32 *m, f32 angle, f32, f32, f32); +// # f32* myglm_perspective(f32, f32, f32, f32); +// # f32* myglm_look_at(glm__Vec3, glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_mult(glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_cross(glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_normalize(glm__Vec3); +pub struct Mat4 { +pub: + data &f32 +} + +struct Vec2 { + x f32 + y f32 +} + +struct Vec3 { + x f32 + y f32 + z f32 +} + +pub fn vec3(x f32, y f32, z f32) Vec3 { + res := Vec3{ + x: x + y: y + z: z + } + return res +} + +fn mat4(f &f32) Mat4 { + res := Mat4{ + data: unsafe { f } + } + return res +} + +pub fn (v Vec3) str() string { + return 'Vec3{ $v.x, $v.y, $v.z }' +} + +pub fn (v Vec2) str() string { + return 'Vec3{ $v.x, $v.y }' +} + +pub fn (m Mat4) str() string { + mut s := '[ ' + for i in 0 .. 4 { + if i != 0 { + s += ' ' + } + for j in 0 .. 4 { + val := unsafe {m.data[i * 4 + j]} + s += '${val:5.2f} ' + } + if i != 3 { + s += '\n' + } + } + s += ']' + return s +} + +fn vec2(x int, y int) Vec2 { + res := Vec2{ + x: f32(x) + y: f32(y) + } + return res +} + +fn (a Vec3) add(b Vec3) Vec3 { + res := Vec3{ + x: a.x + b.x + y: a.y + b.y + z: a.z + b.z + } + return res +} + +fn (a Vec3) sub(b Vec3) Vec3 { + res := Vec3{ + x: a.x - b.x + y: a.y - b.y + z: a.z - b.z + } + return res +} + +// fn (a Vec3) mult(b Vec3) Vec3 { +// # return myglm_mult(a,b); +// } +fn (a Vec3) mult_scalar(b f32) Vec3 { + res := Vec3{ + x: a.x * b + y: a.y * b + z: a.z * b + } + return res +} + +fn (a Vec3) print() { + x := a.x + y := a.y + z := a.z + C.printf(c'vec3{%f,%f,%f}\n', x, y, z) + // println('vec3{$x,$y,$z}') +} + +/* +fn rotate(m Mat4, angle f32, vec Vec3) Mat4 { + // # t_mat4 m; + // println('rotate done') + # return glm__mat4( myglm_rotate(m.data, angle, vec.x,vec.y,vec.z) ); + return Mat4{} +} +*/ +fn f32_calloc(n int) &f32 { + return voidptr(vcalloc_noscan(n * int(sizeof(f32)))) +} + +// fn translate(vec Vec3) *f32 { +pub fn translate(m Mat4, v Vec3) Mat4 { + // # return glm__mat4(myglm_translate(vec.x,vec.y,vec.z) ); + a := m.data + mut out := f32_calloc(16) + x := v.x + y := v.y + z := v.z + unsafe { + a00 := a[0] + a01 := a[1] + a02 := a[2] + a03 := a[3] + a10 := a[4] + a11 := a[5] + a12 := a[6] + a13 := a[7] + a20 := a[8] + a21 := a[9] + a22 := a[10] + a23 := a[11] + out[0] = a00 + out[1] = a01 + out[2] = a02 + out[3] = a03 + out[4] = a10 + out[5] = a11 + out[6] = a12 + out[7] = a13 + out[8] = a20 + out[9] = a21 + out[10] = a22 + out[11] = a23 + out[12] = a00 * x + a10 * y + a20 * z + a[12] + out[13] = a01 * x + a11 * y + a21 * z + a[13] + out[14] = a02 * x + a12 * y + a22 * z + a[14] + out[15] = a03 * x + a13 * y + a23 * z + a[15] + } + return mat4(out) +} + +/* +fn normalize(vec Vec3) Vec3 { + # return myglm_normalize(vec); + return Vec3{} +} +*/ +// https://github.com/g-truc/glm/blob/0ceb2b755fb155d593854aefe3e45d416ce153a4/glm/ext/matrix_clip_space.inl +pub fn ortho(left f32, right f32, bottom f32, top f32) Mat4 { + // println('glm ortho($left, $right, $bottom, $top)') + // mat<4, 4, T, defaultp> Result(static_cast(1)); + n := 16 + mut res := f32_calloc(n) + unsafe { + res[0] = 2.0 / (right - left) + res[5] = 2.0 / (top - bottom) + res[10] = 1.0 + res[12] = -(right + left) / (right - left) + res[13] = -(top + bottom) / (top - bottom) + res[15] = 1.0 + } + return mat4(res) +} + +// https://github.com/g-truc/glm/blob/0ceb2b755fb155d593854aefe3e45d416ce153a4/glm/ext/matrix_clip_space.inl +pub fn ortho_zo(left f32, right f32, bottom f32, top f32, zNear f32, zFar f32) Mat4 { + // println('glm ortho($left, $right, $bottom, $top)') + // mat<4, 4, T, defaultp> Result(static_cast(1)); + n := 16 + mut res := f32_calloc(n) + unsafe { + res[0] = 2.0 / (right - left) + res[5] = 2.0 / (top - bottom) + res[10] = 1.0 + res[12] = -(right + left) / (right - left) + res[13] = -(top + bottom) / (top - bottom) + res[14] = -zNear / (zFar - zNear) + res[15] = 1.0 + } + return mat4(res) +} + +// fn scale(a *f32, v Vec3) *f32 { +pub fn scale(m Mat4, v Vec3) Mat4 { + a := m.data + mut out := f32_calloc(16) + x := v.x + y := v.y + z := v.z + unsafe { + out[0] = a[0] * v.x + out[1] = a[1] * x + out[2] = a[2] * x + out[3] = a[3] * x + out[4] = a[4] * y + out[5] = a[5] * y + out[6] = a[6] * y + out[7] = a[7] * y + out[8] = a[8] * z + out[9] = a[9] * z + out[10] = a[10] * z + out[11] = a[11] * z + out[12] = a[12] + out[13] = a[13] + out[14] = a[14] + out[15] = a[15] + } + return mat4(out) +} + +// multiplies two matrices +pub fn mult(a Mat4, b Mat4) Mat4 { + mut out := f32_calloc(16) + for i in 0 .. 4 { + for r in 0 .. 4 { + mut prod := f32(0) + for c in 0 .. 4 { + prod += unsafe {a.data[c * 4 + r] * b.data[i * 4 + c]} + } + unsafe { + out[i * 4 + r] = prod + } + } + } + return mat4(out) +} + +pub fn rotate(angle f32, axis Vec3, src Mat4) Mat4 { + c := f32(math.cos(angle)) + s := f32(math.sin(angle)) + oneminusc := f32(1.0) - c + xy := axis.x * axis.y + yz := axis.y * axis.z + xz := axis.x * axis.z + xs := axis.x * s + ys := axis.y * s + zs := axis.z * s + f00 := axis.x * axis.x * oneminusc + c + f01 := xy * oneminusc + zs + f02 := xz * oneminusc - ys + f10 := xy * oneminusc - zs + f11 := axis.y * axis.y * oneminusc + c + f12 := yz * oneminusc + xs + f20 := xz * oneminusc + ys + f21 := yz * oneminusc - xs + f22 := axis.z * axis.z * oneminusc + c + data := src.data + unsafe { + t00 := data[0] * f00 + data[4] * f01 + data[8] * f02 + t01 := data[1] * f00 + data[5] * f01 + data[9] * f02 + t02 := data[2] * f00 + data[6] * f01 + data[10] * f02 + t03 := data[3] * f00 + data[7] * f01 + data[11] * f02 + t10 := data[0] * f10 + data[4] * f11 + data[8] * f12 + t11 := data[1] * f10 + data[5] * f11 + data[9] * f12 + t12 := data[2] * f10 + data[6] * f11 + data[10] * f12 + t13 := data[3] * f10 + data[7] * f11 + data[11] * f12 + mut dest := src.data + dest[8] = data[0] * f20 + data[4] * f21 + data[8] * f22 + dest[9] = data[1] * f20 + data[5] * f21 + data[9] * f22 + dest[10] = data[2] * f20 + data[6] * f21 + data[10] * f22 + dest[11] = data[3] * f20 + data[7] * f21 + data[11] * f22 + dest[0] = t00 + dest[1] = t01 + dest[2] = t02 + dest[3] = t03 + dest[4] = t10 + dest[5] = t11 + dest[6] = t12 + dest[7] = t13 + return mat4(dest) + } +} + +// fn rotate_z(a *f32, rad f32) *f32 { +pub fn rotate_z(m Mat4, rad f32) Mat4 { + a := m.data + mut out := f32_calloc(16) + s := f32(math.sin(rad)) + c := f32(math.cos(rad)) + unsafe { + a00 := a[0] + a01 := a[1] + a02 := a[2] + a03 := a[3] + a10 := a[4] + a11 := a[5] + a12 := a[6] + a13 := a[7] + out[8] = a[8] + out[9] = a[9] + out[10] = a[10] + out[11] = a[11] + out[12] = a[12] + out[13] = a[13] + out[14] = a[14] + out[15] = a[15] + // Perform axis-specific matrix multiplication + out[0] = a00 * c + a10 * s + out[1] = a01 * c + a11 * s + out[2] = a02 * c + a12 * s + out[3] = a03 * c + a13 * s + out[4] = a10 * c - a00 * s + out[5] = a11 * c - a01 * s + out[6] = a12 * c - a02 * s + out[7] = a13 * c - a03 * s + } + return mat4(out) +} + +pub fn identity() Mat4 { + // 1 0 0 0 + // 0 1 0 0 + // 0 0 1 0 + // 0 0 0 1 + n := 16 + mut res := f32_calloc(int(sizeof(f32)) * n) + unsafe { + res[0] = 1 + res[5] = 1 + res[10] = 1 + res[15] = 1 + } + return mat4(res) +} + +// returns *f32 without allocation +pub fn identity2(mut res &f32) { + res[0] = 1 + res[5] = 1 + res[10] = 1 + res[15] = 1 + // # f32 f[16]={0};// for (int i =0;i<16;i++) + // # printf("!!%d\n", f[0]); + // # glm__identity2(&f); + // # gl__Shader_set_mat4(shader, tos2("projection"), f) ; +} + +pub fn identity3() []f32 { + res := [f32(1.0), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + return res +} + +// https://github.com/toji/gl-matrix/blob/1549cf21dfa14a2bc845993485343d519cf064fe/src/gl-matrix/mat4.js +fn ortho_js(left f32, right f32, bottom f32, top f32) &f32 { + // mynear := 1 + // myfar := 1 + lr := 1.0 / (left - right) + bt := 1.0 / (bottom - top) + nf := f32(1.0) / 1.0 // (mynear -myfar) + unsafe { + mut out := &f32(malloc_noscan(int(sizeof(f32) * 16))) + out[0] = -2.0 * lr + out[1] = 0 + out[2] = 0 + out[3] = 0 + out[4] = 0 + out[5] = -2.0 * bt + out[6] = 0 + out[7] = 0 + out[8] = 0 + out[9] = 0 + out[10] = 2.0 * nf + out[11] = 0 + out[12] = (left + right) * lr + out[13] = (top + bottom) * bt + out[14] = 1.0 * nf // (far + near) * nf; + out[15] = 1 + return out + } + // f := 0.0 + // return &f +} + +// fn ortho_old(a, b, c, d f32) *f32 { +// # return myglm_ortho(a,b,c,d); +// } +fn cross(a Vec3, b Vec3) Vec3 { + // # return myglm_cross(a,b); + return Vec3{} +} + +/* +fn perspective(degrees f32, ratio f32, a, b f32) Mat4 { + // println('lang per degrees=$degrees ratio=$ratio a=$a b=$b') + // # printf("lang pers degrees=%f ratio=%f a=%f b=%f\n", degrees, ratio, a,b); + # return glm__mat4( myglm_perspective(degrees, ratio, a,b) ) ; + return Mat4{} +} + +fn look_at(eye, center, up Vec3) Mat4 { + # return glm__mat4( myglm_look_at(eye, center, up) ) ; + return Mat4{} +} +*/ diff --git a/v_windows/v/vlib/glm/glm_test.v b/v_windows/v/vlib/glm/glm_test.v new file mode 100644 index 0000000..c2a4266 --- /dev/null +++ b/v_windows/v/vlib/glm/glm_test.v @@ -0,0 +1,155 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// might need special case for this +// import gl +import glm + +fn cmp(a f32, b f32) bool { + return int(a * 1000) == int(b * 1000) +} + +fn test_ortho() { + projection := glm.ortho(0, 200, 400, 0) + $if debug { + println(unsafe { projection.data[0] }) + } + unsafe { + assert cmp(projection.data[0], 0.01) + assert cmp(projection.data[1], 0.000000) + assert cmp(projection.data[2], 0.000000) + assert cmp(projection.data[3], 0.000000) + assert cmp(projection.data[4], 0.000000) + assert cmp(projection.data[5], -0.005000) + assert cmp(projection.data[6], 0.000000) + assert cmp(projection.data[7], 0.000000) + assert cmp(projection.data[8], 0.000000) + assert cmp(projection.data[9], 0.000000) + assert cmp(projection.data[10], 1.000000) + assert cmp(projection.data[11], 0.000000) + assert cmp(projection.data[12], -1.000000) + assert cmp(projection.data[13], 1.000000) + assert cmp(projection.data[14], 0.000000) + assert cmp(projection.data[15], 1.000000) + } + // f := gg.ortho(1,2,3,4) + /* + // for debugging broken tetris in gg.o + # projection.data[0]=0.010000; + # projection.data[1]=0.000000; + # projection.data[2]=0.000000; + # projection.data[3]=0.000000; + # projection.data[4]=0.000000; + # projection.data[5]=-0.005000; + # projection.data[6]=0.000000; + # projection.data[7]=0.000000; + # projection.data[8]=0.000000; + # projection.data[9]=0.000000; + # projection.data[10]=1.000000; + # projection.data[11]=0.000000; + # projection.data[12]=-1.000000; + # projection.data[13]=1.000000; + # projection.data[14]=0.000000; + # projection.data[15]=1.000000; + */ +} + +fn test_rotate() { + $if debug { + println('rotate') + } + mut m := glm.identity() + m = glm.scale(m, glm.vec3(2, 2, 2)) + $if debug { + println(m) + } + m = glm.rotate_z(m, 1) + $if debug { + println(m) + } + mut m1 := glm.identity() + mut m2 := glm.identity() + m1 = glm.rotate(1, glm.vec3(1, 0, 0), m1) + m2 = glm.rotate(1, glm.vec3(0, 1, 0), m2) + mut same := true + for i in 0 .. 15 { + if unsafe { m1.data[i] } != unsafe { m2.data[i] } { + same = false + } + } + assert !same +} + +fn test_translate() { + mut m := glm.identity() + m = glm.translate(m, glm.vec3(0, 0, -0.5)) + $if debug { + println(m) + } + unsafe { + assert m.data[0] == 1.0 + assert m.data[1] == 0.0 + assert m.data[2] == 0.0 + assert m.data[3] == 0.0 + // + assert m.data[4] == 0.0 + assert m.data[5] == 1.0 + assert m.data[6] == 0.0 + assert m.data[7] == 0.0 + assert m.data[8] == 0.0 + assert m.data[9] == 0.0 + assert m.data[10] == 1.0 + assert m.data[11] == 0.0 + // + assert m.data[12] == 0.0 + assert m.data[13] == 0.0 + assert m.data[14] == -0.5 + assert m.data[15] == 1.0 + } +} + +fn f32_calloc(n int) &f32 { + return voidptr(vcalloc(n * int(sizeof(f32)))) +} + +fn test_mult1() { + mut adata := f32_calloc(16) + unsafe { + adata[1 * 4 + 1] = 6 + adata[2 * 4 + 3] = 2 + adata[0 * 4 + 2] = 3 + adata[2 * 4 + 1] = 1 + } + mut bdata := f32_calloc(16) + unsafe { + bdata[1 * 4 + 1] = -2 + bdata[2 * 4 + 3] = 1 + bdata[0 * 4 + 2] = 6 + bdata[2 * 4 + 1] = -3 + } + mut expected := f32_calloc(16) + unsafe { + expected[0 * 4 + 0] = 0 // 0*0+0*0+0*6+0*0 + expected[0 * 4 + 1] = 6 // 0*0+0*6+1*6+0*0 + expected[0 * 4 + 2] = 0 // 3*0+0*0+0*6+0*0 + expected[0 * 4 + 3] = 12 // 0*0+0*0+2*6+0*0 + expected[1 * 4 + 0] = 0 // 0*0+0*-2+0*0+0*0 + expected[1 * 4 + 1] = -12 // 0*0­+6*-2+1*0­+0*0 + expected[1 * 4 + 2] = 0 // 3*0­+0*-2­+0*0­+0*0 + expected[1 * 4 + 3] = 0 // 0*0­+0*-2­+2*0­+0*0 + expected[2 * 4 + 0] = 0 // 0*0­+0*-3­+0*0­+0*1 + expected[2 * 4 + 1] = -18 // 0*0­+6*-3­+1*0­+0*1 + expected[2 * 4 + 2] = 0 // 3*0­+0*-3+0*0­+0*1 + expected[2 * 4 + 3] = 0 // 0*0­+0*-3­+2*0­+0*1 + expected[3 * 4 + 0] = 0 // 0*0­+0*0­+0*0­+0*0 + expected[3 * 4 + 1] = 0 // 0*0­+6*0­+1*0­+0*0 + expected[3 * 4 + 2] = 0 // 3*0­+0*0­+0*0­+0*0 + expected[3 * 4 + 3] = 0 // 0*0­+0*0­+2*0­+0*0 + } + mut a := glm.Mat4{adata} + b := glm.Mat4{bdata} + a = glm.mult(a, b) + for i in 0 .. 15 { + assert unsafe { a.data[i] } == unsafe { expected[i] } + } +} diff --git a/v_windows/v/vlib/gx/color.v b/v_windows/v/vlib/gx/color.v new file mode 100644 index 0000000..e50d932 --- /dev/null +++ b/v_windows/v/vlib/gx/color.v @@ -0,0 +1,234 @@ +module gx + +pub const ( + blue = Color{ + r: 0 + g: 0 + b: 255 + } + red = Color{ + r: 255 + g: 0 + b: 0 + } + green = Color{ + r: 0 + g: 255 + b: 0 + } + yellow = Color{ + r: 255 + g: 255 + b: 0 + } + orange = Color{ + r: 255 + g: 165 + b: 0 + } + purple = Color{ + r: 128 + g: 0 + b: 128 + } + black = Color{ + r: 0 + g: 0 + b: 0 + } + gray = Color{ + r: 128 + g: 128 + b: 128 + } + indigo = Color{ + r: 75 + g: 0 + b: 130 + } + pink = Color{ + r: 255 + g: 192 + b: 203 + } + violet = Color{ + r: 238 + g: 130 + b: 238 + } + white = Color{ + r: 255 + g: 255 + b: 255 + } + dark_blue = Color{ + r: 0 + g: 0 + b: 139 + } + dark_gray = Color{ + r: 169 + g: 169 + b: 169 + } + dark_green = Color{ + r: 0 + g: 100 + b: 0 + } + dark_red = Color{ + r: 139 + g: 0 + b: 0 + } + light_blue = Color{ + r: 173 + g: 216 + b: 230 + } + light_gray = Color{ + r: 211 + g: 211 + b: 211 + } + light_green = Color{ + r: 144 + g: 238 + b: 144 + } + light_red = Color{ + r: 255 + g: 204 + b: 203 + } +) + +// Color represents a 32 bit color value in sRGB format +pub struct Color { +pub mut: + r byte + g byte + b byte + a byte = 255 +} + +// hex takes in a 32 bit integer and splits it into 4 byte values +pub fn hex(color int) Color { + return Color{ + r: byte((color >> 24) & 0xFF) + g: byte((color >> 16) & 0xFF) + b: byte((color >> 8) & 0xFF) + a: byte(color & 0xFF) + } +} + +pub fn rgb(r byte, g byte, b byte) Color { + return Color{ + r: r + g: g + b: b + } +} + +pub fn rgba(r byte, g byte, b byte, a byte) Color { + return Color{ + r: r + g: g + b: b + a: a + } +} + +pub fn (c Color) + (c2 Color) Color { + return Color{ + r: c.r + c2.r + g: c.g + c2.g + b: c.b + c2.b + a: c.b + c2.a + } +} + +pub fn (c Color) - (c2 Color) Color { + return Color{ + r: c.r - c2.r + g: c.g - c2.g + b: c.b - c2.b + a: c.b - c2.a + } +} + +pub fn (c Color) * (c2 Color) Color { + return Color{ + r: c.r * c2.r + g: c.g * c2.g + b: c.b * c2.b + a: c.b * c2.a + } +} + +pub fn (c Color) / (c2 Color) Color { + return Color{ + r: c.r / c2.r + g: c.g / c2.g + b: c.b / c2.b + a: c.b / c2.a + } +} + +pub fn (c Color) eq(c2 Color) bool { + return c.r == c2.r && c.g == c2.g && c.b == c2.b && c.a == c2.a +} + +pub fn (c Color) str() string { + return 'Color{$c.r, $c.g, $c.b, $c.a}' +} + +// rgba8 - convert a color value to an int in the RGBA8 order. +// see https://developer.apple.com/documentation/coreimage/ciformat +[inline] +pub fn (c Color) rgba8() int { + return (int(c.r) << 24) + (int(c.g) << 16) + (int(c.b) << 8) + int(c.a) +} + +// bgra8 - convert a color value to an int in the BGRA8 order. +// see https://developer.apple.com/documentation/coreimage/ciformat +[inline] +pub fn (c Color) bgra8() int { + return (int(c.b) << 24) + (int(c.g) << 16) + (int(c.r) << 8) + int(c.a) +} + +// abgr8 - convert a color value to an int in the ABGR8 order. +// see https://developer.apple.com/documentation/coreimage/ciformat +[inline] +pub fn (c Color) abgr8() int { + return (int(c.a) << 24) + (int(c.b) << 16) + (int(c.g) << 8) + int(c.r) +} + +const ( + string_colors = { + 'blue': blue + 'red': red + 'green': green + 'yellow': yellow + 'orange': orange + 'purple': purple + 'black': black + 'gray': gray + 'indigo': indigo + 'pink': pink + 'violet': violet + 'white': white + 'dark_blue': dark_blue + 'dark_gray': dark_gray + 'dark_green': dark_green + 'dark_red': dark_red + 'light_blue': light_blue + 'light_gray': light_gray + 'light_green': light_green + 'light_red': light_red + } +) + +pub fn color_from_string(s string) Color { + return gx.string_colors[s] +} diff --git a/v_windows/v/vlib/gx/color_test.v b/v_windows/v/vlib/gx/color_test.v new file mode 100644 index 0000000..533a41d --- /dev/null +++ b/v_windows/v/vlib/gx/color_test.v @@ -0,0 +1,63 @@ +import gx + +fn test_hex() { + // valid colors + a := gx.hex(0x6c5ce7ff) + b := gx.rgba(108, 92, 231, 255) + assert a.eq(b) + // doesn't give right value with short hex value + short := gx.hex(0xfff) + assert !short.eq(gx.white) +} + +fn test_add() { + a := gx.rgba(100, 100, 100, 100) + b := gx.rgba(100, 100, 100, 100) + r := gx.rgba(200, 200, 200, 200) + assert (a + b).eq(r) +} + +fn test_sub() { + a := gx.rgba(100, 100, 100, 100) + b := gx.rgba(100, 100, 100, 100) + r := gx.rgba(0, 0, 0, 0) + assert (a - b).eq(r) +} + +fn test_mult() { + a := gx.rgba(10, 10, 10, 10) + b := gx.rgba(10, 10, 10, 10) + r := gx.rgba(100, 100, 100, 100) + assert (a * b).eq(r) +} + +fn test_div() { + a := gx.rgba(100, 100, 100, 100) + b := gx.rgba(10, 10, 10, 10) + r := gx.rgba(10, 10, 10, 10) + assert (a / b).eq(r) +} + +fn test_rgba8() { + assert gx.white.rgba8() == -1 + assert gx.black.rgba8() == 255 + assert gx.red.rgba8() == -16776961 + assert gx.green.rgba8() == 16711935 + assert gx.blue.rgba8() == 65535 +} + +fn test_bgra8() { + assert gx.white.bgra8() == -1 + assert gx.black.bgra8() == 255 + assert gx.red.bgra8() == 65535 + assert gx.green.bgra8() == 16711935 + assert gx.blue.bgra8() == -16776961 +} + +fn test_abgr8() { + assert gx.white.abgr8() == -1 + assert gx.black.abgr8() == -16777216 + assert gx.red.abgr8() == -16776961 + assert gx.green.abgr8() == -16711936 + assert gx.blue.abgr8() == -65536 +} diff --git a/v_windows/v/vlib/gx/image.v b/v_windows/v/vlib/gx/image.v new file mode 100644 index 0000000..4a20ea1 --- /dev/null +++ b/v_windows/v/vlib/gx/image.v @@ -0,0 +1,14 @@ +module gx + +pub struct Image { +mut: + obj voidptr +pub: + id int + width int + height int +} + +pub fn (i Image) is_empty() bool { + return isnil(i.obj) +} diff --git a/v_windows/v/vlib/gx/text.c.v b/v_windows/v/vlib/gx/text.c.v new file mode 100644 index 0000000..6bd3b8e --- /dev/null +++ b/v_windows/v/vlib/gx/text.c.v @@ -0,0 +1,18 @@ +module gx + +import fontstash + +const used_import = fontstash.used_import + +pub enum HorizontalAlign { + left = C.FONS_ALIGN_LEFT + center = C.FONS_ALIGN_CENTER + right = C.FONS_ALIGN_RIGHT +} + +pub enum VerticalAlign { + top = C.FONS_ALIGN_TOP + middle = C.FONS_ALIGN_MIDDLE + bottom = C.FONS_ALIGN_BOTTOM + baseline = C.FONS_ALIGN_BASELINE +} diff --git a/v_windows/v/vlib/gx/text.v b/v_windows/v/vlib/gx/text.v new file mode 100644 index 0000000..fee934a --- /dev/null +++ b/v_windows/v/vlib/gx/text.v @@ -0,0 +1,20 @@ +module gx + +// TODO: remove these and uae the enum everywhere +pub const ( + align_left = HorizontalAlign.left + align_right = HorizontalAlign.right +) + +pub struct TextCfg { +pub: + color Color = black + size int = 16 + align HorizontalAlign = .left + vertical_align VerticalAlign = .top + max_width int + family string + bold bool + mono bool + italic bool +} diff --git a/v_windows/v/vlib/hash/crc32/crc32.v b/v_windows/v/vlib/hash/crc32/crc32.v new file mode 100644 index 0000000..d29aa12 --- /dev/null +++ b/v_windows/v/vlib/hash/crc32/crc32.v @@ -0,0 +1,63 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// This is a very basic crc32 implementation +// at the moment with no architecture optimizations +module crc32 + +// polynomials +pub const ( + ieee = u32(0xedb88320) + castagnoli = u32(0x82f63b78) + koopman = u32(0xeb31d82e) +) + +// The size of a CRC-32 checksum in bytes. +const ( + size = 4 +) + +struct Crc32 { +mut: + table []u32 +} + +fn (mut c Crc32) generate_table(poly int) { + for i in 0 .. 256 { + mut crc := u32(i) + for _ in 0 .. 8 { + if crc & u32(1) == u32(1) { + crc = (crc >> 1) ^ u32(poly) + } else { + crc >>= u32(1) + } + } + c.table << crc + } +} + +fn (c &Crc32) sum32(b []byte) u32 { + mut crc := ~u32(0) + for i in 0 .. b.len { + crc = c.table[byte(crc) ^ b[i]] ^ (crc >> 8) + } + return ~crc +} + +pub fn (c &Crc32) checksum(b []byte) u32 { + return c.sum32(b) +} + +// pass the polynomial to use +pub fn new(poly int) &Crc32 { + mut c := &Crc32{} + c.generate_table(poly) + return c +} + +// calculate crc32 using ieee +pub fn sum(b []byte) u32 { + c := new(int(crc32.ieee)) + return c.sum32(b) +} diff --git a/v_windows/v/vlib/hash/crc32/crc32_test.v b/v_windows/v/vlib/hash/crc32/crc32_test.v new file mode 100644 index 0000000..7355179 --- /dev/null +++ b/v_windows/v/vlib/hash/crc32/crc32_test.v @@ -0,0 +1,14 @@ +import hash.crc32 + +fn test_hash_crc32() { + b1 := 'testing crc32'.bytes() + sum1 := crc32.sum(b1) + assert sum1 == u32(1212124400) + assert sum1.hex() == '483f8cf0' + + c := crc32.new(int(crc32.ieee)) + b2 := 'testing crc32 again'.bytes() + sum2 := c.checksum(b2) + assert sum2 == u32(1420327025) + assert sum2.hex() == '54a87871' +} diff --git a/v_windows/v/vlib/hash/fnv1a/fnv1a.v b/v_windows/v/vlib/hash/fnv1a/fnv1a.v new file mode 100644 index 0000000..275c8a2 --- /dev/null +++ b/v_windows/v/vlib/hash/fnv1a/fnv1a.v @@ -0,0 +1,44 @@ +module fnv1a + +const ( + fnv64_prime = u64(1099511628211) + fnv64_offset_basis = u64(14695981039346656037) + fnv32_offset_basis = u32(2166136261) + fnv32_prime = u32(16777619) +) + +[inline] +pub fn sum32_string(data string) u32 { + mut hash := fnv1a.fnv32_offset_basis + for i in 0 .. data.len { + hash = (hash ^ u32(data[i])) * fnv1a.fnv32_prime + } + return hash +} + +[inline] +pub fn sum32(data []byte) u32 { + mut hash := fnv1a.fnv32_offset_basis + for i in 0 .. data.len { + hash = (hash ^ u32(data[i])) * fnv1a.fnv32_prime + } + return hash +} + +[inline] +pub fn sum64_string(data string) u64 { + mut hash := fnv1a.fnv64_offset_basis + for i in 0 .. data.len { + hash = (hash ^ u64(data[i])) * fnv1a.fnv64_prime + } + return hash +} + +[inline] +pub fn sum64(data []byte) u64 { + mut hash := fnv1a.fnv64_offset_basis + for i in 0 .. data.len { + hash = (hash ^ u64(data[i])) * fnv1a.fnv64_prime + } + return hash +} diff --git a/v_windows/v/vlib/hash/fnv1a/fnv1a_test.v b/v_windows/v/vlib/hash/fnv1a/fnv1a_test.v new file mode 100644 index 0000000..f775ab1 --- /dev/null +++ b/v_windows/v/vlib/hash/fnv1a/fnv1a_test.v @@ -0,0 +1,12 @@ +import hash.fnv1a + +fn test_fnv1a() { + $if windows { + return + } + a := 'apple' + b := fnv1a.sum64_string(a) + c := fnv1a.sum64(a.bytes()) + assert b.hex() == 'f74a62a458befdbf' + assert c.hex() == 'f74a62a458befdbf' +} diff --git a/v_windows/v/vlib/hash/hash.v b/v_windows/v/vlib/hash/hash.v new file mode 100644 index 0000000..eabb166 --- /dev/null +++ b/v_windows/v/vlib/hash/hash.v @@ -0,0 +1,20 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module hash + +interface Hasher { + // Sum appends the current hash to b and returns the resulting array. + // It does not change the underlying hash state. + sum(b []byte) []byte + size() int + block_size() int +} + +interface Hash32er { + sum32() u32 +} + +interface Hash64er { + sum64() u64 +} diff --git a/v_windows/v/vlib/hash/wyhash.c.v b/v_windows/v/vlib/hash/wyhash.c.v new file mode 100644 index 0000000..3f93d24 --- /dev/null +++ b/v_windows/v/vlib/hash/wyhash.c.v @@ -0,0 +1,33 @@ +module hash + +//#flag -I @VEXEROOT/thirdparty/wyhash +//#include "wyhash.h" +fn C.wyhash(&byte, u64, u64, &u64) u64 + +fn C.wyhash64(u64, u64) u64 + +fn init() { + _ := { + 1: 1 + } +} + +[inline] +pub fn wyhash_c(key &byte, len u64, seed u64) u64 { + return C.wyhash(key, len, seed, &u64(C._wyp)) +} + +[inline] +pub fn wyhash64_c(a u64, b u64) u64 { + return C.wyhash64(a, b) +} + +[inline] +pub fn sum64_string(key string, seed u64) u64 { + return wyhash_c(key.str, u64(key.len), seed) +} + +[inline] +pub fn sum64(key []byte, seed u64) u64 { + return wyhash_c(&byte(key.data), u64(key.len), seed) +} diff --git a/v_windows/v/vlib/hash/wyhash.js.v b/v_windows/v/vlib/hash/wyhash.js.v new file mode 100644 index 0000000..26af4da --- /dev/null +++ b/v_windows/v/vlib/hash/wyhash.js.v @@ -0,0 +1 @@ +module hash diff --git a/v_windows/v/vlib/hash/wyhash.v b/v_windows/v/vlib/hash/wyhash.v new file mode 100644 index 0000000..fb438a1 --- /dev/null +++ b/v_windows/v/vlib/hash/wyhash.v @@ -0,0 +1,72 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// this is an implementation of wyhash v4 +// from https://github.com/wangyi-fudan/wyhash +// +// TODO: use u128 once implemented +// currently the C version performs slightly better +// because it uses 128 bit int when available and +// branch prediction hints. the C version will be +// removed once the perfomance is matched. +// you can test performance by running: +// `v run cmd/tools/bench/wyhash.v` +// try running with and without the `-prod` flag +module hash + +const ( + wyp0 = u64(0xa0761d6478bd642f) + wyp1 = u64(0xe7037ed1a0b428db) + wyp2 = u64(0x8ebc6af09c88c6e3) + wyp3 = u64(0x589965cc75374cc3) + wyp4 = u64(0x1d8e4e27c47d124f) +) + +[inline] +fn wyrotr(v u64, k u32) u64 { + return (v >> k) | (v << (64 - k)) +} + +[inline] +pub fn wymum(a u64, b u64) u64 { + /* + mut r := u128(a) + r = r*b + return (r>>64)^r + */ + mask32 := u32(4294967295) + x0 := a & mask32 + x1 := a >> 32 + y0 := b & mask32 + y1 := b >> 32 + w0 := x0 * y0 + t := x1 * y0 + (w0 >> 32) + mut w1 := t & mask32 + w2 := t >> 32 + w1 += x0 * y1 + hi := x1 * y1 + w2 + (w1 >> 32) + lo := a * b + return hi ^ lo +} + +[inline] +fn wyr3(p &byte, k u64) u64 { + unsafe { + return (u64(p[0]) << 16) | (u64(p[k >> 1]) << 8) | u64(p[k - 1]) + } +} + +[inline] +fn wyr4(p &byte) u64 { + unsafe { + return u32(p[0]) | (u32(p[1]) << u32(8)) | (u32(p[2]) << u32(16)) | (u32(p[3]) << u32(24)) + } +} + +[inline] +fn wyr8(p &byte) u64 { + unsafe { + return u64(p[0]) | (u64(p[1]) << 8) | (u64(p[2]) << 16) | (u64(p[3]) << 24) | (u64(p[4]) << 32) | (u64(p[5]) << 40) | (u64(p[6]) << 48) | (u64(p[7]) << 56) + } +} diff --git a/v_windows/v/vlib/io/buffered_reader.v b/v_windows/v/vlib/io/buffered_reader.v new file mode 100644 index 0000000..f336e2d --- /dev/null +++ b/v_windows/v/vlib/io/buffered_reader.v @@ -0,0 +1,145 @@ +module io + +// BufferedReader provides a buffered interface for a reader +struct BufferedReader { +mut: + reader Reader + buf []byte + offset int // current offset in the buffer + len int + fails int // how many times fill_buffer has read 0 bytes in a row + mfails int // maximum fails, after which we can assume that the stream has ended +pub mut: + end_of_stream bool // whether we reached the end of the upstream reader +} + +// BufferedReaderConfig are options that can be given to a reader +pub struct BufferedReaderConfig { + reader Reader + cap int = 128 * 1024 // large for fast reading of big(ish) files + retries int = 2 // how many times to retry before assuming the stream ended +} + +// new_buffered_reader creates new BufferedReader +pub fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader { + if o.cap <= 0 { + panic('new_buffered_reader should be called with a positive `cap`') + } + // create + r := &BufferedReader{ + reader: o.reader + buf: []byte{len: o.cap, cap: o.cap} + offset: 0 + mfails: o.retries + } + return r +} + +// read fufills the Reader interface +pub fn (mut r BufferedReader) read(mut buf []byte) ?int { + if r.end_of_stream { + return none + } + // read data out of the buffer if we dont have any + if r.needs_fill() { + if !r.fill_buffer() { + // end of stream + return none + } + } + read := copy(buf, r.buf[r.offset..r.len]) + if read == 0 { + return none + } + r.offset += read + return read +} + +pub fn (mut r BufferedReader) free() { + unsafe { + r.buf.free() + } +} + +// fill_buffer attempts to refill the internal buffer +// and returns whether it got any data +fn (mut r BufferedReader) fill_buffer() bool { + if r.end_of_stream { + // we know we have already reached the end of stream + // so return early + return true + } + r.offset = 0 + r.len = 0 + r.len = r.reader.read(mut r.buf) or { + // end of stream was reached + r.end_of_stream = true + return false + } + if r.len == 0 { + r.fails++ + } else { + r.fails = 0 + } + if r.fails >= r.mfails { + // When reading 0 bytes several times in a row, assume the stream has ended. + // This prevents infinite loops ¯\_(ツ)_/¯ ... + r.end_of_stream = true + return false + } + // we got some data + return true +} + +// needs_fill returns whether the buffer needs refilling +fn (r BufferedReader) needs_fill() bool { + return r.offset >= r.len +} + +// end_of_stream returns whether the end of the stream was reached +pub fn (r BufferedReader) end_of_stream() bool { + return r.end_of_stream +} + +// read_line attempts to read a line from the buffered reader +// it will read until it finds a new line character (\n) or +// the end of stream +pub fn (mut r BufferedReader) read_line() ?string { + if r.end_of_stream { + return none + } + mut line := []byte{} + for { + if r.needs_fill() { + // go fetch some new data + if !r.fill_buffer() { + // We are at the end of the stream + if line.len == 0 { + // we had nothing so return nothing + return none + } + return line.bytestr() + } + } + // try and find a newline character + mut i := r.offset + for ; i < r.len; i++ { + c := r.buf[i] + if c == `\n` { + // great, we hit something + // do some checking for whether we hit \r\n or just \n + if i != 0 && r.buf[i - 1] == `\r` { + x := i - 1 + line << r.buf[r.offset..x] + } else { + line << r.buf[r.offset..i] + } + r.offset = i + 1 + return line.bytestr() + } + } + line << r.buf[r.offset..i] + r.offset = i + } + return none +} diff --git a/v_windows/v/vlib/io/custom_string_reading_test.v b/v_windows/v/vlib/io/custom_string_reading_test.v new file mode 100644 index 0000000..76e3307 --- /dev/null +++ b/v_windows/v/vlib/io/custom_string_reading_test.v @@ -0,0 +1,57 @@ +import io + +struct StringReader { + text string +mut: + place int +} + +fn imin(a int, b int) int { + return if a < b { a } else { b } +} + +fn (mut s StringReader) read(mut buf []byte) ?int { + $if debug { + eprintln('>>>> StringReader.read output buf.len: $buf.len') + } + if s.place > s.text.len + 1 { + return none + } + mut howmany := imin(buf.len, s.text.len - s.place) + xxx := s.text[s.place..s.place + howmany].bytes() + read := copy(buf, xxx) + s.place += read + return read +} + +fn read_from_string(text string, capacity int) []byte { + mut str := StringReader{ + text: text + } + mut stream := io.new_buffered_reader(reader: str, cap: capacity) + // + mut buf := []byte{len: 1} + mut res := []byte{} + mut i := 0 + for { + z := stream.read(mut buf) or { break } + res << buf + $if debug { + println('capacity: $capacity, i: $i, buf: $buf | z: $z') + } + i++ + } + return res +} + +pub fn test_reading_from_a_string() { + for capacity in 1 .. 1000 { + assert read_from_string('a', capacity) == [byte(`a`)] + assert read_from_string('ab', capacity) == [byte(`a`), `b`] + assert read_from_string('abc', capacity) == [byte(`a`), `b`, `c`] + assert read_from_string('abcde', capacity) == [byte(`a`), `b`, `c`, `d`, `e`] + large_string_bytes := []byte{len: 1000, init: `x`} + large_string := large_string_bytes.bytestr() + assert read_from_string(large_string, capacity) == large_string_bytes + } +} diff --git a/v_windows/v/vlib/io/io.v b/v_windows/v/vlib/io/io.v new file mode 100644 index 0000000..c07e074 --- /dev/null +++ b/v_windows/v/vlib/io/io.v @@ -0,0 +1,16 @@ +module io + +const ( + buf_max_len = 1024 +) + +pub fn cp(src Reader, mut dst Writer) ? { + mut buf := []byte{len: io.buf_max_len} + for { + len := src.read(mut buf) or { break } + dst.write(buf[..len]) or { return err } + } + unsafe { + buf.free() + } +} diff --git a/v_windows/v/vlib/io/io_cp_test.v b/v_windows/v/vlib/io/io_cp_test.v new file mode 100644 index 0000000..21c743a --- /dev/null +++ b/v_windows/v/vlib/io/io_cp_test.v @@ -0,0 +1,13 @@ +import io +import os + +fn test_cp() ? { + mut f := os.open(@FILE) or { panic(err) } + defer { + f.close() + } + mut r := io.new_buffered_reader(reader: f) + mut stdout := os.stdout() + io.cp(r, mut stdout) ? + assert true +} diff --git a/v_windows/v/vlib/io/io_test.v b/v_windows/v/vlib/io/io_test.v new file mode 100644 index 0000000..fca17c2 --- /dev/null +++ b/v_windows/v/vlib/io/io_test.v @@ -0,0 +1,41 @@ +import io + +struct Buf { +pub: + bytes []byte +mut: + i int +} + +struct Writ { +pub mut: + bytes []byte +} + +fn (mut b Buf) read(mut buf []byte) ?int { + if !(b.i < b.bytes.len) { + return none + } + n := copy(buf, b.bytes[b.i..]) + b.i += n + return n +} + +fn (mut w Writ) write(buf []byte) ?int { + if buf.len <= 0 { + return none + } + w.bytes << buf + return buf.len +} + +fn test_copy() { + src := Buf{ + bytes: 'abcdefghij'.repeat(10).bytes() + } + mut dst := Writ{ + bytes: []byte{} + } + io.cp(src, mut dst) or { assert false } + assert dst.bytes == src.bytes +} diff --git a/v_windows/v/vlib/io/multi_writer.v b/v_windows/v/vlib/io/multi_writer.v new file mode 100644 index 0000000..837ea30 --- /dev/null +++ b/v_windows/v/vlib/io/multi_writer.v @@ -0,0 +1,33 @@ +module io + +// new_multi_writer returns a Writer that writes to all writers. The write +// function of the returned Writer writes to all writers of the MultiWriter, +// returns the length of bytes written, and if any writer fails to write the +// full length an error is returned and writing to other writers stops, and if +// any writer returns an error the error is returned immediately and writing to +// other writers stops. +pub fn new_multi_writer(writers ...Writer) Writer { + return &MultiWriter{ + writers: writers + } +} + +// MultiWriter writes to all its writers. +pub struct MultiWriter { +pub mut: + writers []Writer +} + +// write writes to all writers of the MultiWriter. Returns the length of bytes +// written. If any writer fails to write the full length an error is returned +// and writing to other writers stops. If any writer returns an error the error +// is returned immediately and writing to other writers stops. +pub fn (mut m MultiWriter) write(buf []byte) ?int { + for mut w in m.writers { + n := w.write(buf) ? + if n != buf.len { + return error('io: incomplete write to writer of MultiWriter') + } + } + return buf.len +} diff --git a/v_windows/v/vlib/io/multi_writer_test.v b/v_windows/v/vlib/io/multi_writer_test.v new file mode 100644 index 0000000..2e317e8 --- /dev/null +++ b/v_windows/v/vlib/io/multi_writer_test.v @@ -0,0 +1,66 @@ +module io + +fn test_multi_writer_write_successful() { + w0 := TestWriter{} + w1 := TestWriter{} + mut mw := new_multi_writer(w0, w1) + n := mw.write('0123456789'.bytes()) or { + assert false + return + } + assert n == 10 + assert w0.bytes == '0123456789'.bytes() + assert w1.bytes == '0123456789'.bytes() +} + +fn test_multi_writer_write_incomplete() { + w0 := TestWriter{} + w1 := TestIncompleteWriter{} + mut mw := new_multi_writer(w0, w1) + n := mw.write('0123456789'.bytes()) or { + assert w0.bytes == '0123456789'.bytes() + assert w1.bytes == '012345678'.bytes() + return + } + assert false +} + +fn test_multi_writer_write_error() { + w0 := TestWriter{} + w1 := TestErrorWriter{} + w2 := TestWriter{} + mut mw := new_multi_writer(w0, w1, w2) + n := mw.write('0123456789'.bytes()) or { + assert w0.bytes == '0123456789'.bytes() + assert w2.bytes == [] + return + } + assert false +} + +struct TestWriter { +pub mut: + bytes []byte +} + +fn (mut w TestWriter) write(buf []byte) ?int { + w.bytes << buf + return buf.len +} + +struct TestIncompleteWriter { +pub mut: + bytes []byte +} + +fn (mut w TestIncompleteWriter) write(buf []byte) ?int { + b := buf[..buf.len - 1] + w.bytes << b + return b.len +} + +struct TestErrorWriter {} + +fn (mut w TestErrorWriter) write(buf []byte) ?int { + return error('error writer errored') +} diff --git a/v_windows/v/vlib/io/os_file_reader_test.v b/v_windows/v/vlib/io/os_file_reader_test.v new file mode 100644 index 0000000..1ed3a1f --- /dev/null +++ b/v_windows/v/vlib/io/os_file_reader_test.v @@ -0,0 +1,29 @@ +import os +import io + +fn read_file(file string, cap int) []string { + mut lines := []string{} + mut f := os.open(file) or { panic(err) } + defer { + f.close() + } + mut r := io.new_buffered_reader(reader: f, cap: cap) + for { + l := r.read_line() or { break } + lines << l + // println('Line: $l') + } + assert lines.len > 0 + assert r.end_of_stream == true + println('------------------------------------------------ cap: ${cap:6}; read: ${lines.len:3} lines') + return lines +} + +fn test_file_reader() { + for cap := 1; cap <= 10000; cap += 256 { + lines := read_file(@FILE, cap) + assert lines.last() == '// my last line' + } +} + +// my last line diff --git a/v_windows/v/vlib/io/reader.v b/v_windows/v/vlib/io/reader.v new file mode 100644 index 0000000..61f566f --- /dev/null +++ b/v_windows/v/vlib/io/reader.v @@ -0,0 +1,66 @@ +module io + +// Reader represents a stream of data that can be read +pub interface Reader { + // read reads up to buf.len bytes and places + // them into buf. + // A type that implements this should return + // `none` on end of stream (EOF) instead of just returning 0 + read(mut buf []byte) ?int +} + +const ( + read_all_len = 10 * 1024 + read_all_grow_len = 1024 +) + +// ReadAllConfig allows options to be passed for the behaviour +// of read_all +pub struct ReadAllConfig { + reader Reader + read_to_end_of_stream bool +} + +// read_all reads all bytes from a reader until either a 0 length read +// or if read_to_end_of_stream is true then the end of the stream (`none`) +pub fn read_all(config ReadAllConfig) ?[]byte { + r := config.reader + read_till_eof := config.read_to_end_of_stream + + mut b := []byte{len: io.read_all_len} + mut read := 0 + for { + new_read := r.read(mut b[read..]) or { break } + read += new_read + if !read_till_eof && read == 0 { + break + } + if b.len == read { + unsafe { b.grow_len(io.read_all_grow_len) } + } + } + return b[..read] +} + +// read_any reads any available bytes from a reader +// (until the reader returns a read of 0 length) +pub fn read_any(r Reader) ?[]byte { + mut b := []byte{len: io.read_all_len} + mut read := 0 + for { + new_read := r.read(mut b[read..]) or { break } + read += new_read + if new_read == 0 { + break + } + if b.len == read { + unsafe { b.grow_len(io.read_all_grow_len) } + } + } + return b[..read] +} + +// RandomReader represents a stream of data that can be read from at a random location +interface RandomReader { + read_from(pos u64, mut buf []byte) ?int +} diff --git a/v_windows/v/vlib/io/reader_test.v b/v_windows/v/vlib/io/reader_test.v new file mode 100644 index 0000000..c7a2265 --- /dev/null +++ b/v_windows/v/vlib/io/reader_test.v @@ -0,0 +1,130 @@ +module io + +struct Buf { +pub: + bytes []byte +mut: + i int +} + +fn (mut b Buf) read(mut buf []byte) ?int { + if !(b.i < b.bytes.len) { + return none + } + n := copy(buf, b.bytes[b.i..]) + b.i += n + return n +} + +fn test_read_all() { + buf := Buf{ + bytes: '123'.repeat(10).bytes() + } + res := read_all(reader: buf) or { + assert false + ''.bytes() + } + assert res == '123'.repeat(10).bytes() +} + +fn test_read_all_huge() { + buf := Buf{ + bytes: '123'.repeat(100000).bytes() + } + res := read_all(reader: buf) or { + assert false + ''.bytes() + } + assert res == '123'.repeat(100000).bytes() +} + +struct StringReader { + text string +mut: + place int +} + +fn (mut s StringReader) read(mut buf []byte) ?int { + if s.place >= s.text.len { + return none + } + read := copy(buf, s.text[s.place..].bytes()) + s.place += read + return read +} + +const ( + newline_count = 100000 +) + +fn test_stringreader() { + text := '12345\n'.repeat(io.newline_count) + mut s := StringReader{ + text: text + } + mut r := new_buffered_reader(reader: s) + for i := 0; true; i++ { + if _ := r.read_line() { + } else { + assert i == io.newline_count + break + } + } + if _ := r.read_line() { + assert false + } + leftover := read_all(reader: r) or { + assert false + panic('bad') + } + if leftover.len > 0 { + assert false + } +} + +fn test_stringreader2() { + text := '12345\r\n'.repeat(io.newline_count) + mut s := StringReader{ + text: text + } + mut r := new_buffered_reader(reader: s) + for i := 0; true; i++ { + if _ := r.read_line() { + } else { + assert i == io.newline_count + break + } + } + if _ := r.read_line() { + assert false + } + leftover := read_all(reader: r) or { + assert false + panic('bad') + } + if leftover.len > 0 { + assert false + } +} + +fn test_leftover() { + text := 'This is a test\r\nNice try!' + mut s := StringReader{ + text: text + } + mut r := new_buffered_reader(reader: s) + _ := r.read_line() or { + assert false + panic('bad') + } + line2 := r.read_line() or { + assert false + panic('bad') + } + assert line2 == 'Nice try!' + if _ := r.read_line() { + assert false + panic('bad') + } + assert r.end_of_stream() +} diff --git a/v_windows/v/vlib/io/readerwriter.v b/v_windows/v/vlib/io/readerwriter.v new file mode 100644 index 0000000..ba44172 --- /dev/null +++ b/v_windows/v/vlib/io/readerwriter.v @@ -0,0 +1,34 @@ +module io + +// ReaderWriter represents a stream that can be read from and wrote to +pub interface ReaderWriter { + // from Reader + read(mut buf []byte) ?int + // from Writer + write(buf []byte) ?int +} + +// ReaderWriterImpl is a ReaderWriter that can be made from +// a seperate reader and writer (see fn make_readerwriter) +struct ReaderWriterImpl { + r Reader +mut: + w Writer +} + +pub fn (mut r ReaderWriterImpl) read(mut buf []byte) ?int { + return r.r.read(mut buf) +} + +pub fn (mut r ReaderWriterImpl) write(buf []byte) ?int { + return r.w.write(buf) +} + +// make_readerwriter takes a rstream and a wstream and makes +// an rwstream with them +pub fn make_readerwriter(r Reader, w Writer) ReaderWriterImpl { + return ReaderWriterImpl{ + r: r + w: w + } +} diff --git a/v_windows/v/vlib/io/util/util.v b/v_windows/v/vlib/io/util/util.v new file mode 100644 index 0000000..6f0d93f --- /dev/null +++ b/v_windows/v/vlib/io/util/util.v @@ -0,0 +1,104 @@ +module util + +import os +import rand +import rand.seed as rseed + +const ( + retries = 10000 +) + +pub struct TempFileOptions { + path string = os.temp_dir() + pattern string +} + +// temp_file returns an uniquely named, open, writable, `os.File` and it's path +pub fn temp_file(tfo TempFileOptions) ?(os.File, string) { + mut d := tfo.path + if d == '' { + d = os.temp_dir() + } + os.is_writable_folder(d) or { + return error(@FN + + ' could not create temporary file in "$d". Please ensure write permissions.') + } + d = d.trim_right(os.path_separator) + mut rng := rand.new_default() + prefix, suffix := prefix_and_suffix(tfo.pattern) or { return error(@FN + ' ' + err.msg) } + for retry := 0; retry < util.retries; retry++ { + path := os.join_path(d, prefix + random_number(mut rng) + suffix) + mut mode := 'rw+' + $if windows { + mode = 'w+' + } + mut file := os.open_file(path, mode, 0o600) or { + rng.seed(rseed.time_seed_array(2)) + continue + } + if os.exists(path) && os.is_file(path) { + return file, path + } + } + return error(@FN + + ' could not create temporary file in "$d". Retry limit ($util.retries) exhausted. Please ensure write permissions.') +} + +pub struct TempDirOptions { + path string = os.temp_dir() + pattern string +} + +// temp_dir returns an uniquely named, writable, directory path +pub fn temp_dir(tdo TempFileOptions) ?string { + mut d := tdo.path + if d == '' { + d = os.temp_dir() + } + os.is_writable_folder(d) or { + return error(@FN + + ' could not create temporary directory "$d". Please ensure write permissions.') + } + d = d.trim_right(os.path_separator) + mut rng := rand.new_default() + prefix, suffix := prefix_and_suffix(tdo.pattern) or { return error(@FN + ' ' + err.msg) } + for retry := 0; retry < util.retries; retry++ { + path := os.join_path(d, prefix + random_number(mut rng) + suffix) + os.mkdir_all(path) or { + rng.seed(rseed.time_seed_array(2)) + continue + } + if os.is_dir(path) && os.exists(path) { + os.is_writable_folder(path) or { + return error(@FN + + ' could not create temporary directory "$d". Please ensure write permissions.') + } + return path + } + } + return error(@FN + + ' could not create temporary directory "$d". Retry limit ($util.retries) exhausted. Please ensure write permissions.') +} + +// * Utility functions +fn random_number(mut rng rand.PRNG) string { + s := (u32(1e9) + (u32(os.getpid()) + rng.u32() % u32(1e9))).str() + return s.substr(1, s.len) +} + +fn prefix_and_suffix(pattern string) ?(string, string) { + mut pat := pattern + if pat.contains(os.path_separator) { + return error('pattern cannot contain path separators ($os.path_separator).') + } + pos := pat.last_index('*') or { -1 } + mut prefix := '' + mut suffix := '' + if pos != -1 { + prefix = pat.substr(0, pos) + suffix = pat.substr(pos + 1, pat.len) + } else { + prefix = pat + } + return prefix, suffix +} diff --git a/v_windows/v/vlib/io/util/util_test.v b/v_windows/v/vlib/io/util/util_test.v new file mode 100644 index 0000000..18f0164 --- /dev/null +++ b/v_windows/v/vlib/io/util/util_test.v @@ -0,0 +1,127 @@ +import os +import io.util + +const ( + // tfolder will contain all the temporary files/subfolders made by + // the different tests. It would be removed in testsuite_end(), so + // individual os tests do not need to clean up after themselves. + tfolder = os.join_path(os.temp_dir(), 'v', 'tests', 'io_util_test') +) + +fn testsuite_begin() { + eprintln('testsuite_begin, tfolder = $tfolder') + os.rmdir_all(tfolder) or {} + assert !os.is_dir(tfolder) + os.mkdir_all(tfolder) or { panic(err) } + os.chdir(tfolder) or {} + assert os.is_dir(tfolder) +} + +fn testsuite_end() { + os.chdir(os.wd_at_startup) or {} + os.rmdir_all(tfolder) or {} + assert !os.is_dir(tfolder) + // eprintln('testsuite_end , tfolder = $tfolder removed.') +} + +fn test_temp_file() { + // Test defaults + mut f, mut path := util.temp_file() or { + assert false + return + } + mut prev_path := path + defer { + f.close() + } + assert os.is_file(path) + assert f.is_opened + // Test pattern + f.close() + f, path = util.temp_file( + pattern: 'some_*_test.file' + ) or { + assert false + return + } + assert path != prev_path + assert os.is_file(path) + assert f.is_opened + mut filename := os.file_name(path) + assert filename.contains('_test.file') + // Check for 9 digits where the wildcard is placed in the pattern + for i, c in filename { + if i > 4 && i <= 4 + 9 { + assert c.is_digit() + } + } + // Test custom path + prev_path = path + f.close() + f, path = util.temp_file( + path: tfolder + ) or { + assert false + return + } + assert path != prev_path + assert os.is_file(path) + assert path.contains(tfolder) + assert f.is_opened + filename = os.file_name(path) + for c in filename { + assert c.is_digit() + } +} + +fn test_temp_dir() { + // Test defaults + mut path := util.temp_dir() or { + assert false + return + } + assert os.is_dir(path) + mut writable := os.is_writable_folder(path) or { + assert false + return + } + assert writable + mut prev_path := path + // Test pattern + path = util.temp_dir( + pattern: 'some_*_test_dir' + ) or { + assert false + return + } + assert path != prev_path + assert os.is_dir(path) + mut filename := os.file_name(path) + assert filename.contains('_test_dir') + // Check for 9 digits where the wildcard is placed in the pattern + for i, c in filename { + if i > 4 && i <= 4 + 9 { + assert c.is_digit() + } + } + // Test custom path + prev_path = path + path = util.temp_dir( + path: tfolder + ) or { + assert false + return + } + assert path != prev_path + assert os.is_dir(path) + writable = os.is_writable_folder(path) or { + assert false + return + } + assert writable + assert path.contains(tfolder) + filename = os.file_name(path) + for c in filename { + assert c.is_digit() + } +} diff --git a/v_windows/v/vlib/io/writer.v b/v_windows/v/vlib/io/writer.v new file mode 100644 index 0000000..322dd45 --- /dev/null +++ b/v_windows/v/vlib/io/writer.v @@ -0,0 +1,12 @@ +module io + +// Writer represents a stream of data that can be wrote to +pub interface Writer { + write(buf []byte) ?int +} + +// RandomWriter represents a stream of data that can be wrote to +// at a random pos +pub interface RandomWriter { + write_to(pos u64, buf []byte) ?int +} diff --git a/v_windows/v/vlib/json/json_decode_test.v b/v_windows/v/vlib/json/json_decode_test.v new file mode 100644 index 0000000..5b21838 --- /dev/null +++ b/v_windows/v/vlib/json/json_decode_test.v @@ -0,0 +1,46 @@ +import json + +struct TestTwin { + id int + seed string + pubkey string +} + +struct TestTwins { +mut: + twins []TestTwin [required] +} + +fn test_json_decode_fails_to_decode_unrecognised_array_of_dicts() { + data := '[{"twins":[{"id":123,"seed":"abcde","pubkey":"xyzasd"},{"id":456,"seed":"dfgdfgdfgd","pubkey":"skjldskljh45sdf"}]}]' + json.decode(TestTwins, data) or { + assert err.msg == "expected field 'twins' is missing" + return + } + assert false +} + +fn test_json_decode_works_with_a_dict_of_arrays() { + data := '{"twins":[{"id":123,"seed":"abcde","pubkey":"xyzasd"},{"id":456,"seed":"dfgdfgdfgd","pubkey":"skjldskljh45sdf"}]}' + res := json.decode(TestTwins, data) or { + assert false + exit(1) + } + assert res.twins[0].id == 123 + assert res.twins[0].seed == 'abcde' + assert res.twins[0].pubkey == 'xyzasd' + assert res.twins[1].id == 456 + assert res.twins[1].seed == 'dfgdfgdfgd' + assert res.twins[1].pubkey == 'skjldskljh45sdf' +} + +struct Mount { + size u64 +} + +fn test_decode_u64() ? { + data := '{"size": 10737418240}' + m := json.decode(Mount, data) ? + assert m.size == 10737418240 + println(m) +} diff --git a/v_windows/v/vlib/json/json_primitives.v b/v_windows/v/vlib/json/json_primitives.v new file mode 100644 index 0000000..2e635e5 --- /dev/null +++ b/v_windows/v/vlib/json/json_primitives.v @@ -0,0 +1,204 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module json + +#flag -I @VEXEROOT/thirdparty/cJSON +#flag @VEXEROOT/thirdparty/cJSON/cJSON.o +#include "cJSON.h" +#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key)) + +struct C.cJSON { + valueint int + valuedouble f32 + valuestring &char +} + +fn C.cJSON_IsTrue(&C.cJSON) bool + +fn C.cJSON_CreateNumber(int) &C.cJSON + +fn C.cJSON_CreateBool(bool) &C.cJSON + +fn C.cJSON_CreateString(&char) &C.cJSON + +fn C.cJSON_Parse(&char) &C.cJSON + +fn C.cJSON_PrintUnformatted(&C.cJSON) &char + +fn C.cJSON_Print(&C.cJSON) &char + +pub fn decode(typ voidptr, s string) ?voidptr { + // compiler implementation + return 0 +} + +pub fn encode(x voidptr) string { + // compiler implementation + return '' +} + +pub fn encode_pretty(x voidptr) string { + // compiler implementation + return '' +} + +fn decode_int(root &C.cJSON) int { + if isnil(root) { + return 0 + } + return root.valueint +} + +fn decode_i8(root &C.cJSON) i8 { + if isnil(root) { + return i8(0) + } + return i8(root.valueint) +} + +fn decode_i16(root &C.cJSON) i16 { + if isnil(root) { + return i16(0) + } + return i16(root.valueint) +} + +fn decode_i64(root &C.cJSON) i64 { + if isnil(root) { + return i64(0) + } + return i64(root.valuedouble) // i64 is double in C +} + +fn decode_byte(root &C.cJSON) byte { + if isnil(root) { + return byte(0) + } + return byte(root.valueint) +} + +fn decode_u16(root &C.cJSON) u16 { + if isnil(root) { + return u16(0) + } + return u16(root.valueint) +} + +fn decode_u32(root &C.cJSON) u32 { + if isnil(root) { + return u32(0) + } + return u32(root.valueint) +} + +fn decode_u64(root &C.cJSON) u64 { + if isnil(root) { + return u64(0) + } + return u64(root.valuedouble) +} + +fn decode_f32(root &C.cJSON) f32 { + if isnil(root) { + return f32(0) + } + return root.valuedouble +} + +fn decode_f64(root &C.cJSON) f64 { + if isnil(root) { + return f64(0) + } + return f64(root.valuedouble) +} + +fn decode_string(root &C.cJSON) string { + if isnil(root) { + return '' + } + if isnil(root.valuestring) { + return '' + } + // println('decode string valuestring="$root.valuestring"') + // return tos(root.valuestring, _strlen(root.valuestring)) + return unsafe { tos_clone(&byte(root.valuestring)) } // , _strlen(root.valuestring)) +} + +fn decode_bool(root &C.cJSON) bool { + if isnil(root) { + return false + } + return C.cJSON_IsTrue(root) +} + +// /////////////////// +fn encode_int(val int) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_i8(val i8) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_i16(val i16) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_i64(val i64) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_byte(val byte) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_u16(val u16) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_u32(val u32) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_u64(val u64) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_f32(val f32) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_f64(val f64) &C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn encode_bool(val bool) &C.cJSON { + return C.cJSON_CreateBool(val) +} + +fn encode_string(val string) &C.cJSON { + return C.cJSON_CreateString(&char(val.str)) +} + +// /////////////////////// +// user := decode_User(json_parse(js_string_var)) +fn json_parse(s string) &C.cJSON { + return C.cJSON_Parse(&char(s.str)) +} + +// json_string := json_print(encode_User(user)) +fn json_print(json &C.cJSON) string { + s := C.cJSON_PrintUnformatted(json) + return unsafe { tos(&byte(s), C.strlen(&char(s))) } +} + +fn json_print_pretty(json &C.cJSON) string { + s := C.cJSON_Print(json) + return unsafe { tos(&byte(s), C.strlen(&char(s))) } +} + +// / cjson wrappers +// fn json_array_for_each(val, root &C.cJSON) { +// #cJSON_ArrayForEach (val ,root) +// } diff --git a/v_windows/v/vlib/json/json_test.v b/v_windows/v/vlib/json/json_test.v new file mode 100644 index 0000000..ca6b2ac --- /dev/null +++ b/v_windows/v/vlib/json/json_test.v @@ -0,0 +1,368 @@ +import json +import time + +enum JobTitle { + manager + executive + worker +} + +struct Employee { + name string + age int + salary f32 + title JobTitle +} + +fn test_simple() ? { + x := Employee{'Peter', 28, 95000.5, .worker} + s := json.encode(x) + eprintln('Employee x: $s') + assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2}' + y := json.decode(Employee, s) ? + eprintln('Employee y: $y') + assert y.name == 'Peter' + assert y.age == 28 + assert y.salary == 95000.5 + assert y.title == .worker +} + +fn test_decode_top_level_array() { + s := '[{"name":"Peter", "age": 29}, {"name":"Bob", "age":31}]' + x := json.decode([]Employee, s) or { panic(err) } + assert x.len == 2 + assert x[0].name == 'Peter' + assert x[0].age == 29 + assert x[1].name == 'Bob' + assert x[1].age == 31 +} + +fn bar(payload string) ?Bar { // ?T doesn't work currently + result := json.decode(T, payload) ? + return result +} + +struct Bar { + x string +} + +fn test_generic() { + result := bar('{"x":"test"}') or { Bar{} } + assert result.x == 'test' +} + +struct User2 { + age int + nums []int + reg_date time.Time +} + +struct User { + age int + nums []int + last_name string [json: lastName] + is_registered bool [json: IsRegistered] + typ int [json: 'type'] + pets string [json: 'pet_animals'; raw] +} + +fn test_parse_user() ? { + s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}' + u2 := json.decode(User2, s) ? + println(u2) + u := json.decode(User, s) ? + println(u) + assert u.age == 10 + assert u.last_name == 'Johnson' + assert u.is_registered == true + assert u.nums.len == 3 + assert u.nums[0] == 1 + assert u.nums[1] == 2 + assert u.nums[2] == 3 + assert u.typ == 1 + assert u.pets == '{"name":"Bob","animal":"Dog"}' +} + +fn test_encode_decode_time() ? { + user := User2{ + age: 25 + reg_date: time.new_time(year: 2020, month: 12, day: 22, hour: 7, minute: 23) + } + s := json.encode(user) + println(s) + assert s.contains('"reg_date":1608621780') + user2 := json.decode(User2, s) ? + assert user2.reg_date.str() == '2020-12-22 07:23:00' + println(user2) + println(user2.reg_date) +} + +fn (mut u User) foo() string { + return json.encode(u) +} + +fn test_encode_user() { + mut usr := User{ + age: 10 + nums: [1, 2, 3] + last_name: 'Johnson' + is_registered: true + typ: 0 + pets: 'foo' + } + expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}' + out := json.encode(usr) + println(out) + assert out == expected + // Test json.encode on mutable pointers + assert usr.foo() == expected +} + +struct Color { + space string + point string [raw] +} + +fn test_raw_json_field() { + color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { + println('text') + return + } + assert color.point == '{"Y":123}' + assert color.space == 'YCbCr' +} + +fn test_bad_raw_json_field() { + color := json.decode(Color, '{"space": "YCbCr"}') or { + println('text') + return + } + assert color.point == '' + assert color.space == 'YCbCr' +} + +struct City { + name string +} + +struct Country { + cities []City + name string +} + +fn test_struct_in_struct() ? { + country := json.decode(Country, '{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}') ? + assert country.name == 'UK' + assert country.cities.len == 2 + assert country.cities[0].name == 'London' + assert country.cities[1].name == 'Manchester' + println(country.cities) +} + +fn test_encode_map() { + expected := '{"one":1,"two":2,"three":3,"four":4}' + numbers := { + 'one': 1 + 'two': 2 + 'three': 3 + 'four': 4 + } + out := json.encode(numbers) + println(out) + assert out == expected +} + +fn test_parse_map() ? { + expected := { + 'one': 1 + 'two': 2 + 'three': 3 + 'four': 4 + } + out := json.decode(map[string]int, '{"one":1,"two":2,"three":3,"four":4}') ? + println(out) + assert out == expected +} + +struct Data { + countries []Country + users map[string]User + extra map[string]map[string]int +} + +fn test_nested_type() ? { + data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' + data := Data{ + countries: [ + Country{ + name: 'UK' + cities: [City{'London'}, City{'Manchester'}] + }, + Country{ + name: 'KU' + cities: [City{'Donlon'}, City{'Termanches'}] + }, + ] + users: { + 'Foo': User{ + age: 10 + nums: [1, 2, 3] + last_name: 'Johnson' + is_registered: true + typ: 0 + pets: 'little foo' + } + 'Boo': User{ + age: 20 + nums: [5, 3, 1] + last_name: 'Smith' + is_registered: false + typ: 4 + pets: 'little boo' + } + } + extra: { + '2': { + 'n1': 2 + 'n2': 4 + 'n3': 8 + 'n4': 16 + } + '3': { + 'n1': 3 + 'n2': 9 + 'n3': 27 + 'n4': 81 + } + } + } + out := json.encode(data) + println(out) + assert out == data_expected + data2 := json.decode(Data, data_expected) ? + assert data2.countries.len == data.countries.len + for i in 0 .. 1 { + assert data2.countries[i].name == data.countries[i].name + assert data2.countries[i].cities.len == data.countries[i].cities.len + for j in 0 .. 1 { + assert data2.countries[i].cities[j].name == data.countries[i].cities[j].name + } + } + for key, user in data.users { + assert data2.users[key].age == user.age + assert data2.users[key].nums == user.nums + assert data2.users[key].last_name == user.last_name + assert data2.users[key].is_registered == user.is_registered + assert data2.users[key].typ == user.typ + // assert data2.users[key].pets == user.pets // TODO FIX + } + for k, v in data.extra { + for k2, v2 in v { + assert data2.extra[k][k2] == v2 + } + } +} + +struct Foo { +pub: + name string + data T +} + +fn test_generic_struct() ? { + foo_int := Foo{'bar', 12} + foo_enc := json.encode(foo_int) + assert foo_enc == '{"name":"bar","data":12}' + foo_dec := json.decode(Foo, foo_enc) ? + assert foo_dec.name == 'bar' + assert foo_dec.data == 12 +} + +fn test_errors() { + invalid_array := fn () { + data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":{"name":"Donlon"},"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' + json.decode(Data, data) or { + println(err) + assert err.msg.starts_with('Json element is not an array:') + return + } + assert false + } + invalid_object := fn () { + data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":[{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}],"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' + json.decode(Data, data) or { + println(err) + assert err.msg.starts_with('Json element is not an object:') + return + } + assert false + } + invalid_array() + invalid_object() +} + +type ID = string + +struct Message { + id ID +} + +fn test_decode_alias_struct() ? { + msg := json.decode(Message, '{"id": "118499178790780929"}') ? + // hacky way of comparing aliased strings + assert msg.id.str() == '118499178790780929' +} + +fn test_encode_alias_struct() { + expected := '{"id":"118499178790780929"}' + msg := Message{'118499178790780929'} + out := json.encode(msg) + assert out == expected +} + +struct List { + id int + items []string +} + +fn test_list() ? { + list := json.decode(List, '{"id": 1, "items": ["1", "2"]}') ? + assert list.id == 1 + assert list.items == ['1', '2'] +} + +fn test_list_no_id() ? { + list := json.decode(List, '{"items": ["1", "2"]}') ? + assert list.id == 0 + assert list.items == ['1', '2'] +} + +fn test_list_no_items() ? { + list := json.decode(List, '{"id": 1}') ? + assert list.id == 1 + assert list.items == [] +} + +struct Info { + id int + items []string + maps map[string]string +} + +fn test_decode_null_object() ? { + info := json.decode(Info, '{"id": 22, "items": null, "maps": null}') ? + assert info.id == 22 + assert '$info.items' == '[]' + assert '$info.maps' == '{}' +} + +struct Foo2 { + name string +} + +fn test_pretty() { + foo := Foo2{'Bob'} + assert json.encode_pretty(foo) == '{ + "name": "Bob" +}' +} diff --git a/v_windows/v/vlib/log/log.v b/v_windows/v/vlib/log/log.v new file mode 100644 index 0000000..a6259a9 --- /dev/null +++ b/v_windows/v/vlib/log/log.v @@ -0,0 +1,185 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module log + +import os +import time +import term + +// Level defines possible log levels used by `Log` +pub enum Level { + fatal = 1 + error + warn + info + debug +} + +pub enum LogTarget { + console + file + both +} + +// tag returns the tag for log level `l` as a string. +fn tag_to_cli(l Level) string { + return match l { + .fatal { term.red('FATAL') } + .error { term.red('ERROR') } + .warn { term.yellow('WARN ') } + .info { term.white('INFO ') } + .debug { term.blue('DEBUG') } + } +} + +// tag returns the tag for log level `l` as a string. +fn tag_to_file(l Level) string { + return match l { + .fatal { 'FATAL' } + .error { 'ERROR' } + .warn { 'WARN ' } + .info { 'INFO ' } + .debug { 'DEBUG' } + } +} + +interface Logger { + fatal(s string) + error(s string) + warn(s string) + info(s string) + debug(s string) +} + +// Log represents a logging object +pub struct Log { +mut: + level Level + output_label string + ofile os.File + output_target LogTarget // if true output to file else use stdout/stderr. +pub mut: + output_file_name string // log output to this file +} + +// set_level sets the internal logging to `level`. +pub fn (mut l Log) set_level(level Level) { + l.level = level +} + +// set_output_level sets the internal logging output to `level`. +pub fn (mut l Log) set_output_level(level Level) { + l.level = level +} + +// set_full_logpath sets the output label and output path from `full_log_path`. +pub fn (mut l Log) set_full_logpath(full_log_path string) { + rlog_file := os.real_path(full_log_path) + l.set_output_label(os.file_name(rlog_file)) + l.set_output_path(os.dir(rlog_file)) +} + +// set_output_label sets the `label` for the output. +pub fn (mut l Log) set_output_label(label string) { + l.output_label = label +} + +// set_output_path sets the file to which output is logged to. +pub fn (mut l Log) set_output_path(output_file_path string) { + if l.ofile.is_opened { + l.ofile.close() + } + l.output_target = .file + l.output_file_name = os.join_path(os.real_path(output_file_path), l.output_label) + ofile := os.open_append(l.output_file_name) or { + panic('error while opening log file $l.output_file_name for appending') + } + l.ofile = ofile +} + +// log_to_console_too turns on logging to the console too, in addition to logging to a file. +// You have to call it *after* calling .set_output_path(output_file_path). +pub fn (mut l Log) log_to_console_too() { + if l.output_target != .file { + panic('log_to_console_too should be called *after* .set_output_path') + } + l.output_target = .both +} + +// flush writes the log file content to disk. +pub fn (mut l Log) flush() { + l.ofile.flush() +} + +// close closes the log file. +pub fn (mut l Log) close() { + l.ofile.close() +} + +// log_file writes log line `s` with `level` to the log file. +fn (mut l Log) log_file(s string, level Level) { + timestamp := time.now().format_ss() + e := tag_to_file(level) + l.ofile.writeln('$timestamp [$e] $s') or { panic(err) } +} + +// log_cli writes log line `s` with `level` to stdout. +fn (l &Log) log_cli(s string, level Level) { + timestamp := time.now().format_ss() + e := tag_to_cli(level) + println('$timestamp [$e] $s') +} + +// send_output writes log line `s` with `level` to either the log file or the console +// according to the value of the `.output_target` field. +pub fn (mut l Log) send_output(s &string, level Level) { + if l.output_target == .file || l.output_target == .both { + l.log_file(s, level) + } + if l.output_target == .console || l.output_target == .both { + l.log_cli(s, level) + } +} + +// fatal logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.fatal` category. +pub fn (mut l Log) fatal(s string) { + if int(l.level) < int(Level.fatal) { + return + } + l.send_output(s, .fatal) + l.ofile.close() + panic('$l.output_label: $s') +} + +// error logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.error` category. +pub fn (mut l Log) error(s string) { + if int(l.level) < int(Level.error) { + return + } + l.send_output(s, .error) +} + +// warn logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.warn` category. +pub fn (mut l Log) warn(s string) { + if int(l.level) < int(Level.warn) { + return + } + l.send_output(s, .warn) +} + +// info logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.info` category. +pub fn (mut l Log) info(s string) { + if int(l.level) < int(Level.info) { + return + } + l.send_output(s, .info) +} + +// debug logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.debug` category. +pub fn (mut l Log) debug(s string) { + if int(l.level) < int(Level.debug) { + return + } + l.send_output(s, .debug) +} diff --git a/v_windows/v/vlib/math/ROADMAP.md b/v_windows/v/vlib/math/ROADMAP.md new file mode 100644 index 0000000..8564244 --- /dev/null +++ b/v_windows/v/vlib/math/ROADMAP.md @@ -0,0 +1,4 @@ +- [x] Move `vsl/vmath` to `vlib/math` as default backend +- [ ] Implement `log` in pure V +- [ ] Implement `pow` in pure V +- [ ] Define functions for initial release of hardware implementations diff --git a/v_windows/v/vlib/math/abs.c.v b/v_windows/v/vlib/math/abs.c.v new file mode 100644 index 0000000..983ddcb --- /dev/null +++ b/v_windows/v/vlib/math/abs.c.v @@ -0,0 +1,8 @@ +module math + +fn C.fabs(x f64) f64 + +[inline] +pub fn abs(a f64) f64 { + return C.fabs(a) +} diff --git a/v_windows/v/vlib/math/abs.js.v b/v_windows/v/vlib/math/abs.js.v new file mode 100644 index 0000000..abf90b0 --- /dev/null +++ b/v_windows/v/vlib/math/abs.js.v @@ -0,0 +1,9 @@ +module math + +fn JS.Math.abs(x f64) f64 + +// Returns the absolute value. +[inline] +pub fn abs(a f64) f64 { + return JS.Math.abs(a) +} diff --git a/v_windows/v/vlib/math/abs.v b/v_windows/v/vlib/math/abs.v new file mode 100644 index 0000000..55bcff2 --- /dev/null +++ b/v_windows/v/vlib/math/abs.v @@ -0,0 +1,18 @@ +module math + +// Returns the absolute value. +[inline] +pub fn abs(x f64) f64 { + if x > 0.0 { + return x + } + return -x +} + +[inline] +pub fn fabs(x f64) f64 { + if x > 0.0 { + return x + } + return -x +} diff --git a/v_windows/v/vlib/math/big/big.c.v b/v_windows/v/vlib/math/big/big.c.v new file mode 100644 index 0000000..7ef0534 --- /dev/null +++ b/v_windows/v/vlib/math/big/big.c.v @@ -0,0 +1,344 @@ +module big + +// Wrapper for https://github.com/kokke/tiny-bignum-c +#flag -I @VEXEROOT/thirdparty/bignum +#flag @VEXEROOT/thirdparty/bignum/bn.o +#include "bn.h" + +struct C.bn { +mut: + array [32]u32 +} + +// Big unsigned integer number. +type Number = C.bn + +fn C.bignum_init(n &Number) + +fn C.bignum_from_int(n &Number, i u64) + +fn C.bignum_to_int(n &Number) int + +fn C.bignum_from_string(n &Number, s &char, nbytes int) + +fn C.bignum_to_string(n &Number, s &char, maxsize int) + +// c = a + b +fn C.bignum_add(a &Number, b &Number, c &Number) + +// c = a - b +fn C.bignum_sub(a &Number, b &Number, c &Number) + +// c = a * b +fn C.bignum_mul(a &Number, b &Number, c &Number) + +// c = a / b +fn C.bignum_div(a &Number, b &Number, c &Number) + +// c = a % b +fn C.bignum_mod(a &Number, b &Number, c &Number) + +// c = a/b d=a%b +fn C.bignum_divmod(a &Number, b &Number, c &Number, d &Number) + +// c = a & b +fn C.bignum_and(a &Number, b &Number, c &Number) + +// c = a | b +fn C.bignum_or(a &Number, b &Number, c &Number) + +// c = a xor b +fn C.bignum_xor(a &Number, b &Number, c &Number) + +// b = a << nbits +fn C.bignum_lshift(a &Number, b &Number, nbits int) + +// b = a >> nbits +fn C.bignum_rshift(a &Number, b &Number, nbits int) + +fn C.bignum_cmp(a &Number, b &Number) int + +fn C.bignum_is_zero(a &Number) int + +// n++ +fn C.bignum_inc(n &Number) + +// n-- +fn C.bignum_dec(n &Number) + +// c = a ^ b +fn C.bignum_pow(a &Number, b &Number, c &Number) + +// b = integer_square_root_of(a) +fn C.bignum_isqrt(a &Number, b &Number) + +// copy src number to dst number +fn C.bignum_assign(dst &Number, src &Number) + +// new returns a bignum, initialized to 0 +pub fn new() Number { + return Number{} +} + +// conversion actions to/from big numbers: +// from_int converts an ordinary int number `i` to big.Number +pub fn from_int(i int) Number { + n := Number{} + C.bignum_from_int(&n, i) + return n +} + +// from_u64 converts an ordinary u64 number `u` to big.Number +pub fn from_u64(u u64) Number { + n := Number{} + C.bignum_from_int(&n, u) + return n +} + +// from_hex_string converts a hex string to big.Number +pub fn from_hex_string(input string) Number { + mut s := input.trim_prefix('0x') + if s.len == 0 { + s = '0' + } + padding := '0'.repeat((8 - s.len % 8) % 8) + s = padding + s + n := Number{} + C.bignum_from_string(&n, &char(s.str), s.len) + return n +} + +// from_string converts a decimal string to big.Number +pub fn from_string(input string) Number { + mut n := from_int(0) + for _, c in input { + d := from_int(int(c - `0`)) + n = (n * big.ten) + d + } + return n +} + +// from_bytes converts an array of bytes (little-endian) to a big.Number. +// Higher precedence bytes are expected at lower indices in the array. +pub fn from_bytes(input []byte) ?Number { + if input.len > 128 { + return error('input array too large. big.Number can only hold up to 1024 bit numbers') + } + // pad input + mut padded_input := []byte{len: ((input.len + 3) & ~0x3) - input.len, cap: (input.len + 3) & ~0x3, init: 0x0} + padded_input << input + // combine every 4 bytes into a u32 and insert into n.array + mut n := Number{} + for i := 0; i < padded_input.len; i += 4 { + x3 := u32(padded_input[i]) + x2 := u32(padded_input[i + 1]) + x1 := u32(padded_input[i + 2]) + x0 := u32(padded_input[i + 3]) + val := (x3 << 24) | (x2 << 16) | (x1 << 8) | x0 + n.array[(padded_input.len - i) / 4 - 1] = val + } + return n +} + +// .int() converts (a small) big.Number `n` to an ordinary integer. +pub fn (n &Number) int() int { + r := C.bignum_to_int(n) + return r +} + +const ( + ten = from_int(10) +) + +// .str returns a decimal representation of the big unsigned integer number n. +pub fn (n &Number) str() string { + if n.is_zero() { + return '0' + } + mut digits := []byte{} + mut x := n.clone() + + for !x.is_zero() { + // changes to reflect new api + div, mod := divmod(&x, &big.ten) + digits << byte(mod.int()) + `0` + x = div + } + return digits.reverse().bytestr() +} + +// .hexstr returns a hexadecimal representation of the bignum `n` +pub fn (n &Number) hexstr() string { + mut buf := [8192]byte{} + mut s := '' + unsafe { + bp := &buf[0] + // NB: C.bignum_to_string(), returns the HEXADECIMAL representation of the bignum n + C.bignum_to_string(n, &char(bp), 8192) + s = tos_clone(bp) + } + if s.len == 0 { + return '0' + } + return s +} + +// ////////////////////////////////////////////////////////// +// overloaded ops for the numbers: +pub fn (a &Number) + (b &Number) Number { + c := Number{} + C.bignum_add(a, b, &c) + return c +} + +pub fn (a &Number) - (b &Number) Number { + c := Number{} + C.bignum_sub(a, b, &c) + return c +} + +pub fn (a &Number) * (b &Number) Number { + c := Number{} + C.bignum_mul(a, b, &c) + return c +} + +pub fn (a &Number) / (b &Number) Number { + c := Number{} + C.bignum_div(a, b, &c) + return c +} + +pub fn (a &Number) % (b &Number) Number { + c := Number{} + C.bignum_mod(a, b, &c) + return c +} + +// divmod returns a pair of quotient and remainder from div modulo operation +// between two bignums `a` and `b` +pub fn divmod(a &Number, b &Number) (Number, Number) { + c := Number{} + d := Number{} + C.bignum_divmod(a, b, &c, &d) + return c, d +} + +// ////////////////////////////////////////////////////////// +pub fn cmp(a &Number, b &Number) int { + return C.bignum_cmp(a, b) +} + +pub fn (a &Number) is_zero() bool { + return C.bignum_is_zero(a) != 0 +} + +pub fn (mut a Number) inc() { + C.bignum_inc(&a) +} + +pub fn (mut a Number) dec() { + C.bignum_dec(&a) +} + +pub fn pow(a &Number, b &Number) Number { + c := Number{} + C.bignum_pow(a, b, &c) + return c +} + +pub fn (a &Number) isqrt() Number { + b := Number{} + C.bignum_isqrt(a, &b) + return b +} + +// ////////////////////////////////////////////////////////// +pub fn b_and(a &Number, b &Number) Number { + c := Number{} + C.bignum_and(a, b, &c) + return c +} + +pub fn b_or(a &Number, b &Number) Number { + c := Number{} + C.bignum_or(a, b, &c) + return c +} + +pub fn b_xor(a &Number, b &Number) Number { + c := Number{} + C.bignum_xor(a, b, &c) + return c +} + +pub fn (a &Number) lshift(nbits int) Number { + b := Number{} + C.bignum_lshift(a, &b, nbits) + return b +} + +pub fn (a &Number) rshift(nbits int) Number { + b := Number{} + C.bignum_rshift(a, &b, nbits) + return b +} + +pub fn (a &Number) clone() Number { + b := Number{} + C.bignum_assign(&b, a) + return b +} + +// ////////////////////////////////////////////////////////// +pub fn factorial(nn &Number) Number { + mut n := nn.clone() + mut a := nn.clone() + n.dec() + mut i := 1 + for !n.is_zero() { + res := a * n + n.dec() + a = res + i++ + } + return a +} + +pub fn fact(n int) Number { + return factorial(from_int(n)) +} + +// bytes returns an array of the bytes for the number `n`, +// in little endian format, where .bytes()[0] is the least +// significant byte. The result is NOT trimmed, and will contain 0s, even +// after the significant bytes. +// This method is faster than .bytes_trimmed(), but may be less convenient. +// Example: assert big.from_int(1).bytes()[0] == byte(0x01) +// Example: assert big.from_int(1024).bytes()[1] == byte(0x04) +// Example: assert big.from_int(1048576).bytes()[2] == byte(0x10) +pub fn (n &Number) bytes() []byte { + mut res := []byte{len: 128, init: 0} + unsafe { C.memcpy(res.data, n, 128) } + return res +} + +// bytes_trimmed returns an array of the bytes for the number `n`, +// in little endian format, where .bytes_trimmed()[0] is the least +// significant byte. The result is trimmed, so that *the last* byte +// of the result is also the the last meaningfull byte, != 0 . +// Example: assert big.from_int(1).bytes_trimmed() == [byte(0x01)] +// Example: assert big.from_int(1024).bytes_trimmed() == [byte(0x00), 0x04] +// Example: assert big.from_int(1048576).bytes_trimmed() == [byte(0x00), 0x00, 0x10] +pub fn (n &Number) bytes_trimmed() []byte { + mut res := []byte{len: 128, init: 0} + unsafe { C.memcpy(res.data, n, 128) } + mut non_zero_idx := 127 + for ; non_zero_idx >= 0; non_zero_idx-- { + if res[non_zero_idx] != 0 { + break + } + } + res.trim(non_zero_idx + 1) + return res +} diff --git a/v_windows/v/vlib/math/big/big.js.v b/v_windows/v/vlib/math/big/big.js.v new file mode 100644 index 0000000..0ed469c --- /dev/null +++ b/v_windows/v/vlib/math/big/big.js.v @@ -0,0 +1,198 @@ +module big + +struct JS.BigInt {} + +#const jsNumber = Number; + +pub struct Number { +} + +pub fn new() Number { + return Number{} +} + +pub fn from_int(i int) Number { + n := Number{} + #n.value = BigInt(+i) + + return n +} + +pub fn from_u64(u u64) Number { + n := Number{} + #n.value = BigInt(u.val) + + return n +} + +pub fn from_hex_string(input string) Number { + n := Number{} + #n.value = BigInt(input.val) + + return n +} + +pub fn from_string(input string) Number { + n := Number{} + #n.value = BigInt(input.val) + + return n +} + +pub fn (n &Number) int() int { + r := 0 + #r.val = jsNumber(n.val.value) + + return r +} + +pub fn (n &Number) str() string { + s := '' + #s.str = n.val.value + "" + + return s +} + +pub fn (a &Number) + (b &Number) Number { + c := Number{} + #c.value = a.val.value + b.val.value + + return c +} + +pub fn (a &Number) - (b &Number) Number { + c := Number{} + #c.value = a.val.value - b.val.value + + return c +} + +pub fn (a &Number) / (b &Number) Number { + c := Number{} + #c.value = a.val.value / b.val.value + + return c +} + +pub fn (a &Number) * (b &Number) Number { + c := Number{} + #c.value = a.val.value * b.val.value + + return c +} + +/* +pub fn (a &Number) % (b &Number) Number { + c := Number{} + # c.value = a.val.value % b.val.value + return c +}*/ + +pub fn divmod(a &Number, b &Number) (Number, Number) { + c := Number{} + d := Number{} + #c.value = a.val.value / b.val.value + #d.value = a.val.value % b.val.value + + return c, d +} + +pub fn cmp(a &Number, b &Number) int { + res := 0 + + #if (a.val.value < b.val.value) res.val = -1 + #else if (a.val.value > b.val.value) res.val = 1 + #else res.val = 0 + + return res +} + +pub fn (a &Number) is_zero() bool { + res := false + #res.val = a.val.value == BigInt(0) + + return res +} + +pub fn (mut a Number) inc() { + #a.val.value = a.val.value + BigInt(1) +} + +pub fn (mut a Number) dec() { + #a.val.value = a.val.value - BigInt(1) +} + +pub fn (a &Number) isqrt() Number { + b := Number{} + #let x0 = a.val.value >> 1n + #if (x0) { + #let x1 = (x0 + a.val.value / x0) >> 1n + #while (x1 < x0) { + #x0 = x1 + #x1 = (x0 + a.val.value / x0) >> 1n + #} + #b.value = x0 + #} else { b.value = a.val.value; } + + return b +} + +pub fn b_and(a &Number, b &Number) Number { + c := Number{} + #c.value = a.val.value & b.val.value + + return c +} + +pub fn b_or(a &Number, b &Number) Number { + c := Number{} + #c.value = a.val.value | b.val.value + + return c +} + +pub fn b_xor(a &Number, b &Number) Number { + c := Number{} + #c.value = a.val.value ^ b.val.value + + return c +} + +pub fn (a &Number) lshift(nbits int) Number { + c := Number{} + #c.value = a.val.value << BigInt(+nbits) + + return c +} + +pub fn (a &Number) rshift(nbits int) Number { + c := Number{} + #c.value = a.val.value << BigInt(+nbits) + + return c +} + +pub fn (a &Number) clone() Number { + b := Number{} + #b.value = a.val.value + + return b +} + +pub fn factorial(nn &Number) Number { + mut n := nn.clone() + mut a := nn.clone() + n.dec() + mut i := 1 + for !n.is_zero() { + res := a * n + n.dec() + a = res + i++ + } + return a +} + +pub fn fact(n int) Number { + return factorial(from_int(n)) +} diff --git a/v_windows/v/vlib/math/big/big_test.v b/v_windows/v/vlib/math/big/big_test.v new file mode 100644 index 0000000..9da5845 --- /dev/null +++ b/v_windows/v/vlib/math/big/big_test.v @@ -0,0 +1,181 @@ +import math.big + +fn test_new_big() { + n := big.new() + assert sizeof(big.Number) == 128 + assert n.hexstr() == '0' +} + +fn test_from_int() { + assert big.from_int(255).hexstr() == 'ff' + assert big.from_int(127).hexstr() == '7f' + assert big.from_int(1024).hexstr() == '400' + assert big.from_int(2147483647).hexstr() == '7fffffff' + assert big.from_int(-1).hexstr() == 'ffffffffffffffff' +} + +fn test_from_u64() { + assert big.from_u64(255).hexstr() == 'ff' + assert big.from_u64(127).hexstr() == '7f' + assert big.from_u64(1024).hexstr() == '400' + assert big.from_u64(4294967295).hexstr() == 'ffffffff' + assert big.from_u64(4398046511104).hexstr() == '40000000000' + assert big.from_u64(-1).hexstr() == 'ffffffffffffffff' +} + +fn test_plus() { + mut a := big.from_u64(2) + b := big.from_u64(3) + c := a + b + assert c.hexstr() == '5' + assert (big.from_u64(1024) + big.from_u64(1024)).hexstr() == '800' + a += b + assert a.hexstr() == '5' + a.inc() + assert a.hexstr() == '6' + a.dec() + a.dec() + assert a.hexstr() == '4' +} + +fn test_minus() { + a := big.from_u64(2) + mut b := big.from_u64(3) + c := b - a + assert c.hexstr() == '1' + e := big.from_u64(1024) + ee := e - e + assert ee.hexstr() == '0' + b -= a + assert b.hexstr() == '1' +} + +fn test_divide() { + a := big.from_u64(2) + mut b := big.from_u64(3) + c := b / a + assert c.hexstr() == '1' + assert (b % a).hexstr() == '1' + e := big.from_u64(1024) // dec(1024) == hex(0x400) + ee := e / e + assert ee.hexstr() == '1' + assert (e / a).hexstr() == '200' + assert (e / (a * a)).hexstr() == '100' + b /= a + assert b.hexstr() == '1' +} + +fn test_multiply() { + mut a := big.from_u64(2) + b := big.from_u64(3) + c := b * a + assert c.hexstr() == '6' + e := big.from_u64(1024) + e2 := e * e + e4 := e2 * e2 + e8 := e2 * e2 * e2 * e2 + e9 := e8 + big.from_u64(1) + d := ((e9 * e9) + b) * c + assert e4.hexstr() == '10000000000' + assert e8.hexstr() == '100000000000000000000' + assert e9.hexstr() == '100000000000000000001' + assert d.hexstr() == '60000000000000000000c00000000000000000018' + a *= b + assert a.hexstr() == '6' +} + +fn test_mod() { + assert (big.from_u64(13) % big.from_u64(10)).int() == 3 + assert (big.from_u64(13) % big.from_u64(9)).int() == 4 + assert (big.from_u64(7) % big.from_u64(5)).int() == 2 +} + +fn test_divmod() { + x, y := big.divmod(big.from_u64(13), big.from_u64(10)) + assert x.int() == 1 + assert y.int() == 3 + p, q := big.divmod(big.from_u64(13), big.from_u64(9)) + assert p.int() == 1 + assert q.int() == 4 + c, d := big.divmod(big.from_u64(7), big.from_u64(5)) + assert c.int() == 1 + assert d.int() == 2 +} + +fn test_from_str() { + assert big.from_string('9870123').str() == '9870123' + assert big.from_string('').str() == '0' + assert big.from_string('0').str() == '0' + assert big.from_string('1').str() == '1' + for i := 1; i < 307; i += 61 { + input := '9'.repeat(i) + out := big.from_string(input).str() + // eprintln('>> i: $i input: $input.str()') + // eprintln('>> i: $i out: $out.str()') + assert input == out + } +} + +fn test_from_hex_str() { + assert big.from_hex_string('0x123').hexstr() == '123' + for i in 1 .. 33 { + input := 'e'.repeat(i) + out := big.from_hex_string(input).hexstr() + assert input == out + } + assert big.from_string('0').hexstr() == '0' +} + +fn test_str() { + assert big.from_u64(255).str() == '255' + assert big.from_u64(127).str() == '127' + assert big.from_u64(1024).str() == '1024' + assert big.from_u64(4294967295).str() == '4294967295' + assert big.from_u64(4398046511104).str() == '4398046511104' + assert big.from_int(int(4294967295)).str() == '18446744073709551615' + assert big.from_int(-1).str() == '18446744073709551615' + assert big.from_hex_string('e'.repeat(80)).str() == '1993587900192849410235353592424915306962524220866209251950572167300738410728597846688097947807470' +} + +fn test_factorial() { + f5 := big.factorial(big.from_u64(5)) + assert f5.hexstr() == '78' + f100 := big.factorial(big.from_u64(100)) + assert f100.hexstr() == '1b30964ec395dc24069528d54bbda40d16e966ef9a70eb21b5b2943a321cdf10391745570cca9420c6ecb3b72ed2ee8b02ea2735c61a000000000000000000000000' +} + +fn trimbytes(n int, x []byte) []byte { + mut res := x.clone() + res.trim(n) + return res +} + +fn test_bytes() { + assert big.from_int(0).bytes().len == 128 + assert big.from_hex_string('e'.repeat(100)).bytes().len == 128 + assert trimbytes(3, big.from_int(1).bytes()) == [byte(0x01), 0x00, 0x00] + assert trimbytes(3, big.from_int(1024).bytes()) == [byte(0x00), 0x04, 0x00] + assert trimbytes(3, big.from_int(1048576).bytes()) == [byte(0x00), 0x00, 0x10] +} + +fn test_bytes_trimmed() { + assert big.from_int(0).bytes_trimmed().len == 0 + assert big.from_hex_string('AB'.repeat(50)).bytes_trimmed().len == 50 + assert big.from_int(1).bytes_trimmed() == [byte(0x01)] + assert big.from_int(1024).bytes_trimmed() == [byte(0x00), 0x04] + assert big.from_int(1048576).bytes_trimmed() == [byte(0x00), 0x00, 0x10] +} + +fn test_from_bytes() ? { + assert big.from_bytes([]) ?.hexstr() == '0' + assert big.from_bytes([byte(0x13)]) ?.hexstr() == '13' + assert big.from_bytes([byte(0x13), 0x37]) ?.hexstr() == '1337' + assert big.from_bytes([byte(0x13), 0x37, 0xca]) ?.hexstr() == '1337ca' + assert big.from_bytes([byte(0x13), 0x37, 0xca, 0xfe]) ?.hexstr() == '1337cafe' + assert big.from_bytes([byte(0x13), 0x37, 0xca, 0xfe, 0xba]) ?.hexstr() == '1337cafeba' + assert big.from_bytes([byte(0x13), 0x37, 0xca, 0xfe, 0xba, 0xbe]) ?.hexstr() == '1337cafebabe' + assert big.from_bytes([]byte{len: 128, init: 0x0}) ?.hexstr() == '0' + if x := big.from_bytes([]byte{len: 129, init: 0x0}) { + return error('expected error, got $x') + } +} diff --git a/v_windows/v/vlib/math/bits.js.v b/v_windows/v/vlib/math/bits.js.v new file mode 100644 index 0000000..0bec945 --- /dev/null +++ b/v_windows/v/vlib/math/bits.js.v @@ -0,0 +1,18 @@ +module math + +pub fn inf(sign int) f64 { + mut res := 0.0 + if sign >= 0 { + #res.val = Infinity + } else { + #res.val = -Infinity + } + return res +} + +pub fn nan() f64 { + mut res := 0.0 + #res.val = NaN + + return res +} diff --git a/v_windows/v/vlib/math/bits.v b/v_windows/v/vlib/math/bits.v new file mode 100644 index 0000000..deaf962 --- /dev/null +++ b/v_windows/v/vlib/math/bits.v @@ -0,0 +1,63 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module math + +const ( + uvnan = u64(0x7FF8000000000001) + uvinf = u64(0x7FF0000000000000) + uvneginf = u64(0xFFF0000000000000) + uvone = u64(0x3FF0000000000000) + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 + normalize_smallest_mask = (u64(1) << 52) + sign_mask = (u64(1) << 63) + frac_mask = ((u64(1) << u64(shift)) - u64(1)) +) + +// inf returns positive infinity if sign >= 0, negative infinity if sign < 0. +pub fn inf(sign int) f64 { + v := if sign >= 0 { math.uvinf } else { math.uvneginf } + return f64_from_bits(v) +} + +// nan returns an IEEE 754 ``not-a-number'' value. +pub fn nan() f64 { + return f64_from_bits(math.uvnan) +} + +// is_nan reports whether f is an IEEE 754 ``not-a-number'' value. +pub fn is_nan(f f64) bool { + // IEEE 754 says that only NaNs satisfy f != f. + // To avoid the floating-point hardware, could use: + // x := f64_bits(f); + // return u32(x>>shift)&mask == mask && x != uvinf && x != uvneginf + return f != f +} + +// is_inf reports whether f is an infinity, according to sign. +// If sign > 0, is_inf reports whether f is positive infinity. +// If sign < 0, is_inf reports whether f is negative infinity. +// If sign == 0, is_inf reports whether f is either infinity. +pub fn is_inf(f f64, sign int) bool { + // Test for infinity by comparing against maximum float. + // To avoid the floating-point hardware, could use: + // x := f64_bits(f); + // return sign >= 0 && x == uvinf || sign <= 0 && x == uvneginf; + return (sign >= 0 && f > max_f64) || (sign <= 0 && f < -max_f64) +} + +pub fn is_finite(f f64) bool { + return !is_nan(f) && !is_inf(f, 0) +} + +// normalize returns a normal number y and exponent exp +// satisfying x == y × 2**exp. It assumes x is finite and non-zero. +pub fn normalize(x f64) (f64, int) { + smallest_normal := 2.2250738585072014e-308 // 2**-1022 + if abs(x) < smallest_normal { + return x * math.normalize_smallest_mask, -52 + } + return x, 0 +} diff --git a/v_windows/v/vlib/math/bits/bits.v b/v_windows/v/vlib/math/bits/bits.v new file mode 100644 index 0000000..a3d2220 --- /dev/null +++ b/v_windows/v/vlib/math/bits/bits.v @@ -0,0 +1,478 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module bits + +const ( + // See http://supertech.csail.mit.edu/papers/debruijn.pdf + de_bruijn32 = u32(0x077CB531) + de_bruijn32tab = [byte(0), 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, + 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9] + de_bruijn64 = u64(0x03f79d71b4ca8b09) + de_bruijn64tab = [byte(0), 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4, 62, 47, + 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5, 63, 55, 48, 27, 60, 41, 37, 16, + 46, 35, 44, 21, 52, 32, 23, 11, 54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, + 6, + ] +) + +const ( + m0 = u64(0x5555555555555555) // 01010101 ... + m1 = u64(0x3333333333333333) // 00110011 ... + m2 = u64(0x0f0f0f0f0f0f0f0f) // 00001111 ... + m3 = u64(0x00ff00ff00ff00ff) // etc. + m4 = u64(0x0000ffff0000ffff) +) + +const ( + // save importing math mod just for these + max_u32 = u32(4294967295) + max_u64 = u64(18446744073709551615) +) + +// --- LeadingZeros --- +// leading_zeros_8 returns the number of leading zero bits in x; the result is 8 for x == 0. +pub fn leading_zeros_8(x byte) int { + return 8 - len_8(x) +} + +// leading_zeros_16 returns the number of leading zero bits in x; the result is 16 for x == 0. +pub fn leading_zeros_16(x u16) int { + return 16 - len_16(x) +} + +// leading_zeros_32 returns the number of leading zero bits in x; the result is 32 for x == 0. +pub fn leading_zeros_32(x u32) int { + return 32 - len_32(x) +} + +// leading_zeros_64 returns the number of leading zero bits in x; the result is 64 for x == 0. +pub fn leading_zeros_64(x u64) int { + return 64 - len_64(x) +} + +// --- TrailingZeros --- +// trailing_zeros_8 returns the number of trailing zero bits in x; the result is 8 for x == 0. +pub fn trailing_zeros_8(x byte) int { + return int(ntz_8_tab[x]) +} + +// trailing_zeros_16 returns the number of trailing zero bits in x; the result is 16 for x == 0. +pub fn trailing_zeros_16(x u16) int { + if x == 0 { + return 16 + } + // see comment in trailing_zeros_64 + return int(bits.de_bruijn32tab[u32(x & -x) * bits.de_bruijn32 >> (32 - 5)]) +} + +// trailing_zeros_32 returns the number of trailing zero bits in x; the result is 32 for x == 0. +pub fn trailing_zeros_32(x u32) int { + if x == 0 { + return 32 + } + // see comment in trailing_zeros_64 + return int(bits.de_bruijn32tab[(x & -x) * bits.de_bruijn32 >> (32 - 5)]) +} + +// trailing_zeros_64 returns the number of trailing zero bits in x; the result is 64 for x == 0. +pub fn trailing_zeros_64(x u64) int { + if x == 0 { + return 64 + } + // If popcount is fast, replace code below with return popcount(^x & (x - 1)). + // + // x & -x leaves only the right-most bit set in the word. Let k be the + // index of that bit. Since only a single bit is set, the value is two + // to the power of k. Multiplying by a power of two is equivalent to + // left shifting, in this case by k bits. The de Bruijn (64 bit) constant + // is such that all six bit, consecutive substrings are distinct. + // Therefore, if we have a left shifted version of this constant we can + // find by how many bits it was shifted by looking at which six bit + // substring ended up at the top of the word. + // (Knuth, volume 4, section 7.3.1) + return int(bits.de_bruijn64tab[(x & -x) * bits.de_bruijn64 >> (64 - 6)]) +} + +// --- OnesCount --- +// ones_count_8 returns the number of one bits ("population count") in x. +pub fn ones_count_8(x byte) int { + return int(pop_8_tab[x]) +} + +// ones_count_16 returns the number of one bits ("population count") in x. +pub fn ones_count_16(x u16) int { + return int(pop_8_tab[x >> 8] + pop_8_tab[x & u16(0xff)]) +} + +// ones_count_32 returns the number of one bits ("population count") in x. +pub fn ones_count_32(x u32) int { + return int(pop_8_tab[x >> 24] + pop_8_tab[x >> 16 & 0xff] + pop_8_tab[x >> 8 & 0xff] + + pop_8_tab[x & u32(0xff)]) +} + +// ones_count_64 returns the number of one bits ("population count") in x. +pub fn ones_count_64(x u64) int { + // Implementation: Parallel summing of adjacent bits. + // See "Hacker's Delight", Chap. 5: Counting Bits. + // The following pattern shows the general approach: + // + // x = x>>1&(m0&m) + x&(m0&m) + // x = x>>2&(m1&m) + x&(m1&m) + // x = x>>4&(m2&m) + x&(m2&m) + // x = x>>8&(m3&m) + x&(m3&m) + // x = x>>16&(m4&m) + x&(m4&m) + // x = x>>32&(m5&m) + x&(m5&m) + // return int(x) + // + // Masking (& operations) can be left away when there's no + // danger that a field's sum will carry over into the next + // field: Since the result cannot be > 64, 8 bits is enough + // and we can ignore the masks for the shifts by 8 and up. + // Per "Hacker's Delight", the first line can be simplified + // more, but it saves at best one instruction, so we leave + // it alone for clarity. + mut y := (x >> u64(1) & (bits.m0 & bits.max_u64)) + (x & (bits.m0 & bits.max_u64)) + y = (y >> u64(2) & (bits.m1 & bits.max_u64)) + (y & (bits.m1 & bits.max_u64)) + y = ((y >> 4) + y) & (bits.m2 & bits.max_u64) + y += y >> 8 + y += y >> 16 + y += y >> 32 + return int(y) & ((1 << 7) - 1) +} + +// --- RotateLeft --- +// rotate_left_8 returns the value of x rotated left by (k mod 8) bits. +// To rotate x right by k bits, call rotate_left_8(x, -k). +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn rotate_left_8(x byte, k int) byte { + n := byte(8) + s := byte(k) & (n - byte(1)) + return ((x << s) | (x >> (n - s))) +} + +// rotate_left_16 returns the value of x rotated left by (k mod 16) bits. +// To rotate x right by k bits, call rotate_left_16(x, -k). +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn rotate_left_16(x u16, k int) u16 { + n := u16(16) + s := u16(k) & (n - u16(1)) + return ((x << s) | (x >> (n - s))) +} + +// rotate_left_32 returns the value of x rotated left by (k mod 32) bits. +// To rotate x right by k bits, call rotate_left_32(x, -k). +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn rotate_left_32(x u32, k int) u32 { + n := u32(32) + s := u32(k) & (n - u32(1)) + return ((x << s) | (x >> (n - s))) +} + +// rotate_left_64 returns the value of x rotated left by (k mod 64) bits. +// To rotate x right by k bits, call rotate_left_64(x, -k). +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn rotate_left_64(x u64, k int) u64 { + n := u64(64) + s := u64(k) & (n - u64(1)) + return ((x << s) | (x >> (n - s))) +} + +// --- Reverse --- +// reverse_8 returns the value of x with its bits in reversed order. +[inline] +pub fn reverse_8(x byte) byte { + return rev_8_tab[x] +} + +// reverse_16 returns the value of x with its bits in reversed order. +[inline] +pub fn reverse_16(x u16) u16 { + return u16(rev_8_tab[x >> 8]) | (u16(rev_8_tab[x & u16(0xff)]) << 8) +} + +// reverse_32 returns the value of x with its bits in reversed order. +[inline] +pub fn reverse_32(x u32) u32 { + mut y := ((x >> u32(1) & (bits.m0 & bits.max_u32)) | ((x & (bits.m0 & bits.max_u32)) << 1)) + y = ((y >> u32(2) & (bits.m1 & bits.max_u32)) | ((y & (bits.m1 & bits.max_u32)) << u32(2))) + y = ((y >> u32(4) & (bits.m2 & bits.max_u32)) | ((y & (bits.m2 & bits.max_u32)) << u32(4))) + return reverse_bytes_32(u32(y)) +} + +// reverse_64 returns the value of x with its bits in reversed order. +[inline] +pub fn reverse_64(x u64) u64 { + mut y := ((x >> u64(1) & (bits.m0 & bits.max_u64)) | ((x & (bits.m0 & bits.max_u64)) << 1)) + y = ((y >> u64(2) & (bits.m1 & bits.max_u64)) | ((y & (bits.m1 & bits.max_u64)) << 2)) + y = ((y >> u64(4) & (bits.m2 & bits.max_u64)) | ((y & (bits.m2 & bits.max_u64)) << 4)) + return reverse_bytes_64(y) +} + +// --- ReverseBytes --- +// reverse_bytes_16 returns the value of x with its bytes in reversed order. +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn reverse_bytes_16(x u16) u16 { + return (x >> 8) | (x << 8) +} + +// reverse_bytes_32 returns the value of x with its bytes in reversed order. +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn reverse_bytes_32(x u32) u32 { + y := ((x >> u32(8) & (bits.m3 & bits.max_u32)) | ((x & (bits.m3 & bits.max_u32)) << u32(8))) + return u32((y >> 16) | (y << 16)) +} + +// reverse_bytes_64 returns the value of x with its bytes in reversed order. +// +// This function's execution time does not depend on the inputs. +[inline] +pub fn reverse_bytes_64(x u64) u64 { + mut y := ((x >> u64(8) & (bits.m3 & bits.max_u64)) | ((x & (bits.m3 & bits.max_u64)) << u64(8))) + y = ((y >> u64(16) & (bits.m4 & bits.max_u64)) | ((y & (bits.m4 & bits.max_u64)) << u64(16))) + return (y >> 32) | (y << 32) +} + +// --- Len --- +// len_8 returns the minimum number of bits required to represent x; the result is 0 for x == 0. +pub fn len_8(x byte) int { + return int(len_8_tab[x]) +} + +// len_16 returns the minimum number of bits required to represent x; the result is 0 for x == 0. +pub fn len_16(x u16) int { + mut y := x + mut n := 0 + if y >= 1 << 8 { + y >>= 8 + n = 8 + } + return n + int(len_8_tab[y]) +} + +// len_32 returns the minimum number of bits required to represent x; the result is 0 for x == 0. +pub fn len_32(x u32) int { + mut y := x + mut n := 0 + if y >= (1 << 16) { + y >>= 16 + n = 16 + } + if y >= (1 << 8) { + y >>= 8 + n += 8 + } + return n + int(len_8_tab[y]) +} + +// len_64 returns the minimum number of bits required to represent x; the result is 0 for x == 0. +pub fn len_64(x u64) int { + mut y := x + mut n := 0 + if y >= u64(1) << u64(32) { + y >>= 32 + n = 32 + } + if y >= u64(1) << u64(16) { + y >>= 16 + n += 16 + } + if y >= u64(1) << u64(8) { + y >>= 8 + n += 8 + } + return n + int(len_8_tab[y]) +} + +// --- Add with carry --- +// Add returns the sum with carry of x, y and carry: sum = x + y + carry. +// The carry input must be 0 or 1; otherwise the behavior is undefined. +// The carryOut output is guaranteed to be 0 or 1. +// +// add_32 returns the sum with carry of x, y and carry: sum = x + y + carry. +// The carry input must be 0 or 1; otherwise the behavior is undefined. +// The carryOut output is guaranteed to be 0 or 1. +// +// This function's execution time does not depend on the inputs. +pub fn add_32(x u32, y u32, carry u32) (u32, u32) { + sum64 := u64(x) + u64(y) + u64(carry) + sum := u32(sum64) + carry_out := u32(sum64 >> 32) + return sum, carry_out +} + +// add_64 returns the sum with carry of x, y and carry: sum = x + y + carry. +// The carry input must be 0 or 1; otherwise the behavior is undefined. +// The carryOut output is guaranteed to be 0 or 1. +// +// This function's execution time does not depend on the inputs. +pub fn add_64(x u64, y u64, carry u64) (u64, u64) { + sum := x + y + carry + // The sum will overflow if both top bits are set (x & y) or if one of them + // is (x | y), and a carry from the lower place happened. If such a carry + // happens, the top bit will be 1 + 0 + 1 = 0 (&^ sum). + carry_out := ((x & y) | ((x | y) & ~sum)) >> 63 + return sum, carry_out +} + +// --- Subtract with borrow --- +// Sub returns the difference of x, y and borrow: diff = x - y - borrow. +// The borrow input must be 0 or 1; otherwise the behavior is undefined. +// The borrowOut output is guaranteed to be 0 or 1. +// +// sub_32 returns the difference of x, y and borrow, diff = x - y - borrow. +// The borrow input must be 0 or 1; otherwise the behavior is undefined. +// The borrowOut output is guaranteed to be 0 or 1. +// +// This function's execution time does not depend on the inputs. +pub fn sub_32(x u32, y u32, borrow u32) (u32, u32) { + diff := x - y - borrow + // The difference will underflow if the top bit of x is not set and the top + // bit of y is set (^x & y) or if they are the same (^(x ^ y)) and a borrow + // from the lower place happens. If that borrow happens, the result will be + // 1 - 1 - 1 = 0 - 0 - 1 = 1 (& diff). + borrow_out := ((~x & y) | (~(x ^ y) & diff)) >> 31 + return diff, borrow_out +} + +// sub_64 returns the difference of x, y and borrow: diff = x - y - borrow. +// The borrow input must be 0 or 1; otherwise the behavior is undefined. +// The borrowOut output is guaranteed to be 0 or 1. +// +// This function's execution time does not depend on the inputs. +pub fn sub_64(x u64, y u64, borrow u64) (u64, u64) { + diff := x - y - borrow + // See Sub32 for the bit logic. + borrow_out := ((~x & y) | (~(x ^ y) & diff)) >> 63 + return diff, borrow_out +} + +// --- Full-width multiply --- +const ( + two32 = u64(0x100000000) + mask32 = two32 - 1 + overflow_error = 'Overflow Error' + divide_error = 'Divide Error' +) + +// mul_32 returns the 64-bit product of x and y: (hi, lo) = x * y +// with the product bits' upper half returned in hi and the lower +// half returned in lo. +// +// This function's execution time does not depend on the inputs. +pub fn mul_32(x u32, y u32) (u32, u32) { + tmp := u64(x) * u64(y) + hi := u32(tmp >> 32) + lo := u32(tmp) + return hi, lo +} + +// mul_64 returns the 128-bit product of x and y: (hi, lo) = x * y +// with the product bits' upper half returned in hi and the lower +// half returned in lo. +// +// This function's execution time does not depend on the inputs. +pub fn mul_64(x u64, y u64) (u64, u64) { + x0 := x & bits.mask32 + x1 := x >> 32 + y0 := y & bits.mask32 + y1 := y >> 32 + w0 := x0 * y0 + t := x1 * y0 + (w0 >> 32) + mut w1 := t & bits.mask32 + w2 := t >> 32 + w1 += x0 * y1 + hi := x1 * y1 + w2 + (w1 >> 32) + lo := x * y + return hi, lo +} + +// --- Full-width divide --- +// div_32 returns the quotient and remainder of (hi, lo) divided by y: +// quo = (hi, lo)/y, rem = (hi, lo)%y with the dividend bits' upper +// half in parameter hi and the lower half in parameter lo. +// div_32 panics for y == 0 (division by zero) or y <= hi (quotient overflow). +pub fn div_32(hi u32, lo u32, y u32) (u32, u32) { + if y != 0 && y <= hi { + panic(bits.overflow_error) + } + z := (u64(hi) << 32) | u64(lo) + quo := u32(z / u64(y)) + rem := u32(z % u64(y)) + return quo, rem +} + +// div_64 returns the quotient and remainder of (hi, lo) divided by y: +// quo = (hi, lo)/y, rem = (hi, lo)%y with the dividend bits' upper +// half in parameter hi and the lower half in parameter lo. +// div_64 panics for y == 0 (division by zero) or y <= hi (quotient overflow). +pub fn div_64(hi u64, lo u64, y1 u64) (u64, u64) { + mut y := y1 + if y == 0 { + panic(bits.overflow_error) + } + if y <= hi { + panic(bits.overflow_error) + } + s := u32(leading_zeros_64(y)) + y <<= s + yn1 := y >> 32 + yn0 := y & bits.mask32 + un32 := (hi << s) | (lo >> (64 - s)) + un10 := lo << s + un1 := un10 >> 32 + un0 := un10 & bits.mask32 + mut q1 := un32 / yn1 + mut rhat := un32 - q1 * yn1 + for q1 >= bits.two32 || q1 * yn0 > bits.two32 * rhat + un1 { + q1-- + rhat += yn1 + if rhat >= bits.two32 { + break + } + } + un21 := un32 * bits.two32 + un1 - q1 * y + mut q0 := un21 / yn1 + rhat = un21 - q0 * yn1 + for q0 >= bits.two32 || q0 * yn0 > bits.two32 * rhat + un0 { + q0-- + rhat += yn1 + if rhat >= bits.two32 { + break + } + } + return q1 * bits.two32 + q0, (un21 * bits.two32 + un0 - q0 * y) >> s +} + +// rem_32 returns the remainder of (hi, lo) divided by y. Rem32 panics +// for y == 0 (division by zero) but, unlike Div32, it doesn't panic +// on a quotient overflow. +pub fn rem_32(hi u32, lo u32, y u32) u32 { + return u32(((u64(hi) << 32) | u64(lo)) % u64(y)) +} + +// rem_64 returns the remainder of (hi, lo) divided by y. Rem64 panics +// for y == 0 (division by zero) but, unlike div_64, it doesn't panic +// on a quotient overflow. +pub fn rem_64(hi u64, lo u64, y u64) u64 { + // We scale down hi so that hi < y, then use div_64 to compute the + // rem with the guarantee that it won't panic on quotient overflow. + // Given that + // hi ≡ hi%y (mod y) + // we have + // hi<<64 + lo ≡ (hi%y)<<64 + lo (mod y) + _, rem := div_64(hi % y, lo, y) + return rem +} diff --git a/v_windows/v/vlib/math/bits/bits_tables.v b/v_windows/v/vlib/math/bits/bits_tables.v new file mode 100644 index 0000000..830e1e8 --- /dev/null +++ b/v_windows/v/vlib/math/bits/bits_tables.v @@ -0,0 +1,79 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module bits + +const ( + ntz_8_tab = [byte(0x08), 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, + 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, + 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x01, 0x00] + pop_8_tab = [byte(0x00), 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x01, 0x02, 0x02, 0x03, + 0x02, 0x03, 0x03, 0x04, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, + 0x04, 0x03, 0x04, 0x04, 0x05, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, + 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, + 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, + 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, + 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, + 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x03, 0x04, 0x04, 0x05, 0x04, + 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 0x01, 0x02, 0x02, 0x03, + 0x02, 0x03, 0x03, 0x04, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x02, 0x03, 0x03, + 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x02, 0x03, + 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x03, + 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, + 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, + 0x06, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, + 0x06, 0x07, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x05, 0x06, 0x04, 0x05, 0x05, 0x06, 0x05, + 0x06, 0x06, 0x07, 0x04, 0x05, 0x05, 0x06, 0x05, 0x06, 0x06, 0x07, 0x05, 0x06, 0x06, 0x07, + 0x06, 0x07, 0x07, 0x08] + rev_8_tab = [byte(0x00), 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, + 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, + 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, + 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, + 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, + 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, + 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, + 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, + 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, + 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, + 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, + 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, + 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, + 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, + 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, + 0x3f, 0xbf, 0x7f, 0xff] + len_8_tab = [byte(0x00), 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08] +) diff --git a/v_windows/v/vlib/math/bits/bits_test.v b/v_windows/v/vlib/math/bits/bits_test.v new file mode 100644 index 0000000..23a01b3 --- /dev/null +++ b/v_windows/v/vlib/math/bits/bits_test.v @@ -0,0 +1,288 @@ +// +// test suite for bits and bits math functions +// +module bits + +fn test_bits() { + mut i := 0 + mut i1 := u64(0) + + // + // --- LeadingZeros --- + // + + // 8 bit + i = 1 + for x in 0 .. 8 { + // C.printf("x:%02x lz: %d cmp: %d\n", i << x, leading_zeros_8(i << x), 7-x) + assert leading_zeros_8(byte(i << x)) == 7 - x + } + + // 16 bit + i = 1 + for x in 0 .. 16 { + // C.printf("x:%04x lz: %d cmp: %d\n", u16(i) << x, leading_zeros_16(u16(i) << x), 15-x) + assert leading_zeros_16(u16(i) << x) == 15 - x + } + + // 32 bit + i = 1 + for x in 0 .. 32 { + // C.printf("x:%08x lz: %d cmp: %d\n", u32(i) << x, leading_zeros_32(u32(i) << x), 31-x) + assert leading_zeros_32(u32(i) << x) == 31 - x + } + + // 64 bit + i = 1 + for x in 0 .. 64 { + // C.printf("x:%016llx lz: %llu cmp: %d\n", u64(i) << x, leading_zeros_64(u64(i) << x), 63-x) + assert leading_zeros_64(u64(i) << x) == 63 - x + } + + // + // --- ones_count --- + // + + // 8 bit + i = 0 + for x in 0 .. 9 { + // C.printf("x:%02x lz: %llu cmp: %d\n", byte(i), ones_count_8(byte(i)), x) + assert ones_count_8(byte(i)) == x + i = (i << 1) + 1 + } + + // 16 bit + i = 0 + for x in 0 .. 17 { + // C.printf("x:%04x lz: %llu cmp: %d\n", u16(i), ones_count_16(u16(i)), x) + assert ones_count_16(u16(i)) == x + i = (i << 1) + 1 + } + + // 32 bit + i = 0 + for x in 0 .. 33 { + // C.printf("x:%08x lz: %llu cmp: %d\n", u32(i), ones_count_32(u32(i)), x) + assert ones_count_32(u32(i)) == x + i = (i << 1) + 1 + } + + // 64 bit + i1 = 0 + for x in 0 .. 65 { + // C.printf("x:%016llx lz: %llu cmp: %d\n", u64(i1), ones_count_64(u64(i1)), x) + assert ones_count_64(i1) == x + i1 = (i1 << 1) + 1 + } + + // + // --- rotate_left/right --- + // + assert rotate_left_8(0x12, 4) == 0x21 + assert rotate_left_16(0x1234, 8) == 0x3412 + assert rotate_left_32(0x12345678, 16) == 0x56781234 + assert rotate_left_64(0x1234567887654321, 32) == 0x8765432112345678 + + // + // --- reverse --- + // + + // 8 bit + i = 0 + for _ in 0 .. 9 { + mut rv := byte(0) + mut bc := 0 + mut n := i + for bc < 8 { + rv = (rv << 1) | (byte(n) & 0x01) + bc++ + n = n >> 1 + } + // C.printf("x:%02x lz: %llu cmp: %d\n", byte(i), reverse_8(byte(i)), rv) + assert reverse_8(byte(i)) == rv + i = (i << 1) + 1 + } + + // 16 bit + i = 0 + for _ in 0 .. 17 { + mut rv := u16(0) + mut bc := 0 + mut n := i + for bc < 16 { + rv = (rv << 1) | (u16(n) & 0x01) + bc++ + n = n >> 1 + } + // C.printf("x:%04x lz: %llu cmp: %d\n", u16(i), reverse_16(u16(i)), rv) + assert reverse_16(u16(i)) == rv + i = (i << 1) + 1 + } + + // 32 bit + i = 0 + for _ in 0 .. 33 { + mut rv := u32(0) + mut bc := 0 + mut n := i + for bc < 32 { + rv = (rv << 1) | (u32(n) & 0x01) + bc++ + n = n >> 1 + } + // C.printf("x:%08x lz: %llu cmp: %d\n", u32(i), reverse_32(u32(i)), rv) + assert reverse_32(u32(i)) == rv + i = (i << 1) + 1 + } + + // 64 bit + i1 = 0 + for _ in 0 .. 64 { + mut rv := u64(0) + mut bc := 0 + mut n := i1 + for bc < 64 { + rv = (rv << 1) | (n & 0x01) + bc++ + n = n >> 1 + } + // C.printf("x:%016llx lz: %016llx cmp: %016llx\n", u64(i1), reverse_64(u64(i1)), rv) + assert reverse_64(i1) == rv + i1 = (i1 << 1) + 1 + } + + // + // --- add --- + // + + // 32 bit + i = 1 + for x in 0 .. 32 { + v := u32(i) << x + sum, carry := add_32(v, v, u32(0)) + // C.printf("x:%08x [%llu,%llu] %llu\n", u32(i) << x, sum, carry, u64(v) + u64(v)) + assert ((u64(carry) << 32) | u64(sum)) == u64(v) + u64(v) + } + mut sum_32t, mut carry_32t := add_32(0x8000_0000, 0x8000_0000, u32(0)) + assert sum_32t == u32(0) + assert carry_32t == u32(1) + + sum_32t, carry_32t = add_32(0xFFFF_FFFF, 0xFFFF_FFFF, u32(1)) + assert sum_32t == 0xFFFF_FFFF + assert carry_32t == u32(1) + + // 64 bit + i = 1 + for x in 0 .. 63 { + v := u64(i) << x + sum, carry := add_64(v, v, u64(0)) + // C.printf("x:%16x [%llu,%llu] %llu\n", u64(i) << x, sum, carry, u64(v >> 32) + u64(v >> 32)) + assert ((carry << 32) | sum) == v + v + } + mut sum_64t, mut carry_64t := add_64(0x8000_0000_0000_0000, 0x8000_0000_0000_0000, + u64(0)) + assert sum_64t == u64(0) + assert carry_64t == u64(1) + + sum_64t, carry_64t = add_64(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF, u64(1)) + assert sum_64t == 0xFFFF_FFFF_FFFF_FFFF + assert carry_64t == u64(1) + + // + // --- sub --- + // + + // 32 bit + i = 1 + for x in 1 .. 32 { + v0 := u32(i) << x + v1 := v0 >> 1 + mut diff, mut borrow_out := sub_32(v0, v1, u32(0)) + // C.printf("x:%08x [%llu,%llu] %08x\n", u32(i) << x, diff, borrow_out, v0 - v1) + assert diff == v1 + + diff, borrow_out = sub_32(v0, v1, u32(1)) + // C.printf("x:%08x [%llu,%llu] %08x\n", u32(i) << x, diff, borrow_out, v0 - v1) + assert diff == (v1 - 1) + assert borrow_out == u32(0) + + diff, borrow_out = sub_32(v1, v0, u32(1)) + // C.printf("x:%08x [%llu,%llu] %08x\n", u32(i) << x, diff, borrow_out, v1 - v0) + assert borrow_out == u32(1) + } + + // 64 bit + i = 1 + for x in 1 .. 64 { + v0 := u64(i) << x + v1 := v0 >> 1 + mut diff, mut borrow_out := sub_64(v0, v1, u64(0)) + // C.printf("x:%08x [%llu,%llu] %08x\n", u64(i) << x, diff, borrow_out, v0 - v1) + assert diff == v1 + + diff, borrow_out = sub_64(v0, v1, u64(1)) + // C.printf("x:%08x [%llu,%llu] %08x\n", u64(i) << x, diff, borrow_out, v0 - v1) + assert diff == (v1 - 1) + assert borrow_out == u64(0) + + diff, borrow_out = sub_64(v1, v0, u64(1)) + // C.printf("x:%08x [%llu,%llu] %08x\n",u64(i) << x, diff, borrow_out, v1 - v0) + assert borrow_out == u64(1) + } + + // + // --- mul --- + // + + // 32 bit + i = 1 + for x in 0 .. 32 { + v0 := u32(i) << x + v1 := v0 - 1 + hi, lo := mul_32(v0, v1) + assert (u64(hi) << 32) | (u64(lo)) == u64(v0) * u64(v1) + } + + // 64 bit + i = 1 + for x in 0 .. 64 { + v0 := u64(i) << x + v1 := v0 - 1 + hi, lo := mul_64(v0, v1) + // C.printf("v0: %llu v1: %llu [%llu,%llu] tt: %llu\n", v0, v1, hi, lo, (v0 >> 32) * (v1 >> 32)) + assert (hi & 0xFFFF_FFFF_0000_0000) == (((v0 >> 32) * (v1 >> 32)) & 0xFFFF_FFFF_0000_0000) + assert (lo & 0x0000_0000_FFFF_FFFF) == (((v0 & 0x0000_0000_FFFF_FFFF) * (v1 & 0x0000_0000_FFFF_FFFF)) & 0x0000_0000_FFFF_FFFF) + } + + // + // --- div --- + // + + // 32 bit + i = 1 + for x in 0 .. 31 { + hi := u32(i) << x + lo := hi - 1 + y := u32(3) << x + quo, rem := div_32(hi, lo, y) + // C.printf("[%08x_%08x] %08x (%08x,%08x)\n", hi, lo, y, quo, rem) + tst := ((u64(hi) << 32) | u64(lo)) + assert quo == (tst / u64(y)) + assert rem == (tst % u64(y)) + assert rem == rem_32(hi, lo, y) + } + + // 64 bit + i = 1 + for x in 0 .. 62 { + hi := u64(i) << x + lo := u64(2) // hi - 1 + y := u64(0x4000_0000_0000_0000) + quo, rem := div_64(hi, lo, y) + // C.printf("[%016llx_%016llx] %016llx (%016llx,%016llx)\n", hi, lo, y, quo, rem) + assert quo == u64(2) << (x + 1) + _, rem1 := div_64(hi % y, lo, y) + assert rem == rem1 + assert rem == rem_64(hi, lo, y) + } +} diff --git a/v_windows/v/vlib/math/cbrt.c.v b/v_windows/v/vlib/math/cbrt.c.v new file mode 100644 index 0000000..892075a --- /dev/null +++ b/v_windows/v/vlib/math/cbrt.c.v @@ -0,0 +1,9 @@ +module math + +fn C.cbrt(x f64) f64 + +// cbrt calculates cubic root. +[inline] +pub fn cbrt(a f64) f64 { + return C.cbrt(a) +} diff --git a/v_windows/v/vlib/math/cbrt.js.v b/v_windows/v/vlib/math/cbrt.js.v new file mode 100644 index 0000000..306bba2 --- /dev/null +++ b/v_windows/v/vlib/math/cbrt.js.v @@ -0,0 +1,9 @@ +module math + +fn JS.Math.cbrt(x f64) f64 + +// cbrt calculates cubic root. +[inline] +pub fn cbrt(a f64) f64 { + return JS.Math.cbrt(a) +} diff --git a/v_windows/v/vlib/math/cbrt.v b/v_windows/v/vlib/math/cbrt.v new file mode 100644 index 0000000..2c34ef2 --- /dev/null +++ b/v_windows/v/vlib/math/cbrt.v @@ -0,0 +1,52 @@ +module math + +// cbrt returns the cube root of a. +// +// special cases are: +// cbrt(±0) = ±0 +// cbrt(±inf) = ±inf +// cbrt(nan) = nan +pub fn cbrt(a f64) f64 { + mut x := a + b1 := 715094163 // (682-0.03306235651)*2**20 + b2 := 696219795 // (664-0.03306235651)*2**20 + c := 5.42857142857142815906e-01 // 19/35 = 0x3FE15F15F15F15F1 + d := -7.05306122448979611050e-01 // -864/1225 = 0xBFE691DE2532C834 + e_ := 1.41428571428571436819e+00 // 99/70 = 0x3FF6A0EA0EA0EA0F + f := 1.60714285714285720630e+00 // 45/28 = 0x3FF9B6DB6DB6DB6E + g := 3.57142857142857150787e-01 // 5/14 = 0x3FD6DB6DB6DB6DB7 + smallest_normal := 2.22507385850720138309e-308 // 2**-1022 = 0x0010000000000000 + if x == 0.0 || is_nan(x) || is_inf(x, 0) { + return x + } + mut sign := false + if x < 0 { + x = -x + sign = true + } + // rough cbrt to 5 bits + mut t := f64_from_bits(f64_bits(x) / u64(3 + (u64(b1) << 32))) + if x < smallest_normal { + // subnormal number + t = f64(u64(1) << 54) // set t= 2**54 + t *= x + t = f64_from_bits(f64_bits(t) / u64(3 + (u64(b2) << 32))) + } + // new cbrt to 23 bits + mut r := t * t / x + mut s := c + r * t + t *= g + f / (s + e_ + d / s) + // chop to 22 bits, make larger than cbrt(x) + t = f64_from_bits(f64_bits(t) & (u64(0xffffffffc) << 28) + (u64(1) << 30)) + // one step newton iteration to 53 bits with error less than 0.667ulps + s = t * t // t*t is exact + r = x / s + w := t + t + r = (r - t) / (w + r) // r-s is exact + t = t + t * r + // restore the sign bit + if sign { + t = -t + } + return t +} diff --git a/v_windows/v/vlib/math/complex/complex.v b/v_windows/v/vlib/math/complex/complex.v new file mode 100644 index 0000000..b7ec6aa --- /dev/null +++ b/v_windows/v/vlib/math/complex/complex.v @@ -0,0 +1,374 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module complex + +import math + +pub struct Complex { +pub: + re f64 + im f64 +} + +pub fn complex(re f64, im f64) Complex { + return Complex{re, im} +} + +// To String method +pub fn (c Complex) str() string { + mut out := '${c.re:.6f}' + out += if c.im >= 0 { '+${c.im:.6f}' } else { '${c.im:.6f}' } + out += 'i' + return out +} + +// Complex Modulus value +// mod() and abs() return the same +pub fn (c Complex) abs() f64 { + return math.hypot(c.re, c.im) +} + +pub fn (c Complex) mod() f64 { + return c.abs() +} + +// Complex Angle +pub fn (c Complex) angle() f64 { + return math.atan2(c.im, c.re) +} + +// Complex Addition c1 + c2 +pub fn (c1 Complex) + (c2 Complex) Complex { + return Complex{c1.re + c2.re, c1.im + c2.im} +} + +// Complex Substraction c1 - c2 +pub fn (c1 Complex) - (c2 Complex) Complex { + return Complex{c1.re - c2.re, c1.im - c2.im} +} + +// Complex Multiplication c1 * c2 +pub fn (c1 Complex) * (c2 Complex) Complex { + return Complex{(c1.re * c2.re) + ((c1.im * c2.im) * -1), (c1.re * c2.im) + (c1.im * c2.re)} +} + +// Complex Division c1 / c2 +pub fn (c1 Complex) / (c2 Complex) Complex { + denom := (c2.re * c2.re) + (c2.im * c2.im) + return Complex{((c1.re * c2.re) + ((c1.im * -c2.im) * -1)) / denom, ((c1.re * -c2.im) + + (c1.im * c2.re)) / denom} +} + +// Complex Addition c1.add(c2) +pub fn (c1 Complex) add(c2 Complex) Complex { + return c1 + c2 +} + +// Complex Subtraction c1.subtract(c2) +pub fn (c1 Complex) subtract(c2 Complex) Complex { + return c1 - c2 +} + +// Complex Multiplication c1.multiply(c2) +pub fn (c1 Complex) multiply(c2 Complex) Complex { + return Complex{(c1.re * c2.re) + ((c1.im * c2.im) * -1), (c1.re * c2.im) + (c1.im * c2.re)} +} + +// Complex Division c1.divide(c2) +pub fn (c1 Complex) divide(c2 Complex) Complex { + denom := (c2.re * c2.re) + (c2.im * c2.im) + return Complex{((c1.re * c2.re) + ((c1.im * -c2.im) * -1)) / denom, ((c1.re * -c2.im) + + (c1.im * c2.re)) / denom} +} + +// Complex Conjugate +pub fn (c Complex) conjugate() Complex { + return Complex{c.re, -c.im} +} + +// Complex Additive Inverse +// Based on +// http://tutorial.math.lamar.edu/Extras/ComplexPrimer/Arithmetic.aspx +pub fn (c Complex) addinv() Complex { + return Complex{-c.re, -c.im} +} + +// Complex Multiplicative Inverse +// Based on +// http://tutorial.math.lamar.edu/Extras/ComplexPrimer/Arithmetic.aspx +pub fn (c Complex) mulinv() Complex { + return Complex{c.re / (c.re * c.re + c.im * c.im), -c.im / (c.re * c.re + c.im * c.im)} +} + +// Complex Power +// Based on +// https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers/multiplying-and-dividing-complex-numbers-in-polar-form/a/complex-number-polar-form-review +pub fn (c Complex) pow(n f64) Complex { + r := math.pow(c.abs(), n) + angle := c.angle() + return Complex{r * math.cos(n * angle), r * math.sin(n * angle)} +} + +// Complex nth root +pub fn (c Complex) root(n f64) Complex { + return c.pow(1.0 / n) +} + +// Complex Exponential +// Using Euler's Identity +// Based on +// https://www.math.wisc.edu/~angenent/Free-Lecture-Notes/freecomplexnumbers.pdf +pub fn (c Complex) exp() Complex { + a := math.exp(c.re) + return Complex{a * math.cos(c.im), a * math.sin(c.im)} +} + +// Complex Natural Logarithm +// Based on +// http://www.chemistrylearning.com/logarithm-of-complex-number/ +pub fn (c Complex) ln() Complex { + return Complex{math.log(c.abs()), c.angle()} +} + +// Complex Log Base Complex +// Based on +// http://www.milefoot.com/math/complex/summaryops.htm +pub fn (c Complex) log(base Complex) Complex { + return base.ln().divide(c.ln()) +} + +// Complex Argument +// Based on +// http://mathworld.wolfram.com/ComplexArgument.html +pub fn (c Complex) arg() f64 { + return math.atan2(c.im, c.re) +} + +// Complex raised to Complex Power +// Based on +// http://mathworld.wolfram.com/ComplexExponentiation.html +pub fn (c Complex) cpow(p Complex) Complex { + a := c.arg() + b := math.pow(c.re, 2) + math.pow(c.im, 2) + d := p.re * a + (1.0 / 2) * p.im * math.log(b) + t1 := math.pow(b, p.re / 2) * math.exp(-p.im * a) + return Complex{t1 * math.cos(d), t1 * math.sin(d)} +} + +// Complex Sin +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) sin() Complex { + return Complex{math.sin(c.re) * math.cosh(c.im), math.cos(c.re) * math.sinh(c.im)} +} + +// Complex Cosine +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) cos() Complex { + return Complex{math.cos(c.re) * math.cosh(c.im), -(math.sin(c.re) * math.sinh(c.im))} +} + +// Complex Tangent +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) tan() Complex { + return c.sin().divide(c.cos()) +} + +// Complex Cotangent +// Based on +// http://www.suitcaseofdreams.net/Trigonometric_Functions.htm +pub fn (c Complex) cot() Complex { + return c.cos().divide(c.sin()) +} + +// Complex Secant +// Based on +// http://www.suitcaseofdreams.net/Trigonometric_Functions.htm +pub fn (c Complex) sec() Complex { + return complex(1, 0).divide(c.cos()) +} + +// Complex Cosecant +// Based on +// http://www.suitcaseofdreams.net/Trigonometric_Functions.htm +pub fn (c Complex) csc() Complex { + return complex(1, 0).divide(c.sin()) +} + +// Complex Arc Sin / Sin Inverse +// Based on +// http://www.milefoot.com/math/complex/summaryops.htm +pub fn (c Complex) asin() Complex { + return complex(0, -1).multiply(complex(0, 1).multiply(c).add(complex(1, 0).subtract(c.pow(2)).root(2)).ln()) +} + +// Complex Arc Consine / Consine Inverse +// Based on +// http://www.milefoot.com/math/complex/summaryops.htm +pub fn (c Complex) acos() Complex { + return complex(0, -1).multiply(c.add(complex(0, 1).multiply(complex(1, 0).subtract(c.pow(2)).root(2))).ln()) +} + +// Complex Arc Tangent / Tangent Inverse +// Based on +// http://www.milefoot.com/math/complex/summaryops.htm +pub fn (c Complex) atan() Complex { + i := complex(0, 1) + return complex(0, 1.0 / 2).multiply(i.add(c).divide(i.subtract(c)).ln()) +} + +// Complex Arc Cotangent / Cotangent Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse_Functions.htm +pub fn (c Complex) acot() Complex { + return complex(1, 0).divide(c).atan() +} + +// Complex Arc Secant / Secant Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse_Functions.htm +pub fn (c Complex) asec() Complex { + return complex(1, 0).divide(c).acos() +} + +// Complex Arc Cosecant / Cosecant Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse_Functions.htm +pub fn (c Complex) acsc() Complex { + return complex(1, 0).divide(c).asin() +} + +// Complex Hyperbolic Sin +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) sinh() Complex { + return Complex{math.cos(c.im) * math.sinh(c.re), math.sin(c.im) * math.cosh(c.re)} +} + +// Complex Hyperbolic Cosine +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) cosh() Complex { + return Complex{math.cos(c.im) * math.cosh(c.re), math.sin(c.im) * math.sinh(c.re)} +} + +// Complex Hyperbolic Tangent +// Based on +// http://www.milefoot.com/math/complex/functionsofi.htm +pub fn (c Complex) tanh() Complex { + return c.sinh().divide(c.cosh()) +} + +// Complex Hyperbolic Cotangent +// Based on +// http://www.suitcaseofdreams.net/Hyperbolic_Functions.htm +pub fn (c Complex) coth() Complex { + return c.cosh().divide(c.sinh()) +} + +// Complex Hyperbolic Secant +// Based on +// http://www.suitcaseofdreams.net/Hyperbolic_Functions.htm +pub fn (c Complex) sech() Complex { + return complex(1, 0).divide(c.cosh()) +} + +// Complex Hyperbolic Cosecant +// Based on +// http://www.suitcaseofdreams.net/Hyperbolic_Functions.htm +pub fn (c Complex) csch() Complex { + return complex(1, 0).divide(c.sinh()) +} + +// Complex Hyperbolic Arc Sin / Sin Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +pub fn (c Complex) asinh() Complex { + return c.add(c.pow(2).add(complex(1, 0)).root(2)).ln() +} + +// Complex Hyperbolic Arc Consine / Consine Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +pub fn (c Complex) acosh() Complex { + if c.re > 1 { + return c.add(c.pow(2).subtract(complex(1, 0)).root(2)).ln() + } else { + one := complex(1, 0) + return c.add(c.add(one).root(2).multiply(c.subtract(one).root(2))).ln() + } +} + +// Complex Hyperbolic Arc Tangent / Tangent Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +pub fn (c Complex) atanh() Complex { + one := complex(1, 0) + if c.re < 1 { + return complex(1.0 / 2, 0).multiply(one.add(c).divide(one.subtract(c)).ln()) + } else { + return complex(1.0 / 2, 0).multiply(one.add(c).ln().subtract(one.subtract(c).ln())) + } +} + +// Complex Hyperbolic Arc Cotangent / Cotangent Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +pub fn (c Complex) acoth() Complex { + one := complex(1, 0) + if c.re < 0 || c.re > 1 { + return complex(1.0 / 2, 0).multiply(c.add(one).divide(c.subtract(one)).ln()) + } else { + div := one.divide(c) + return complex(1.0 / 2, 0).multiply(one.add(div).ln().subtract(one.subtract(div).ln())) + } +} + +// Complex Hyperbolic Arc Secant / Secant Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +// For certain scenarios, Result mismatch in crossverification with Wolfram Alpha - analysis pending +// pub fn (c Complex) asech() Complex { +// one := complex(1,0) +// if(c.re < -1.0) { +// return one.subtract( +// one.subtract( +// c.pow(2) +// ) +// .root(2) +// ) +// .divide(c) +// .ln() +// } +// else { +// return one.add( +// one.subtract( +// c.pow(2) +// ) +// .root(2) +// ) +// .divide(c) +// .ln() +// } +// } + +// Complex Hyperbolic Arc Cosecant / Cosecant Inverse +// Based on +// http://www.suitcaseofdreams.net/Inverse__Hyperbolic_Functions.htm +pub fn (c Complex) acsch() Complex { + one := complex(1, 0) + if c.re < 0 { + return one.subtract(one.add(c.pow(2)).root(2)).divide(c).ln() + } else { + return one.add(one.add(c.pow(2)).root(2)).divide(c).ln() + } +} + +// Complex Equals +pub fn (c1 Complex) equals(c2 Complex) bool { + return (c1.re == c2.re) && (c1.im == c2.im) +} diff --git a/v_windows/v/vlib/math/complex/complex_test.v b/v_windows/v/vlib/math/complex/complex_test.v new file mode 100644 index 0000000..ccd448e --- /dev/null +++ b/v_windows/v/vlib/math/complex/complex_test.v @@ -0,0 +1,797 @@ +import math +import math.complex as cmplx + +fn tst_res(str1 string, str2 string) bool { + if (math.abs(str1.f64() - str2.f64())) < 1e-5 { + return true + } + return false +} + +fn test_complex_addition() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c1 := cmplx.complex(0, -10) + mut c2 := cmplx.complex(-40, 8) + mut result := c1 + c2 + assert result.equals(cmplx.complex(-40, -2)) + c1 = cmplx.complex(-71, 2) + c2 = cmplx.complex(88, -12) + result = c1 + c2 + assert result.equals(cmplx.complex(17, -10)) + c1 = cmplx.complex(0, -30) + c2 = cmplx.complex(52, -30) + result = c1 + c2 + assert result.equals(cmplx.complex(52, -60)) + c1 = cmplx.complex(12, -9) + c2 = cmplx.complex(32, -6) + result = c1 + c2 + assert result.equals(cmplx.complex(44, -15)) +} + +fn test_complex_subtraction() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c1 := cmplx.complex(-8, 0) + mut c2 := cmplx.complex(6, 30) + mut result := c1 - c2 + assert result.equals(cmplx.complex(-14, -30)) + c1 = cmplx.complex(-19, 7) + c2 = cmplx.complex(29, 32) + result = c1 - c2 + assert result.equals(cmplx.complex(-48, -25)) + c1 = cmplx.complex(12, 0) + c2 = cmplx.complex(23, 13) + result = c1 - c2 + assert result.equals(cmplx.complex(-11, -13)) + c1 = cmplx.complex(-14, 3) + c2 = cmplx.complex(0, 14) + result = c1 - c2 + assert result.equals(cmplx.complex(-14, -11)) +} + +fn test_complex_multiplication() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c1 := cmplx.complex(1, 2) + mut c2 := cmplx.complex(1, -4) + mut result := c1 * c2 + assert result.equals(cmplx.complex(9, -2)) + c1 = cmplx.complex(-4, -4) + c2 = cmplx.complex(-5, -3) + result = c1 * c2 + assert result.equals(cmplx.complex(8, 32)) + c1 = cmplx.complex(4, 4) + c2 = cmplx.complex(-2, -5) + result = c1 * c2 + assert result.equals(cmplx.complex(12, -28)) + c1 = cmplx.complex(2, -2) + c2 = cmplx.complex(4, -4) + result = c1 * c2 + assert result.equals(cmplx.complex(0, -16)) +} + +fn test_complex_division() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c1 := cmplx.complex(-9, -6) + mut c2 := cmplx.complex(-3, -2) + mut result := c1 / c2 + assert result.equals(cmplx.complex(3, 0)) + c1 = cmplx.complex(-23, 11) + c2 = cmplx.complex(5, 1) + result = c1 / c2 + assert result.equals(cmplx.complex(-4, 3)) + c1 = cmplx.complex(8, -2) + c2 = cmplx.complex(-4, 1) + result = c1 / c2 + assert result.equals(cmplx.complex(-2, 0)) + c1 = cmplx.complex(11, 24) + c2 = cmplx.complex(-4, -1) + result = c1 / c2 + assert result.equals(cmplx.complex(-4, -5)) +} + +fn test_complex_conjugate() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c1 := cmplx.complex(0, 8) + mut result := c1.conjugate() + assert result.equals(cmplx.complex(0, -8)) + c1 = cmplx.complex(7, 3) + result = c1.conjugate() + assert result.equals(cmplx.complex(7, -3)) + c1 = cmplx.complex(2, 2) + result = c1.conjugate() + assert result.equals(cmplx.complex(2, -2)) + c1 = cmplx.complex(7, 0) + result = c1.conjugate() + assert result.equals(cmplx.complex(7, 0)) +} + +fn test_complex_equals() { + mut c1 := cmplx.complex(0, 8) + mut c2 := cmplx.complex(0, 8) + assert c1.equals(c2) + c1 = cmplx.complex(-3, 19) + c2 = cmplx.complex(-3, 19) + assert c1.equals(c2) +} + +fn test_complex_abs() { + mut c1 := cmplx.complex(3, 4) + assert c1.abs() == 5 + c1 = cmplx.complex(1, 2) + assert c1.abs() == math.sqrt(5) + assert c1.abs() == c1.conjugate().abs() + c1 = cmplx.complex(7, 0) + assert c1.abs() == 7 +} + +fn test_complex_angle() { + // Test is based on and verified from practice examples of Khan Academy + // https://www.khanacademy.org/math/precalculus/imaginary-and-complex-numbers + mut c := cmplx.complex(1, 0) + assert c.angle() * 180 / math.pi == 0 + c = cmplx.complex(1, 1) + assert c.angle() * 180 / math.pi == 45 + c = cmplx.complex(0, 1) + assert c.angle() * 180 / math.pi == 90 + c = cmplx.complex(-1, 1) + assert c.angle() * 180 / math.pi == 135 + c = cmplx.complex(-1, -1) + assert c.angle() * 180 / math.pi == -135 + cc := c.conjugate() + assert cc.angle() + c.angle() == 0 +} + +fn test_complex_addinv() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-5, -7) + mut result := c1.addinv() + assert result.equals(c2) + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(3, -4) + result = c1.addinv() + assert result.equals(c2) + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(1, 2) + result = c1.addinv() + assert result.equals(c2) +} + +fn test_complex_mulinv() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.067568, -0.094595) + mut result := c1.mulinv() + // Some issue with precision comparison in f64 using == operator hence serializing to string + println(c2.str()) + println(result.str()) + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.12, -0.16) + result = c1.mulinv() + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.2, 0.4) + result = c1.mulinv() + assert result.equals(c2) +} + +fn test_complex_mod() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut result := c1.mod() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(result.str(), '8.602325') + c1 = cmplx.complex(-3, 4) + result = c1.mod() + assert result == 5 + c1 = cmplx.complex(-1, -2) + result = c1.mod() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(result.str(), '2.236068') +} + +fn test_complex_pow() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-24.0, 70.0) + mut result := c1.pow(2) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(117, 44) + result = c1.pow(3) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-7, -24) + result = c1.pow(4) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_root() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(2.607904, 1.342074) + mut result := c1.root(2) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(1.264953, 1.150614) + result = c1.root(3) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(1.068059, -0.595482) + result = c1.root(4) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_exp() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(111.889015, 97.505457) + mut result := c1.exp() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.032543, -0.037679) + result = c1.exp() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.153092, -0.334512) + result = c1.exp() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_ln() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(2.152033, 0.950547) + mut result := c1.ln() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(1.609438, 2.214297) + result = c1.ln() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(0.804719, -2.034444) + result = c1.ln() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_arg() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(2.152033, 0.950547) + mut result := c1.arg() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(result.str(), '0.950547') + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(1.609438, 2.214297) + result = c1.arg() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(result.str(), '2.214297') + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(0.804719, -2.034444) + result = c1.arg() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(result.str(), '-2.034444') +} + +fn test_complex_log() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut b1 := cmplx.complex(-6, -2) + mut c2 := cmplx.complex(0.232873, -1.413175) + mut result := c1.log(b1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + b1 = cmplx.complex(3, -1) + c2 = cmplx.complex(0.152198, -0.409312) + result = c1.log(b1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + b1 = cmplx.complex(0, 9) + c2 = cmplx.complex(-0.298243, 1.197981) + result = c1.log(b1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_cpow() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut r1 := cmplx.complex(2, 2) + mut c2 := cmplx.complex(11.022341, -0.861785) + mut result := c1.cpow(r1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + r1 = cmplx.complex(-4, -2) + c2 = cmplx.complex(0.118303, 0.063148) + result = c1.cpow(r1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + r1 = cmplx.complex(8, -9) + c2 = cmplx.complex(-0.000000, 0.000007) + result = c1.cpow(r1) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_sin() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-525.794515, 155.536550) + mut result := c1.sin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-3.853738, -27.016813) + result = c1.sin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-3.165779, -1.959601) + result = c1.sin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_cos() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(155.536809, 525.793641) + mut result := c1.cos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-27.034946, 3.851153) + result = c1.cos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(2.032723, -3.051898) + result = c1.cos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_tan() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-0.000001, 1.000001) + mut result := c1.tan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(0.000187, 0.999356) + result = c1.tan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.033813, -1.014794) + result = c1.tan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_cot() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-0.000001, -0.999999) + mut result := c1.cot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(0.000188, -1.000644) + result = c1.cot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.032798, 0.984329) + result = c1.cot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_sec() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.000517, -0.001749) + mut result := c1.sec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.036253, -0.005164) + result = c1.sec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(0.151176, 0.226974) + result = c1.sec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_csc() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(-0.001749, -0.000517) + mut result := c1.csc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.005174, 0.036276) + result = c1.csc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.228375, 0.141363) + result = c1.csc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_asin() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.617064, 2.846289) + mut result := c1.asin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.633984, 2.305509) + result = c1.asin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.427079, -1.528571) + result = c1.asin() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_acos() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.953732, -2.846289) + mut result := c1.acos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(2.204780, -2.305509) + result = c1.acos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(1.997875, 1.528571) + result = c1.acos() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_atan() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(1.502727, 0.094441) + mut result := c1.atan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-1.448307, 0.158997) + result = c1.atan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-1.338973, -0.402359) + result = c1.atan() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_acot() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.068069, -0.094441) + mut result := c1.acot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.122489, -0.158997) + result = c1.acot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.231824, 0.402359) + result = c1.acot() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_asec() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(1.503480, 0.094668) + mut result := c1.asec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(1.689547, 0.160446) + result = c1.asec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(1.757114, -0.396568) + result = c1.asec() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_acsc() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.067317, -0.094668) + mut result := c1.acsc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.118751, -0.160446) + result = c1.acsc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.186318, 0.396568) + result = c1.acsc() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_sinh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(55.941968, 48.754942) + mut result := c1.sinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(6.548120, -7.619232) + result = c1.sinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(0.489056, -1.403119) + result = c1.sinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_cosh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(55.947047, 48.750515) + mut result := c1.cosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-6.580663, 7.581553) + result = c1.cosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.642148, 1.068607) + result = c1.cosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_tanh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.999988, 0.000090) + mut result := c1.tanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-1.000710, 0.004908) + result = c1.tanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-1.166736, 0.243458) + result = c1.tanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_coth() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(1.000012, -0.000090) + mut result := c1.coth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.999267, -0.004901) + result = c1.coth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.821330, -0.171384) + result = c1.coth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_sech() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.010160, -0.008853) + mut result := c1.sech() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.065294, -0.075225) + result = c1.sech() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.413149, -0.687527) + result = c1.sech() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_csch() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.010159, -0.008854) + mut result := c1.csch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(0.064877, 0.075490) + result = c1.csch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(0.221501, 0.635494) + result = c1.csch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_asinh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(2.844098, 0.947341) + mut result := c1.asinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-2.299914, 0.917617) + result = c1.asinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-1.469352, -1.063440) + result = c1.asinh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_acosh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(2.846289, 0.953732) + mut result := c1.acosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(2.305509, 2.204780) + result = c1.acosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(1.528571, -1.997875) + result = c1.acosh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_atanh() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.067066, 1.476056) + mut result := c1.atanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.117501, 1.409921) + result = c1.atanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.173287, -1.178097) + result = c1.atanh() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_acoth() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.067066, -0.094740) + mut result := c1.acoth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.117501, -0.160875) + result = c1.acoth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.173287, 0.392699) + result = c1.acoth() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +// fn test_complex_asech() { +// // Tests were also verified on Wolfram Alpha +// mut c1 := cmplx.complex(5,7) +// mut c2 := cmplx.complex(0.094668,-1.503480) +// mut result := c1.asech() +// // Some issue with precision comparison in f64 using == operator hence serializing to string +// assert result.str() == c2.str() +// c1 = cmplx.complex(-3,4) +// c2 = cmplx.complex(0.160446,-1.689547) +// result = c1.asech() +// // Some issue with precision comparison in f64 using == operator hence serializing to string +// assert result.str() c2.str() +// c1 = cmplx.complex(-1,-2) +// c2 = cmplx.complex(0.396568,1.757114) +// result = c1.asech() +// // Some issue with precision comparison in f64 using == operator hence serializing to string +// assert result.str() == c2.str() +// } + +fn test_complex_acsch() { + // Tests were also verified on Wolfram Alpha + mut c1 := cmplx.complex(5, 7) + mut c2 := cmplx.complex(0.067819, -0.094518) + mut result := c1.acsch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-3, 4) + c2 = cmplx.complex(-0.121246, -0.159507) + result = c1.acsch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() + c1 = cmplx.complex(-1, -2) + c2 = cmplx.complex(-0.215612, 0.401586) + result = c1.acsch() + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert result.str() == c2.str() +} + +fn test_complex_re_im() { + c := cmplx.complex(2.1, 9.05) + assert c.re == 2.1 + assert c.im == 9.05 +} diff --git a/v_windows/v/vlib/math/const.v b/v_windows/v/vlib/math/const.v new file mode 100644 index 0000000..7c473f7 --- /dev/null +++ b/v_windows/v/vlib/math/const.v @@ -0,0 +1,51 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module math + +pub const ( + e = 2.71828182845904523536028747135266249775724709369995957496696763 + pi = 3.14159265358979323846264338327950288419716939937510582097494459 + pi_2 = pi / 2.0 + pi_4 = pi / 4.0 + phi = 1.61803398874989484820458683436563811772030917980576286213544862 + tau = 6.28318530717958647692528676655900576839433879875021164194988918 + sqrt2 = 1.41421356237309504880168872420969807856967187537694807317667974 + sqrt_e = 1.64872127070012814684865078781416357165377610071014801157507931 + sqrt_pi = 1.77245385090551602729816748334114518279754945612238712821380779 + sqrt_tau = 2.50662827463100050241576528481104525300698674060993831662992357 + sqrt_phi = 1.27201964951406896425242246173749149171560804184009624861664038 + ln2 = 0.693147180559945309417232121458176568075500134360255254120680009 + log2_e = 1.0 / ln2 + ln10 = 2.30258509299404568401799145468436420760110148862877297603332790 + log10_e = 1.0 / ln10 +) + +// Floating-point limit values +// max is the largest finite value representable by the type. +// smallest_non_zero is the smallest positive, non-zero value representable by the type. +pub const ( + max_f32 = 3.40282346638528859811704183484516925440e+38 // 2**127 * (2**24 - 1) / 2**23 + smallest_non_zero_f32 = 1.401298464324817070923729583289916131280e-45 // 1 / 2**(127 - 1 + 23) + max_f64 = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52 + smallest_non_zero_f64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52) +) + +// Integer limit values +pub const ( + max_i8 = 127 + min_i8 = -128 + max_i16 = 32767 + min_i16 = -32768 + max_i32 = 2147483647 + min_i32 = -2147483648 + // -9223372036854775808 is wrong because C compilers parse literal values + // without sign first, and 9223372036854775808 overflows i64, hence the + // consecutive subtraction by 1 + min_i64 = i64(-9223372036854775807 - 1) + max_i64 = i64(9223372036854775807) + max_u8 = 255 + max_u16 = 65535 + max_u32 = u32(4294967295) + max_u64 = u64(18446744073709551615) +) diff --git a/v_windows/v/vlib/math/div.c.v b/v_windows/v/vlib/math/div.c.v new file mode 100644 index 0000000..90cee1a --- /dev/null +++ b/v_windows/v/vlib/math/div.c.v @@ -0,0 +1,9 @@ +module math + +fn C.fmod(x f64, y f64) f64 + +// fmod returns the floating-point remainder of number / denom (rounded towards zero): +[inline] +pub fn fmod(x f64, y f64) f64 { + return C.fmod(x, y) +} diff --git a/v_windows/v/vlib/math/div.v b/v_windows/v/vlib/math/div.v new file mode 100644 index 0000000..6ad3c0d --- /dev/null +++ b/v_windows/v/vlib/math/div.v @@ -0,0 +1,87 @@ +module math + +// Floating-point mod function. +// mod returns the floating-point remainder of x/y. +// The magnitude of the result is less than y and its +// sign agrees with that of x. +// +// special cases are: +// mod(±inf, y) = nan +// mod(nan, y) = nan +// mod(x, 0) = nan +// mod(x, ±inf) = x +// mod(x, nan) = nan +pub fn mod(x f64, y f64) f64 { + return fmod(x, y) +} + +// fmod returns the floating-point remainder of number / denom (rounded towards zero) +pub fn fmod(x f64, y f64) f64 { + if y == 0 || is_inf(x, 0) || is_nan(x) || is_nan(y) { + return nan() + } + abs_y := abs(y) + abs_y_fr, abs_y_exp := frexp(abs_y) + mut r := x + if x < 0 { + r = -x + } + for r >= abs_y { + rfr, mut rexp := frexp(r) + if rfr < abs_y_fr { + rexp = rexp - 1 + } + r = r - ldexp(abs_y, rexp - abs_y_exp) + } + if x < 0 { + r = -r + } + return r +} + +// gcd calculates greatest common (positive) divisor (or zero if a and b are both zero). +pub fn gcd(a_ i64, b_ i64) i64 { + mut a := a_ + mut b := b_ + if a < 0 { + a = -a + } + if b < 0 { + b = -b + } + for b != 0 { + a %= b + if a == 0 { + return b + } + b %= a + } + return a +} + +// egcd returns (gcd(a, b), x, y) such that |a*x + b*y| = gcd(a, b) +pub fn egcd(a i64, b i64) (i64, i64, i64) { + mut old_r, mut r := a, b + mut old_s, mut s := i64(1), i64(0) + mut old_t, mut t := i64(0), i64(1) + + for r != 0 { + quot := old_r / r + old_r, r = r, old_r % r + old_s, s = s, old_s - quot * s + old_t, t = t, old_t - quot * t + } + return if old_r < 0 { -old_r } else { old_r }, old_s, old_t +} + +// lcm calculates least common (non-negative) multiple. +pub fn lcm(a i64, b i64) i64 { + if a == 0 { + return a + } + res := a * (b / gcd(b, a)) + if res < 0 { + return -res + } + return res +} diff --git a/v_windows/v/vlib/math/erf.c.v b/v_windows/v/vlib/math/erf.c.v new file mode 100644 index 0000000..1606152 --- /dev/null +++ b/v_windows/v/vlib/math/erf.c.v @@ -0,0 +1,17 @@ +module math + +fn C.erf(x f64) f64 + +fn C.erfc(x f64) f64 + +// erf computes the error function value +[inline] +pub fn erf(a f64) f64 { + return C.erf(a) +} + +// erfc computes the complementary error function value +[inline] +pub fn erfc(a f64) f64 { + return C.erfc(a) +} diff --git a/v_windows/v/vlib/math/erf.v b/v_windows/v/vlib/math/erf.v new file mode 100644 index 0000000..2375789 --- /dev/null +++ b/v_windows/v/vlib/math/erf.v @@ -0,0 +1,327 @@ +module math + +/* +* x + * 2 |\ + * erf(x) = --------- | exp(-t*t)dt + * sqrt(pi) \| + * 0 + * + * erfc(x) = 1-erf(x) + * Note that + * erf(-x) = -erf(x) + * erfc(-x) = 2 - erfc(x) + * + * Method: + * 1. For |x| in [0, 0.84375] + * erf(x) = x + x*R(x**2) + * erfc(x) = 1 - erf(x) if x in [-.84375,0.25] + * = 0.5 + ((0.5-x)-x*R) if x in [0.25,0.84375] + * where R = P/Q where P is an odd poly of degree 8 and + * Q is an odd poly of degree 10. + * -57.90 + * | R - (erf(x)-x)/x | <= 2 + * + * + * Remark. The formula is derived by noting + * erf(x) = (2/sqrt(pi))*(x - x**3/3 + x**5/10 - x**7/42 + ....) + * and that + * 2/sqrt(pi) = 1.128379167095512573896158903121545171688 + * is close to one. The interval is chosen because the fix + * point of erf(x) is near 0.6174 (i.e., erf(x)=x when x is + * near 0.6174), and by some experiment, 0.84375 is chosen to + * guarantee the error is less than one ulp for erf. + * + * 2. For |x| in [0.84375,1.25], let s_ = |x| - 1, and + * c = 0.84506291151 rounded to single (24 bits) + * erf(x) = sign(x) * (c + P1(s_)/Q1(s_)) + * erfc(x) = (1-c) - P1(s_)/Q1(s_) if x > 0 + * 1+(c+P1(s_)/Q1(s_)) if x < 0 + * |P1/Q1 - (erf(|x|)-c)| <= 2**-59.06 + * Remark: here we use the taylor series expansion at x=1. + * erf(1+s_) = erf(1) + s_*Poly(s_) + * = 0.845.. + P1(s_)/Q1(s_) + * That is, we use rational approximation to approximate + * erf(1+s_) - (c = (single)0.84506291151) + * Note that |P1/Q1|< 0.078 for x in [0.84375,1.25] + * where + * P1(s_) = degree 6 poly in s_ + * Q1(s_) = degree 6 poly in s_ + * + * 3. For x in [1.25,1/0.35(~2.857143)], + * erfc(x) = (1/x)*exp(-x*x-0.5625+R1/s1) + * erf(x) = 1 - erfc(x) + * where + * R1(z) = degree 7 poly in z, (z=1/x**2) + * s1(z) = degree 8 poly in z + * + * 4. For x in [1/0.35,28] + * erfc(x) = (1/x)*exp(-x*x-0.5625+R2/s2) if x > 0 + * = 2.0 - (1/x)*exp(-x*x-0.5625+R2/s2) if -6 x >= 28 + * erf(x) = sign(x) *(1 - tiny) (raise inexact) + * erfc(x) = tiny*tiny (raise underflow) if x > 0 + * = 2 - tiny if x<0 + * + * 7. special case: + * erf(0) = 0, erf(inf) = 1, erf(-inf) = -1, + * erfc(0) = 1, erfc(inf) = 0, erfc(-inf) = 2, + * erfc/erf(nan) is nan +*/ +const ( + erx = 8.45062911510467529297e-01 // 0x3FEB0AC160000000 + // Coefficients for approximation to erf in [0, 0.84375] + efx = 1.28379167095512586316e-01 // 0x3FC06EBA8214DB69 + efx8 = 1.02703333676410069053e+00 // 0x3FF06EBA8214DB69 + pp0 = 1.28379167095512558561e-01 // 0x3FC06EBA8214DB68 + pp1 = -3.25042107247001499370e-01 // 0xBFD4CD7D691CB913 + pp2 = -2.84817495755985104766e-02 // 0xBF9D2A51DBD7194F + pp3 = -5.77027029648944159157e-03 // 0xBF77A291236668E4 + pp4 = -2.37630166566501626084e-05 // 0xBEF8EAD6120016AC + qq1 = 3.97917223959155352819e-01 // 0x3FD97779CDDADC09 + qq2 = 6.50222499887672944485e-02 // 0x3FB0A54C5536CEBA + qq3 = 5.08130628187576562776e-03 // 0x3F74D022C4D36B0F + qq4 = 1.32494738004321644526e-04 // 0x3F215DC9221C1A10 + qq5 = -3.96022827877536812320e-06 // 0xBED09C4342A26120 + // Coefficients for approximation to erf in [0.84375, 1.25] + pa0 = -2.36211856075265944077e-03 // 0xBF6359B8BEF77538 + pa1 = 4.14856118683748331666e-01 // 0x3FDA8D00AD92B34D + pa2 = -3.72207876035701323847e-01 // 0xBFD7D240FBB8C3F1 + pa3 = 3.18346619901161753674e-01 // 0x3FD45FCA805120E4 + pa4 = -1.10894694282396677476e-01 // 0xBFBC63983D3E28EC + pa5 = 3.54783043256182359371e-02 // 0x3FA22A36599795EB + pa6 = -2.16637559486879084300e-03 // 0xBF61BF380A96073F + qa1 = 1.06420880400844228286e-01 // 0x3FBB3E6618EEE323 + qa2 = 5.40397917702171048937e-01 // 0x3FE14AF092EB6F33 + qa3 = 7.18286544141962662868e-02 // 0x3FB2635CD99FE9A7 + qa4 = 1.26171219808761642112e-01 // 0x3FC02660E763351F + qa5 = 1.36370839120290507362e-02 // 0x3F8BEDC26B51DD1C + qa6 = 1.19844998467991074170e-02 // 0x3F888B545735151D + // Coefficients for approximation to erfc in [1.25, 1/0.35] + ra0 = -9.86494403484714822705e-03 // 0xBF843412600D6435 + ra1 = -6.93858572707181764372e-01 // 0xBFE63416E4BA7360 + ra2 = -1.05586262253232909814e+01 // 0xC0251E0441B0E726 + ra3 = -6.23753324503260060396e+01 // 0xC04F300AE4CBA38D + ra4 = -1.62396669462573470355e+02 // 0xC0644CB184282266 + ra5 = -1.84605092906711035994e+02 // 0xC067135CEBCCABB2 + ra6 = -8.12874355063065934246e+01 // 0xC054526557E4D2F2 + ra7 = -9.81432934416914548592e+00 // 0xC023A0EFC69AC25C + sa1 = 1.96512716674392571292e+01 // 0x4033A6B9BD707687 + sa2 = 1.37657754143519042600e+02 // 0x4061350C526AE721 + sa3 = 4.34565877475229228821e+02 // 0x407B290DD58A1A71 + sa4 = 6.45387271733267880336e+02 // 0x40842B1921EC2868 + sa5 = 4.29008140027567833386e+02 // 0x407AD02157700314 + sa6 = 1.08635005541779435134e+02 // 0x405B28A3EE48AE2C + sa7 = 6.57024977031928170135e+00 // 0x401A47EF8E484A93 + sa8 = -6.04244152148580987438e-02 // 0xBFAEEFF2EE749A62 + // Coefficients for approximation to erfc in [1/.35, 28] + rb0 = -9.86494292470009928597e-03 // 0xBF84341239E86F4A + rb1 = -7.99283237680523006574e-01 // 0xBFE993BA70C285DE + rb2 = -1.77579549177547519889e+01 // 0xC031C209555F995A + rb3 = -1.60636384855821916062e+02 // 0xC064145D43C5ED98 + rb4 = -6.37566443368389627722e+02 // 0xC083EC881375F228 + rb5 = -1.02509513161107724954e+03 // 0xC09004616A2E5992 + rb6 = -4.83519191608651397019e+02 // 0xC07E384E9BDC383F + sb1 = 3.03380607434824582924e+01 // 0x403E568B261D5190 + sb2 = 3.25792512996573918826e+02 // 0x40745CAE221B9F0A + sb3 = 1.53672958608443695994e+03 // 0x409802EB189D5118 + sb4 = 3.19985821950859553908e+03 // 0x40A8FFB7688C246A + sb5 = 2.55305040643316442583e+03 // 0x40A3F219CEDF3BE6 + sb6 = 4.74528541206955367215e+02 // 0x407DA874E79FE763 + sb7 = -2.24409524465858183362e+01 // 0xC03670E242712D62 +) + +// erf returns the error function of x. +// +// special cases are: +// erf(+inf) = 1 +// erf(-inf) = -1 +// erf(nan) = nan +pub fn erf(a f64) f64 { + mut x := a + very_tiny := 2.848094538889218e-306 // 0x0080000000000000 + small := 1.0 / f64(u64(1) << 28) // 2**-28 + if is_nan(x) { + return nan() + } + if is_inf(x, 1) { + return 1.0 + } + if is_inf(x, -1) { + return f64(-1) + } + mut sign := false + if x < 0 { + x = -x + sign = true + } + if x < 0.84375 { // |x| < 0.84375 + mut temp := 0.0 + if x < small { // |x| < 2**-28 + if x < very_tiny { + temp = 0.125 * (8.0 * x + math.efx8 * x) // avoid underflow + } else { + temp = x + math.efx * x + } + } else { + z := x * x + r := math.pp0 + z * (math.pp1 + z * (math.pp2 + z * (math.pp3 + z * math.pp4))) + s_ := 1.0 + z * (math.qq1 + z * (math.qq2 + z * (math.qq3 + z * (math.qq4 + + z * math.qq5)))) + y := r / s_ + temp = x + x * y + } + if sign { + return -temp + } + return temp + } + if x < 1.25 { // 0.84375 <= |x| < 1.25 + s_ := x - 1 + p := math.pa0 + s_ * (math.pa1 + s_ * (math.pa2 + s_ * (math.pa3 + s_ * (math.pa4 + + s_ * (math.pa5 + s_ * math.pa6))))) + q := 1.0 + s_ * (math.qa1 + s_ * (math.qa2 + s_ * (math.qa3 + s_ * (math.qa4 + + s_ * (math.qa5 + s_ * math.qa6))))) + if sign { + return -math.erx - p / q + } + return math.erx + p / q + } + if x >= 6 { // inf > |x| >= 6 + if sign { + return -1 + } + return 1.0 + } + s_ := 1.0 / (x * x) + mut r := 0.0 + mut s := 0.0 + if x < 1.0 / 0.35 { // |x| < 1 / 0.35 ~ 2.857143 + r = math.ra0 + s_ * (math.ra1 + s_ * (math.ra2 + s_ * (math.ra3 + s_ * (math.ra4 + + s_ * (math.ra5 + s_ * (math.ra6 + s_ * math.ra7)))))) + s = 1.0 + s_ * (math.sa1 + s_ * (math.sa2 + s_ * (math.sa3 + s_ * (math.sa4 + + s_ * (math.sa5 + s_ * (math.sa6 + s_ * (math.sa7 + s_ * math.sa8))))))) + } else { // |x| >= 1 / 0.35 ~ 2.857143 + r = math.rb0 + s_ * (math.rb1 + s_ * (math.rb2 + s_ * (math.rb3 + s_ * (math.rb4 + + s_ * (math.rb5 + s_ * math.rb6))))) + s = 1.0 + s_ * (math.sb1 + s_ * (math.sb2 + s_ * (math.sb3 + s_ * (math.sb4 + + s_ * (math.sb5 + s_ * (math.sb6 + s_ * math.sb7)))))) + } + z := f64_from_bits(f64_bits(x) & 0xffffffff00000000) // pseudo-single (20-bit) precision x + r_ := exp(-z * z - 0.5625) * exp((z - x) * (z + x) + r / s) + if sign { + return r_ / x - 1.0 + } + return 1.0 - r_ / x +} + +// erfc returns the complementary error function of x. +// +// special cases are: +// erfc(+inf) = 0 +// erfc(-inf) = 2 +// erfc(nan) = nan +pub fn erfc(a f64) f64 { + mut x := a + tiny := 1.0 / f64(u64(1) << 56) // 2**-56 + // special cases + if is_nan(x) { + return nan() + } + if is_inf(x, 1) { + return 0.0 + } + if is_inf(x, -1) { + return 2.0 + } + mut sign := false + if x < 0 { + x = -x + sign = true + } + if x < 0.84375 { // |x| < 0.84375 + mut temp := 0.0 + if x < tiny { // |x| < 2**-56 + temp = x + } else { + z := x * x + r := math.pp0 + z * (math.pp1 + z * (math.pp2 + z * (math.pp3 + z * math.pp4))) + s_ := 1.0 + z * (math.qq1 + z * (math.qq2 + z * (math.qq3 + z * (math.qq4 + + z * math.qq5)))) + y := r / s_ + if x < 0.25 { // |x| < 1.0/4 + temp = x + x * y + } else { + temp = 0.5 + (x * y + (x - 0.5)) + } + } + if sign { + return 1.0 + temp + } + return 1.0 - temp + } + if x < 1.25 { // 0.84375 <= |x| < 1.25 + s_ := x - 1 + p := math.pa0 + s_ * (math.pa1 + s_ * (math.pa2 + s_ * (math.pa3 + s_ * (math.pa4 + + s_ * (math.pa5 + s_ * math.pa6))))) + q := 1.0 + s_ * (math.qa1 + s_ * (math.qa2 + s_ * (math.qa3 + s_ * (math.qa4 + + s_ * (math.qa5 + s_ * math.qa6))))) + if sign { + return 1.0 + math.erx + p / q + } + return 1.0 - math.erx - p / q + } + if x < 28 { // |x| < 28 + s_ := 1.0 / (x * x) + mut r := 0.0 + mut s := 0.0 + if x < 1.0 / 0.35 { // |x| < 1 / 0.35 ~ 2.857143 + r = math.ra0 + s_ * (math.ra1 + s_ * (math.ra2 + s_ * (math.ra3 + s_ * (math.ra4 + + s_ * (math.ra5 + s_ * (math.ra6 + s_ * math.ra7)))))) + s = 1.0 + s_ * (math.sa1 + s_ * (math.sa2 + s_ * (math.sa3 + s_ * (math.sa4 + + s_ * (math.sa5 + s_ * (math.sa6 + s_ * (math.sa7 + s_ * math.sa8))))))) + } else { // |x| >= 1 / 0.35 ~ 2.857143 + if sign && x > 6 { + return 2.0 // x < -6 + } + r = math.rb0 + s_ * (math.rb1 + s_ * (math.rb2 + s_ * (math.rb3 + s_ * (math.rb4 + + s_ * (math.rb5 + s_ * math.rb6))))) + s = 1.0 + s_ * (math.sb1 + s_ * (math.sb2 + s_ * (math.sb3 + s_ * (math.sb4 + + s_ * (math.sb5 + s_ * (math.sb6 + s_ * math.sb7)))))) + } + z := f64_from_bits(f64_bits(x) & 0xffffffff00000000) // pseudo-single (20-bit) precision x + r_ := exp(-z * z - 0.5625) * exp((z - x) * (z + x) + r / s) + if sign { + return 2.0 - r_ / x + } + return r_ / x + } + if sign { + return 2.0 + } + return 0.0 +} diff --git a/v_windows/v/vlib/math/erf_test.v b/v_windows/v/vlib/math/erf_test.v new file mode 100644 index 0000000..2102514 --- /dev/null +++ b/v_windows/v/vlib/math/erf_test.v @@ -0,0 +1,29 @@ +module math + +fn test_erf() { + assert is_nan(erf(nan())) + assert tolerance(erf(-1.0), -0.8427007888650501, 1e-8) + assert tolerance(erf(0.0), 0.0, 1e-11) + assert tolerance(erf(1e-15), 0.0000000000000011283791670955126615773132947717431253912942469337536, + 1e-11) + assert tolerance(erf(0.1), 0.11246291601917208, 1e-11) + assert tolerance(erf(0.3), 0.32862677677789676, 1e-7) + assert tolerance(erf(0.5), 0.5204998778130465376827466538919645287364515757579637, + 1e-9) + assert tolerance(erf(1.0), 0.8427007888650501, 1e-8) + assert tolerance(erf(1.5), 0.966105146259005, 1e-9) + assert tolerance(erf(6.0), 0.99999999999999997848026328750108688340664960081261537, + 1e-12) + assert tolerance(erf(5.0), 0.99999999999846254020557196514981165651461662110988195, + 1e-12) + assert tolerance(erf(4.0), 0.999999984582742, 1e-12) + assert tolerance(erf(inf(1)), 1.0, 1e-12) + assert tolerance(erf(inf(-1)), -1.0, 1e-12) +} + +fn test_erfc() { + assert tolerance(erfc(-1.0), 1.84270078886505, 1e-8) + assert tolerance(erfc(0.0), 1.0, 1e-11) + assert tolerance(erfc(0.1), 0.8875370839808279, 1e-11) + assert tolerance(erfc(0.2), 0.7772974103342554, 1e-9) +} diff --git a/v_windows/v/vlib/math/exp.c.v b/v_windows/v/vlib/math/exp.c.v new file mode 100644 index 0000000..7818438 --- /dev/null +++ b/v_windows/v/vlib/math/exp.c.v @@ -0,0 +1,17 @@ +module math + +fn C.exp(x f64) f64 + +fn C.exp2(x f64) f64 + +// exp calculates exponent of the number (math.pow(math.E, x)). +[inline] +pub fn exp(x f64) f64 { + return C.exp(x) +} + +// exp2 returns the base-2 exponential function of a (math.pow(2, x)). +[inline] +pub fn exp2(x f64) f64 { + return C.exp2(x) +} diff --git a/v_windows/v/vlib/math/exp.js.v b/v_windows/v/vlib/math/exp.js.v new file mode 100644 index 0000000..cf41f61 --- /dev/null +++ b/v_windows/v/vlib/math/exp.js.v @@ -0,0 +1,12 @@ +module math + +fn JS.Math.exp(x f64) f64 + +// exp calculates exponent of the number (math.pow(math.E, x)). +[inline] +pub fn exp(x f64) f64 { + mut res := 0.0 + #res.val = Math.exp(x) + + return res +} diff --git a/v_windows/v/vlib/math/exp.v b/v_windows/v/vlib/math/exp.v new file mode 100644 index 0000000..7a11eb6 --- /dev/null +++ b/v_windows/v/vlib/math/exp.v @@ -0,0 +1,214 @@ +module math + +import math.internal + +const ( + f64_max_exp = f64(1024) + f64_min_exp = f64(-1021) + threshold = 7.09782712893383973096e+02 // 0x40862E42FEFA39EF + ln2_x56 = 3.88162421113569373274e+01 // 0x4043687a9f1af2b1 + ln2_halfx3 = 1.03972077083991796413e+00 // 0x3ff0a2b23f3bab73 + ln2_half = 3.46573590279972654709e-01 // 0x3fd62e42fefa39ef + ln2hi = 6.93147180369123816490e-01 // 0x3fe62e42fee00000 + ln2lo = 1.90821492927058770002e-10 // 0x3dea39ef35793c76 + inv_ln2 = 1.44269504088896338700e+00 // 0x3ff71547652b82fe + // scaled coefficients related to expm1 + expm1_q1 = -3.33333333333331316428e-02 // 0xBFA11111111110F4 + expm1_q2 = 1.58730158725481460165e-03 // 0x3F5A01A019FE5585 + expm1_q3 = -7.93650757867487942473e-05 // 0xBF14CE199EAADBB7 + expm1_q4 = 4.00821782732936239552e-06 // 0x3ED0CFCA86E65239 + expm1_q5 = -2.01099218183624371326e-07 // 0xBE8AFDB76E09C32D +) + +// exp returns e**x, the base-e exponential of x. +// +// special cases are: +// exp(+inf) = +inf +// exp(nan) = nan +// Very large values overflow to 0 or +inf. +// Very small values underflow to 1. +pub fn exp(x f64) f64 { + log2e := 1.44269504088896338700e+00 + overflow := 7.09782712893383973096e+02 + underflow := -7.45133219101941108420e+02 + near_zero := 1.0 / (1 << 28) // 2**-28 + // special cases + if is_nan(x) || is_inf(x, 1) { + return x + } + if is_inf(x, -1) { + return 0.0 + } + if x > overflow { + return inf(1) + } + if x < underflow { + return 0.0 + } + if -near_zero < x && x < near_zero { + return 1.0 + x + } + // reduce; computed as r = hi - lo for extra precision. + mut k := 0 + if x < 0 { + k = int(log2e * x - 0.5) + } + if x > 0 { + k = int(log2e * x + 0.5) + } + hi := x - f64(k) * math.ln2hi + lo := f64(k) * math.ln2lo + // compute + return expmulti(hi, lo, k) +} + +// exp2 returns 2**x, the base-2 exponential of x. +// +// special cases are the same as exp. +pub fn exp2(x f64) f64 { + overflow := 1.0239999999999999e+03 + underflow := -1.0740e+03 + if is_nan(x) || is_inf(x, 1) { + return x + } + if is_inf(x, -1) { + return 0 + } + if x > overflow { + return inf(1) + } + if x < underflow { + return 0 + } + // argument reduction; x = r×lg(e) + k with |r| ≤ ln(2)/2. + // computed as r = hi - lo for extra precision. + mut k := 0 + if x > 0 { + k = int(x + 0.5) + } + if x < 0 { + k = int(x - 0.5) + } + mut t := x - f64(k) + hi := t * math.ln2hi + lo := -t * math.ln2lo + // compute + return expmulti(hi, lo, k) +} + +pub fn ldexp(x f64, e int) f64 { + if x == 0.0 { + return x + } else { + mut y, ex := frexp(x) + mut e2 := f64(e + ex) + if e2 >= math.f64_max_exp { + y *= pow(2.0, e2 - math.f64_max_exp + 1.0) + e2 = math.f64_max_exp - 1.0 + } else if e2 <= math.f64_min_exp { + y *= pow(2.0, e2 - math.f64_min_exp - 1.0) + e2 = math.f64_min_exp + 1.0 + } + return y * pow(2.0, e2) + } +} + +// frexp breaks f into a normalized fraction +// and an integral power of two. +// It returns frac and exp satisfying f == frac × 2**exp, +// with the absolute value of frac in the interval [½, 1). +// +// special cases are: +// frexp(±0) = ±0, 0 +// frexp(±inf) = ±inf, 0 +// frexp(nan) = nan, 0 +// pub fn frexp(f f64) (f64, int) { +// // special cases +// if f == 0.0 { +// return f, 0 // correctly return -0 +// } +// if is_inf(f, 0) || is_nan(f) { +// return f, 0 +// } +// f_norm, mut exp := normalize(f) +// mut x := f64_bits(f_norm) +// exp += int((x>>shift)&mask) - bias + 1 +// x &= ~(mask << shift) +// x |= (-1 + bias) << shift +// return f64_from_bits(x), exp +pub fn frexp(x f64) (f64, int) { + if x == 0.0 { + return 0.0, 0 + } else if !is_finite(x) { + return x, 0 + } else if abs(x) >= 0.5 && abs(x) < 1 { // Handle the common case + return x, 0 + } else { + ex := ceil(log(abs(x)) / ln2) + mut ei := int(ex) // Prevent underflow and overflow of 2**(-ei) + if ei < int(math.f64_min_exp) { + ei = int(math.f64_min_exp) + } + if ei > -int(math.f64_min_exp) { + ei = -int(math.f64_min_exp) + } + mut f := x * pow(2.0, -ei) + if !is_finite(f) { // This should not happen + return f, 0 + } + for abs(f) >= 1.0 { + ei++ + f /= 2.0 + } + for abs(f) > 0 && abs(f) < 0.5 { + ei-- + f *= 2.0 + } + return f, ei + } +} + +// special cases are: +// expm1(+inf) = +inf +// expm1(-inf) = -1 +// expm1(nan) = nan +pub fn expm1(x f64) f64 { + if is_inf(x, 1) || is_nan(x) { + return x + } + if is_inf(x, -1) { + return f64(-1) + } + // FIXME: this should be improved + if abs(x) < ln2 { // Compute the taylor series S = x + (1/2!) x^2 + (1/3!) x^3 + ... + mut i := 1.0 + mut sum := x + mut term := x / 1.0 + i++ + term *= x / f64(i) + sum += term + for abs(term) > abs(sum) * internal.f64_epsilon { + i++ + term *= x / f64(i) + sum += term + } + return sum + } else { + return exp(x) - 1 + } +} + +// exp1 returns e**r × 2**k where r = hi - lo and |r| ≤ ln(2)/2. +fn expmulti(hi f64, lo f64, k int) f64 { + exp_p1 := 1.66666666666666657415e-01 // 0x3FC55555; 0x55555555 + exp_p2 := -2.77777777770155933842e-03 // 0xBF66C16C; 0x16BEBD93 + exp_p3 := 6.61375632143793436117e-05 // 0x3F11566A; 0xAF25DE2C + exp_p4 := -1.65339022054652515390e-06 // 0xBEBBBD41; 0xC5D26BF1 + exp_p5 := 4.13813679705723846039e-08 // 0x3E663769; 0x72BEA4D0 + r := hi - lo + t := r * r + c := r - t * (exp_p1 + t * (exp_p2 + t * (exp_p3 + t * (exp_p4 + t * exp_p5)))) + y := 1 - ((lo - (r * c) / (2 - c)) - hi) + // TODO(rsc): make sure ldexp can handle boundary k + return ldexp(y, k) +} diff --git a/v_windows/v/vlib/math/factorial.v b/v_windows/v/vlib/math/factorial.v new file mode 100644 index 0000000..116e083 --- /dev/null +++ b/v_windows/v/vlib/math/factorial.v @@ -0,0 +1,55 @@ +module math + +// factorial calculates the factorial of the provided value. +pub fn factorial(n f64) f64 { + // For a large postive argument (n >= factorials_table.len) return max_f64 + if n >= factorials_table.len { + return max_f64 + } + // Otherwise return n!. + if n == f64(i64(n)) && n >= 0.0 { + return factorials_table[i64(n)] + } + return gamma(n + 1.0) +} + +// log_factorial calculates the log-factorial of the provided value. +pub fn log_factorial(n f64) f64 { + // For a large postive argument (n < 0) return max_f64 + if n < 0 { + return -max_f64 + } + // If n < N then return ln(n!). + if n != f64(i64(n)) { + return log_gamma(n + 1) + } else if n < log_factorials_table.len { + return log_factorials_table[i64(n)] + } + // Otherwise return asymptotic expansion of ln(n!). + return log_factorial_asymptotic_expansion(int(n)) +} + +fn log_factorial_asymptotic_expansion(n int) f64 { + m := 6 + mut term := []f64{} + xx := f64((n + 1) * (n + 1)) + mut xj := f64(n + 1) + log_factorial := log_sqrt_2pi - xj + (xj - 0.5) * log(xj) + mut i := 0 + for i = 0; i < m; i++ { + term << bernoulli[i] / xj + xj *= xx + } + mut sum := term[m - 1] + for i = m - 2; i >= 0; i-- { + if abs(sum) <= abs(term[i]) { + break + } + sum = term[i] + } + for i >= 0 { + sum += term[i] + i-- + } + return log_factorial + sum +} diff --git a/v_windows/v/vlib/math/factorial_tables.v b/v_windows/v/vlib/math/factorial_tables.v new file mode 100644 index 0000000..5154b21 --- /dev/null +++ b/v_windows/v/vlib/math/factorial_tables.v @@ -0,0 +1,711 @@ +module math + +const ( + log_sqrt_2pi = 9.18938533204672741780329736e-1 + bernoulli = [ + /* + Bernoulli numbers B(2),B(4),B(6),...,B(20). Only B(2),...,B(10) currently + * used. + */ + 1.0 / (6.0 * 2.0 * 1.0), + -1.0 / (30.0 * 4.0 * 3.0), + 1.0 / (42.0 * 6.0 * 5.0), + -1.0 / (30.0 * 8.0 * 7.0), + 5.0 / (66.0 * 10.0 * 9.0), + -691.0 / (2730.0 * 12.0 * 11.0), + 7.0 / (6.0 * 14.0 * 13.0), + -3617.0 / (510.0 * 16.0 * 15.0), + 43867.0 / (796.0 * 18.0 * 17.0), + -174611.0 / (330.0 * 20.0 * 19.0), + ] + factorials_table = [ + // 0! + 1.000000000000000000000e+0, + // 1! + 1.000000000000000000000e+0, + // 2! + 2.000000000000000000000e+0, + // 3! + 6.000000000000000000000e+0, + // 4! + 2.400000000000000000000e+1, + // 5! + 1.200000000000000000000e+2, + // 6! + 7.200000000000000000000e+2, + // 7! + 5.040000000000000000000e+3, + // 8! + 4.032000000000000000000e+4, + // 9! + 3.628800000000000000000e+5, + // 10! + 3.628800000000000000000e+6, + // 11! + 3.991680000000000000000e+7, + // 12! + 4.790016000000000000000e+8, + // 13! + 6.227020800000000000000e+9, + // 14! + 8.717829120000000000000e+10, + // 15! + 1.307674368000000000000e+12, + // 16! + 2.092278988800000000000e+13, + // 17! + 3.556874280960000000000e+14, + // 18! + 6.402373705728000000000e+15, + // 19! + 1.216451004088320000000e+17, + // 20! + 2.432902008176640000000e+18, + // 21! + 5.109094217170944000000e+19, + // 22! + 1.124000727777607680000e+21, + // 23! + 2.585201673888497664000e+22, + // 24! + 6.204484017332394393600e+23, + // 25! + 1.551121004333098598400e+25, + // 26! + 4.032914611266056355840e+26, + // 27! + 1.088886945041835216077e+28, + // 28! + 3.048883446117138605015e+29, + // 29! + 8.841761993739701954544e+30, + // 30! + 2.652528598121910586363e+32, + // 31! + 8.222838654177922817726e+33, + // 32! + 2.631308369336935301672e+35, + // 33! + 8.683317618811886495518e+36, + // 34! + 2.952327990396041408476e+38, + // 35! + 1.033314796638614492967e+40, + // 36! + 3.719933267899012174680e+41, + // 37! + 1.376375309122634504632e+43, + // 38! + 5.230226174666011117600e+44, + // 39! + 2.039788208119744335864e+46, + // 40! + 8.159152832478977343456e+47, + // 41! + 3.345252661316380710817e+49, + // 42! + 1.405006117752879898543e+51, + // 43! + 6.041526306337383563736e+52, + // 44! + 2.658271574788448768044e+54, + // 45! + 1.196222208654801945620e+56, + // 46! + 5.502622159812088949850e+57, + // 47! + 2.586232415111681806430e+59, + // 48! + 1.241391559253607267086e+61, + // 49! + 6.082818640342675608723e+62, + // 50! + 3.041409320171337804361e+64, + // 51! + 1.551118753287382280224e+66, + // 52! + 8.065817517094387857166e+67, + // 53! + 4.274883284060025564298e+69, + // 54! + 2.308436973392413804721e+71, + // 55! + 1.269640335365827592597e+73, + // 56! + 7.109985878048634518540e+74, + // 57! + 4.052691950487721675568e+76, + // 58! + 2.350561331282878571829e+78, + // 59! + 1.386831185456898357379e+80, + // 60! + 8.320987112741390144276e+81, + // 61! + 5.075802138772247988009e+83, + // 62! + 3.146997326038793752565e+85, + // 63! + 1.982608315404440064116e+87, + // 64! + 1.268869321858841641034e+89, + // 65! + 8.247650592082470666723e+90, + // 66! + 5.443449390774430640037e+92, + // 67! + 3.647111091818868528825e+94, + // 68! + 2.480035542436830599601e+96, + // 69! + 1.711224524281413113725e+98, + // 70! + 1.197857166996989179607e+100, + // 71! + 8.504785885678623175212e+101, + // 72! + 6.123445837688608686152e+103, + // 73! + 4.470115461512684340891e+105, + // 74! + 3.307885441519386412260e+107, + // 75! + 2.480914081139539809195e+109, + // 76! + 1.885494701666050254988e+111, + // 77! + 1.451830920282858696341e+113, + // 78! + 1.132428117820629783146e+115, + // 79! + 8.946182130782975286851e+116, + // 80! + 7.156945704626380229481e+118, + // 81! + 5.797126020747367985880e+120, + // 82! + 4.753643337012841748421e+122, + // 83! + 3.945523969720658651190e+124, + // 84! + 3.314240134565353266999e+126, + // 85! + 2.817104114380550276949e+128, + // 86! + 2.422709538367273238177e+130, + // 87! + 2.107757298379527717214e+132, + // 88! + 1.854826422573984391148e+134, + // 89! + 1.650795516090846108122e+136, + // 90! + 1.485715964481761497310e+138, + // 91! + 1.352001527678402962552e+140, + // 92! + 1.243841405464130725548e+142, + // 93! + 1.156772507081641574759e+144, + // 94! + 1.087366156656743080274e+146, + // 95! + 1.032997848823905926260e+148, + // 96! + 9.916779348709496892096e+149, + // 97! + 9.619275968248211985333e+151, + // 98! + 9.426890448883247745626e+153, + // 99! + 9.332621544394415268170e+155, + // 100! + 9.332621544394415268170e+157, + // 101! + 9.425947759838359420852e+159, + // 102! + 9.614466715035126609269e+161, + // 103! + 9.902900716486180407547e+163, + // 104! + 1.029901674514562762385e+166, + // 105! + 1.081396758240290900504e+168, + // 106! + 1.146280563734708354534e+170, + // 107! + 1.226520203196137939352e+172, + // 108! + 1.324641819451828974500e+174, + // 109! + 1.443859583202493582205e+176, + // 110! + 1.588245541522742940425e+178, + // 111! + 1.762952551090244663872e+180, + // 112! + 1.974506857221074023537e+182, + // 113! + 2.231192748659813646597e+184, + // 114! + 2.543559733472187557120e+186, + // 115! + 2.925093693493015690688e+188, + // 116! + 3.393108684451898201198e+190, + // 117! + 3.969937160808720895402e+192, + // 118! + 4.684525849754290656574e+194, + // 119! + 5.574585761207605881323e+196, + // 120! + 6.689502913449127057588e+198, + // 121! + 8.094298525273443739682e+200, + // 122! + 9.875044200833601362412e+202, + // 123! + 1.214630436702532967577e+205, + // 124! + 1.506141741511140879795e+207, + // 125! + 1.882677176888926099744e+209, + // 126! + 2.372173242880046885677e+211, + // 127! + 3.012660018457659544810e+213, + // 128! + 3.856204823625804217357e+215, + // 129! + 4.974504222477287440390e+217, + // 130! + 6.466855489220473672507e+219, + // 131! + 8.471580690878820510985e+221, + // 132! + 1.118248651196004307450e+224, + // 133! + 1.487270706090685728908e+226, + // 134! + 1.992942746161518876737e+228, + // 135! + 2.690472707318050483595e+230, + // 136! + 3.659042881952548657690e+232, + // 137! + 5.012888748274991661035e+234, + // 138! + 6.917786472619488492228e+236, + // 139! + 9.615723196941089004197e+238, + // 140! + 1.346201247571752460588e+241, + // 141! + 1.898143759076170969429e+243, + // 142! + 2.695364137888162776589e+245, + // 143! + 3.854370717180072770522e+247, + // 144! + 5.550293832739304789551e+249, + // 145! + 8.047926057471991944849e+251, + // 146! + 1.174997204390910823948e+254, + // 147! + 1.727245890454638911203e+256, + // 148! + 2.556323917872865588581e+258, + // 149! + 3.808922637630569726986e+260, + // 150! + 5.713383956445854590479e+262, + // 151! + 8.627209774233240431623e+264, + // 152! + 1.311335885683452545607e+267, + // 153! + 2.006343905095682394778e+269, + // 154! + 3.089769613847350887959e+271, + // 155! + 4.789142901463393876336e+273, + // 156! + 7.471062926282894447084e+275, + // 157! + 1.172956879426414428192e+278, + // 158! + 1.853271869493734796544e+280, + // 159! + 2.946702272495038326504e+282, + // 160! + 4.714723635992061322407e+284, + // 161! + 7.590705053947218729075e+286, + // 162! + 1.229694218739449434110e+289, + // 163! + 2.004401576545302577600e+291, + // 164! + 3.287218585534296227263e+293, + // 165! + 5.423910666131588774984e+295, + // 166! + 9.003691705778437366474e+297, + // 167! + 1.503616514864999040201e+300, + // 168! + 2.526075744973198387538e+302, + // 169! + 4.269068009004705274939e+304, + // 170! + 7.257415615307998967397e+306, + ] + log_factorials_table = [ + // 0! + 0.000000000000000000000e+0, + // 1! + 0.000000000000000000000e+0, + // 2! + 6.931471805599453094172e-1, + // 3! + 1.791759469228055000812e+0, + // 4! + 3.178053830347945619647e+0, + // 5! + 4.787491742782045994248e+0, + // 6! + 6.579251212010100995060e+0, + // 7! + 8.525161361065414300166e+0, + // 8! + 1.060460290274525022842e+1, + // 9! + 1.280182748008146961121e+1, + // 10! + 1.510441257307551529523e+1, + // 11! + 1.750230784587388583929e+1, + // 12! + 1.998721449566188614952e+1, + // 13! + 2.255216385312342288557e+1, + // 14! + 2.519122118273868150009e+1, + // 15! + 2.789927138384089156609e+1, + // 16! + 3.067186010608067280376e+1, + // 17! + 3.350507345013688888401e+1, + // 18! + 3.639544520803305357622e+1, + // 19! + 3.933988418719949403622e+1, + // 20! + 4.233561646075348502966e+1, + // 21! + 4.538013889847690802616e+1, + // 22! + 4.847118135183522387964e+1, + // 23! + 5.160667556776437357045e+1, + // 24! + 5.478472939811231919009e+1, + // 25! + 5.800360522298051993929e+1, + // 26! + 6.126170176100200198477e+1, + // 27! + 6.455753862700633105895e+1, + // 28! + 6.788974313718153498289e+1, + // 29! + 7.125703896716800901007e+1, + // 30! + 7.465823634883016438549e+1, + // 31! + 7.809222355331531063142e+1, + // 32! + 8.155795945611503717850e+1, + // 33! + 8.505446701758151741396e+1, + // 34! + 8.858082754219767880363e+1, + // 35! + 9.213617560368709248333e+1, + // 36! + 9.571969454214320248496e+1, + // 37! + 9.933061245478742692933e+1, + // 38! + 1.029681986145138126988e+2, + // 39! + 1.066317602606434591262e+2, + // 40! + 1.103206397147573954291e+2, + // 41! + 1.140342117814617032329e+2, + // 42! + 1.177718813997450715388e+2, + // 43! + 1.215330815154386339623e+2, + // 44! + 1.253172711493568951252e+2, + // 45! + 1.291239336391272148826e+2, + // 46! + 1.329525750356163098828e+2, + // 47! + 1.368027226373263684696e+2, + // 48! + 1.406739236482342593987e+2, + // 49! + 1.445657439463448860089e+2, + // 50! + 1.484777669517730320675e+2, + // 51! + 1.524095925844973578392e+2, + // 52! + 1.563608363030787851941e+2, + // 53! + 1.603311282166309070282e+2, + // 54! + 1.643201122631951814118e+2, + // 55! + 1.683274454484276523305e+2, + // 56! + 1.723527971391628015638e+2, + // 57! + 1.763958484069973517152e+2, + // 58! + 1.804562914175437710518e+2, + // 59! + 1.845338288614494905025e+2, + // 60! + 1.886281734236715911873e+2, + // 61! + 1.927390472878449024360e+2, + // 62! + 1.968661816728899939914e+2, + // 63! + 2.010093163992815266793e+2, + // 64! + 2.051681994826411985358e+2, + // 65! + 2.093425867525368356464e+2, + // 66! + 2.135322414945632611913e+2, + // 67! + 2.177369341139542272510e+2, + // 68! + 2.219564418191303339501e+2, + // 69! + 2.261905483237275933323e+2, + // 70! + 2.304390435657769523214e+2, + // 71! + 2.347017234428182677427e+2, + // 72! + 2.389783895618343230538e+2, + // 73! + 2.432688490029827141829e+2, + // 74! + 2.475729140961868839366e+2, + // 75! + 2.518904022097231943772e+2, + // 76! + 2.562211355500095254561e+2, + // 77! + 2.605649409718632093053e+2, + // 78! + 2.649216497985528010421e+2, + // 79! + 2.692910976510198225363e+2, + // 80! + 2.736731242856937041486e+2, + // 81! + 2.780675734403661429141e+2, + // 82! + 2.824742926876303960274e+2, + // 83! + 2.868931332954269939509e+2, + // 84! + 2.913239500942703075662e+2, + // 85! + 2.957666013507606240211e+2, + // 86! + 3.002209486470141317540e+2, + // 87! + 3.046868567656687154726e+2, + // 88! + 3.091641935801469219449e+2, + // 89! + 3.136528299498790617832e+2, + // 90! + 3.181526396202093268500e+2, + // 91! + 3.226634991267261768912e+2, + // 92! + 3.271852877037752172008e+2, + // 93! + 3.317178871969284731381e+2, + // 94! + 3.362611819791984770344e+2, + // 95! + 3.408150588707990178690e+2, + // 96! + 3.453794070622668541074e+2, + // 97! + 3.499541180407702369296e+2, + // 98! + 3.545390855194408088492e+2, + // 99! + 3.591342053695753987760e+2, + // 100! + 3.637393755555634901441e+2, + // 101! + 3.683544960724047495950e+2, + // 102! + 3.729794688856890206760e+2, + // 103! + 3.776141978739186564468e+2, + // 104! + 3.822585887730600291111e+2, + // 105! + 3.869125491232175524822e+2, + // 106! + 3.915759882173296196258e+2, + // 107! + 3.962488170517915257991e+2, + // 108! + 4.009309482789157454921e+2, + // 109! + 4.056222961611448891925e+2, + // 110! + 4.103227765269373054205e+2, + // 111! + 4.150323067282496395563e+2, + // 112! + 4.197508055995447340991e+2, + // 113! + 4.244781934182570746677e+2, + // 114! + 4.292143918666515701285e+2, + // 115! + 4.339593239950148201939e+2, + // 116! + 4.387129141861211848399e+2, + // 117! + 4.434750881209189409588e+2, + // 118! + 4.482457727453846057188e+2, + // 119! + 4.530248962384961351041e+2, + // 120! + 4.578123879812781810984e+2, + // 121! + 4.626081785268749221865e+2, + // 122! + 4.674121995716081787447e+2, + // 123! + 4.722243839269805962399e+2, + // 124! + 4.770446654925856331047e+2, + // 125! + 4.818729792298879342285e+2, + // 126! + 4.867092611368394122258e+2, + // 127! + 4.915534482232980034989e+2, + // 128! + 4.964054784872176206648e+2, + // 129! + 5.012652908915792927797e+2, + // 130! + 5.061328253420348751997e+2, + // 131! + 5.110080226652360267439e+2, + // 132! + 5.158908245878223975982e+2, + // 133! + 5.207811737160441513633e+2, + // 134! + 5.256790135159950627324e+2, + // 135! + 5.305842882944334921812e+2, + // 136! + 5.354969431801695441897e+2, + // 137! + 5.404169241059976691050e+2, + // 138! + 5.453441777911548737966e+2, + // 139! + 5.502786517242855655538e+2, + // 140! + 5.552202941468948698523e+2, + // 141! + 5.601690540372730381305e+2, + // 142! + 5.651248810948742988613e+2, + // 143! + 5.700877257251342061414e+2, + // 144! + 5.750575390247102067619e+2, + // 145! + 5.800342727671307811636e+2, + // 146! + 5.850178793888391176022e+2, + // 147! + 5.900083119756178539038e+2, + // 148! + 5.950055242493819689670e+2, + // 149! + 6.000094705553274281080e+2, + // 150! + 6.050201058494236838580e+2, + // 151! + 6.100373856862386081868e+2, + // 152! + 6.150612662070848845750e+2, + // 153! + 6.200917041284773200381e+2, + // 154! + 6.251286567308909491967e+2, + // 155! + 6.301720818478101958172e+2, + // 156! + 6.352219378550597328635e+2, + // 157! + 6.402781836604080409209e+2, + // 158! + 6.453407786934350077245e+2, + // 159! + 6.504096828956552392500e+2, + // 160! + 6.554848567108890661717e+2, + // 161! + 6.605662610758735291676e+2, + // 162! + 6.656538574111059132426e+2, + // 163! + 6.707476076119126755767e+2, + // 164! + 6.758474740397368739994e+2, + // 165! + 6.809534195136374546094e+2, + // 166! + 6.860654073019939978423e+2, + // 167! + 6.911834011144107529496e+2, + // 168! + 6.963073650938140118743e+2, + // 169! + 7.014372638087370853465e+2, + // 170! + 7.065730622457873471107e+2, + // 171! + 7.117147258022900069535e+2, + ] +) diff --git a/v_windows/v/vlib/math/factorial_test.v b/v_windows/v/vlib/math/factorial_test.v new file mode 100644 index 0000000..77c2043 --- /dev/null +++ b/v_windows/v/vlib/math/factorial_test.v @@ -0,0 +1,13 @@ +module math + +fn test_factorial() { + assert factorial(12) == 479001600 + assert factorial(5) == 120 + assert factorial(0) == 1 +} + +fn test_log_factorial() { + assert log_factorial(12) == log(479001600) + assert log_factorial(5) == log(120) + assert log_factorial(0) == log(1) +} diff --git a/v_windows/v/vlib/math/floor.c.v b/v_windows/v/vlib/math/floor.c.v new file mode 100644 index 0000000..1dc9330 --- /dev/null +++ b/v_windows/v/vlib/math/floor.c.v @@ -0,0 +1,34 @@ +module math + +fn C.ceil(x f64) f64 + +fn C.floor(x f64) f64 + +fn C.round(x f64) f64 + +fn C.trunc(x f64) f64 + +// ceil returns the nearest f64 greater or equal to the provided value. +[inline] +pub fn ceil(x f64) f64 { + return C.ceil(x) +} + +// floor returns the nearest f64 lower or equal of the provided value. +[inline] +pub fn floor(x f64) f64 { + return C.floor(x) +} + +// round returns the integer nearest to the provided value. +[inline] +pub fn round(x f64) f64 { + return C.round(x) +} + +// trunc rounds a toward zero, returning the nearest integral value that is not +// larger in magnitude than a. +[inline] +pub fn trunc(x f64) f64 { + return C.trunc(x) +} diff --git a/v_windows/v/vlib/math/floor.js.v b/v_windows/v/vlib/math/floor.js.v new file mode 100644 index 0000000..6c995d1 --- /dev/null +++ b/v_windows/v/vlib/math/floor.js.v @@ -0,0 +1,34 @@ +module math + +fn JS.Math.ceil(x f64) f64 + +fn JS.Math.floor(x f64) f64 + +fn JS.Math.round(x f64) f64 + +fn JS.Math.trunc(x f64) f64 + +// ceil returns the nearest f64 greater or equal to the provided value. +[inline] +pub fn ceil(x f64) f64 { + return JS.Math.ceil(x) +} + +// floor returns the nearest f64 lower or equal of the provided value. +[inline] +pub fn floor(x f64) f64 { + return JS.Math.floor(x) +} + +// round returns the integer nearest to the provided value. +[inline] +pub fn round(x f64) f64 { + return JS.Math.round(x) +} + +// trunc rounds a toward zero, returning the nearest integral value that is not +// larger in magnitude than a. +[inline] +pub fn trunc(x f64) f64 { + return JS.Math.trunc(x) +} diff --git a/v_windows/v/vlib/math/floor.v b/v_windows/v/vlib/math/floor.v new file mode 100644 index 0000000..a2c3208 --- /dev/null +++ b/v_windows/v/vlib/math/floor.v @@ -0,0 +1,105 @@ +module math + +// floor returns the greatest integer value less than or equal to x. +// +// special cases are: +// floor(±0) = ±0 +// floor(±inf) = ±inf +// floor(nan) = nan +pub fn floor(x f64) f64 { + if x == 0 || is_nan(x) || is_inf(x, 0) { + return x + } + if x < 0 { + mut d, fract := modf(-x) + if fract != 0.0 { + d = d + 1 + } + return -d + } + d, _ := modf(x) + return d +} + +// ceil returns the least integer value greater than or equal to x. +// +// special cases are: +// ceil(±0) = ±0 +// ceil(±inf) = ±inf +// ceil(nan) = nan +pub fn ceil(x f64) f64 { + return -floor(-x) +} + +// trunc returns the integer value of x. +// +// special cases are: +// trunc(±0) = ±0 +// trunc(±inf) = ±inf +// trunc(nan) = nan +pub fn trunc(x f64) f64 { + if x == 0 || is_nan(x) || is_inf(x, 0) { + return x + } + d, _ := modf(x) + return d +} + +// round returns the nearest integer, rounding half away from zero. +// +// special cases are: +// round(±0) = ±0 +// round(±inf) = ±inf +// round(nan) = nan +pub fn round(x f64) f64 { + if x == 0 || is_nan(x) || is_inf(x, 0) { + return x + } + // Largest integer <= x + mut y := floor(x) // Fractional part + mut r := x - y // Round up to nearest. + if r > 0.5 { + unsafe { + goto rndup + } + } + // Round to even + if r == 0.5 { + r = y - 2.0 * floor(0.5 * y) + if r == 1.0 { + rndup: + y += 1.0 + } + } + // Else round down. + return y +} + +// round_to_even returns the nearest integer, rounding ties to even. +// +// special cases are: +// round_to_even(±0) = ±0 +// round_to_even(±inf) = ±inf +// round_to_even(nan) = nan +pub fn round_to_even(x f64) f64 { + mut bits := f64_bits(x) + mut e_ := (bits >> shift) & mask + if e_ >= bias { + // round abs(x) >= 1. + // - Large numbers without fractional components, infinity, and nan are unchanged. + // - Add 0.499.. or 0.5 before truncating depending on whether the truncated + // number is even or odd (respectively). + half_minus_ulp := u64(u64(1) << (shift - 1)) - 1 + e_ -= u64(bias) + bits += (half_minus_ulp + (bits >> (shift - e_)) & 1) >> e_ + bits &= frac_mask >> e_ + bits ^= frac_mask >> e_ + } else if e_ == bias - 1 && bits & frac_mask != 0 { + // round 0.5 < abs(x) < 1. + bits = bits & sign_mask | uvone // +-1 + } else { + // round abs(x) <= 0.5 including denormals. + bits &= sign_mask // +-0 + } + return f64_from_bits(bits) +} diff --git a/v_windows/v/vlib/math/fractions/approximations.v b/v_windows/v/vlib/math/fractions/approximations.v new file mode 100644 index 0000000..dd4f855 --- /dev/null +++ b/v_windows/v/vlib/math/fractions/approximations.v @@ -0,0 +1,119 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fractions + +import math + +const ( + default_eps = 1.0e-4 + max_iterations = 50 + zero = fraction(0, 1) +) + +// ------------------------------------------------------------------------ +// Unwrapped evaluation methods for fast evaluation of continued fractions. +// ------------------------------------------------------------------------ +// We need these functions because the evaluation of continued fractions +// always has to be done from the end. Also, the numerator-denominator pairs +// are generated from front to end. This means building a result from a +// previous one isn't possible. So we need unrolled versions to ensure that +// we don't take too much of a performance penalty by calling eval_cf +// several times. +// ------------------------------------------------------------------------ +// eval_1 returns the result of evaluating a continued fraction series of length 1 +fn eval_1(whole i64, d []i64) Fraction { + return fraction(whole * d[0] + 1, d[0]) +} + +// eval_2 returns the result of evaluating a continued fraction series of length 2 +fn eval_2(whole i64, d []i64) Fraction { + den := d[0] * d[1] + 1 + return fraction(whole * den + d[1], den) +} + +// eval_3 returns the result of evaluating a continued fraction series of length 3 +fn eval_3(whole i64, d []i64) Fraction { + d1d2_plus_n2 := d[1] * d[2] + 1 + den := d[0] * d1d2_plus_n2 + d[2] + return fraction(whole * den + d1d2_plus_n2, den) +} + +// eval_cf evaluates a continued fraction series and returns a Fraction. +fn eval_cf(whole i64, den []i64) Fraction { + count := den.len + // Offload some small-scale calculations + // to dedicated functions + match count { + 1 { + return eval_1(whole, den) + } + 2 { + return eval_2(whole, den) + } + 3 { + return eval_3(whole, den) + } + else { + last := count - 1 + mut n := i64(1) + mut d := den[last] + // The calculations are done from back to front + for index := count - 2; index >= 0; index-- { + t := d + d = den[index] * d + n + n = t + } + return fraction(d * whole + n, d) + } + } +} + +// approximate returns a Fraction that approcimates the given value to +// within the default epsilon value (1.0e-4). This means the result will +// be accurate to 3 places after the decimal. +pub fn approximate(val f64) Fraction { + return approximate_with_eps(val, fractions.default_eps) +} + +// approximate_with_eps returns a Fraction +pub fn approximate_with_eps(val f64, eps f64) Fraction { + if val == 0.0 { + return fractions.zero + } + if eps < 0.0 { + panic('Epsilon value cannot be negative.') + } + if math.fabs(val) > math.max_i64 { + panic('Value out of range.') + } + // The integer part is separated first. Then we process the fractional + // part to generate numerators and denominators in tandem. + whole := i64(val) + mut frac := val - f64(whole) + // Quick exit for integers + if frac == 0.0 { + return fraction(whole, 1) + } + mut d := []i64{} + mut partial := fractions.zero + // We must complete the approximation within the maximum number of + // itertations allowed. If we can't panic. + // Empirically tested: the hardest constant to approximate is the + // golden ratio (math.phi) and for f64s, it only needs 38 iterations. + for _ in 0 .. fractions.max_iterations { + // We calculate the reciprocal. That's why the numerator is + // always 1. + frac = 1.0 / frac + den := i64(frac) + d << den + // eval_cf is called often so it needs to be performant + partial = eval_cf(whole, d) + // Check if we're done + if math.fabs(val - partial.f64()) < eps { + return partial + } + frac -= f64(den) + } + panic("Couldn't converge. Please create an issue on https://github.com/vlang/v") +} diff --git a/v_windows/v/vlib/math/fractions/approximations_test.v b/v_windows/v/vlib/math/fractions/approximations_test.v new file mode 100644 index 0000000..5ee92bf --- /dev/null +++ b/v_windows/v/vlib/math/fractions/approximations_test.v @@ -0,0 +1,189 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import math.fractions +import math + +fn test_half() { + float_val := 0.5 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(1, 2)) +} + +fn test_third() { + float_val := 1.0 / 3.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(1, 3)) +} + +fn test_minus_one_twelfth() { + float_val := -1.0 / 12.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(-1, 12)) +} + +fn test_zero() { + float_val := 0.0 + println('Pre') + fract_val := fractions.approximate(float_val) + println('Post') + assert fract_val.equals(fractions.fraction(0, 1)) +} + +fn test_minus_one() { + float_val := -1.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(-1, 1)) +} + +fn test_thirty_three() { + float_val := 33.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(33, 1)) +} + +fn test_millionth() { + float_val := 1.0 / 1000000.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(1, 1000000)) +} + +fn test_minus_27_by_57() { + float_val := -27.0 / 57.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(-27, 57)) +} + +fn test_29_by_104() { + float_val := 29.0 / 104.0 + fract_val := fractions.approximate(float_val) + assert fract_val.equals(fractions.fraction(29, 104)) +} + +fn test_140710_232() { + float_val := 140710.232 + fract_val := fractions.approximate(float_val) + // Approximation will match perfectly for upto 3 places after the decimal + // The result will be within default_eps of original value + assert fract_val.f64() == float_val +} + +fn test_pi_1_digit() { + assert fractions.approximate_with_eps(math.pi, 5.0e-2).equals(fractions.fraction(22, + 7)) +} + +fn test_pi_2_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-3).equals(fractions.fraction(22, + 7)) +} + +fn test_pi_3_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-4).equals(fractions.fraction(333, + 106)) +} + +fn test_pi_4_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-5).equals(fractions.fraction(355, + 113)) +} + +fn test_pi_5_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-6).equals(fractions.fraction(355, + 113)) +} + +fn test_pi_6_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-7).equals(fractions.fraction(355, + 113)) +} + +fn test_pi_7_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-8).equals(fractions.fraction(103993, + 33102)) +} + +fn test_pi_8_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-9).equals(fractions.fraction(103993, + 33102)) +} + +fn test_pi_9_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-10).equals(fractions.fraction(104348, + 33215)) +} + +fn test_pi_10_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-11).equals(fractions.fraction(312689, + 99532)) +} + +fn test_pi_11_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-12).equals(fractions.fraction(1146408, + 364913)) +} + +fn test_pi_12_digits() { + assert fractions.approximate_with_eps(math.pi, 5.0e-13).equals(fractions.fraction(4272943, + 1360120)) +} + +fn test_phi_1_digit() { + assert fractions.approximate_with_eps(math.phi, 5.0e-2).equals(fractions.fraction(5, + 3)) +} + +fn test_phi_2_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-3).equals(fractions.fraction(21, + 13)) +} + +fn test_phi_3_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-4).equals(fractions.fraction(55, + 34)) +} + +fn test_phi_4_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-5).equals(fractions.fraction(233, + 144)) +} + +fn test_phi_5_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-6).equals(fractions.fraction(610, + 377)) +} + +fn test_phi_6_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-7).equals(fractions.fraction(1597, + 987)) +} + +fn test_phi_7_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-8).equals(fractions.fraction(6765, + 4181)) +} + +fn test_phi_8_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-9).equals(fractions.fraction(17711, + 10946)) +} + +fn test_phi_9_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-10).equals(fractions.fraction(75025, + 46368)) +} + +fn test_phi_10_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-11).equals(fractions.fraction(196418, + 121393)) +} + +fn test_phi_11_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-12).equals(fractions.fraction(514229, + 317811)) +} + +fn test_phi_12_digits() { + assert fractions.approximate_with_eps(math.phi, 5.0e-13).equals(fractions.fraction(2178309, + 1346269)) +} diff --git a/v_windows/v/vlib/math/fractions/fraction.v b/v_windows/v/vlib/math/fractions/fraction.v new file mode 100644 index 0000000..69c8f63 --- /dev/null +++ b/v_windows/v/vlib/math/fractions/fraction.v @@ -0,0 +1,259 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fractions + +import math +import math.bits + +// Fraction Struct +// --------------- +// A Fraction has a numerator (n) and a denominator (d). If the user uses +// the helper functions in this module, then the following are guaranteed: +// 1. If the user provides n and d with gcd(n, d) > 1, the fraction will +// not be reduced automatically. +// 2. d cannot be set to zero. The factory function will panic. +// 3. If provided d is negative, it will be made positive. n will change as well. +struct Fraction { +pub: + n i64 + d i64 + is_reduced bool +} + +// A factory function for creating a Fraction, adds a boundary condition +// to ensure that the denominator is non-zero. It automatically converts +// the negative denominator to positive and adjusts the numerator. +// NOTE: Fractions created are not reduced by default. +pub fn fraction(n i64, d i64) Fraction { + if d == 0 { + panic('Denominator cannot be zero') + } + // The denominator is always guaranteed to be positive (and non-zero). + if d < 0 { + return fraction(-n, -d) + } + return Fraction{ + n: n + d: d + is_reduced: math.gcd(n, d) == 1 + } +} + +// To String method +pub fn (f Fraction) str() string { + return '$f.n/$f.d' +} + +// +// + ---------------------+ +// | Arithmetic functions.| +// + ---------------------+ +// +// These are implemented from Knuth, TAOCP Vol 2. Section 4.5 +// +// Returns a correctly reduced result for both addition and subtraction +// NOTE: requires reduced inputs +fn general_addition_result(f1 Fraction, f2 Fraction, addition bool) Fraction { + d1 := math.gcd(f1.d, f2.d) + // d1 happens to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D) + if d1 == 1 { + num1n2d := f1.n * f2.d + num1d2n := f1.d * f2.n + n := if addition { num1n2d + num1d2n } else { num1n2d - num1d2n } + return Fraction{ + n: n + d: f1.d * f2.d + is_reduced: true + } + } + // Here d1 > 1. + f1den := f1.d / d1 + f2den := f2.d / d1 + term1 := f1.n * f2den + term2 := f2.n * f1den + t := if addition { term1 + term2 } else { term1 - term2 } + d2 := math.gcd(t, d1) + return Fraction{ + n: t / d2 + d: f1den * (f2.d / d2) + is_reduced: true + } +} + +// Fraction add using operator overloading +pub fn (f1 Fraction) + (f2 Fraction) Fraction { + return general_addition_result(f1.reduce(), f2.reduce(), true) +} + +// Fraction subtract using operator overloading +pub fn (f1 Fraction) - (f2 Fraction) Fraction { + return general_addition_result(f1.reduce(), f2.reduce(), false) +} + +// Returns a correctly reduced result for both multiplication and division +// NOTE: requires reduced inputs +fn general_multiplication_result(f1 Fraction, f2 Fraction, multiplication bool) Fraction { + // * Theorem: If f1 and f2 are reduced i.e. gcd(f1.n, f1.d) == 1 and gcd(f2.n, f2.d) == 1, + // then gcd(f1.n * f2.n, f1.d * f2.d) == gcd(f1.n, f2.d) * gcd(f1.d, f2.n) + // * Knuth poses this an exercise for 4.5.1. - Exercise 2 + // * Also, note that: + // The terms are flipped for multiplication and division, so the gcds must be calculated carefully + // We do multiple divisions in order to prevent any possible overflows. + // * One more thing: + // if d = gcd(a, b) for example, then d divides both a and b + if multiplication { + d1 := math.gcd(f1.n, f2.d) + d2 := math.gcd(f1.d, f2.n) + return Fraction{ + n: (f1.n / d1) * (f2.n / d2) + d: (f2.d / d1) * (f1.d / d2) + is_reduced: true + } + } else { + d1 := math.gcd(f1.n, f2.n) + d2 := math.gcd(f1.d, f2.d) + return Fraction{ + n: (f1.n / d1) * (f2.d / d2) + d: (f2.n / d1) * (f1.d / d2) + is_reduced: true + } + } +} + +// Fraction multiply using operator overloading +pub fn (f1 Fraction) * (f2 Fraction) Fraction { + return general_multiplication_result(f1.reduce(), f2.reduce(), true) +} + +// Fraction divide using operator overloading +pub fn (f1 Fraction) / (f2 Fraction) Fraction { + if f2.n == 0 { + panic('Cannot divide by zero') + } + // If the second fraction is negative, it will + // mess up the sign. We need positive denominator + if f2.n < 0 { + return f1.negate() / f2.negate() + } + return general_multiplication_result(f1.reduce(), f2.reduce(), false) +} + +// Fraction negate method +pub fn (f Fraction) negate() Fraction { + return Fraction{ + n: -f.n + d: f.d + is_reduced: f.is_reduced + } +} + +// Fraction reciprocal method +pub fn (f Fraction) reciprocal() Fraction { + if f.n == 0 { + panic('Denominator cannot be zero') + } + return Fraction{ + n: f.d + d: f.n + is_reduced: f.is_reduced + } +} + +// Fraction method which reduces the fraction +pub fn (f Fraction) reduce() Fraction { + if f.is_reduced { + return f + } + cf := math.gcd(f.n, f.d) + return Fraction{ + n: f.n / cf + d: f.d / cf + is_reduced: true + } +} + +// f64 converts the Fraction to 64-bit floating point +pub fn (f Fraction) f64() f64 { + return f64(f.n) / f64(f.d) +} + +// +// + ------------------+ +// | Utility functions.| +// + ------------------+ +// +// Returns the absolute value of an i64 +fn abs(num i64) i64 { + if num < 0 { + return -num + } else { + return num + } +} + +// cmp_i64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise +fn cmp_i64s(a i64, b i64) int { + if a == b { + return 0 + } else if a > b { + return 1 + } else { + return -1 + } +} + +// cmp_f64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise +fn cmp_f64s(a f64, b f64) int { + // V uses epsilon comparison internally + if a == b { + return 0 + } else if a > b { + return 1 + } else { + return -1 + } +} + +// Two integers are safe to multiply when their bit lengths +// sum up to less than 64 (conservative estimate). +fn safe_to_multiply(a i64, b i64) bool { + return (bits.len_64(u64(abs(a))) + bits.len_64(u64(abs(b)))) < 64 +} + +// cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise +fn cmp(f1 Fraction, f2 Fraction) int { + if safe_to_multiply(f1.n, f2.d) && safe_to_multiply(f2.n, f1.d) { + return cmp_i64s(f1.n * f2.d, f2.n * f1.d) + } else { + return cmp_f64s(f1.f64(), f2.f64()) + } +} + +// +-----------------------------+ +// | Public comparison functions | +// +-----------------------------+ +// equals returns true if both the Fractions are equal +pub fn (f1 Fraction) equals(f2 Fraction) bool { + return cmp(f1, f2) == 0 +} + +// ge returns true if f1 >= f2 +pub fn (f1 Fraction) ge(f2 Fraction) bool { + return cmp(f1, f2) >= 0 +} + +// gt returns true if f1 > f2 +pub fn (f1 Fraction) gt(f2 Fraction) bool { + return cmp(f1, f2) > 0 +} + +// le returns true if f1 <= f2 +pub fn (f1 Fraction) le(f2 Fraction) bool { + return cmp(f1, f2) <= 0 +} + +// lt returns true if f1 < f2 +pub fn (f1 Fraction) lt(f2 Fraction) bool { + return cmp(f1, f2) < 0 +} diff --git a/v_windows/v/vlib/math/fractions/fraction_test.v b/v_windows/v/vlib/math/fractions/fraction_test.v new file mode 100644 index 0000000..4928b7c --- /dev/null +++ b/v_windows/v/vlib/math/fractions/fraction_test.v @@ -0,0 +1,269 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import math.fractions + +// (Old) results are verified using https://www.calculatorsoup.com/calculators/math/fractions.php +// Newer ones are contrived for corner cases or prepared by hand. +fn test_4_by_8_f64_and_str() { + f := fractions.fraction(4, 8) + assert f.f64() == 0.5 + assert f.str() == '4/8' +} + +fn test_10_by_5_f64_and_str() { + f := fractions.fraction(10, 5) + assert f.f64() == 2.0 + assert f.str() == '10/5' +} + +fn test_9_by_3_f64_and_str() { + f := fractions.fraction(9, 3) + assert f.f64() == 3.0 + assert f.str() == '9/3' +} + +fn test_4_by_minus_5_f64_and_str() { + f := fractions.fraction(4, -5) + assert f.f64() == -0.8 + assert f.str() == '-4/5' +} + +fn test_minus_7_by_minus_92_str() { + f := fractions.fraction(-7, -5) + assert f.str() == '7/5' +} + +fn test_4_by_8_plus_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(5, 10) + sum := f1 + f2 + assert sum.f64() == 1.0 + assert sum.str() == '1/1' + assert sum.equals(fractions.fraction(1, 1)) +} + +fn test_5_by_5_plus_8_by_8() { + f1 := fractions.fraction(5, 5) + f2 := fractions.fraction(8, 8) + sum := f1 + f2 + assert sum.f64() == 2.0 + assert sum.str() == '2/1' + assert sum.equals(fractions.fraction(2, 1)) +} + +fn test_9_by_3_plus_1_by_3() { + f1 := fractions.fraction(9, 3) + f2 := fractions.fraction(1, 3) + sum := f1 + f2 + assert sum.str() == '10/3' + assert sum.equals(fractions.fraction(10, 3)) +} + +fn test_3_by_7_plus_1_by_4() { + f1 := fractions.fraction(3, 7) + f2 := fractions.fraction(1, 4) + sum := f1 + f2 + assert sum.str() == '19/28' + assert sum.equals(fractions.fraction(19, 28)) +} + +fn test_36529_by_12409100000_plus_418754901_by_9174901000() { + f1 := fractions.fraction(i64(36529), i64(12409100000)) + f2 := fractions.fraction(i64(418754901), i64(9174901000)) + sum := f1 + f2 + assert sum.str() == '5196706591957729/113852263999100000' +} + +fn test_4_by_8_plus_minus_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(-5, 10) + diff := f2 + f1 + assert diff.f64() == 0 + assert diff.str() == '0/1' +} + +fn test_4_by_8_minus_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(5, 10) + diff := f2 - f1 + assert diff.f64() == 0 + assert diff.str() == '0/1' +} + +fn test_5_by_5_minus_8_by_8() { + f1 := fractions.fraction(5, 5) + f2 := fractions.fraction(8, 8) + diff := f2 - f1 + assert diff.f64() == 0 + assert diff.str() == '0/1' +} + +fn test_9_by_3_minus_1_by_3() { + f1 := fractions.fraction(9, 3) + f2 := fractions.fraction(1, 3) + diff := f1 - f2 + assert diff.str() == '8/3' +} + +fn test_3_by_7_minus_1_by_4() { + f1 := fractions.fraction(3, 7) + f2 := fractions.fraction(1, 4) + diff := f1 - f2 + assert diff.str() == '5/28' +} + +fn test_36529_by_12409100000_minus_418754901_by_9174901000() { + f1 := fractions.fraction(i64(36529), i64(12409100000)) + f2 := fractions.fraction(i64(418754901), i64(9174901000)) + sum := f1 - f2 + assert sum.str() == '-5196036292040471/113852263999100000' +} + +fn test_4_by_8_times_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(5, 10) + product := f1 * f2 + assert product.f64() == 0.25 + assert product.str() == '1/4' +} + +fn test_5_by_5_times_8_by_8() { + f1 := fractions.fraction(5, 5) + f2 := fractions.fraction(8, 8) + product := f1 * f2 + assert product.f64() == 1.0 + assert product.str() == '1/1' +} + +fn test_9_by_3_times_1_by_3() { + f1 := fractions.fraction(9, 3) + f2 := fractions.fraction(1, 3) + product := f1 * f2 + assert product.f64() == 1.0 + assert product.str() == '1/1' +} + +fn test_3_by_7_times_1_by_4() { + f1 := fractions.fraction(3, 7) + f2 := fractions.fraction(1, 4) + product := f2 * f1 + assert product.f64() == (3.0 / 28.0) + assert product.str() == '3/28' +} + +fn test_4_by_8_over_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(5, 10) + q := f1 / f2 + assert q.f64() == 1.0 + assert q.str() == '1/1' +} + +fn test_5_by_5_over_8_by_8() { + f1 := fractions.fraction(5, 5) + f2 := fractions.fraction(8, 8) + q := f1 / f2 + assert q.f64() == 1.0 + assert q.str() == '1/1' +} + +fn test_9_by_3_over_1_by_3() { + f1 := fractions.fraction(9, 3) + f2 := fractions.fraction(1, 3) + q := f1 / f2 + assert q.f64() == 9.0 + assert q.str() == '9/1' +} + +fn test_3_by_7_over_1_by_4() { + f1 := fractions.fraction(3, 7) + f2 := fractions.fraction(1, 4) + q := f1 / f2 + assert q.str() == '12/7' +} + +fn test_reciprocal_4_by_8() { + f := fractions.fraction(4, 8) + assert f.reciprocal().str() == '8/4' +} + +fn test_reciprocal_5_by_10() { + f := fractions.fraction(5, 10) + assert f.reciprocal().str() == '10/5' +} + +fn test_reciprocal_5_by_5() { + f := fractions.fraction(5, 5) + assert f.reciprocal().str() == '5/5' +} + +fn test_reciprocal_8_by_8() { + f := fractions.fraction(8, 8) + assert f.reciprocal().str() == '8/8' +} + +fn test_reciprocal_9_by_3() { + f := fractions.fraction(9, 3) + assert f.reciprocal().str() == '3/9' +} + +fn test_reciprocal_1_by_3() { + f := fractions.fraction(1, 3) + assert f.reciprocal().str() == '3/1' +} + +fn test_reciprocal_7_by_3() { + f := fractions.fraction(7, 3) + assert f.reciprocal().str() == '3/7' +} + +fn test_reciprocal_1_by_4() { + f := fractions.fraction(1, 4) + assert f.reciprocal().str() == '4/1' +} + +fn test_4_by_8_equals_5_by_10() { + f1 := fractions.fraction(4, 8) + f2 := fractions.fraction(5, 10) + assert f1.equals(f2) +} + +fn test_1_by_2_does_not_equal_3_by_4() { + f1 := fractions.fraction(1, 2) + f2 := fractions.fraction(3, 4) + assert !f1.equals(f2) +} + +fn test_reduce_3_by_9() { + f := fractions.fraction(3, 9) + assert f.reduce().equals(fractions.fraction(1, 3)) +} + +fn test_1_by_3_less_than_2_by_4() { + f1 := fractions.fraction(1, 3) + f2 := fractions.fraction(2, 4) + assert f1.lt(f2) + assert f1.le(f2) +} + +fn test_2_by_3_greater_than_2_by_4() { + f1 := fractions.fraction(2, 3) + f2 := fractions.fraction(2, 4) + assert f1.gt(f2) + assert f1.ge(f2) +} + +fn test_5_by_7_not_less_than_2_by_4() { + f1 := fractions.fraction(5, 7) + f2 := fractions.fraction(2, 4) + assert !f1.lt(f2) + assert !f1.le(f2) +} + +fn test_49_by_75_not_greater_than_2_by_3() { + f1 := fractions.fraction(49, 75) + f2 := fractions.fraction(2, 3) + assert !f1.gt(f2) + assert !f1.ge(f2) +} diff --git a/v_windows/v/vlib/math/gamma.c.v b/v_windows/v/vlib/math/gamma.c.v new file mode 100644 index 0000000..a4451ce --- /dev/null +++ b/v_windows/v/vlib/math/gamma.c.v @@ -0,0 +1,17 @@ +module math + +fn C.tgamma(x f64) f64 + +fn C.lgamma(x f64) f64 + +// gamma computes the gamma function value +[inline] +pub fn gamma(a f64) f64 { + return C.tgamma(a) +} + +// log_gamma computes the log-gamma function value +[inline] +pub fn log_gamma(x f64) f64 { + return C.lgamma(x) +} diff --git a/v_windows/v/vlib/math/gamma.v b/v_windows/v/vlib/math/gamma.v new file mode 100644 index 0000000..e0061db --- /dev/null +++ b/v_windows/v/vlib/math/gamma.v @@ -0,0 +1,335 @@ +module math + +// gamma function computed by Stirling's formula. +// The pair of results must be multiplied together to get the actual answer. +// The multiplication is left to the caller so that, if careful, the caller can avoid +// infinity for 172 <= x <= 180. +// The polynomial is valid for 33 <= x <= 172 larger values are only used +// in reciprocal and produce denormalized floats. The lower precision there +// masks any imprecision in the polynomial. +fn stirling(x f64) (f64, f64) { + if x > 200 { + return inf(1), 1.0 + } + sqrt_two_pi := 2.506628274631000502417 + max_stirling := 143.01608 + mut w := 1.0 / x + w = 1.0 + w * ((((gamma_s[0] * w + gamma_s[1]) * w + gamma_s[2]) * w + gamma_s[3]) * w + + gamma_s[4]) + mut y1 := exp(x) + mut y2 := 1.0 + if x > max_stirling { // avoid Pow() overflow + v := pow(x, 0.5 * x - 0.25) + y1_ := y1 + y1 = v + y2 = v / y1_ + } else { + y1 = pow(x, x - 0.5) / y1 + } + return y1, f64(sqrt_two_pi) * w * y2 +} + +// gamma returns the gamma function of x. +// +// special ifs are: +// gamma(+inf) = +inf +// gamma(+0) = +inf +// gamma(-0) = -inf +// gamma(x) = nan for integer x < 0 +// gamma(-inf) = nan +// gamma(nan) = nan +pub fn gamma(a f64) f64 { + mut x := a + euler := 0.57721566490153286060651209008240243104215933593992 // A001620 + if is_neg_int(x) || is_inf(x, -1) || is_nan(x) { + return nan() + } + if is_inf(x, 1) { + return inf(1) + } + if x == 0.0 { + return copysign(inf(1), x) + } + mut q := abs(x) + mut p := floor(q) + if q > 33 { + if x >= 0 { + y1, y2 := stirling(x) + return y1 * y2 + } + // Note: x is negative but (checked above) not a negative integer, + // so x must be small enough to be in range for conversion to i64. + // If |x| were >= 2⁶³ it would have to be an integer. + mut signgam := 1 + ip := i64(p) + if (ip & 1) == 0 { + signgam = -1 + } + mut z := q - p + if z > 0.5 { + p = p + 1 + z = q - p + } + z = q * sin(pi * z) + if z == 0 { + return inf(signgam) + } + sq1, sq2 := stirling(q) + absz := abs(z) + d := absz * sq1 * sq2 + if is_inf(d, 0) { + z = pi / absz / sq1 / sq2 + } else { + z = pi / d + } + return f64(signgam) * z + } + // Reduce argument + mut z := 1.0 + for x >= 3 { + x = x - 1 + z = z * x + } + for x < 0 { + if x > -1e-09 { + unsafe { + goto small + } + } + z = z / x + x = x + 1 + } + for x < 2 { + if x < 1e-09 { + unsafe { + goto small + } + } + z = z / x + x = x + 1 + } + if x == 2 { + return z + } + x = x - 2 + p = (((((x * gamma_p[0] + gamma_p[1]) * x + gamma_p[2]) * x + gamma_p[3]) * x + + gamma_p[4]) * x + gamma_p[5]) * x + gamma_p[6] + q = ((((((x * gamma_q[0] + gamma_q[1]) * x + gamma_q[2]) * x + gamma_q[3]) * x + + gamma_q[4]) * x + gamma_q[5]) * x + gamma_q[6]) * x + gamma_q[7] + if true { + return z * p / q + } + small: + if x == 0 { + return inf(1) + } + return z / ((1.0 + euler * x) * x) +} + +// log_gamma returns the natural logarithm and sign (-1 or +1) of Gamma(x). +// +// special ifs are: +// log_gamma(+inf) = +inf +// log_gamma(0) = +inf +// log_gamma(-integer) = +inf +// log_gamma(-inf) = -inf +// log_gamma(nan) = nan +pub fn log_gamma(x f64) f64 { + y, _ := log_gamma_sign(x) + return y +} + +pub fn log_gamma_sign(a f64) (f64, int) { + mut x := a + ymin := 1.461632144968362245 + tiny := exp2(-70) + two52 := exp2(52) // 0x4330000000000000 ~4.5036e+15 + two58 := exp2(58) // 0x4390000000000000 ~2.8823e+17 + tc := 1.46163214496836224576e+00 // 0x3FF762D86356BE3F + tf := -1.21486290535849611461e-01 // 0xBFBF19B9BCC38A42 + // tt := -(tail of tf) + tt := -3.63867699703950536541e-18 // 0xBC50C7CAA48A971F + mut sign := 1 + if is_nan(x) { + return x, sign + } + if is_inf(x, 1) { + return x, sign + } + if x == 0.0 { + return inf(1), sign + } + mut neg := false + if x < 0 { + x = -x + neg = true + } + if x < tiny { // if |x| < 2**-70, return -log(|x|) + if neg { + sign = -1 + } + return -log(x), sign + } + mut nadj := 0.0 + if neg { + if x >= two52 { + // x| >= 2**52, must be -integer + return inf(1), sign + } + t := sin_pi(x) + if t == 0 { + return inf(1), sign + } + nadj = log(pi / abs(t * x)) + if t < 0 { + sign = -1 + } + } + mut lgamma := 0.0 + if x == 1 || x == 2 { // purge off 1 and 2 + return 0.0, sign + } else if x < 2 { // use lgamma(x) = lgamma(x+1) - log(x) + mut y := 0.0 + mut i := 0 + if x <= 0.9 { + lgamma = -log(x) + if x >= (ymin - 1 + 0.27) { // 0.7316 <= x <= 0.9 + y = 1.0 - x + i = 0 + } else if x >= (ymin - 1 - 0.27) { // 0.2316 <= x < 0.7316 + y = x - (tc - 1) + i = 1 + } else { // 0 < x < 0.2316 + y = x + i = 2 + } + } else { + lgamma = 0 + if x >= (ymin + 0.27) { // 1.7316 <= x < 2 + y = f64(2) - x + i = 0 + } else if x >= (ymin - 0.27) { // 1.2316 <= x < 1.7316 + y = x - tc + i = 1 + } else { // 0.9 < x < 1.2316 + y = x - 1 + i = 2 + } + } + if i == 0 { + z := y * y + gamma_p1 := lgamma_a[0] + z * (lgamma_a[2] + z * (lgamma_a[4] + z * (lgamma_a[6] + + z * (lgamma_a[8] + z * lgamma_a[10])))) + gamma_p2 := z * (lgamma_a[1] + z * (lgamma_a[3] + z * (lgamma_a[5] + z * (lgamma_a[7] + + z * (lgamma_a[9] + z * lgamma_a[11]))))) + p := y * gamma_p1 + gamma_p2 + lgamma += (p - 0.5 * y) + } else if i == 1 { + z := y * y + w := z * y + gamma_p1 := lgamma_t[0] + w * (lgamma_t[3] + w * (lgamma_t[6] + w * (lgamma_t[9] + + w * lgamma_t[12]))) // parallel comp + gamma_p2 := lgamma_t[1] + w * (lgamma_t[4] + w * (lgamma_t[7] + w * (lgamma_t[10] + + w * lgamma_t[13]))) + gamma_p3 := lgamma_t[2] + w * (lgamma_t[5] + w * (lgamma_t[8] + w * (lgamma_t[11] + + w * lgamma_t[14]))) + p := z * gamma_p1 - (tt - w * (gamma_p2 + y * gamma_p3)) + lgamma += (tf + p) + } else if i == 2 { + gamma_p1 := y * (lgamma_u[0] + y * (lgamma_u[1] + y * (lgamma_u[2] + y * (lgamma_u[3] + + y * (lgamma_u[4] + y * lgamma_u[5]))))) + gamma_p2 := 1.0 + y * (lgamma_v[1] + y * (lgamma_v[2] + y * (lgamma_v[3] + + y * (lgamma_v[4] + y * lgamma_v[5])))) + lgamma += (-0.5 * y + gamma_p1 / gamma_p2) + } + } else if x < 8 { // 2 <= x < 8 + i := int(x) + y := x - f64(i) + p := y * (lgamma_s[0] + y * (lgamma_s[1] + y * (lgamma_s[2] + y * (lgamma_s[3] + + y * (lgamma_s[4] + y * (lgamma_s[5] + y * lgamma_s[6])))))) + q := 1.0 + y * (lgamma_r[1] + y * (lgamma_r[2] + y * (lgamma_r[3] + y * (lgamma_r[4] + + y * (lgamma_r[5] + y * lgamma_r[6]))))) + lgamma = 0.5 * y + p / q + mut z := 1.0 // lgamma(1+s) = log(s) + lgamma(s) + if i == 7 { + z *= (y + 6) + z *= (y + 5) + z *= (y + 4) + z *= (y + 3) + z *= (y + 2) + lgamma += log(z) + } else if i == 6 { + z *= (y + 5) + z *= (y + 4) + z *= (y + 3) + z *= (y + 2) + lgamma += log(z) + } else if i == 5 { + z *= (y + 4) + z *= (y + 3) + z *= (y + 2) + lgamma += log(z) + } else if i == 4 { + z *= (y + 3) + z *= (y + 2) + lgamma += log(z) + } else if i == 3 { + z *= (y + 2) + lgamma += log(z) + } + } else if x < two58 { // 8 <= x < 2**58 + t := log(x) + z := 1.0 / x + y := z * z + w := lgamma_w[0] + z * (lgamma_w[1] + y * (lgamma_w[2] + y * (lgamma_w[3] + + y * (lgamma_w[4] + y * (lgamma_w[5] + y * lgamma_w[6]))))) + lgamma = (x - 0.5) * (t - 1.0) + w + } else { // 2**58 <= x <= Inf + lgamma = x * (log(x) - 1.0) + } + if neg { + lgamma = nadj - lgamma + } + return lgamma, sign +} + +// sin_pi(x) is a helper function for negative x +fn sin_pi(x_ f64) f64 { + mut x := x_ + two52 := exp2(52) // 0x4330000000000000 ~4.5036e+15 + two53 := exp2(53) // 0x4340000000000000 ~9.0072e+15 + if x < 0.25 { + return -sin(pi * x) + } + // argument reduction + mut z := floor(x) + mut n := 0 + if z != x { // inexact + x = mod(x, 2) + n = int(x * 4) + } else { + if x >= two53 { // x must be even + x = 0 + n = 0 + } else { + if x < two52 { + z = x + two52 // exact + } + n = 1 & int(f64_bits(z)) + x = f64(n) + n <<= 2 + } + } + if n == 0 { + x = sin(pi * x) + } else if n == 1 || n == 2 { + x = cos(pi * (0.5 - x)) + } else if n == 3 || n == 4 { + x = sin(pi * (1.0 - x)) + } else if n == 5 || n == 6 { + x = -cos(pi * (x - 1.5)) + } else { + x = sin(pi * (x - 2)) + } + return -x +} diff --git a/v_windows/v/vlib/math/gamma_tables.v b/v_windows/v/vlib/math/gamma_tables.v new file mode 100644 index 0000000..8bf9076 --- /dev/null +++ b/v_windows/v/vlib/math/gamma_tables.v @@ -0,0 +1,163 @@ +module math + +const ( + gamma_p = [ + 1.60119522476751861407e-04, + 1.19135147006586384913e-03, + 1.04213797561761569935e-02, + 4.76367800457137231464e-02, + 2.07448227648435975150e-01, + 4.94214826801497100753e-01, + 9.99999999999999996796e-01, + ] + gamma_q = [ + -2.31581873324120129819e-05, + 5.39605580493303397842e-04, + -4.45641913851797240494e-03, + 1.18139785222060435552e-02, + 3.58236398605498653373e-02, + -2.34591795718243348568e-01, + 7.14304917030273074085e-02, + 1.00000000000000000320e+00, + ] + gamma_s = [ + 7.87311395793093628397e-04, + -2.29549961613378126380e-04, + -2.68132617805781232825e-03, + 3.47222221605458667310e-03, + 8.33333333333482257126e-02, + ] + lgamma_a = [ + // 0x3FB3C467E37DB0C8 + 7.72156649015328655494e-02, + // 0x3FD4A34CC4A60FAD + 3.22467033424113591611e-01, + // 0x3FB13E001A5562A7 + 6.73523010531292681824e-02, + // 0x3F951322AC92547B + 2.05808084325167332806e-02, + // 0x3F7E404FB68FEFE8 + 7.38555086081402883957e-03, + // 0x3F67ADD8CCB7926B + 2.89051383673415629091e-03, + // 0x3F538A94116F3F5D + 1.19270763183362067845e-03, + // 0x3F40B6C689B99C00 + 5.10069792153511336608e-04, + // 0x3F2CF2ECED10E54D + 2.20862790713908385557e-04, + // 0x3F1C5088987DFB07 + 1.08011567247583939954e-04, + // 0x3EFA7074428CFA52 + 2.52144565451257326939e-05, + // 0x3F07858E90A45837 + 4.48640949618915160150e-05, + ] + lgamma_r = [ + // placeholder + 1.0, + // 0x3FF645A762C4AB74 + 1.39200533467621045958e+00, + // 0x3FE71A1893D3DCDC + 7.21935547567138069525e-01, + // 0x3FC601EDCCFBDF27 + 1.71933865632803078993e-01, + // 0x3F9317EA742ED475 + 1.86459191715652901344e-02, + // 0x3F497DDACA41A95B + 7.77942496381893596434e-04, + // 0x3EDEBAF7A5B38140 + 7.32668430744625636189e-06, + ] + lgamma_s = [ + // 0xBFB3C467E37DB0C8 + -7.72156649015328655494e-02, + // 0x3FCB848B36E20878 + 2.14982415960608852501e-01, + // 0x3FD4D98F4F139F59 + 3.25778796408930981787e-01, + // 0x3FC2BB9CBEE5F2F7 + 1.46350472652464452805e-01, + // 0x3F9B481C7E939961 + 2.66422703033638609560e-02, + // 0x3F5E26B67368F239 + 1.84028451407337715652e-03, + // 0x3F00BFECDD17E945 + 3.19475326584100867617e-05, + ] + lgamma_t = [ + // 0x3FDEF72BC8EE38A2 + 4.83836122723810047042e-01, + // 0xBFC2E4278DC6C509 + -1.47587722994593911752e-01, + // 0x3FB08B4294D5419B + 6.46249402391333854778e-02, + // 0xBFA0C9A8DF35B713 + -3.27885410759859649565e-02, + // 0x3F9266E7970AF9EC + 1.79706750811820387126e-02, + // 0xBF851F9FBA91EC6A + -1.03142241298341437450e-02, + // 0x3F78FCE0E370E344 + 6.10053870246291332635e-03, + // 0xBF6E2EFFB3E914D7 + -3.68452016781138256760e-03, + // 0x3F6282D32E15C915 + 2.25964780900612472250e-03, + // 0xBF56FE8EBF2D1AF1 + -1.40346469989232843813e-03, + // 0x3F4CDF0CEF61A8E9 + 8.81081882437654011382e-04, + // 0xBF41A6109C73E0EC + -5.38595305356740546715e-04, + // 0x3F34AF6D6C0EBBF7 + 3.15632070903625950361e-04, + // 0xBF347F24ECC38C38 + -3.12754168375120860518e-04, + // 0x3F35FD3EE8C2D3F4 + 3.35529192635519073543e-04, + ] + lgamma_u = [ + // 0xBFB3C467E37DB0C8 + -7.72156649015328655494e-02, + // 0x3FE4401E8B005DFF + 6.32827064025093366517e-01, + // 0x3FF7475CD119BD6F + 1.45492250137234768737e+00, + // 0x3FEF497644EA8450 + 9.77717527963372745603e-01, + // 0x3FCD4EAEF6010924 + 2.28963728064692451092e-01, + // 0x3F8B678BBF2BAB09 + 1.33810918536787660377e-02, + ] + lgamma_v = [ + 1.0, + // 0x4003A5D7C2BD619C + 2.45597793713041134822e+00, + // 0x40010725A42B18F5 + 2.12848976379893395361e+00, + // 0x3FE89DFBE45050AF + 7.69285150456672783825e-01, + // 0x3FBAAE55D6537C88 + 1.04222645593369134254e-01, + // 0x3F6A5ABB57D0CF61 + 3.21709242282423911810e-03, + ] + lgamma_w = [ + // 0x3FDACFE390C97D69 + 4.18938533204672725052e-01, + // 0x3FB555555555553B + 8.33333333333329678849e-02, + // 0xBF66C16C16B02E5C + -2.77777777728775536470e-03, + // 0x3F4A019F98CF38B6 + 7.93650558643019558500e-04, + // 0xBF4380CB8C0FE741 + -5.95187557450339963135e-04, + // 0x3F4B67BA4CDAD5D1 + 8.36339918996282139126e-04, + // 0xBF5AB89D0B9E43E4 + -1.63092934096575273989e-03, + ] +) diff --git a/v_windows/v/vlib/math/hypot.c.v b/v_windows/v/vlib/math/hypot.c.v new file mode 100644 index 0000000..9710ea4 --- /dev/null +++ b/v_windows/v/vlib/math/hypot.c.v @@ -0,0 +1,9 @@ +module math + +fn C.hypot(x f64, y f64) f64 + +// Returns hypotenuse of a right triangle. +[inline] +pub fn hypot(x f64, y f64) f64 { + return C.hypot(x, y) +} diff --git a/v_windows/v/vlib/math/hypot.v b/v_windows/v/vlib/math/hypot.v new file mode 100644 index 0000000..4137505 --- /dev/null +++ b/v_windows/v/vlib/math/hypot.v @@ -0,0 +1,24 @@ +module math + +pub fn hypot(x f64, y f64) f64 { + if is_inf(x, 0) || is_inf(y, 0) { + return inf(1) + } + if is_nan(x) || is_nan(y) { + return nan() + } + mut result := 0.0 + if x != 0.0 || y != 0.0 { + abs_x := abs(x) + abs_y := abs(y) + min, max := minmax(abs_x, abs_y) + rat := min / max + root_term := sqrt(1.0 + rat * rat) + if max < max_f64 / root_term { + result = max * root_term + } else { + panic('overflow in hypot_e function') + } + } + return result +} diff --git a/v_windows/v/vlib/math/internal/machine.v b/v_windows/v/vlib/math/internal/machine.v new file mode 100644 index 0000000..e4ffb62 --- /dev/null +++ b/v_windows/v/vlib/math/internal/machine.v @@ -0,0 +1,58 @@ +module internal + +// contants to do fine tuning of precision for the functions +// implemented in pure V +pub const ( + f64_epsilon = 2.2204460492503131e-16 + sqrt_f64_epsilon = 1.4901161193847656e-08 + root3_f64_epsilon = 6.0554544523933429e-06 + root4_f64_epsilon = 1.2207031250000000e-04 + root5_f64_epsilon = 7.4009597974140505e-04 + root6_f64_epsilon = 2.4607833005759251e-03 + log_f64_epsilon = -3.6043653389117154e+01 + f64_min = 2.2250738585072014e-308 + sqrt_f64_min = 1.4916681462400413e-154 + root3_f64_min = 2.8126442852362996e-103 + root4_f64_min = 1.2213386697554620e-77 + root5_f64_min = 2.9476022969691763e-62 + root6_f64_min = 5.3034368905798218e-52 + log_f64_min = -7.0839641853226408e+02 + f64_max = 1.7976931348623157e+308 + sqrt_f64_max = 1.3407807929942596e+154 + root3_f64_max = 5.6438030941222897e+102 + root4_f64_max = 1.1579208923731620e+77 + root5_f64_max = 4.4765466227572707e+61 + root6_f64_max = 2.3756689782295612e+51 + log_f64_max = 7.0978271289338397e+02 + f32_epsilon = 1.1920928955078125e-07 + sqrt_f32_epsilon = 3.4526698300124393e-04 + root3_f32_epsilon = 4.9215666011518501e-03 + root4_f32_epsilon = 1.8581361171917516e-02 + root5_f32_epsilon = 4.1234622211652937e-02 + root6_f32_epsilon = 7.0153878019335827e-02 + log_f32_epsilon = -1.5942385152878742e+01 + f32_min = 1.1754943508222875e-38 + sqrt_f32_min = 1.0842021724855044e-19 + root3_f32_min = 2.2737367544323241e-13 + root4_f32_min = 3.2927225399135965e-10 + root5_f32_min = 2.5944428542140822e-08 + root6_f32_min = 4.7683715820312542e-07 + log_f32_min = -8.7336544750553102e+01 + f32_max = 3.4028234663852886e+38 + sqrt_f32_max = 1.8446743523953730e+19 + root3_f32_max = 6.9814635196223242e+12 + root4_f32_max = 4.2949672319999986e+09 + root5_f32_max = 5.0859007855960041e+07 + root6_f32_max = 2.6422459233807749e+06 + log_f32_max = 8.8722839052068352e+01 + sflt_epsilon = 4.8828125000000000e-04 + sqrt_sflt_epsilon = 2.2097086912079612e-02 + root3_sflt_epsilon = 7.8745065618429588e-02 + root4_sflt_epsilon = 1.4865088937534013e-01 + root5_sflt_epsilon = 2.1763764082403100e-01 + root6_sflt_epsilon = 2.8061551207734325e-01 + log_sflt_epsilon = -7.6246189861593985e+00 + max_int_fact_arg = 170 + max_f64_fact_arg = 171.0 + max_long_f64_fact_arg = 1755.5 +) diff --git a/v_windows/v/vlib/math/invhyp.v b/v_windows/v/vlib/math/invhyp.v new file mode 100644 index 0000000..4ef93cb --- /dev/null +++ b/v_windows/v/vlib/math/invhyp.v @@ -0,0 +1,51 @@ +module math + +import math.internal + +pub fn acosh(x f64) f64 { + if x == 0.0 { + return 0.0 + } else if x > 1.0 / internal.sqrt_f64_epsilon { + return log(x) + pi * 2 + } else if x > 2.0 { + return log(2.0 * x - 1.0 / (sqrt(x * x - 1.0) + x)) + } else if x > 1.0 { + t := x - 1.0 + return log1p(t + sqrt(2.0 * t + t * t)) + } else if x == 1.0 { + return 0.0 + } else { + return nan() + } +} + +pub fn asinh(x f64) f64 { + a := abs(x) + s := if x < 0 { -1.0 } else { 1.0 } + if a > 1.0 / internal.sqrt_f64_epsilon { + return s * (log(a) + pi * 2.0) + } else if a > 2.0 { + return s * log(2.0 * a + 1.0 / (a + sqrt(a * a + 1.0))) + } else if a > internal.sqrt_f64_epsilon { + a2 := a * a + return s * log1p(a + a2 / (1.0 + sqrt(1.0 + a2))) + } else { + return x + } +} + +pub fn atanh(x f64) f64 { + a := abs(x) + s := if x < 0 { -1.0 } else { 1.0 } + if a > 1.0 { + return nan() + } else if a == 1.0 { + return if x < 0 { inf(-1) } else { inf(1) } + } else if a >= 0.5 { + return s * 0.5 * log1p(2.0 * a / (1.0 - a)) + } else if a > internal.f64_epsilon { + return s * 0.5 * log1p(2.0 * a + 2.0 * a * a / (1.0 - a)) + } else { + return x + } +} diff --git a/v_windows/v/vlib/math/invtrig.c.v b/v_windows/v/vlib/math/invtrig.c.v new file mode 100644 index 0000000..ee4caf6 --- /dev/null +++ b/v_windows/v/vlib/math/invtrig.c.v @@ -0,0 +1,33 @@ +module math + +fn C.acos(x f64) f64 + +fn C.asin(x f64) f64 + +fn C.atan(x f64) f64 + +fn C.atan2(y f64, x f64) f64 + +// acos calculates inverse cosine (arccosine). +[inline] +pub fn acos(a f64) f64 { + return C.acos(a) +} + +// asin calculates inverse sine (arcsine). +[inline] +pub fn asin(a f64) f64 { + return C.asin(a) +} + +// atan calculates inverse tangent (arctangent). +[inline] +pub fn atan(a f64) f64 { + return C.atan(a) +} + +// atan2 calculates inverse tangent with two arguments, returns the angle between the X axis and the point. +[inline] +pub fn atan2(a f64, b f64) f64 { + return C.atan2(a, b) +} diff --git a/v_windows/v/vlib/math/invtrig.js.v b/v_windows/v/vlib/math/invtrig.js.v new file mode 100644 index 0000000..2e280a5 --- /dev/null +++ b/v_windows/v/vlib/math/invtrig.js.v @@ -0,0 +1,33 @@ +module math + +fn JS.Math.acos(x f64) f64 + +fn JS.Math.asin(x f64) f64 + +fn JS.Math.atan(x f64) f64 + +fn JS.Math.atan2(y f64, x f64) f64 + +// acos calculates inverse cosine (arccosine). +[inline] +pub fn acos(a f64) f64 { + return JS.Math.acos(a) +} + +// asin calculates inverse sine (arcsine). +[inline] +pub fn asin(a f64) f64 { + return JS.Math.asin(a) +} + +// atan calculates inverse tangent (arctangent). +[inline] +pub fn atan(a f64) f64 { + return JS.Math.atan(a) +} + +// atan2 calculates inverse tangent with two arguments, returns the angle between the X axis and the point. +[inline] +pub fn atan2(a f64, b f64) f64 { + return JS.Math.atan2(a, b) +} diff --git a/v_windows/v/vlib/math/invtrig.v b/v_windows/v/vlib/math/invtrig.v new file mode 100644 index 0000000..c40d87f --- /dev/null +++ b/v_windows/v/vlib/math/invtrig.v @@ -0,0 +1,219 @@ +module math + +// The original C code, the long comment, and the constants below were +// from http://netlib.sandia.gov/cephes/cmath/atan.c, available from +// http://www.netlib.org/cephes/ctgz. +// The go code is a version of the original C. +// +// atan.c +// Inverse circular tangent (arctangent) +// +// SYNOPSIS: +// double x, y, atan() +// y = atan( x ) +// +// DESCRIPTION: +// Returns radian angle between -pi/2.0 and +pi/2.0 whose tangent is x. +// +// Range reduction is from three intervals into the interval from zero to 0.66. +// The approximant uses a rational function of degree 4/5 of the form +// x + x**3 P(x)/Q(x). +// +// ACCURACY: +// Relative error: +// arithmetic domain # trials peak rms +// DEC -10, 10 50000 2.4e-17 8.3e-18 +// IEEE -10, 10 10^6 1.8e-16 5.0e-17 +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov +// pi/2.0 = PIO2 + morebits +// tan3pio8 = tan(3*pi/8) + +const ( + morebits = 6.123233995736765886130e-17 + tan3pio8 = 2.41421356237309504880 +) + +// xatan evaluates a series valid in the range [0, 0.66]. +[inline] +fn xatan(x f64) f64 { + xatan_p0 := -8.750608600031904122785e-01 + xatan_p1 := -1.615753718733365076637e+01 + xatan_p2 := -7.500855792314704667340e+01 + xatan_p3 := -1.228866684490136173410e+02 + xatan_p4 := -6.485021904942025371773e+01 + xatan_q0 := 2.485846490142306297962e+01 + xatan_q1 := 1.650270098316988542046e+02 + xatan_q2 := 4.328810604912902668951e+02 + xatan_q3 := 4.853903996359136964868e+02 + xatan_q4 := 1.945506571482613964425e+02 + mut z := x * x + z = z * ((((xatan_p0 * z + xatan_p1) * z + xatan_p2) * z + xatan_p3) * z + xatan_p4) / (((((z + + xatan_q0) * z + xatan_q1) * z + xatan_q2) * z + xatan_q3) * z + xatan_q4) + z = x * z + x + return z +} + +// satan reduces its argument (known to be positive) +// to the range [0, 0.66] and calls xatan. +[inline] +fn satan(x f64) f64 { + if x <= 0.66 { + return xatan(x) + } + if x > math.tan3pio8 { + return pi / 2.0 - xatan(1.0 / x) + f64(math.morebits) + } + return pi / 4 + xatan((x - 1.0) / (x + 1.0)) + 0.5 * f64(math.morebits) +} + +// atan returns the arctangent, in radians, of x. +// +// special cases are: +// atan(±0) = ±0 +// atan(±inf) = ±pi/2.0 +pub fn atan(x f64) f64 { + if x == 0 { + return x + } + if x > 0 { + return satan(x) + } + return -satan(-x) +} + +// atan2 returns the arc tangent of y/x, using +// the signs of the two to determine the quadrant +// of the return value. +// +// special cases are (in order): +// atan2(y, nan) = nan +// atan2(nan, x) = nan +// atan2(+0, x>=0) = +0 +// atan2(-0, x>=0) = -0 +// atan2(+0, x<=-0) = +pi +// atan2(-0, x<=-0) = -pi +// atan2(y>0, 0) = +pi/2.0 +// atan2(y<0, 0) = -pi/2.0 +// atan2(+inf, +inf) = +pi/4 +// atan2(-inf, +inf) = -pi/4 +// atan2(+inf, -inf) = 3pi/4 +// atan2(-inf, -inf) = -3pi/4 +// atan2(y, +inf) = 0 +// atan2(y>0, -inf) = +pi +// atan2(y<0, -inf) = -pi +// atan2(+inf, x) = +pi/2.0 +// atan2(-inf, x) = -pi/2.0 +pub fn atan2(y f64, x f64) f64 { + // special cases + if is_nan(y) || is_nan(x) { + return nan() + } + if y == 0.0 { + if x >= 0 && !signbit(x) { + return copysign(0, y) + } + return copysign(pi, y) + } + if x == 0.0 { + return copysign(pi / 2.0, y) + } + if is_inf(x, 0) { + if is_inf(x, 1) { + if is_inf(y, 0) { + return copysign(pi / 4, y) + } + return copysign(0, y) + } + if is_inf(y, 0) { + return copysign(3.0 * pi / 4.0, y) + } + return copysign(pi, y) + } + if is_inf(y, 0) { + return copysign(pi / 2.0, y) + } + // Call atan and determine the quadrant. + q := atan(y / x) + if x < 0 { + if q <= 0 { + return q + pi + } + return q - pi + } + return q +} + +/* +Floating-point arcsine and arccosine. + + They are implemented by computing the arctangent + after appropriate range reduction. +*/ + +// asin returns the arcsine, in radians, of x. +// +// special cases are: +// asin(±0) = ±0 +// asin(x) = nan if x < -1 or x > 1 +pub fn asin(x_ f64) f64 { + mut x := x_ + if x == 0.0 { + return x // special case + } + mut sign := false + if x < 0.0 { + x = -x + sign = true + } + if x > 1.0 { + return nan() // special case + } + mut temp := sqrt(1.0 - x * x) + if x > 0.7 { + temp = pi / 2.0 - satan(temp / x) + } else { + temp = satan(x / temp) + } + if sign { + temp = -temp + } + return temp +} + +// acos returns the arccosine, in radians, of x. +// +// special case is: +// acos(x) = nan if x < -1 or x > 1 +[inline] +pub fn acos(x f64) f64 { + if (x < -1.0) || (x > 1.0) { + return nan() + } + if x == 0.0 { + return nan() + } + if x > 0.5 { + return f64(2.0) * asin(sqrt(0.5 - 0.5 * x)) + } + mut z := pi / f64(4.0) - asin(x) + z = z + math.morebits + z = z + pi / f64(4.0) + return z +} diff --git a/v_windows/v/vlib/math/log.c.v b/v_windows/v/vlib/math/log.c.v new file mode 100644 index 0000000..f2f8c93 --- /dev/null +++ b/v_windows/v/vlib/math/log.c.v @@ -0,0 +1,25 @@ +module math + +fn C.log(x f64) f64 + +fn C.log2(x f64) f64 + +fn C.log10(x f64) f64 + +// log calculates natural (base-e) logarithm of the provided value. +[inline] +pub fn log(x f64) f64 { + return C.log(x) +} + +// log2 calculates base-2 logarithm of the provided value. +[inline] +pub fn log2(x f64) f64 { + return C.log2(x) +} + +// log10 calculates the common (base-10) logarithm of the provided value. +[inline] +pub fn log10(x f64) f64 { + return C.log10(x) +} diff --git a/v_windows/v/vlib/math/log.js.v b/v_windows/v/vlib/math/log.js.v new file mode 100644 index 0000000..a1a0cbb --- /dev/null +++ b/v_windows/v/vlib/math/log.js.v @@ -0,0 +1,9 @@ +module math + +fn JS.Math.log(x f64) f64 + +// log calculates natural (base-e) logarithm of the provided value. +[inline] +pub fn log(x f64) f64 { + return JS.Math.log(x) +} diff --git a/v_windows/v/vlib/math/log.v b/v_windows/v/vlib/math/log.v new file mode 100644 index 0000000..47ef731 --- /dev/null +++ b/v_windows/v/vlib/math/log.v @@ -0,0 +1,76 @@ +module math + +pub fn log_n(x f64, b f64) f64 { + y := log(x) + z := log(b) + return y / z +} + +// log10 returns the decimal logarithm of x. +// The special cases are the same as for log. +pub fn log10(x f64) f64 { + return log(x) * (1.0 / ln10) +} + +// log2 returns the binary logarithm of x. +// The special cases are the same as for log. +pub fn log2(x f64) f64 { + frac, exp := frexp(x) + // Make sure exact powers of two give an exact answer. + // Don't depend on log(0.5)*(1/ln2)+exp being exactly exp-1. + if frac == 0.5 { + return f64(exp - 1) + } + return log(frac) * (1.0 / ln2) + f64(exp) +} + +pub fn log1p(x f64) f64 { + y := 1.0 + x + z := y - 1.0 + return log(y) - (z - x) / y // cancels errors with IEEE arithmetic +} + +// log_b returns the binary exponent of x. +// +// special cases are: +// log_b(±inf) = +inf +// log_b(0) = -inf +// log_b(nan) = nan +pub fn log_b(x f64) f64 { + if x == 0 { + return inf(-1) + } + if is_inf(x, 0) { + return inf(1) + } + if is_nan(x) { + return x + } + return f64(ilog_b_(x)) +} + +// ilog_b returns the binary exponent of x as an integer. +// +// special cases are: +// ilog_b(±inf) = max_i32 +// ilog_b(0) = min_i32 +// ilog_b(nan) = max_i32 +pub fn ilog_b(x f64) int { + if x == 0 { + return min_i32 + } + if is_nan(x) { + return max_i32 + } + if is_inf(x, 0) { + return max_i32 + } + return ilog_b_(x) +} + +// ilog_b returns the binary exponent of x. It assumes x is finite and +// non-zero. +fn ilog_b_(x_ f64) int { + x, exp := normalize(x_) + return int((f64_bits(x) >> shift) & mask) - bias + exp +} diff --git a/v_windows/v/vlib/math/math.c.v b/v_windows/v/vlib/math/math.c.v new file mode 100644 index 0000000..503b6ab --- /dev/null +++ b/v_windows/v/vlib/math/math.c.v @@ -0,0 +1,14 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module math + +#include + +$if windows { + $if tinyc { + #flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o + } +} $else { + #flag -lm +} diff --git a/v_windows/v/vlib/math/math.v b/v_windows/v/vlib/math/math.v new file mode 100644 index 0000000..4a05815 --- /dev/null +++ b/v_windows/v/vlib/math/math.v @@ -0,0 +1,169 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module math + +// aprox_sin returns an approximation of sin(a) made using lolremez +pub fn aprox_sin(a f64) f64 { + a0 := 1.91059300966915117e-31 + a1 := 1.00086760103908896 + a2 := -1.21276126894734565e-2 + a3 := -1.38078780785773762e-1 + a4 := -2.67353392911981221e-2 + a5 := 2.08026600266304389e-2 + a6 := -3.03996055049204407e-3 + a7 := 1.38235642404333740e-4 + return a0 + a * (a1 + a * (a2 + a * (a3 + a * (a4 + a * (a5 + a * (a6 + a * a7)))))) +} + +// aprox_cos returns an approximation of sin(a) made using lolremez +pub fn aprox_cos(a f64) f64 { + a0 := 9.9995999154986614e-1 + a1 := 1.2548995793001028e-3 + a2 := -5.0648546280678015e-1 + a3 := 1.2942246466519995e-2 + a4 := 2.8668384702547972e-2 + a5 := 7.3726485210586547e-3 + a6 := -3.8510875386947414e-3 + a7 := 4.7196604604366623e-4 + a8 := -1.8776444013090451e-5 + return a0 + a * (a1 + a * (a2 + a * (a3 + a * (a4 + a * (a5 + a * (a6 + a * (a7 + a * a8))))))) +} + +// copysign returns a value with the magnitude of x and the sign of y +[inline] +pub fn copysign(x f64, y f64) f64 { + return f64_from_bits((f64_bits(x) & ~sign_mask) | (f64_bits(y) & sign_mask)) +} + +// degrees convert from degrees to radians. +[inline] +pub fn degrees(radians f64) f64 { + return radians * (180.0 / pi) +} + +// digits returns an array of the digits of n in the given base. +pub fn digits(_n int, base int) []int { + if base < 2 { + panic('digits: Cannot find digits of n with base $base') + } + mut n := _n + mut sign := 1 + if n < 0 { + sign = -1 + n = -n + } + mut res := []int{} + for n != 0 { + res << (n % base) * sign + n /= base + } + return res +} + +// max returns the maximum value of the two provided. +[inline] +pub fn max(a f64, b f64) f64 { + if a > b { + return a + } + return b +} + +// min returns the minimum value of the two provided. +[inline] +pub fn min(a f64, b f64) f64 { + if a < b { + return a + } + return b +} + +// minmax returns the minimum and maximum value of the two provided. +pub fn minmax(a f64, b f64) (f64, f64) { + if a < b { + return a, b + } + return b, a +} + +// sign returns the corresponding sign -1.0, 1.0 of the provided number. +// if n is not a number, its sign is nan too. +[inline] +pub fn sign(n f64) f64 { + if is_nan(n) { + return nan() + } + return copysign(1.0, n) +} + +// signi returns the corresponding sign -1.0, 1.0 of the provided number. +[inline] +pub fn signi(n f64) int { + return int(copysign(1.0, n)) +} + +// radians convert from radians to degrees. +[inline] +pub fn radians(degrees f64) f64 { + return degrees * (pi / 180.0) +} + +// signbit returns a value with the boolean representation of the sign for x +[inline] +pub fn signbit(x f64) bool { + return f64_bits(x) & sign_mask != 0 +} + +pub fn tolerance(a f64, b f64, tol f64) bool { + mut ee := tol + // Multiplying by ee here can underflow denormal values to zero. + // Check a==b so that at least if a and b are small and identical + // we say they match. + if a == b { + return true + } + mut d := a - b + if d < 0 { + d = -d + } + // note: b is correct (expected) value, a is actual value. + // make error tolerance a fraction of b, not a. + if b != 0 { + ee = ee * b + if ee < 0 { + ee = -ee + } + } + return d < ee +} + +pub fn close(a f64, b f64) bool { + return tolerance(a, b, 1e-14) +} + +pub fn veryclose(a f64, b f64) bool { + return tolerance(a, b, 4e-16) +} + +pub fn alike(a f64, b f64) bool { + if is_nan(a) && is_nan(b) { + return true + } else if a == b { + return signbit(a) == signbit(b) + } + return false +} + +fn is_odd_int(x f64) bool { + xi, xf := modf(x) + return xf == 0 && (i64(xi) & 1) == 1 +} + +fn is_neg_int(x f64) bool { + if x < 0 { + _, xf := modf(x) + return xf == 0 + } + return false +} diff --git a/v_windows/v/vlib/math/math_test.v b/v_windows/v/vlib/math/math_test.v new file mode 100644 index 0000000..cdda616 --- /dev/null +++ b/v_windows/v/vlib/math/math_test.v @@ -0,0 +1,970 @@ +module math + +struct Fi { + f f64 + i int +} + +const ( + vf_ = [f64(4.9790119248836735e+00), 7.7388724745781045e+00, -2.7688005719200159e-01, + -5.0106036182710749e+00, 9.6362937071984173e+00, 2.9263772392439646e+00, + 5.2290834314593066e+00, 2.7279399104360102e+00, 1.8253080916808550e+00, + -8.6859247685756013e+00, + ] + // The expected results below were computed by the high precision calculators + // at https://keisan.casio.com/. More exact input values (array vf_[], above) + // were obtained by printing them with "%.26f". The answers were calculated + // to 26 digits (by using the "Digit number" drop-down control of each + // calculator). + acos_ = [f64(1.0496193546107222142571536e+00), 6.8584012813664425171660692e-01, + 1.5984878714577160325521819e+00, 2.0956199361475859327461799e+00, + 2.7053008467824138592616927e-01, 1.2738121680361776018155625e+00, + 1.0205369421140629186287407e+00, 1.2945003481781246062157835e+00, + 1.3872364345374451433846657e+00, 2.6231510803970463967294145e+00] + acosh_ = [f64(2.4743347004159012494457618e+00), 2.8576385344292769649802701e+00, + 7.2796961502981066190593175e-01, 2.4796794418831451156471977e+00, + 3.0552020742306061857212962e+00, 2.044238592688586588942468e+00, + 2.5158701513104513595766636e+00, 1.99050839282411638174299e+00, + 1.6988625798424034227205445e+00, 2.9611454842470387925531875e+00] + asin_ = [f64(5.2117697218417440497416805e-01), 8.8495619865825236751471477e-01, + -2.769154466281941332086016e-02, -5.2482360935268931351485822e-01, + 1.3002662421166552333051524e+00, 2.9698415875871901741575922e-01, + 5.5025938468083370060258102e-01, 2.7629597861677201301553823e-01, + 1.83559892257451475846656e-01, -1.0523547536021497774980928e+00] + asinh_ = [f64(2.3083139124923523427628243e+00), 2.743551594301593620039021e+00, + -2.7345908534880091229413487e-01, -2.3145157644718338650499085e+00, + 2.9613652154015058521951083e+00, 1.7949041616585821933067568e+00, + 2.3564032905983506405561554e+00, 1.7287118790768438878045346e+00, + 1.3626658083714826013073193e+00, -2.8581483626513914445234004e+00] + atan_ = [f64(1.372590262129621651920085e+00), 1.442290609645298083020664e+00, + -2.7011324359471758245192595e-01, -1.3738077684543379452781531e+00, + 1.4673921193587666049154681e+00, 1.2415173565870168649117764e+00, + 1.3818396865615168979966498e+00, 1.2194305844639670701091426e+00, + 1.0696031952318783760193244e+00, -1.4561721938838084990898679e+00] + atanh_ = [f64(5.4651163712251938116878204e-01), 1.0299474112843111224914709e+00, + -2.7695084420740135145234906e-02, -5.5072096119207195480202529e-01, + 1.9943940993171843235906642e+00, 3.01448604578089708203017e-01, + 5.8033427206942188834370595e-01, 2.7987997499441511013958297e-01, + 1.8459947964298794318714228e-01, -1.3273186910532645867272502e+00] + atan2_ = [f64(1.1088291730037004444527075e+00), 9.1218183188715804018797795e-01, + 1.5984772603216203736068915e+00, 2.0352918654092086637227327e+00, + 8.0391819139044720267356014e-01, 1.2861075249894661588866752e+00, + 1.0889904479131695712182587e+00, 1.3044821793397925293797357e+00, + 1.3902530903455392306872261e+00, 2.2859857424479142655411058e+00] + ceil_ = [f64(5.0000000000000000e+00), 8.0000000000000000e+00, copysign(0, -1), + -5.0000000000000000e+00, 1.0000000000000000e+01, 3.0000000000000000e+00, + 6.0000000000000000e+00, 3.0000000000000000e+00, 2.0000000000000000e+00, + -8.0000000000000000e+00, + ] + cos_ = [f64(2.634752140995199110787593e-01), 1.148551260848219865642039e-01, + 9.6191297325640768154550453e-01, 2.938141150061714816890637e-01, + -9.777138189897924126294461e-01, -9.7693041344303219127199518e-01, + 4.940088096948647263961162e-01, -9.1565869021018925545016502e-01, + -2.517729313893103197176091e-01, -7.39241351595676573201918e-01] + // Results for 100000 * pi + vf_[i] + cos_large_ = [f64(2.634752141185559426744e-01), 1.14855126055543100712e-01, + 9.61912973266488928113e-01, 2.9381411499556122552e-01, -9.777138189880161924641e-01, + -9.76930413445147608049e-01, 4.940088097314976789841e-01, -9.15658690217517835002e-01, + -2.51772931436786954751e-01, -7.3924135157173099849e-01] + cosh_ = [f64(7.2668796942212842775517446e+01), 1.1479413465659254502011135e+03, + 1.0385767908766418550935495e+00, 7.5000957789658051428857788e+01, + 7.655246669605357888468613e+03, 9.3567491758321272072888257e+00, + 9.331351599270605471131735e+01, 7.6833430994624643209296404e+00, + 3.1829371625150718153881164e+00, 2.9595059261916188501640911e+03] + exp_ = [f64(1.4533071302642137507696589e+02), 2.2958822575694449002537581e+03, + 7.5814542574851666582042306e-01, 6.6668778421791005061482264e-03, + 1.5310493273896033740861206e+04, 1.8659907517999328638667732e+01, + 1.8662167355098714543942057e+02, 1.5301332413189378961665788e+01, + 6.2047063430646876349125085e+00, 1.6894712385826521111610438e-04] + expm1_ = [f64(5.105047796122957327384770212e-02), 8.046199708567344080562675439e-02, + -2.764970978891639815187418703e-03, -4.8871434888875355394330300273e-02, + 1.0115864277221467777117227494e-01, 2.969616407795910726014621657e-02, + 5.368214487944892300914037972e-02, 2.765488851131274068067445335e-02, + 1.842068661871398836913874273e-02, -8.3193870863553801814961137573e-02] + expm1_large_ = [f64(4.2031418113550844e+21), 4.0690789717473863e+33, -0.9372627915981363e+00, + -1.0, 7.077694784145933e+41, 5.117936223839153e+12, 5.124137759001189e+22, + 7.03546003972584e+11, 8.456921800389698e+07, -1.0] + exp2_ = [f64(3.1537839463286288034313104e+01), 2.1361549283756232296144849e+02, + 8.2537402562185562902577219e-01, 3.1021158628740294833424229e-02, + 7.9581744110252191462569661e+02, 7.6019905892596359262696423e+00, + 3.7506882048388096973183084e+01, 6.6250893439173561733216375e+00, + 3.5438267900243941544605339e+00, 2.4281533133513300984289196e-03] + fabs_ = [f64(4.9790119248836735e+00), 7.7388724745781045e+00, 2.7688005719200159e-01, + 5.0106036182710749e+00, 9.6362937071984173e+00, 2.9263772392439646e+00, + 5.2290834314593066e+00, 2.7279399104360102e+00, 1.8253080916808550e+00, + 8.6859247685756013e+00, + ] + floor_ = [f64(4.0000000000000000e+00), 7.0000000000000000e+00, -1.0000000000000000e+00, + -6.0000000000000000e+00, 9.0000000000000000e+00, 2.0000000000000000e+00, + 5.0000000000000000e+00, 2.0000000000000000e+00, 1.0000000000000000e+00, + -9.0000000000000000e+00, + ] + fmod_ = [f64(4.197615023265299782906368e-02), 2.261127525421895434476482e+00, + 3.231794108794261433104108e-02, 4.989396381728925078391512e+00, + 3.637062928015826201999516e-01, 1.220868282268106064236690e+00, + 4.770916568540693347699744e+00, 1.816180268691969246219742e+00, + 8.734595415957246977711748e-01, 1.314075231424398637614104e+00] + frexp_ = [Fi{6.2237649061045918750e-01, 3}, Fi{9.6735905932226306250e-01, 3}, + Fi{-5.5376011438400318000e-01, -1}, Fi{-6.2632545228388436250e-01, 3}, + Fi{6.02268356699901081250e-01, 4}, Fi{7.3159430981099115000e-01, 2}, + Fi{6.5363542893241332500e-01, 3}, Fi{6.8198497760900255000e-01, 2}, + Fi{9.1265404584042750000e-01, 1}, Fi{-5.4287029803597508250e-01, 4}] + gamma_ = [f64(2.3254348370739963835386613898e+01), 2.991153837155317076427529816e+03, + -4.561154336726758060575129109e+00, 7.719403468842639065959210984e-01, + 1.6111876618855418534325755566e+05, 1.8706575145216421164173224946e+00, + 3.4082787447257502836734201635e+01, 1.579733951448952054898583387e+00, + 9.3834586598354592860187267089e-01, -2.093995902923148389186189429e-05] + log_gamma_ = [Fi{3.146492141244545774319734e+00, 1}, Fi{8.003414490659126375852113e+00, 1}, + Fi{1.517575735509779707488106e+00, -1}, Fi{-2.588480028182145853558748e-01, 1}, + Fi{1.1989897050205555002007985e+01, 1}, Fi{6.262899811091257519386906e-01, 1}, + Fi{3.5287924899091566764846037e+00, 1}, Fi{4.5725644770161182299423372e-01, 1}, + Fi{-6.363667087767961257654854e-02, 1}, Fi{-1.077385130910300066425564e+01, -1}] + log_ = [f64(1.605231462693062999102599e+00), 2.0462560018708770653153909e+00, + -1.2841708730962657801275038e+00, 1.6115563905281545116286206e+00, + 2.2655365644872016636317461e+00, 1.0737652208918379856272735e+00, + 1.6542360106073546632707956e+00, 1.0035467127723465801264487e+00, + 6.0174879014578057187016475e-01, 2.161703872847352815363655e+00] + logb_ = [f64(2.0000000000000000e+00), 2.0000000000000000e+00, -2.0000000000000000e+00, + 2.0000000000000000e+00, 3.0000000000000000e+00, 1.0000000000000000e+00, + 2.0000000000000000e+00, 1.0000000000000000e+00, 0.0000000000000000e+00, + 3.0000000000000000e+00, + ] + log10_ = [f64(6.9714316642508290997617083e-01), 8.886776901739320576279124e-01, + -5.5770832400658929815908236e-01, 6.998900476822994346229723e-01, + 9.8391002850684232013281033e-01, 4.6633031029295153334285302e-01, + 7.1842557117242328821552533e-01, 4.3583479968917773161304553e-01, + 2.6133617905227038228626834e-01, 9.3881606348649405716214241e-01] + log1p_ = [f64(4.8590257759797794104158205e-02), 7.4540265965225865330849141e-02, + -2.7726407903942672823234024e-03, -5.1404917651627649094953380e-02, + 9.1998280672258624681335010e-02, 2.8843762576593352865894824e-02, + 5.0969534581863707268992645e-02, 2.6913947602193238458458594e-02, + 1.8088493239630770262045333e-02, -9.0865245631588989681559268e-02] + log2_ = [f64(2.3158594707062190618898251e+00), 2.9521233862883917703341018e+00, + -1.8526669502700329984917062e+00, 2.3249844127278861543568029e+00, + 3.268478366538305087466309e+00, 1.5491157592596970278166492e+00, + 2.3865580889631732407886495e+00, 1.447811865817085365540347e+00, + 8.6813999540425116282815557e-01, 3.118679457227342224364709e+00] + modf_ = [[f64(4.0000000000000000e+00), 9.7901192488367350108546816e-01], + [f64(7.0000000000000000e+00), 7.3887247457810456552351752e-01], + [f64(-0.0), -2.7688005719200159404635997e-01], + [f64(-5.0000000000000000e+00), + -1.060361827107492160848778e-02, + ], + [f64(9.0000000000000000e+00), 6.3629370719841737980004837e-01], + [f64(2.0000000000000000e+00), 9.2637723924396464525443662e-01], + [f64(5.0000000000000000e+00), 2.2908343145930665230025625e-01], + [f64(2.0000000000000000e+00), 7.2793991043601025126008608e-01], + [f64(1.0000000000000000e+00), 8.2530809168085506044576505e-01], + [f64(-8.0000000000000000e+00), -6.8592476857560136238589621e-01], + ] + nextafter32_ = [4.979012489318848e+00, 7.738873004913330e+00, -2.768800258636475e-01, + -5.010602951049805e+00, 9.636294364929199e+00, 2.926377534866333e+00, 5.229084014892578e+00, + 2.727940082550049e+00, 1.825308203697205e+00, -8.685923576354980e+00] + nextafter64_ = [f64(4.97901192488367438926388786e+00), 7.73887247457810545370193722e+00, + -2.7688005719200153853520874e-01, -5.01060361827107403343006808e+00, + 9.63629370719841915615688777e+00, 2.92637723924396508934364647e+00, + 5.22908343145930754047867595e+00, 2.72793991043601069534929593e+00, + 1.82530809168085528249036997e+00, -8.68592476857559958602905681e+00] + pow_ = [f64(9.5282232631648411840742957e+04), 5.4811599352999901232411871e+07, + 5.2859121715894396531132279e-01, 9.7587991957286474464259698e-06, + 4.328064329346044846740467e+09, 8.4406761805034547437659092e+02, + 1.6946633276191194947742146e+05, 5.3449040147551939075312879e+02, + 6.688182138451414936380374e+01, 2.0609869004248742886827439e-09] + remainder_ = [f64(4.197615023265299782906368e-02), 2.261127525421895434476482e+00, + 3.231794108794261433104108e-02, -2.120723654214984321697556e-02, + 3.637062928015826201999516e-01, 1.220868282268106064236690e+00, + -4.581668629186133046005125e-01, -9.117596417440410050403443e-01, + 8.734595415957246977711748e-01, 1.314075231424398637614104e+00] + round_ = [f64(5), 8, copysign(0, -1), -5, 10, 3, 5, 3, 2, -9] + signbit_ = [false, false, true, true, false, false, false, false, false, true] + sin_ = [f64(-9.6466616586009283766724726e-01), 9.9338225271646545763467022e-01, + -2.7335587039794393342449301e-01, 9.5586257685042792878173752e-01, + -2.099421066779969164496634e-01, 2.135578780799860532750616e-01, + -8.694568971167362743327708e-01, 4.019566681155577786649878e-01, + 9.6778633541687993721617774e-01, -6.734405869050344734943028e-01] + // Results for 100000 * pi + vf_[i] + sin_large_ = [f64(-9.646661658548936063912e-01), 9.933822527198506903752e-01, + -2.7335587036246899796e-01, 9.55862576853689321268e-01, -2.099421066862688873691e-01, + 2.13557878070308981163e-01, -8.694568970959221300497e-01, 4.01956668098863248917e-01, + 9.67786335404528727927e-01, -6.7344058693131973066e-01] + sinh_ = [f64(7.2661916084208532301448439e+01), 1.1479409110035194500526446e+03, + -2.8043136512812518927312641e-01, -7.499429091181587232835164e+01, + 7.6552466042906758523925934e+03, 9.3031583421672014313789064e+00, + 9.330815755828109072810322e+01, 7.6179893137269146407361477e+00, + 3.021769180549615819524392e+00, -2.95950575724449499189888e+03] + sqrt_ = [f64(2.2313699659365484748756904e+00), 2.7818829009464263511285458e+00, + 5.2619393496314796848143251e-01, 2.2384377628763938724244104e+00, + 3.1042380236055381099288487e+00, 1.7106657298385224403917771e+00, + 2.286718922705479046148059e+00, 1.6516476350711159636222979e+00, + 1.3510396336454586262419247e+00, 2.9471892997524949215723329e+00] + tan_ = [f64(-3.661316565040227801781974e+00), 8.64900232648597589369854e+00, + -2.8417941955033612725238097e-01, 3.253290185974728640827156e+00, + 2.147275640380293804770778e-01, -2.18600910711067004921551e-01, + -1.760002817872367935518928e+00, -4.389808914752818126249079e-01, + -3.843885560201130679995041e+00, 9.10988793377685105753416e-01] + // Results for 100000 * pi + vf_[i] + tan_large_ = [f64(-3.66131656475596512705e+00), 8.6490023287202547927e+00, + -2.841794195104782406e-01, 3.2532901861033120983e+00, 2.14727564046880001365e-01, + -2.18600910700688062874e-01, -1.760002817699722747043e+00, -4.38980891453536115952e-01, + -3.84388555942723509071e+00, 9.1098879344275101051e-01] + tanh_ = [f64(9.9990531206936338549262119e-01), 9.9999962057085294197613294e-01, + -2.7001505097318677233756845e-01, -9.9991110943061718603541401e-01, + 9.9999999146798465745022007e-01, 9.9427249436125236705001048e-01, + 9.9994257600983138572705076e-01, 9.9149409509772875982054701e-01, + 9.4936501296239685514466577e-01, -9.9999994291374030946055701e-01] + trunc_ = [f64(4.0000000000000000e+00), 7.0000000000000000e+00, copysign(0, -1), + -5.0000000000000000e+00, 9.0000000000000000e+00, 2.0000000000000000e+00, + 5.0000000000000000e+00, 2.0000000000000000e+00, 1.0000000000000000e+00, + -8.0000000000000000e+00, + ] +) + +fn soclose(a f64, b f64, e_ f64) bool { + return tolerance(a, b, e_) +} + +fn test_nan() { + nan_f64 := nan() + assert nan_f64 != nan_f64 + nan_f32 := f32(nan_f64) + assert nan_f32 != nan_f32 +} + +fn test_acos() { + for i := 0; i < math.vf_.len; i++ { + a := math.vf_[i] / 10 + f := acos(a) + assert soclose(math.acos_[i], f, 1e-7) + } + vfacos_sc_ := [-pi, 1, pi, nan()] + acos_sc_ := [nan(), 0, nan(), nan()] + for i := 0; i < vfacos_sc_.len; i++ { + f := acos(vfacos_sc_[i]) + assert alike(acos_sc_[i], f) + } +} + +fn test_acosh() { + for i := 0; i < math.vf_.len; i++ { + a := 1.0 + abs(math.vf_[i]) + f := acosh(a) + assert veryclose(math.acosh_[i], f) + } + vfacosh_sc_ := [inf(-1), 0.5, 1, inf(1), nan()] + acosh_sc_ := [nan(), nan(), 0, inf(1), nan()] + for i := 0; i < vfacosh_sc_.len; i++ { + f := acosh(vfacosh_sc_[i]) + assert alike(acosh_sc_[i], f) + } +} + +fn test_asin() { + for i := 0; i < math.vf_.len; i++ { + a := math.vf_[i] / 10 + f := asin(a) + assert veryclose(math.asin_[i], f) + } + vfasin_sc_ := [-pi, copysign(0, -1), 0, pi, nan()] + asin_sc_ := [nan(), copysign(0, -1), 0, nan(), nan()] + for i := 0; i < vfasin_sc_.len; i++ { + f := asin(vfasin_sc_[i]) + assert alike(asin_sc_[i], f) + } +} + +fn test_asinh() { + for i := 0; i < math.vf_.len; i++ { + f := asinh(math.vf_[i]) + assert veryclose(math.asinh_[i], f) + } + vfasinh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + asinh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfasinh_sc_.len; i++ { + f := asinh(vfasinh_sc_[i]) + assert alike(asinh_sc_[i], f) + } +} + +fn test_atan() { + for i := 0; i < math.vf_.len; i++ { + f := atan(math.vf_[i]) + assert veryclose(math.atan_[i], f) + } + vfatan_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + atan_sc_ := [f64(-pi / 2), copysign(0, -1), 0, pi / 2, nan()] + for i := 0; i < vfatan_sc_.len; i++ { + f := atan(vfatan_sc_[i]) + assert alike(atan_sc_[i], f) + } +} + +fn test_atanh() { + for i := 0; i < math.vf_.len; i++ { + a := math.vf_[i] / 10 + f := atanh(a) + assert veryclose(math.atanh_[i], f) + } + vfatanh_sc_ := [inf(-1), -pi, -1, copysign(0, -1), 0, 1, pi, inf(1), + nan(), + ] + atanh_sc_ := [nan(), nan(), inf(-1), copysign(0, -1), 0, inf(1), + nan(), nan(), nan()] + for i := 0; i < vfatanh_sc_.len; i++ { + f := atanh(vfatanh_sc_[i]) + assert alike(atanh_sc_[i], f) + } +} + +fn test_atan2() { + for i := 0; i < math.vf_.len; i++ { + f := atan2(10, math.vf_[i]) + assert veryclose(math.atan2_[i], f) + } + vfatan2_sc_ := [[inf(-1), inf(-1)], [inf(-1), -pi], [inf(-1), 0], + [inf(-1), pi], [inf(-1), inf(1)], [inf(-1), nan()], [-pi, inf(-1)], + [-pi, 0], [-pi, inf(1)], [-pi, nan()], [f64(-0.0), inf(-1)], + [f64(-0.0), -pi], [f64(-0.0), -0.0], [f64(-0.0), 0], [f64(-0.0), pi], + [f64(-0.0), inf(1)], [f64(-0.0), nan()], [f64(0), inf(-1)], + [f64(0), -pi], [f64(0), -0.0], [f64(0), 0], [f64(0), pi], + [f64(0), inf(1)], [f64(0), nan()], [pi, inf(-1)], [pi, 0], + [pi, inf(1)], [pi, nan()], [inf(1), inf(-1)], [inf(1), -pi], + [inf(1), 0], [inf(1), pi], [inf(1), inf(1)], [inf(1), nan()], + [nan(), nan()], + ] + atan2_sc_ := [f64(-3.0) * pi / 4.0, /* atan2(-inf, -inf) */ -pi / 2, /* atan2(-inf, -pi) */ + -pi / 2, + /* atan2(-inf, +0) */ -pi / 2, /* atan2(-inf, pi) */ -pi / 4, /* atan2(-inf, +inf) */ + nan(), /* atan2(-inf, nan) */ -pi, /* atan2(-pi, -inf) */ -pi / 2, /* atan2(-pi, +0) */ + -0.0, + /* atan2(-pi, inf) */ nan(), /* atan2(-pi, nan) */ -pi, /* atan2(-0, -inf) */ -pi, + /* atan2(-0, -pi) */ -pi, /* atan2(-0, -0) */ -0.0, /* atan2(-0, +0) */ -0.0, /* atan2(-0, pi) */ + -0.0, + /* atan2(-0, +inf) */ nan(), /* atan2(-0, nan) */ pi, /* atan2(+0, -inf) */ pi, /* atan2(+0, -pi) */ + pi, /* atan2(+0, -0) */ 0, /* atan2(+0, +0) */ 0, /* atan2(+0, pi) */ 0, /* atan2(+0, +inf) */ + nan(), /* atan2(+0, nan) */ pi, /* atan2(pi, -inf) */ pi / 2, /* atan2(pi, +0) */ 0, + /* atan2(pi, +inf) */ nan(), /* atan2(pi, nan) */ 3.0 * pi / 4, /* atan2(+inf, -inf) */ + pi / 2, /* atan2(+inf, -pi) */ pi / 2, /* atan2(+inf, +0) */ pi / 2, /* atan2(+inf, pi) */ + pi / 4, /* atan2(+inf, +inf) */ nan(), /* atan2(+inf, nan) */ + nan(), /* atan2(nan, nan) */ + ] + for i := 0; i < vfatan2_sc_.len; i++ { + f := atan2(vfatan2_sc_[i][0], vfatan2_sc_[i][1]) + assert alike(atan2_sc_[i], f) + } +} + +fn test_ceil() { + // for i := 0; i < vf_.len; i++ { + // f := ceil(vf_[i]) + // assert alike(ceil_[i], f) + // } + vfceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + ceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfceil_sc_.len; i++ { + f := ceil(vfceil_sc_[i]) + assert alike(ceil_sc_[i], f) + } +} + +fn test_cos() { + for i := 0; i < math.vf_.len; i++ { + f := cos(math.vf_[i]) + assert veryclose(math.cos_[i], f) + } + vfcos_sc_ := [inf(-1), inf(1), nan()] + cos_sc_ := [nan(), nan(), nan()] + for i := 0; i < vfcos_sc_.len; i++ { + f := cos(vfcos_sc_[i]) + assert alike(cos_sc_[i], f) + } +} + +fn test_cosh() { + for i := 0; i < math.vf_.len; i++ { + f := cosh(math.vf_[i]) + assert close(math.cosh_[i], f) + } + vfcosh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + cosh_sc_ := [inf(1), 1, 1, inf(1), nan()] + for i := 0; i < vfcosh_sc_.len; i++ { + f := cosh(vfcosh_sc_[i]) + assert alike(cosh_sc_[i], f) + } +} + +fn test_expm1() { + for i := 0; i < math.vf_.len; i++ { + a := math.vf_[i] / 100 + f := expm1(a) + assert veryclose(math.expm1_[i], f) + } + for i := 0; i < math.vf_.len; i++ { + a := math.vf_[i] * 10 + f := expm1(a) + assert close(math.expm1_large_[i], f) + } + // vfexpm1_sc_ := [f64(-710), copysign(0, -1), 0, 710, inf(1), nan()] + // expm1_sc_ := [f64(-1), copysign(0, -1), 0, inf(1), inf(1), nan()] + // for i := 0; i < vfexpm1_sc_.len; i++ { + // f := expm1(vfexpm1_sc_[i]) + // assert alike(expm1_sc_[i], f) + // } +} + +fn test_abs() { + for i := 0; i < math.vf_.len; i++ { + f := abs(math.vf_[i]) + assert math.fabs_[i] == f + } +} + +fn test_floor() { + for i := 0; i < math.vf_.len; i++ { + f := floor(math.vf_[i]) + assert alike(math.floor_[i], f) + } + vfceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + ceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfceil_sc_.len; i++ { + f := floor(vfceil_sc_[i]) + assert alike(ceil_sc_[i], f) + } +} + +fn test_max() { + for i := 0; i < math.vf_.len; i++ { + f := max(math.vf_[i], math.ceil_[i]) + assert math.ceil_[i] == f + } +} + +fn test_min() { + for i := 0; i < math.vf_.len; i++ { + f := min(math.vf_[i], math.floor_[i]) + assert math.floor_[i] == f + } +} + +fn test_signi() { + assert signi(inf(-1)) == -1 + assert signi(-72234878292.4586129) == -1 + assert signi(-10) == -1 + assert signi(-pi) == -1 + assert signi(-1) == -1 + assert signi(-0.000000000001) == -1 + assert signi(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) == -1 + assert signi(-0.0) == -1 + // + assert signi(inf(1)) == 1 + assert signi(72234878292.4586129) == 1 + assert signi(10) == 1 + assert signi(pi) == 1 + assert signi(1) == 1 + assert signi(0.000000000001) == 1 + assert signi(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) == 1 + assert signi(0.0) == 1 + assert signi(nan()) == 1 +} + +fn test_sign() { + assert sign(inf(-1)) == -1.0 + assert sign(-72234878292.4586129) == -1.0 + assert sign(-10) == -1.0 + assert sign(-pi) == -1.0 + assert sign(-1) == -1.0 + assert sign(-0.000000000001) == -1.0 + assert sign(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) == -1.0 + assert sign(-0.0) == -1.0 + // + assert sign(inf(1)) == 1.0 + assert sign(72234878292.4586129) == 1 + assert sign(10) == 1.0 + assert sign(pi) == 1.0 + assert sign(1) == 1.0 + assert sign(0.000000000001) == 1.0 + assert sign(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) == 1.0 + assert sign(0.0) == 1.0 + assert is_nan(sign(nan())) + assert is_nan(sign(-nan())) +} + +fn test_mod() { + for i := 0; i < math.vf_.len; i++ { + f := mod(10, math.vf_[i]) + assert math.fmod_[i] == f + } + // verify precision of result for extreme inputs + f := mod(5.9790119248836734e+200, 1.1258465975523544) + assert (0.6447968302508578) == f +} + +fn test_exp() { + for i := 0; i < math.vf_.len; i++ { + f := exp(math.vf_[i]) + assert veryclose(math.exp_[i], f) + } + vfexp_sc_ := [inf(-1), -2000, 2000, inf(1), nan(), /* smallest f64 that overflows Exp(x) */ + 7.097827128933841e+02, 1.48852223e+09, 1.4885222e+09, 1, /* near zero */ + 3.725290298461915e-09, + /* denormal */ -740] + exp_sc_ := [f64(0), 0, inf(1), inf(1), nan(), inf(1), inf(1), + inf(1), 2.718281828459045, 1.0000000037252903, 4.2e-322] + for i := 0; i < vfexp_sc_.len; i++ { + f := exp(vfexp_sc_[i]) + assert alike(exp_sc_[i], f) + } +} + +fn test_exp2() { + for i := 0; i < math.vf_.len; i++ { + f := exp2(math.vf_[i]) + assert soclose(math.exp2_[i], f, 1e-9) + } + vfexp2_sc_ := [f64(-2000), 2000, inf(1), nan(), /* smallest f64 that overflows Exp2(x) */ + 1024, /* near underflow */ -1.07399999999999e+03, /* near zero */ 3.725290298461915e-09] + exp2_sc_ := [f64(0), inf(1), inf(1), nan(), inf(1), 5e-324, 1.0000000025821745] + for i := 0; i < vfexp2_sc_.len; i++ { + f := exp2(vfexp2_sc_[i]) + assert alike(exp2_sc_[i], f) + } + for n := -1074; n < 1024; n++ { + f := exp2(f64(n)) + vf := ldexp(1, n) + assert veryclose(f, vf) + } +} + +fn test_frexp() { + for i := 0; i < math.vf_.len; i++ { + f, j := frexp(math.vf_[i]) + assert veryclose(math.frexp_[i].f, f) || math.frexp_[i].i != j + } + // vffrexp_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + // frexp_sc_ := [Fi{inf(-1), 0}, Fi{copysign(0, -1), 0}, Fi{0, 0}, + // Fi{inf(1), 0}, Fi{nan(), 0}] + // for i := 0; i < vffrexp_sc_.len; i++ { + // f, j := frexp(vffrexp_sc_[i]) + // assert alike(frexp_sc_[i].f, f) || frexp_sc_[i].i != j + // } +} + +fn test_gamma() { + vfgamma_ := [[inf(1), inf(1)], [inf(-1), nan()], [f64(0), inf(1)], + [f64(-0.0), inf(-1)], [nan(), nan()], [f64(-1), nan()], + [f64(-2), nan()], [f64(-3), nan()], [f64(-1e+16), nan()], + [f64(-1e+300), nan()], [f64(1.7e+308), inf(1)], /* Test inputs inspi_red by Python test suite. */ + // Outputs computed at high precision by PARI/GP. + // If recomputing table entries), be careful to use + // high-precision (%.1000g) formatting of the f64 inputs. + // For example), -2.0000000000000004 is the f64 with exact value + //-2.00000000000000044408920985626161695), and + // gamma(-2.0000000000000004) = -1249999999999999.5386078562728167651513), while + // gamma(-2.00000000000000044408920985626161695) = -1125899906826907.2044875028130093136826. + // Thus the table lists -1.1258999068426235e+15 as the answer. + [f64(0.5), 1.772453850905516], [f64(1.5), 0.886226925452758], + [f64(2.5), 1.329340388179137], [f64(3.5), 3.3233509704478426], + [f64(-0.5), -3.544907701811032], [f64(-1.5), 2.363271801207355], + [f64(-2.5), -0.9453087204829419], [f64(-3.5), 0.2700882058522691], + [f64(0.1), 9.51350769866873], [f64(0.01), 99.4325851191506], + [f64(1e-08), 9.999999942278434e+07], [f64(1e-16), 1e+16], + [f64(0.001), 999.4237724845955], [f64(1e-16), 1e+16], + [f64(1e-308), 1e+308], [f64(5.6e-309), 1.7857142857142864e+308], + [f64(5.5e-309), inf(1)], [f64(1e-309), inf(1)], [f64(1e-323), inf(1)], + [f64(5e-324), inf(1)], [f64(-0.1), -10.686287021193193], + [f64(-0.01), -100.58719796441078], [f64(-1e-08), -1.0000000057721567e+08], + [f64(-1e-16), -1e+16], [f64(-0.001), -1000.5782056293586], + [f64(-1e-16), -1e+16], [f64(-1e-308), -1e+308], [f64(-5.6e-309), -1.7857142857142864e+308], + [f64(-5.5e-309), inf(-1)], [f64(-1e-309), inf(-1)], [f64(-1e-323), inf(-1)], + [f64(-5e-324), inf(-1)], [f64(-0.9999999999999999), -9.007199254740992e+15], + [f64(-1.0000000000000002), 4.5035996273704955e+15], + [f64(-1.9999999999999998), + 2.2517998136852485e+15, + ], + [f64(-2.0000000000000004), -1.1258999068426235e+15], + [f64(-100.00000000000001), + -7.540083334883109e-145, + ], + [f64(-99.99999999999999), 7.540083334884096e-145], [f64(17), 2.0922789888e+13], + [f64(171), 7.257415615307999e+306], [f64(171.6), 1.5858969096672565e+308], + [f64(171.624), 1.7942117599248104e+308], [f64(171.625), inf(1)], + [f64(172), inf(1)], [f64(2000), inf(1)], [f64(-100.5), -3.3536908198076787e-159], + [f64(-160.5), -5.255546447007829e-286], [f64(-170.5), -3.3127395215386074e-308], + [f64(-171.5), 1.9316265431712e-310], [f64(-176.5), -1.196e-321], + [f64(-177.5), 5e-324], [f64(-178.5), -0.0], [f64(-179.5), 0], + [f64(-201.0001), 0], [f64(-202.9999), -0.0], [f64(-1000.5), -0.0], + [f64(-1.0000000003e+09), -0.0], [f64(-4.5035996273704955e+15), 0], + [f64(-63.349078729022985), 4.177797167776188e-88], + [f64(-127.45117632943295), + 1.183111089623681e-214, + ], + ] + _ := vfgamma_[0][0] + // @todo: Figure out solution for C backend + // for i := 0; i < math.vf_.len; i++ { + // f := gamma(math.vf_[i]) + // assert veryclose(math.gamma_[i], f) + // } + // for _, g in vfgamma_ { + // f := gamma(g[0]) + // if is_nan(g[1]) || is_inf(g[1], 0) || g[1] == 0 || f == 0 { + // assert alike(g[1], f) + // } else if g[0] > -50 && g[0] <= 171 { + // assert veryclose(g[1], f) + // } else { + // assert soclose(g[1], f, 1e-9) + // } + // } +} + +fn test_hypot() { + for i := 0; i < math.vf_.len; i++ { + a := abs(1e+200 * math.tanh_[i] * sqrt(2.0)) + f := hypot(1e+200 * math.tanh_[i], 1e+200 * math.tanh_[i]) + assert veryclose(a, f) + } + vfhypot_sc_ := [[inf(-1), inf(-1)], [inf(-1), 0], [inf(-1), + inf(1), + ], + [inf(-1), nan()], [f64(-0.0), -0.0], [f64(-0.0), 0], [f64(0), -0.0], + [f64(0), 0], /* +0,0 */ [f64(0), inf(-1)], [f64(0), inf(1)], + [f64(0), nan()], [inf(1), inf(-1)], [inf(1), 0], [inf(1), + inf(1), + ], + [inf(1), nan()], [nan(), inf(-1)], [nan(), 0], [nan(), + inf(1), + ], + [nan(), nan()], + ] + hypot_sc_ := [inf(1), inf(1), inf(1), inf(1), 0, 0, 0, 0, inf(1), + inf(1), nan(), inf(1), inf(1), inf(1), inf(1), inf(1), + nan(), inf(1), nan()] + for i := 0; i < vfhypot_sc_.len; i++ { + f := hypot(vfhypot_sc_[i][0], vfhypot_sc_[i][1]) + assert alike(hypot_sc_[i], f) + } +} + +fn test_ldexp() { + for i := 0; i < math.vf_.len; i++ { + f := ldexp(math.frexp_[i].f, math.frexp_[i].i) + assert veryclose(math.vf_[i], f) + } + vffrexp_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + frexp_sc_ := [Fi{inf(-1), 0}, Fi{copysign(0, -1), 0}, Fi{0, 0}, + Fi{inf(1), 0}, Fi{nan(), 0}] + for i := 0; i < vffrexp_sc_.len; i++ { + f := ldexp(frexp_sc_[i].f, frexp_sc_[i].i) + assert alike(vffrexp_sc_[i], f) + } + vfldexp_sc_ := [Fi{0, 0}, Fi{0, -1075}, Fi{0, 1024}, Fi{copysign(0, -1), 0}, + Fi{copysign(0, -1), -1075}, Fi{copysign(0, -1), 1024}, + Fi{inf(1), 0}, Fi{inf(1), -1024}, Fi{inf(-1), 0}, Fi{inf(-1), -1024}, + Fi{nan(), -1024}, Fi{10, 1 << (u64(sizeof(int) - 1) * 8)}, + Fi{10, -(1 << (u64(sizeof(int) - 1) * 8))}, + ] + ldexp_sc_ := [f64(0), 0, 0, copysign(0, -1), copysign(0, -1), + copysign(0, -1), inf(1), inf(1), inf(-1), inf(-1), nan(), + inf(1), 0] + for i := 0; i < vfldexp_sc_.len; i++ { + f := ldexp(vfldexp_sc_[i].f, vfldexp_sc_[i].i) + assert alike(ldexp_sc_[i], f) + } +} + +fn test_log_gamma() { + for i := 0; i < math.vf_.len; i++ { + f, s := log_gamma_sign(math.vf_[i]) + assert soclose(math.log_gamma_[i].f, f, 1e-6) && math.log_gamma_[i].i == s + } + // vflog_gamma_sc_ := [inf(-1), -3, 0, 1, 2, inf(1), nan()] + // log_gamma_sc_ := [Fi{inf(-1), 1}, Fi{inf(1), 1}, Fi{inf(1), 1}, + // Fi{0, 1}, Fi{0, 1}, Fi{inf(1), 1}, Fi{nan(), 1}] + // for i := 0; i < vflog_gamma_sc_.len; i++ { + // f, s := log_gamma_sign(vflog_gamma_sc_[i]) + // assert alike(log_gamma_sc_[i].f, f) && log_gamma_sc_[i].i == s + // } +} + +fn test_log() { + for i := 0; i < math.vf_.len; i++ { + a := abs(math.vf_[i]) + f := log(a) + assert math.log_[i] == f + } + vflog_sc_ := [inf(-1), -pi, copysign(0, -1), 0, 1, inf(1), + nan(), + ] + log_sc_ := [nan(), nan(), inf(-1), inf(-1), 0, inf(1), nan()] + f := log(10) + assert f == ln10 + for i := 0; i < vflog_sc_.len; i++ { + g := log(vflog_sc_[i]) + assert alike(log_sc_[i], g) + } +} + +fn test_log10() { + for i := 0; i < math.vf_.len; i++ { + a := abs(math.vf_[i]) + f := log10(a) + assert veryclose(math.log10_[i], f) + } + vflog_sc_ := [inf(-1), -pi, copysign(0, -1), 0, 1, inf(1), + nan(), + ] + log_sc_ := [nan(), nan(), inf(-1), inf(-1), 0, inf(1), nan()] + for i := 0; i < vflog_sc_.len; i++ { + f := log10(vflog_sc_[i]) + assert alike(log_sc_[i], f) + } +} + +fn test_pow() { + for i := 0; i < math.vf_.len; i++ { + f := pow(10, math.vf_[i]) + assert close(math.pow_[i], f) + } + vfpow_sc_ := [[inf(-1), -pi], [inf(-1), -3], [inf(-1), -0.0], + [inf(-1), 0], [inf(-1), 1], [inf(-1), 3], [inf(-1), pi], + [inf(-1), 0.5], [inf(-1), nan()], [-pi, inf(-1)], [-pi, -pi], + [-pi, -0.0], [-pi, 0], [-pi, 1], [-pi, pi], [-pi, inf(1)], + [-pi, nan()], [f64(-1), inf(-1)], [f64(-1), inf(1)], [f64(-1), nan()], + [f64(-1 / 2), inf(-1)], [f64(-1 / 2), inf(1)], [f64(-0.0), inf(-1)], + [f64(-0.0), -pi], [f64(-0.0), -0.5], [f64(-0.0), -3], + [f64(-0.0), 3], [f64(-0.0), pi], [f64(-0.0), 0.5], [f64(-0.0), inf(1)], + [f64(0), inf(-1)], [f64(0), -pi], [f64(0), -3], [f64(0), -0.0], + [f64(0), 0], [f64(0), 3], [f64(0), pi], [f64(0), inf(1)], + [f64(0), nan()], [f64(1 / 2), inf(-1)], [f64(1 / 2), inf(1)], + [f64(1), inf(-1)], [f64(1), inf(1)], [f64(1), nan()], + [pi, inf(-1)], [pi, -0.0], [pi, 0], [pi, 1], [pi, inf(1)], + [pi, nan()], [inf(1), -pi], [inf(1), -0.0], [inf(1), 0], + [inf(1), 1], [inf(1), pi], [inf(1), nan()], [nan(), -pi], + [nan(), -0.0], [nan(), 0], [nan(), 1], [nan(), pi], [nan(), + nan(), + ]] + pow_sc_ := [f64(0), /* pow(-inf, -pi) */ -0.0, /* pow(-inf, -3) */ 1, /* pow(-inf, -0) */ 1, /* pow(-inf, +0) */ + inf(-1), /* pow(-inf, 1) */ inf(-1), /* pow(-inf, 3) */ + inf(1), /* pow(-inf, pi) */ inf(1), /* pow(-inf, 0.5) */ + nan(), /* pow(-inf, nan) */ 0, /* pow(-pi, -inf) */ nan(), /* pow(-pi, -pi) */ + 1, /* pow(-pi, -0) */ 1, /* pow(-pi, +0) */ -pi, /* pow(-pi, 1) */ nan(), /* pow(-pi, pi) */ + inf(1), /* pow(-pi, +inf) */ nan(), /* pow(-pi, nan) */ 1, /* pow(-1, -inf) IEEE 754-2008 */ + 1, /* pow(-1, +inf) IEEE 754-2008 */ nan(), /* pow(-1, nan) */ + inf(1), /* pow(-1/2, -inf) */ 0, /* pow(-1/2, +inf) */ inf(1), /* pow(-0, -inf) */ + inf(1), /* pow(-0, -pi) */ inf(1), /* pow(-0, -0.5) */ + inf(-1), /* pow(-0, -3) IEEE 754-2008 */ -0.0, /* pow(-0, 3) IEEE 754-2008 */ 0, /* pow(-0, pi) */ + 0, /* pow(-0, 0.5) */ 0, /* pow(-0, +inf) */ inf(1), /* pow(+0, -inf) */ + inf(1), /* pow(+0, -pi) */ inf(1), /* pow(+0, -3) */ 1, /* pow(+0, -0) */ 1, /* pow(+0, +0) */ + 0, /* pow(+0, 3) */ 0, + /* pow(+0, pi) */ 0, /* pow(+0, +inf) */ nan(), /* pow(+0, nan) */ + inf(1), /* pow(1/2, -inf) */ 0, /* pow(1/2, +inf) */ 1, /* pow(1, -inf) IEEE 754-2008 */ + 1, /* pow(1, +inf) IEEE 754-2008 */ 1, /* pow(1, nan) IEEE 754-2008 */ 0, /* pow(pi, -inf) */ + 1, /* pow(pi, -0) */ 1, /* pow(pi, +0) */ pi, /* pow(pi, 1) */ inf(1), /* pow(pi, +inf) */ + nan(), /* pow(pi, nan) */ 0, /* pow(+inf, -pi) */ 1, /* pow(+inf, -0) */ 1, /* pow(+inf, +0) */ + inf(1), /* pow(+inf, 1) */ inf(1), /* pow(+inf, pi) */ + nan(), /* pow(+inf, nan) */ nan(), /* pow(nan, -pi) */ 1, /* pow(nan, -0) */ 1, /* pow(nan, +0) */ + nan(), /* pow(nan, 1) */ nan(), /* pow(nan, pi) */ nan(), /* pow(nan, nan) */] + for i := 0; i < vfpow_sc_.len; i++ { + f := pow(vfpow_sc_[i][0], vfpow_sc_[i][1]) + assert alike(pow_sc_[i], f) + } +} + +fn test_round() { + for i := 0; i < math.vf_.len; i++ { + f := round(math.vf_[i]) + // @todo: Figure out why is this happening and fix it + if math.round_[i] == 0 { + // 0 compared to -0 with alike fails + continue + } + assert alike(math.round_[i], f) + } + vfround_sc_ := [[f64(0), 0], [nan(), nan()], [inf(1), inf(1)]] + // vfround_even_sc_ := [[f64(0), 0], [f64(1.390671161567e-309), 0], /* denormal */ + // [f64(0.49999999999999994), 0], /* 0.5-epsilon */ [f64(0.5), 0], + // [f64(0.5000000000000001), 1], /* 0.5+epsilon */ [f64(-1.5), -2], + // [f64(-2.5), -2], [nan(), nan()], [inf(1), inf(1)], + // [f64(2251799813685249.5), 2251799813685250], + // // 1 bit fractian [f64(2251799813685250.5), 2251799813685250], + // [f64(4503599627370495.5), 4503599627370496], /* 1 bit fraction, rounding to 0 bit fractian */ + // [f64(4503599627370497), 4503599627370497], /* large integer */ + // ] + for i := 0; i < vfround_sc_.len; i++ { + f := round(vfround_sc_[i][0]) + assert alike(vfround_sc_[i][1], f) + } +} + +fn test_sin() { + for i := 0; i < math.vf_.len; i++ { + f := sin(math.vf_[i]) + assert veryclose(math.sin_[i], f) + } + vfsin_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + sin_sc_ := [nan(), copysign(0, -1), 0, nan(), nan()] + for i := 0; i < vfsin_sc_.len; i++ { + f := sin(vfsin_sc_[i]) + assert alike(sin_sc_[i], f) + } +} + +fn test_sincos() { + for i := 0; i < math.vf_.len; i++ { + f, g := sincos(math.vf_[i]) + assert veryclose(math.sin_[i], f) + assert veryclose(math.cos_[i], g) + } + vfsin_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + sin_sc_ := [nan(), copysign(0, -1), 0, nan(), nan()] + for i := 0; i < vfsin_sc_.len; i++ { + f, _ := sincos(vfsin_sc_[i]) + assert alike(sin_sc_[i], f) + } + vfcos_sc_ := [inf(-1), inf(1), nan()] + cos_sc_ := [nan(), nan(), nan()] + for i := 0; i < vfcos_sc_.len; i++ { + _, f := sincos(vfcos_sc_[i]) + assert alike(cos_sc_[i], f) + } +} + +fn test_sinh() { + for i := 0; i < math.vf_.len; i++ { + f := sinh(math.vf_[i]) + assert close(math.sinh_[i], f) + } + vfsinh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + sinh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfsinh_sc_.len; i++ { + f := sinh(vfsinh_sc_[i]) + assert alike(sinh_sc_[i], f) + } +} + +fn test_sqrt() { + for i := 0; i < math.vf_.len; i++ { + mut a := abs(math.vf_[i]) + mut f := sqrt(a) + assert veryclose(math.sqrt_[i], f) + a = abs(math.vf_[i]) + f = sqrt(a) + assert veryclose(math.sqrt_[i], f) + } + vfsqrt_sc_ := [inf(-1), -pi, copysign(0, -1), 0, inf(1), nan()] + sqrt_sc_ := [nan(), nan(), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfsqrt_sc_.len; i++ { + mut f := sqrt(vfsqrt_sc_[i]) + assert alike(sqrt_sc_[i], f) + f = sqrt(vfsqrt_sc_[i]) + assert alike(sqrt_sc_[i], f) + } +} + +fn test_tan() { + for i := 0; i < math.vf_.len; i++ { + f := tan(math.vf_[i]) + assert veryclose(math.tan_[i], f) + } + vfsin_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + sin_sc_ := [nan(), copysign(0, -1), 0, nan(), nan()] + // same special cases as sin + for i := 0; i < vfsin_sc_.len; i++ { + f := tan(vfsin_sc_[i]) + assert alike(sin_sc_[i], f) + } +} + +fn test_tanh() { + for i := 0; i < math.vf_.len; i++ { + f := tanh(math.vf_[i]) + assert veryclose(math.tanh_[i], f) + } + vftanh_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + tanh_sc_ := [f64(-1), copysign(0, -1), 0, 1, nan()] + for i := 0; i < vftanh_sc_.len; i++ { + f := tanh(vftanh_sc_[i]) + assert alike(tanh_sc_[i], f) + } +} + +fn test_trunc() { + // for i := 0; i < vf_.len; i++ { + // f := trunc(vf_[i]) + // assert alike(trunc_[i], f) + // } + vfceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + ceil_sc_ := [inf(-1), copysign(0, -1), 0, inf(1), nan()] + for i := 0; i < vfceil_sc_.len; i++ { + f := trunc(vfceil_sc_[i]) + assert alike(ceil_sc_[i], f) + } +} + +fn test_gcd() { + assert gcd(6, 9) == 3 + assert gcd(6, -9) == 3 + assert gcd(-6, -9) == 3 + assert gcd(0, 0) == 0 +} + +fn test_egcd() { + helper := fn (a i64, b i64, expected_g i64) { + g, x, y := egcd(a, b) + assert g == expected_g + assert abs(a * x + b * y) == g + } + + helper(6, 9, 3) + helper(6, -9, 3) + helper(-6, -9, 3) + helper(0, 0, 0) +} + +fn test_lcm() { + assert lcm(2, 3) == 6 + assert lcm(-2, 3) == 6 + assert lcm(-2, -3) == 6 + assert lcm(0, 0) == 0 +} + +fn test_digits() { + digits_in_10th_base := digits(125, 10) + assert digits_in_10th_base[0] == 5 + assert digits_in_10th_base[1] == 2 + assert digits_in_10th_base[2] == 1 + digits_in_16th_base := digits(15, 16) + assert digits_in_16th_base[0] == 15 + negative_digits := digits(-4, 2) + assert negative_digits[2] == -1 +} + +// Check that math functions of high angle values +// return accurate results. [since (vf_[i] + large) - large != vf_[i], +// testing for Trig(vf_[i] + large) == Trig(vf_[i]), where large is +// a multiple of 2 * pi, is misleading.] +fn test_large_cos() { + large := 100000.0 * pi + for i := 0; i < math.vf_.len; i++ { + f1 := math.cos_large_[i] + f2 := cos(math.vf_[i] + large) + assert soclose(f1, f2, 4e-8) + } +} + +fn test_large_sin() { + large := 100000.0 * pi + for i := 0; i < math.vf_.len; i++ { + f1 := math.sin_large_[i] + f2 := sin(math.vf_[i] + large) + assert soclose(f1, f2, 4e-9) + } +} + +fn test_large_tan() { + large := 100000.0 * pi + for i := 0; i < math.vf_.len; i++ { + f1 := math.tan_large_[i] + f2 := tan(math.vf_[i] + large) + assert soclose(f1, f2, 4e-8) + } +} diff --git a/v_windows/v/vlib/math/mathutil/mathutil.v b/v_windows/v/vlib/math/mathutil/mathutil.v new file mode 100644 index 0000000..0930e26 --- /dev/null +++ b/v_windows/v/vlib/math/mathutil/mathutil.v @@ -0,0 +1,19 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module mathutil + +[inline] +pub fn min(a T, b T) T { + return if a < b { a } else { b } +} + +[inline] +pub fn max(a T, b T) T { + return if a > b { a } else { b } +} + +[inline] +pub fn abs(a T) T { + return if a > 0 { a } else { -a } +} diff --git a/v_windows/v/vlib/math/mathutil/mathutil_test.v b/v_windows/v/vlib/math/mathutil/mathutil_test.v new file mode 100644 index 0000000..6b3b68a --- /dev/null +++ b/v_windows/v/vlib/math/mathutil/mathutil_test.v @@ -0,0 +1,22 @@ +import math.mathutil as mu + +fn test_min() { + assert mu.min(42, 13) == 13 + assert mu.min(5, -10) == -10 + assert mu.min(7.1, 7.3) == 7.1 + assert mu.min(u32(32), u32(17)) == 17 +} + +fn test_max() { + assert mu.max(42, 13) == 42 + assert mu.max(5, -10) == 5 + assert mu.max(7.1, 7.3) == 7.3 + assert mu.max(u32(60), u32(17)) == 60 +} + +fn test_abs() { + assert mu.abs(99) == 99 + assert mu.abs(-10) == 10 + assert mu.abs(1.2345) == 1.2345 + assert mu.abs(-5.5) == 5.5 +} diff --git a/v_windows/v/vlib/math/modf.v b/v_windows/v/vlib/math/modf.v new file mode 100644 index 0000000..bac08bf --- /dev/null +++ b/v_windows/v/vlib/math/modf.v @@ -0,0 +1,29 @@ +module math + +const ( + modf_maxpowtwo = 4.503599627370496000e+15 +) + +// modf returns integer and fractional floating-point numbers +// that sum to f. Both values have the same sign as f. +// +// special cases are: +// modf(±inf) = ±inf, nan +// modf(nan) = nan, nan +pub fn modf(f f64) (f64, f64) { + abs_f := abs(f) + mut i := 0.0 + if abs_f >= math.modf_maxpowtwo { + i = f // it must be an integer + } else { + i = abs_f + math.modf_maxpowtwo // shift fraction off right + i -= math.modf_maxpowtwo // shift back without fraction + for i > abs_f { // above arithmetic might round + i -= 1.0 // test again just to be sure + } + if f < 0.0 { + i = -i + } + } + return i, f - i // signed fractional part +} diff --git a/v_windows/v/vlib/math/nextafter.v b/v_windows/v/vlib/math/nextafter.v new file mode 100644 index 0000000..8aef904 --- /dev/null +++ b/v_windows/v/vlib/math/nextafter.v @@ -0,0 +1,45 @@ +module math + +// nextafter32 returns the next representable f32 value after x towards y. +// +// special cases are: +// nextafter32(x, x) = x +// nextafter32(nan, y) = nan +// nextafter32(x, nan) = nan +pub fn nextafter32(x f32, y f32) f32 { + mut r := f32(0.0) + if is_nan(f64(x)) || is_nan(f64(y)) { + r = f32(nan()) + } else if x == y { + r = x + } else if x == 0 { + r = f32(copysign(f64(f32_from_bits(1)), f64(y))) + } else if (y > x) == (x > 0) { + r = f32_from_bits(f32_bits(x) + 1) + } else { + r = f32_from_bits(f32_bits(x) - 1) + } + return r +} + +// nextafter returns the next representable f64 value after x towards y. +// +// special cases are: +// nextafter(x, x) = x +// nextafter(nan, y) = nan +// nextafter(x, nan) = nan +pub fn nextafter(x f64, y f64) f64 { + mut r := 0.0 + if is_nan(x) || is_nan(y) { + r = nan() + } else if x == y { + r = x + } else if x == 0 { + r = copysign(f64_from_bits(1), y) + } else if (y > x) == (x > 0) { + r = f64_from_bits(f64_bits(x) + 1) + } else { + r = f64_from_bits(f64_bits(x) - 1) + } + return r +} diff --git a/v_windows/v/vlib/math/poly.v b/v_windows/v/vlib/math/poly.v new file mode 100644 index 0000000..2b62638 --- /dev/null +++ b/v_windows/v/vlib/math/poly.v @@ -0,0 +1,65 @@ +module math + +import math.internal + +fn poly_n_eval(c []f64, n int, x f64) f64 { + if c.len == 0 { + panic('coeficients can not be empty') + } + len := int(min(c.len, n)) + mut ans := c[len - 1] + for e in c[..len - 1] { + ans = e + x * ans + } + return ans +} + +fn poly_n_1_eval(c []f64, n int, x f64) f64 { + if c.len == 0 { + panic('coeficients can not be empty') + } + len := int(min(c.len, n)) - 1 + mut ans := c[len - 1] + for e in c[..len - 1] { + ans = e + x * ans + } + return ans +} + +[inline] +fn poly_eval(c []f64, x f64) f64 { + return poly_n_eval(c, c.len, x) +} + +[inline] +fn poly_1_eval(c []f64, x f64) f64 { + return poly_n_1_eval(c, c.len, x) +} + +// data for a Chebyshev series over a given interval +struct ChebSeries { +pub: + c []f64 // coefficients + order int // order of expansion + a f64 // lower interval point + b f64 // upper interval point +} + +fn (cs ChebSeries) eval_e(x f64) (f64, f64) { + mut d := 0.0 + mut dd := 0.0 + y := (2.0 * x - cs.a - cs.b) / (cs.b - cs.a) + y2 := 2.0 * y + mut e_ := 0.0 + mut temp := 0.0 + for j := cs.order; j >= 1; j-- { + temp = d + d = y2 * d - dd + cs.c[j] + e_ += abs(y2 * temp) + abs(dd) + abs(cs.c[j]) + dd = temp + } + temp = d + d = y * d - dd + 0.5 * cs.c[0] + e_ += abs(y * temp) + abs(dd) + 0.5 * abs(cs.c[0]) + return d, f64(internal.f64_epsilon) * e_ + abs(cs.c[cs.order]) +} diff --git a/v_windows/v/vlib/math/pow.c.v b/v_windows/v/vlib/math/pow.c.v new file mode 100644 index 0000000..7344f50 --- /dev/null +++ b/v_windows/v/vlib/math/pow.c.v @@ -0,0 +1,17 @@ +module math + +fn C.pow(x f64, y f64) f64 + +fn C.powf(x f32, y f32) f32 + +// pow returns base raised to the provided power. +[inline] +pub fn pow(a f64, b f64) f64 { + return C.pow(a, b) +} + +// powf returns base raised to the provided power. (float32) +[inline] +pub fn powf(a f32, b f32) f32 { + return C.powf(a, b) +} diff --git a/v_windows/v/vlib/math/pow.js.v b/v_windows/v/vlib/math/pow.js.v new file mode 100644 index 0000000..c8a5129 --- /dev/null +++ b/v_windows/v/vlib/math/pow.js.v @@ -0,0 +1,7 @@ +module math + +fn JS.Math.pow(x f64, y f64) f64 + +pub fn pow(x f64, y f64) f64 { + return JS.Math.pow(x, y) +} diff --git a/v_windows/v/vlib/math/pow.v b/v_windows/v/vlib/math/pow.v new file mode 100644 index 0000000..bc8034b --- /dev/null +++ b/v_windows/v/vlib/math/pow.v @@ -0,0 +1,37 @@ +module math + +const ( + pow10tab = [f64(1e+00), 1e+01, 1e+02, 1e+03, 1e+04, 1e+05, 1e+06, 1e+07, 1e+08, 1e+09, + 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22, + 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31] + pow10postab32 = [f64(1e+00), 1e+32, 1e+64, 1e+96, 1e+128, 1e+160, 1e+192, 1e+224, 1e+256, 1e+288] + pow10negtab32 = [f64(1e-00), 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, + 1e-320, + ] +) + +// powf returns base raised to the provided power. (float32) +[inline] +pub fn powf(a f32, b f32) f32 { + return f32(pow(a, b)) +} + +// pow10 returns 10**n, the base-10 exponential of n. +// +// special cases are: +// pow10(n) = 0 for n < -323 +// pow10(n) = +inf for n > 308 +pub fn pow10(n int) f64 { + if 0 <= n && n <= 308 { + return math.pow10postab32[u32(n) / 32] * math.pow10tab[u32(n) % 32] + } + if -323 <= n && n <= 0 { + return math.pow10negtab32[u32(-n) / 32] / math.pow10tab[u32(-n) % 32] + } + // n < -323 || 308 < n + if n > 0 { + return inf(1) + } + // n < -323 + return 0.0 +} diff --git a/v_windows/v/vlib/math/q_rsqrt.v b/v_windows/v/vlib/math/q_rsqrt.v new file mode 100644 index 0000000..570d3a3 --- /dev/null +++ b/v_windows/v/vlib/math/q_rsqrt.v @@ -0,0 +1,12 @@ +module math + +[inline] +pub fn q_rsqrt(x f64) f64 { + x_half := 0.5 * x + mut i := i64(f64_bits(x)) + i = 0x5fe6eb50c7b537a9 - (i >> 1) + mut j := f64_from_bits(u64(i)) + j *= (1.5 - x_half * j * j) + j *= (1.5 - x_half * j * j) + return j +} diff --git a/v_windows/v/vlib/math/sin.c.v b/v_windows/v/vlib/math/sin.c.v new file mode 100644 index 0000000..64098f4 --- /dev/null +++ b/v_windows/v/vlib/math/sin.c.v @@ -0,0 +1,33 @@ +module math + +fn C.cos(x f64) f64 + +fn C.cosf(x f32) f32 + +fn C.sin(x f64) f64 + +fn C.sinf(x f32) f32 + +// cos calculates cosine. +[inline] +pub fn cos(a f64) f64 { + return C.cos(a) +} + +// cosf calculates cosine. (float32) +[inline] +pub fn cosf(a f32) f32 { + return C.cosf(a) +} + +// sin calculates sine. +[inline] +pub fn sin(a f64) f64 { + return C.sin(a) +} + +// sinf calculates sine. (float32) +[inline] +pub fn sinf(a f32) f32 { + return C.sinf(a) +} diff --git a/v_windows/v/vlib/math/sin.js.v b/v_windows/v/vlib/math/sin.js.v new file mode 100644 index 0000000..40ef854 --- /dev/null +++ b/v_windows/v/vlib/math/sin.js.v @@ -0,0 +1,17 @@ +module math + +fn JS.Math.cos(x f64) f64 + +fn JS.Math.sin(x f64) f64 + +// cos calculates cosine. +[inline] +pub fn cos(a f64) f64 { + return JS.Math.cos(a) +} + +// sin calculates sine. +[inline] +pub fn sin(a f64) f64 { + return JS.Math.sin(a) +} diff --git a/v_windows/v/vlib/math/sin.v b/v_windows/v/vlib/math/sin.v new file mode 100644 index 0000000..193eb79 --- /dev/null +++ b/v_windows/v/vlib/math/sin.v @@ -0,0 +1,179 @@ +module math + +import math.internal + +const ( + sin_data = [ + -0.3295190160663511504173, + 0.0025374284671667991990, + 0.0006261928782647355874, + -4.6495547521854042157541e-06, + -5.6917531549379706526677e-07, + 3.7283335140973803627866e-09, + 3.0267376484747473727186e-10, + -1.7400875016436622322022e-12, + -1.0554678305790849834462e-13, + 5.3701981409132410797062e-16, + 2.5984137983099020336115e-17, + -1.1821555255364833468288e-19, + ] + sin_cs = ChebSeries{ + c: sin_data + order: 11 + a: -1 + b: 1 + } + cos_data = [ + 0.165391825637921473505668118136, + -0.00084852883845000173671196530195, + -0.000210086507222940730213625768083, + 1.16582269619760204299639757584e-6, + 1.43319375856259870334412701165e-7, + -7.4770883429007141617951330184e-10, + -6.0969994944584252706997438007e-11, + 2.90748249201909353949854872638e-13, + 1.77126739876261435667156490461e-14, + -7.6896421502815579078577263149e-17, + -3.7363121133079412079201377318e-18, + ] + cos_cs = ChebSeries{ + c: cos_data + order: 10 + a: -1 + b: 1 + } +) + +pub fn sin(x f64) f64 { + p1 := 7.85398125648498535156e-1 + p2 := 3.77489470793079817668e-8 + p3 := 2.69515142907905952645e-15 + sgn_x := if x < 0 { -1 } else { 1 } + abs_x := abs(x) + if abs_x < internal.root4_f64_epsilon { + x2 := x * x + return x * (1.0 - x2 / 6.0) + } else { + mut sgn_result := sgn_x + mut y := floor(abs_x / (0.25 * pi)) + mut octant := int(y - ldexp(floor(ldexp(y, -3)), 3)) + if (octant & 1) == 1 { + octant++ + octant &= 7 + y += 1.0 + } + if octant > 3 { + octant -= 4 + sgn_result = -sgn_result + } + z := ((abs_x - y * p1) - y * p2) - y * p3 + mut result := 0.0 + if octant == 0 { + t := 8.0 * abs(z) / pi - 1.0 + sin_cs_val, _ := math.sin_cs.eval_e(t) + result = z * (1.0 + z * z * sin_cs_val) + } else { + t := 8.0 * abs(z) / pi - 1.0 + cos_cs_val, _ := math.cos_cs.eval_e(t) + result = 1.0 - 0.5 * z * z * (1.0 - z * z * cos_cs_val) + } + result *= sgn_result + return result + } +} + +pub fn cos(x f64) f64 { + p1 := 7.85398125648498535156e-1 + p2 := 3.77489470793079817668e-8 + p3 := 2.69515142907905952645e-15 + abs_x := abs(x) + if abs_x < internal.root4_f64_epsilon { + x2 := x * x + return 1.0 - 0.5 * x2 + } else { + mut sgn_result := 1 + mut y := floor(abs_x / (0.25 * pi)) + mut octant := int(y - ldexp(floor(ldexp(y, -3)), 3)) + if (octant & 1) == 1 { + octant++ + octant &= 7 + y += 1.0 + } + if octant > 3 { + octant -= 4 + sgn_result = -sgn_result + } + if octant > 1 { + sgn_result = -sgn_result + } + z := ((abs_x - y * p1) - y * p2) - y * p3 + mut result := 0.0 + if octant == 0 { + t := 8.0 * abs(z) / pi - 1.0 + cos_cs_val, _ := math.cos_cs.eval_e(t) + result = 1.0 - 0.5 * z * z * (1.0 - z * z * cos_cs_val) + } else { + t := 8.0 * abs(z) / pi - 1.0 + sin_cs_val, _ := math.sin_cs.eval_e(t) + result = z * (1.0 + z * z * sin_cs_val) + } + result *= sgn_result + return result + } +} + +// cosf calculates cosine. (float32). +[inline] +pub fn cosf(a f32) f32 { + return f32(cos(a)) +} + +// sinf calculates sine. (float32) +[inline] +pub fn sinf(a f32) f32 { + return f32(sin(a)) +} + +pub fn sincos(x f64) (f64, f64) { + p1 := 7.85398125648498535156e-1 + p2 := 3.77489470793079817668e-8 + p3 := 2.69515142907905952645e-15 + sgn_x := if x < 0 { -1 } else { 1 } + abs_x := abs(x) + if abs_x < internal.root4_f64_epsilon { + x2 := x * x + return x * (1.0 - x2 / 6.0), 1.0 - 0.5 * x2 + } else { + mut sgn_result_sin := sgn_x + mut sgn_result_cos := 1 + mut y := floor(abs_x / (0.25 * pi)) + mut octant := int(y - ldexp(floor(ldexp(y, -3)), 3)) + if (octant & 1) == 1 { + octant++ + octant &= 7 + y += 1.0 + } + if octant > 3 { + octant -= 4 + sgn_result_sin = -sgn_result_sin + sgn_result_cos = -sgn_result_cos + } + sgn_result_cos = if octant > 1 { -sgn_result_cos } else { sgn_result_cos } + z := ((abs_x - y * p1) - y * p2) - y * p3 + t := 8.0 * abs(z) / pi - 1.0 + sin_cs_val, _ := math.sin_cs.eval_e(t) + cos_cs_val, _ := math.cos_cs.eval_e(t) + mut result_sin := 0.0 + mut result_cos := 0.0 + if octant == 0 { + result_sin = z * (1.0 + z * z * sin_cs_val) + result_cos = 1.0 - 0.5 * z * z * (1.0 - z * z * cos_cs_val) + } else { + result_sin = 1.0 - 0.5 * z * z * (1.0 - z * z * cos_cs_val) + result_cos = z * (1.0 + z * z * sin_cs_val) + } + result_sin *= sgn_result_sin + result_cos *= sgn_result_cos + return result_sin, result_cos + } +} diff --git a/v_windows/v/vlib/math/sinh.c.v b/v_windows/v/vlib/math/sinh.c.v new file mode 100644 index 0000000..b25eef0 --- /dev/null +++ b/v_windows/v/vlib/math/sinh.c.v @@ -0,0 +1,17 @@ +module math + +fn C.cosh(x f64) f64 + +fn C.sinh(x f64) f64 + +// cosh calculates hyperbolic cosine. +[inline] +pub fn cosh(a f64) f64 { + return C.cosh(a) +} + +// sinh calculates hyperbolic sine. +[inline] +pub fn sinh(a f64) f64 { + return C.sinh(a) +} diff --git a/v_windows/v/vlib/math/sinh.js.v b/v_windows/v/vlib/math/sinh.js.v new file mode 100644 index 0000000..8c7d72f --- /dev/null +++ b/v_windows/v/vlib/math/sinh.js.v @@ -0,0 +1,17 @@ +module math + +fn JS.Math.cosh(x f64) f64 + +fn JS.Math.sinh(x f64) f64 + +// cosh calculates hyperbolic cosine. +[inline] +pub fn cosh(a f64) f64 { + return JS.Math.cosh(a) +} + +// sinh calculates hyperbolic sine. +[inline] +pub fn sinh(a f64) f64 { + return JS.Math.sinh(a) +} diff --git a/v_windows/v/vlib/math/sinh.v b/v_windows/v/vlib/math/sinh.v new file mode 100644 index 0000000..6bbf880 --- /dev/null +++ b/v_windows/v/vlib/math/sinh.v @@ -0,0 +1,49 @@ +module math + +// sinh calculates hyperbolic sine. +pub fn sinh(x_ f64) f64 { + mut x := x_ + // The coefficients are #2029 from Hart & Cheney. (20.36D) + p0 := -0.6307673640497716991184787251e+6 + p1 := -0.8991272022039509355398013511e+5 + p2 := -0.2894211355989563807284660366e+4 + p3 := -0.2630563213397497062819489e+2 + q0 := -0.6307673640497716991212077277e+6 + q1 := 0.1521517378790019070696485176e+5 + q2 := -0.173678953558233699533450911e+3 + mut sign := false + if x < 0 { + x = -x + sign = true + } + mut temp := 0.0 + if x > 21 { + temp = exp(x) * 0.5 + } else if x > 0.5 { + ex := exp(x) + temp = (ex - 1.0 / ex) * 0.5 + } else { + sq := x * x + temp = (((p3 * sq + p2) * sq + p1) * sq + p0) * x + temp = temp / (((sq + q2) * sq + q1) * sq + q0) + } + if sign { + temp = -temp + } + return temp +} + +// cosh returns the hyperbolic cosine of x. +// +// special cases are: +// cosh(±0) = 1 +// cosh(±inf) = +inf +// cosh(nan) = nan +pub fn cosh(x f64) f64 { + abs_x := abs(x) + if abs_x > 21 { + return exp(abs_x) * 0.5 + } + ex := exp(abs_x) + return (ex + 1.0 / ex) * 0.5 +} diff --git a/v_windows/v/vlib/math/sqrt.c.v b/v_windows/v/vlib/math/sqrt.c.v new file mode 100644 index 0000000..a070c32 --- /dev/null +++ b/v_windows/v/vlib/math/sqrt.c.v @@ -0,0 +1,17 @@ +module math + +fn C.sqrt(x f64) f64 + +fn C.sqrtf(x f32) f32 + +// sqrt calculates square-root of the provided value. +[inline] +pub fn sqrt(a f64) f64 { + return C.sqrt(a) +} + +// sqrtf calculates square-root of the provided value. (float32) +[inline] +pub fn sqrtf(a f32) f32 { + return C.sqrtf(a) +} diff --git a/v_windows/v/vlib/math/sqrt.v b/v_windows/v/vlib/math/sqrt.v new file mode 100644 index 0000000..6513b79 --- /dev/null +++ b/v_windows/v/vlib/math/sqrt.v @@ -0,0 +1,37 @@ +module math + +// special cases are: +// sqrt(+inf) = +inf +// sqrt(±0) = ±0 +// sqrt(x < 0) = nan +// sqrt(nan) = nan +[inline] +pub fn sqrt(a f64) f64 { + mut x := a + if x == 0.0 || is_nan(x) || is_inf(x, 1) { + return x + } + if x < 0.0 { + return nan() + } + z, ex := frexp(x) + w := x + // approximate square root of number between 0.5 and 1 + // relative error of approximation = 7.47e-3 + x = 4.173075996388649989089e-1 + 5.9016206709064458299663e-1 * z // adjust for odd powers of 2 + if (ex & 1) != 0 { + x *= sqrt2 + } + x = ldexp(x, ex >> 1) + // newton iterations + x = 0.5 * (x + w / x) + x = 0.5 * (x + w / x) + x = 0.5 * (x + w / x) + return x +} + +// sqrtf calculates square-root of the provided value. (float32) +[inline] +pub fn sqrtf(a f32) f32 { + return f32(sqrt(a)) +} diff --git a/v_windows/v/vlib/math/stats/stats.v b/v_windows/v/vlib/math/stats/stats.v new file mode 100644 index 0000000..d7317bf --- /dev/null +++ b/v_windows/v/vlib/math/stats/stats.v @@ -0,0 +1,249 @@ +module stats + +import math + +// TODO: Implement all of them with generics + +// This module defines the following statistical operations on f64 array +// --------------------------- +// | Summary of Functions | +// --------------------------- +// ----------------------------------------------------------------------- +// freq - Frequency +// mean - Mean +// geometric_mean - Geometric Mean +// harmonic_mean - Harmonic Mean +// median - Median +// mode - Mode +// rms - Root Mean Square +// population_variance - Population Variance +// sample_variance - Sample Variance +// population_stddev - Population Standard Deviation +// sample_stddev - Sample Standard Deviation +// mean_absdev - Mean Absolute Deviation +// min - Minimum of the Array +// max - Maximum of the Array +// range - Range of the Array ( max - min ) +// ----------------------------------------------------------------------- + +// Measure of Occurance +// Frequency of a given number +// Based on +// https://www.mathsisfun.com/data/frequency-distribution.html +pub fn freq(arr []f64, val f64) int { + if arr.len == 0 { + return 0 + } + mut count := 0 + for v in arr { + if v == val { + count++ + } + } + return count +} + +// Measure of Central Tendancy +// Mean of the given input array +// Based on +// https://www.mathsisfun.com/data/central-measures.html +pub fn mean(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut sum := f64(0) + for v in arr { + sum += v + } + return sum / f64(arr.len) +} + +// Measure of Central Tendancy +// Geometric Mean of the given input array +// Based on +// https://www.mathsisfun.com/numbers/geometric-mean.html +pub fn geometric_mean(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut sum := f64(1) + for v in arr { + sum *= v + } + return math.pow(sum, f64(1) / arr.len) +} + +// Measure of Central Tendancy +// Harmonic Mean of the given input array +// Based on +// https://www.mathsisfun.com/numbers/harmonic-mean.html +pub fn harmonic_mean(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut sum := f64(0) + for v in arr { + sum += f64(1) / v + } + return f64(arr.len) / sum +} + +// Measure of Central Tendancy +// Median of the given input array ( input array is assumed to be sorted ) +// Based on +// https://www.mathsisfun.com/data/central-measures.html +pub fn median(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + if arr.len % 2 == 0 { + mid := (arr.len / 2) - 1 + return (arr[mid] + arr[mid + 1]) / f64(2) + } else { + return arr[((arr.len - 1) / 2)] + } +} + +// Measure of Central Tendancy +// Mode of the given input array +// Based on +// https://www.mathsisfun.com/data/central-measures.html +pub fn mode(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut freqs := []int{} + for v in arr { + freqs << freq(arr, v) + } + mut max := 0 + for i in 0 .. freqs.len { + if freqs[i] > freqs[max] { + max = i + } + } + return arr[max] +} + +// Root Mean Square of the given input array +// Based on +// https://en.wikipedia.org/wiki/Root_mean_square +pub fn rms(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut sum := f64(0) + for v in arr { + sum += math.pow(v, 2) + } + return math.sqrt(sum / f64(arr.len)) +} + +// Measure of Dispersion / Spread +// Population Variance of the given input array +// Based on +// https://www.mathsisfun.com/data/standard-deviation.html +pub fn population_variance(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + m := mean(arr) + mut sum := f64(0) + for v in arr { + sum += math.pow(v - m, 2) + } + return sum / f64(arr.len) +} + +// Measure of Dispersion / Spread +// Sample Variance of the given input array +// Based on +// https://www.mathsisfun.com/data/standard-deviation.html +pub fn sample_variance(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + m := mean(arr) + mut sum := f64(0) + for v in arr { + sum += math.pow(v - m, 2) + } + return sum / f64(arr.len - 1) +} + +// Measure of Dispersion / Spread +// Population Standard Deviation of the given input array +// Based on +// https://www.mathsisfun.com/data/standard-deviation.html +pub fn population_stddev(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + return math.sqrt(population_variance(arr)) +} + +// Measure of Dispersion / Spread +// Sample Standard Deviation of the given input array +// Based on +// https://www.mathsisfun.com/data/standard-deviation.html +pub fn sample_stddev(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + return math.sqrt(sample_variance(arr)) +} + +// Measure of Dispersion / Spread +// Mean Absolute Deviation of the given input array +// Based on +// https://en.wikipedia.org/wiki/Average_absolute_deviation +pub fn mean_absdev(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + amean := mean(arr) + mut sum := f64(0) + for v in arr { + sum += math.abs(v - amean) + } + return sum / f64(arr.len) +} + +// Minimum of the given input array +pub fn min(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut min := arr[0] + for v in arr { + if v < min { + min = v + } + } + return min +} + +// Maximum of the given input array +pub fn max(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + mut max := arr[0] + for v in arr { + if v > max { + max = v + } + } + return max +} + +// Measure of Dispersion / Spread +// Range ( Maximum - Minimum ) of the given input array +// Based on +// https://www.mathsisfun.com/data/range.html +pub fn range(arr []f64) f64 { + if arr.len == 0 { + return f64(0) + } + return max(arr) - min(arr) +} diff --git a/v_windows/v/vlib/math/stats/stats_test.v b/v_windows/v/vlib/math/stats/stats_test.v new file mode 100644 index 0000000..c18daff --- /dev/null +++ b/v_windows/v/vlib/math/stats/stats_test.v @@ -0,0 +1,269 @@ +import math.stats +import math + +fn test_freq() { + // Tests were also verified on Wolfram Alpha + data := [f64(10.0), f64(10.0), f64(5.9), f64(2.7)] + mut o := stats.freq(data, 10.0) + assert o == 2 + o = stats.freq(data, 2.7) + assert o == 1 + o = stats.freq(data, 15) + assert o == 0 +} + +fn tst_res(str1 string, str2 string) bool { + if (math.abs(str1.f64() - str2.f64())) < 1e-5 { + return true + } + return false +} + +fn test_mean() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '5.762500') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '17.650000') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '37.708000') +} + +fn test_geometric_mean() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.geometric_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '5.15993') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.geometric_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert o.str() == 'nan' || o.str() == '-nan' || o.str() == '-1.#IND00' || o == f64(0) + || o.str() == '-nan(ind)' // Because in math it yields a complex number + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.geometric_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '25.064496') +} + +fn test_harmonic_mean() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.harmonic_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '4.626519') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.harmonic_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '9.134577') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.harmonic_mean(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '16.555477') +} + +fn test_median() { + // Tests were also verified on Wolfram Alpha + // Assumes sorted array + + // Even + mut data := [f64(2.7), f64(4.45), f64(5.9), f64(10.0)] + mut o := stats.median(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '5.175000') + data = [f64(-3.0), f64(1.89), f64(4.4), f64(67.31)] + o = stats.median(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '3.145000') + data = [f64(7.88), f64(12.0), f64(54.83), f64(76.122)] + o = stats.median(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '33.415000') + + // Odd + data = [f64(2.7), f64(4.45), f64(5.9), f64(10.0), f64(22)] + o = stats.median(data) + assert o == f64(5.9) + data = [f64(-3.0), f64(1.89), f64(4.4), f64(9), f64(67.31)] + o = stats.median(data) + assert o == f64(4.4) + data = [f64(7.88), f64(3.3), f64(12.0), f64(54.83), f64(76.122)] + o = stats.median(data) + assert o == f64(12.0) +} + +fn test_mode() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(2.7), f64(2.7), f64(4.45), f64(5.9), f64(10.0)] + mut o := stats.mode(data) + assert o == f64(2.7) + data = [f64(-3.0), f64(1.89), f64(1.89), f64(1.89), f64(9), f64(4.4), f64(4.4), f64(9), + f64(67.31), + ] + o = stats.mode(data) + assert o == f64(1.89) + // Testing greedy nature + data = [f64(2.0), f64(4.0), f64(2.0), f64(4.0)] + o = stats.mode(data) + assert o == f64(2.0) +} + +fn test_rms() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.rms(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '6.362046') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.rms(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '33.773393') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.rms(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '47.452561') +} + +fn test_population_variance() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.population_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '7.269219') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.population_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '829.119550') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.population_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '829.852282') +} + +fn test_sample_variance() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.sample_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '9.692292') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.sample_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '1105.492733') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.sample_variance(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '1106.469709') +} + +fn test_population_stddev() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.population_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '2.696149') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.population_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '28.794436') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.population_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '28.807157') +} + +fn test_sample_stddev() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.sample_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '3.113245') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.sample_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '33.248951') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.sample_stddev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '33.263639') +} + +fn test_mean_absdev() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.mean_absdev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '2.187500') + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.mean_absdev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '24.830000') + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.mean_absdev(data) + // Some issue with precision comparison in f64 using == operator hence serializing to string + assert tst_res(o.str(), '27.768000') +} + +fn test_min() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.min(data) + assert o == f64(2.7) + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.min(data) + assert o == f64(-3.0) + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.min(data) + assert o == f64(7.88) +} + +fn test_max() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.max(data) + assert o == f64(10.0) + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.max(data) + assert o == f64(67.31) + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.max(data) + assert o == f64(76.122) +} + +fn test_range() { + // Tests were also verified on Wolfram Alpha + mut data := [f64(10.0), f64(4.45), f64(5.9), f64(2.7)] + mut o := stats.range(data) + assert o == f64(7.3) + data = [f64(-3.0), f64(67.31), f64(4.4), f64(1.89)] + o = stats.range(data) + assert o == f64(70.31) + data = [f64(12.0), f64(7.88), f64(76.122), f64(54.83)] + o = stats.range(data) + assert o == f64(68.242) +} + +fn test_passing_empty() { + data := []f64{} + assert stats.freq(data, 0) == 0 + assert stats.mean(data) == f64(0) + assert stats.geometric_mean(data) == f64(0) + assert stats.harmonic_mean(data) == f64(0) + assert stats.median(data) == f64(0) + assert stats.mode(data) == f64(0) + assert stats.rms(data) == f64(0) + assert stats.population_variance(data) == f64(0) + assert stats.sample_variance(data) == f64(0) + assert stats.population_stddev(data) == f64(0) + assert stats.sample_stddev(data) == f64(0) + assert stats.mean_absdev(data) == f64(0) + assert stats.min(data) == f64(0) + assert stats.max(data) == f64(0) + assert stats.range(data) == f64(0) +} diff --git a/v_windows/v/vlib/math/tan.c.v b/v_windows/v/vlib/math/tan.c.v new file mode 100644 index 0000000..af8e1ca --- /dev/null +++ b/v_windows/v/vlib/math/tan.c.v @@ -0,0 +1,17 @@ +module math + +fn C.tan(x f64) f64 + +fn C.tanf(x f32) f32 + +// tan calculates tangent. +[inline] +pub fn tan(a f64) f64 { + return C.tan(a) +} + +// tanf calculates tangent. (float32) +[inline] +pub fn tanf(a f32) f32 { + return C.tanf(a) +} diff --git a/v_windows/v/vlib/math/tan.js.v b/v_windows/v/vlib/math/tan.js.v new file mode 100644 index 0000000..072e46d --- /dev/null +++ b/v_windows/v/vlib/math/tan.js.v @@ -0,0 +1,9 @@ +module math + +fn JS.Math.tan(x f64) f64 + +// tan calculates tangent. +[inline] +pub fn tan(a f64) f64 { + return JS.Math.tan(a) +} diff --git a/v_windows/v/vlib/math/tan.v b/v_windows/v/vlib/math/tan.v new file mode 100644 index 0000000..b37287f --- /dev/null +++ b/v_windows/v/vlib/math/tan.v @@ -0,0 +1,113 @@ +module math + +const ( + tan_p = [ + -1.30936939181383777646e+4, + 1.15351664838587416140e+6, + -1.79565251976484877988e+7, + ] + tan_q = [ + 1.00000000000000000000e+0, + 1.36812963470692954678e+4, + -1.32089234440210967447e+6, + 2.50083801823357915839e+7, + -5.38695755929454629881e+7, + ] + tan_dp1 = 7.853981554508209228515625e-1 + tan_dp2 = 7.94662735614792836714e-9 + tan_dp3 = 3.06161699786838294307e-17 + tan_lossth = 1.073741824e+9 +) + +// tan calculates tangent of a number +pub fn tan(a f64) f64 { + mut x := a + if x == 0.0 || is_nan(x) { + return x + } + if is_inf(x, 0) { + return nan() + } + mut sign := 1 // make argument positive but save the sign + if x < 0 { + x = -x + sign = -1 + } + if x > math.tan_lossth { + return 0.0 + } + // compute x mod pi_4 + mut y := floor(x * 4.0 / pi) // strip high bits of integer part + mut z := ldexp(y, -3) + z = floor(z) // integer part of y/8 + z = y - ldexp(z, 3) // y - 16 * (y/16) // integer and fractional part modulo one octant + mut octant := int(z) // map zeros and singularities to origin + if (octant & 1) == 1 { + octant++ + y += 1.0 + } + z = ((x - y * math.tan_dp1) - y * math.tan_dp2) - y * math.tan_dp3 + zz := z * z + if zz > 1.0e-14 { + y = z + z * (zz * (((math.tan_p[0] * zz) + math.tan_p[1]) * zz + math.tan_p[2]) / ((((zz + + math.tan_q[1]) * zz + math.tan_q[2]) * zz + math.tan_q[3]) * zz + math.tan_q[4])) + } else { + y = z + } + if (octant & 2) == 2 { + y = -1.0 / y + } + if sign < 0 { + y = -y + } + return y +} + +// tanf calculates tangent. (float32) +[inline] +pub fn tanf(a f32) f32 { + return f32(tan(a)) +} + +// tan calculates cotangent of a number +pub fn cot(a f64) f64 { + mut x := a + if x == 0.0 { + return inf(1) + } + mut sign := 1 // make argument positive but save the sign + if x < 0 { + x = -x + sign = -1 + } + if x > math.tan_lossth { + return 0.0 + } + // compute x mod pi_4 + mut y := floor(x * 4.0 / pi) // strip high bits of integer part + mut z := ldexp(y, -3) + z = floor(z) // integer part of y/8 + z = y - ldexp(z, 3) // y - 16 * (y/16) // integer and fractional part modulo one octant + mut octant := int(z) // map zeros and singularities to origin + if (octant & 1) == 1 { + octant++ + y += 1.0 + } + z = ((x - y * math.tan_dp1) - y * math.tan_dp2) - y * math.tan_dp3 + zz := z * z + if zz > 1.0e-14 { + y = z + z * (zz * (((math.tan_p[0] * zz) + math.tan_p[1]) * zz + math.tan_p[2]) / ((((zz + + math.tan_q[1]) * zz + math.tan_q[2]) * zz + math.tan_q[3]) * zz + math.tan_q[4])) + } else { + y = z + } + if (octant & 2) == 2 { + y = -y + } else { + y = 1.0 / y + } + if sign < 0 { + y = -y + } + return y +} diff --git a/v_windows/v/vlib/math/tanh.c.v b/v_windows/v/vlib/math/tanh.c.v new file mode 100644 index 0000000..05568c2 --- /dev/null +++ b/v_windows/v/vlib/math/tanh.c.v @@ -0,0 +1,9 @@ +module math + +fn C.tanh(x f64) f64 + +// tanh calculates hyperbolic tangent. +[inline] +pub fn tanh(a f64) f64 { + return C.tanh(a) +} diff --git a/v_windows/v/vlib/math/tanh.js.v b/v_windows/v/vlib/math/tanh.js.v new file mode 100644 index 0000000..e4cff6a --- /dev/null +++ b/v_windows/v/vlib/math/tanh.js.v @@ -0,0 +1,9 @@ +module math + +fn JS.Math.tanh(x f64) f64 + +// tanh calculates hyperbolic tangent. +[inline] +pub fn tanh(a f64) f64 { + return JS.Math.tanh(a) +} diff --git a/v_windows/v/vlib/math/tanh.v b/v_windows/v/vlib/math/tanh.v new file mode 100644 index 0000000..ac9590e --- /dev/null +++ b/v_windows/v/vlib/math/tanh.v @@ -0,0 +1,45 @@ +module math + +const ( + tanh_p = [ + -9.64399179425052238628e-1, + -9.92877231001918586564e+1, + -1.61468768441708447952e+3, + ] + tanh_q = [ + 1.12811678491632931402e+2, + 2.23548839060100448583e+3, + 4.84406305325125486048e+3, + ] +) + +// tanh returns the hyperbolic tangent of x. +// +// special cases are: +// tanh(±0) = ±0 +// tanh(±inf) = ±1 +// tanh(nan) = nan +pub fn tanh(x f64) f64 { + maxlog := 8.8029691931113054295988e+01 // log(2**127) + mut z := abs(x) + if z > 0.5 * maxlog { + if x < 0 { + return f64(-1) + } + return 1.0 + } else if z >= 0.625 { + s := exp(2.0 * z) + z = 1.0 - 2.0 / (s + 1.0) + if x < 0 { + z = -z + } + } else { + if x == 0 { + return x + } + s := x * x + z = x + x * s * ((math.tanh_p[0] * s + math.tanh_p[1]) * s + math.tanh_p[2]) / (((s + + math.tanh_q[0]) * s + math.tanh_q[1]) * s + math.tanh_q[2]) + } + return z +} diff --git a/v_windows/v/vlib/math/unsafe.js.v b/v_windows/v/vlib/math/unsafe.js.v new file mode 100644 index 0000000..38ee167 --- /dev/null +++ b/v_windows/v/vlib/math/unsafe.js.v @@ -0,0 +1,59 @@ +module math + +// f32_bits returns the IEEE 754 binary representation of f, +// with the sign bit of f and the result in the same bit position. +// f32_bits(f32_from_bits(x)) == x. +pub fn f32_bits(f f32) u32 { + p := u32(0) + #let buffer = new ArrayBuffer(4) + #let floatArr = new Float32Array(buffer) + #floatArr[0] = f.val + #let uintArr = new Uint32Array(buffer) + #p.val = uintArr[0] + + return p +} + +// f32_from_bits returns the floating-point number corresponding +// to the IEEE 754 binary representation b, with the sign bit of b +// and the result in the same bit position. +// f32_from_bits(f32_bits(x)) == x. +pub fn f32_from_bits(b u32) f32 { + p := f32(0.0) + #let buffer = new ArrayBuffer(4) + #let floatArr = new Float32Array(buffer) + #let uintArr = new Uint32Array(buffer) + #uintArr[0] = Number(b.val) + #p.val = floatArr[0] + + return p +} + +// f64_bits returns the IEEE 754 binary representation of f, +// with the sign bit of f and the result in the same bit position, +// and f64_bits(f64_from_bits(x)) == x. +pub fn f64_bits(f f64) u64 { + p := u64(0) + #let buffer = new ArrayBuffer(8) + #let floatArr = new Float64Array(buffer) + #floatArr[0] = f.val + #let uintArr = new BigUint64Array(buffer) + #p.val = uintArr[0] + + return p +} + +// f64_from_bits returns the floating-point number corresponding +// to the IEEE 754 binary representation b, with the sign bit of b +// and the result in the same bit position. +// f64_from_bits(f64_bits(x)) == x. +pub fn f64_from_bits(b u64) f64 { + p := 0.0 + #let buffer = new ArrayBuffer(8) + #let floatArr = new Float64Array(buffer) + #let uintArr = new BigUint64Array(buffer) + #uintArr[0] = Number(b.val) + #p.val = floatArr[0] + + return p +} diff --git a/v_windows/v/vlib/math/unsafe.v b/v_windows/v/vlib/math/unsafe.v new file mode 100644 index 0000000..e6ebc6f --- /dev/null +++ b/v_windows/v/vlib/math/unsafe.v @@ -0,0 +1,38 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module math + +// f32_bits returns the IEEE 754 binary representation of f, +// with the sign bit of f and the result in the same bit position. +// f32_bits(f32_from_bits(x)) == x. +pub fn f32_bits(f f32) u32 { + p := *unsafe { &u32(&f) } + return p +} + +// f32_from_bits returns the floating-point number corresponding +// to the IEEE 754 binary representation b, with the sign bit of b +// and the result in the same bit position. +// f32_from_bits(f32_bits(x)) == x. +pub fn f32_from_bits(b u32) f32 { + p := *unsafe { &f32(&b) } + return p +} + +// f64_bits returns the IEEE 754 binary representation of f, +// with the sign bit of f and the result in the same bit position, +// and f64_bits(f64_from_bits(x)) == x. +pub fn f64_bits(f f64) u64 { + p := *unsafe { &u64(&f) } + return p +} + +// f64_from_bits returns the floating-point number corresponding +// to the IEEE 754 binary representation b, with the sign bit of b +// and the result in the same bit position. +// f64_from_bits(f64_bits(x)) == x. +pub fn f64_from_bits(b u64) f64 { + p := *unsafe { &f64(&b) } + return p +} diff --git a/v_windows/v/vlib/math/util/util.v b/v_windows/v/vlib/math/util/util.v new file mode 100644 index 0000000..0802d28 --- /dev/null +++ b/v_windows/v/vlib/math/util/util.v @@ -0,0 +1,82 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module util + +// imin returns the smallest of two integer values +[inline] +pub fn imin(a int, b int) int { + return if a < b { a } else { b } +} + +// imin returns the biggest of two integer values +[inline] +pub fn imax(a int, b int) int { + return if a > b { a } else { b } +} + +// iabs returns an integer as absolute value +[inline] +pub fn iabs(v int) int { + return if v > 0 { v } else { -v } +} + +// umin returns the smallest of two u32 values +[inline] +pub fn umin(a u32, b u32) u32 { + return if a < b { a } else { b } +} + +// umax returns the biggest of two u32 values +[inline] +pub fn umax(a u32, b u32) u32 { + return if a > b { a } else { b } +} + +// uabs returns an u32 as absolute value +[inline] +pub fn uabs(v u32) u32 { + return if v > 0 { v } else { -v } +} + +// fmin_32 returns the smallest `f32` of input `a` and `b`. +// Example: assert fmin_32(2.0,3.0) == 2.0 +[inline] +pub fn fmin_32(a f32, b f32) f32 { + return if a < b { a } else { b } +} + +// fmax_32 returns the largest `f32` of input `a` and `b`. +// Example: assert fmax_32(2.0,3.0) == 3.0 +[inline] +pub fn fmax_32(a f32, b f32) f32 { + return if a > b { a } else { b } +} + +// fabs_32 returns the absolute value of `a` as a `f32` value. +// Example: assert fabs_32(-2.0) == 2.0 +[inline] +pub fn fabs_32(v f32) f32 { + return if v > 0 { v } else { -v } +} + +// fmin_64 returns the smallest `f64` of input `a` and `b`. +// Example: assert fmin_64(2.0,3.0) == 2.0 +[inline] +pub fn fmin_64(a f64, b f64) f64 { + return if a < b { a } else { b } +} + +// fmax_64 returns the largest `f64` of input `a` and `b`. +// Example: assert fmax_64(2.0,3.0) == 3.0 +[inline] +pub fn fmax_64(a f64, b f64) f64 { + return if a > b { a } else { b } +} + +// fabs_64 returns the absolute value of `a` as a `f64` value. +// Example: assert fabs_64(-2.0) == f64(2.0) +[inline] +pub fn fabs_64(v f64) f64 { + return if v > 0 { v } else { -v } +} diff --git a/v_windows/v/vlib/mssql/README.md b/v_windows/v/vlib/mssql/README.md new file mode 100644 index 0000000..ff4fefc --- /dev/null +++ b/v_windows/v/vlib/mssql/README.md @@ -0,0 +1,69 @@ +# SQL Server ODBC + +* This is a V wrapper of SQL Server ODBC C/C++ library + +## Dependencies +* ODBC C/C++ library + * Linux Install: + * Details: https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server + * `msodbcsql17` and `unixodbc-dev` packages are needed + * Windows Install: + * `odbc` lib is included in windows sdk for most of distributions, + so there is no need to install it separately + * Details: https://docs.microsoft.com/en-us/sql/connect/odbc/microsoft-odbc-driver-for-sql-server + +## Windows Notes +### Using `msvc` +* Make sure `cl.exe` of `msvc` is accessible from command line. +You can run `v` commands in `Visual Studio 2019 Developer Command Prompt` to be safe. +* C Headers and dlls can be automatically resolved by `msvc`. +### Using `tcc` +* Copy those headers to `@VEXEROOT\thirdparty\mssql\include`. +The version number `10.0.18362.0` might differ on your system. +Command Prompt commands: +```cmd +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sql.h" thirdparty\mssql\include +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqlext.h" thirdparty\mssql\include +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqltypes.h" thirdparty\mssql\include +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\sqlucode.h" thirdparty\mssql\include +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\sal.h" thirdparty\mssql\include +copy "C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\concurrencysal.h" thirdparty\mssql\include +``` +* dlls can be automatically resolved by `tcc` + +## TODO +* Support Mac +* Support ORM + +## Usage +```v ignore +import mssql + +fn test_example() ? { + // connect to server + config := mssql.Config{ + driver: 'ODBC Driver 17 for SQL Server' + server: 'tcp:localhost' + uid: '' + pwd: '' + } + + mut conn := mssql.Connection{} + + conn.connect(config.get_conn_str()) ? + + defer { + conn.close() + } + + // get current db name + mut query := 'SELECT DB_NAME()' + mut res := conn.query(query) ? + assert res == mssql.Result{ + rows: [mssql.Row{ + vals: ['master'] + }] + num_rows_affected: -1 + } +} +``` diff --git a/v_windows/v/vlib/mssql/_cdef_nix.c.v b/v_windows/v/vlib/mssql/_cdef_nix.c.v new file mode 100644 index 0000000..0a9ec00 --- /dev/null +++ b/v_windows/v/vlib/mssql/_cdef_nix.c.v @@ -0,0 +1,6 @@ +module mssql + +#flag -lodbc + +#include +#include diff --git a/v_windows/v/vlib/mssql/_cdef_windows.c.v b/v_windows/v/vlib/mssql/_cdef_windows.c.v new file mode 100644 index 0000000..61724ee --- /dev/null +++ b/v_windows/v/vlib/mssql/_cdef_windows.c.v @@ -0,0 +1,12 @@ +module mssql + +// mssql module does not support tcc on windows + +// odbc32 lib comes with windows sdk and does not need to be installed separately. +// v builder for msvc can resolve the sdk includes search path, so no need to repeat here. +#flag windows -lodbc32 + +// Special handling of sql headers on windows. +// Source is in v third party folder. +#flag windows -I@VEXEROOT/thirdparty/mssql/include +#include diff --git a/v_windows/v/vlib/mssql/_cdefs.c.v b/v_windows/v/vlib/mssql/_cdefs.c.v new file mode 100644 index 0000000..7ce0d08 --- /dev/null +++ b/v_windows/v/vlib/mssql/_cdefs.c.v @@ -0,0 +1,27 @@ +module mssql + +fn C.SQLAllocHandle(handle_type C.SQLSMALLINT, input_handle C.SQLHANDLE, output_handle &C.SQLHANDLE) C.SQLRETURN + +fn C.SQLSetEnvAttr(environment_handle C.SQLHENV, attribute C.SQLINTEGER, value C.SQLPOINTER, string_length C.SQLINTEGER) C.SQLRETURN + +fn C.SQLGetDiagRec(handle_type C.SQLSMALLINT, handle C.SQLHANDLE, rec_number C.SQLSMALLINT, sql_state &C.SQLCHAR, native_error &C.SQLINTEGER, message_text &C.SQLCHAR, buffer_length C.SQLSMALLINT, text_length &C.SQLSMALLINT) C.SQLRETURN + +fn C.SQLSetConnectAttr(connection_handle C.SQLHDBC, attribute C.SQLINTEGER, value C.SQLPOINTER, string_length C.SQLINTEGER) C.SQLRETURN + +fn C.SQLDriverConnect(hdbc C.SQLHDBC, hwnd C.SQLHWND, sz_conn_str_in &C.SQLCHAR, cb_conn_str_in C.SQLSMALLINT, sz_conn_str_out &C.SQLCHAR, cb_conn_str_out_max C.SQLSMALLINT, pcb_conn_str_out &C.SQLSMALLINT, f_driver_completion C.SQLUSMALLINT) C.SQLRETURN + +fn C.SQLDisconnect(connection_handle C.SQLHDBC) C.SQLRETURN + +fn C.SQLExecDirect(statement_handle C.SQLHSTMT, statement_text &C.SQLCHAR, text_length C.SQLINTEGER) C.SQLRETURN + +fn C.SQLBindCol(statement_handle C.SQLHSTMT, column_number C.SQLUSMALLINT, target_type C.SQLSMALLINT, target_value C.SQLPOINTER, buffer_length C.SQLLEN, str_len_or_ind &C.SQLLEN) C.SQLRETURN + +fn C.SQLFetch(statement_handle C.SQLHSTMT) C.SQLRETURN + +fn C.SQLFreeHandle(handle_type C.SQLSMALLINT, handle C.SQLHANDLE) C.SQLRETURN + +fn C.SQLNumResultCols(statement_handle C.SQLHSTMT, column_count &C.SQLSMALLINT) C.SQLRETURN + +fn C.SQLColAttribute(statement_handle C.SQLHSTMT, column_number C.SQLUSMALLINT, field_identifier C.SQLUSMALLINT, character_attribute C.SQLPOINTER, buffer_length C.SQLSMALLINT, string_length C.SQLSMALLINT, numeric_attribute &C.SQLLEN) C.SQLRETURN + +fn C.SQLRowCount(statement_handle C.SQLHSTMT, row_count &C.SQLLEN) C.SQLRETURN diff --git a/v_windows/v/vlib/mssql/config.v b/v_windows/v/vlib/mssql/config.v new file mode 100644 index 0000000..7f26d6f --- /dev/null +++ b/v_windows/v/vlib/mssql/config.v @@ -0,0 +1,20 @@ +module mssql + +pub struct Config { +pub: + driver string + server string + uid string + pwd string + // if dbname empty, conn str will not contain Database info, + // and it is up to the server to choose which db to connect to. + dbname string +} + +pub fn (cfg Config) get_conn_str() string { + mut str := 'Driver=$cfg.driver;Server=$cfg.server;UID=$cfg.uid;PWD=$cfg.pwd' + if cfg.dbname != '' { + str += ';Database=$cfg.dbname' + } + return str +} diff --git a/v_windows/v/vlib/mssql/mssql.v b/v_windows/v/vlib/mssql/mssql.v new file mode 100644 index 0000000..a885e4b --- /dev/null +++ b/v_windows/v/vlib/mssql/mssql.v @@ -0,0 +1,125 @@ +module mssql + +pub struct Connection { +mut: + henv C.SQLHENV = C.SQLHENV(C.SQL_NULL_HENV) // Environment + hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC) // Connection handle +pub mut: + conn_str string +} + +// connect to db +pub fn (mut conn Connection) connect(conn_str string) ?bool { + conn_str_c := unsafe { &C.SQLCHAR(conn_str.str) } + mut retcode := C.SQLRETURN(C.SQL_SUCCESS) + // Allocate environment handle + retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(C.SQL_NULL_HANDLE), + unsafe { &C.SQLHANDLE(&conn.henv) }) + check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_ENV)', C.SQLHANDLE(conn.henv), C.SQLSMALLINT(C.SQL_HANDLE_ENV)) ? + + // Set the ODBC version environment attribute + retcode = C.SQLSetEnvAttr(conn.henv, C.SQLINTEGER(C.SQL_ATTR_ODBC_VERSION), &C.SQLPOINTER(C.SQL_OV_ODBC3), + C.SQLINTEGER(0)) + check_error(retcode, 'SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)', C.SQLHANDLE(conn.henv), + C.SQLSMALLINT(C.SQL_HANDLE_ENV)) ? + + // Allocate connection handle + retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.henv), + unsafe { &C.SQLHANDLE(&conn.hdbc) }) + check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_DBC)', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC)) ? + + // Set login timeout to 5 seconds + retcode = C.SQLSetConnectAttr(conn.hdbc, C.SQLINTEGER(C.SQL_LOGIN_TIMEOUT), C.SQLPOINTER(5), + C.SQLINTEGER(0)) + check_error(retcode, 'SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)', C.SQLHANDLE(conn.hdbc), + C.SQLSMALLINT(C.SQL_HANDLE_DBC)) ? + + // Connect to data source + mut outstr := [1024]char{} + mut outstrlen := C.SQLSMALLINT(0) + retcode = C.SQLDriverConnect(conn.hdbc, C.SQLHWND(0), conn_str_c, C.SQLSMALLINT(C.SQL_NTS), + &C.SQLCHAR(&outstr[0]), C.SQLSMALLINT(sizeof(outstr)), &outstrlen, C.SQLUSMALLINT(C.SQL_DRIVER_NOPROMPT)) + check_error(retcode, 'SQLDriverConnect()', C.SQLHANDLE(conn.hdbc), C.SQLSMALLINT(C.SQL_HANDLE_DBC)) ? + conn.conn_str = conn_str + return true +} + +// close - closes the connection. +pub fn (mut conn Connection) close() { + // Connection + if conn.hdbc != C.SQLHDBC(C.SQL_NULL_HDBC) { + C.SQLDisconnect(conn.hdbc) + C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_DBC), C.SQLHANDLE(conn.hdbc)) + conn.hdbc = C.SQLHDBC(C.SQL_NULL_HDBC) + } + // Environment + if conn.henv != C.SQLHENV(C.SQL_NULL_HENV) { + C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_ENV), C.SQLHANDLE(conn.henv)) + conn.henv = C.SQLHENV(C.SQL_NULL_HENV) + } +} + +// query executes a sql query +pub fn (mut conn Connection) query(q string) ?Result { + mut hstmt := new_hstmt(conn.hdbc) ? + defer { + hstmt.close() + } + + hstmt.exec(q) ? + + affected := hstmt.retrieve_affected_rows() ? + + hstmt.prepare_read() ? + raw_rows := hstmt.read_rows() ? + + mut res := Result{ + rows: []Row{} + num_rows_affected: affected + } + + for rr in raw_rows { + res.rows << Row{ + vals: rr + } + } + + return res +} + +// check_error checks odbc return code and extract error string if available +fn check_error(e C.SQLRETURN, s string, h C.SQLHANDLE, t C.SQLSMALLINT) ? { + if e != C.SQLRETURN(C.SQL_SUCCESS) && e != C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { + err_str := extract_error(s, h, t) + return error(err_str) + } +} + +// extract_error extracts error string from odbc +fn extract_error(fnName string, handle C.SQLHANDLE, tp C.SQLSMALLINT) string { + mut err_str := fnName + mut i := 0 + mut native_error := C.SQLINTEGER(0) + mut sql_state := [7]char{} + mut message_text := [256]char{} + mut text_length := C.SQLSMALLINT(0) + mut ret := C.SQLRETURN(C.SQL_SUCCESS) + + for ret == C.SQLRETURN(C.SQL_SUCCESS) { + i++ + ret = C.SQLGetDiagRec(tp, handle, C.SQLSMALLINT(i), &C.SQLCHAR(&sql_state[0]), + &native_error, &C.SQLCHAR(&message_text[0]), C.SQLSMALLINT(sizeof(message_text)), + &text_length) + + // add driver error string + if ret == C.SQLRETURN(C.SQL_SUCCESS) || ret == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { + unsafe { + state_str := (&sql_state[0]).vstring() + native_error_code := int(native_error) + txt_str := (&message_text[0]).vstring() + err_str += '\n\todbc=$state_str:$i:$native_error_code:$txt_str' + } + } + } + return err_str +} diff --git a/v_windows/v/vlib/mssql/result.v b/v_windows/v/vlib/mssql/result.v new file mode 100644 index 0000000..f5483a2 --- /dev/null +++ b/v_windows/v/vlib/mssql/result.v @@ -0,0 +1,13 @@ +module mssql + +pub struct Row { +pub mut: + vals []string +} + +pub struct Result { +pub mut: + rows []Row + // the number of rows affected by sql statement + num_rows_affected int +} diff --git a/v_windows/v/vlib/mssql/stmt_handle.v b/v_windows/v/vlib/mssql/stmt_handle.v new file mode 100644 index 0000000..8e0b792 --- /dev/null +++ b/v_windows/v/vlib/mssql/stmt_handle.v @@ -0,0 +1,127 @@ +module mssql + +// HStmt is handle for sql statement +struct HStmt { +mut: + // db connection reference. Owner is Connection struct. + hdbc C.SQLHDBC = C.SQLHDBC(C.SQL_NULL_HDBC) + // statement handle + hstmt C.SQLHSTMT = C.SQLHSTMT(C.SQL_NULL_HSTMT) + // fields used for computation + column_count int = -1 + // columns + buffers [][]char + // indicators for each column + indicators []C.SQLLEN +} + +// new_hstmt constructs a new statement handle +fn new_hstmt(hdbc C.SQLHDBC) ?HStmt { + mut retcode := C.SQLRETURN(C.SQL_SUCCESS) + mut hstmt := C.SQLHSTMT(C.SQL_NULL_HSTMT) + // Allocate statement handle + retcode = C.SQLAllocHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(hdbc), unsafe { &C.SQLHANDLE(&hstmt) }) + check_error(retcode, 'SQLAllocHandle(SQL_HANDLE_STMT)', C.SQLHANDLE(hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + + return HStmt{ + hdbc: hdbc + hstmt: hstmt + } +} + +// close the statement handle +fn (mut h HStmt) close() { + // Deallocate handle + if h.hstmt != C.SQLHSTMT(C.SQL_NULL_HSTMT) { + // check error code? + C.SQLFreeHandle(C.SQLSMALLINT(C.SQL_HANDLE_STMT), C.SQLHANDLE(h.hstmt)) + h.hstmt = C.SQLHSTMT(C.SQL_NULL_HSTMT) + } +} + +// exec executes a Sql statement. Result is stored in odbc driver, and not yet read. +fn (h HStmt) exec(sql string) ? { + retcode := C.SQLExecDirect(h.hstmt, sql.str, C.SQLINTEGER(C.SQL_NTS)) + check_error(retcode, 'SQLExecDirect()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? +} + +// retrieve_affected_rows returns number of rows affected/modified by the last operation. -1 if not applicable. +fn (h HStmt) retrieve_affected_rows() ?int { + count_ret := C.SQLLEN(0) + retcode := C.SQLRowCount(h.hstmt, &count_ret) + check_error(retcode, 'SQLRowCount()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + return int(count_ret) +} + +fn (h HStmt) retrieve_column_count() ?int { + mut retcode := C.SQLRETURN(C.SQL_SUCCESS) + col_count_buff := C.SQLSMALLINT(0) + retcode = C.SQLNumResultCols(h.hstmt, &col_count_buff) + check_error(retcode, 'SQLNumResultCols()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + return int(col_count_buff) +} + +// allocate buffers and bind them to drivers +fn (mut h HStmt) prepare_read() ? { + mut retcode := C.SQLRETURN(C.SQL_SUCCESS) + + column_count := h.retrieve_column_count() ? + h.column_count = column_count // remember the count because read will need it + + h.buffers = [][]char{len: h.column_count, cap: h.column_count} + h.indicators = []C.SQLLEN{len: h.column_count, cap: h.column_count} + + for i := 0; i < h.column_count; i++ { + i_col := C.SQLUSMALLINT(i + 1) // col number starts with 1 + size_ret := C.SQLLEN(0) + // find out buffer size needed to read data in this column + retcode = C.SQLColAttribute(h.hstmt, i_col, C.SQLUSMALLINT(C.SQL_DESC_LENGTH), + C.SQLPOINTER(0), C.SQLSMALLINT(0), C.SQLSMALLINT(0), &size_ret) + check_error(retcode, 'SQLColAttribute()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + + // buffer allocation is the size + 1 to include termination char, since SQL_DESC_LENGTH does not include it. + allocate_size := size_ret + C.SQLLEN(1) + allocate_size_int := int(allocate_size) + buff := []char{len: allocate_size_int, cap: allocate_size_int} + + // bind the buffer + retcode = C.SQLBindCol(h.hstmt, C.SQLUSMALLINT(i_col), C.SQLSMALLINT(C.SQL_C_CHAR), + C.SQLPOINTER(&buff[0]), allocate_size, &h.indicators[i]) + check_error(retcode, 'SQLBindCol()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + + // record the buffer in HStmt + h.buffers[i] = buff + } +} + +// fetch all rows +fn (h HStmt) read_rows() ?[][]string { + mut retcode := C.SQLRETURN(C.SQL_SUCCESS) + + mut res := [][]string{} + + if h.column_count <= 0 { + // there is nothing in the driver to read from + return res + } + + // Fetch and print each row of data until SQL_NO_DATA returned. + for { + mut row := []string{} + retcode = C.SQLFetch(h.hstmt) + if retcode == C.SQLRETURN(C.SQL_SUCCESS) || retcode == C.SQLRETURN(C.SQL_SUCCESS_WITH_INFO) { + // copy buffered result to res + for content in h.buffers { + row << string(content) + } + } else { + if retcode != C.SQLRETURN(C.SQL_NO_DATA) { + check_error(retcode, 'SQLFetch()', C.SQLHANDLE(h.hstmt), C.SQLSMALLINT(C.SQL_HANDLE_STMT)) ? + } else { + break + } + } + res << row + } + return res +} diff --git a/v_windows/v/vlib/mysql/README.md b/v_windows/v/vlib/mysql/README.md new file mode 100644 index 0000000..3d0ab97 --- /dev/null +++ b/v_windows/v/vlib/mysql/README.md @@ -0,0 +1,30 @@ +For Linux, you need to install `MySQL development` package and `pkg-config`. +For Windows, install [the installer](https://dev.mysql.com/downloads/installer/) , +then copy the `include` and `lib` folders to `\thirdparty\mysql`. + +## Basic Usage + +```v oksyntax +import mysql + +// Create connection +mut connection := mysql.Connection{ + username: 'root' + dbname: 'mysql' +} +// Connect to server +connection.connect() ? +// Change the default database +connection.select_db('db_users') ? +// Do a query +get_users_query_result := connection.query('SELECT * FROM users') ? +// Get the result as maps +for user in get_users_query_result.maps() { + // Access the name of user + println(user['name']) +} +// Free the query result +get_users_query_result.free() +// Close the connection if needed +connection.close() +``` diff --git a/v_windows/v/vlib/mysql/_cdefs.c.v b/v_windows/v/vlib/mysql/_cdefs.c.v new file mode 100644 index 0000000..23aebed --- /dev/null +++ b/v_windows/v/vlib/mysql/_cdefs.c.v @@ -0,0 +1,101 @@ +module mysql + +[typedef] +struct C.MYSQL { +} + +[typedef] +struct C.MYSQL_RES { +} + +[typedef] +struct C.MYSQL_FIELD { + name &byte // Name of column + org_name &byte // Original column name, if an alias + table &byte // Table of column if column was a field + org_table &byte // Org table name, if table was an alias + db &byte // Database for table + catalog &byte // Catalog for table + def &byte // Default value (set by mysql_list_fields) + length int // Width of column (create length) + max_length int // Max width for selected set + name_length u32 + org_name_length u32 + table_length u32 + org_table_length u32 + db_length u32 + catalog_length u32 + def_length u32 + flags u32 // Div flags + decimals u32 // Number of decimals in field + charsetnr u32 // Character set + @type int // Type of field. See mysql_com.h for types +} + +fn C.mysql_init(mysql &C.MYSQL) &C.MYSQL + +fn C.mysql_real_connect(mysql &C.MYSQL, host &char, user &char, passwd &char, db &char, port u32, unix_socket &char, client_flag ConnectionFlag) &C.MYSQL + +fn C.mysql_query(mysql &C.MYSQL, q &byte) int + +fn C.mysql_real_query(mysql &C.MYSQL, q &byte, len u32) int + +fn C.mysql_select_db(mysql &C.MYSQL, db &byte) int + +fn C.mysql_change_user(mysql &C.MYSQL, user &byte, password &byte, db &byte) bool + +fn C.mysql_affected_rows(mysql &C.MYSQL) u64 + +fn C.mysql_options(mysql &C.MYSQL, option int, arg voidptr) int + +fn C.mysql_get_option(mysql &C.MYSQL, option int, arg voidptr) int + +fn C.mysql_list_tables(mysql &C.MYSQL, wild &byte) &C.MYSQL_RES + +fn C.mysql_num_fields(res &C.MYSQL_RES) int + +fn C.mysql_num_rows(res &C.MYSQL_RES) u64 + +fn C.mysql_autocommit(mysql &C.MYSQL, mode bool) + +fn C.mysql_refresh(mysql &C.MYSQL, options u32) int + +fn C.mysql_reset_connection(mysql &C.MYSQL) int + +fn C.mysql_ping(mysql &C.MYSQL) int + +fn C.mysql_store_result(mysql &C.MYSQL) &C.MYSQL_RES + +fn C.mysql_fetch_row(res &C.MYSQL_RES) &&byte + +fn C.mysql_fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD + +fn C.mysql_free_result(res &C.MYSQL_RES) + +fn C.mysql_real_escape_string(mysql &C.MYSQL, to &byte, from &byte, len u64) u64 + +// fn C.mysql_real_escape_string_quote(mysql &C.MYSQL, to &byte, from &byte, len u64, quote byte) u64 (Don't exist in mariadb) + +fn C.mysql_close(sock &C.MYSQL) + +// INFO & VERSION +fn C.mysql_info(mysql &C.MYSQL) &byte + +fn C.mysql_get_host_info(mysql &C.MYSQL) &byte + +fn C.mysql_get_server_info(mysql &C.MYSQL) &byte + +fn C.mysql_get_server_version(mysql &C.MYSQL) u64 + +fn C.mysql_get_client_version() u64 + +fn C.mysql_get_client_info() &byte + +// DEBUG & ERROR INFO +fn C.mysql_error(mysql &C.MYSQL) &byte + +fn C.mysql_errno(mysql &C.MYSQL) int + +fn C.mysql_dump_debug_info(mysql &C.MYSQL) int + +fn C.mysql_debug(debug &byte) diff --git a/v_windows/v/vlib/mysql/_cdefs_nix.c.v b/v_windows/v/vlib/mysql/_cdefs_nix.c.v new file mode 100644 index 0000000..1462e89 --- /dev/null +++ b/v_windows/v/vlib/mysql/_cdefs_nix.c.v @@ -0,0 +1,11 @@ +module mysql + +// Need to check if mysqlclient is not there and use mariadb as alternative because newer system doesn't support mysql 8.0 as default + +$if $pkgconfig('mysqlclient') { + #pkgconfig mysqlclient +} $else { + #pkgconfig mariadb +} + +#include # Please install the mysqlclient development headers diff --git a/v_windows/v/vlib/mysql/_cdefs_windows.c.v b/v_windows/v/vlib/mysql/_cdefs_windows.c.v new file mode 100644 index 0000000..9e18696 --- /dev/null +++ b/v_windows/v/vlib/mysql/_cdefs_windows.c.v @@ -0,0 +1,5 @@ +module mysql + +#flag windows -I@VEXEROOT/thirdparty/mysql/include +#flag windows @VEXEROOT/thirdparty/mysql/lib/libmysql.dll +#include # Please install https://dev.mysql.com/downloads/installer/ , then put the include/ and lib/ folders in thirdparty/mysql diff --git a/v_windows/v/vlib/mysql/consts.v b/v_windows/v/vlib/mysql/consts.v new file mode 100644 index 0000000..2e9962a --- /dev/null +++ b/v_windows/v/vlib/mysql/consts.v @@ -0,0 +1,13 @@ +module mysql + +// MYSQL REFRESH FLAGS +pub const ( + refresh_grant = u32(C.REFRESH_GRANT) + refresh_log = u32(C.REFRESH_LOG) + refresh_tables = u32(C.REFRESH_TABLES) + refresh_hosts = u32(C.REFRESH_HOSTS) + refresh_status = u32(C.REFRESH_STATUS) + refresh_threads = u32(C.REFRESH_THREADS) + refresh_slave = u32(C.REFRESH_SLAVE) + refresh_master = u32(C.REFRESH_MASTER) +) diff --git a/v_windows/v/vlib/mysql/enums.v b/v_windows/v/vlib/mysql/enums.v new file mode 100644 index 0000000..e2bfef5 --- /dev/null +++ b/v_windows/v/vlib/mysql/enums.v @@ -0,0 +1,78 @@ +module mysql + +pub enum FieldType { + type_decimal + type_tiny + type_short + type_long + type_float + type_double + type_null + type_timestamp + type_longlong + type_int24 + type_date + type_time + type_datetime + type_year + type_newdate + type_varchar + type_bit + type_timestamp2 + type_datetime2 + type_time2 + type_json = 245 + type_newdecimal + type_enum + type_set + type_tiny_blob + type_medium_blob + type_long_blob + type_blob + type_var_string + type_string + type_geometry +} + +pub fn (f FieldType) str() string { + return match f { + .type_decimal { 'decimal' } + .type_tiny { 'tiny' } + .type_short { 'short' } + .type_long { 'long' } + .type_float { 'float' } + .type_double { 'double' } + .type_null { 'null' } + .type_timestamp { 'timestamp' } + .type_longlong { 'longlong' } + .type_int24 { 'int24' } + .type_date { 'date' } + .type_time { 'time' } + .type_datetime { 'datetime' } + .type_year { 'year' } + .type_newdate { 'newdate' } + .type_varchar { 'varchar' } + .type_bit { 'bit' } + .type_timestamp2 { 'timestamp2' } + .type_datetime2 { 'datetime2' } + .type_time2 { 'time2' } + .type_json { 'json' } + .type_newdecimal { 'newdecimal' } + .type_enum { 'enum' } + .type_set { 'set' } + .type_tiny_blob { 'tiny_blob' } + .type_medium_blob { 'medium_blob' } + .type_long_blob { 'long_blob' } + .type_blob { 'blob' } + .type_var_string { 'var_string' } + .type_string { 'string' } + .type_geometry { 'geometry' } + } +} + +pub fn (f FieldType) get_len() u32 { + return match f { + .type_blob { 262140 } + else { 0 } + } +} diff --git a/v_windows/v/vlib/mysql/mysql.v b/v_windows/v/vlib/mysql/mysql.v new file mode 100644 index 0000000..90e7008 --- /dev/null +++ b/v_windows/v/vlib/mysql/mysql.v @@ -0,0 +1,239 @@ +module mysql + +// Values for the capabilities flag bitmask used by the MySQL protocol. +// See more on https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html#details +pub enum ConnectionFlag { + // CAN_HANDLE_EXPIRED_PASSWORDS = C.CAN_HANDLE_EXPIRED_PASSWORDS + client_compress = C.CLIENT_COMPRESS + client_found_rows = C.CLIENT_FOUND_ROWS + client_ignore_sigpipe = C.CLIENT_IGNORE_SIGPIPE + client_ignore_space = C.CLIENT_IGNORE_SPACE + client_interactive = C.CLIENT_INTERACTIVE + client_local_files = C.CLIENT_LOCAL_FILES + client_multi_results = C.CLIENT_MULTI_RESULTS + client_multi_statements = C.CLIENT_MULTI_STATEMENTS + client_no_schema = C.CLIENT_NO_SCHEMA + client_odbc = C.CLIENT_ODBC + // client_optional_resultset_metadata = C.CLIENT_OPTIONAL_RESULTSET_METADATA + client_ssl = C.CLIENT_SSL + client_remember_options = C.CLIENT_REMEMBER_OPTIONS +} + +struct SQLError { + msg string + code int +} + +// TODO: Documentation +pub struct Connection { +mut: + conn &C.MYSQL = C.mysql_init(0) +pub mut: + host string = '127.0.0.1' + port u32 = 3306 + username string + password string + dbname string + flag ConnectionFlag +} + +// connect - create a new connection to the MySQL server. +pub fn (mut conn Connection) connect() ?bool { + instance := C.mysql_init(conn.conn) + conn.conn = C.mysql_real_connect(instance, conn.host.str, conn.username.str, conn.password.str, + conn.dbname.str, conn.port, 0, conn.flag) + if isnil(conn.conn) { + return error_with_code(get_error_msg(instance), get_errno(instance)) + } + return true +} + +// query - make an SQL query and receive the results. +// `query()` cannot be used for statements that contain binary data; +// Use `real_query()` instead. +pub fn (conn Connection) query(q string) ?Result { + if C.mysql_query(conn.conn, q.str) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + res := C.mysql_store_result(conn.conn) + return Result{res} +} + +// real_query - make an SQL query and receive the results. +// `real_query()` can be used for statements containing binary data. +// (Binary data may contain the `\0` character, which `query()` +// interprets as the end of the statement string). In addition, +// `real_query()` is faster than `query()`. +pub fn (mut conn Connection) real_query(q string) ?Result { + if C.mysql_real_query(conn.conn, q.str, q.len) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + res := C.mysql_store_result(conn.conn) + return Result{res} +} + +// select_db - change the default database for database queries. +pub fn (mut conn Connection) select_db(dbname string) ?bool { + if C.mysql_select_db(conn.conn, dbname.str) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return true +} + +// change_user - change the mysql user for the connection. +// Passing an empty string for the `dbname` parameter, resultsg in only changing +// the user and not changing the default database for the connection. +pub fn (mut conn Connection) change_user(username string, password string, dbname string) ?bool { + mut ret := true + if dbname != '' { + ret = C.mysql_change_user(conn.conn, username.str, password.str, dbname.str) + } else { + ret = C.mysql_change_user(conn.conn, username.str, password.str, 0) + } + if !ret { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return ret +} + +// affected_rows - return the number of rows changed/deleted/inserted +// by the last `UPDATE`, `DELETE`, or `INSERT` query. +pub fn (conn &Connection) affected_rows() u64 { + return C.mysql_affected_rows(conn.conn) +} + +// autocommit - turns on/off the auto-committing mode for the connection. +// When it is on, then each query is commited right away. +pub fn (mut conn Connection) autocommit(mode bool) { + C.mysql_autocommit(conn.conn, mode) +} + +// tables - returns a list of the names of the tables in the current database, +// that match the simple regular expression specified by the `wildcard` parameter. +// The `wildcard` parameter may contain the wildcard characters `%` or `_`. +// If an empty string is passed, it will return all tables. +// Calling `tables()` is similar to executing query `SHOW TABLES [LIKE wildcard]`. +pub fn (conn &Connection) tables(wildcard string) ?[]string { + cres := C.mysql_list_tables(conn.conn, wildcard.str) + if isnil(cres) { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + res := Result{cres} + mut tables := []string{} + for row in res.rows() { + tables << row.vals[0] + } + return tables +} + +// escape_string - creates a legal SQL string for use in an SQL statement. +// The `s` argument is encoded to produce an escaped SQL string, +// taking into account the current character set of the connection. +pub fn (conn &Connection) escape_string(s string) string { + unsafe { + to := malloc_noscan(2 * s.len + 1) + C.mysql_real_escape_string(conn.conn, to, s.str, s.len) + return to.vstring() + } +} + +// set_option - sets extra connect options that affect the behavior of +// a connection. This function may be called multiple times to set several +// options. To retrieve the current values for an option, use `get_option()`. +pub fn (mut conn Connection) set_option(option_type int, val voidptr) { + C.mysql_options(conn.conn, option_type, val) +} + +// get_option - return the value of an option, settable by `set_option`. +// https://dev.mysql.com/doc/c-api/5.7/en/mysql-get-option.html +pub fn (conn &Connection) get_option(option_type int) ?voidptr { + ret := voidptr(0) + if C.mysql_get_option(conn.conn, option_type, &ret) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return ret +} + +// refresh - flush the tables or caches, or resets replication server +// information. The connected user must have the `RELOAD` privilege. +pub fn (mut conn Connection) refresh(options u32) ?bool { + if C.mysql_refresh(conn.conn, options) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return true +} + +// reset - resets the connection, and clear the session state. +pub fn (mut conn Connection) reset() ?bool { + if C.mysql_reset_connection(conn.conn) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return true +} + +// ping - pings a server connection, or tries to reconnect if the connection +// has gone down. +pub fn (mut conn Connection) ping() ?bool { + if C.mysql_ping(conn.conn) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return true +} + +// close - closes the connection. +pub fn (mut conn Connection) close() { + C.mysql_close(conn.conn) +} + +// info - returns information about the most recently executed query. +// See more on https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html +pub fn (conn &Connection) info() string { + return resolve_nil_str(C.mysql_info(conn.conn)) +} + +// get_host_info - returns a string describing the type of connection in use, +// including the server host name. +pub fn (conn &Connection) get_host_info() string { + return unsafe { C.mysql_get_host_info(conn.conn).vstring() } +} + +// get_server_info - returns a string representing the MySQL server version. +// For example, `8.0.24`. +pub fn (conn &Connection) get_server_info() string { + return unsafe { C.mysql_get_server_info(conn.conn).vstring() } +} + +// get_server_version - returns an integer, representing the MySQL server +// version. The value has the format `XYYZZ` where `X` is the major version, +// `YY` is the release level (or minor version), and `ZZ` is the sub-version +// within the release level. For example, `8.0.24` is returned as `80024`. +pub fn (conn &Connection) get_server_version() u64 { + return C.mysql_get_server_version(conn.conn) +} + +// dump_debug_info - instructs the server to write debugging information +// to the error log. The connected user must have the `SUPER` privilege. +pub fn (mut conn Connection) dump_debug_info() ?bool { + if C.mysql_dump_debug_info(conn.conn) != 0 { + return error_with_code(get_error_msg(conn.conn), get_errno(conn.conn)) + } + return true +} + +// get_client_info - returns client version information as a string. +pub fn get_client_info() string { + return unsafe { C.mysql_get_client_info().vstring() } +} + +// get_client_version - returns the client version information as an integer. +pub fn get_client_version() u64 { + return C.mysql_get_client_version() +} + +// debug - does a `DBUG_PUSH` with the given string. +// `debug()` uses the Fred Fish debug library. +// To use this function, you must compile the client library to support debugging. +// See https://dev.mysql.com/doc/c-api/8.0/en/mysql-debug.html +pub fn debug(debug string) { + C.mysql_debug(debug.str) +} diff --git a/v_windows/v/vlib/mysql/mysql_orm_test.v b/v_windows/v/vlib/mysql/mysql_orm_test.v new file mode 100644 index 0000000..2acb182 --- /dev/null +++ b/v_windows/v/vlib/mysql/mysql_orm_test.v @@ -0,0 +1,77 @@ +import orm +import mysql + +fn test_mysql_orm() { + mut mdb := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: '' + dbname: 'mysql' + } + mdb.connect() or { panic(err) } + db := orm.Connection(mdb) + db.create('Test', [ + orm.TableField{ + name: 'id' + typ: 7 + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + kind: .plain + arg: 'serial' + }, + ] + }, + orm.TableField{ + name: 'name' + typ: 18 + attrs: [] + }, + orm.TableField{ + name: 'age' + typ: 7 + }, + ]) or { panic(err) } + + db.insert('Test', orm.QueryData{ + fields: ['name', 'age'] + data: [orm.string_to_primitive('Louis'), orm.int_to_primitive(101)] + }) or { panic(err) } + + res := db.@select(orm.SelectConfig{ + table: 'Test' + has_where: true + fields: ['id', 'name', 'age'] + types: [7, 18, 8] + }, orm.QueryData{}, orm.QueryData{ + fields: ['name', 'age'] + data: [orm.Primitive('Louis'), i64(101)] + types: [18, 8] + is_and: [true, true] + kinds: [.eq, .eq] + }) or { panic(err) } + + id := res[0][0] + name := res[0][1] + age := res[0][2] + + assert id is int + if id is int { + assert id == 1 + } + + assert name is string + if name is string { + assert name == 'Louis' + } + + assert age is i64 + if age is i64 { + assert age == 101 + } +} diff --git a/v_windows/v/vlib/mysql/orm.v b/v_windows/v/vlib/mysql/orm.v new file mode 100644 index 0000000..3a19a16 --- /dev/null +++ b/v_windows/v/vlib/mysql/orm.v @@ -0,0 +1,296 @@ +module mysql + +import orm +import time + +type Prims = byte | f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 + +// sql expr + +pub fn (db Connection) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive { + query := orm.orm_select_gen(config, '`', false, '?', 0, where) + mut ret := [][]orm.Primitive{} + mut stmt := db.init_stmt(query) + stmt.prepare() ? + + mysql_stmt_binder(mut stmt, where) ? + mysql_stmt_binder(mut stmt, data) ? + if data.data.len > 0 || where.data.len > 0 { + stmt.bind_params() ? + } + + mut status := stmt.execute() ? + num_fields := stmt.get_field_count() + metadata := stmt.gen_metadata() + fields := stmt.fetch_fields(metadata) + + mut dataptr := []Prims{} + + for i in 0 .. num_fields { + f := unsafe { fields[i] } + match FieldType(f.@type) { + .type_tiny { + dataptr << byte(0) + } + .type_short { + dataptr << u16(0) + } + .type_long { + dataptr << u32(0) + } + .type_longlong { + dataptr << u64(0) + } + .type_float { + dataptr << f32(0) + } + .type_double { + dataptr << f64(0) + } + .type_string { + dataptr << '' + } + else { + dataptr << byte(0) + } + } + } + + mut vptr := []&char{} + + for d in dataptr { + vptr << d.get_data_ptr() + } + + unsafe { dataptr.free() } + + lens := []u32{len: int(num_fields), init: 0} + stmt.bind_res(fields, vptr, lens, num_fields) + stmt.bind_result_buffer() ? + stmt.store_result() ? + + mut row := 0 + + for { + status = stmt.fetch_stmt() ? + + if status == 1 || status == 100 { + break + } + row++ + data_list := buffer_to_primitive(vptr, config.types) ? + ret << data_list + } + + stmt.close() ? + + return ret +} + +// sql stmt + +pub fn (db Connection) insert(table string, data orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .insert, false, '?', 1, data, orm.QueryData{}) + mysql_stmt_worker(db, query, data, orm.QueryData{}) ? +} + +pub fn (db Connection) update(table string, data orm.QueryData, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .update, false, '?', 1, data, where) + mysql_stmt_worker(db, query, data, where) ? +} + +pub fn (db Connection) delete(table string, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .delete, false, '?', 1, orm.QueryData{}, where) + mysql_stmt_worker(db, query, orm.QueryData{}, where) ? +} + +pub fn (db Connection) last_id() orm.Primitive { + query := 'SELECT last_insert_rowid();' + id := db.query(query) or { + Result{ + result: 0 + } + } + return orm.Primitive(id.rows()[0].vals[0].int()) +} + +// table +pub fn (db Connection) create(table string, fields []orm.TableField) ? { + query := orm.orm_table_gen(table, '`', false, 0, fields, mysql_type_from_v, false) or { + return err + } + mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +pub fn (db Connection) drop(table string) ? { + query := 'DROP TABLE `$table`;' + mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +fn mysql_stmt_worker(db Connection, query string, data orm.QueryData, where orm.QueryData) ? { + mut stmt := db.init_stmt(query) + stmt.prepare() ? + mysql_stmt_binder(mut stmt, data) ? + mysql_stmt_binder(mut stmt, where) ? + if data.data.len > 0 || where.data.len > 0 { + stmt.bind_params() ? + } + stmt.execute() ? + stmt.close() ? +} + +fn mysql_stmt_binder(mut stmt Stmt, d orm.QueryData) ? { + for data in d.data { + stmt_binder_match(mut stmt, data) + } +} + +fn stmt_binder_match(mut stmt Stmt, data orm.Primitive) { + match data { + bool { + stmt.bind_bool(&data) + } + i8 { + stmt.bind_i8(&data) + } + i16 { + stmt.bind_i16(&data) + } + int { + stmt.bind_int(&data) + } + i64 { + stmt.bind_i64(&data) + } + byte { + stmt.bind_byte(&data) + } + u16 { + stmt.bind_u16(&data) + } + u32 { + stmt.bind_u32(&data) + } + u64 { + stmt.bind_u64(&data) + } + f32 { + stmt.bind_f32(unsafe { &f32(&data) }) + } + f64 { + stmt.bind_f64(unsafe { &f64(&data) }) + } + string { + stmt.bind_text(data) + } + time.Time { + stmt.bind_int(&int(data.unix)) + } + orm.InfixType { + stmt_binder_match(mut stmt, data.right) + } + } +} + +fn buffer_to_primitive(data_list []&char, types []int) ?[]orm.Primitive { + mut res := []orm.Primitive{} + + for i, data in data_list { + mut primitive := orm.Primitive(0) + match types[i] { + 5 { + primitive = *(&i8(data)) + } + 6 { + primitive = *(&i16(data)) + } + 7, -1 { + primitive = *(&int(data)) + } + 8 { + primitive = *(&i64(data)) + } + 9 { + primitive = *(&byte(data)) + } + 10 { + primitive = *(&u16(data)) + } + 11 { + primitive = *(&u32(data)) + } + 12 { + primitive = *(&u64(data)) + } + 13 { + primitive = *(&f32(data)) + } + 14 { + primitive = *(&f64(data)) + } + 15 { + primitive = *(&bool(data)) + } + orm.string { + primitive = unsafe { cstring_to_vstring(&char(data)) } + } + orm.time { + timestamp := *(&int(data)) + primitive = time.unix(timestamp) + } + else { + return error('Unknown type ${types[i]}') + } + } + res << primitive + } + + return res +} + +fn mysql_type_from_v(typ int) ?string { + str := match typ { + 5, 9, 16 { + 'TINYINT' + } + 6, 10 { + 'SMALLINT' + } + 7, 11, orm.time { + 'INT' + } + 8, 12 { + 'BIGINT' + } + 13 { + 'FLOAT' + } + 14 { + 'DOUBLE' + } + orm.string { + 'TEXT' + } + -1 { + 'SERIAL' + } + else { + '' + } + } + if str == '' { + return error('Unknown type $typ') + } + return str +} + +fn (p Prims) get_data_ptr() &char { + return match p { + string { + p.str + } + else { + &char(&p) + } + } +} diff --git a/v_windows/v/vlib/mysql/result.v b/v_windows/v/vlib/mysql/result.v new file mode 100644 index 0000000..e9e1909 --- /dev/null +++ b/v_windows/v/vlib/mysql/result.v @@ -0,0 +1,153 @@ +module mysql + +pub struct Result { + result &C.MYSQL_RES +} + +pub struct Row { +pub mut: + vals []string +} + +pub struct Field { + name string + org_name string + table string + org_table string + db string + catalog string + def string + length int + max_length int + name_length u32 + org_name_length u32 + table_length u32 + org_table_length u32 + db_length u32 + catalog_length u32 + def_length u32 + flags u32 + decimals u32 + charsetnr u32 + type_ FieldType +} + +// fetch_row - fetches the next row from a result. +pub fn (r Result) fetch_row() &&byte { + return C.mysql_fetch_row(r.result) +} + +// n_rows - returns the number of rows from a result. +pub fn (r Result) n_rows() u64 { + return C.mysql_num_rows(r.result) +} + +// n_fields - returns the number of columns from a result. +pub fn (r Result) n_fields() int { + return C.mysql_num_fields(r.result) +} + +// rows - returns array of rows, each containing an array of values, +// one for each column. +pub fn (r Result) rows() []Row { + mut rows := []Row{} + nr_cols := r.n_fields() + for rr := r.fetch_row(); rr; rr = r.fetch_row() { + mut row := Row{} + for i in 0 .. nr_cols { + if unsafe { rr[i] == 0 } { + row.vals << '' + } else { + row.vals << mystring(unsafe { &byte(rr[i]) }) + } + } + rows << row + } + return rows +} + +// maps - returns an array of maps, each containing a set of +// field name: field value pairs. +pub fn (r Result) maps() []map[string]string { + mut array_map := []map[string]string{} + rows := r.rows() + fields := r.fields() + for i in 0 .. rows.len { + mut map_val := map[string]string{} + for j in 0 .. fields.len { + map_val[fields[j].name] = rows[i].vals[j] + } + array_map << map_val + } + return array_map +} + +// fields - returns an array of fields/columns. +// The definitions apply primarily for columns of results, +// such as those produced by `SELECT` statements. +pub fn (r Result) fields() []Field { + mut fields := []Field{} + nr_cols := r.n_fields() + orig_fields := C.mysql_fetch_fields(r.result) + for i in 0 .. nr_cols { + unsafe { + fields << Field{ + name: mystring(orig_fields[i].name) + org_name: mystring(orig_fields[i].org_name) + table: mystring(orig_fields[i].table) + org_table: mystring(orig_fields[i].org_table) + db: mystring(orig_fields[i].db) + catalog: mystring(orig_fields[i].catalog) + def: resolve_nil_str(orig_fields[i].def) + length: orig_fields.length + max_length: orig_fields.max_length + name_length: orig_fields.name_length + org_name_length: orig_fields.org_name_length + table_length: orig_fields.table_length + org_table_length: orig_fields.org_table_length + db_length: orig_fields.db_length + catalog_length: orig_fields.catalog_length + def_length: orig_fields.def_length + flags: orig_fields.flags + decimals: orig_fields.decimals + charsetnr: orig_fields.charsetnr + type_: FieldType(orig_fields.@type) + } + } + } + return fields +} + +// str - serializes the field +pub fn (f Field) str() string { + return ' +{ + name: "$f.name" + org_name: "$f.org_name" + table: "$f.table" + org_table: "$f.org_table" + db: "$f.db" + catalog: "$f.catalog" + def: "$f.def" + length: $f.length + max_length: $f.max_length + name_length: $f.name_length + org_name_length: $f.org_name_length + table_length: $f.table_length + org_table_length: $f.org_table_length + db_length: $f.db_length + catalog_length: $f.catalog_length + def_length: $f.def_length + flags: $f.flags + decimals: $f.decimals + charsetnr: $f.charsetnr + type: $f.type_.str() +} +' +} + +// free - frees the memory used by a result +[unsafe] +pub fn (r &Result) free() { + C.mysql_free_result(r.result) +} diff --git a/v_windows/v/vlib/mysql/stmt.c.v b/v_windows/v/vlib/mysql/stmt.c.v new file mode 100644 index 0000000..ee91746 --- /dev/null +++ b/v_windows/v/vlib/mysql/stmt.c.v @@ -0,0 +1,233 @@ +module mysql + +[typedef] +struct C.MYSQL_STMT { + mysql &C.MYSQL + stmt_id u32 +} + +[typedef] +struct C.MYSQL_BIND { +mut: + buffer_type int + buffer voidptr + buffer_length u32 + length &u32 +} + +const ( + mysql_type_decimal = C.MYSQL_TYPE_DECIMAL + mysql_type_tiny = C.MYSQL_TYPE_TINY + mysql_type_short = C.MYSQL_TYPE_SHORT + mysql_type_long = C.MYSQL_TYPE_LONG + mysql_type_float = C.MYSQL_TYPE_FLOAT + mysql_type_double = C.MYSQL_TYPE_DOUBLE + mysql_type_null = C.MYSQL_TYPE_NULL + mysql_type_timestamp = C.MYSQL_TYPE_TIMESTAMP + mysql_type_longlong = C.MYSQL_TYPE_LONGLONG + mysql_type_int24 = C.MYSQL_TYPE_INT24 + mysql_type_date = C.MYSQL_TYPE_DATE + mysql_type_time = C.MYSQL_TYPE_TIME + mysql_type_datetime = C.MYSQL_TYPE_DATETIME + mysql_type_year = C.MYSQL_TYPE_YEAR + mysql_type_varchar = C.MYSQL_TYPE_VARCHAR + mysql_type_bit = C.MYSQL_TYPE_BIT + mysql_type_timestamp22 = C.MYSQL_TYPE_TIMESTAMP + mysql_type_json = C.MYSQL_TYPE_JSON + mysql_type_newdecimal = C.MYSQL_TYPE_NEWDECIMAL + mysql_type_enum = C.MYSQL_TYPE_ENUM + mysql_type_set = C.MYSQL_TYPE_SET + mysql_type_tiny_blob = C.MYSQL_TYPE_TINY_BLOB + mysql_type_medium_blob = C.MYSQL_TYPE_MEDIUM_BLOB + mysql_type_long_blob = C.MYSQL_TYPE_LONG_BLOB + mysql_type_blob = C.MYSQL_TYPE_BLOB + mysql_type_var_string = C.MYSQL_TYPE_VAR_STRING + mysql_type_string = C.MYSQL_TYPE_STRING + mysql_type_geometry = C.MYSQL_TYPE_GEOMETRY + mysql_no_data = C.MYSQL_NO_DATA +) + +fn C.mysql_stmt_init(&C.MYSQL) &C.MYSQL_STMT +fn C.mysql_stmt_prepare(&C.MYSQL_STMT, &char, u32) int +fn C.mysql_stmt_bind_param(&C.MYSQL_STMT, &C.MYSQL_BIND) bool +fn C.mysql_stmt_execute(&C.MYSQL_STMT) int +fn C.mysql_stmt_close(&C.MYSQL_STMT) bool +fn C.mysql_stmt_free_result(&C.MYSQL_STMT) bool +fn C.mysql_stmt_error(&C.MYSQL_STMT) &char +fn C.mysql_stmt_result_metadata(&C.MYSQL_STMT) &C.MYSQL_RES + +fn C.mysql_stmt_field_count(&C.MYSQL_STMT) u16 +fn C.mysql_stmt_bind_result(&C.MYSQL_STMT, &C.MYSQL_BIND) bool +fn C.mysql_stmt_fetch(&C.MYSQL_STMT) int +fn C.mysql_stmt_next_result(&C.MYSQL_STMT) int +fn C.mysql_stmt_store_result(&C.MYSQL_STMT) int + +struct Stmt { + stmt &C.MYSQL_STMT = &C.MYSQL_STMT(0) + query string +mut: + binds []C.MYSQL_BIND + res []C.MYSQL_BIND +} + +pub fn (db Connection) init_stmt(query string) Stmt { + return Stmt{ + stmt: C.mysql_stmt_init(db.conn) + query: query + binds: []C.MYSQL_BIND{} + } +} + +pub fn (stmt Stmt) prepare() ? { + res := C.mysql_stmt_prepare(stmt.stmt, stmt.query.str, stmt.query.len) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } +} + +pub fn (stmt Stmt) bind_params() ? { + res := C.mysql_stmt_bind_param(stmt.stmt, &C.MYSQL_BIND(stmt.binds.data)) + if res && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +pub fn (stmt Stmt) execute() ?int { + res := C.mysql_stmt_execute(stmt.stmt) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) next() ?int { + res := C.mysql_stmt_next_result(stmt.stmt) + if res > 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) gen_metadata() &C.MYSQL_RES { + return C.mysql_stmt_result_metadata(stmt.stmt) +} + +pub fn (stmt Stmt) fetch_fields(res &C.MYSQL_RES) &C.MYSQL_FIELD { + return C.mysql_fetch_fields(res) +} + +pub fn (stmt Stmt) fetch_stmt() ?int { + res := C.mysql_stmt_fetch(stmt.stmt) + if res !in [0, 100] && stmt.get_error_msg() != '' { + return stmt.error(res) + } + return res +} + +pub fn (stmt Stmt) close() ? { + if !C.mysql_stmt_close(stmt.stmt) && stmt.get_error_msg() != '' { + return stmt.error(1) + } + if !C.mysql_stmt_free_result(stmt.stmt) && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +fn (stmt Stmt) get_error_msg() string { + return unsafe { cstring_to_vstring(&char(C.mysql_stmt_error(stmt.stmt))) } +} + +pub fn (stmt Stmt) error(code int) IError { + msg := stmt.get_error_msg() + return IError(&SQLError{ + msg: '$msg ($code) ($stmt.query)' + code: code + }) +} + +fn (stmt Stmt) get_field_count() u16 { + return C.mysql_stmt_field_count(stmt.stmt) +} + +pub fn (mut stmt Stmt) bind_bool(b &bool) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_byte(b &byte) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_i8(b &i8) { + stmt.bind(mysql.mysql_type_tiny, b, 0) +} + +pub fn (mut stmt Stmt) bind_i16(b &i16) { + stmt.bind(mysql.mysql_type_short, b, 0) +} + +pub fn (mut stmt Stmt) bind_u16(b &u16) { + stmt.bind(mysql.mysql_type_short, b, 0) +} + +pub fn (mut stmt Stmt) bind_int(b &int) { + stmt.bind(mysql.mysql_type_long, b, 0) +} + +pub fn (mut stmt Stmt) bind_u32(b &u32) { + stmt.bind(mysql.mysql_type_long, b, 0) +} + +pub fn (mut stmt Stmt) bind_i64(b &i64) { + stmt.bind(mysql.mysql_type_longlong, b, 0) +} + +pub fn (mut stmt Stmt) bind_u64(b &u64) { + stmt.bind(mysql.mysql_type_longlong, b, 0) +} + +pub fn (mut stmt Stmt) bind_f32(b &f32) { + stmt.bind(mysql.mysql_type_float, b, 0) +} + +pub fn (mut stmt Stmt) bind_f64(b &f64) { + stmt.bind(mysql.mysql_type_double, b, 0) +} + +pub fn (mut stmt Stmt) bind_text(b string) { + stmt.bind(mysql.mysql_type_string, b.str, u32(b.len)) +} + +pub fn (mut stmt Stmt) bind(typ int, buffer voidptr, buf_len u32) { + stmt.binds << C.MYSQL_BIND{ + buffer_type: typ + buffer: buffer + buffer_length: buf_len + length: 0 + } +} + +pub fn (mut stmt Stmt) bind_res(fields &C.MYSQL_FIELD, dataptr []&char, lens []u32, num_fields int) { + for i in 0 .. num_fields { + len := FieldType(unsafe { fields[i].@type }).get_len() + stmt.res << C.MYSQL_BIND{ + buffer_type: unsafe { fields[i].@type } + buffer: dataptr[i] + length: &lens[i] + buffer_length: &len + } + } +} + +pub fn (mut stmt Stmt) bind_result_buffer() ? { + res := C.mysql_stmt_bind_result(stmt.stmt, &C.MYSQL_BIND(stmt.res.data)) + if res && stmt.get_error_msg() != '' { + return stmt.error(1) + } +} + +pub fn (mut stmt Stmt) store_result() ? { + res := C.mysql_stmt_store_result(stmt.stmt) + if res != 0 && stmt.get_error_msg() != '' { + return stmt.error(res) + } +} diff --git a/v_windows/v/vlib/mysql/utils.v b/v_windows/v/vlib/mysql/utils.v new file mode 100644 index 0000000..2d63327 --- /dev/null +++ b/v_windows/v/vlib/mysql/utils.v @@ -0,0 +1,26 @@ +module mysql + +// get_error_msg - returns error message from MySQL instance. +fn get_error_msg(conn &C.MYSQL) string { + return unsafe { C.mysql_error(conn).vstring() } +} + +// get_errno - returns error number from MySQL instance. +fn get_errno(conn &C.MYSQL) int { + return C.mysql_errno(conn) +} + +// resolve_nil_str - returns an empty string if passed value is a nil pointer. +fn resolve_nil_str(ptr &byte) string { + if isnil(ptr) { + return '' + } + return unsafe { ptr.vstring() } +} + +[inline] +fn mystring(b &byte) string { + unsafe { + return b.vstring() + } +} diff --git a/v_windows/v/vlib/net/aasocket.c.v b/v_windows/v/vlib/net/aasocket.c.v new file mode 100644 index 0000000..60418c3 --- /dev/null +++ b/v_windows/v/vlib/net/aasocket.c.v @@ -0,0 +1,104 @@ +module net + +$if windows { + // This is mainly here for tcc on windows + // which apparently doesnt have this definition + #include "@VROOT/vlib/net/ipv6_v6only.h" +} + +// Select represents a select operation +enum Select { + read + write + except +} + +// SocketType are the available sockets +pub enum SocketType { + udp = C.SOCK_DGRAM + tcp = C.SOCK_STREAM + seqpacket = C.SOCK_SEQPACKET +} + +// AddrFamily are the available address families +pub enum AddrFamily { + unix = C.AF_UNIX + ip = C.AF_INET + ip6 = C.AF_INET6 + unspec = C.AF_UNSPEC +} + +fn C.socket(domain AddrFamily, typ SocketType, protocol int) int + +// fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen C.socklen_t) int +fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen u32) int + +fn C.htonl(hostlong u32) int + +fn C.htons(netshort u16) int + +// fn C.bind(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int +// use voidptr for arg 2 becasue sockaddr is a generic descriptor for any kind of socket operation, +// it can also take sockaddr_in depending on the type of socket used in arg 1 +fn C.bind(sockfd int, addr &Addr, addrlen u32) int + +fn C.listen(sockfd int, backlog int) int + +// fn C.accept(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int +fn C.accept(sockfd int, addr &Addr, addrlen &u32) int + +fn C.getaddrinfo(node &char, service &char, hints &C.addrinfo, res &&C.addrinfo) int + +fn C.freeaddrinfo(info &C.addrinfo) + +// fn C.connect(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int +fn C.connect(sockfd int, addr &Addr, addrlen u32) int + +// fn C.send(sockfd int, buf voidptr, len size_t, flags int) size_t +fn C.send(sockfd int, buf voidptr, len size_t, flags int) int + +// fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &C.sockaddr, addrlen C.socklen_t) size_t +fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &Addr, addrlen u32) int + +// fn C.recv(sockfd int, buf voidptr, len size_t, flags int) size_t +fn C.recv(sockfd int, buf voidptr, len size_t, flags int) int + +// fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &C.sockaddr, addrlen &C.socklen_t) size_t +fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &Addr, addrlen &u32) int + +fn C.shutdown(socket int, how int) int + +fn C.ntohs(netshort u16) int + +// fn C.getpeername(sockfd int, addr &C.sockaddr, addlen &C.socklen_t) int +fn C.getpeername(sockfd int, addr &Addr, addlen &u32) int + +fn C.inet_ntop(af AddrFamily, src voidptr, dst &char, dst_size int) &char + +fn C.WSAAddressToStringA(lpsaAddress &Addr, dwAddressLength u32, lpProtocolInfo voidptr, lpszAddressString &char, lpdwAddressStringLength &u32) int + +// fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int +fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &u32) int + +fn C.getsockopt(sockfd int, level int, optname int, optval voidptr, optlen &u32) int + +// defined in builtin +// fn C.read() int +// fn C.close() int + +fn C.ioctlsocket(s int, cmd int, argp &u32) int + +fn C.fcntl(fd int, cmd int, arg ...voidptr) int + +fn C.@select(ndfs int, readfds &C.fd_set, writefds &C.fd_set, exceptfds &C.fd_set, timeout &C.timeval) int + +fn C.FD_ZERO(fdset &C.fd_set) + +fn C.FD_SET(fd int, fdset &C.fd_set) + +fn C.FD_ISSET(fd int, fdset &C.fd_set) bool + +fn C.inet_pton(family AddrFamily, saddr &char, addr voidptr) int + +[typedef] +pub struct C.fd_set {} diff --git a/v_windows/v/vlib/net/address.v b/v_windows/v/vlib/net/address.v new file mode 100644 index 0000000..af1a000 --- /dev/null +++ b/v_windows/v/vlib/net/address.v @@ -0,0 +1,258 @@ +module net + +import io.util +import os + +union AddrData { + Unix + Ip + Ip6 +} + +const ( + addr_ip6_any = [16]byte{init: byte(0)} + addr_ip_any = [4]byte{init: byte(0)} +) + +fn new_ip6(port u16, addr [16]byte) Addr { + a := Addr{ + f: u16(AddrFamily.ip6) + addr: AddrData{ + Ip6: Ip6{ + port: u16(C.htons(port)) + } + } + } + + copy(a.addr.Ip6.addr[0..], addr[0..]) + + return a +} + +fn new_ip(port u16, addr [4]byte) Addr { + a := Addr{ + f: u16(AddrFamily.ip) + addr: AddrData{ + Ip: Ip{ + port: u16(C.htons(port)) + } + } + } + + copy(a.addr.Ip6.addr[0..], addr[0..]) + + return a +} + +fn temp_unix() ?Addr { + // create a temp file to get a filename + // close it + // remove it + // then reuse the filename + mut file, filename := util.temp_file() ? + file.close() + os.rm(filename) ? + addrs := resolve_addrs(filename, .unix, .udp) ? + return addrs[0] +} + +pub fn (a Addr) family() AddrFamily { + return AddrFamily(a.f) +} + +const ( + max_ip_len = 24 + max_ip6_len = 46 +) + +fn (a Ip) str() string { + buf := []byte{len: net.max_ip_len, init: 0} + + res := &char(C.inet_ntop(.ip, &a.addr, buf.data, buf.len)) + + if res == 0 { + return '' + } + + saddr := buf.bytestr() + port := C.ntohs(a.port) + + return '$saddr:$port' +} + +fn (a Ip6) str() string { + buf := []byte{len: net.max_ip6_len, init: 0} + + res := &char(C.inet_ntop(.ip6, &a.addr, buf.data, buf.len)) + + if res == 0 { + return '' + } + + saddr := buf.bytestr() + port := C.ntohs(a.port) + + return '[$saddr]:$port' +} + +const aoffset = __offsetof(Addr, addr) + +fn (a Addr) len() u32 { + match a.family() { + .ip { + return sizeof(Ip) + net.aoffset + } + .ip6 { + return sizeof(Ip6) + net.aoffset + } + .unix { + return sizeof(Unix) + net.aoffset + } + else { + panic('Unknown address family') + } + } +} + +pub fn resolve_addrs(addr string, family AddrFamily, @type SocketType) ?[]Addr { + match family { + .ip, .ip6, .unspec { + return resolve_ipaddrs(addr, family, @type) + } + .unix { + resolved := Unix{} + + if addr.len > max_unix_path { + return error('net: resolve_addrs Unix socket address is too long') + } + + // Copy the unix path into the address struct + unsafe { + C.memcpy(&resolved.path, addr.str, addr.len) + } + + return [Addr{ + f: u16(AddrFamily.unix) + addr: AddrData{ + Unix: resolved + } + }] + } + } +} + +pub fn resolve_addrs_fuzzy(addr string, @type SocketType) ?[]Addr { + if addr.len == 0 { + return none + } + + // Use a small heuristic to figure out what address family this is + // (out of the ones that we support) + + if addr.contains(':') { + // Colon is a reserved character in unix paths + // so this must be an ip address + return resolve_addrs(addr, .unspec, @type) + } + + return resolve_addrs(addr, .unix, @type) +} + +pub fn resolve_ipaddrs(addr string, family AddrFamily, typ SocketType) ?[]Addr { + address, port := split_address(addr) ? + + if addr[0] == `:` { + // Use in6addr_any + return [new_ip6(port, net.addr_ip6_any)] + } + + mut hints := C.addrinfo{ + // ai_family: int(family) + // ai_socktype: int(typ) + // ai_flags: C.AI_PASSIVE + } + hints.ai_family = int(family) + hints.ai_socktype = int(typ) + hints.ai_flags = C.AI_PASSIVE + hints.ai_protocol = 0 + hints.ai_addrlen = 0 + hints.ai_addr = voidptr(0) + hints.ai_canonname = voidptr(0) + hints.ai_next = voidptr(0) + results := &C.addrinfo(0) + + sport := '$port' + + // This might look silly but is recommended by MSDN + $if windows { + socket_error(0 - C.getaddrinfo(&char(address.str), &char(sport.str), &hints, &results)) ? + } $else { + x := C.getaddrinfo(&char(address.str), &char(sport.str), &hints, &results) + wrap_error(x) ? + } + + defer { + C.freeaddrinfo(results) + } + + // Now that we have our linked list of addresses + // convert them into an array + mut addresses := []Addr{} + + for result := results; !isnil(result); result = result.ai_next { + match AddrFamily(result.ai_family) { + .ip, .ip6 { + new_addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + unsafe { + C.memcpy(&new_addr, result.ai_addr, result.ai_addrlen) + } + addresses << new_addr + } + else { + panic('Unexpected address family $result.ai_family') + } + } + } + + return addresses +} + +fn (a Addr) str() string { + match AddrFamily(a.f) { + .ip { + unsafe { + return a.addr.Ip.str() + } + } + .ip6 { + unsafe { + return a.addr.Ip6.str() + } + } + .unix { + unsafe { + return tos_clone(a.addr.Unix.path[0..max_unix_path].data) + } + } + .unspec { + return '<.unspec>' + } + } +} + +pub fn addr_from_socket_handle(handle int) Addr { + addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + size := sizeof(addr) + + C.getsockname(handle, voidptr(&addr), &size) + + return addr +} diff --git a/v_windows/v/vlib/net/address_darwin.c.v b/v_windows/v/vlib/net/address_darwin.c.v new file mode 100644 index 0000000..041ccf2 --- /dev/null +++ b/v_windows/v/vlib/net/address_darwin.c.v @@ -0,0 +1,74 @@ +module net + +const max_unix_path = 104 + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_in6 { +mut: + // 1 + 1 + 2 + 4 + 16 + 4 = 28; + sin6_len byte // 1 + sin6_family byte // 1 + sin6_port u16 // 2 + sin6_flowinfo u32 // 4 + sin6_addr [16]byte // 16 + sin6_scope_id u32 // 4 +} + +struct C.sockaddr_in { +mut: + sin_len byte + sin_family byte + sin_port u16 + sin_addr u32 + sin_zero [8]char +} + +struct C.sockaddr_un { +mut: + sun_len byte + sun_family byte + sun_path [max_unix_path]char +} + +[_pack: '1'] +struct Ip6 { + port u16 + flow_info u32 + addr [16]byte + scope_id u32 +} + +[_pack: '1'] +struct Ip { + port u16 + addr [4]byte + // Pad to size so that socket functions + // dont complain to us (see in.h and bind()) + // TODO(emily): I would really like to use + // some constant calculations here + // so that this doesnt have to be hardcoded + sin_pad [8]byte +} + +struct Unix { + path [max_unix_path]char +} + +[_pack: '1'] +struct Addr { +pub: + len u8 + f u8 + addr AddrData +} diff --git a/v_windows/v/vlib/net/address_default.c.v b/v_windows/v/vlib/net/address_default.c.v new file mode 100644 index 0000000..95942cc --- /dev/null +++ b/v_windows/v/vlib/net/address_default.c.v @@ -0,0 +1,32 @@ +module net + +const max_unix_path = 104 + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_in { + sin_family byte + sin_port u16 + sin_addr u32 +} + +struct C.sockaddr_in6 { + sin6_family byte + sin6_port u16 + sin6_addr [4]u32 +} + +struct C.sockaddr_un { + sun_family byte + sun_path [max_unix_path]char +} diff --git a/v_windows/v/vlib/net/address_freebsd.c.v b/v_windows/v/vlib/net/address_freebsd.c.v new file mode 100644 index 0000000..bc84665 --- /dev/null +++ b/v_windows/v/vlib/net/address_freebsd.c.v @@ -0,0 +1,77 @@ +module net + +#include +#include + +const max_unix_path = 104 + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_in6 { +mut: + // 1 + 1 + 2 + 4 + 16 + 4 = 28; + sin6_len byte // 1 + sin6_family byte // 1 + sin6_port u16 // 2 + sin6_flowinfo u32 // 4 + sin6_addr [16]byte // 16 + sin6_scope_id u32 // 4 +} + +struct C.sockaddr_in { +mut: + sin_len byte + sin_family byte + sin_port u16 + sin_addr u32 + sin_zero [8]char +} + +struct C.sockaddr_un { +mut: + sun_len byte + sun_family byte + sun_path [max_unix_path]char +} + +[_pack: '1'] +struct Ip6 { + port u16 + flow_info u32 + addr [16]byte + scope_id u32 +} + +[_pack: '1'] +struct Ip { + port u16 + addr [4]byte + // Pad to size so that socket functions + // dont complain to us (see in.h and bind()) + // TODO(emily): I would really like to use + // some constant calculations here + // so that this doesnt have to be hardcoded + sin_pad [8]byte +} + +struct Unix { + path [max_unix_path]char +} + +[_pack: '1'] +struct Addr { +pub: + len u8 + f u8 + addr AddrData +} diff --git a/v_windows/v/vlib/net/address_linux.c.v b/v_windows/v/vlib/net/address_linux.c.v new file mode 100644 index 0000000..a6f7a97 --- /dev/null +++ b/v_windows/v/vlib/net/address_linux.c.v @@ -0,0 +1,63 @@ +module net + +const max_unix_path = 108 + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_in { + sin_family u16 + sin_port u16 + sin_addr u32 +} + +struct C.sockaddr_in6 { + sin6_family u16 + sin6_port u16 + sin6_addr [4]u32 +} + +struct C.sockaddr_un { + sun_family u16 + sun_path [max_unix_path]char +} + +[_pack: '1'] +struct Ip6 { + port u16 + flow_info u32 + addr [16]byte + scope_id u32 +} + +[_pack: '1'] +struct Ip { + port u16 + addr [4]byte + // Pad to size so that socket functions + // dont complain to us (see in.h and bind()) + // TODO(emily): I would really like to use + // some constant calculations here + // so that this doesnt have to be hardcoded + sin_pad [8]byte +} + +struct Unix { + path [max_unix_path]byte +} + +[_pack: '1'] +struct Addr { +pub: + f u16 + addr AddrData +} diff --git a/v_windows/v/vlib/net/address_test.v b/v_windows/v/vlib/net/address_test.v new file mode 100644 index 0000000..5b3aab0 --- /dev/null +++ b/v_windows/v/vlib/net/address_test.v @@ -0,0 +1,98 @@ +module net + +$if windows { + $if msvc { + // Force these to be included before afunix! + #include + #include + #include + } $else { + #include "@VROOT/vlib/net/afunix.h" + } +} $else { + #include +} + +fn test_diagnostics() { + dump(aoffset) + eprintln('--------') + in6 := C.sockaddr_in6{} + our_ip6 := Ip6{} + $if macos { + dump(__offsetof(C.sockaddr_in6, sin6_len)) + } + dump(__offsetof(C.sockaddr_in6, sin6_family)) + dump(__offsetof(C.sockaddr_in6, sin6_port)) + dump(__offsetof(C.sockaddr_in6, sin6_addr)) + $if macos { + dump(sizeof(in6.sin6_len)) + } + dump(sizeof(in6.sin6_family)) + dump(sizeof(in6.sin6_port)) + dump(sizeof(in6.sin6_addr)) + dump(sizeof(in6)) + eprintln('') + dump(__offsetof(Ip6, port)) + dump(__offsetof(Ip6, addr)) + dump(sizeof(our_ip6.port)) + dump(sizeof(our_ip6.addr)) + dump(sizeof(our_ip6)) + eprintln('--------') + in4 := C.sockaddr_in{} + our_ip4 := Ip{} + $if macos { + dump(__offsetof(C.sockaddr_in, sin_len)) + } + dump(__offsetof(C.sockaddr_in, sin_family)) + dump(__offsetof(C.sockaddr_in, sin_port)) + dump(__offsetof(C.sockaddr_in, sin_addr)) + $if macos { + dump(sizeof(in4.sin_len)) + } + dump(sizeof(in4.sin_family)) + dump(sizeof(in4.sin_port)) + dump(sizeof(in4.sin_addr)) + dump(sizeof(in4)) + eprintln('') + dump(__offsetof(Ip, port)) + dump(__offsetof(Ip, addr)) + dump(sizeof(our_ip4.port)) + dump(sizeof(our_ip4.addr)) + dump(sizeof(our_ip4)) + eprintln('--------') + dump(__offsetof(C.sockaddr_un, sun_path)) + dump(__offsetof(Unix, path)) + eprintln('--------') +} + +fn test_sizes_unix_sun_path() { + x1 := C.sockaddr_un{} + x2 := Unix{} + assert sizeof(x1.sun_path) == sizeof(x2.path) +} + +fn test_offsets_ipv6() { + assert __offsetof(C.sockaddr_in6, sin6_addr) == __offsetof(Ip6, addr) + aoffset + assert __offsetof(C.sockaddr_in6, sin6_port) == __offsetof(Ip6, port) + aoffset +} + +fn test_offsets_ipv4() { + assert __offsetof(C.sockaddr_in, sin_addr) == __offsetof(Ip, addr) + aoffset + assert __offsetof(C.sockaddr_in, sin_port) == __offsetof(Ip, port) + aoffset +} + +fn test_offsets_unix() { + assert __offsetof(C.sockaddr_un, sun_path) == __offsetof(Unix, path) + aoffset +} + +fn test_sizes_ipv6() { + assert sizeof(C.sockaddr_in6) == sizeof(Ip6) + aoffset +} + +fn test_sizes_ipv4() { + assert sizeof(C.sockaddr_in) == sizeof(Ip) + aoffset +} + +fn test_sizes_unix() { + assert sizeof(C.sockaddr_un) == sizeof(Unix) + aoffset +} diff --git a/v_windows/v/vlib/net/address_windows.c.v b/v_windows/v/vlib/net/address_windows.c.v new file mode 100644 index 0000000..e50fdb4 --- /dev/null +++ b/v_windows/v/vlib/net/address_windows.c.v @@ -0,0 +1,58 @@ +module net + +const max_unix_path = 108 + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_in { + sin_family u16 + sin_port u16 + sin_addr u32 +} + +struct C.sockaddr_in6 { + sin6_family u16 + sin6_port u16 + sin6_addr [4]u32 +} + +struct C.sockaddr_un { + sun_family u16 + sun_path [max_unix_path]char +} + +[_pack: '1'] +struct Ip6 { + port u16 + flow_info u32 + addr [16]byte + scope_id u32 +} + +[_pack: '1'] +struct Ip { + port u16 + addr [4]byte + sin_pad [8]byte +} + +struct Unix { + path [max_unix_path]byte +} + +[_pack: '1'] +struct Addr { +pub: + f u16 + addr AddrData +} diff --git a/v_windows/v/vlib/net/afunix.h b/v_windows/v/vlib/net/afunix.h new file mode 100644 index 0000000..5fedca2 --- /dev/null +++ b/v_windows/v/vlib/net/afunix.h @@ -0,0 +1,26 @@ +/** + * This file has no copyright assigned and is placed in the Public Domain. + * This file is part of the mingw-w64 runtime package. + * No warranty is given; refer to the file DISCLAIMER.PD within this package. + */ + +#ifndef _AFUNIX_ +#define _AFUNIX_ + +#define UNIX_PATH_MAX 108 + +#if !defined(ADDRESS_FAMILY) +#define UNDEF_ADDRESS_FAMILY +#define ADDRESS_FAMILY unsigned short +#endif + +typedef struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +} SOCKADDR_UN, *PSOCKADDR_UN; + +#if defined(UNDEF_ADDRESS_FAMILY) +#undef ADDRESS_FAMILY +#endif + +#endif /* _AFUNIX_ */ diff --git a/v_windows/v/vlib/net/common.v b/v_windows/v/vlib/net/common.v new file mode 100644 index 0000000..aab8f16 --- /dev/null +++ b/v_windows/v/vlib/net/common.v @@ -0,0 +1,129 @@ +module net + +import time + +// no_deadline should be given to functions when no deadline is wanted (i.e. all functions +// return instantly) +const no_deadline = time.Time{ + unix: 0 +} + +// no_timeout should be given to functions when no timeout is wanted (i.e. all functions +// return instantly) +pub const no_timeout = time.Duration(0) + +// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions +// only ever return with data) +pub const infinite_timeout = time.infinite + +// Shutdown shutsdown a socket and closes it +fn shutdown(handle int) ? { + $if windows { + C.shutdown(handle, C.SD_BOTH) + socket_error(C.closesocket(handle)) ? + } $else { + C.shutdown(handle, C.SHUT_RDWR) + socket_error(C.close(handle)) ? + } +} + +// Select waits for an io operation (specified by parameter `test`) to be available +fn @select(handle int, test Select, timeout time.Duration) ?bool { + set := C.fd_set{} + + C.FD_ZERO(&set) + C.FD_SET(handle, &set) + + seconds := timeout / time.second + microseconds := time.Duration(timeout - (seconds * time.second)).microseconds() + + mut tt := C.timeval{ + tv_sec: u64(seconds) + tv_usec: u64(microseconds) + } + + mut timeval_timeout := &tt + + // infinite timeout is signaled by passing null as the timeout to + // select + if timeout == net.infinite_timeout { + timeval_timeout = &C.timeval(0) + } + + match test { + .read { + socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout)) ? + } + .write { + socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout)) ? + } + .except { + socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout)) ? + } + } + + return C.FD_ISSET(handle, &set) +} + +// select_with_retry will retry the select if select is failing +// due to interrupted system call. This can happen on signals +// for example the GC Boehm uses signals internally on garbage +// collection +[inline] +fn select_with_retry(handle int, test Select, timeout time.Duration) ?bool { + mut retries := 10 + for retries > 0 { + ready := @select(handle, test, timeout) or { + if err.code == 4 { + // signal! lets retry max 10 times + // suspend thread with sleep to let the gc get + // cycles in the case the Bohem gc is interupting + time.sleep(1 * time.millisecond) + retries -= 1 + continue + } + // we got other error + return err + } + return ready + } + return error('failed to @select more that three times due to interrupted system call') +} + +// wait_for_common wraps the common wait code +fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test Select) ? { + if deadline.unix == 0 { + // do not accept negative timeout + if timeout < 0 { + return err_timed_out + } + ready := select_with_retry(handle, test, timeout) ? + if ready { + return + } + return err_timed_out + } + // Convert the deadline into a timeout + // and use that + d_timeout := deadline.unix - time.now().unix + if d_timeout < 0 { + // deadline is in the past so this has already + // timed out + return err_timed_out + } + ready := select_with_retry(handle, test, timeout) ? + if ready { + return + } + return err_timed_out +} + +// wait_for_write waits for a write io operation to be available +fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .write) +} + +// wait_for_read waits for a read io operation to be available +fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .read) +} diff --git a/v_windows/v/vlib/net/conv/conv.c.v b/v_windows/v/vlib/net/conv/conv.c.v new file mode 100644 index 0000000..e29741a --- /dev/null +++ b/v_windows/v/vlib/net/conv/conv.c.v @@ -0,0 +1,21 @@ +module conv + +// host to net 32 (htonl) +pub fn htn32(host &u32) u32 { + return C.htonl(host) +} + +// host to net 16 (htons) +pub fn htn16(host &u16) u16 { + return C.htons(host) +} + +// net to host 32 (ntohl) +pub fn nth32(host &u32) u32 { + return C.ntohl(host) +} + +// net to host 16 (ntohs) +pub fn nth16(host &u16) u16 { + return C.ntohs(host) +} diff --git a/v_windows/v/vlib/net/conv/conv_default.c.v b/v_windows/v/vlib/net/conv/conv_default.c.v new file mode 100644 index 0000000..8e8c582 --- /dev/null +++ b/v_windows/v/vlib/net/conv/conv_default.c.v @@ -0,0 +1,46 @@ +module conv + +#include + +fn C.htonl(host u32) u32 +fn C.htons(host u16) u16 + +fn C.ntohl(net u32) u32 +fn C.ntohs(net u16) u16 + +struct Bytes { +mut: + first u32 + last u32 +} + +union LongLong { + Bytes + ll u64 +} + +// host to net 64 (htonll) +pub fn htn64(host &u64) u64 { + mut ll := LongLong{ + ll: host + } + + unsafe { + ll.first = htn32(ll.first) + ll.last = htn32(ll.last) + } + return unsafe { ll.ll } +} + +// net to host 64 (ntohll) +pub fn nth64(net &u64) u64 { + mut ll := LongLong{ + ll: net + } + + unsafe { + ll.first = nth32(ll.first) + ll.last = nth32(ll.last) + } + return unsafe { ll.ll } +} diff --git a/v_windows/v/vlib/net/conv/conv_windows.c.v b/v_windows/v/vlib/net/conv/conv_windows.c.v new file mode 100644 index 0000000..15827f7 --- /dev/null +++ b/v_windows/v/vlib/net/conv/conv_windows.c.v @@ -0,0 +1,21 @@ +module conv + +#include + +fn C.htonll(host u64) u64 +fn C.htonl(host u32) u32 +fn C.htons(host u16) u16 + +fn C.ntohll(net u32) u32 +fn C.ntohl(net u32) u32 +fn C.ntohs(net u16) u16 + +// host to net 64 (htonll) +pub fn htn64(host &u64) u64 { + return C.htonll(host) +} + +// net to host 64 (htonll) +pub fn nth64(host &u64) u64 { + return C.ntohll(host) +} diff --git a/v_windows/v/vlib/net/errors.v b/v_windows/v/vlib/net/errors.v new file mode 100644 index 0000000..f6ada74 --- /dev/null +++ b/v_windows/v/vlib/net/errors.v @@ -0,0 +1,70 @@ +module net + +const ( + errors_base = 0 +) + +// Well defined errors that are returned from socket functions +pub const ( + err_new_socket_failed = error_with_code('net: new_socket failed to create socket', + errors_base + 1) + err_option_not_settable = error_with_code('net: set_option_xxx option not settable', + errors_base + 2) + err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type', + errors_base + 3) + err_port_out_of_range = error_with_code('', errors_base + 5) + err_no_udp_remote = error_with_code('', errors_base + 6) + err_connect_failed = error_with_code('net: connect failed', errors_base + 7) + err_connect_timed_out = error_with_code('net: connect timed out', errors_base + 8) + err_timed_out = error_with_code('net: op timed out', errors_base + 9) + err_timed_out_code = errors_base + 9 +) + +pub fn socket_error(potential_code int) ?int { + $if windows { + if potential_code < 0 { + last_error_int := C.WSAGetLastError() + last_error := wsa_error(last_error_int) + return error_with_code('net: socket error: ($last_error_int) $last_error', + int(last_error)) + } + } $else { + if potential_code < 0 { + last_error := error_code() + return error_with_code('net: socket error: $last_error', last_error) + } + } + + return potential_code +} + +pub fn wrap_error(error_code int) ? { + $if windows { + enum_error := wsa_error(error_code) + return error_with_code('net: socket error: $enum_error', error_code) + } $else { + if error_code == 0 { + return + } + return error_with_code('net: socket error: $error_code', error_code) + } +} + +// wrap_read_result takes a read result and sees if it is 0 for graceful +// connection termination and returns none +// e.g. res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0))? +[inline] +fn wrap_read_result(result int) ?int { + if result == 0 { + return none + } + return result +} + +[inline] +fn wrap_write_result(result int) ?int { + if result == 0 { + return none + } + return result +} diff --git a/v_windows/v/vlib/net/ftp/ftp.v b/v_windows/v/vlib/net/ftp/ftp.v new file mode 100644 index 0000000..41b2cde --- /dev/null +++ b/v_windows/v/vlib/net/ftp/ftp.v @@ -0,0 +1,265 @@ +module ftp + +/* +basic ftp module + RFC-959 + https://tools.ietf.org/html/rfc959 + + Methods: + ftp.connect(host) + ftp.login(user, passw) + pwd := ftp.pwd() + ftp.cd(folder) + dtp := ftp.pasv() + ftp.dir() + ftp.get(file) + dtp.read() + dtp.close() + ftp.close() +*/ +import net +import io + +const ( + connected = 220 + specify_password = 331 + logged_in = 230 + login_first = 503 + anonymous = 530 + open_data_connection = 150 + close_data_connection = 226 + command_ok = 200 + denied = 550 + passive_mode = 227 + complete = 226 +) + +struct DTP { +mut: + conn &net.TcpConn + reader io.BufferedReader + ip string + port int +} + +fn (mut dtp DTP) read() ?[]byte { + mut data := []byte{} + mut buf := []byte{len: 1024} + for { + len := dtp.reader.read(mut buf) or { break } + if len == 0 { + break + } + data << buf[..len] + } + return data +} + +fn (mut dtp DTP) close() { + dtp.conn.close() or { panic(err) } +} + +struct FTP { +mut: + conn &net.TcpConn + reader io.BufferedReader + buffer_size int +} + +pub fn new() FTP { + mut f := FTP{ + conn: 0 + } + f.buffer_size = 1024 + return f +} + +fn (mut zftp FTP) write(data string) ?int { + $if debug { + println('FTP.v >>> $data') + } + return zftp.conn.write('$data\r\n'.bytes()) +} + +fn (mut zftp FTP) read() ?(int, string) { + mut data := zftp.reader.read_line() ? + $if debug { + println('FTP.v <<< $data') + } + if data.len < 5 { + return 0, '' + } + code := data[..3].int() + if data[3] == `-` { + for { + data = zftp.reader.read_line() ? + if data[..3].int() == code && data[3] != `-` { + break + } + } + } + return code, data +} + +pub fn (mut zftp FTP) connect(ip string) ?bool { + zftp.conn = net.dial_tcp('$ip:21') ? + zftp.reader = io.new_buffered_reader(reader: zftp.conn) + code, _ := zftp.read() ? + if code == ftp.connected { + return true + } + return false +} + +pub fn (mut zftp FTP) login(user string, passwd string) ?bool { + zftp.write('USER $user') or { + $if debug { + println('ERROR sending user') + } + return false + } + mut code, _ := zftp.read() ? + if code == ftp.logged_in { + return true + } + if code != ftp.specify_password { + return false + } + zftp.write('PASS $passwd') or { + $if debug { + println('ERROR sending password') + } + return false + } + code, _ = zftp.read() ? + if code == ftp.logged_in { + return true + } + return false +} + +pub fn (mut zftp FTP) close() ? { + zftp.write('QUIT') ? + zftp.conn.close() ? +} + +pub fn (mut zftp FTP) pwd() ?string { + zftp.write('PWD') ? + _, data := zftp.read() ? + spl := data.split('"') // " + if spl.len >= 2 { + return spl[1] + } + return data +} + +pub fn (mut zftp FTP) cd(dir string) ? { + zftp.write('CWD $dir') or { return } + mut code, mut data := zftp.read() ? + match int(code) { + ftp.denied { + $if debug { + println('CD $dir denied!') + } + } + ftp.complete { + code, data = zftp.read() ? + } + else {} + } + $if debug { + println('CD $data') + } +} + +fn new_dtp(msg string) ?&DTP { + if !is_dtp_message_valid(msg) { + return error('Bad message') + } + ip, port := get_host_ip_from_dtp_message(msg) + mut dtp := &DTP{ + ip: ip + port: port + conn: 0 + } + conn := net.dial_tcp('$ip:$port') or { return error('Cannot connect to the data channel') } + dtp.conn = conn + dtp.reader = io.new_buffered_reader(reader: dtp.conn) + return dtp +} + +fn (mut zftp FTP) pasv() ?&DTP { + zftp.write('PASV') ? + code, data := zftp.read() ? + $if debug { + println('pass: $data') + } + if code != ftp.passive_mode { + return error('pasive mode not allowed') + } + dtp := new_dtp(data) ? + return dtp +} + +pub fn (mut zftp FTP) dir() ?[]string { + mut dtp := zftp.pasv() or { return error('Cannot establish data connection') } + zftp.write('LIST') ? + code, _ := zftp.read() ? + if code == ftp.denied { + return error('`LIST` denied') + } + if code != ftp.open_data_connection { + return error('Data channel empty') + } + list_dir := dtp.read() ? + result, _ := zftp.read() ? + if result != ftp.close_data_connection { + println('`LIST` not ok') + } + dtp.close() + mut dir := []string{} + sdir := list_dir.bytestr() + for lfile in sdir.split('\n') { + if lfile.len > 1 { + dir << lfile.after(' ').trim_space() + } + } + return dir +} + +pub fn (mut zftp FTP) get(file string) ?[]byte { + mut dtp := zftp.pasv() or { return error('Cannot stablish data connection') } + zftp.write('RETR $file') ? + code, _ := zftp.read() ? + if code == ftp.denied { + return error('Permission denied') + } + if code != ftp.open_data_connection { + return error('Data connection not ready') + } + blob := dtp.read() ? + dtp.close() + return blob +} + +fn is_dtp_message_valid(msg string) bool { + // An example of message: + // '227 Entering Passive Mode (209,132,183,61,48,218)' + return msg.contains('(') && msg.contains(')') && msg.contains(',') +} + +fn get_host_ip_from_dtp_message(msg string) (string, int) { + mut par_start_idx := -1 + mut par_end_idx := -1 + for i, c in msg { + if c == `(` { + par_start_idx = i + 1 + } else if c == `)` { + par_end_idx = i + } + } + data := msg[par_start_idx..par_end_idx].split(',') + ip := data[0..4].join('.') + port := data[4].int() * 256 + data[5].int() + return ip, port +} diff --git a/v_windows/v/vlib/net/ftp/ftp_test.v b/v_windows/v/vlib/net/ftp/ftp_test.v new file mode 100644 index 0000000..a62316d --- /dev/null +++ b/v_windows/v/vlib/net/ftp/ftp_test.v @@ -0,0 +1,50 @@ +import net.ftp + +fn test_ftp_cleint() { + $if !network ? { + return + } + // NB: this function makes network calls to external servers, + // that is why it is not a very good idea to run it in CI. + // If you want to run it manually, use: + // `v -d network vlib/net/ftp/ftp_test.v` + ftp_client_test_inside() or { panic(err) } +} + +fn ftp_client_test_inside() ? { + mut zftp := ftp.new() + // eprintln(zftp) + defer { + zftp.close() or { panic(err) } + } + connect_result := zftp.connect('ftp.redhat.com') ? + assert connect_result + login_result := zftp.login('ftp', 'ftp') ? + assert login_result + pwd := zftp.pwd() ? + assert pwd.len > 0 + zftp.cd('/') or { + assert false + return + } + dir_list1 := zftp.dir() or { + assert false + return + } + assert dir_list1.len > 0 + zftp.cd('/suse/linux/enterprise/11Server/en/SAT-TOOLS/SRPMS/') or { + assert false + return + } + dir_list2 := zftp.dir() or { + assert false + return + } + assert dir_list2.len > 0 + assert dir_list2.contains('katello-host-tools-3.3.5-8.sles11_4sat.src.rpm') + blob := zftp.get('katello-host-tools-3.3.5-8.sles11_4sat.src.rpm') or { + assert false + return + } + assert blob.len > 0 +} diff --git a/v_windows/v/vlib/net/html/README.md b/v_windows/v/vlib/net/html/README.md new file mode 100644 index 0000000..a92a6e6 --- /dev/null +++ b/v_windows/v/vlib/net/html/README.md @@ -0,0 +1,16 @@ +net/http is an HTML written in pure V. + +## Usage +```v oksyntax +import net.html + +fn main() { + doc := html.parse('

Hello world!

') + tag := doc.get_tag('h1')[0] //

Hello world!

+ println(tag.name) // h1 + println(tag.content) // Hello world! + println(tag.attributes) // {'class':'title'} + println(tag.str()) //

Hello world!

+} +``` +More examples found on [`parser_test.v`](parser_test.v) and [`html_test.v`](html_test.v) diff --git a/v_windows/v/vlib/net/html/data_structures.v b/v_windows/v/vlib/net/html/data_structures.v new file mode 100644 index 0000000..688b756 --- /dev/null +++ b/v_windows/v/vlib/net/html/data_structures.v @@ -0,0 +1,91 @@ +module html + +const ( + null_element = int(0x80000000) +) + +struct Stack { +mut: + elements []int + size int +} + +[inline] +fn is_null(data int) bool { + return data == html.null_element +} + +[inline] +fn (stack Stack) is_empty() bool { + return stack.size <= 0 +} + +fn (stack Stack) peek() int { + return if !stack.is_empty() { stack.elements[stack.size - 1] } else { html.null_element } +} + +fn (mut stack Stack) pop() int { + mut to_return := html.null_element + if !stack.is_empty() { + to_return = stack.elements[stack.size - 1] + stack.size-- + } + return to_return +} + +fn (mut stack Stack) push(item int) { + if stack.elements.len > stack.size { + stack.elements[stack.size] = item + } else { + stack.elements << item + } + stack.size++ +} + +struct BTree { +mut: + all_tags []Tag + node_pointer int + childrens [][]int + parents []int +} + +fn (mut btree BTree) add_children(tag Tag) int { + btree.all_tags << tag + if btree.all_tags.len > 1 { + for btree.childrens.len <= btree.node_pointer { + mut temp_array := btree.childrens + temp_array << []int{} + btree.childrens = temp_array + } + btree.childrens[btree.node_pointer] << btree.all_tags.len - 1 + for btree.parents.len < btree.all_tags.len { + mut temp_array := btree.parents + temp_array << 0 + btree.parents = temp_array + } + btree.parents[btree.all_tags.len - 1] = btree.node_pointer + } + return btree.all_tags.len - 1 +} + +[inline] +fn (btree BTree) get_children() []int { + return btree.childrens[btree.node_pointer] +} + +[inline] +fn (btree BTree) get_parent() int { + return btree.parents[btree.node_pointer] +} + +[inline] +fn (btree BTree) get_stored() Tag { + return btree.all_tags[btree.node_pointer] +} + +fn (mut btree BTree) move_pointer(to int) { + if to < btree.all_tags.len { + btree.node_pointer = to + } +} diff --git a/v_windows/v/vlib/net/html/dom.v b/v_windows/v/vlib/net/html/dom.v new file mode 100644 index 0000000..b145ddc --- /dev/null +++ b/v_windows/v/vlib/net/html/dom.v @@ -0,0 +1,189 @@ +module html + +import os + +// The W3C Document Object Model (DOM) is a platform and language-neutral +// interface that allows programs and scripts to dynamically access and +// update the content, structure, and style of a document. +// +// https://www.w3.org/TR/WD-DOM/introduction.html +pub struct DocumentObjectModel { +mut: + root &Tag + constructed bool + btree BTree + all_tags []&Tag + all_attributes map[string][]&Tag + close_tags map[string]bool // add a counter to see count how many times is closed and parse correctly + attributes map[string][]string + tag_attributes map[string][][]&Tag + tag_type map[string][]&Tag + debug_file os.File +} + +[if debug] +fn (mut dom DocumentObjectModel) print_debug(data string) { + $if debug { + if data.len > 0 { + dom.debug_file.writeln(data) or { panic(err) } + } + } +} + +[inline] +fn is_close_tag(tag &Tag) bool { + return tag.name.len > 0 && tag.name[0] == `/` +} + +fn (mut dom DocumentObjectModel) where_is(item_name string, attribute_name string) int { + if attribute_name !in dom.attributes { + dom.attributes[attribute_name] = []string{} + } + mut string_array := dom.attributes[attribute_name] + mut counter := 0 + for value in string_array { + if value == item_name { + return counter + } + counter++ + } + string_array << item_name + dom.attributes[attribute_name] = string_array + return string_array.len - 1 +} + +fn (mut dom DocumentObjectModel) add_tag_attribute(tag &Tag) { + for attribute_name, _ in tag.attributes { + attribute_value := tag.attributes[attribute_name] + location := dom.where_is(attribute_value, attribute_name) + if attribute_name !in dom.tag_attributes { + dom.tag_attributes[attribute_name] = [] + } + for { + mut temp_array := dom.tag_attributes[attribute_name] + temp_array << []&Tag{} + dom.tag_attributes[attribute_name] = temp_array + if location < dom.tag_attributes[attribute_name].len + 1 { + break + } + } + mut temp_array := dom.tag_attributes[attribute_name][location] + temp_array << tag + dom.tag_attributes[attribute_name][location] = temp_array + } +} + +fn (mut dom DocumentObjectModel) add_tag_by_type(tag &Tag) { + tag_name := tag.name + if tag_name !in dom.tag_type { + dom.tag_type[tag_name] = [tag] + } else { + mut temp_array := dom.tag_type[tag_name] + temp_array << tag + dom.tag_type[tag_name] = temp_array + } +} + +fn (mut dom DocumentObjectModel) add_tag_by_attribute(tag &Tag) { + for attribute_name in tag.attributes.keys() { + if attribute_name !in dom.all_attributes { + dom.all_attributes[attribute_name] = [tag] + } else { + mut temp_array := dom.all_attributes[attribute_name] + temp_array << tag + dom.all_attributes[attribute_name] = temp_array + } + } +} + +fn (mut dom DocumentObjectModel) construct(tag_list []&Tag) { + dom.constructed = true + mut temp_map := map[string]int{} + mut temp_int := null_element + mut temp_string := '' + mut stack := Stack{} + dom.btree = BTree{} + dom.root = tag_list[0] + dom.all_tags = [tag_list[0]] + temp_map['0'] = dom.btree.add_children(tag_list[0]) + stack.push(0) + root_index := 0 + for index := 1; index < tag_list.len; index++ { + mut tag := tag_list[index] + dom.print_debug(tag.str()) + if is_close_tag(tag) { + temp_int = stack.peek() + temp_string = tag.name[1..] + for !is_null(temp_int) && temp_string != tag_list[temp_int].name + && !tag_list[temp_int].closed { + dom.print_debug(temp_string + ' >> ' + tag_list[temp_int].name + ' ' + + (temp_string == tag_list[temp_int].name).str()) + stack.pop() + temp_int = stack.peek() + } + temp_int = stack.peek() + temp_int = if !is_null(temp_int) { stack.pop() } else { root_index } + if is_null(temp_int) { + stack.push(root_index) + } + dom.print_debug('Removed ' + temp_string + ' -- ' + tag_list[temp_int].name) + } else if tag.name.len > 0 { + dom.add_tag_attribute(tag) // error here + dom.add_tag_by_attribute(tag) + dom.add_tag_by_type(tag) + dom.all_tags << tag + temp_int = stack.peek() + if !is_null(temp_int) { + dom.btree.move_pointer(temp_map[temp_int.str()]) + temp_map[index.str()] = dom.btree.add_children(tag) + mut temp_tag := tag_list[temp_int] + position_in_parent := temp_tag.add_child(tag) // tag_list[temp_int] = temp_tag + tag.add_parent(temp_tag, position_in_parent) + /* + dom.print_debug("Added ${tag.name} as child of '" + tag_list[temp_int].name + + "' which now has ${dom.btree.get_children().len} childrens") + */ + dom.print_debug("Added $tag.name as child of '" + temp_tag.name + + "' which now has $temp_tag.children.len childrens") + } else { // dom.new_root(tag) + stack.push(root_index) + } + temp_string = '/' + tag.name + if temp_string in dom.close_tags && !tag.closed { // if tag ends with /> + dom.print_debug('Pushed ' + temp_string) + stack.push(index) + } + } + } // println(tag_list[root_index]) for debug purposes + dom.root = tag_list[0] +} + +// get_tag_by_attribute_value retrieves all the tags in the document that has the given attribute name and value. +pub fn (mut dom DocumentObjectModel) get_tag_by_attribute_value(name string, value string) []&Tag { + location := dom.where_is(value, name) + return if dom.tag_attributes[name].len > location { + dom.tag_attributes[name][location] + } else { + []&Tag{} + } +} + +// get_tag retrieves all the tags in the document that has the given tag name. +pub fn (dom DocumentObjectModel) get_tag(name string) []&Tag { + return if name in dom.tag_type { dom.tag_type[name] } else { []&Tag{} } +} + +// get_tag_by_attribute retrieves all the tags in the document that has the given attribute name. +pub fn (dom DocumentObjectModel) get_tag_by_attribute(name string) []&Tag { + return if name in dom.all_attributes { dom.all_attributes[name] } else { []&Tag{} } +} + +// get_root returns the root of the document. +pub fn (dom DocumentObjectModel) get_root() &Tag { + return dom.root +} + +// get_tags returns all of the tags stored in the document. +pub fn (dom DocumentObjectModel) get_tags() []&Tag { + return dom.all_tags +} diff --git a/v_windows/v/vlib/net/html/dom_test.v b/v_windows/v/vlib/net/html/dom_test.v new file mode 100644 index 0000000..d4fd292 --- /dev/null +++ b/v_windows/v/vlib/net/html/dom_test.v @@ -0,0 +1,56 @@ +module html + +import strings + +fn generate_temp_html() string { + mut temp_html := strings.new_builder(200) + temp_html.write_string('Giant String') + for counter := 0; counter < 4; counter++ { + temp_html.write_string("
Look at $counter
") + } + temp_html.write_string('') + return temp_html.str() +} + +fn test_search_by_tag_type() { + dom := parse(generate_temp_html()) + assert dom.get_tag('div').len == 4 + assert dom.get_tag('head').len == 1 + assert dom.get_tag('body').len == 1 +} + +fn test_search_by_attribute_value() { + mut dom := parse(generate_temp_html()) + // println(temp_html) + print('Amount ') + println(dom.get_tag_by_attribute_value('id', 'name_0')) + assert dom.get_tag_by_attribute_value('id', 'name_0').len == 1 +} + +fn test_access_parent() { + mut dom := parse(generate_temp_html()) + div_tags := dom.get_tag('div') + parent := div_tags[0].parent + assert parent != 0 + for div_tag in div_tags { + assert div_tag.parent == parent + } +} + +fn test_search_by_attributes() { + dom := parse(generate_temp_html()) + assert dom.get_tag_by_attribute('id').len == 4 +} + +fn test_tags_used() { + dom := parse(generate_temp_html()) + assert dom.get_tags().len == 9 +} + +fn test_access_tag_fields() { + dom := parse(generate_temp_html()) + id_tags := dom.get_tag_by_attribute('id') + assert id_tags[0].name == 'div' + assert id_tags[1].attributes['class'] == 'several-1' +} diff --git a/v_windows/v/vlib/net/html/html.v b/v_windows/v/vlib/net/html/html.v new file mode 100644 index 0000000..293b643 --- /dev/null +++ b/v_windows/v/vlib/net/html/html.v @@ -0,0 +1,18 @@ +module html + +import os + +// parse parses and returns the DOM from the given text. +pub fn parse(text string) DocumentObjectModel { + mut parser := Parser{} + parser.parse_html(text) + return parser.get_dom() +} + +// parse_file parses and returns the DOM from the contents of a file. +pub fn parse_file(filename string) DocumentObjectModel { + content := os.read_file(filename) or { return DocumentObjectModel{ + root: &Tag{} + } } + return parse(content) +} diff --git a/v_windows/v/vlib/net/html/html_test.v b/v_windows/v/vlib/net/html/html_test.v new file mode 100644 index 0000000..51271cd --- /dev/null +++ b/v_windows/v/vlib/net/html/html_test.v @@ -0,0 +1,15 @@ +module html + +fn test_parse() { + doc := parse('

Hello world!

') + tags := doc.get_tag('h1') + assert tags.len == 1 + h1_tag := tags[0] //

Hello world!

+ assert h1_tag.name == 'h1' + assert h1_tag.content == 'Hello world!' + assert h1_tag.attributes.len == 2 + // TODO: do not remove. Attributes must not have an empty attr. + // assert h1_tag.attributes.len == 1 + assert h1_tag.str() == '

Hello world!

' + // assert h1_tag.str() == '

Hello world!

' +} diff --git a/v_windows/v/vlib/net/html/parser.v b/v_windows/v/vlib/net/html/parser.v new file mode 100644 index 0000000..5b9bbd1 --- /dev/null +++ b/v_windows/v/vlib/net/html/parser.v @@ -0,0 +1,260 @@ +module html + +import os +import strings + +struct LexicalAttributes { +mut: + current_tag &Tag + open_tag bool + open_code bool + open_string int + open_comment bool + is_attribute bool + opened_code_type string + line_count int + lexeme_builder strings.Builder = strings.new_builder(100) + code_tags map[string]bool = { + 'script': true + 'style': true + } +} + +// Parser is responsible for reading the HTML strings and converting them into a `DocumentObjectModel`. +pub struct Parser { +mut: + dom DocumentObjectModel + lexical_attributes LexicalAttributes = LexicalAttributes{ + current_tag: &Tag{} + } + filename string = 'direct-parse' + initialized bool + tags []&Tag + debug_file os.File +} + +// This function is used to add a tag for the parser ignore it's content. +// For example, if you have an html or XML with a custom tag, like `' + parser.parse_html(temp_html) + assert parser.tags[2].content.len == script_content.replace('\n', '').len +} diff --git a/v_windows/v/vlib/net/html/tag.v b/v_windows/v/vlib/net/html/tag.v new file mode 100644 index 0000000..62260c0 --- /dev/null +++ b/v_windows/v/vlib/net/html/tag.v @@ -0,0 +1,68 @@ +module html + +import strings + +enum CloseTagType { + in_name + new_tag +} + +// Tag holds the information of an HTML tag. +[heap] +pub struct Tag { +pub mut: + name string + content string + children []&Tag + attributes map[string]string // attributes will be like map[name]value + last_attribute string + parent &Tag = 0 + position_in_parent int + closed bool + close_type CloseTagType = .in_name +} + +fn (mut tag Tag) add_parent(t &Tag, position int) { + tag.position_in_parent = position + tag.parent = t +} + +fn (mut tag Tag) add_child(t &Tag) int { + tag.children << t + return tag.children.len +} + +// text returns the text contents of the tag. +pub fn (tag Tag) text() string { + if tag.name.len >= 2 && tag.name[..2] == 'br' { + return '\n' + } + mut text_str := strings.new_builder(200) + text_str.write_string(tag.content.replace('\n', '')) + for child in tag.children { + text_str.write_string(child.text()) + } + return text_str.str() +} + +pub fn (tag &Tag) str() string { + mut html_str := strings.new_builder(200) + html_str.write_string('<$tag.name') + for key, value in tag.attributes { + html_str.write_string(' $key') + if value.len > 0 { + html_str.write_string('="$value"') + } + } + html_str.write_string(if tag.closed && tag.close_type == .in_name { '/>' } else { '>' }) + html_str.write_string(tag.content) + if tag.children.len > 0 { + for child in tag.children { + html_str.write_string(child.str()) + } + } + if !tag.closed || tag.close_type == .new_tag { + html_str.write_string('') + } + return html_str.str() +} diff --git a/v_windows/v/vlib/net/http/backend_nix.c.v b/v_windows/v/vlib/net/http/backend_nix.c.v new file mode 100644 index 0000000..1243442 --- /dev/null +++ b/v_windows/v/vlib/net/http/backend_nix.c.v @@ -0,0 +1,74 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import strings +import net.openssl + +const ( + is_used = openssl.is_used +) + +fn (req &Request) ssl_do(port int, method Method, host_name string, path string) ?Response { + // ssl_method := C.SSLv23_method() + ctx := C.SSL_CTX_new(C.TLS_method()) + C.SSL_CTX_set_verify_depth(ctx, 4) + flags := C.SSL_OP_NO_SSLv2 | C.SSL_OP_NO_SSLv3 | C.SSL_OP_NO_COMPRESSION + C.SSL_CTX_set_options(ctx, flags) + mut res := C.SSL_CTX_load_verify_locations(ctx, c'random-org-chain.pem', 0) + web := C.BIO_new_ssl_connect(ctx) + addr := host_name + ':' + port.str() + res = C.BIO_set_conn_hostname(web, addr.str) + ssl := &openssl.SSL(0) + C.BIO_get_ssl(web, &ssl) + preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4' + res = C.SSL_set_cipher_list(voidptr(ssl), &char(preferred_ciphers.str)) + if res != 1 { + println('http: openssl: cipher failed') + } + res = C.SSL_set_tlsext_host_name(voidptr(ssl), host_name.str) + res = C.BIO_do_connect(web) + if res != 1 { + return error('cannot connect the endpoint') + } + res = C.BIO_do_handshake(web) + C.SSL_get_peer_certificate(voidptr(ssl)) + res = C.SSL_get_verify_result(voidptr(ssl)) + // ///// + req_headers := req.build_request_headers(method, host_name, path) + $if trace_http_request ? { + eprintln('> $req_headers') + } + // println(req_headers) + C.BIO_puts(web, &char(req_headers.str)) + mut content := strings.new_builder(100) + mut buff := [bufsize]byte{} + bp := unsafe { &buff[0] } + mut readcounter := 0 + for { + readcounter++ + len := unsafe { C.BIO_read(web, bp, bufsize) } + if len <= 0 { + break + } + $if debug_http ? { + eprintln('ssl_do, read ${readcounter:4d} | len: $len') + eprintln('-'.repeat(20)) + eprintln(unsafe { tos(bp, len) }) + eprintln('-'.repeat(20)) + } + unsafe { content.write_ptr(bp, len) } + } + if web != 0 { + C.BIO_free_all(web) + } + if ctx != 0 { + C.SSL_CTX_free(ctx) + } + response_text := content.str() + $if trace_http_response ? { + eprintln('< $response_text') + } + return parse_response(response_text) +} diff --git a/v_windows/v/vlib/net/http/backend_windows.c.v b/v_windows/v/vlib/net/http/backend_windows.c.v new file mode 100644 index 0000000..9181166 --- /dev/null +++ b/v_windows/v/vlib/net/http/backend_windows.c.v @@ -0,0 +1,28 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +#flag windows -I @VEXEROOT/thirdparty/vschannel +#flag -l ws2_32 -l crypt32 -l secur32 -l user32 +#include "vschannel.c" + +fn C.new_tls_context() C.TlsContext + +fn (req &Request) ssl_do(port int, method Method, host_name string, path string) ?Response { + mut ctx := C.new_tls_context() + C.vschannel_init(&ctx) + mut buff := unsafe { malloc_noscan(C.vsc_init_resp_buff_size) } + addr := host_name + sdata := req.build_request_headers(method, host_name, path) + $if trace_http_request ? { + eprintln('> $sdata') + } + length := C.request(&ctx, port, addr.to_wide(), sdata.str, &buff) + C.vschannel_cleanup(&ctx) + response_text := unsafe { buff.vstring_with_len(length) } + $if trace_http_response ? { + eprintln('< $response_text') + } + return parse_response(response_text) +} diff --git a/v_windows/v/vlib/net/http/chunked/dechunk.v b/v_windows/v/vlib/net/http/chunked/dechunk.v new file mode 100644 index 0000000..0e82586 --- /dev/null +++ b/v_windows/v/vlib/net/http/chunked/dechunk.v @@ -0,0 +1,72 @@ +module chunked + +import strings +// See: https://en.wikipedia.org/wiki/Chunked_transfer_encoding +// ///////////////////////////////////////////////////////////// +// The chunk size is transferred as a hexadecimal number +// followed by \r\n as a line separator, +// followed by a chunk of data of the given size. +// The end is marked with a chunk with size 0. + +struct ChunkScanner { +mut: + pos int + text string +} + +fn (mut s ChunkScanner) read_chunk_size() int { + mut n := 0 + for { + if s.pos >= s.text.len { + break + } + c := s.text[s.pos] + if !c.is_hex_digit() { + break + } + n = n << 4 + n += int(unhex(c)) + s.pos++ + } + return n +} + +fn unhex(c byte) byte { + if `0` <= c && c <= `9` { + return c - `0` + } else if `a` <= c && c <= `f` { + return c - `a` + 10 + } else if `A` <= c && c <= `F` { + return c - `A` + 10 + } + return 0 +} + +fn (mut s ChunkScanner) skip_crlf() { + s.pos += 2 +} + +fn (mut s ChunkScanner) read_chunk(chunksize int) string { + startpos := s.pos + s.pos += chunksize + return s.text[startpos..s.pos] +} + +pub fn decode(text string) string { + mut sb := strings.new_builder(100) + mut cscanner := ChunkScanner{ + pos: 0 + text: text + } + for { + csize := cscanner.read_chunk_size() + if 0 == csize { + break + } + cscanner.skip_crlf() + sb.write_string(cscanner.read_chunk(csize)) + cscanner.skip_crlf() + } + cscanner.skip_crlf() + return sb.str() +} diff --git a/v_windows/v/vlib/net/http/cookie.v b/v_windows/v/vlib/net/http/cookie.v new file mode 100644 index 0000000..d647b3d --- /dev/null +++ b/v_windows/v/vlib/net/http/cookie.v @@ -0,0 +1,413 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import time +import strings + +pub struct Cookie { +pub mut: + name string + value string + path string // optional + domain string // optional + expires time.Time // optional + raw_expires string // for reading cookies only. optional. + // max_age=0 means no 'Max-Age' attribute specified. + // max_age<0 means delete cookie now, equivalently 'Max-Age: 0' + // max_age>0 means Max-Age attribute present and given in seconds + max_age int + secure bool + http_only bool + same_site SameSite + raw string + unparsed []string // Raw text of unparsed attribute-value pairs +} + +// SameSite allows a server to define a cookie attribute making it impossible for +// the browser to send this cookie along with cross-site requests. The main +// goal is to mitigate the risk of cross-origin information leakage, and provide +// some protection against cross-site request forgery attacks. +// +// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. +pub enum SameSite { + same_site_default_mode = 1 + same_site_lax_mode + same_site_strict_mode + same_site_none_mode +} + +// Parses all "Set-Cookie" values from the header `h` and +// returns the successfully parsed Cookies. +pub fn read_set_cookies(h map[string][]string) []&Cookie { + cookies_s := h['Set-Cookie'] + cookie_count := cookies_s.len + if cookie_count == 0 { + return [] + } + mut cookies := []&Cookie{} + for _, line in cookies_s { + c := parse_cookie(line) or { continue } + cookies << &c + } + return cookies +} + +// Parses all "Cookie" values from the header `h` and +// returns the successfully parsed Cookies. +// +// if `filter` isn't empty, only cookies of that name are returned +pub fn read_cookies(h map[string][]string, filter string) []&Cookie { + lines := h['Cookie'] + if lines.len == 0 { + return [] + } + mut cookies := []&Cookie{} + for _, line_ in lines { + mut line := line_.trim_space() + mut part := '' + for line.len > 0 { + if line.index_any(';') > 0 { + line_parts := line.split(';') + part = line_parts[0] + line = line_parts[1] + } else { + part = line + line = '' + } + part = part.trim_space() + if part.len == 0 { + continue + } + mut name := part + mut val := '' + if part.contains('=') { + val_parts := part.split('=') + name = val_parts[0] + val = val_parts[1] + } + if !is_cookie_name_valid(name) { + continue + } + if filter != '' && filter != name { + continue + } + val = parse_cookie_value(val, true) or { continue } + cookies << &Cookie{ + name: name + value: val + } + } + } + return cookies +} + +// Returns the serialization of the cookie for use in a Cookie header +// (if only Name and Value are set) or a Set-Cookie response +// header (if other fields are set). +// +// If c.name is invalid, the empty string is returned. +pub fn (c &Cookie) str() string { + if !is_cookie_name_valid(c.name) { + return '' + } + // extra_cookie_length derived from typical length of cookie attributes + // see RFC 6265 Sec 4.1. + extra_cookie_length := 110 + mut b := strings.new_builder(c.name.len + c.value.len + c.domain.len + c.path.len + + extra_cookie_length) + b.write_string(c.name) + b.write_string('=') + b.write_string(sanitize_cookie_value(c.value)) + if c.path.len > 0 { + b.write_string('; path=') + b.write_string(sanitize_cookie_path(c.path)) + } + if c.domain.len > 0 { + if valid_cookie_domain(c.domain) { + // A `domain` containing illegal characters is not + // sanitized but simply dropped which turns the cookie + // into a host-only cookie. A leading dot is okay + // but won't be sent. + mut d := c.domain + if d[0] == `.` { + d = d.substr(1, d.len) + } + b.write_string('; domain=') + b.write_string(d) + } else { + // TODO: Log invalid cookie domain warning + } + } + if c.expires.year > 1600 { + e := c.expires + time_str := '$e.weekday_str(), $e.day.str() $e.smonth() $e.year $e.hhmmss() GMT' + b.write_string('; expires=') + b.write_string(time_str) + } + // TODO: Fix this. Techically a max age of 0 or less should be 0 + // We need a way to not have a max age. + if c.max_age > 0 { + b.write_string('; Max-Age=') + b.write_string(c.max_age.str()) + } else if c.max_age < 0 { + b.write_string('; Max-Age=0') + } + if c.http_only { + b.write_string('; HttpOnly') + } + if c.secure { + b.write_string('; Secure') + } + match c.same_site { + .same_site_default_mode { + b.write_string('; SameSite') + } + .same_site_none_mode { + b.write_string('; SameSite=None') + } + .same_site_lax_mode { + b.write_string('; SameSite=Lax') + } + .same_site_strict_mode { + b.write_string('; SameSite=Strict') + } + } + return b.str() +} + +fn sanitize(valid fn (byte) bool, v string) string { + mut ok := true + for i in 0 .. v.len { + if valid(v[i]) { + continue + } + // TODO: Warn that we're dropping the invalid byte? + ok = false + break + } + if ok { + return v.clone() + } + return v.bytes().filter(valid(it)).bytestr() +} + +fn sanitize_cookie_name(name string) string { + return name.replace_each(['\n', '-', '\r', '-']) +} + +// https://tools.ietf.org/html/rfc6265#section-4.1.1 +// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) +// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E +// ; US-ASCII characters excluding CTLs, +// ; whitespace DQUOTE, comma, semicolon, +// ; and backslash +// We loosen this as spaces and commas are common in cookie values +// but we produce a quoted cookie-value in when value starts or ends +// with a comma or space. +pub fn sanitize_cookie_value(v string) string { + val := sanitize(valid_cookie_value_byte, v) + if v.len == 0 { + return v + } + // Check for the existence of a space or comma + if val.starts_with(' ') || val.ends_with(' ') || val.starts_with(',') || val.ends_with(',') { + return '"$v"' + } + return v +} + +fn sanitize_cookie_path(v string) string { + return sanitize(valid_cookie_path_byte, v) +} + +fn valid_cookie_value_byte(b byte) bool { + return 0x20 <= b && b < 0x7f && b != `"` && b != `;` && b != `\\` +} + +fn valid_cookie_path_byte(b byte) bool { + return 0x20 <= b && b < 0x7f && b != `!` +} + +fn valid_cookie_domain(v string) bool { + if is_cookie_domain_name(v) { + return true + } + // TODO + // valid_ip := net.parse_ip(v) or { + // false + // } + // if valid_ip { + // return true + // } + return false +} + +pub fn is_cookie_domain_name(_s string) bool { + mut s := _s + if s.len == 0 { + return false + } + if s.len > 255 { + return false + } + if s[0] == `.` { + s = s.substr(1, s.len) + } + mut last := `.` + mut ok := false + mut part_len := 0 + for i, _ in s { + c := s[i] + if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) { + // No '_' allowed here (in contrast to package net). + ok = true + part_len++ + } else if `0` <= c && c <= `9` { + // fine + part_len++ + } else if c == `-` { + // Byte before dash cannot be dot. + if last == `.` { + return false + } + part_len++ + } else if c == `.` { + // Byte before dot cannot be dot, dash. + if last == `.` || last == `-` { + return false + } + if part_len > 63 || part_len == 0 { + return false + } + part_len = 0 + } else { + return false + } + last = c + } + if last == `-` || part_len > 63 { + return false + } + return ok +} + +fn parse_cookie_value(_raw string, allow_double_quote bool) ?string { + mut raw := _raw + // Strip the quotes, if present + if allow_double_quote && raw.len > 1 && raw[0] == `"` && raw[raw.len - 1] == `"` { + raw = raw.substr(1, raw.len - 1) + } + for i in 0 .. raw.len { + if !valid_cookie_value_byte(raw[i]) { + return error('http.cookie: invalid cookie value') + } + } + return raw +} + +fn is_cookie_name_valid(name string) bool { + if name == '' { + return false + } + for b in name { + if b < 33 || b > 126 { + return false + } + } + return true +} + +fn parse_cookie(line string) ?Cookie { + mut parts := line.trim_space().split(';') + if parts.len == 1 && parts[0] == '' { + return error('malformed cookie') + } + parts[0] = parts[0].trim_space() + keyval := parts[0].split('=') + if keyval.len != 2 { + return error('malformed cookie') + } + name := keyval[0] + raw_value := keyval[1] + if !is_cookie_name_valid(name) { + return error('malformed cookie') + } + value := parse_cookie_value(raw_value, true) or { return error('malformed cookie') } + mut c := Cookie{ + name: name + value: value + raw: line + } + for i, _ in parts { + parts[i] = parts[i].trim_space() + if parts[i].len == 0 { + continue + } + mut attr := parts[i] + mut raw_val := '' + if attr.contains('=') { + pieces := attr.split('=') + attr = pieces[0] + raw_val = pieces[1] + } + lower_attr := attr.to_lower() + val := parse_cookie_value(raw_val, false) or { + c.unparsed << parts[i] + continue + } + match lower_attr { + 'samesite' { + lower_val := val.to_lower() + match lower_val { + 'lax' { c.same_site = .same_site_lax_mode } + 'strict' { c.same_site = .same_site_strict_mode } + 'none' { c.same_site = .same_site_none_mode } + else { c.same_site = .same_site_default_mode } + } + } + 'secure' { + c.secure = true + continue + } + 'httponly' { + c.http_only = true + continue + } + 'domain' { + c.domain = val + continue + } + 'max-age' { + mut secs := val.int() + if secs != 0 && val[0] != `0` { + break + } + if secs <= 0 { + secs = -1 + } + c.max_age = secs + continue + } + // TODO: Fix this once time works better + // 'expires' { + // c.raw_expires = val + // mut exptime := time.parse_iso(val) + // if exptime.year == 0 { + // exptime = time.parse_iso('Mon, 02-Jan-2006 15:04:05 MST') + // } + // c.expires = exptime + // continue + // } + 'path' { + c.path = val + continue + } + else { + c.unparsed << parts[i] + } + } + } + return c +} diff --git a/v_windows/v/vlib/net/http/cookie_test.v b/v_windows/v/vlib/net/http/cookie_test.v new file mode 100644 index 0000000..6a0c0cd --- /dev/null +++ b/v_windows/v/vlib/net/http/cookie_test.v @@ -0,0 +1,468 @@ +import net.http + +struct SetCookieTestCase { + cookie &http.Cookie + raw string +} + +struct ReadSetCookiesTestCase { + header map[string][]string + cookies []&http.Cookie +} + +struct AddCookieTestCase { + cookie []&http.Cookie + raw string +} + +const ( + write_set_cookie_tests = [ + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-1' + value: 'v1' + } + raw: 'cookie-1=v1' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-2' + value: 'two' + max_age: 3600 + } + raw: 'cookie-2=two; Max-Age=3600' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-3' + value: 'three' + domain: '.example.com' + } + raw: 'cookie-3=three; domain=example.com' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-4' + value: 'four' + path: '/restricted/' + } + raw: 'cookie-4=four; path=/restricted/' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-5' + value: 'five' + domain: 'wrong;bad.abc' + } + raw: 'cookie-5=five' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-6' + value: 'six' + domain: 'bad-.abc' + } + raw: 'cookie-6=six' + }, + // SetCookieTestCase{ + // cookie: &http.Cookie{name: 'cookie-7', value: 'seven', domain: '127.0.0.1'}, + // raw: 'cookie-7=seven; domain=127.0.0.1' + // }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-8' + value: 'eight' + domain: '::1' + } + raw: 'cookie-8=eight' + }, + // { + // cookie: &http.Cookie{name: 'cookie-9', value: 'expiring', expires: time.unix(1257894000, 0)}, + // 'cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT', + // }, + // According to IETF 6265 Section 5.1.1.5, the year cannot be less than 1601 + // SetCookieTestCase{ + // cookie: &http.Cookie{name: 'cookie-10', value: 'expiring-1601', expires: time.parse('Mon, 01 Jan 1601 01:01:01 GMT')}, + // raw: 'cookie-10=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT' + // }, + // SetCookieTestCase{ + // cookie: &http.Cookie{name: 'cookie-11', value: 'invalid-expiry', expires: time.parse('Mon, 01 Jan 1600 01:01:01 GMT')}, + // raw: 'cookie-11=invalid-expiry' + // }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-12' + value: 'samesite-default' + same_site: .same_site_default_mode + } + raw: 'cookie-12=samesite-default; SameSite' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-13' + value: 'samesite-lax' + same_site: .same_site_lax_mode + } + raw: 'cookie-13=samesite-lax; SameSite=Lax' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-14' + value: 'samesite-strict' + same_site: .same_site_strict_mode + } + raw: 'cookie-14=samesite-strict; SameSite=Strict' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'cookie-15' + value: 'samesite-none' + same_site: .same_site_none_mode + } + raw: 'cookie-15=samesite-none; SameSite=None' + }, + // The 'special' cookies have values containing commas or spaces which + // are disallowed by RFC 6265 but are common in the wild. + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-1' + value: 'a z' + } + raw: 'special-1=a z' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-2' + value: ' z' + } + raw: 'special-2=" z"' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-3' + value: 'a ' + } + raw: 'special-3="a "' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-4' + value: ' ' + } + raw: 'special-4=" "' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-5' + value: 'a,z' + } + raw: 'special-5=a,z' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-6' + value: ',z' + } + raw: 'special-6=",z"' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-7' + value: 'a,' + } + raw: 'special-7="a,"' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'special-8' + value: ',' + } + raw: 'special-8=","' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'empty-value' + value: '' + } + raw: 'empty-value=' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: '' + } + raw: '' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: '\t' + } + raw: '' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: '\r' + } + raw: '' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'a\nb' + value: 'v' + } + raw: '' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'a\nb' + value: 'v' + } + raw: '' + }, + SetCookieTestCase{ + cookie: &http.Cookie{ + name: 'a\rb' + value: 'v' + } + raw: '' + }, + ] + add_cookies_tests = [ + AddCookieTestCase{ + cookie: [] + raw: '' + }, + AddCookieTestCase{ + cookie: [&http.Cookie{ + name: 'cookie-1' + value: 'v1' + }] + raw: 'cookie-1=v1' + }, + AddCookieTestCase{ + cookie: [&http.Cookie{ + name: 'cookie-1' + value: 'v1' + }, + &http.Cookie{ + name: 'cookie-2' + value: 'v2' + }, + &http.Cookie{ + name: 'cookie-3' + value: 'v3' + }, + ] + raw: 'cookie-1=v1; cookie-2=v2; cookie-3=v3' + }, + ] + read_set_cookies_tests = [ + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['Cookie-1=v1'] + } + cookies: [&http.Cookie{ + name: 'Cookie-1' + value: 'v1' + raw: 'Cookie-1=v1' + }] + }, + // ReadSetCookiesTestCase{ + // header: {"Set-Cookie": ["NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"]}, + // cookies: [&http.Cookie{ + // name: "NID", + // value: "99=YsDT5i3E-CXax-", + // path: "/", + // domain: ".google.ch", + // http_only: true, + // expires: time.parse_iso('Wed, 23-Nov-2011 01:05:03 GMT'), + // raw_expires: "Wed, 23-Nov-2011 01:05:03 GMT", + // raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly" + // }] + // }, + // ReadSetCookiesTestCase{ + // header: {"Set-Cookie": [".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"]}, + // cookies: [&http.Cookie{ + // name: ".ASPXAUTH", + // value: "7E3AA", + // path: "/", + // expires: time.parse_iso('Wed, 07-Mar-2012 14:25:06 GMT'), + // raw_expires: "Wed, 07-Mar-2012 14:25:06 GMT", + // http_only: true, + // raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly" + // }] + // }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['ASP.NET_SessionId=foo; path=/; HttpOnly'] + } + cookies: [ + &http.Cookie{ + name: 'ASP.NET_SessionId' + value: 'foo' + path: '/' + http_only: true + raw: 'ASP.NET_SessionId=foo; path=/; HttpOnly' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['samesitedefault=foo; SameSite'] + } + cookies: [ + &http.Cookie{ + name: 'samesitedefault' + value: 'foo' + same_site: .same_site_default_mode + raw: 'samesitedefault=foo; SameSite' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['samesitelax=foo; SameSite=Lax'] + } + cookies: [ + &http.Cookie{ + name: 'samesitelax' + value: 'foo' + same_site: .same_site_lax_mode + raw: 'samesitelax=foo; SameSite=Lax' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['samesitestrict=foo; SameSite=Strict'] + } + cookies: [ + &http.Cookie{ + name: 'samesitestrict' + value: 'foo' + same_site: .same_site_strict_mode + raw: 'samesitestrict=foo; SameSite=Strict' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['samesitenone=foo; SameSite=None'] + } + cookies: [ + &http.Cookie{ + name: 'samesitenone' + value: 'foo' + same_site: .same_site_none_mode + raw: 'samesitenone=foo; SameSite=None' + }, + ] + }, + // Make sure we can properly read back the Set-Cookie headers we create + // for values containing spaces or commas: + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-1=a z'] + } + cookies: [ + &http.Cookie{ + name: 'special-1' + value: 'a z' + raw: 'special-1=a z' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-2=" z"'] + } + cookies: [ + &http.Cookie{ + name: 'special-2' + value: ' z' + raw: 'special-2=" z"' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-3="a "'] + } + cookies: [ + &http.Cookie{ + name: 'special-3' + value: 'a ' + raw: 'special-3="a "' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-4=" "'] + } + cookies: [ + &http.Cookie{ + name: 'special-4' + value: ' ' + raw: 'special-4=" "' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-5=a,z'] + } + cookies: [ + &http.Cookie{ + name: 'special-5' + value: 'a,z' + raw: 'special-5=a,z' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-6=",z"'] + } + cookies: [ + &http.Cookie{ + name: 'special-6' + value: ',z' + raw: 'special-6=",z"' + }, + ] + }, + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-7=","'] + } + cookies: [ + &http.Cookie{ + name: 'special-7' + value: ',' + raw: 'special-8=","' + }, + ] + } + // TODO(bradfitz): users have reported seeing this in the + // wild, but do browsers handle it? RFC 6265 just says "don't + // do that" (section 3) and then never mentions header folding + // again. + // Header{"Set-Cookie": ["ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"]}, + ] +) + +fn test_write_set_cookies() { + for _, tt in write_set_cookie_tests { + assert tt.cookie.str() == tt.raw + } +} + +fn test_read_set_cookies() { + for _, tt in read_set_cookies_tests { + h := tt.header['Set-Cookie'][0] + c := http.read_set_cookies(tt.header) + println(h) + println(c[0].str()) + assert c[0].str() == h + } +} diff --git a/v_windows/v/vlib/net/http/download.v b/v_windows/v/vlib/net/http/download.v new file mode 100644 index 0000000..455c1e0 --- /dev/null +++ b/v_windows/v/vlib/net/http/download.v @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import os + +pub fn download_file(url string, out string) ? { + $if debug_http ? { + println('download file url=$url out=$out') + } + s := get(url) or { return err } + if s.status() != .ok { + return error('received http code $s.status_code') + } + os.write_file(out, s.text) ? + // download_file_with_progress(url, out, empty, empty) +} diff --git a/v_windows/v/vlib/net/http/download_nix.c.v b/v_windows/v/vlib/net/http/download_nix.c.v new file mode 100644 index 0000000..724a256 --- /dev/null +++ b/v_windows/v/vlib/net/http/download_nix.c.v @@ -0,0 +1,52 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +type DownloadFn = fn (written int) + +/* +struct DownloadStruct { +mut: + stream voidptr + written int + cb DownloadFn +} +*/ +fn download_cb(ptr voidptr, size size_t, nmemb size_t, userp voidptr) { + /* + mut data := &DownloadStruct(userp) + written := C.fwrite(ptr, size, nmemb, data.stream) + data.written += written + data.cb(data.written) + //#data->cb(data->written); // TODO + return written + */ +} + +pub fn download_file_with_progress(url string, out string, cb DownloadFn, cb_finished fn ()) { + /* + curl := C.curl_easy_init() + if isnil(curl) { + return + } + cout := out.str + fp := C.fopen(cout, 'wb') + C.curl_easy_setopt(curl, CURLOPT_URL, url.str) + C.curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_cb) + data := &DownloadStruct { + stream:fp + cb: cb + } + C.curl_easy_setopt(curl, CURLOPT_WRITEDATA, data) + mut d := 0.0 + C.curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d) + C.curl_easy_perform(curl) + C.curl_easy_cleanup(curl) + C.fclose(fp) + cb_finished() + */ +} + +fn empty() { +} diff --git a/v_windows/v/vlib/net/http/download_windows.c.v b/v_windows/v/vlib/net/http/download_windows.c.v new file mode 100644 index 0000000..422b6da --- /dev/null +++ b/v_windows/v/vlib/net/http/download_windows.c.v @@ -0,0 +1,29 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module http + +#flag -l urlmon + +#include + +fn download_file_with_progress(url string, out string, cb voidptr, cb_finished voidptr) { +} + +/* +pub fn download_file(url, out string) { + C.URLDownloadToFile(0, url.to_wide(), out.to_wide(), 0, 0) + /* + if (res == S_OK) { + println('Download Ok') + # } else if(res == E_OUTOFMEMORY) { + println('Buffer length invalid, or insufficient memory') + # } else if(res == INET_E_DOWNLOAD_FAILURE) { + println('URL is invalid') + # } else { + # printf("Download error: %d\n", res); + # } + */ +} +*/ diff --git a/v_windows/v/vlib/net/http/header.v b/v_windows/v/vlib/net/http/header.v new file mode 100644 index 0000000..c05bdbc --- /dev/null +++ b/v_windows/v/vlib/net/http/header.v @@ -0,0 +1,698 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import strings + +// CommonHeader is an enum of the most common HTTP headers +pub enum CommonHeader { + accept + accept_ch + accept_charset + accept_ch_lifetime + accept_encoding + accept_language + accept_patch + accept_post + accept_ranges + access_control_allow_credentials + access_control_allow_headers + access_control_allow_methods + access_control_allow_origin + access_control_expose_headers + access_control_max_age + access_control_request_headers + access_control_request_method + age + allow + alt_svc + authorization + cache_control + clear_site_data + connection + content_disposition + content_encoding + content_language + content_length + content_location + content_range + content_security_policy + content_security_policy_report_only + content_type + cookie + cross_origin_embedder_policy + cross_origin_opener_policy + cross_origin_resource_policy + date + device_memory + digest + dnt + early_data + etag + expect + expect_ct + expires + feature_policy + forwarded + from + host + if_match + if_modified_since + if_none_match + if_range + if_unmodified_since + index + keep_alive + large_allocation + last_modified + link + location + nel + origin + pragma + proxy_authenticate + proxy_authorization + range + referer + referrer_policy + retry_after + save_data + sec_fetch_dest + sec_fetch_mode + sec_fetch_site + sec_fetch_user + sec_websocket_accept + server + server_timing + set_cookie + sourcemap + strict_transport_security + te + timing_allow_origin + tk + trailer + transfer_encoding + upgrade + upgrade_insecure_requests + user_agent + vary + via + want_digest + warning + www_authenticate + x_content_type_options + x_dns_prefetch_control + x_forwarded_for + x_forwarded_host + x_forwarded_proto + x_frame_options + x_xss_protection +} + +pub fn (h CommonHeader) str() string { + return match h { + .accept { 'Accept' } + .accept_ch { 'Accept-CH' } + .accept_charset { 'Accept-Charset' } + .accept_ch_lifetime { 'Accept-CH-Lifetime' } + .accept_encoding { 'Accept-Encoding' } + .accept_language { 'Accept-Language' } + .accept_patch { 'Accept-Patch' } + .accept_post { 'Accept-Post' } + .accept_ranges { 'Accept-Ranges' } + .access_control_allow_credentials { 'Access-Control-Allow-Credentials' } + .access_control_allow_headers { 'Access-Control-Allow-Headers' } + .access_control_allow_methods { 'Access-Control-Allow-Methods' } + .access_control_allow_origin { 'Access-Control-Allow-Origin' } + .access_control_expose_headers { 'Access-Control-Expose-Headers' } + .access_control_max_age { 'Access-Control-Max-Age' } + .access_control_request_headers { 'Access-Control-Request-Headers' } + .access_control_request_method { 'Access-Control-Request-Method' } + .age { 'Age' } + .allow { 'Allow' } + .alt_svc { 'Alt-Svc' } + .authorization { 'Authorization' } + .cache_control { 'Cache-Control' } + .clear_site_data { 'Clear-Site-Data' } + .connection { 'Connection' } + .content_disposition { 'Content-Disposition' } + .content_encoding { 'Content-Encoding' } + .content_language { 'Content-Language' } + .content_length { 'Content-Length' } + .content_location { 'Content-Location' } + .content_range { 'Content-Range' } + .content_security_policy { 'Content-Security-Policy' } + .content_security_policy_report_only { 'Content-Security-Policy-Report-Only' } + .content_type { 'Content-Type' } + .cookie { 'Cookie' } + .cross_origin_embedder_policy { 'Cross-Origin-Embedder-Policy' } + .cross_origin_opener_policy { 'Cross-Origin-Opener-Policy' } + .cross_origin_resource_policy { 'Cross-Origin-Resource-Policy' } + .date { 'Date' } + .device_memory { 'Device-Memory' } + .digest { 'Digest' } + .dnt { 'DNT' } + .early_data { 'Early-Data' } + .etag { 'ETag' } + .expect { 'Expect' } + .expect_ct { 'Expect-CT' } + .expires { 'Expires' } + .feature_policy { 'Feature-Policy' } + .forwarded { 'Forwarded' } + .from { 'From' } + .host { 'Host' } + .if_match { 'If-Match' } + .if_modified_since { 'If-Modified-Since' } + .if_none_match { 'If-None-Match' } + .if_range { 'If-Range' } + .if_unmodified_since { 'If-Unmodified-Since' } + .index { 'Index' } + .keep_alive { 'Keep-Alive' } + .large_allocation { 'Large-Allocation' } + .last_modified { 'Last-Modified' } + .link { 'Link' } + .location { 'Location' } + .nel { 'NEL' } + .origin { 'Origin' } + .pragma { 'Pragma' } + .proxy_authenticate { 'Proxy-Authenticate' } + .proxy_authorization { 'Proxy-Authorization' } + .range { 'Range' } + .referer { 'Referer' } + .referrer_policy { 'Referrer-Policy' } + .retry_after { 'Retry-After' } + .save_data { 'Save-Data' } + .sec_fetch_dest { 'Sec-Fetch-Dest' } + .sec_fetch_mode { 'Sec-Fetch-Mode' } + .sec_fetch_site { 'Sec-Fetch-Site' } + .sec_fetch_user { 'Sec-Fetch-User' } + .sec_websocket_accept { 'Sec-WebSocket-Accept' } + .server { 'Server' } + .server_timing { 'Server-Timing' } + .set_cookie { 'Set-Cookie' } + .sourcemap { 'SourceMap' } + .strict_transport_security { 'Strict-Transport-Security' } + .te { 'TE' } + .timing_allow_origin { 'Timing-Allow-Origin' } + .tk { 'Tk' } + .trailer { 'Trailer' } + .transfer_encoding { 'Transfer-Encoding' } + .upgrade { 'Upgrade' } + .upgrade_insecure_requests { 'Upgrade-Insecure-Requests' } + .user_agent { 'User-Agent' } + .vary { 'Vary' } + .via { 'Via' } + .want_digest { 'Want-Digest' } + .warning { 'Warning' } + .www_authenticate { 'WWW-Authenticate' } + .x_content_type_options { 'X-Content-Type-Options' } + .x_dns_prefetch_control { 'X-DNS-Prefetch-Control' } + .x_forwarded_for { 'X-Forwarded-For' } + .x_forwarded_host { 'X-Forwarded-Host' } + .x_forwarded_proto { 'X-Forwarded-Proto' } + .x_frame_options { 'X-Frame-Options' } + .x_xss_protection { 'X-XSS-Protection' } + } +} + +const common_header_map = { + 'accept': CommonHeader.accept + 'accept-ch': .accept_ch + 'accept-charset': .accept_charset + 'accept-ch-lifetime': .accept_ch_lifetime + 'accept-encoding': .accept_encoding + 'accept-language': .accept_language + 'accept-patch': .accept_patch + 'accept-post': .accept_post + 'accept-ranges': .accept_ranges + 'access-control-allow-credentials': .access_control_allow_credentials + 'access-control-allow-headers': .access_control_allow_headers + 'access-control-allow-methods': .access_control_allow_methods + 'access-control-allow-origin': .access_control_allow_origin + 'access-control-expose-headers': .access_control_expose_headers + 'access-control-max-age': .access_control_max_age + 'access-control-request-headers': .access_control_request_headers + 'access-control-request-method': .access_control_request_method + 'age': .age + 'allow': .allow + 'alt-svc': .alt_svc + 'authorization': .authorization + 'cache-control': .cache_control + 'clear-site-data': .clear_site_data + 'connection': .connection + 'content-disposition': .content_disposition + 'content-encoding': .content_encoding + 'content-language': .content_language + 'content-length': .content_length + 'content-location': .content_location + 'content-range': .content_range + 'content-security-policy': .content_security_policy + 'content-security-policy-report-only': .content_security_policy_report_only + 'content-type': .content_type + 'cookie': .cookie + 'cross-origin-embedder-policy': .cross_origin_embedder_policy + 'cross-origin-opener-policy': .cross_origin_opener_policy + 'cross-origin-resource-policy': .cross_origin_resource_policy + 'date': .date + 'device-memory': .device_memory + 'digest': .digest + 'dnt': .dnt + 'early-data': .early_data + 'etag': .etag + 'expect': .expect + 'expect-ct': .expect_ct + 'expires': .expires + 'feature-policy': .feature_policy + 'forwarded': .forwarded + 'from': .from + 'host': .host + 'if-match': .if_match + 'if-modified-since': .if_modified_since + 'if-none-match': .if_none_match + 'if-range': .if_range + 'if-unmodified-since': .if_unmodified_since + 'index': .index + 'keep-alive': .keep_alive + 'large-allocation': .large_allocation + 'last-modified': .last_modified + 'link': .link + 'location': .location + 'nel': .nel + 'origin': .origin + 'pragma': .pragma + 'proxy-authenticate': .proxy_authenticate + 'proxy-authorization': .proxy_authorization + 'range': .range + 'referer': .referer + 'referrer-policy': .referrer_policy + 'retry-after': .retry_after + 'save-data': .save_data + 'sec-fetch-dest': .sec_fetch_dest + 'sec-fetch-mode': .sec_fetch_mode + 'sec-fetch-site': .sec_fetch_site + 'sec-fetch-user': .sec_fetch_user + 'sec-websocket-accept': .sec_websocket_accept + 'server': .server + 'server-timing': .server_timing + 'set-cookie': .set_cookie + 'sourcemap': .sourcemap + 'strict-transport-security': .strict_transport_security + 'te': .te + 'timing-allow-origin': .timing_allow_origin + 'tk': .tk + 'trailer': .trailer + 'transfer-encoding': .transfer_encoding + 'upgrade': .upgrade + 'upgrade-insecure-requests': .upgrade_insecure_requests + 'user-agent': .user_agent + 'vary': .vary + 'via': .via + 'want-digest': .want_digest + 'warning': .warning + 'www-authenticate': .www_authenticate + 'x-content-type-options': .x_content_type_options + 'x-dns-prefetch-control': .x_dns_prefetch_control + 'x-forwarded-for': .x_forwarded_for + 'x-forwarded-host': .x_forwarded_host + 'x-forwarded-proto': .x_forwarded_proto + 'x-frame-options': .x_frame_options + 'x-xss-protection': .x_xss_protection +} + +// Header represents the key-value pairs in an HTTP header +[noinit] +pub struct Header { +mut: + data map[string][]string + // map of lowercase header keys to their original keys + // in order of appearance + keys map[string][]string +} + +pub fn (mut h Header) free() { + unsafe { + h.data.free() + h.keys.free() + } +} + +pub struct HeaderConfig { + key CommonHeader + value string +} + +// Create a new Header object +pub fn new_header(kvs ...HeaderConfig) Header { + mut h := Header{ + data: map[string][]string{} + } + for kv in kvs { + h.add(kv.key, kv.value) + } + return h +} + +// new_header_from_map creates a Header from key value pairs +pub fn new_header_from_map(kvs map[CommonHeader]string) Header { + mut h := new_header() + h.add_map(kvs) + return h +} + +// new_custom_header_from_map creates a Header from string key value pairs +pub fn new_custom_header_from_map(kvs map[string]string) ?Header { + mut h := new_header() + h.add_custom_map(kvs) ? + return h +} + +// add appends a value to the header key. +pub fn (mut h Header) add(key CommonHeader, value string) { + k := key.str() + h.data[k] << value + h.add_key(k) +} + +// add_custom appends a value to a custom header key. This function will +// return an error if the key contains invalid header characters. +pub fn (mut h Header) add_custom(key string, value string) ? { + is_valid(key) ? + h.data[key] << value + h.add_key(key) +} + +// add_map appends the value for each header key. +pub fn (mut h Header) add_map(kvs map[CommonHeader]string) { + for k, v in kvs { + h.add(k, v) + } +} + +// add_custom_map appends the value for each custom header key. +pub fn (mut h Header) add_custom_map(kvs map[string]string) ? { + for k, v in kvs { + h.add_custom(k, v) ? + } +} + +// set sets the key-value pair. This function will clear any other values +// that exist for the CommonHeader. +pub fn (mut h Header) set(key CommonHeader, value string) { + k := key.str() + h.data[k] = [value] + h.add_key(k) +} + +// set_custom sets the key-value pair for a custom header key. This +// function will clear any other values that exist for the header. This +// function will return an error if the key contains invalid header +// characters. +pub fn (mut h Header) set_custom(key string, value string) ? { + is_valid(key) ? + h.data[key] = [value] + h.add_key(key) +} + +// delete deletes all values for a key. +pub fn (mut h Header) delete(key CommonHeader) { + h.delete_custom(key.str()) +} + +// delete_custom deletes all values for a custom header key. +pub fn (mut h Header) delete_custom(key string) { + h.data.delete(key) + + // remove key from keys metadata + kl := key.to_lower() + if kl in h.keys { + h.keys[kl] = h.keys[kl].filter(it != key) + } +} + +pub struct HeaderCoerceConfig { + canonicalize bool +} + +// coerce coerces data in the Header by joining keys that match +// case-insensitively into one entry. +pub fn (mut h Header) coerce(flags ...HeaderCoerceConfig) { + canon := flags.any(it.canonicalize) + + for kl, data_keys in h.keys { + master_key := if canon { canonicalize(kl) } else { data_keys[0] } + + // save master data + master_data := h.data[master_key] + h.data.delete(master_key) + + for key in data_keys { + if key == master_key { + h.data[master_key] << master_data + continue + } + h.data[master_key] << h.data[key] + h.data.delete(key) + } + h.keys[kl] = [master_key] + } +} + +// contains returns whether the header key exists in the map. +pub fn (h Header) contains(key CommonHeader) bool { + return h.contains_custom(key.str()) +} + +pub struct HeaderQueryConfig { + exact bool +} + +// contains_custom returns whether the custom header key exists in the map. +pub fn (h Header) contains_custom(key string, flags ...HeaderQueryConfig) bool { + if flags.any(it.exact) { + return key in h.data + } + return key.to_lower() in h.keys +} + +// get gets the first value for the CommonHeader, or none if the key +// does not exist. +pub fn (h Header) get(key CommonHeader) ?string { + return h.get_custom(key.str()) +} + +// get_custom gets the first value for the custom header, or none if +// the key does not exist. +pub fn (h Header) get_custom(key string, flags ...HeaderQueryConfig) ?string { + mut data_key := key + if !flags.any(it.exact) { + // get the first key from key metadata + k := key.to_lower() + if h.keys[k].len == 0 { + return none + } + data_key = h.keys[k][0] + } + if h.data[data_key].len == 0 { + return none + } + return h.data[data_key][0] +} + +// starting_with gets the first header starting with key, or none if +// the key does not exist. +pub fn (h Header) starting_with(key string) ?string { + for k, _ in h.data { + if k.starts_with(key) { + return k + } + } + return none +} + +// values gets all values for the CommonHeader. +pub fn (h Header) values(key CommonHeader) []string { + return h.custom_values(key.str()) +} + +// custom_values gets all values for the custom header. +pub fn (h Header) custom_values(key string, flags ...HeaderQueryConfig) []string { + if flags.any(it.exact) { + return h.data[key] + } + // case insensitive lookup + mut values := []string{cap: 10} + for k in h.keys[key.to_lower()] { + values << h.data[k] + } + return values +} + +// keys gets all header keys as strings +pub fn (h Header) keys() []string { + return h.data.keys() +} + +pub struct HeaderRenderConfig { + version Version + coerce bool + canonicalize bool +} + +// render renders the Header into a string for use in sending HTTP +// requests. All header lines will end in `\r\n` +[manualfree] +pub fn (h Header) render(flags HeaderRenderConfig) string { + // estimate ~48 bytes per header + mut sb := strings.new_builder(h.data.len * 48) + if flags.coerce { + for kl, data_keys in h.keys { + key := if flags.version == .v2_0 { + kl + } else if flags.canonicalize { + canonicalize(kl) + } else { + data_keys[0] + } + for k in data_keys { + for v in h.data[k] { + sb.write_string(key) + sb.write_string(': ') + sb.write_string(v) + sb.write_string('\r\n') + } + } + } + } else { + for k, vs in h.data { + key := if flags.version == .v2_0 { + k.to_lower() + } else if flags.canonicalize { + canonicalize(k.to_lower()) + } else { + k + } + for v in vs { + sb.write_string(key) + sb.write_string(': ') + sb.write_string(v) + sb.write_string('\r\n') + } + } + } + res := sb.str() + unsafe { sb.free() } + return res +} + +// join combines two Header structs into a new Header struct +pub fn (h Header) join(other Header) Header { + mut combined := Header{ + data: h.data.clone() + keys: h.keys.clone() + } + for k in other.keys() { + for v in other.custom_values(k, exact: true) { + combined.add_custom(k, v) or { + // panic because this should never fail + panic('unexpected error: $err') + } + } + } + return combined +} + +// canonicalize canonicalizes an HTTP header key +// Common headers are determined by the common_header_map +// Custom headers are capitalized on the first letter and any letter after a '-' +// NOTE: Assumes sl is lowercase, since the caller usually already has the lowercase key +fn canonicalize(sl string) string { + // check if we have a common header + if sl in http.common_header_map { + return http.common_header_map[sl].str() + } + return sl.split('-').map(it.capitalize()).join('-') +} + +// Helper function to add a key to the keys map +fn (mut h Header) add_key(key string) { + kl := key.to_lower() + if !h.keys[kl].contains(key) { + h.keys[kl] << key + } +} + +// Custom error struct for invalid header tokens +struct HeaderKeyError { + msg string + code int + header string + invalid_char byte +} + +// is_valid checks if the header token contains all valid bytes +fn is_valid(header string) ? { + for _, c in header { + if int(c) >= 128 || !is_token(c) { + return IError(HeaderKeyError{ + msg: "Invalid header key: '$header'" + code: 1 + header: header + invalid_char: c + }) + } + } + if header.len == 0 { + return IError(HeaderKeyError{ + msg: "Invalid header key: '$header'" + code: 2 + header: header + invalid_char: 0 + }) + } +} + +// is_token checks if the byte is valid for a header token +fn is_token(b byte) bool { + return match b { + 33, 35...39, 42, 43, 45, 46, 48...57, 65...90, 94...122, 124, 126 { true } + else { false } + } +} + +// str returns the headers string as seen in HTTP/1.1 requests. +// Key order is not guaranteed. +pub fn (h Header) str() string { + return h.render(version: .v1_1) +} + +// parse_headers parses a newline delimited string into a Header struct +fn parse_headers(s string) ?Header { + mut h := new_header() + mut last_key := '' + mut last_value := '' + for line in s.split_into_lines() { + if line.len == 0 { + break + } + // handle header fold + if line[0] == ` ` || line[0] == `\t` { + last_value += ' ${line.trim(' \t')}' + continue + } else if last_key != '' { + h.add_custom(last_key, last_value) ? + } + last_key, last_value = parse_header(line) ? + } + h.add_custom(last_key, last_value) ? + return h +} + +fn parse_header(s string) ?(string, string) { + if !s.contains(':') { + return error('missing colon in header') + } + words := s.split_nth(':', 2) + // TODO: parse quoted text according to the RFC + return words[0], words[1].trim(' \t') +} diff --git a/v_windows/v/vlib/net/http/header_test.v b/v_windows/v/vlib/net/http/header_test.v new file mode 100644 index 0000000..4f5f2ce --- /dev/null +++ b/v_windows/v/vlib/net/http/header_test.v @@ -0,0 +1,387 @@ +module http + +fn test_header_new() { + h := new_header(HeaderConfig{ key: .accept, value: 'nothing' }, + key: .expires + value: 'yesterday' + ) + assert h.contains(.accept) + assert h.contains(.expires) + accept := h.get(.accept) or { '' } + expires := h.get(.expires) or { '' } + assert accept == 'nothing' + assert expires == 'yesterday' +} + +fn test_header_invalid_key() { + mut h := new_header() + h.add_custom('space is invalid', ':(') or { return } + panic('should have returned') +} + +fn test_header_adds_multiple() { + mut h := new_header() + h.add(.accept, 'one') + h.add(.accept, 'two') + + assert h.values(.accept) == ['one', 'two'] +} + +fn test_header_get() ? { + mut h := new_header(key: .dnt, value: 'one') + h.add_custom('dnt', 'two') ? + dnt := h.get_custom('dnt') or { '' } + exact := h.get_custom('dnt', exact: true) or { '' } + assert dnt == 'one' + assert exact == 'two' +} + +fn test_header_set() ? { + mut h := new_header(HeaderConfig{ key: .dnt, value: 'one' }, + key: .dnt + value: 'two' + ) + assert h.values(.dnt) == ['one', 'two'] + h.set_custom('DNT', 'three') ? + assert h.values(.dnt) == ['three'] +} + +fn test_header_delete() { + mut h := new_header(HeaderConfig{ key: .dnt, value: 'one' }, + key: .dnt + value: 'two' + ) + assert h.values(.dnt) == ['one', 'two'] + h.delete(.dnt) + assert h.values(.dnt) == [] +} + +fn test_header_delete_not_existing() { + mut h := new_header() + assert h.data.len == 0 + assert h.keys.len == 0 + h.delete(.dnt) + assert h.data.len == 0 + assert h.keys.len == 0 +} + +fn test_custom_header() ? { + mut h := new_header() + h.add_custom('AbC', 'dEf') ? + h.add_custom('aBc', 'GhI') ? + assert h.custom_values('AbC', exact: true) == ['dEf'] + assert h.custom_values('aBc', exact: true) == ['GhI'] + assert h.custom_values('ABC') == ['dEf', 'GhI'] + assert h.custom_values('abc') == ['dEf', 'GhI'] + assert h.keys() == ['AbC', 'aBc'] + h.delete_custom('AbC') + h.delete_custom('aBc') + + h.add_custom('abc', 'def') ? + assert h.custom_values('abc') == ['def'] + assert h.custom_values('ABC') == ['def'] + assert h.keys() == ['abc'] + h.delete_custom('abc') + + h.add_custom('accEPT', '*/*') ? + assert h.custom_values('ACCept') == ['*/*'] + assert h.values(.accept) == ['*/*'] + assert h.keys() == ['accEPT'] +} + +fn test_contains_custom() ? { + mut h := new_header() + h.add_custom('Hello', 'world') ? + assert h.contains_custom('hello') + assert h.contains_custom('HELLO') + assert h.contains_custom('Hello', exact: true) + assert h.contains_custom('hello', exact: true) == false + assert h.contains_custom('HELLO', exact: true) == false +} + +fn test_get_custom() ? { + mut h := new_header() + h.add_custom('Hello', 'world') ? + assert h.get_custom('hello') ? == 'world' + assert h.get_custom('HELLO') ? == 'world' + assert h.get_custom('Hello', exact: true) ? == 'world' + if _ := h.get_custom('hello', exact: true) { + // should be none + assert false + } + if _ := h.get_custom('HELLO', exact: true) { + // should be none + assert false + } +} + +fn test_starting_with() ? { + mut h := new_header() + h.add_custom('Hello-1', 'world') ? + h.add_custom('Hello-21', 'world') ? + assert h.starting_with('Hello-') ? == 'Hello-1' + assert h.starting_with('Hello-2') ? == 'Hello-21' +} + +fn test_custom_values() ? { + mut h := new_header() + h.add_custom('Hello', 'world') ? + assert h.custom_values('hello') == ['world'] + assert h.custom_values('HELLO') == ['world'] + assert h.custom_values('Hello', exact: true) == ['world'] + assert h.custom_values('hello', exact: true) == [] + assert h.custom_values('HELLO', exact: true) == [] +} + +fn test_coerce() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add(.accept, 'bar') + assert h.values(.accept) == ['foo', 'bar'] + assert h.keys().len == 2 + + h.coerce() + assert h.values(.accept) == ['foo', 'bar'] + assert h.keys() == ['accept'] // takes the first occurrence +} + +fn test_coerce_canonicalize() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add(.accept, 'bar') + assert h.values(.accept) == ['foo', 'bar'] + assert h.keys().len == 2 + + h.coerce(canonicalize: true) + assert h.values(.accept) == ['foo', 'bar'] + assert h.keys() == ['Accept'] // canonicalize header +} + +fn test_coerce_custom() ? { + mut h := new_header() + h.add_custom('Hello', 'foo') ? + h.add_custom('hello', 'bar') ? + h.add_custom('HELLO', 'baz') ? + assert h.custom_values('hello') == ['foo', 'bar', 'baz'] + assert h.keys().len == 3 + + h.coerce() + assert h.custom_values('hello') == ['foo', 'bar', 'baz'] + assert h.keys() == ['Hello'] // takes the first occurrence +} + +fn test_coerce_canonicalize_custom() ? { + mut h := new_header() + h.add_custom('foo-BAR', 'foo') ? + h.add_custom('FOO-bar', 'bar') ? + assert h.custom_values('foo-bar') == ['foo', 'bar'] + assert h.keys().len == 2 + + h.coerce(canonicalize: true) + assert h.custom_values('foo-bar') == ['foo', 'bar'] + assert h.keys() == ['Foo-Bar'] // capitalizes the header +} + +fn test_render_version() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add_custom('Accept', 'bar') ? + h.add(.accept, 'baz') + + s1_0 := h.render(version: .v1_0) + assert s1_0.contains('accept: foo\r\n') + assert s1_0.contains('Accept: bar\r\n') + assert s1_0.contains('Accept: baz\r\n') + + s1_1 := h.render(version: .v1_1) + assert s1_1.contains('accept: foo\r\n') + assert s1_1.contains('Accept: bar\r\n') + assert s1_1.contains('Accept: baz\r\n') + + s2_0 := h.render(version: .v2_0) + assert s2_0.contains('accept: foo\r\n') + assert s2_0.contains('accept: bar\r\n') + assert s2_0.contains('accept: baz\r\n') +} + +fn test_render_coerce() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add_custom('Accept', 'bar') ? + h.add(.accept, 'baz') + h.add(.host, 'host') + + s1_0 := h.render(version: .v1_1, coerce: true) + assert s1_0.contains('accept: foo\r\n') + assert s1_0.contains('accept: bar\r\n') + assert s1_0.contains('accept: baz\r\n') + assert s1_0.contains('Host: host\r\n') + + s1_1 := h.render(version: .v1_1, coerce: true) + assert s1_1.contains('accept: foo\r\n') + assert s1_1.contains('accept: bar\r\n') + assert s1_1.contains('accept: baz\r\n') + assert s1_1.contains('Host: host\r\n') + + s2_0 := h.render(version: .v2_0, coerce: true) + assert s2_0.contains('accept: foo\r\n') + assert s2_0.contains('accept: bar\r\n') + assert s2_0.contains('accept: baz\r\n') + assert s2_0.contains('host: host\r\n') +} + +fn test_render_canonicalize() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add_custom('Accept', 'bar') ? + h.add(.accept, 'baz') + h.add(.host, 'host') + + s1_0 := h.render(version: .v1_1, canonicalize: true) + assert s1_0.contains('Accept: foo\r\n') + assert s1_0.contains('Accept: bar\r\n') + assert s1_0.contains('Accept: baz\r\n') + assert s1_0.contains('Host: host\r\n') + + s1_1 := h.render(version: .v1_1, canonicalize: true) + assert s1_1.contains('Accept: foo\r\n') + assert s1_1.contains('Accept: bar\r\n') + assert s1_1.contains('Accept: baz\r\n') + assert s1_1.contains('Host: host\r\n') + + s2_0 := h.render(version: .v2_0, canonicalize: true) + assert s2_0.contains('accept: foo\r\n') + assert s2_0.contains('accept: bar\r\n') + assert s2_0.contains('accept: baz\r\n') + assert s2_0.contains('host: host\r\n') +} + +fn test_render_coerce_canonicalize() ? { + mut h := new_header() + h.add_custom('accept', 'foo') ? + h.add_custom('Accept', 'bar') ? + h.add(.accept, 'baz') + h.add(.host, 'host') + + s1_0 := h.render(version: .v1_1, coerce: true, canonicalize: true) + assert s1_0.contains('Accept: foo\r\n') + assert s1_0.contains('Accept: bar\r\n') + assert s1_0.contains('Accept: baz\r\n') + assert s1_0.contains('Host: host\r\n') + + s1_1 := h.render(version: .v1_1, coerce: true, canonicalize: true) + assert s1_1.contains('Accept: foo\r\n') + assert s1_1.contains('Accept: bar\r\n') + assert s1_1.contains('Accept: baz\r\n') + assert s1_1.contains('Host: host\r\n') + + s2_0 := h.render(version: .v2_0, coerce: true, canonicalize: true) + assert s2_0.contains('accept: foo\r\n') + assert s2_0.contains('accept: bar\r\n') + assert s2_0.contains('accept: baz\r\n') + assert s2_0.contains('host: host\r\n') +} + +fn test_str() ? { + mut h := new_header() + h.add(.accept, 'text/html') + h.add_custom('Accept', 'image/jpeg') ? + h.add_custom('X-custom', 'Hello') ? + + // key order is not guaranteed + assert h.str() == 'Accept: text/html\r\nAccept: image/jpeg\r\nX-custom: Hello\r\n' + || h.str() == 'X-custom: Hello\r\nAccept:text/html\r\nAccept: image/jpeg\r\n' +} + +fn test_header_from_map() ? { + h := new_header_from_map({ + CommonHeader.accept: 'nothing' + CommonHeader.expires: 'yesterday' + }) + assert h.contains(.accept) + assert h.contains(.expires) + assert h.get(.accept) or { '' } == 'nothing' + assert h.get(.expires) or { '' } == 'yesterday' +} + +fn test_custom_header_from_map() ? { + h := new_custom_header_from_map({ + 'Server': 'VWeb' + 'foo': 'bar' + }) ? + assert h.contains_custom('server') + assert h.contains_custom('foo') + assert h.get_custom('server') or { '' } == 'VWeb' + assert h.get_custom('foo') or { '' } == 'bar' +} + +fn test_header_join() ? { + h1 := new_header_from_map({ + CommonHeader.accept: 'nothing' + CommonHeader.expires: 'yesterday' + }) + h2 := new_custom_header_from_map({ + 'Server': 'VWeb' + 'foo': 'bar' + }) ? + h3 := h1.join(h2) + // h1 is unchanged + assert h1.contains(.accept) + assert h1.contains(.expires) + assert !h1.contains_custom('Server') + assert !h1.contains_custom('foo') + // h2 is unchanged + assert !h2.contains(.accept) + assert !h2.contains(.expires) + assert h2.contains_custom('Server') + assert h2.contains_custom('foo') + // h3 has all four headers + assert h3.contains(.accept) + assert h3.contains(.expires) + assert h3.contains_custom('Server') + assert h3.contains_custom('foo') +} + +fn parse_headers_test(s string, expected map[string]string) ? { + assert parse_headers(s) ? == new_custom_header_from_map(expected) ? +} + +fn test_parse_headers() ? { + parse_headers_test('foo: bar', { + 'foo': 'bar' + }) ? + parse_headers_test('foo: \t bar', { + 'foo': 'bar' + }) ? + parse_headers_test('foo: bar\r\n\tbaz', { + 'foo': 'bar baz' + }) ? + parse_headers_test('foo: bar \r\n\tbaz\r\n buzz', { + 'foo': 'bar baz buzz' + }) ? + parse_headers_test('foo: bar\r\nbar:baz', { + 'foo': 'bar' + 'bar': 'baz' + }) ? + parse_headers_test('foo: bar\r\nbar:baz\r\n', { + 'foo': 'bar' + 'bar': 'baz' + }) ? + parse_headers_test('foo: bar\r\nbar:baz\r\n\r\n', { + 'foo': 'bar' + 'bar': 'baz' + }) ? + assert parse_headers('foo: bar\r\nfoo:baz') ?.custom_values('foo') == ['bar', 'baz'] + + if x := parse_headers(' oops: oh no') { + return error('should have errored, but got $x') + } +} + +fn test_set_cookie() { + // multiple Set-Cookie headers should be sent when rendered + mut h := new_header() + h.add(.set_cookie, 'foo') + h.add(.set_cookie, 'bar') + assert h.render() == 'Set-Cookie: foo\r\nSet-Cookie: bar\r\n' +} diff --git a/v_windows/v/vlib/net/http/http.v b/v_windows/v/vlib/net/http/http.v new file mode 100644 index 0000000..7bdc5e2 --- /dev/null +++ b/v_windows/v/vlib/net/http/http.v @@ -0,0 +1,186 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import net.urllib + +const ( + max_redirects = 4 + content_type_default = 'text/plain' + bufsize = 1536 +) + +// FetchConfig holds configurations of fetch +pub struct FetchConfig { +pub mut: + url string + method Method + header Header + data string + params map[string]string + cookies map[string]string + user_agent string = 'v.http' + verbose bool +} + +pub fn new_request(method Method, url_ string, data string) ?Request { + url := if method == .get { url_ + '?' + data } else { url_ } + // println('new req() method=$method url="$url" dta="$data"') + return Request{ + method: method + url: url + data: data + /* + headers: { + 'Accept-Encoding': 'compress' + } + */ + } +} + +// get sends a GET HTTP request to the URL +pub fn get(url string) ?Response { + return fetch(method: .get, url: url) +} + +// post sends a POST HTTP request to the URL with a string data +pub fn post(url string, data string) ?Response { + return fetch( + method: .post + url: url + data: data + header: new_header(key: .content_type, value: http.content_type_default) + ) +} + +// post_json sends a POST HTTP request to the URL with a JSON data +pub fn post_json(url string, data string) ?Response { + return fetch( + method: .post + url: url + data: data + header: new_header(key: .content_type, value: 'application/json') + ) +} + +// post_form sends a POST HTTP request to the URL with X-WWW-FORM-URLENCODED data +pub fn post_form(url string, data map[string]string) ?Response { + return fetch( + method: .post + url: url + header: new_header(key: .content_type, value: 'application/x-www-form-urlencoded') + data: url_encode_form_data(data) + ) +} + +// put sends a PUT HTTP request to the URL with a string data +pub fn put(url string, data string) ?Response { + return fetch( + method: .put + url: url + data: data + header: new_header(key: .content_type, value: http.content_type_default) + ) +} + +// patch sends a PATCH HTTP request to the URL with a string data +pub fn patch(url string, data string) ?Response { + return fetch( + method: .patch + url: url + data: data + header: new_header(key: .content_type, value: http.content_type_default) + ) +} + +// head sends a HEAD HTTP request to the URL +pub fn head(url string) ?Response { + return fetch(method: .head, url: url) +} + +// delete sends a DELETE HTTP request to the URL +pub fn delete(url string) ?Response { + return fetch(method: .delete, url: url) +} + +// fetch sends an HTTP request to the URL with the given method and configurations +pub fn fetch(config FetchConfig) ?Response { + if config.url == '' { + return error('http.fetch: empty url') + } + url := build_url_from_fetch(config) or { return error('http.fetch: invalid url $config.url') } + req := Request{ + method: config.method + url: url + data: config.data + header: config.header + cookies: config.cookies + user_agent: config.user_agent + user_ptr: 0 + verbose: config.verbose + } + res := req.do() ? + return res +} + +// get_text sends a GET HTTP request to the URL and returns the text content of the response +pub fn get_text(url string) string { + resp := fetch(url: url, method: .get) or { return '' } + return resp.text +} + +// url_encode_form_data converts mapped data to an URL encoded string +pub fn url_encode_form_data(data map[string]string) string { + mut pieces := []string{} + for key_, value_ in data { + key := urllib.query_escape(key_) + value := urllib.query_escape(value_) + pieces << '$key=$value' + } + return pieces.join('&') +} + +[deprecated: 'use fetch()'] +fn fetch_with_method(method Method, _config FetchConfig) ?Response { + mut config := _config + config.method = method + return fetch(config) +} + +fn build_url_from_fetch(config FetchConfig) ?string { + mut url := urllib.parse(config.url) ? + if config.params.len == 0 { + return url.str() + } + mut pieces := []string{cap: config.params.len} + for key, val in config.params { + pieces << '$key=$val' + } + mut query := pieces.join('&') + if url.raw_query.len > 1 { + query = url.raw_query + '&' + query + } + url.raw_query = query + return url.str() +} + +// unescape_url is deprecated, use urllib.query_unescape() instead +pub fn unescape_url(s string) string { + panic('http.unescape_url() was replaced with urllib.query_unescape()') +} + +// escape_url is deprecated, use urllib.query_escape() instead +pub fn escape_url(s string) string { + panic('http.escape_url() was replaced with urllib.query_escape()') +} + +// unescape is deprecated, use urllib.query_escape() instead +pub fn unescape(s string) string { + panic('http.unescape() was replaced with http.unescape_url()') +} + +// escape is deprecated, use urllib.query_unescape() instead +pub fn escape(s string) string { + panic('http.escape() was replaced with http.escape_url()') +} diff --git a/v_windows/v/vlib/net/http/http_httpbin_test.v b/v_windows/v/vlib/net/http/http_httpbin_test.v new file mode 100644 index 0000000..a3ddccc --- /dev/null +++ b/v_windows/v/vlib/net/http/http_httpbin_test.v @@ -0,0 +1,95 @@ +module http + +// internal tests have access to *everything in the module* +import json + +struct HttpbinResponseBody { + args map[string]string + data string + files map[string]string + form map[string]string + headers map[string]string + json map[string]string + origin string + url string +} + +fn http_fetch_mock(_methods []string, _config FetchConfig) ?[]Response { + url := 'https://httpbin.org/' + methods := if _methods.len == 0 { ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] } else { _methods } + mut config := _config + mut result := []Response{} + // Note: httpbin doesn't support head + for method in methods { + lmethod := method.to_lower() + config.method = method_from_str(method) + res := fetch(FetchConfig{ ...config, url: url + lmethod }) ? + // TODO + // body := json.decode(HttpbinResponseBody,res.text)? + result << res + } + return result +} + +fn test_http_fetch_bare() { + $if !network ? { + return + } + responses := http_fetch_mock([], FetchConfig{}) or { panic(err) } + for response in responses { + assert response.status() == .ok + } +} + +fn test_http_fetch_with_data() { + $if !network ? { + return + } + responses := http_fetch_mock(['POST', 'PUT', 'PATCH', 'DELETE'], + data: 'hello world' + ) or { panic(err) } + for response in responses { + payload := json.decode(HttpbinResponseBody, response.text) or { panic(err) } + assert payload.data == 'hello world' + } +} + +fn test_http_fetch_with_params() { + $if !network ? { + return + } + responses := http_fetch_mock([], + params: { + 'a': 'b' + 'c': 'd' + } + ) or { panic(err) } + for response in responses { + // payload := json.decode(HttpbinResponseBody,response.text) or { + // panic(err) + // } + assert response.status() == .ok + // TODO + // assert payload.args['a'] == 'b' + // assert payload.args['c'] == 'd' + } +} + +fn test_http_fetch_with_headers() ? { + $if !network ? { + return + } + mut header := new_header() + header.add_custom('Test-Header', 'hello world') ? + responses := http_fetch_mock([], + header: header + ) or { panic(err) } + for response in responses { + // payload := json.decode(HttpbinResponseBody,response.text) or { + // panic(err) + // } + assert response.status() == .ok + // TODO + // assert payload.headers['Test-Header'] == 'hello world' + } +} diff --git a/v_windows/v/vlib/net/http/http_test.v b/v_windows/v/vlib/net/http/http_test.v new file mode 100644 index 0000000..8b68073 --- /dev/null +++ b/v_windows/v/vlib/net/http/http_test.v @@ -0,0 +1,56 @@ +import net.http + +fn test_http_get() { + $if !network ? { + return + } + assert http.get_text('https://vlang.io/version') == '0.1.5' + println('http ok') +} + +fn test_http_get_from_vlang_utc_now() { + $if !network ? { + return + } + urls := ['http://vlang.io/utc_now', 'https://vlang.io/utc_now'] + for url in urls { + println('Test getting current time from $url by http.get') + res := http.get(url) or { panic(err) } + assert res.status() == .ok + assert res.text.len > 0 + assert res.text.int() > 1566403696 + println('Current time is: $res.text.int()') + } +} + +fn test_public_servers() { + $if !network ? { + return + } + urls := [ + 'http://github.com/robots.txt', + 'http://google.com/robots.txt', + 'https://github.com/robots.txt', + 'https://google.com/robots.txt', + // 'http://yahoo.com/robots.txt', + // 'https://yahoo.com/robots.txt', + ] + for url in urls { + println('Testing http.get on public url: $url ') + res := http.get(url) or { panic(err) } + assert res.status() == .ok + assert res.text.len > 0 + } +} + +fn test_relative_redirects() { + $if !network ? { + return + } $else { + return + } // tempfix periodic: httpbin relative redirects are broken + res := http.get('https://httpbin.org/relative-redirect/3?abc=xyz') or { panic(err) } + assert res.status() == .ok + assert res.text.len > 0 + assert res.text.contains('"abc": "xyz"') +} diff --git a/v_windows/v/vlib/net/http/method.v b/v_windows/v/vlib/net/http/method.v new file mode 100644 index 0000000..91c93e1 --- /dev/null +++ b/v_windows/v/vlib/net/http/method.v @@ -0,0 +1,48 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +// The methods listed here are some of the most used ones, ordered by +// commonality. A comprehensive list is available at: +// https://www.iana.org/assignments/http-methods/http-methods.xhtml +pub enum Method { + get + post + put + head + delete + options + trace + connect + patch +} + +pub fn (m Method) str() string { + return match m { + .get { 'GET' } + .post { 'POST' } + .put { 'PUT' } + .head { 'HEAD' } + .delete { 'DELETE' } + .options { 'OPTIONS' } + .trace { 'TRACE' } + .connect { 'CONNECT' } + .patch { 'PATCH' } + } +} + +pub fn method_from_str(m string) Method { + return match m { + 'GET' { Method.get } + 'POST' { Method.post } + 'PUT' { Method.put } + 'HEAD' { Method.head } + 'DELETE' { Method.delete } + 'OPTIONS' { Method.options } + 'TRACE' { Method.trace } + 'CONNECT' { Method.connect } + 'PATCH' { Method.patch } + else { Method.get } // should we default to GET? + } +} diff --git a/v_windows/v/vlib/net/http/request.v b/v_windows/v/vlib/net/http/request.v new file mode 100644 index 0000000..4664659 --- /dev/null +++ b/v_windows/v/vlib/net/http/request.v @@ -0,0 +1,324 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import io +import net +import net.urllib +import strings +import time + +// Request holds information about an HTTP request (either received by +// a server or to be sent by a client) +pub struct Request { +pub mut: + version Version = .v1_1 + method Method + header Header + cookies map[string]string + data string + url string + user_agent string = 'v.http' + verbose bool + user_ptr voidptr + // NOT implemented for ssl connections + // time = -1 for no timeout + read_timeout i64 = 30 * time.second + write_timeout i64 = 30 * time.second +} + +fn (mut req Request) free() { + unsafe { req.header.free() } +} + +// add_header adds the key and value of an HTTP request header +// To add a custom header, use add_custom_header +pub fn (mut req Request) add_header(key CommonHeader, val string) { + req.header.add(key, val) +} + +// add_custom_header adds the key and value of an HTTP request header +// This method may fail if the key contains characters that are not permitted +pub fn (mut req Request) add_custom_header(key string, val string) ? { + return req.header.add_custom(key, val) +} + +// do will send the HTTP request and returns `http.Response` as soon as the response is recevied +pub fn (req &Request) do() ?Response { + mut url := urllib.parse(req.url) or { return error('http.Request.do: invalid url $req.url') } + mut rurl := url + mut resp := Response{} + mut no_redirects := 0 + for { + if no_redirects == max_redirects { + return error('http.request.do: maximum number of redirects reached ($max_redirects)') + } + qresp := req.method_and_url_to_response(req.method, rurl) ? + resp = qresp + if resp.status() !in [.moved_permanently, .found, .see_other, .temporary_redirect, + .permanent_redirect, + ] { + break + } + // follow any redirects + mut redirect_url := resp.header.get(.location) or { '' } + if redirect_url.len > 0 && redirect_url[0] == `/` { + url.set_path(redirect_url) or { + return error('http.request.do: invalid path in redirect: "$redirect_url"') + } + redirect_url = url.str() + } + qrurl := urllib.parse(redirect_url) or { + return error('http.request.do: invalid URL in redirect "$redirect_url"') + } + rurl = qrurl + no_redirects++ + } + return resp +} + +fn (req &Request) method_and_url_to_response(method Method, url urllib.URL) ?Response { + host_name := url.hostname() + scheme := url.scheme + p := url.escaped_path().trim_left('/') + path := if url.query().len > 0 { '/$p?$url.query().encode()' } else { '/$p' } + mut nport := url.port().int() + if nport == 0 { + if scheme == 'http' { + nport = 80 + } + if scheme == 'https' { + nport = 443 + } + } + // println('fetch $method, $scheme, $host_name, $nport, $path ') + if scheme == 'https' { + // println('ssl_do( $nport, $method, $host_name, $path )') + res := req.ssl_do(nport, method, host_name, path) ? + return res + } else if scheme == 'http' { + // println('http_do( $nport, $method, $host_name, $path )') + res := req.http_do('$host_name:$nport', method, path) ? + return res + } + return error('http.request.method_and_url_to_response: unsupported scheme: "$scheme"') +} + +fn (req &Request) build_request_headers(method Method, host_name string, path string) string { + ua := req.user_agent + mut uheaders := []string{} + if !req.header.contains(.host) { + uheaders << 'Host: $host_name\r\n' + } + if !req.header.contains(.user_agent) { + uheaders << 'User-Agent: $ua\r\n' + } + if req.data.len > 0 && !req.header.contains(.content_length) { + uheaders << 'Content-Length: $req.data.len\r\n' + } + for key in req.header.keys() { + if key == CommonHeader.cookie.str() { + continue + } + val := req.header.custom_values(key).join('; ') + uheaders << '$key: $val\r\n' + } + uheaders << req.build_request_cookies_header() + version := if req.version == .unknown { Version.v1_1 } else { req.version } + return '$method $path $version\r\n' + uheaders.join('') + 'Connection: close\r\n\r\n' + req.data +} + +fn (req &Request) build_request_cookies_header() string { + if req.cookies.keys().len < 1 { + return '' + } + mut cookie := []string{} + for key, val in req.cookies { + cookie << '$key=$val' + } + cookie << req.header.values(.cookie) + return 'Cookie: ' + cookie.join('; ') + '\r\n' +} + +fn (req &Request) http_do(host string, method Method, path string) ?Response { + host_name, _ := net.split_address(host) ? + s := req.build_request_headers(method, host_name, path) + mut client := net.dial_tcp(host) ? + client.set_read_timeout(req.read_timeout) + client.set_write_timeout(req.write_timeout) + // TODO this really needs to be exposed somehow + client.write(s.bytes()) ? + $if trace_http_request ? { + eprintln('> $s') + } + mut bytes := io.read_all(reader: client) ? + client.close() ? + response_text := bytes.bytestr() + $if trace_http_response ? { + eprintln('< $response_text') + } + return parse_response(response_text) +} + +// referer returns 'Referer' header value of the given request +pub fn (req &Request) referer() string { + return req.header.get(.referer) or { '' } +} + +// Parse a raw HTTP request into a Request object +pub fn parse_request(mut reader io.BufferedReader) ?Request { + // request line + mut line := reader.read_line() ? + method, target, version := parse_request_line(line) ? + + // headers + mut header := new_header() + line = reader.read_line() ? + for line != '' { + key, value := parse_header(line) ? + header.add_custom(key, value) ? + line = reader.read_line() ? + } + header.coerce(canonicalize: true) + + // body + mut body := []byte{} + if length := header.get(.content_length) { + n := length.int() + if n > 0 { + body = []byte{len: n} + mut count := 0 + for count < body.len { + count += reader.read(mut body[count..]) or { break } + } + } + } + + return Request{ + method: method + url: target.str() + header: header + data: body.bytestr() + version: version + } +} + +fn parse_request_line(s string) ?(Method, urllib.URL, Version) { + words := s.split(' ') + if words.len != 3 { + return error('malformed request line') + } + method := method_from_str(words[0]) + target := urllib.parse(words[1]) ? + version := version_from_str(words[2]) + if version == .unknown { + return error('unsupported version') + } + + return method, target, version +} + +// Parse URL encoded key=value&key=value forms +fn parse_form(body string) map[string]string { + words := body.split('&') + mut form := map[string]string{} + for word in words { + kv := word.split_nth('=', 2) + if kv.len != 2 { + continue + } + key := urllib.query_unescape(kv[0]) or { continue } + val := urllib.query_unescape(kv[1]) or { continue } + form[key] = val + } + return form + // } + // todo: parse form-data and application/json + // ... +} + +struct FileData { +pub: + filename string + content_type string + data string +} + +struct UnexpectedExtraAttributeError { + msg string + code int +} + +struct MultiplePathAttributesError { + msg string = 'Expected at most one path attribute' + code int +} + +fn parse_multipart_form(body string, boundary string) (map[string]string, map[string][]FileData) { + sections := body.split(boundary) + fields := sections[1..sections.len - 1] + mut form := map[string]string{} + mut files := map[string][]FileData{} + + for field in fields { + // TODO: do not split into lines; do same parsing for HTTP body + lines := field.split_into_lines()[1..] + disposition := parse_disposition(lines[0]) + // Grab everything between the double quotes + name := disposition['name'] or { continue } + // Parse files + // TODO: filename* + if 'filename' in disposition { + filename := disposition['filename'] + // Parse Content-Type header + if lines.len == 1 || !lines[1].to_lower().starts_with('content-type:') { + continue + } + mut ct := lines[1].split_nth(':', 2)[1] + ct = ct.trim_left(' \t') + data := lines_to_string(field.len, lines, 3, lines.len - 1) + files[name] << FileData{ + filename: filename + content_type: ct + data: data + } + continue + } + data := lines_to_string(field.len, lines, 2, lines.len - 1) + form[name] = data + } + return form, files +} + +// Parse the Content-Disposition header of a multipart form +// Returns a map of the key="value" pairs +// Example: parse_disposition('Content-Disposition: form-data; name="a"; filename="b"') == {'name': 'a', 'filename': 'b'} +fn parse_disposition(line string) map[string]string { + mut data := map[string]string{} + for word in line.split(';') { + kv := word.split_nth('=', 2) + if kv.len != 2 { + continue + } + key, value := kv[0].to_lower().trim_left(' \t'), kv[1] + if value.starts_with('"') && value.ends_with('"') { + data[key] = value[1..value.len - 1] + } else { + data[key] = value + } + } + return data +} + +[manualfree] +fn lines_to_string(len int, lines []string, start int, end int) string { + mut sb := strings.new_builder(len) + for i in start .. end { + sb.writeln(lines[i]) + } + sb.cut_last(1) // last newline + res := sb.str() + unsafe { sb.free() } + return res +} diff --git a/v_windows/v/vlib/net/http/request_test.v b/v_windows/v/vlib/net/http/request_test.v new file mode 100644 index 0000000..3950ad8 --- /dev/null +++ b/v_windows/v/vlib/net/http/request_test.v @@ -0,0 +1,138 @@ +module http + +import io + +struct StringReader { + text string +mut: + place int +} + +fn (mut s StringReader) read(mut buf []byte) ?int { + if s.place >= s.text.len { + return none + } + max_bytes := 100 + end := if s.place + max_bytes >= s.text.len { s.text.len } else { s.place + max_bytes } + n := copy(buf, s.text[s.place..end].bytes()) + s.place += n + return n +} + +fn reader(s string) &io.BufferedReader { + return io.new_buffered_reader( + reader: &StringReader{ + text: s + } + ) +} + +fn test_parse_request_not_http() { + mut reader__ := reader('hello') + parse_request(mut reader__) or { return } + panic('should not have parsed') +} + +fn test_parse_request_no_headers() { + mut reader_ := reader('GET / HTTP/1.1\r\n\r\n') + req := parse_request(mut reader_) or { panic('did not parse: $err') } + assert req.method == .get + assert req.url == '/' + assert req.version == .v1_1 +} + +fn test_parse_request_two_headers() { + mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a\r\nTest2: B\r\n\r\n') + req := parse_request(mut reader_) or { panic('did not parse: $err') } + assert req.header.custom_values('Test1') == ['a'] + assert req.header.custom_values('Test2') == ['B'] +} + +fn test_parse_request_two_header_values() { + mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a; b\r\nTest2: c\r\nTest2: d\r\n\r\n') + req := parse_request(mut reader_) or { panic('did not parse: $err') } + assert req.header.custom_values('Test1') == ['a; b'] + assert req.header.custom_values('Test2') == ['c', 'd'] +} + +fn test_parse_request_body() { + mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a\r\nTest2: b\r\nContent-Length: 4\r\n\r\nbodyabc') + req := parse_request(mut reader_) or { panic('did not parse: $err') } + assert req.data == 'body' +} + +fn test_parse_request_line() { + method, target, version := parse_request_line('GET /target HTTP/1.1') or { + panic('did not parse: $err') + } + assert method == .get + assert target.str() == '/target' + assert version == .v1_1 +} + +fn test_parse_form() { + assert parse_form('foo=bar&bar=baz') == { + 'foo': 'bar' + 'bar': 'baz' + } + assert parse_form('foo=bar=&bar=baz') == { + 'foo': 'bar=' + 'bar': 'baz' + } + assert parse_form('foo=bar%3D&bar=baz') == { + 'foo': 'bar=' + 'bar': 'baz' + } + assert parse_form('foo=b%26ar&bar=baz') == { + 'foo': 'b&ar' + 'bar': 'baz' + } + assert parse_form('a=b& c=d') == { + 'a': 'b' + ' c': 'd' + } + assert parse_form('a=b&c= d ') == { + 'a': 'b' + 'c': ' d ' + } +} + +fn test_parse_multipart_form() { + boundary := '6844a625b1f0b299' + names := ['foo', 'fooz'] + file := 'bar.v' + ct := 'application/octet-stream' + contents := ['baz', 'buzz'] + data := "--------------------------$boundary +Content-Disposition: form-data; name=\"${names[0]}\"; filename=\"$file\" +Content-Type: $ct + +${contents[0]} +--------------------------$boundary +Content-Disposition: form-data; name=\"${names[1]}\" + +${contents[1]} +--------------------------$boundary-- +" + form, files := parse_multipart_form(data, boundary) + assert files == { + names[0]: [FileData{ + filename: file + content_type: ct + data: contents[0] + }] + } + + assert form == { + names[1]: contents[1] + } +} + +fn test_parse_large_body() ? { + body := 'A'.repeat(101) // greater than max_bytes + req := 'GET / HTTP/1.1\r\nContent-Length: $body.len\r\n\r\n$body' + mut reader_ := reader(req) + result := parse_request(mut reader_) ? + assert result.data.len == body.len + assert result.data == body +} diff --git a/v_windows/v/vlib/net/http/response.v b/v_windows/v/vlib/net/http/response.v new file mode 100644 index 0000000..caa8228 --- /dev/null +++ b/v_windows/v/vlib/net/http/response.v @@ -0,0 +1,152 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import net.http.chunked +import strconv + +// Response represents the result of the request +pub struct Response { +pub mut: + text string + header Header + status_code int + status_msg string + http_version string +} + +fn (mut resp Response) free() { + unsafe { resp.header.free() } +} + +// Formats resp to bytes suitable for HTTP response transmission +pub fn (resp Response) bytes() []byte { + // TODO: build []byte directly; this uses two allocations + return resp.bytestr().bytes() +} + +// Formats resp to a string suitable for HTTP response transmission +pub fn (resp Response) bytestr() string { + return ('HTTP/$resp.http_version $resp.status_code $resp.status_msg\r\n' + '${resp.header.render( + version: resp.version() + )}\r\n' + '$resp.text') +} + +// Parse a raw HTTP response into a Response object +pub fn parse_response(resp string) ?Response { + version, status_code, status_msg := parse_status_line(resp.all_before('\n')) ? + // Build resp header map and separate the body + start_idx, end_idx := find_headers_range(resp) ? + header := parse_headers(resp.substr(start_idx, end_idx)) ? + mut text := resp.substr(end_idx, resp.len) + if header.get(.transfer_encoding) or { '' } == 'chunked' { + text = chunked.decode(text) + } + return Response{ + http_version: version + status_code: status_code + status_msg: status_msg + header: header + text: text + } +} + +// parse_status_line parses the first HTTP response line into the HTTP +// version, status code, and reason phrase +fn parse_status_line(line string) ?(string, int, string) { + if line.len < 5 || line[..5].to_lower() != 'http/' { + return error('response does not start with HTTP/') + } + data := line.split_nth(' ', 3) + if data.len != 3 { + return error('expected at least 3 tokens') + } + version := data[0].substr(5, data[0].len) + // validate version is 1*DIGIT "." 1*DIGIT + digits := version.split_nth('.', 3) + if digits.len != 2 { + return error('HTTP version malformed') + } + for digit in digits { + strconv.atoi(digit) or { return error('HTTP version must contain only integers') } + } + return version, strconv.atoi(data[1]) ?, data[2] +} + +// cookies parses the Set-Cookie headers into Cookie objects +pub fn (r Response) cookies() []Cookie { + mut cookies := []Cookie{} + for cookie in r.header.values(.set_cookie) { + cookies << parse_cookie(cookie) or { continue } + } + return cookies +} + +// status parses the status_code into a Status struct +pub fn (r Response) status() Status { + return status_from_int(r.status_code) +} + +// set_status sets the status_code and status_msg of the response +pub fn (mut r Response) set_status(s Status) { + r.status_code = s.int() + r.status_msg = s.str() +} + +// version parses the version +pub fn (r Response) version() Version { + return version_from_str('HTTP/$r.http_version') +} + +// set_version sets the http_version string of the response +pub fn (mut r Response) set_version(v Version) { + if v == .unknown { + r.http_version = '' + return + } + maj, min := v.protos() + r.http_version = '${maj}.$min' +} + +pub struct ResponseConfig { + version Version = .v1_1 + status Status = .ok + header Header + text string +} + +// new_response creates a Response object from the configuration. This +// function will add a Content-Length header if text is not empty. +pub fn new_response(conf ResponseConfig) Response { + mut resp := Response{ + text: conf.text + header: conf.header + } + if conf.text.len > 0 && !resp.header.contains(.content_length) { + resp.header.add(.content_length, conf.text.len.str()) + } + resp.set_status(conf.status) + resp.set_version(conf.version) + return resp +} + +// find_headers_range returns the start (inclusive) and end (exclusive) +// index of the headers in the string, including the trailing newlines. This +// helper function expects the first line in `data` to be the HTTP status line +// (HTTP/1.1 200 OK). +fn find_headers_range(data string) ?(int, int) { + start_idx := data.index('\n') or { return error('no start index found') } + 1 + mut count := 0 + for i := start_idx; i < data.len; i++ { + if data[i] == `\n` { + count++ + } else if data[i] != `\r` { + count = 0 + } + if count == 2 { + return start_idx, i + 1 + } + } + return error('no end index found') +} diff --git a/v_windows/v/vlib/net/http/response_test.v b/v_windows/v/vlib/net/http/response_test.v new file mode 100644 index 0000000..bf2fba3 --- /dev/null +++ b/v_windows/v/vlib/net/http/response_test.v @@ -0,0 +1,36 @@ +module http + +fn test_response_bytestr() ? { + { + resp := new_response( + status: .ok + text: 'Foo' + ) + assert resp.bytestr() == 'HTTP/1.1 200 OK\r\n' + 'Content-Length: 3\r\n' + '\r\n' + 'Foo' + } + { + resp := new_response( + status: .found + text: 'Foo' + header: new_header(key: .location, value: '/') + ) + lines := resp.bytestr().split_into_lines() + assert lines[0] == 'HTTP/1.1 302 Found' + // header order is not guaranteed + check_headers(['Location: /', 'Content-Length: 3'], lines[1..3]) ? + assert lines[3] == '' + assert lines[4] == 'Foo' + } +} + +// check_headers is a helper function for asserting all expected headers +// are found because rendered header order is not guaranteed. The check +// is O(n^2) which is fine for small lists. +fn check_headers(expected []string, found []string) ? { + assert expected.len == found.len + for header in expected { + if !found.contains(header) { + return error('expected header "$header" not in $found') + } + } +} diff --git a/v_windows/v/vlib/net/http/server.v b/v_windows/v/vlib/net/http/server.v new file mode 100644 index 0000000..7a9660d --- /dev/null +++ b/v_windows/v/vlib/net/http/server.v @@ -0,0 +1,123 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +import io +import net +import time + +// ServerStatus is the current status of the server. +// .running means that the server is active and serving. +// .stopped means that the server is not active but still listening. +// .closed means that the server is completely inactive. +pub enum ServerStatus { + running + stopped + closed +} + +interface Handler { + handle(Request) Response +} + +pub struct Server { +mut: + state ServerStatus = .closed + listener net.TcpListener +pub mut: + port int = 8080 + handler Handler = DebugHandler{} + read_timeout time.Duration = 30 * time.second + write_timeout time.Duration = 30 * time.second + accept_timeout time.Duration = 30 * time.second +} + +pub fn (mut s Server) listen_and_serve() ? { + if s.handler is DebugHandler { + eprintln('Server handler not set, using debug handler') + } + s.listener = net.listen_tcp(.ip6, ':$s.port') ? + s.listener.set_accept_timeout(s.accept_timeout) + eprintln('Listening on :$s.port') + s.state = .running + for { + // break if we have a stop signal + if s.state != .running { + break + } + mut conn := s.listener.accept() or { + if err.msg != 'net: op timed out' { + eprintln('accept() failed: $err; skipping') + } + continue + } + conn.set_read_timeout(s.read_timeout) + conn.set_write_timeout(s.write_timeout) + // TODO: make concurrent + s.parse_and_respond(mut conn) + } + if s.state == .stopped { + s.close() + } +} + +// stop signals the server that it should not respond anymore +[inline] +pub fn (mut s Server) stop() { + s.state = .stopped +} + +// close immediatly closes the port and signals the server that it has been closed +[inline] +pub fn (mut s Server) close() { + s.state = .closed + s.listener.close() or { return } +} + +[inline] +pub fn (s &Server) status() ServerStatus { + return s.state +} + +fn (s &Server) parse_and_respond(mut conn net.TcpConn) { + defer { + conn.close() or { eprintln('close() failed: $err') } + } + + mut reader := io.new_buffered_reader(reader: conn) + defer { + reader.free() + } + req := parse_request(mut reader) or { + $if debug { + // only show in debug mode to prevent abuse + eprintln('error parsing request: $err') + } + return + } + mut resp := s.handler.handle(req) + if resp.version() == .unknown { + resp.set_version(req.version) + } + conn.write(resp.bytes()) or { eprintln('error sending response: $err') } +} + +// DebugHandler implements the Handler interface by echoing the request +// in the response +struct DebugHandler {} + +fn (d DebugHandler) handle(req Request) Response { + $if debug { + eprintln('[$time.now()] $req.method $req.url\n\r$req.header\n\r$req.data - 200 OK') + } $else { + eprintln('[$time.now()] $req.method $req.url - 200') + } + mut r := Response{ + text: req.data + header: req.header + } + r.set_status(.ok) + r.set_version(req.version) + return r +} diff --git a/v_windows/v/vlib/net/http/server_test.v b/v_windows/v/vlib/net/http/server_test.v new file mode 100644 index 0000000..790da30 --- /dev/null +++ b/v_windows/v/vlib/net/http/server_test.v @@ -0,0 +1,90 @@ +import net.http +import time + +fn test_server_stop() ? { + mut server := &http.Server{ + accept_timeout: 1 * time.second + } + t := go server.listen_and_serve() + time.sleep(250 * time.millisecond) + mut watch := time.new_stopwatch() + server.stop() + assert server.status() == .stopped + assert watch.elapsed() < 100 * time.millisecond + t.wait() ? + assert watch.elapsed() < 999 * time.millisecond +} + +fn test_server_close() ? { + mut server := &http.Server{ + accept_timeout: 1 * time.second + handler: MyHttpHandler{} + } + t := go server.listen_and_serve() + time.sleep(250 * time.millisecond) + mut watch := time.new_stopwatch() + server.close() + assert server.status() == .closed + assert watch.elapsed() < 100 * time.millisecond + t.wait() ? + assert watch.elapsed() < 999 * time.millisecond +} + +struct MyHttpHandler { +mut: + counter int + oks int + not_founds int +} + +fn (mut handler MyHttpHandler) handle(req http.Request) http.Response { + handler.counter++ + // eprintln('$time.now() | counter: $handler.counter | $req.method $req.url\n$req.header\n$req.data - 200 OK\n') + mut r := http.Response{ + text: req.data + ', $req.url' + header: req.header + } + match req.url.all_before('?') { + '/endpoint', '/another/endpoint' { + r.set_status(.ok) + handler.oks++ + } + else { + r.set_status(.not_found) + handler.not_founds++ + } + } + r.set_version(req.version) + return r +} + +const cport = 8198 + +fn test_server_custom_handler() ? { + mut handler := MyHttpHandler{} + mut server := &http.Server{ + accept_timeout: 1 * time.second + handler: handler + port: cport + } + t := go server.listen_and_serve() + for server.status() != .running { + time.sleep(10 * time.millisecond) + } + x := http.fetch(url: 'http://localhost:$cport/endpoint?abc=xyz', data: 'my data') ? + assert x.text == 'my data, /endpoint?abc=xyz' + assert x.status_code == 200 + assert x.http_version == '1.1' + y := http.fetch(url: 'http://localhost:$cport/another/endpoint', data: 'abcde') ? + assert y.text == 'abcde, /another/endpoint' + assert y.status_code == 200 + assert y.status() == .ok + assert y.http_version == '1.1' + // + http.fetch(url: 'http://localhost:$cport/something/else') ? + server.stop() + t.wait() ? + assert handler.counter == 3 + assert handler.oks == 2 + assert handler.not_founds == 1 +} diff --git a/v_windows/v/vlib/net/http/status.v b/v_windows/v/vlib/net/http/status.v new file mode 100644 index 0000000..f4bc9ee --- /dev/null +++ b/v_windows/v/vlib/net/http/status.v @@ -0,0 +1,255 @@ +// Copyright (c) 2020 Justin E. Jones. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +// The status codes listed here are based on the comprehensive list, +// available at: +// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +pub enum Status { + unknown = -1 + unassigned = 0 + cont = 100 + switching_protocols = 101 + processing = 102 + checkpoint_draft = 103 + ok = 200 + created = 201 + accepted = 202 + non_authoritative_information = 203 + no_content = 204 + reset_content = 205 + partial_content = 206 + multi_status = 207 + already_reported = 208 + im_used = 226 + multiple_choices = 300 + moved_permanently = 301 + found = 302 + see_other = 303 + not_modified = 304 + use_proxy = 305 + switch_proxy = 306 + temporary_redirect = 307 + permanent_redirect = 308 + bad_request = 400 + unauthorized = 401 + payment_required = 402 + forbidden = 403 + not_found = 404 + method_not_allowed = 405 + not_acceptable = 406 + proxy_authentication_required = 407 + request_timeout = 408 + conflict = 409 + gone = 410 + length_required = 411 + precondition_failed = 412 + request_entity_too_large = 413 + request_uri_too_long = 414 + unsupported_media_type = 415 + requested_range_not_satisfiable = 416 + expectation_failed = 417 + im_a_teapot = 418 + misdirected_request = 421 + unprocessable_entity = 422 + locked = 423 + failed_dependency = 424 + unordered_collection = 425 + upgrade_required = 426 + precondition_required = 428 + too_many_requests = 429 + request_header_fields_too_large = 431 + unavailable_for_legal_reasons = 451 + client_closed_request = 499 + internal_server_error = 500 + not_implemented = 501 + bad_gateway = 502 + service_unavailable = 503 + gateway_timeout = 504 + http_version_not_supported = 505 + variant_also_negotiates = 506 + insufficient_storage = 507 + loop_detected = 508 + bandwidth_limit_exceeded = 509 + not_extended = 510 + network_authentication_required = 511 +} + +pub fn status_from_int(code int) Status { + return match code { + 100 { Status.cont } + 101 { Status.switching_protocols } + 102 { Status.processing } + 103 { Status.checkpoint_draft } + 104...199 { Status.unassigned } + 200 { Status.ok } + 201 { Status.created } + 202 { Status.accepted } + 203 { Status.non_authoritative_information } + 204 { Status.no_content } + 205 { Status.reset_content } + 206 { Status.partial_content } + 207 { Status.multi_status } + 208 { Status.already_reported } + 209...225 { Status.unassigned } + 226 { Status.im_used } + 227...299 { Status.unassigned } + 300 { Status.multiple_choices } + 301 { Status.moved_permanently } + 302 { Status.found } + 303 { Status.see_other } + 304 { Status.not_modified } + 305 { Status.use_proxy } + 306 { Status.switch_proxy } + 307 { Status.temporary_redirect } + 308 { Status.permanent_redirect } + 309...399 { Status.unassigned } + 400 { Status.bad_request } + 401 { Status.unauthorized } + 402 { Status.payment_required } + 403 { Status.forbidden } + 404 { Status.not_found } + 405 { Status.method_not_allowed } + 406 { Status.not_acceptable } + 407 { Status.proxy_authentication_required } + 408 { Status.request_timeout } + 409 { Status.conflict } + 410 { Status.gone } + 411 { Status.length_required } + 412 { Status.precondition_failed } + 413 { Status.request_entity_too_large } + 414 { Status.request_uri_too_long } + 415 { Status.unsupported_media_type } + 416 { Status.requested_range_not_satisfiable } + 417 { Status.expectation_failed } + 418 { Status.im_a_teapot } + 419...420 { Status.unassigned } + 421 { Status.misdirected_request } + 422 { Status.unprocessable_entity } + 423 { Status.locked } + 424 { Status.failed_dependency } + 425 { Status.unordered_collection } + 426 { Status.upgrade_required } + 428 { Status.precondition_required } + 429 { Status.too_many_requests } + 431 { Status.request_header_fields_too_large } + 432...450 { Status.unassigned } + 451 { Status.unavailable_for_legal_reasons } + 452...499 { Status.unassigned } + 500 { Status.internal_server_error } + 501 { Status.not_implemented } + 502 { Status.bad_gateway } + 503 { Status.service_unavailable } + 504 { Status.gateway_timeout } + 505 { Status.http_version_not_supported } + 506 { Status.variant_also_negotiates } + 507 { Status.insufficient_storage } + 508 { Status.loop_detected } + 509 { Status.bandwidth_limit_exceeded } + 510 { Status.not_extended } + 511 { Status.network_authentication_required } + 512...599 { Status.unassigned } + else { Status.unknown } + } +} + +pub fn (code Status) str() string { + return match code { + .cont { 'Continue' } + .switching_protocols { 'Switching Protocols' } + .processing { 'Processing' } + .checkpoint_draft { 'Checkpoint Draft' } + .ok { 'OK' } + .created { 'Created' } + .accepted { 'Accepted' } + .non_authoritative_information { 'Non Authoritative Information' } + .no_content { 'No Content' } + .reset_content { 'Reset Content' } + .partial_content { 'Partial Content' } + .multi_status { 'Multi Status' } + .already_reported { 'Already Reported' } + .im_used { 'IM Used' } + .multiple_choices { 'Multiple Choices' } + .moved_permanently { 'Moved Permanently' } + .found { 'Found' } + .see_other { 'See Other' } + .not_modified { 'Not Modified' } + .use_proxy { 'Use Proxy' } + .switch_proxy { 'Switch Proxy' } + .temporary_redirect { 'Temporary Redirect' } + .permanent_redirect { 'Permanent Redirect' } + .bad_request { 'Bad Request' } + .unauthorized { 'Unauthorized' } + .payment_required { 'Payment Required' } + .forbidden { 'Forbidden' } + .not_found { 'Not Found' } + .method_not_allowed { 'Method Not Allowed' } + .not_acceptable { 'Not Acceptable' } + .proxy_authentication_required { 'Proxy Authentication Required' } + .request_timeout { 'Request Timeout' } + .conflict { 'Conflict' } + .gone { 'Gone' } + .length_required { 'Length Required' } + .precondition_failed { 'Precondition Failed' } + .request_entity_too_large { 'Request Entity Too Large' } + .request_uri_too_long { 'Request URI Too Long' } + .unsupported_media_type { 'Unsupported Media Type' } + .requested_range_not_satisfiable { 'Requested Range Not Satisfiable' } + .expectation_failed { 'Expectation Failed' } + .im_a_teapot { 'Im a teapot' } + .misdirected_request { 'Misdirected Request' } + .unprocessable_entity { 'Unprocessable Entity' } + .locked { 'Locked' } + .failed_dependency { 'Failed Dependency' } + .unordered_collection { 'Unordered Collection' } + .upgrade_required { 'Upgrade Required' } + .precondition_required { 'Precondition Required' } + .too_many_requests { 'Too Many Requests' } + .request_header_fields_too_large { 'Request Header Fields Too Large' } + .unavailable_for_legal_reasons { 'Unavailable For Legal Reasons' } + .internal_server_error { 'Internal Server Error' } + .not_implemented { 'Not Implemented' } + .bad_gateway { 'Bad Gateway' } + .service_unavailable { 'Service Unavailable' } + .gateway_timeout { 'Gateway Timeout' } + .http_version_not_supported { 'HTTP Version Not Supported' } + .variant_also_negotiates { 'Variant Also Negotiates' } + .insufficient_storage { 'Insufficient Storage' } + .loop_detected { 'Loop Detected' } + .bandwidth_limit_exceeded { 'Bandwidth Limit Exceeded' } + .not_extended { 'Not Extended' } + .network_authentication_required { 'Network Authentication Required' } + .unassigned { 'Unassigned' } + else { 'Unknown' } + } +} + +// int converts an assigned and known Status to its integral equivalent. +// if a Status is unknown or unassigned, this method will return zero +pub fn (code Status) int() int { + if code in [.unknown, .unassigned] { + return 0 + } + return int(code) +} + +// is_valid returns true if the status code is assigned and known +pub fn (code Status) is_valid() bool { + number := code.int() + return number >= 100 && number < 600 +} + +// is_error will return true if the status code represents either a client or +// a server error; otherwise will return false +pub fn (code Status) is_error() bool { + number := code.int() + return number >= 400 && number < 600 +} + +// is_success will return true if the status code represents either an +// informational, success, or redirection response; otherwise will return false +pub fn (code Status) is_success() bool { + number := code.int() + return number >= 100 && number < 400 +} diff --git a/v_windows/v/vlib/net/http/status_test.v b/v_windows/v/vlib/net/http/status_test.v new file mode 100644 index 0000000..154aec3 --- /dev/null +++ b/v_windows/v/vlib/net/http/status_test.v @@ -0,0 +1,49 @@ +module http + +fn test_str() { + code := Status.bad_gateway + actual := code.str() + assert actual == 'Bad Gateway' +} + +fn test_int() { + code := Status.see_other + actual := code.int() + assert actual == 303 +} + +fn test_is_valid() { + code := Status.gateway_timeout + actual := code.is_valid() + assert actual == true +} + +fn test_is_valid_negative() { + code := Status.unassigned + actual := code.is_valid() + assert actual == false +} + +fn test_is_error() { + code := Status.too_many_requests + actual := code.is_error() + assert actual == true +} + +fn test_is_error_negative() { + code := Status.cont + actual := code.is_error() + assert actual == false +} + +fn test_is_success() { + code := Status.accepted + actual := code.is_success() + assert actual == true +} + +fn test_is_success_negative() { + code := Status.forbidden + actual := code.is_success() + assert actual == false +} diff --git a/v_windows/v/vlib/net/http/version.v b/v_windows/v/vlib/net/http/version.v new file mode 100644 index 0000000..f4388a3 --- /dev/null +++ b/v_windows/v/vlib/net/http/version.v @@ -0,0 +1,40 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module http + +// The versions listed here are the most common ones. +pub enum Version { + unknown + v1_1 + v2_0 + v1_0 +} + +pub fn (v Version) str() string { + return match v { + .v1_1 { 'HTTP/1.1' } + .v2_0 { 'HTTP/2.0' } + .v1_0 { 'HTTP/1.0' } + .unknown { 'unknown' } + } +} + +pub fn version_from_str(v string) Version { + return match v.to_lower() { + 'http/1.1' { Version.v1_1 } + 'http/2.0' { Version.v2_0 } + 'http/1.0' { Version.v1_0 } + else { Version.unknown } + } +} + +// protos returns the version major and minor numbers +pub fn (v Version) protos() (int, int) { + match v { + .v1_1 { return 1, 1 } + .v2_0 { return 2, 0 } + .v1_0 { return 1, 0 } + .unknown { return 0, 0 } + } +} diff --git a/v_windows/v/vlib/net/ipv6_v6only.h b/v_windows/v/vlib/net/ipv6_v6only.h new file mode 100644 index 0000000..79393df --- /dev/null +++ b/v_windows/v/vlib/net/ipv6_v6only.h @@ -0,0 +1,5 @@ +#if !defined(IPV6_V6ONLY) + +#define IPV6_V6ONLY 27 + +#endif diff --git a/v_windows/v/vlib/net/net_nix.c.v b/v_windows/v/vlib/net/net_nix.c.v new file mode 100644 index 0000000..a9fa531 --- /dev/null +++ b/v_windows/v/vlib/net/net_nix.c.v @@ -0,0 +1,26 @@ +module net + +#include +#include +// inet.h is needed for inet_ntop on macos +#include +#include +#include +#include + +#flag solaris -lsocket + +fn error_code() int { + return C.errno +} + +fn init() { +} + +pub const ( + msg_nosignal = 0x4000 +) + +const ( + error_ewouldblock = C.EWOULDBLOCK +) diff --git a/v_windows/v/vlib/net/net_windows.c.v b/v_windows/v/vlib/net/net_windows.c.v new file mode 100644 index 0000000..337176f --- /dev/null +++ b/v_windows/v/vlib/net/net_windows.c.v @@ -0,0 +1,780 @@ +module net + +// WsaError is all of the socket errors that WSA provides from WSAGetLastError +pub enum WsaError { + // + // MessageId: WSAEINTR + // + // MessageText: + // + // A blocking operation was interrupted by a call to WSACancelBlockingCall. + // + wsaeintr = 10004 + // + // MessageId: WSAEBADF + // + // MessageText: + // + // The file handle supplied is not valid. + // + wsaebadf = 10009 + // + // MessageId: WSAEACCES + // + // MessageText: + // + // An attempt was made to access a socket in a way forbidden by its access permissions. + // + wsaeacces = 10013 + // + // MessageId: WSAEFAULT + // + // MessageText: + // + // The system detected an invalid pointer address in attempting to use a pointer argument in a call. + // + wsaefault = 10014 + // + // MessageId: WSAEINVAL + // + // MessageText: + // + // An invalid argument was supplied. + // + wsaeinval = 10022 + // + // MessageId: WSAEMFILE + // + // MessageText: + // + // Too many open sockets. + // + wsaemfile = 10024 + // + // MessageId: WSAEWOULDBLOCK + // + // MessageText: + // + // A non-blocking socket operation could not be completed immediately. + // + wsaewouldblock = 10035 + // + // MessageId: WSAEINPROGRESS + // + // MessageText: + // + // A blocking operation is currently executing. + // + wsaeinprogress = 10036 + // + // MessageId: WSAEALREADY + // + // MessageText: + // + // An operation was attempted on a non-blocking socket that already had an operation in progress. + // + wsaealready = 10037 + // + // MessageId: WSAENOTSOCK + // + // MessageText: + // + // An operation was attempted on something that is not a socket. + // + wsaenotsock = 10038 + // + // MessageId: WSAEDESTADDRREQ + // + // MessageText: + // + // A required address was omitted from an operation on a socket. + // + wsaedestaddrreq = 10039 + // + // MessageId: WSAEMSGSIZE + // + // MessageText: + // + // A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. + // + wsaemsgsize = 10040 + // + // MessageId: WSAEPROTOTYPE + // + // MessageText: + // + // A protocol was specified in the socket function call that does not support the semantics of the socket type requested. + // + wsaeprototype = 10041 + // + // MessageId: WSAENOPROTOOPT + // + // MessageText: + // + // An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call. + // + wsaenoprotoopt = 10042 + // + // MessageId: WSAEPROTONOSUPPORT + // + // MessageText: + // + // The requested protocol has not been configured into the system, or no implementation for it exists. + // + wsaeprotonosupport = 10043 + // + // MessageId: WSAESOCKTNOSUPPORT + // + // MessageText: + // + // The support for the specified socket type does not exist in this address family. + // + wsaesocktnosupport = 10044 + // + // MessageId: WSAEOPNOTSUPP + // + // MessageText: + // + // The attempted operation is not supported for the type of object referenced. + // + wsaeopnotsupp = 10045 + // + // MessageId: WSAEPFNOSUPPORT + // + // MessageText: + // + // The protocol family has not been configured into the system or no implementation for it exists. + // + wsaepfnosupport = 10046 + // + // MessageId: WSAEAFNOSUPPORT + // + // MessageText: + // + // An address incompatible with the requested protocol was used. + // + wsaeafnosupport = 10047 + // + // MessageId: WSAEADDRINUSE + // + // MessageText: + // + // Only one usage of each socket address (protocol/network address/port) is normally permitted. + // + wsaeaddrinuse = 10048 + // + // MessageId: WSAEADDRNOTAVAIL + // + // MessageText: + // + // The requested address is not valid in its context. + // + wsaeaddrnotavail = 10049 + // + // MessageId: WSAENETDOWN + // + // MessageText: + // + // A socket operation encountered a dead network. + // + wsaenetdown = 10050 + // + // MessageId: WSAENETUNREACH + // + // MessageText: + // + // A socket operation was attempted to an unreachable network. + // + wsaenetunreach = 10051 + // + // MessageId: WSAENETRESET + // + // MessageText: + // + // The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. + // + wsaenetreset = 10052 + // + // MessageId: WSAECONNABORTED + // + // MessageText: + // + // An established connection was aborted by the software in your host machine. + // + wsaeconnaborted = 10053 + // + // MessageId: WSAECONNRESET + // + // MessageText: + // + // An existing connection was forcibly closed by the remote host. + // + wsaeconnreset = 10054 + // + // MessageId: WSAENOBUFS + // + // MessageText: + // + // An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. + // + wsaenobufs = 10055 + // + // MessageId: WSAEISCONN + // + // MessageText: + // + // A connect request was made on an already connected socket. + // + wsaeisconn = 10056 + // + // MessageId: WSAENOTCONN + // + // MessageText: + // + // A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. + // + wsaenotconn = 10057 + // + // MessageId: WSAESHUTDOWN + // + // MessageText: + // + // A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call. + // + wsaeshutdown = 10058 + // + // MessageId: WSAETOOMANYREFS + // + // MessageText: + // + // Too many references to some kernel object. + // + wsaetoomanyrefs = 10059 + // + // MessageId: WSAETIMEDOUT + // + // MessageText: + // + // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + // + wsaetimedout = 10060 + // + // MessageId: WSAECONNREFUSED + // + // MessageText: + // + // No connection could be made because the target machine actively refused it. + // + wsaeconnrefused = 10061 + // + // MessageId: WSAELOOP + // + // MessageText: + // + // Cannot translate name. + // + wsaeloop = 10062 + // + // MessageId: WSAENAMETOOLONG + // + // MessageText: + // + // Name component or name was too long. + // + wsaenametoolong = 10063 + // + // MessageId: WSAEHOSTDOWN + // + // MessageText: + // + // A socket operation failed because the destination host was down. + // + wsaehostdown = 10064 + // + // MessageId: WSAEHOSTUNREACH + // + // MessageText: + // + // A socket operation was attempted to an unreachable host. + // + wsaehostunreach = 10065 + // + // MessageId: WSAENOTEMPTY + // + // MessageText: + // + // Cannot remove a directory that is not empty. + // + wsaenotempty = 10066 + // + // MessageId: WSAEPROCLIM + // + // MessageText: + // + // A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously. + // + wsaeproclim = 10067 + // + // MessageId: WSAEUSERS + // + // MessageText: + // + // Ran out of quota. + // + wsaeusers = 10068 + // + // MessageId: WSAEDQUOT + // + // MessageText: + // + // Ran out of disk quota. + // + wsaedquot = 10069 + // + // MessageId: WSAESTALE + // + // MessageText: + // + // File handle reference is no longer available. + // + wsaestale = 10070 + // + // MessageId: WSAEREMOTE + // + // MessageText: + // + // Item is not available locally. + // + wsaeremote = 10071 + // + // MessageId: WSASYSNOTREADY + // + // MessageText: + // + // WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable. + // + wsasysnotready = 10091 + // + // MessageId: WSAVERNOTSUPPORTED + // + // MessageText: + // + // The Windows Sockets version requested is not supported. + // + wsavernotsupported = 10092 + // + // MessageId: WSANOTINITIALISED + // + // MessageText: + // + // Either the application has not called WSAStartup, or WSAStartup failed. + // + wsanotinitialised = 10093 + // + // MessageId: WSAEDISCON + // + // MessageText: + // + // Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence. + // + wsaediscon = 10101 + // + // MessageId: WSAENOMORE + // + // MessageText: + // + // No more results can be returned by WSALookupServiceNext. + // + wsaenomore = 10102 + // + // MessageId: WSAECANCELLED + // + // MessageText: + // + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + // + wsaecancelled = 10103 + // + // MessageId: WSAEINVALIDPROCTABLE + // + // MessageText: + // + // The procedure call table is invalid. + // + wsaeinvalidproctable = 10104 + // + // MessageId: WSAEINVALIDPROVIDER + // + // MessageText: + // + // The requested service provider is invalid. + // + wsaeinvalidprovider = 10105 + // + // MessageId: WSAEPROVIDERFAILEDINIT + // + // MessageText: + // + // The requested service provider could not be loaded or initialized. + // + wsaeproviderfailedinit = 10106 + // + // MessageId: WSASYSCALLFAILURE + // + // MessageText: + // + // A system call has failed. + // + wsasyscallfailure = 10107 + // + // MessageId: WSASERVICE_NOT_FOUND + // + // MessageText: + // + // No such service is known. The service cannot be found in the specified name space. + // + wsaservice_not_found = 10108 + // + // MessageId: WSATYPE_NOT_FOUND + // + // MessageText: + // + // The specified class was not found. + // + wsatype_not_found = 10109 + // + // MessageId: WSA_E_NO_MORE + // + // MessageText: + // + // No more results can be returned by WSALookupServiceNext. + // + wsa_e_no_more = 10110 + // + // MessageId: WSA_E_CANCELLED + // + // MessageText: + // + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + // + wsa_e_cancelled = 10111 + // + // MessageId: WSAEREFUSED + // + // MessageText: + // + // A database query failed because it was actively refused. + // + wsaerefused = 10112 + // + // MessageId: WSAHOST_NOT_FOUND + // + // MessageText: + // + // No such host is known. + // + wsahost_not_found = 11001 + // + // MessageId: WSATRY_AGAIN + // + // MessageText: + // + // This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server. + // + wsatry_again = 11002 + // + // MessageId: WSANO_RECOVERY + // + // MessageText: + // + // A non-recoverable error occurred during a database lookup. + // + wsano_recovery = 11003 + // + // MessageId: WSANO_DATA + // + // MessageText: + // + // The requested name is valid, but no data of the requested type was found. + // + wsano_data = 11004 + // + // MessageId: WSA_QOS_RECEIVERS + // + // MessageText: + // + // At least one reserve has arrived. + // + wsa_qos_receivers = 11005 + // + // MessageId: WSA_QOS_SENDERS + // + // MessageText: + // + // At least one path has arrived. + // + wsa_qos_senders = 11006 + // + // MessageId: WSA_QOS_NO_SENDERS + // + // MessageText: + // + // There are no senders. + // + wsa_qos_no_senders = 11007 + // + // MessageId: WSA_QOS_NO_RECEIVERS + // + // MessageText: + // + // There are no receivers. + // + wsa_qos_no_receivers = 11008 + // + // MessageId: WSA_QOS_REQUEST_CONFIRMED + // + // MessageText: + // + // Reserve has been confirmed. + // + wsa_qos_request_confirmed = 11009 + // + // MessageId: WSA_QOS_ADMISSION_FAILURE + // + // MessageText: + // + // Error due to lack of resources. + // + wsa_qos_admission_failure = 11010 + // + // MessageId: WSA_QOS_POLICY_FAILURE + // + // MessageText: + // + // Rejected for administrative reasons - bad credentials. + // + wsa_qos_policy_failure = 11011 + // + // MessageId: WSA_QOS_BAD_STYLE + // + // MessageText: + // + // Unknown or conflicting style. + // + wsa_qos_bad_style = 11012 + // + // MessageId: WSA_QOS_BAD_OBJECT + // + // MessageText: + // + // Problem with some part of the filterspec or providerspecific buffer in general. + // + wsa_qos_bad_object = 11013 + // + // MessageId: WSA_QOS_TRAFFIC_CTRL_ERROR + // + // MessageText: + // + // Problem with some part of the flowspec. + // + wsa_qos_traffic_ctrl_error = 11014 + // + // MessageId: WSA_QOS_GENERIC_ERROR + // + // MessageText: + // + // General QOS error. + // + wsa_qos_generic_error = 11015 + // + // MessageId: WSA_QOS_ESERVICETYPE + // + // MessageText: + // + // An invalid or unrecognized service type was found in the flowspec. + // + wsa_qos_eservicetype = 11016 + // + // MessageId: WSA_QOS_EFLOWSPEC + // + // MessageText: + // + // An invalid or inconsistent flowspec was found in the QOS structure. + // + wsa_qos_eflowspec = 11017 + // + // MessageId: WSA_QOS_EPROVSPECBUF + // + // MessageText: + // + // Invalid QOS provider-specific buffer. + // + wsa_qos_eprovspecbuf = 11018 + // + // MessageId: WSA_QOS_EFILTERSTYLE + // + // MessageText: + // + // An invalid QOS filter style was used. + // + wsa_qos_efilterstyle = 11019 + // + // MessageId: WSA_QOS_EFILTERTYPE + // + // MessageText: + // + // An invalid QOS filter type was used. + // + wsa_qos_efiltertype = 11020 + // + // MessageId: WSA_QOS_EFILTERCOUNT + // + // MessageText: + // + // An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR. + // + wsa_qos_efiltercount = 11021 + // + // MessageId: WSA_QOS_EOBJLENGTH + // + // MessageText: + // + // An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer. + // + wsa_qos_eobjlength = 11022 + // + // MessageId: WSA_QOS_EFLOWCOUNT + // + // MessageText: + // + // An incorrect number of flow descriptors was specified in the QOS structure. + // + wsa_qos_eflowcount = 11023 + // + // MessageId: WSA_QOS_EUNKOWNPSOBJ + // + // MessageText: + // + // An unrecognized object was found in the QOS provider-specific buffer. + // + wsa_qos_eunkownpsobj = 11024 + // + // MessageId: WSA_QOS_EPOLICYOBJ + // + // MessageText: + // + // An invalid policy object was found in the QOS provider-specific buffer. + // + wsa_qos_epolicyobj = 11025 + // + // MessageId: WSA_QOS_EFLOWDESC + // + // MessageText: + // + // An invalid QOS flow descriptor was found in the flow descriptor list. + // + wsa_qos_eflowdesc = 11026 + // + // MessageId: WSA_QOS_EPSFLOWSPEC + // + // MessageText: + // + // An invalid or inconsistent flowspec was found in the QOS provider specific buffer. + // + wsa_qos_epsflowspec = 11027 + // + // MessageId: WSA_QOS_EPSFILTERSPEC + // + // MessageText: + // + // An invalid FILTERSPEC was found in the QOS provider-specific buffer. + // + wsa_qos_epsfilterspec = 11028 + // + // MessageId: WSA_QOS_ESDMODEOBJ + // + // MessageText: + // + // An invalid shape discard mode object was found in the QOS provider specific buffer. + // + wsa_qos_esdmodeobj = 11029 + // + // MessageId: WSA_QOS_ESHAPERATEOBJ + // + // MessageText: + // + // An invalid shaping rate object was found in the QOS provider-specific buffer. + // + wsa_qos_eshaperateobj = 11030 + // + // MessageId: WSA_QOS_RESERVED_PETYPE + // + // MessageText: + // + // A reserved policy element was found in the QOS provider-specific buffer. + // + wsa_qos_reserved_petype = 11031 + // + // MessageId: WSA_SECURE_HOST_NOT_FOUND + // + // MessageText: + // + // No such host is known securely. + // + wsa_secure_host_not_found = 11032 + // + // MessageId: WSA_IPSEC_NAME_POLICY_ERROR + // + // MessageText: + // + // Name based IPSEC policy could not be added. + // + wsa_ipsec_name_policy_error = 11033 +} + +// wsa_error casts an int to its WsaError value +pub fn wsa_error(code int) WsaError { + return WsaError(code) +} + +const ( + error_ewouldblock = WsaError.wsaewouldblock +) + +// Link to Winsock library +#flag -lws2_32 +#include +#include + +// Constants that windows needs +const ( + fionbio = C.FIONBIO + msg_nosignal = 0 + wsa_v22 = 0x202 // C.MAKEWORD(2, 2) +) + +// Error code returns the last socket error +fn error_code() int { + return C.WSAGetLastError() +} + +struct C.WSAData { +mut: + wVersion u16 + wHighVersion u16 + szDescription [257]byte + szSystemStatus [129]byte + iMaxSockets u16 + iMaxUdpDg u16 + lpVendorInfo &byte +} + +fn init() { + mut wsadata := C.WSAData{ + lpVendorInfo: 0 + } + res := C.WSAStartup(net.wsa_v22, &wsadata) + if res != 0 { + panic('socket: WSAStartup failed') + } +} diff --git a/v_windows/v/vlib/net/openssl/c.v b/v_windows/v/vlib/net/openssl/c.v new file mode 100644 index 0000000..dedba2a --- /dev/null +++ b/v_windows/v/vlib/net/openssl/c.v @@ -0,0 +1,120 @@ +module openssl + +// On Linux, prefer a localy built openssl, because it is +// much more likely for it to be newer, than the system +// openssl from libssl-dev. If there is no local openssl, +// the next flag is harmless, since it will still use the +// (older) system openssl. +#flag linux -I/usr/local/include/openssl -L/usr/local/lib +#flag windows -l libssl -l libcrypto +#flag -lssl -lcrypto +#flag linux -ldl -lpthread +// MacPorts +#flag darwin -I/opt/local/include +#flag darwin -L/opt/local/lib +// Brew +#flag darwin -I/usr/local/opt/openssl/include +#flag darwin -L/usr/local/opt/openssl/lib +// Brew arm64 +#flag darwin -I /opt/homebrew/opt/openssl/include +#flag darwin -L /opt/homebrew/opt/openssl/lib +// +#include # Please install OpenSSL development headers +#include +#include + +pub struct C.SSL { +} + +pub struct SSL_CTX { +} + +pub struct SSL { +} + +pub struct SSL_METHOD { +} + +pub struct OPENSSL_INIT_SETTINGS { +} + +fn C.BIO_new_ssl_connect(ctx &C.SSL_CTX) &C.BIO + +fn C.BIO_set_conn_hostname(b &C.BIO, name &char) int + +// there are actually 2 macros for BIO_get_ssl +// fn C.BIO_get_ssl(bp &C.BIO, ssl charptr, c int) +// fn C.BIO_get_ssl(bp &C.BIO, sslp charptr) +fn C.BIO_get_ssl(bp &C.BIO, vargs ...voidptr) + +fn C.BIO_do_connect(b &C.BIO) int + +fn C.BIO_do_handshake(b &C.BIO) int + +fn C.BIO_puts(b &C.BIO, buf &char) + +fn C.BIO_read(b &C.BIO, buf voidptr, len int) int + +fn C.BIO_free_all(a &C.BIO) + +fn C.SSL_CTX_new(method &C.SSL_METHOD) &C.SSL_CTX + +fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options int) + +fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth int) + +fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, ca_file &char, ca_path &char) int + +fn C.SSL_CTX_free(ctx &C.SSL_CTX) + +fn C.SSL_new(&C.SSL_CTX) &C.SSL + +fn C.SSL_set_fd(ssl &C.SSL, fd int) int + +fn C.SSL_connect(&C.SSL) int + +fn C.SSL_set_cipher_list(ctx &SSL, str &char) int + +fn C.SSL_get_peer_certificate(ssl &SSL) &C.X509 + +fn C.ERR_clear_error() + +fn C.SSL_get_error(ssl &C.SSL, ret int) int + +fn C.SSL_get_verify_result(ssl &SSL) int + +fn C.SSL_set_tlsext_host_name(s &SSL, name &char) int + +fn C.SSL_shutdown(&C.SSL) int + +fn C.SSL_free(&C.SSL) + +fn C.SSL_write(ssl &C.SSL, buf voidptr, buflen int) int + +fn C.SSL_read(ssl &C.SSL, buf voidptr, buflen int) int + +fn C.SSL_load_error_strings() + +fn C.SSL_library_init() int + +fn C.SSLv23_client_method() &C.SSL_METHOD + +fn C.TLS_method() voidptr + +fn C.TLSv1_2_method() voidptr + +fn C.OPENSSL_init_ssl(opts u64, settings &OPENSSL_INIT_SETTINGS) int + +fn init() { + $if ssl_pre_1_1_version ? { + // OPENSSL_VERSION_NUMBER < 0x10100000L + C.SSL_load_error_strings() + C.SSL_library_init() + } $else { + C.OPENSSL_init_ssl(C.OPENSSL_INIT_LOAD_SSL_STRINGS, 0) + } +} + +pub const ( + is_used = 1 +) diff --git a/v_windows/v/vlib/net/openssl/openssl.v b/v_windows/v/vlib/net/openssl/openssl.v new file mode 100644 index 0000000..ffcabf5 --- /dev/null +++ b/v_windows/v/vlib/net/openssl/openssl.v @@ -0,0 +1,32 @@ +module openssl + +// ssl_error returns non error ssl code or error if unrecoverable and we should panic +pub fn ssl_error(ret int, ssl voidptr) ?SSLError { + res := C.SSL_get_error(ssl, ret) + match SSLError(res) { + .ssl_error_syscall { + return error_with_code('unrecoverable syscall ($res)', res) + } + .ssl_error_ssl { + return error_with_code('unrecoverable ssl protocol error ($res)', res) + } + else { + return SSLError(res) + } + } +} + +pub enum SSLError { + ssl_error_none = 0 // SSL_ERROR_NONE + ssl_error_ssl = 1 // SSL_ERROR_SSL + ssl_error_want_read = 2 // SSL_ERROR_WANT_READ + ssl_error_want_write = 3 // SSL_ERROR_WANT_WRITE + ssl_error_want_x509_lookup = 4 // SSL_ERROR_WANT_X509_LOOKUP + ssl_error_syscall = 5 // SSL_ERROR_SYSCALL + ssl_error_zero_return = 6 // SSL_ERROR_ZERO_RETURN + ssl_error_want_connect = 7 // SSL_ERROR_WANT_CONNECT + ssl_error_want_accept = 8 // SSL_ERROR_WANT_ACCEPT + ssl_error_want_async = 9 // SSL_ERROR_WANT_ASYNC + ssl_error_want_async_job = 10 // SSL_ERROR_WANT_ASYNC_JOB + ssl_error_want_early = 11 // SSL_ERROR_WANT_EARLY +} diff --git a/v_windows/v/vlib/net/openssl/ssl_connection.v b/v_windows/v/vlib/net/openssl/ssl_connection.v new file mode 100644 index 0000000..58f47f6 --- /dev/null +++ b/v_windows/v/vlib/net/openssl/ssl_connection.v @@ -0,0 +1,268 @@ +module openssl + +import net +import time + +// SSLConn is the current connection +pub struct SSLConn { +mut: + sslctx &C.SSL_CTX + ssl &C.SSL + handle int + duration time.Duration +} + +// new_ssl_conn instance an new SSLCon struct +pub fn new_ssl_conn() &SSLConn { + return &SSLConn{ + sslctx: 0 + ssl: 0 + handle: 0 + } +} + +// Select operation +enum Select { + read + write + except +} + +// shutdown closes the ssl connection and do clean up +pub fn (mut s SSLConn) shutdown() ? { + if s.ssl != 0 { + mut res := 0 + for { + res = C.SSL_shutdown(voidptr(s.ssl)) + if res < 0 { + err_res := ssl_error(res, s.ssl) or { + break // We break to free rest of resources + } + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else { + unsafe { C.SSL_free(voidptr(s.ssl)) } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } + return error('unexepedted ssl error $err_res') + } + if s.ssl != 0 { + unsafe { C.SSL_free(voidptr(s.ssl)) } + } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } + return error('Could not connect using SSL. ($err_res),err') + } else if res == 0 { + continue + } else if res == 1 { + break + } + } + C.SSL_free(voidptr(s.ssl)) + } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } +} + +// connect to server using open ssl +pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ? { + s.handle = tcp_conn.sock.handle + s.duration = tcp_conn.read_timeout() + + s.sslctx = unsafe { C.SSL_CTX_new(C.SSLv23_client_method()) } + if s.sslctx == 0 { + return error("Couldn't get ssl context") + } + + // TODO: Fix option to enable/disable checks for valid + // certificates to allow both secure and self signed + // for now the checks are not done at all to comply + // to current autobahn tests + + // C.SSL_CTX_set_verify_depth(s.sslctx, 4) + // flags := C.SSL_OP_NO_SSLv2 | C.SSL_OP_NO_SSLv3 | C.SSL_OP_NO_COMPRESSION + // C.SSL_CTX_set_options(s.sslctx, flags) + // mut res := C.SSL_CTX_load_verify_locations(s.sslctx, 'random-org-chain.pem', 0) + + s.ssl = unsafe { &C.SSL(C.SSL_new(s.sslctx)) } + if s.ssl == 0 { + return error("Couldn't create OpenSSL instance.") + } + + // preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4' + // mut res := C.SSL_set_cipher_list(s.ssl, preferred_ciphers.str) + // if res != 1 { + // println('http: openssl: cipher failed') + // } + + mut res := C.SSL_set_tlsext_host_name(voidptr(s.ssl), voidptr(hostname.str)) + if res != 1 { + return error('cannot set host name') + } + + if C.SSL_set_fd(voidptr(s.ssl), tcp_conn.sock.handle) != 1 { + return error("Couldn't assign ssl to socket.") + } + for { + res = C.SSL_connect(voidptr(s.ssl)) + if res != 1 { + err_res := ssl_error(res, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } + return error('Could not connect using SSL. ($err_res),err') + } + break + } +} + +pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &byte, len int) ?int { + mut res := 0 + for { + res = C.SSL_read(voidptr(s.ssl), buf_ptr, len) + if res < 0 { + err_res := ssl_error(res, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_zero_return { + return 0 + } + return error('Could not read using SSL. ($err_res)') + } + break + } + return res +} + +pub fn (mut s SSLConn) read_into(mut buffer []byte) ?int { + res := s.socket_read_into_ptr(&byte(buffer.data), buffer.len) ? + return res +} + +// write number of bytes to SSL connection +pub fn (mut s SSLConn) write(bytes []byte) ?int { + unsafe { + mut ptr_base := &byte(bytes.data) + mut total_sent := 0 + for total_sent < bytes.len { + ptr := ptr_base + total_sent + remaining := bytes.len - total_sent + mut sent := C.SSL_write(voidptr(s.ssl), ptr, remaining) + if sent <= 0 { + err_res := ssl_error(sent, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_zero_return { + return error('ssl write on closed connection') // Todo error_with_code close + } + return error_with_code('Could not write SSL. ($err_res),err', int(err_res)) + } + total_sent += sent + } + return total_sent + } +} + +/* +This is basically a copy of Emily socket implementation of select. + This have to be consolidated into common net lib features + when merging this to V +*/ +// [typedef] +// pub struct C.fd_set { +// } + +// Select waits for an io operation (specified by parameter `test`) to be available +fn @select(handle int, test Select, timeout time.Duration) ?bool { + set := C.fd_set{} + + C.FD_ZERO(&set) + C.FD_SET(handle, &set) + + seconds := timeout.milliseconds() / 1000 + microseconds := timeout - (seconds * time.second) + mut tt := C.timeval{ + tv_sec: u64(seconds) + tv_usec: u64(microseconds) + } + + mut timeval_timeout := &tt + + // infinite timeout is signaled by passing null as the timeout to + // select + if timeout == net.infinite_timeout { + timeval_timeout = &C.timeval(0) + } + + match test { + .read { + net.socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout)) ? + } + .write { + net.socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout)) ? + } + .except { + net.socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout)) ? + } + } + + return C.FD_ISSET(handle, &set) +} diff --git a/v_windows/v/vlib/net/smtp/smtp.v b/v_windows/v/vlib/net/smtp/smtp.v new file mode 100644 index 0000000..50c537c --- /dev/null +++ b/v_windows/v/vlib/net/smtp/smtp.v @@ -0,0 +1,190 @@ +module smtp + +/* +* +* smtp module +* Created by: nedimf (07/2020) +*/ +import net +import encoding.base64 +import strings +import time +import io + +const ( + recv_size = 128 +) + +enum ReplyCode { + ready = 220 + close = 221 + auth_ok = 235 + action_ok = 250 + mail_start = 354 +} + +pub enum BodyType { + text + html +} + +pub struct Client { +mut: + conn net.TcpConn + reader io.BufferedReader +pub: + server string + port int = 25 + username string + password string + from string +pub mut: + is_open bool +} + +pub struct Mail { + from string + to string + cc string + bcc string + date time.Time = time.now() + subject string + body_type BodyType + body string +} + +// new_client returns a new SMTP client and connects to it +pub fn new_client(config Client) ?&Client { + mut c := &Client{ + ...config + } + c.reconnect() ? + return c +} + +// reconnect reconnects to the SMTP server if the connection was closed +pub fn (mut c Client) reconnect() ? { + if c.is_open { + return error('Already connected to server') + } + + conn := net.dial_tcp('$c.server:$c.port') or { return error('Connecting to server failed') } + c.conn = conn + + c.reader = io.new_buffered_reader(reader: c.conn) + + c.expect_reply(.ready) or { return error('Received invalid response from server') } + c.send_ehlo() or { return error('Sending EHLO packet failed') } + c.send_auth() or { return error('Authenticating to server failed') } + c.is_open = true +} + +// send sends an email +pub fn (mut c Client) send(config Mail) ? { + if !c.is_open { + return error('Disconnected from server') + } + from := if config.from != '' { config.from } else { c.from } + c.send_mailfrom(from) or { return error('Sending mailfrom failed') } + c.send_mailto(config.to) or { return error('Sending mailto failed') } + c.send_data() or { return error('Sending mail data failed') } + c.send_body(Mail{ + ...config + from: from + }) or { return error('Sending mail body failed') } +} + +// quit closes the connection to the server +pub fn (mut c Client) quit() ? { + c.send_str('QUIT\r\n') ? + c.expect_reply(.close) ? + c.conn.close() ? + c.is_open = false +} + +// expect_reply checks if the SMTP server replied with the expected reply code +fn (mut c Client) expect_reply(expected ReplyCode) ? { + bytes := io.read_all(reader: c.conn) ? + + str := bytes.bytestr().trim_space() + $if smtp_debug ? { + eprintln('\n\n[RECV]') + eprint(str) + } + + if str.len >= 3 { + status := str[..3].int() + if ReplyCode(status) != expected { + return error('Received unexpected status code $status, expecting $expected') + } + } else { + return error('Recieved unexpected SMTP data: $str') + } +} + +[inline] +fn (mut c Client) send_str(s string) ? { + $if smtp_debug ? { + eprintln('\n\n[SEND START]') + eprint(s.trim_space()) + eprintln('\n[SEND END]') + } + c.conn.write(s.bytes()) ? +} + +[inline] +fn (mut c Client) send_ehlo() ? { + c.send_str('EHLO $c.server\r\n') ? + c.expect_reply(.action_ok) ? +} + +[inline] +fn (mut c Client) send_auth() ? { + if c.username.len == 0 { + return + } + mut sb := strings.new_builder(100) + sb.write_b(0) + sb.write_string(c.username) + sb.write_b(0) + sb.write_string(c.password) + a := sb.str() + auth := 'AUTH PLAIN ${base64.encode_str(a)}\r\n' + c.send_str(auth) ? + c.expect_reply(.auth_ok) ? +} + +fn (mut c Client) send_mailfrom(from string) ? { + c.send_str('MAIL FROM: <$from>\r\n') ? + c.expect_reply(.action_ok) ? +} + +fn (mut c Client) send_mailto(to string) ? { + c.send_str('RCPT TO: <$to>\r\n') ? + c.expect_reply(.action_ok) ? +} + +fn (mut c Client) send_data() ? { + c.send_str('DATA\r\n') ? + c.expect_reply(.mail_start) ? +} + +fn (mut c Client) send_body(cfg Mail) ? { + is_html := cfg.body_type == .html + date := cfg.date.utc_string().trim_right(' UTC') // TODO + mut sb := strings.new_builder(200) + sb.write_string('From: $cfg.from\r\n') + sb.write_string('To: <$cfg.to>\r\n') + sb.write_string('Cc: <$cfg.cc>\r\n') + sb.write_string('Bcc: <$cfg.bcc>\r\n') + sb.write_string('Date: $date\r\n') + sb.write_string('Subject: $cfg.subject\r\n') + if is_html { + sb.write_string('Content-Type: text/html; charset=ISO-8859-1') + } + sb.write_string('\r\n\r\n') + sb.write_string(cfg.body) + sb.write_string('\r\n.\r\n') + c.send_str(sb.str()) ? + c.expect_reply(.action_ok) ? +} diff --git a/v_windows/v/vlib/net/smtp/smtp_test.v b/v_windows/v/vlib/net/smtp/smtp_test.v new file mode 100644 index 0000000..d975e57 --- /dev/null +++ b/v_windows/v/vlib/net/smtp/smtp_test.v @@ -0,0 +1,89 @@ +import os +import net.smtp +import time + +// Used to test that a function call returns an error +fn fn_errors(mut c smtp.Client, m smtp.Mail) bool { + c.send(m) or { return true } + return false +} + +/* +* +* smtp_test +* Created by: nedimf (07/2020) +*/ +fn test_smtp() { + $if !network ? { + return + } + + client_cfg := smtp.Client{ + server: 'smtp.mailtrap.io' + from: 'dev@vlang.io' + username: os.getenv('VSMTP_TEST_USER') + password: os.getenv('VSMTP_TEST_PASS') + } + if client_cfg.username == '' && client_cfg.password == '' { + eprintln('Please set VSMTP_TEST_USER and VSMTP_TEST_PASS before running this test') + exit(0) + } + send_cfg := smtp.Mail{ + to: 'dev@vlang.io' + subject: 'Hello from V2' + body: 'Plain text' + } + + mut client := smtp.new_client(client_cfg) or { + assert false + return + } + assert true + client.send(send_cfg) or { + assert false + return + } + assert true + client.send(smtp.Mail{ + ...send_cfg + from: 'alexander@vlang.io' + }) or { + assert false + return + } + client.send(smtp.Mail{ + ...send_cfg + cc: 'alexander@vlang.io,joe@vlang.io' + bcc: 'spytheman@vlang.io' + }) or { + assert false + return + } + client.send(smtp.Mail{ + ...send_cfg + date: time.now().add_days(1000) + }) or { + assert false + return + } + assert true + client.quit() or { + assert false + return + } + assert true + // This call should return an error, since the connection is closed + if !fn_errors(mut client, send_cfg) { + assert false + return + } + client.reconnect() or { + assert false + return + } + client.send(send_cfg) or { + assert false + return + } + assert true +} diff --git a/v_windows/v/vlib/net/socket_options.c.v b/v_windows/v/vlib/net/socket_options.c.v new file mode 100644 index 0000000..4e3240f --- /dev/null +++ b/v_windows/v/vlib/net/socket_options.c.v @@ -0,0 +1,50 @@ +module net + +pub enum SocketOption { + // TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it + // and there is no easy way to define it + broadcast = C.SO_BROADCAST + debug = C.SO_DEBUG + dont_route = C.SO_DONTROUTE + error = C.SO_ERROR + keep_alive = C.SO_KEEPALIVE + linger = C.SO_LINGER + oob_inline = C.SO_OOBINLINE + reuse_addr = C.SO_REUSEADDR + recieve_buf_size = C.SO_RCVBUF + recieve_low_size = C.SO_RCVLOWAT + recieve_timeout = C.SO_RCVTIMEO + send_buf_size = C.SO_SNDBUF + send_low_size = C.SO_SNDLOWAT + send_timeout = C.SO_SNDTIMEO + socket_type = C.SO_TYPE + ipv6_only = C.IPV6_V6ONLY +} + +const ( + opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline] + opts_int = [ + .recieve_buf_size, + .recieve_low_size, + .recieve_timeout, + .send_buf_size, + .send_low_size, + .send_timeout, + ] + + opts_can_set = [ + SocketOption.broadcast, + .debug, + .dont_route, + .keep_alive, + .linger, + .oob_inline, + .recieve_buf_size, + .recieve_low_size, + .recieve_timeout, + .send_buf_size, + .send_low_size, + .send_timeout, + .ipv6_only, + ] +) diff --git a/v_windows/v/vlib/net/tcp.v b/v_windows/v/vlib/net/tcp.v new file mode 100644 index 0000000..2d96194 --- /dev/null +++ b/v_windows/v/vlib/net/tcp.v @@ -0,0 +1,420 @@ +module net + +import time + +const ( + tcp_default_read_timeout = 30 * time.second + tcp_default_write_timeout = 30 * time.second +) + +[heap] +pub struct TcpConn { +pub mut: + sock TcpSocket +mut: + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration + is_blocking bool +} + +pub fn dial_tcp(address string) ?&TcpConn { + addrs := resolve_addrs_fuzzy(address, .tcp) ? + + // Very simple dialer + for addr in addrs { + mut s := new_tcp_socket(addr.family()) ? + s.connect(addr) or { + // Connection failed + s.close() or { continue } + continue + } + + return &TcpConn{ + sock: s + read_timeout: net.tcp_default_read_timeout + write_timeout: net.tcp_default_write_timeout + } + } + // failed + return error('dial_tcp failed') +} + +pub fn (mut c TcpConn) close() ? { + $if trace_tcp ? { + eprintln(' TcpConn.close | c.sock.handle: ${c.sock.handle:6}') + } + c.sock.close() ? +} + +// write_ptr blocks and attempts to write all data +pub fn (mut c TcpConn) write_ptr(b &byte, len int) ?int { + $if trace_tcp ? { + eprintln( + '>>> TcpConn.write_ptr | c.sock.handle: $c.sock.handle | b: ${ptr_str(b)} len: $len |\n' + + unsafe { b.vstring_with_len(len) }) + } + $if trace_tcp_data_write ? { + eprintln('>>> TcpConn.write_ptr | data.len: ${len:6} | data: ' + + unsafe { b.vstring_with_len(len) }) + } + unsafe { + mut ptr_base := &byte(b) + mut total_sent := 0 + for total_sent < len { + ptr := ptr_base + total_sent + remaining := len - total_sent + mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal) + $if trace_tcp_data_write ? { + eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, chunk_size: ${chunk_size:6}, sent: ${sent:6}, ptr: ${ptr_str(ptr)}') + } + if sent < 0 { + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_write() ? + continue + } else { + wrap_error(code) ? + } + } + total_sent += sent + } + return total_sent + } +} + +// write blocks and attempts to write all data +pub fn (mut c TcpConn) write(bytes []byte) ?int { + return c.write_ptr(bytes.data, bytes.len) +} + +// write_string blocks and attempts to write all data +pub fn (mut c TcpConn) write_string(s string) ?int { + return c.write_ptr(s.str, s.len) +} + +pub fn (mut c TcpConn) read_ptr(buf_ptr &byte, len int) ?int { + mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ? + $if trace_tcp ? { + eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') + } + if res > 0 { + $if trace_tcp_data_read ? { + eprintln('<<< TcpConn.read_ptr | 1 data.len: ${res:6} | data: ' + + unsafe { buf_ptr.vstring_with_len(res) }) + } + return res + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_read() ? + res = wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ? + $if trace_tcp ? { + eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') + } + $if trace_tcp_data_read ? { + if res > 0 { + eprintln('<<< TcpConn.read_ptr | 2 data.len: ${res:6} | data: ' + + unsafe { buf_ptr.vstring_with_len(res) }) + } + } + return socket_error(res) + } else { + wrap_error(code) ? + } + return none +} + +pub fn (mut c TcpConn) read(mut buf []byte) ?int { + return c.read_ptr(buf.data, buf.len) +} + +pub fn (mut c TcpConn) read_deadline() ?time.Time { + if c.read_deadline.unix == 0 { + return c.read_deadline + } + return none +} + +pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +pub fn (mut c TcpConn) write_deadline() ?time.Time { + if c.write_deadline.unix == 0 { + return c.write_deadline + } + return none +} + +pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +pub fn (c &TcpConn) read_timeout() time.Duration { + return c.read_timeout +} + +pub fn (mut c TcpConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +pub fn (c &TcpConn) write_timeout() time.Duration { + return c.write_timeout +} + +pub fn (mut c TcpConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +[inline] +pub fn (mut c TcpConn) wait_for_read() ? { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +[inline] +pub fn (mut c TcpConn) wait_for_write() ? { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +pub fn (c &TcpConn) peer_addr() ?Addr { + mut addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + mut size := sizeof(Addr) + socket_error(C.getpeername(c.sock.handle, voidptr(&addr), &size)) ? + return addr +} + +pub fn (c &TcpConn) peer_ip() ?string { + return c.peer_addr() ?.str() +} + +pub fn (c &TcpConn) addr() ?Addr { + return c.sock.address() +} + +pub fn (c TcpConn) str() string { + s := c.sock.str().replace('\n', ' ').replace(' ', ' ') + return 'TcpConn{ write_deadline: $c.write_deadline, read_deadline: $c.read_deadline, read_timeout: $c.read_timeout, write_timeout: $c.write_timeout, sock: $s }' +} + +pub struct TcpListener { +pub mut: + sock TcpSocket +mut: + accept_timeout time.Duration + accept_deadline time.Time +} + +pub fn listen_tcp(family AddrFamily, saddr string) ?&TcpListener { + s := new_tcp_socket(family) ? + + addrs := resolve_addrs(saddr, family, .tcp) ? + + // TODO(logic to pick here) + addr := addrs[0] + + // cast to the correct type + alen := addr.len() + bindres := C.bind(s.handle, voidptr(&addr), alen) + socket_error(bindres) ? + socket_error(C.listen(s.handle, 128)) ? + return &TcpListener{ + sock: s + accept_deadline: no_deadline + accept_timeout: infinite_timeout + } +} + +pub fn (mut l TcpListener) accept() ?&TcpConn { + $if trace_tcp ? { + eprintln(' TcpListener.accept | l.sock.handle: ${l.sock.handle:6}') + } + addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + size := sizeof(Addr) + mut new_handle := C.accept(l.sock.handle, voidptr(&addr), &size) + if new_handle <= 0 { + l.wait_for_accept() ? + new_handle = C.accept(l.sock.handle, voidptr(&addr), &size) + if new_handle == -1 || new_handle == 0 { + return error('accept failed') + } + } + new_sock := tcp_socket_from_handle(new_handle) ? + $if trace_tcp ? { + eprintln(' TcpListener.accept | << new_sock.handle: ${new_sock.handle:6}') + } + return &TcpConn{ + sock: new_sock + read_timeout: net.tcp_default_read_timeout + write_timeout: net.tcp_default_write_timeout + } +} + +pub fn (c &TcpListener) accept_deadline() ?time.Time { + if c.accept_deadline.unix != 0 { + return c.accept_deadline + } + return error('invalid deadline') +} + +pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) { + c.accept_deadline = deadline +} + +pub fn (c &TcpListener) accept_timeout() time.Duration { + return c.accept_timeout +} + +pub fn (mut c TcpListener) set_accept_timeout(t time.Duration) { + c.accept_timeout = t +} + +pub fn (mut c TcpListener) wait_for_accept() ? { + return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout) +} + +pub fn (mut c TcpListener) close() ? { + c.sock.close() ? +} + +pub fn (c &TcpListener) addr() ?Addr { + return c.sock.address() +} + +struct TcpSocket { +pub: + handle int +} + +fn new_tcp_socket(family AddrFamily) ?TcpSocket { + handle := socket_error(C.socket(family, SocketType.tcp, 0)) ? + mut s := TcpSocket{ + handle: handle + } + $if trace_tcp ? { + eprintln(' new_tcp_socket | s.handle: ${s.handle:6}') + } + + // TODO(emily): + // we shouldnt be using ioctlsocket in the 21st century + // use the non-blocking socket option instead please :) + + // TODO(emily): + // Move this to its own function on the socket + s.set_option_int(.reuse_addr, 1) ? + + $if !net_blocking_sockets ? { + $if windows { + t := u32(1) // true + socket_error(C.ioctlsocket(handle, fionbio, &t)) ? + } $else { + socket_error(C.fcntl(handle, C.F_SETFL, C.fcntl(handle, C.F_GETFL) | C.O_NONBLOCK)) ? + } + } + return s +} + +fn tcp_socket_from_handle(sockfd int) ?TcpSocket { + mut s := TcpSocket{ + handle: sockfd + } + $if trace_tcp ? { + eprintln(' tcp_socket_from_handle | s.handle: ${s.handle:6}') + } + // s.set_option_bool(.reuse_addr, true)? + s.set_option_int(.reuse_addr, 1) ? + s.set_dualstack(true) or { + // Not ipv6, we dont care + } + $if !net_blocking_sockets ? { + $if windows { + t := u32(1) // true + socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ? + } $else { + socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK)) ? + } + } + return s +} + +pub fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ? { + // TODO reenable when this `in` operation works again + // if opt !in opts_can_set { + // return err_option_not_settable + // } + // if opt !in opts_bool { + // return err_option_wrong_type + // } + x := int(value) + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ? +} + +pub fn (mut s TcpSocket) set_dualstack(on bool) ? { + x := int(!on) + socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x, + sizeof(int))) ? +} + +pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ? { + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int))) ? +} + +fn (mut s TcpSocket) close() ? { + return shutdown(s.handle) +} + +fn (mut s TcpSocket) @select(test Select, timeout time.Duration) ?bool { + return @select(s.handle, test, timeout) +} + +const ( + connect_timeout = 5 * time.second +) + +fn (mut s TcpSocket) connect(a Addr) ? { + res := C.connect(s.handle, voidptr(&a), a.len()) + if res == 0 { + return + } + + // The socket is nonblocking and the connection cannot be completed + // immediately. (UNIX domain sockets failed with EAGAIN instead.) + // It is possible to select(2) or poll(2) for completion by selecting + // the socket for writing. After select(2) indicates writability, + // use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to + // determine whether connect() completed successfully (SO_ERROR is zero) or + // unsuccessfully (SO_ERROR is one of the usual error codes listed here, + // ex‐ plaining the reason for the failure). + write_result := s.@select(.write, net.connect_timeout) ? + if write_result { + err := 0 + len := sizeof(err) + socket_error(C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)) ? + + if err != 0 { + return wrap_error(err) + } + // Succeeded + return + } + + // Get the error + socket_error(C.connect(s.handle, voidptr(&a), a.len())) ? + + // otherwise we timed out + return err_connect_timed_out +} + +// address gets the address of a socket +pub fn (s &TcpSocket) address() ?Addr { + return addr_from_socket_handle(s.handle) +} diff --git a/v_windows/v/vlib/net/tcp_read_line.v b/v_windows/v/vlib/net/tcp_read_line.v new file mode 100644 index 0000000..ec4a18e --- /dev/null +++ b/v_windows/v/vlib/net/tcp_read_line.v @@ -0,0 +1,90 @@ +module net + +const ( + crlf = '\r\n' + msg_peek = 0x02 + max_read = 400 +) + +// get_blocking returns whether the connection is in a blocking state, +// that is calls to .read_line, C.recv etc will block till there is new +// data arrived, instead of returning immediately. +pub fn (mut con TcpConn) get_blocking() bool { + // flags := C.fcntl(con.sock.handle, C.F_GETFL, 0) + // return 0 == flags & C.O_NONBLOCK + return con.is_blocking +} + +// set_blocking will change the state of the connection to either blocking, +// when state is true, or non blocking (false). +// The default for `net` tcp connections is the non blocking mode. +// Calling .read_line will set the connection to blocking mode. +pub fn (mut con TcpConn) set_blocking(state bool) ? { + con.is_blocking = state + $if windows { + mut t := u32(0) + if !con.is_blocking { + t = 1 + } + socket_error(C.ioctlsocket(con.sock.handle, fionbio, &t)) ? + } $else { + mut flags := C.fcntl(con.sock.handle, C.F_GETFL, 0) + if state { + flags &= ~C.O_NONBLOCK + } else { + flags |= C.O_NONBLOCK + } + socket_error(C.fcntl(con.sock.handle, C.F_SETFL, flags)) ? + } +} + +// read_line is a *simple*, *non customizable*, blocking line reader. +// It will *always* return a line, ending with CRLF, or just '', on EOF. +// NB: if you want more control over the buffer, please use a buffered IO +// reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})` +pub fn (mut con TcpConn) read_line() string { + mut buf := [net.max_read]byte{} // where C.recv will store the network data + mut res := '' // The final result, including the ending \n. + if !con.is_blocking { + con.set_blocking(true) or {} + } + for { + mut line := '' // The current line. Can be a partial without \n in it. + n := C.recv(con.sock.handle, &buf[0], net.max_read - 1, net.msg_peek | msg_nosignal) + if n == -1 { + return res + } + if n == 0 { + return res + } + buf[n] = `\0` + mut eol_idx := -1 + for i in 0 .. n { + if int(buf[i]) == `\n` { + eol_idx = i + // Ensure that tos_clone(buf) later, + // will return *only* the first line (including \n), + // and ignore the rest + buf[i + 1] = `\0` + break + } + } + line = unsafe { tos_clone(&buf[0]) } + if eol_idx > 0 { + // At this point, we are sure that recv returned valid data, + // that contains *at least* one line. + // Ensure that the block till the first \n (including it) + // is removed from the socket's receive queue, so that it does + // not get read again. + C.recv(con.sock.handle, &buf[0], eol_idx + 1, msg_nosignal) + res += line + break + } + // recv returned a buffer without \n in it . + C.recv(con.sock.handle, &buf[0], n, msg_nosignal) + res += line + res += net.crlf + break + } + return res +} diff --git a/v_windows/v/vlib/net/tcp_simple_client_server_test.v b/v_windows/v/vlib/net/tcp_simple_client_server_test.v new file mode 100644 index 0000000..317933f --- /dev/null +++ b/v_windows/v/vlib/net/tcp_simple_client_server_test.v @@ -0,0 +1,150 @@ +import io +import net +import strings + +const ( + server_port = ':22443' +) + +fn accept(mut server net.TcpListener, c chan &net.TcpConn) { + c <- server.accept() or { panic(err) } +} + +fn setup() (&net.TcpListener, &net.TcpConn, &net.TcpConn) { + mut server := net.listen_tcp(.ip6, server_port) or { panic(err) } + + c := chan &net.TcpConn{} + go accept(mut server, c) + mut client := net.dial_tcp('localhost$server_port') or { panic(err) } + + socket := <-c + + $if debug_peer_ip ? { + eprintln('$server.addr()\n$client.peer_addr(), $client.addr()\n$socket.peer_addr(), $socket.addr()') + } + assert true + return server, client, socket +} + +fn cleanup(mut server net.TcpListener, mut client net.TcpConn, mut socket net.TcpConn) { + server.close() or {} + client.close() or {} + socket.close() or {} +} + +fn test_socket() { + mut server, mut client, mut socket := setup() + defer { + cleanup(mut server, mut client, mut socket) + } + message := 'Hello World' + socket.write_string(message) or { + assert false + return + } + assert true + $if debug { + println('message send: $message') + } + $if debug { + println('send socket: $socket.sock.handle') + } + mut buf := []byte{len: 1024} + nbytes := client.read(mut buf) or { + assert false + return + } + received := buf[0..nbytes].bytestr() + $if debug { + println('message received: $received') + } + $if debug { + println('client: $client.sock.handle') + } + assert message == received +} + +fn test_socket_write_and_read() { + mut server, mut client, mut socket := setup() + defer { + cleanup(mut server, mut client, mut socket) + } + message1 := 'a message 1' + socket.write_string(message1) or { assert false } + mut rbuf := []byte{len: message1.len} + client.read(mut rbuf) or { + assert false + return + } + line := rbuf.bytestr() + assert line == message1 +} + +fn test_socket_read_line() { + mut server, mut client, mut socket := setup() + mut reader := io.new_buffered_reader(reader: client) + defer { + cleanup(mut server, mut client, mut socket) + } + message1, message2 := 'message1', 'message2' + message := '$message1\n$message2\n' + socket.write_string(message) or { assert false } + assert true + // + line1 := reader.read_line() or { + // println(reader.buf) + assert false + return + } + line2 := reader.read_line() or { + // println(reader.buf) + assert false + return + } + assert line1 == message1 + assert line2 == message2 +} + +fn test_socket_write_fail_without_panic() { + mut server, mut client, mut socket := setup() + defer { + cleanup(mut server, mut client, mut socket) + } + message2 := 'a message 2' + // ensure that socket.write (i.e. done on the server side) + // continues to work, even when the client side has been disconnected + // this test is important for a stable long standing server + client.close() or {} + $if solaris { + return + } + // TODO: fix segfaulting on Solaris + for i := 0; i < 3; i++ { + socket.write_string(message2) or { + println('write to a socket without a recipient should produce an option fail: $err | $message2') + assert true + } + } +} + +fn test_socket_read_line_long_line_without_eol() { + mut server, mut client, mut socket := setup() + mut reader := io.new_buffered_reader(reader: client) + defer { + cleanup(mut server, mut client, mut socket) + } + message := strings.repeat_string('123', 400) + socket.write_string(message) or { + assert false + return + } + socket.write_string('\n') or { + assert false + return + } + line := reader.read_line() or { + assert false + return + } + assert line == message +} diff --git a/v_windows/v/vlib/net/tcp_test.v b/v_windows/v/vlib/net/tcp_test.v new file mode 100644 index 0000000..cbd2aa4 --- /dev/null +++ b/v_windows/v/vlib/net/tcp_test.v @@ -0,0 +1,100 @@ +import net +import os + +const ( + test_port = 45123 +) + +fn handle_conn(mut c net.TcpConn) { + for { + mut buf := []byte{len: 100, init: 0} + read := c.read(mut buf) or { + println('Server: connection dropped') + return + } + c.write(buf[..read]) or { + println('Server: connection dropped') + return + } + } +} + +fn one_shot_echo_server(mut l net.TcpListener, ch_started chan int) ? { + eprintln('> one_shot_echo_server') + ch_started <- 1 + mut new_conn := l.accept() or { return error('could not accept') } + eprintln(' > new_conn: $new_conn') + handle_conn(mut new_conn) + new_conn.close() or {} +} + +fn echo(address string) ? { + mut c := net.dial_tcp(address) ? + defer { + c.close() or {} + } + + println('local: ' + c.addr() ?.str()) + println(' peer: ' + c.peer_addr() ?.str()) + + data := 'Hello from vlib/net!' + c.write_string(data) ? + mut buf := []byte{len: 4096} + read := c.read(mut buf) ? + assert read == data.len + for i := 0; i < read; i++ { + assert buf[i] == data[i] + } + println('Got "$buf.bytestr()"') +} + +fn test_tcp_ip6() { + eprintln('\n>>> ${@FN}') + address := 'localhost:$test_port' + mut l := net.listen_tcp(.ip6, ':$test_port') or { panic(err) } + dump(l) + start_echo_server(mut l) + echo(address) or { panic(err) } + l.close() or {} + // ensure there is at least one new socket created before the next test + l = net.listen_tcp(.ip6, ':${test_port + 1}') or { panic(err) } +} + +fn start_echo_server(mut l net.TcpListener) { + ch_server_started := chan int{} + go one_shot_echo_server(mut l, ch_server_started) + _ := <-ch_server_started +} + +fn test_tcp_ip() { + eprintln('\n>>> ${@FN}') + address := 'localhost:$test_port' + mut l := net.listen_tcp(.ip, address) or { panic(err) } + dump(l) + start_echo_server(mut l) + echo(address) or { panic(err) } + l.close() or {} +} + +fn test_tcp_unix() { + eprintln('\n>>> ${@FN}') + // TODO(emily): + // whilst windows supposedly supports unix sockets + // this doesnt work (wsaeopnotsupp at the call to bind()) + $if !windows { + address := os.real_path('tcp-test.sock') + // address := 'tcp-test.sock' + println('$address') + + mut l := net.listen_tcp(.unix, address) or { panic(err) } + start_echo_server(mut l) + echo(address) or { panic(err) } + l.close() or {} + + os.rm(address) or { panic('failed to remove socket file') } + } +} + +fn testsuite_end() { + eprintln('\ndone') +} diff --git a/v_windows/v/vlib/net/udp.v b/v_windows/v/vlib/net/udp.v new file mode 100644 index 0000000..2fac9f3 --- /dev/null +++ b/v_windows/v/vlib/net/udp.v @@ -0,0 +1,289 @@ +module net + +import time + +const ( + udp_default_read_timeout = time.second / 10 + udp_default_write_timeout = time.second / 10 +) + +struct UdpSocket { + handle int + l Addr + // TODO(emily): replace with option again + // when i figure out how to coerce it properly +mut: + has_r bool + r Addr +} + +pub struct UdpConn { +pub mut: + sock UdpSocket +mut: + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration +} + +pub fn dial_udp(raddr string) ?&UdpConn { + addrs := resolve_addrs_fuzzy(raddr, .udp) ? + + for addr in addrs { + // create a local socket for this + // bind to any port (or file) (we dont care in this + // case because we only care about the remote) + if sock := new_udp_socket_for_remote(addr) { + return &UdpConn{ + sock: sock + read_timeout: net.udp_default_read_timeout + write_timeout: net.udp_default_write_timeout + } + } + } + + return none +} + +// pub fn dial_udp(laddr string, raddr string) ?&UdpConn { +// local := resolve_addr(laddr, .inet, .udp) ? + +// sbase := new_udp_socket() ? + +// sock := UdpSocket{ +// handle: sbase.handle +// l: local +// r: resolve_wrapper(raddr) +// } +// } + +pub fn (mut c UdpConn) write_ptr(b &byte, len int) ?int { + remote := c.sock.remote() or { return err_no_udp_remote } + return c.write_to_ptr(remote, b, len) +} + +pub fn (mut c UdpConn) write(buf []byte) ?int { + return c.write_ptr(buf.data, buf.len) +} + +pub fn (mut c UdpConn) write_string(s string) ?int { + return c.write_ptr(s.str, s.len) +} + +pub fn (mut c UdpConn) write_to_ptr(addr Addr, b &byte, len int) ?int { + res := C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()) + if res >= 0 { + return res + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_write() ? + socket_error(C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len())) ? + } else { + wrap_error(code) ? + } + return none +} + +// write_to blocks and writes the buf to the remote addr specified +pub fn (mut c UdpConn) write_to(addr Addr, buf []byte) ?int { + return c.write_to_ptr(addr, buf.data, buf.len) +} + +// write_to_string blocks and writes the buf to the remote addr specified +pub fn (mut c UdpConn) write_to_string(addr Addr, s string) ?int { + return c.write_to_ptr(addr, s.str, s.len) +} + +// read reads from the socket into buf up to buf.len returning the number of bytes read +pub fn (mut c UdpConn) read(mut buf []byte) ?(int, Addr) { + mut addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + len := sizeof(Addr) + mut res := wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len, + 0, voidptr(&addr), &len)) ? + if res > 0 { + return res, addr + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_read() ? + // same setup as in tcp + res = wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len, 0, + voidptr(&addr), &len)) ? + res2 := socket_error(res) ? + return res2, addr + } else { + wrap_error(code) ? + } + return none +} + +pub fn (c &UdpConn) read_deadline() ?time.Time { + if c.read_deadline.unix == 0 { + return c.read_deadline + } + return none +} + +pub fn (mut c UdpConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +pub fn (c &UdpConn) write_deadline() ?time.Time { + if c.write_deadline.unix == 0 { + return c.write_deadline + } + return none +} + +pub fn (mut c UdpConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +pub fn (c &UdpConn) read_timeout() time.Duration { + return c.read_timeout +} + +pub fn (mut c UdpConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +pub fn (c &UdpConn) write_timeout() time.Duration { + return c.write_timeout +} + +pub fn (mut c UdpConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +[inline] +pub fn (mut c UdpConn) wait_for_read() ? { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +[inline] +pub fn (mut c UdpConn) wait_for_write() ? { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +pub fn (c &UdpConn) str() string { + // TODO + return 'UdpConn' +} + +pub fn (mut c UdpConn) close() ? { + return c.sock.close() +} + +pub fn listen_udp(laddr string) ?&UdpConn { + addrs := resolve_addrs_fuzzy(laddr, .udp) ? + // TODO(emily): + // here we are binding to the first address + // and that is probably not ideal + addr := addrs[0] + return &UdpConn{ + sock: new_udp_socket(addr) ? + read_timeout: net.udp_default_read_timeout + write_timeout: net.udp_default_write_timeout + } +} + +fn new_udp_socket(local_addr Addr) ?&UdpSocket { + family := local_addr.family() + + sockfd := socket_error(C.socket(family, SocketType.udp, 0)) ? + mut s := &UdpSocket{ + handle: sockfd + l: local_addr + r: Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + } + + s.set_option_bool(.reuse_addr, true) ? + + if family == .ip6 { + s.set_dualstack(true) ? + } + + $if !net_blocking_sockets ? { + // NOTE: refer to comments in tcp.v + $if windows { + t := u32(1) // true + socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ? + } $else { + socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK)) ? + } + } + + // cast to the correct type + socket_error(C.bind(s.handle, voidptr(&local_addr), local_addr.len())) ? + return s +} + +fn new_udp_socket_for_remote(raddr Addr) ?&UdpSocket { + // Invent a sutible local address for this remote addr + // Appease compiler + mut addr := Addr{ + addr: AddrData{ + Ip6: Ip6{} + } + } + match raddr.family() { + .ip, .ip6 { + // Use ip6 dualstack + addr = new_ip6(0, addr_ip6_any) + } + .unix { + addr = temp_unix() ? + } + else { + panic('Invalid family') + } + } + mut sock := new_udp_socket(addr) ? + sock.has_r = true + sock.r = raddr + + return sock +} + +pub fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ? { + // TODO reenable when this `in` operation works again + // if opt !in opts_can_set { + // return err_option_not_settable + // } + // if opt !in opts_bool { + // return err_option_wrong_type + // } + x := int(value) + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ? +} + +pub fn (mut s UdpSocket) set_dualstack(on bool) ? { + x := int(!on) + socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x, + sizeof(int))) ? +} + +fn (mut s UdpSocket) close() ? { + return shutdown(s.handle) +} + +fn (mut s UdpSocket) @select(test Select, timeout time.Duration) ?bool { + return @select(s.handle, test, timeout) +} + +fn (s &UdpSocket) remote() ?Addr { + if s.has_r { + return s.r + } + return none +} diff --git a/v_windows/v/vlib/net/udp_test.v b/v_windows/v/vlib/net/udp_test.v new file mode 100644 index 0000000..83675a2 --- /dev/null +++ b/v_windows/v/vlib/net/udp_test.v @@ -0,0 +1,67 @@ +import net + +fn echo_server(mut c net.UdpConn) { + for { + mut buf := []byte{len: 100, init: 0} + read, addr := c.read(mut buf) or { continue } + + println('Server got addr $addr') + + c.write_to(addr, buf[..read]) or { + println('Server: connection dropped') + return + } + } +} + +const ( + local_addr = ':40003' + remote_addr = 'localhost:40003' +) + +fn echo() ? { + mut c := net.dial_udp(remote_addr) ? + defer { + c.close() or {} + } + data := 'Hello from vlib/net!' + + c.write_string(data) ? + + mut buf := []byte{len: 100, init: 0} + read, addr := c.read(mut buf) ? + + assert read == data.len + println('Got address $addr') + // Can't test this here because loopback addresses + // are mapped to other addresses + // assert addr.str() == '127.0.0.1:30001' + + for i := 0; i < read; i++ { + assert buf[i] == data[i] + } + + println('Got "$buf.bytestr()"') + + c.close() ? +} + +fn test_udp() { + mut l := net.listen_udp(local_addr) or { + println(err) + assert false + panic('') + } + + go echo_server(mut l) + echo() or { + println(err) + assert false + } + + l.close() or {} +} + +fn main() { + test_udp() +} diff --git a/v_windows/v/vlib/net/unix/aasocket.c.v b/v_windows/v/vlib/net/unix/aasocket.c.v new file mode 100644 index 0000000..7f762a5 --- /dev/null +++ b/v_windows/v/vlib/net/unix/aasocket.c.v @@ -0,0 +1,104 @@ +module unix + +#include + +// Select represents a select operation +enum Select { + read + write + except +} + +// SocketType are the available sockets +// enum SocketType { +// dgram = C.SOCK_DGRAM +// stream = C.SOCK_STREAM +// seqpacket = C.SOCK_SEQPACKET +// } + +struct C.sockaddr { + sa_family u16 +} + +const max_sun_path = 104 + +// 104 for macos, 108 for linux => use the minimum + +struct C.sockaddr_un { +mut: + // sun_len byte // only on macos + sun_family int + sun_path [104]char // on linux that is 108 +} + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_storage { +} + +// fn C.socket() int + +// fn C.setsockopt() int + +// fn C.htonl() int + +// fn C.htons() int + +// fn C.bind() int + +// fn C.listen() int + +// fn C.accept() int + +// fn C.getaddrinfo() int + +// fn C.connect() int + +// fn C.send() int + +// fn C.sendto() int + +// fn C.recv() int + +// fn C.recvfrom() int + +// fn C.shutdown() int + +// fn C.ntohs() int + +// fn C.getpeername() int + +// fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr + +fn C.WSAAddressToStringA() int + +// fn C.getsockname() int + +// defined in builtin +// fn C.read() int +// fn C.close() int + +fn C.ioctlsocket() int + +// fn C.fcntl() int + +// fn C.@select() int + +// fn C.FD_ZERO() + +// fn C.FD_SET() + +// fn C.FD_ISSET() bool + +[typedef] +struct C.fd_set {} diff --git a/v_windows/v/vlib/net/unix/common.v b/v_windows/v/vlib/net/unix/common.v new file mode 100644 index 0000000..75e591f --- /dev/null +++ b/v_windows/v/vlib/net/unix/common.v @@ -0,0 +1,128 @@ +module unix + +import time +import net + +const ( + error_ewouldblock = C.EWOULDBLOCK +) + +fn C.SUN_LEN(ptr &C.sockaddr_un) int + +fn C.strncpy(&char, &char, int) + +// Shutdown shutsdown a socket and closes it +fn shutdown(handle int) ? { + $if windows { + C.shutdown(handle, C.SD_BOTH) + net.socket_error(C.closesocket(handle)) ? + } $else { + C.shutdown(handle, C.SHUT_RDWR) + net.socket_error(C.close(handle)) ? + } +} + +// Select waits for an io operation (specified by parameter `test`) to be available +fn @select(handle int, test Select, timeout time.Duration) ?bool { + set := C.fd_set{} + + C.FD_ZERO(&set) + C.FD_SET(handle, &set) + + seconds := timeout / time.second + microseconds := time.Duration(timeout - (seconds * time.second)).microseconds() + + mut tt := C.timeval{ + tv_sec: u64(seconds) + tv_usec: u64(microseconds) + } + + mut timeval_timeout := &tt + + // infinite timeout is signaled by passing null as the timeout to + // select + if timeout == unix.infinite_timeout { + timeval_timeout = &C.timeval(0) + } + + match test { + .read { + net.socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout)) ? + } + .write { + net.socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout)) ? + } + .except { + net.socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout)) ? + } + } + + return C.FD_ISSET(handle, &set) +} + +// wait_for_common wraps the common wait code +fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test Select) ? { + if deadline.unix == 0 { + // do not accept negative timeout + if timeout < 0 { + return net.err_timed_out + } + ready := @select(handle, test, timeout) ? + if ready { + return + } + return net.err_timed_out + } + // Convert the deadline into a timeout + // and use that + d_timeout := deadline.unix - time.now().unix + if d_timeout < 0 { + // deadline is in the past so this has already + // timed out + return net.err_timed_out + } + + ready := @select(handle, test, d_timeout) ? + if ready { + return + } + return net.err_timed_out +} + +// wait_for_write waits for a write io operation to be available +fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .write) +} + +// wait_for_read waits for a read io operation to be available +fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .read) +} + +// no_deadline should be given to functions when no deadline is wanted (i.e. all functions +// return instantly) +const ( + no_deadline = time.Time{ + unix: 0 + } +) + +// no_timeout should be given to functions when no timeout is wanted (i.e. all functions +// return instantly) +const ( + no_timeout = time.Duration(0) +) + +// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions +// only ever return with data) +const ( + infinite_timeout = time.infinite +) + +[inline] +fn wrap_read_result(result int) ?int { + if result != 0 { + return result + } + return none +} diff --git a/v_windows/v/vlib/net/unix/stream_nix.v b/v_windows/v/vlib/net/unix/stream_nix.v new file mode 100644 index 0000000..e73acb7 --- /dev/null +++ b/v_windows/v/vlib/net/unix/stream_nix.v @@ -0,0 +1,288 @@ +module unix + +import time +import os +import net + +const ( + unix_default_read_timeout = 30 * time.second + unix_default_write_timeout = 30 * time.second + connect_timeout = 5 * time.second + msg_nosignal = 0x4000 +) + +struct StreamSocket { +pub: + handle int +mut: + path string +} + +struct StreamConn { +pub mut: + sock StreamSocket +mut: + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration +} + +struct StreamListener { +pub mut: + sock StreamSocket +mut: + accept_timeout time.Duration + accept_deadline time.Time +} + +fn error_code() int { + return C.errno +} + +fn new_stream_socket() ?StreamSocket { + sockfd := net.socket_error(C.socket(net.AddrFamily.unix, net.SocketType.tcp, 0)) ? + mut s := StreamSocket{ + handle: sockfd + } + return s +} + +fn (mut s StreamSocket) close() ? { + return shutdown(s.handle) +} + +fn (mut s StreamSocket) @select(test Select, timeout time.Duration) ?bool { + return @select(s.handle, test, timeout) +} + +fn (mut s StreamSocket) connect(a string) ? { + if a.len >= max_sun_path { + return error('Socket path too long! Max length: ${max_sun_path - 1} chars.') + } + mut addr := C.sockaddr_un{} + unsafe { C.memset(&addr, 0, sizeof(C.sockaddr_un)) } + addr.sun_family = C.AF_UNIX + unsafe { C.strncpy(&addr.sun_path[0], &char(a.str), max_sun_path) } + size := C.SUN_LEN(&addr) + res := C.connect(s.handle, voidptr(&addr), size) + // if res != 1 { + // return none + //} + if res == 0 { + return + } + _ := error_code() + write_result := s.@select(.write, unix.connect_timeout) ? + if write_result { + // succeeded + return + } + except_result := s.@select(.except, unix.connect_timeout) ? + if except_result { + return net.err_connect_failed + } + // otherwise we timed out + return net.err_connect_timed_out +} + +pub fn listen_stream(sock string) ?&StreamListener { + if sock.len >= max_sun_path { + return error('Socket path too long! Max length: ${max_sun_path - 1} chars.') + } + mut s := new_stream_socket() ? + s.path = sock + mut addr := C.sockaddr_un{} + unsafe { C.memset(&addr, 0, sizeof(C.sockaddr_un)) } + addr.sun_family = C.AF_UNIX + unsafe { C.strncpy(&addr.sun_path[0], &char(sock.str), max_sun_path) } + size := C.SUN_LEN(&addr) + if os.exists(sock) { + os.rm(sock) ? + } + net.socket_error(C.bind(s.handle, voidptr(&addr), size)) ? + os.chmod(sock, 0o777) ? + net.socket_error(C.listen(s.handle, 128)) ? + return &StreamListener{ + sock: s + } +} + +pub fn connect_stream(path string) ?&StreamConn { + mut s := new_stream_socket() ? + s.connect(path) ? + return &StreamConn{ + sock: s + read_timeout: unix.unix_default_read_timeout + write_timeout: unix.unix_default_write_timeout + } +} + +pub fn (mut l StreamListener) accept() ?&StreamConn { + mut new_handle := C.accept(l.sock.handle, 0, 0) + if new_handle <= 0 { + l.wait_for_accept() ? + new_handle = C.accept(l.sock.handle, 0, 0) + if new_handle == -1 || new_handle == 0 { + return error('accept failed') + } + } + new_sock := StreamSocket{ + handle: new_handle + } + return &StreamConn{ + sock: new_sock + read_timeout: unix.unix_default_read_timeout + write_timeout: unix.unix_default_write_timeout + } +} + +pub fn (c &StreamListener) accept_deadline() ?time.Time { + if c.accept_deadline.unix != 0 { + return c.accept_deadline + } + return error('no deadline') +} + +pub fn (mut c StreamListener) set_accept_deadline(deadline time.Time) { + c.accept_deadline = deadline +} + +pub fn (c &StreamListener) accept_timeout() time.Duration { + return c.accept_timeout +} + +pub fn (mut c StreamListener) set_accept_timeout(t time.Duration) { + c.accept_timeout = t +} + +pub fn (mut c StreamListener) wait_for_accept() ? { + return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout) +} + +pub fn (mut c StreamListener) close() ? { + os.rm(c.sock.path) ? + c.sock.close() ? +} + +pub fn (mut c StreamConn) close() ? { + c.sock.close() ? +} + +// write_ptr blocks and attempts to write all data +pub fn (mut c StreamConn) write_ptr(b &byte, len int) ?int { + $if trace_unix ? { + eprintln( + '>>> StreamConn.write_ptr | c.sock.handle: $c.sock.handle | b: ${ptr_str(b)} len: $len |\n' + + unsafe { b.vstring_with_len(len) }) + } + unsafe { + mut ptr_base := &byte(b) + mut total_sent := 0 + for total_sent < len { + ptr := ptr_base + total_sent + remaining := len - total_sent + mut sent := C.send(c.sock.handle, ptr, remaining, unix.msg_nosignal) + if sent < 0 { + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_write() ? + continue + } else { + net.wrap_error(code) ? + } + } + total_sent += sent + } + return total_sent + } +} + +// write blocks and attempts to write all data +pub fn (mut c StreamConn) write(bytes []byte) ?int { + return c.write_ptr(bytes.data, bytes.len) +} + +// write_string blocks and attempts to write all data +pub fn (mut c StreamConn) write_string(s string) ?int { + return c.write_ptr(s.str, s.len) +} + +pub fn (mut c StreamConn) read_ptr(buf_ptr &byte, len int) ?int { + mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ? + $if trace_unix ? { + eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') + } + if res > 0 { + return res + } + code := error_code() + if code == int(error_ewouldblock) { + c.wait_for_read() ? + res = wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ? + $if trace_unix ? { + eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') + } + return net.socket_error(res) + } else { + net.wrap_error(code) ? + } + return net.socket_error(code) +} + +pub fn (mut c StreamConn) read(mut buf []byte) ?int { + return c.read_ptr(buf.data, buf.len) +} + +pub fn (mut c StreamConn) read_deadline() ?time.Time { + if c.read_deadline.unix == 0 { + return c.read_deadline + } + return none +} + +pub fn (mut c StreamConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +pub fn (mut c StreamConn) write_deadline() ?time.Time { + if c.write_deadline.unix == 0 { + return c.write_deadline + } + return none +} + +pub fn (mut c StreamConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +pub fn (c &StreamConn) read_timeout() time.Duration { + return c.read_timeout +} + +pub fn (mut c StreamConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +pub fn (c &StreamConn) write_timeout() time.Duration { + return c.write_timeout +} + +pub fn (mut c StreamConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +[inline] +pub fn (mut c StreamConn) wait_for_read() ? { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +[inline] +pub fn (mut c StreamConn) wait_for_write() ? { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +pub fn (c StreamConn) str() string { + s := c.sock.str().replace('\n', ' ').replace(' ', ' ') + return 'StreamConn{ write_deadline: $c.write_deadline, read_deadline: $c.read_deadline, read_timeout: $c.read_timeout, write_timeout: $c.write_timeout, sock: $s }' +} diff --git a/v_windows/v/vlib/net/unix/unix_test.v b/v_windows/v/vlib/net/unix/unix_test.v new file mode 100644 index 0000000..cabcd52 --- /dev/null +++ b/v_windows/v/vlib/net/unix/unix_test.v @@ -0,0 +1,50 @@ +import os +import net.unix + +const test_port = os.join_path(os.temp_dir(), 'unix_domain_socket') + +fn handle_conn(mut c unix.StreamConn) { + for { + mut buf := []byte{len: 100, init: 0} + read := c.read(mut buf) or { + println('Server: connection dropped') + return + } + c.write(buf[..read]) or { + println('Server: connection dropped') + return + } + } +} + +fn echo_server(mut l unix.StreamListener) ? { + for { + mut new_conn := l.accept() or { continue } + go handle_conn(mut new_conn) + } +} + +fn echo() ? { + mut c := unix.connect_stream(test_port) ? + defer { + c.close() or {} + } + data := 'Hello from vlib/net!' + c.write_string(data) ? + mut buf := []byte{len: 4096} + read := c.read(mut buf) ? + assert read == data.len + for i := 0; i < read; i++ { + assert buf[i] == data[i] + } + println('Got "$buf.bytestr()"') + return +} + +fn test_tcp() { + assert os.exists(test_port) == false + mut l := unix.listen_stream(test_port) or { panic(err) } + go echo_server(mut l) + echo() or { panic(err) } + l.close() or {} +} diff --git a/v_windows/v/vlib/net/urllib/urllib.v b/v_windows/v/vlib/net/urllib/urllib.v new file mode 100644 index 0000000..3b02ef6 --- /dev/null +++ b/v_windows/v/vlib/net/urllib/urllib.v @@ -0,0 +1,1095 @@ +// urllib parses URLs and implements query escaping. +// See RFC 3986. This module generally follows RFC 3986, except where +// it deviates for compatibility reasons. +// Based off: https://github.com/golang/go/blob/master/src/net/url/url.go +// Last commit: https://github.com/golang/go/commit/fe2ed5054176935d4adcf13e891715ccf2ee3cce +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +module urllib + +import strings + +enum EncodingMode { + encode_path + encode_path_segment + encode_host + encode_zone + encode_user_password + encode_query_component + encode_fragment +} + +const ( + err_msg_escape = 'unescape: invalid URL escape' + err_msg_parse = 'parse: failed parsing url' +) + +fn error_msg(message string, val string) string { + mut msg := 'net.urllib.$message' + if val != '' { + msg = '$msg ($val)' + } + return msg +} + +// Return true if the specified character should be escaped when +// appearing in a URL string, according to RFC 3986. +// +// Please be informed that for now should_escape does not check all +// reserved characters correctly. See golang.org/issue/5684. +fn should_escape(c byte, mode EncodingMode) bool { + // §2.3 Unreserved characters (alphanum) + if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) || (`0` <= c && c <= `9`) { + return false + } + if mode == .encode_host || mode == .encode_zone { + // §3.2.2 host allows + // sub-delims = `!` / `$` / `&` / ``` / `(` / `)` / `*` / `+` / `,` / `;` / `=` + // as part of reg-name. + // We add : because we include :port as part of host. + // We add [ ] because we include [ipv6]:port as part of host. + // We add < > because they`re the only characters left that + // we could possibly allow, and parse will reject them if we + // escape them (because hosts can`t use %-encoding for + // ASCII bytes). + if c in [`!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `:`, `[`, `]`, `<`, `>`, + `"`, + ] { + return false + } + } + match c { + `-`, `_`, `.`, `~` { + // §2.3 Unreserved characters (mark) + return false + } + `$`, `&`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@` { + // §2.2 Reserved characters (reserved) + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + match mode { + .encode_path { + // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. This package + // only manipulates the path as a whole, so we allow those + // last three as well. That leaves only ? to escape. + return c == `?` + } + .encode_path_segment { + // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. + return c == `/` || c == `;` || c == `,` || c == `?` + } + .encode_user_password { + // §3.2.1 + // The RFC allows `;`, `:`, `&`, `=`, `+`, `$`, and `,` in + // userinfo, so we must escape only `@`, `/`, and `?`. + // The parsing of userinfo treats `:` as special so we must escape + // that too. + return c == `@` || c == `/` || c == `?` || c == `:` + } + .encode_query_component { + // §3.4 + // The RFC reserves (so we must escape) everything. + return true + } + .encode_fragment { + // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing. + return false + } + else {} + } + } + else {} + } + if mode == .encode_fragment { + // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are + // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not + // need to be escaped. To minimize potential breakage, we apply two restrictions: + // (1) we always escape sub-delims outside of the fragment, and (2) we always + // escape single quote to avoid breaking callers that had previously assumed that + // single quotes would be escaped. See issue #19917. + match c { + `!`, `(`, `)`, `*` { return false } + else {} + } + } + // Everything else must be escaped. + return true +} + +// query_unescape does the inverse transformation of query_escape, +// converting each 3-byte encoded substring of the form '%AB' into the +// hex-decoded byte 0xAB. +// It returns an error if any % is not followed by two hexadecimal +// digits. +pub fn query_unescape(s string) ?string { + return unescape(s, .encode_query_component) +} + +// path_unescape does the inverse transformation of path_escape, +// converting each 3-byte encoded substring of the form '%AB' into the +// hex-decoded byte 0xAB. It returns an error if any % is not followed +// by two hexadecimal digits. +// +// path_unescape is identical to query_unescape except that it does not +// unescape '+' to ' ' (space). +pub fn path_unescape(s string) ?string { + return unescape(s, .encode_path_segment) +} + +// unescape unescapes a string; the mode specifies +// which section of the URL string is being unescaped. +fn unescape(s_ string, mode EncodingMode) ?string { + mut s := s_ + // Count %, check that they're well-formed. + mut n := 0 + mut has_plus := false + for i := 0; i < s.len; { + x := s[i] + match x { + `%` { + if s == '' { + break + } + n++ + if i + 2 >= s.len || !ishex(s[i + 1]) || !ishex(s[i + 2]) { + if mode == .encode_query_component && i + 1 < s.len { + s = s[..i] + '%25' + s[(i + 1)..] + i += 4 // skip the %25 and the next character + continue + } + s = s[i..] + if s.len > 3 { + s = s[..3] + } + return error(error_msg(urllib.err_msg_escape, s)) + } + // Per https://tools.ietf.org/html/rfc3986#page-21 + // in the host component %-encoding can only be used + // for non-ASCII bytes. + // But https://tools.ietf.org/html/rfc6874#section-2 + // introduces %25 being allowed to escape a percent sign + // in IPv6 scoped-address literals. Yay. + if i + 3 >= s.len && mode == .encode_host && unhex(s[i + 1]) < 8 + && s[i..i + 3] != '%25' { + return error(error_msg(urllib.err_msg_escape, s[i..i + 3])) + } + if mode == .encode_zone { + // RFC 6874 says basically 'anything goes' for zone identifiers + // and that even non-ASCII can be redundantly escaped, + // but it seems prudent to restrict %-escaped bytes here to those + // that are valid host name bytes in their unescaped form. + // That is, you can use escaping in the zone identifier but not + // to introduce bytes you couldn't just write directly. + // But Windows puts spaces here! Yay. + if i + 3 >= s.len { + return error(error_msg('unescape: invalid escape sequence', '')) + } + v := ((unhex(s[i + 1]) << byte(4)) | unhex(s[i + 2])) + if s[i..i + 3] != '%25' && v != ` ` && should_escape(v, .encode_host) { + error(error_msg(urllib.err_msg_escape, s[i..i + 3])) + } + } + i += 3 + } + `+` { + has_plus = mode == .encode_query_component + i++ + } + else { + if (mode == .encode_host || mode == .encode_zone) && s[i] < 0x80 + && should_escape(s[i], mode) { + error(error_msg('unescape: invalid character in host name', s[i..i + 1])) + } + i++ + } + } + } + if n == 0 && !has_plus { + return s + } + if s.len < 2 * n { + return error(error_msg('unescape: invalid escape sequence', '')) + } + mut t := strings.new_builder(s.len - 2 * n) + for i := 0; i < s.len; i++ { + x := s[i] + match x { + `%` { + if i + 2 >= s.len { + return error(error_msg('unescape: invalid escape sequence', '')) + } + t.write_string(((unhex(s[i + 1]) << byte(4)) | unhex(s[i + 2])).ascii_str()) + i += 2 + } + `+` { + if mode == .encode_query_component { + t.write_string(' ') + } else { + t.write_string('+') + } + } + else { + t.write_string(s[i].ascii_str()) + } + } + } + return t.str() +} + +// query_escape escapes the string so it can be safely placed +// inside a URL query. +pub fn query_escape(s string) string { + return escape(s, .encode_query_component) +} + +// path_escape escapes the string so it can be safely placed inside a URL path segment, +// replacing special characters (including /) with %XX sequences as needed. +pub fn path_escape(s string) string { + return escape(s, .encode_path_segment) +} + +fn escape(s string, mode EncodingMode) string { + mut space_count := 0 + mut hex_count := 0 + mut c := byte(0) + for i in 0 .. s.len { + c = s[i] + if should_escape(c, mode) { + if c == ` ` && mode == .encode_query_component { + space_count++ + } else { + hex_count++ + } + } + } + if space_count == 0 && hex_count == 0 { + return s + } + buf := []byte{len: (64)} + mut t := []byte{} + required := s.len + 2 * hex_count + if required <= buf.len { + t = buf[..required] + } else { + t = []byte{len: required} + } + if hex_count == 0 { + copy(t, s.bytes()) + for i in 0 .. s.len { + if s[i] == ` ` { + t[i] = `+` + } + } + return t.bytestr() + } + upperhex := '0123456789ABCDEF' + mut j := 0 + for i in 0 .. s.len { + c1 := s[i] + if c1 == ` ` && mode == .encode_query_component { + t[j] = `+` + j++ + } else if should_escape(c1, mode) { + t[j] = `%` + t[j + 1] = upperhex[c1 >> 4] + t[j + 2] = upperhex[c1 & 15] + j += 3 + } else { + t[j] = s[i] + j++ + } + } + return t.bytestr() +} + +// A URL represents a parsed URL (technically, a URI reference). +// +// The general form represented is: +// +// [scheme:][//[userinfo@]host][/]path[?query][#fragment] +// +// URLs that do not start with a slash after the scheme are interpreted as: +// +// scheme:opaque[?query][#fragment] +// +// Note that the path field is stored in decoded form: /%47%6f%2f becomes /Go/. +// A consequence is that it is impossible to tell which slashes in the path were +// slashes in the raw URL and which were %2f. This distinction is rarely important, +// but when it is, the code should use raw_path, an optional field which only gets +// set if the default encoding is different from path. +// +// URL's String method uses the escaped_path method to obtain the path. See the +// escaped_path method for more details. +pub struct URL { +pub mut: + scheme string + opaque string // encoded opaque data + user &Userinfo // username and password information + host string // host or host:port + path string // path (relative paths may omit leading slash) + raw_path string // encoded path hint (see escaped_path method) + force_query bool // append a query ('?') even if raw_query is empty + raw_query string // encoded query values, without '?' + fragment string // fragment for references, without '#' +} + +// user returns a Userinfo containing the provided username +// and no password set. +pub fn user(username string) &Userinfo { + return &Userinfo{ + username: username + password: '' + password_set: false + } +} + +// user_password returns a Userinfo containing the provided username +// and password. +// +// This functionality should only be used with legacy web sites. +// RFC 2396 warns that interpreting Userinfo this way +// ``is NOT RECOMMENDED, because the passing of authentication +// information in clear text (such as URI) has proven to be a +// security risk in almost every case where it has been used.'' +fn user_password(username string, password string) &Userinfo { + return &Userinfo{username, password, true} +} + +// The Userinfo type is an immutable encapsulation of username and +// password details for a URL. An existing Userinfo value is guaranteed +// to have a username set (potentially empty, as allowed by RFC 2396), +// and optionally a password. +struct Userinfo { +pub: + username string + password string + password_set bool +} + +fn (u &Userinfo) empty() bool { + return isnil(u) || (u.username == '' && u.password == '') +} + +// string returns the encoded userinfo information in the standard form +// of 'username[:password]'. +fn (u &Userinfo) str() string { + if u.empty() { + return '' + } + mut s := escape(u.username, .encode_user_password) + if u.password_set { + s += ':' + escape(u.password, .encode_user_password) + } + return s +} + +// Maybe rawurl is of the form scheme:path. +// (scheme must be [a-zA-Z][a-zA-Z0-9+-.]*) +// If so, return [scheme, path]; else return ['', rawurl] +fn split_by_scheme(rawurl string) ?[]string { + for i in 0 .. rawurl.len { + c := rawurl[i] + if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) { + // do nothing + } else if (`0` <= c && c <= `9`) || (c == `+` || c == `-` || c == `.`) { + if i == 0 { + return ['', rawurl] + } + } else if c == `:` { + if i == 0 { + return error(error_msg('split_by_scheme: missing protocol scheme', '')) + } + return [rawurl[..i], rawurl[i + 1..]] + } else { + // we have encountered an invalid character, + // so there is no valid scheme + return ['', rawurl] + } + } + return ['', rawurl] +} + +fn get_scheme(rawurl string) ?string { + split := split_by_scheme(rawurl) or { return err.msg } + return split[0] +} + +// split slices s into two substrings separated by the first occurence of +// sep. If cutc is true then sep is included with the second substring. +// If sep does not occur in s then s and the empty string is returned. +fn split(s string, sep byte, cutc bool) (string, string) { + i := s.index_byte(sep) + if i < 0 { + return s, '' + } + if cutc { + return s[..i], s[i + 1..] + } + return s[..i], s[i..] +} + +// parse parses rawurl into a URL structure. +// +// The rawurl may be relative (a path, without a host) or absolute +// (starting with a scheme). Trying to parse a hostname and path +// without a scheme is invalid but may not necessarily return an +// error, due to parsing ambiguities. +pub fn parse(rawurl string) ?URL { + // Cut off #frag + u, frag := split(rawurl, `#`, true) + mut url := parse_url(u, false) or { return error(error_msg(urllib.err_msg_parse, u)) } + if frag == '' { + return url + } + f := unescape(frag, .encode_fragment) or { return error(error_msg(urllib.err_msg_parse, + u)) } + url.fragment = f + return url +} + +// parse_request_uri parses rawurl into a URL structure. It assumes that +// rawurl was received in an HTTP request, so the rawurl is interpreted +// only as an absolute URI or an absolute path. +// The string rawurl is assumed not to have a #fragment suffix. +// (Web browsers strip #fragment before sending the URL to a web server.) +fn parse_request_uri(rawurl string) ?URL { + return parse_url(rawurl, true) +} + +// parse_url parses a URL from a string in one of two contexts. If +// via_request is true, the URL is assumed to have arrived via an HTTP request, +// in which case only absolute URLs or path-absolute relative URLs are allowed. +// If via_request is false, all forms of relative URLs are allowed. +[manualfree] +fn parse_url(rawurl string, via_request bool) ?URL { + if string_contains_ctl_byte(rawurl) { + return error(error_msg('parse_url: invalid control character in URL', rawurl)) + } + if rawurl == '' && via_request { + return error(error_msg('parse_url: empty URL', rawurl)) + } + mut url := URL{ + user: 0 + } + if rawurl == '*' { + url.path = '*' + return url + } + // Split off possible leading 'http:', 'mailto:', etc. + // Cannot contain escaped characters. + p := split_by_scheme(rawurl) ? + url.scheme = p[0] + mut rest := p[1] + url.scheme = url.scheme.to_lower() + // if rest.ends_with('?') && strings.count(rest, '?') == 1 { + if rest.ends_with('?') && !rest[..1].contains('?') { + url.force_query = true + rest = rest[..rest.len - 1] + } else { + r, raw_query := split(rest, `?`, true) + rest = r + url.raw_query = raw_query + } + if !rest.starts_with('/') { + if url.scheme != '' { + // We consider rootless paths per RFC 3986 as opaque. + url.opaque = rest + return url + } + if via_request { + return error(error_msg('parse_url: invalid URI for request', '')) + } + // Avoid confusion with malformed schemes, like cache_object:foo/bar. + // See golang.org/issue/16822. + // + // RFC 3986, §3.3: + // In addition, a URI reference (Section 4.1) may be a relative-path reference, + // in which case the first path segment cannot contain a colon (':') character. + colon := rest.index(':') or { return error('there should be a : in the URL') } + slash := rest.index('/') or { return error('there should be a / in the URL') } + if colon >= 0 && (slash < 0 || colon < slash) { + // First path segment has colon. Not allowed in relative URL. + return error(error_msg('parse_url: first path segment in URL cannot contain colon', + '')) + } + } + if ((url.scheme != '' || !via_request) && !rest.starts_with('///')) && rest.starts_with('//') { + authority, r := split(rest[2..], `/`, false) + rest = r + a := parse_authority(authority) ? + url.user = a.user + url.host = a.host + } + // Set path and, optionally, raw_path. + // raw_path is a hint of the encoding of path. We don't want to set it if + // the default escaping of path is equivalent, to help make sure that people + // don't rely on it in general. + url.set_path(rest) ? + return url +} + +struct ParseAuthorityRes { + user &Userinfo + host string +} + +fn parse_authority(authority string) ?ParseAuthorityRes { + i := authority.last_index('@') or { -1 } + mut host := '' + mut zuser := user('') + if i < 0 { + h := parse_host(authority) ? + host = h + } else { + h := parse_host(authority[i + 1..]) ? + host = h + } + if i < 0 { + return ParseAuthorityRes{ + host: host + user: zuser + } + } + mut userinfo := authority[..i] + if !valid_userinfo(userinfo) { + return error(error_msg('parse_authority: invalid userinfo', '')) + } + if !userinfo.contains(':') { + u := unescape(userinfo, .encode_user_password) ? + userinfo = u + zuser = user(userinfo) + } else { + mut username, mut password := split(userinfo, `:`, true) + u := unescape(username, .encode_user_password) ? + username = u + p := unescape(password, .encode_user_password) ? + password = p + zuser = user_password(username, password) + } + return ParseAuthorityRes{ + user: zuser + host: host + } +} + +// parse_host parses host as an authority without user +// information. That is, as host[:port]. +fn parse_host(host string) ?string { + if host.starts_with('[') { + // parse an IP-Literal in RFC 3986 and RFC 6874. + // E.g., '[fe80::1]', '[fe80::1%25en0]', '[fe80::1]:80'. + mut i := host.last_index(']') or { + return error(error_msg("parse_host: missing ']' in host", '')) + } + mut colon_port := host[i + 1..] + if !valid_optional_port(colon_port) { + return error(error_msg('parse_host: invalid port $colon_port after host ', + '')) + } + // RFC 6874 defines that %25 (%-encoded percent) introduces + // the zone identifier, and the zone identifier can use basically + // any %-encoding it likes. That's different from the host, which + // can only %-encode non-ASCII bytes. + // We do impose some restrictions on the zone, to avoid stupidity + // like newlines. + if zone := host[..i].index('%25') { + host1 := unescape(host[..zone], .encode_host) or { return err.msg } + host2 := unescape(host[zone..i], .encode_zone) or { return err.msg } + host3 := unescape(host[i..], .encode_host) or { return err.msg } + return host1 + host2 + host3 + } + if idx := host.last_index(':') { + colon_port = host[idx..] + if !valid_optional_port(colon_port) { + return error(error_msg('parse_host: invalid port $colon_port after host ', + '')) + } + } + } + h := unescape(host, .encode_host) or { return err.msg } + return h + // host = h + // return host +} + +// set_path sets the path and raw_path fields of the URL based on the provided +// escaped path p. It maintains the invariant that raw_path is only specified +// when it differs from the default encoding of the path. +// For example: +// - set_path('/foo/bar') will set path='/foo/bar' and raw_path='' +// - set_path('/foo%2fbar') will set path='/foo/bar' and raw_path='/foo%2fbar' +// set_path will return an error only if the provided path contains an invalid +// escaping. +pub fn (mut u URL) set_path(p string) ?bool { + path := unescape(p, .encode_path) ? + u.path = path + escp := escape(path, .encode_path) + if p == escp { + // Default encoding is fine. + u.raw_path = '' + } else { + u.raw_path = p + } + return true +} + +// escaped_path returns the escaped form of u.path. +// In general there are multiple possible escaped forms of any path. +// escaped_path returns u.raw_path when it is a valid escaping of u.path. +// Otherwise escaped_path ignores u.raw_path and computes an escaped +// form on its own. +// The String and request_uri methods use escaped_path to construct +// their results. +// In general, code should call escaped_path instead of +// reading u.raw_path directly. +pub fn (u &URL) escaped_path() string { + if u.raw_path != '' && valid_encoded_path(u.raw_path) { + unescape(u.raw_path, .encode_path) or { return '' } + return u.raw_path + } + if u.path == '*' { + return '*' // don't escape (Issue 11202) + } + return escape(u.path, .encode_path) +} + +// valid_encoded_path reports whether s is a valid encoded path. +// It must not contain any bytes that require escaping during path encoding. +fn valid_encoded_path(s string) bool { + for i in 0 .. s.len { + // RFC 3986, Appendix A. + // pchar = unreserved / pct-encoded / sub-delims / ':' / '@'. + // should_escape is not quite compliant with the RFC, + // so we check the sub-delims ourselves and let + // should_escape handle the others. + x := s[i] + match x { + `!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `:`, `@` { + // ok + } + `[`, `]` { + // ok - not specified in RFC 3986 but left alone by modern browsers + } + `%` { + // ok - percent encoded, will decode + } + else { + if should_escape(s[i], .encode_path) { + return false + } + } + } + } + return true +} + +// valid_optional_port reports whether port is either an empty string +// or matches /^:\d*$/ +fn valid_optional_port(port string) bool { + if port == '' { + return true + } + if port[0] != `:` { + return false + } + for b in port[1..] { + if b < `0` || b > `9` { + return false + } + } + return true +} + +// str reassembles the URL into a valid URL string. +// The general form of the result is one of: +// +// scheme:opaque?query#fragment +// scheme://userinfo@host/path?query#fragment +// +// If u.opaque is non-empty, String uses the first form; +// otherwise it uses the second form. +// Any non-ASCII characters in host are escaped. +// To obtain the path, String uses u.escaped_path(). +// +// In the second form, the following rules apply: +// - if u.scheme is empty, scheme: is omitted. +// - if u.user is nil, userinfo@ is omitted. +// - if u.host is empty, host/ is omitted. +// - if u.scheme and u.host are empty and u.user is nil, +// the entire scheme://userinfo@host/ is omitted. +// - if u.host is non-empty and u.path begins with a /, +// the form host/path does not add its own /. +// - if u.raw_query is empty, ?query is omitted. +// - if u.fragment is empty, #fragment is omitted. +pub fn (u URL) str() string { + mut buf := strings.new_builder(200) + if u.scheme != '' { + buf.write_string(u.scheme) + buf.write_string(':') + } + if u.opaque != '' { + buf.write_string(u.opaque) + } else { + if u.scheme != '' || u.host != '' || !u.user.empty() { + if u.host != '' || u.path != '' || !u.user.empty() { + buf.write_string('//') + } + if !u.user.empty() { + buf.write_string(u.user.str()) + buf.write_string('@') + } + if u.host != '' { + buf.write_string(escape(u.host, .encode_host)) + } + } + path := u.escaped_path() + if path != '' && path[0] != `/` && u.host != '' { + buf.write_string('/') + } + if buf.len == 0 { + // RFC 3986 §4.2 + // A path segment that contains a colon character (e.g., 'this:that') + // cannot be used as the first segment of a relative-path reference, as + // it would be mistaken for a scheme name. Such a segment must be + // preceded by a dot-segment (e.g., './this:that') to make a relative- + // path reference. + i := path.index_byte(`:`) + if i > -1 { + // TODO remove this when autofree handles tmp + // expressions like this + if i > -1 && path[..i].index_byte(`/`) == -1 { + buf.write_string('./') + } + } + } + buf.write_string(path) + } + if u.force_query || u.raw_query != '' { + buf.write_string('?') + buf.write_string(u.raw_query) + } + if u.fragment != '' { + buf.write_string('#') + buf.write_string(escape(u.fragment, .encode_fragment)) + } + return buf.str() +} + +// Values maps a string key to a list of values. +// It is typically used for query parameters and form values. +// Unlike in the http.Header map, the keys in a Values map +// are case-sensitive. +// parseQuery parses the URL-encoded query string and returns +// a map listing the values specified for each key. +// parseQuery always returns a non-nil map containing all the +// valid query parameters found; err describes the first decoding error +// encountered, if any. +// +// Query is expected to be a list of key=value settings separated by +// ampersands or semicolons. A setting without an equals sign is +// interpreted as a key set to an empty value. +pub fn parse_query(query string) ?Values { + mut m := new_values() + parse_query_values(mut m, query) ? + return m +} + +// parse_query_silent is the same as parse_query +// but any errors will be silent +fn parse_query_silent(query string) Values { + mut m := new_values() + parse_query_values(mut m, query) or {} + return m +} + +fn parse_query_values(mut m Values, query string) ?bool { + mut had_error := false + mut q := query + for q != '' { + mut key := q + mut i := key.index_any('&;') + if i >= 0 { + q = key[i + 1..] + key = key[..i] + } else { + q = '' + } + if key == '' { + continue + } + mut value := '' + if idx := key.index('=') { + i = idx + value = key[i + 1..] + key = key[..i] + } + k := query_unescape(key) or { + had_error = true + continue + } + key = k + v := query_unescape(value) or { + had_error = true + continue + } + value = v + m.add(key, value) + } + if had_error { + return error(error_msg('parse_query_values: failed parsing query string', '')) + } + return true +} + +// encode encodes the values into ``URL encoded'' form +// ('bar=baz&foo=quux') sorted by key. +pub fn (v Values) encode() string { + if v.len == 0 { + return '' + } + mut buf := strings.new_builder(200) + mut keys := []string{} + for k, _ in v.data { + keys << k + } + keys.sort() + for k in keys { + vs := v.data[k] + key_kscaped := query_escape(k) + for _, val in vs.data { + if buf.len > 0 { + buf.write_string('&') + } + buf.write_string(key_kscaped) + buf.write_string('=') + buf.write_string(query_escape(val)) + } + } + return buf.str() +} + +// resolve_path applies special path segments from refs and applies +// them to base, per RFC 3986. +fn resolve_path(base string, ref string) string { + mut full := '' + if ref == '' { + full = base + } else if ref[0] != `/` { + i := base.last_index('/') or { -1 } + full = base[..i + 1] + ref + } else { + full = ref + } + if full == '' { + return '' + } + mut dst := []string{} + src := full.split('/') + for _, elem in src { + match elem { + '.' { + // drop + } + '..' { + if dst.len > 0 { + dst = dst[..dst.len - 1] + } + } + else { + dst << elem + } + } + } + last := src[src.len - 1] + if last == '.' || last == '..' { + // Add final slash to the joined path. + dst << '' + } + return '/' + dst.join('/').trim_left('/') +} + +// is_abs reports whether the URL is absolute. +// Absolute means that it has a non-empty scheme. +pub fn (u &URL) is_abs() bool { + return u.scheme != '' +} + +// parse parses a URL in the context of the receiver. The provided URL +// may be relative or absolute. parse returns nil, err on parse +// failure, otherwise its return value is the same as resolve_reference. +pub fn (u &URL) parse(ref string) ?URL { + refurl := parse(ref) ? + return u.resolve_reference(refurl) +} + +// resolve_reference resolves a URI reference to an absolute URI from +// an absolute base URI u, per RFC 3986 Section 5.2. The URI reference +// may be relative or absolute. resolve_reference always returns a new +// URL instance, even if the returned URL is identical to either the +// base or reference. If ref is an absolute URL, then resolve_reference +// ignores base and returns a copy of ref. +pub fn (u &URL) resolve_reference(ref &URL) ?URL { + mut url := *ref + if ref.scheme == '' { + url.scheme = u.scheme + } + if ref.scheme != '' || ref.host != '' || !ref.user.empty() { + // The 'absoluteURI' or 'net_path' cases. + // We can ignore the error from set_path since we know we provided a + // validly-escaped path. + url.set_path(resolve_path(ref.escaped_path(), '')) ? + return url + } + if ref.opaque != '' { + url.user = user('') + url.host = '' + url.path = '' + return url + } + if ref.path == '' && ref.raw_query == '' { + url.raw_query = u.raw_query + if ref.fragment == '' { + url.fragment = u.fragment + } + } + // The 'abs_path' or 'rel_path' cases. + url.host = u.host + url.user = u.user + url.set_path(resolve_path(u.escaped_path(), ref.escaped_path())) ? + return url +} + +// query parses raw_query and returns the corresponding values. +// It silently discards malformed value pairs. +// To check errors use parseQuery. +pub fn (u &URL) query() Values { + v := parse_query_silent(u.raw_query) + return v +} + +// request_uri returns the encoded path?query or opaque?query +// string that would be used in an HTTP request for u. +pub fn (u &URL) request_uri() string { + mut result := u.opaque + if result == '' { + result = u.escaped_path() + if result == '' { + result = '/' + } + } else { + if result.starts_with('//') { + result = u.scheme + ':' + result + } + } + if u.force_query || u.raw_query != '' { + result += '?' + u.raw_query + } + return result +} + +// hostname returns u.host, stripping any valid port number if present. +// +// If the result is enclosed in square brackets, as literal IPv6 addresses are, +// the square brackets are removed from the result. +pub fn (u &URL) hostname() string { + host, _ := split_host_port(u.host) + return host +} + +// port returns the port part of u.host, without the leading colon. +// If u.host doesn't contain a port, port returns an empty string. +pub fn (u &URL) port() string { + _, port := split_host_port(u.host) + return port +} + +// split_host_port separates host and port. If the port is not valid, it returns +// the entire input as host, and it doesn't check the validity of the host. +// Per RFC 3986, it requires ports to be numeric. +fn split_host_port(hostport string) (string, string) { + mut host := hostport + mut port := '' + colon := host.last_index_byte(`:`) + if colon != -1 { + if valid_optional_port(host[colon..]) { + port = host[colon + 1..] + host = host[..colon] + } + } + if host.starts_with('[') && host.ends_with(']') { + host = host[1..host.len - 1] + } + return host, port +} + +// valid_userinfo reports whether s is a valid userinfo string per RFC 3986 +// Section 3.2.1: +// userinfo = *( unreserved / pct-encoded / sub-delims / ':' ) +// unreserved = ALPHA / DIGIT / '-' / '.' / '_' / '~' +// sub-delims = '!' / '$' / '&' / ''' / '(' / ')' +// / '*' / '+' / ',' / ';' / '=' +// +// It doesn't validate pct-encoded. The caller does that via fn unescape. +pub fn valid_userinfo(s string) bool { + for r in s { + if `A` <= r && r <= `Z` { + continue + } + if `a` <= r && r <= `z` { + continue + } + if `0` <= r && r <= `9` { + continue + } + match r { + `-`, `.`, `_`, `:`, `~`, `!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `%`, + `@` { + continue + } + else { + return false + } + } + } + return true +} + +// string_contains_ctl_byte reports whether s contains any ASCII control character. +fn string_contains_ctl_byte(s string) bool { + for i in 0 .. s.len { + b := s[i] + if b < ` ` || b == 0x7f { + return true + } + } + return false +} + +pub fn ishex(c byte) bool { + if `0` <= c && c <= `9` { + return true + } else if `a` <= c && c <= `f` { + return true + } else if `A` <= c && c <= `F` { + return true + } + return false +} + +fn unhex(c byte) byte { + if `0` <= c && c <= `9` { + return c - `0` + } else if `a` <= c && c <= `f` { + return c - `a` + 10 + } else if `A` <= c && c <= `F` { + return c - `A` + 10 + } + return 0 +} diff --git a/v_windows/v/vlib/net/urllib/urllib_test.v b/v_windows/v/vlib/net/urllib/urllib_test.v new file mode 100644 index 0000000..0870c81 --- /dev/null +++ b/v_windows/v/vlib/net/urllib/urllib_test.v @@ -0,0 +1,51 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import net.urllib + +fn test_net_urllib() { + test_query := 'Hellö Wörld@vlang' + assert urllib.query_escape(test_query) == 'Hell%C3%B6+W%C3%B6rld%40vlang' + + test_url := 'https://joe:pass@www.mydomain.com:8080/som/url?param1=test1¶m2=test2&foo=bar#testfragment' + u := urllib.parse(test_url) or { + assert false + return + } + assert u.scheme == 'https' && u.hostname() == 'www.mydomain.com' && u.port() == '8080' + && u.path == '/som/url' && u.fragment == 'testfragment' && u.user.username == 'joe' + && u.user.password == 'pass' +} + +fn test_str() { + url := urllib.parse('https://en.wikipedia.org/wiki/Brazil_(1985_film)') or { + panic('unable to parse URL') + } + assert url.str() == 'https://en.wikipedia.org/wiki/Brazil_(1985_film)' +} + +fn test_escape_unescape() { + original := 'те ст: т\\%' + escaped := urllib.query_escape(original) + assert escaped == '%D1%82%D0%B5+%D1%81%D1%82%3A+%D1%82%5C%25' + unescaped := urllib.query_unescape(escaped) or { + assert false + return + } + assert unescaped == original +} + +fn test_parse_query() ? { + q1 := urllib.parse_query('format=%22%25l%3A+%25c+%25t%22') ? + q2 := urllib.parse_query('format="%l:+%c+%t"') ? + // dump(q1) + // dump(q2) + assert q1.data['format'].data == ['"%l: %c %t"'] + assert q2.data['format'].data == ['"%l: %c %t"'] +} + +fn test_parse_missing_host() ? { + // issue #10311 + url := urllib.parse('http:///') ? + assert url.str() == 'http://///' +} diff --git a/v_windows/v/vlib/net/urllib/values.v b/v_windows/v/vlib/net/urllib/values.v new file mode 100644 index 0000000..ee5c329 --- /dev/null +++ b/v_windows/v/vlib/net/urllib/values.v @@ -0,0 +1,87 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module urllib + +struct Value { +pub mut: + data []string +} + +struct Values { +pub mut: + data map[string]Value + len int +} + +// new_values returns a new Values struct for creating +// urlencoded query string parameters. it can also be to +// post form data with application/x-www-form-urlencoded. +// values.encode() will return the encoded data +pub fn new_values() Values { + return Values{ + data: map[string]Value{} + } +} + +// Currently you will need to use all()[key].data +// once map[string][]string is implemented +// this will be fixed +pub fn (v &Value) all() []string { + return v.data +} + +// get gets the first value associated with the given key. +// If there are no values associated with the key, get returns +// a empty string. +pub fn (v &Values) get(key string) string { + if v.data.len == 0 { + return '' + } + vs := v.data[key] + if vs.data.len == 0 { + return '' + } + return vs.data[0] +} + +// get_all gets the all the values associated with the given key. +// If there are no values associated with the key, get returns +// a empty []string. +pub fn (v &Values) get_all(key string) []string { + if v.data.len == 0 { + return [] + } + vs := v.data[key] + if vs.data.len == 0 { + return [] + } + return vs.data +} + +// set sets the key to value. It replaces any existing +// values. +pub fn (mut v Values) set(key string, value string) { + mut a := v.data[key] + a.data = [value] + v.data[key] = a + v.len = v.data.len +} + +// add adds the value to key. It appends to any existing +// values associated with key. +pub fn (mut v Values) add(key string, value string) { + mut a := v.data[key] + if a.data.len == 0 { + a.data = [] + } + a.data << value + v.data[key] = a + v.len = v.data.len +} + +// del deletes the values associated with key. +pub fn (mut v Values) del(key string) { + v.data.delete(key) + v.len = v.data.len +} diff --git a/v_windows/v/vlib/net/util.v b/v_windows/v/vlib/net/util.v new file mode 100644 index 0000000..33d7cec --- /dev/null +++ b/v_windows/v/vlib/net/util.v @@ -0,0 +1,27 @@ +module net + +const ( + socket_max_port = u16(0xFFFF) +) + +// validate_port checks whether a port is valid +// and returns the port or an error +pub fn validate_port(port int) ?u16 { + if port <= net.socket_max_port { + return u16(port) + } else { + return err_port_out_of_range + } +} + +// split address splits an address into its host name and its port +pub fn split_address(addr string) ?(string, u16) { + port := addr.all_after_last(':').int() + address := addr.all_before_last(':') + + // TODO(emily): Maybe do some more checking here + // to validate ipv6 address sanity? + + p := validate_port(port) ? + return address, p +} diff --git a/v_windows/v/vlib/net/websocket/events.v b/v_windows/v/vlib/net/websocket/events.v new file mode 100644 index 0000000..a442daf --- /dev/null +++ b/v_windows/v/vlib/net/websocket/events.v @@ -0,0 +1,227 @@ +module websocket + +// MessageEventHandler represents a callback on a new message +struct MessageEventHandler { + handler SocketMessageFn // callback function + handler2 SocketMessageFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object +} + +// ErrorEventHandler represents a callback on error +struct ErrorEventHandler { + handler SocketErrorFn // callback function + handler2 SocketErrorFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object +} + +// OpenEventHandler represents a callback when connection is opened +struct OpenEventHandler { + handler SocketOpenFn // callback function + handler2 SocketOpenFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object +} + +// CloseEventHandler represents a callback on a closing event +struct CloseEventHandler { + handler SocketCloseFn // callback function + handler2 SocketCloseFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object +} + +pub type AcceptClientFn = fn (mut c ServerClient) ?bool + +pub type SocketMessageFn = fn (mut c Client, msg &Message) ? + +pub type SocketMessageFn2 = fn (mut c Client, msg &Message, v voidptr) ? + +pub type SocketErrorFn = fn (mut c Client, err string) ? + +pub type SocketErrorFn2 = fn (mut c Client, err string, v voidptr) ? + +pub type SocketOpenFn = fn (mut c Client) ? + +pub type SocketOpenFn2 = fn (mut c Client, v voidptr) ? + +pub type SocketCloseFn = fn (mut c Client, code int, reason string) ? + +pub type SocketCloseFn2 = fn (mut c Client, code int, reason string, v voidptr) ? + +// on_connect registers a callback when client connects to the server +pub fn (mut s Server) on_connect(fun AcceptClientFn) ? { + if s.accept_client_callbacks.len > 0 { + return error('only one callback can be registered for accept client') + } + s.accept_client_callbacks << fun +} + +// on_message registers a callback on new messages +pub fn (mut s Server) on_message(fun SocketMessageFn) { + s.message_callbacks << MessageEventHandler{ + handler: fun + } +} + +// on_message_ref registers a callback on new messages and provides a reference object +pub fn (mut s Server) on_message_ref(fun SocketMessageFn2, ref voidptr) { + s.message_callbacks << MessageEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// on_close registers a callback on closed socket +pub fn (mut s Server) on_close(fun SocketCloseFn) { + s.close_callbacks << CloseEventHandler{ + handler: fun + } +} + +// on_close_ref registers a callback on closed socket and provides a reference object +pub fn (mut s Server) on_close_ref(fun SocketCloseFn2, ref voidptr) { + s.close_callbacks << CloseEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// on_message registers a callback on new messages +pub fn (mut ws Client) on_message(fun SocketMessageFn) { + ws.message_callbacks << MessageEventHandler{ + handler: fun + } +} + +// on_message_ref registers a callback on new messages and provides a reference object +pub fn (mut ws Client) on_message_ref(fun SocketMessageFn2, ref voidptr) { + ws.message_callbacks << MessageEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// on_error registers a callback on errors +pub fn (mut ws Client) on_error(fun SocketErrorFn) { + ws.error_callbacks << ErrorEventHandler{ + handler: fun + } +} + +// on_error_ref registers a callback on errors and provides a reference object +pub fn (mut ws Client) on_error_ref(fun SocketErrorFn2, ref voidptr) { + ws.error_callbacks << ErrorEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// on_open registers a callback on successful opening the websocket +pub fn (mut ws Client) on_open(fun SocketOpenFn) { + ws.open_callbacks << OpenEventHandler{ + handler: fun + } +} + +// on_open_ref registers a callback on successful opening the websocket +// and provides a reference object +pub fn (mut ws Client) on_open_ref(fun SocketOpenFn2, ref voidptr) { + ws.open_callbacks << OpenEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// on_close registers a callback on closed socket +pub fn (mut ws Client) on_close(fun SocketCloseFn) { + ws.close_callbacks << CloseEventHandler{ + handler: fun + } +} + +// on_close_ref registers a callback on closed socket and provides a reference object +pub fn (mut ws Client) on_close_ref(fun SocketCloseFn2, ref voidptr) { + ws.close_callbacks << CloseEventHandler{ + handler2: fun + ref: ref + is_ref: true + } +} + +// send_connect_event invokes the on_connect callback +fn (mut s Server) send_connect_event(mut c ServerClient) ?bool { + if s.accept_client_callbacks.len == 0 { + // If no callback all client will be accepted + return true + } + fun := s.accept_client_callbacks[0] + res := fun(mut c) ? + return res +} + +// send_message_event invokes the on_message callback +fn (mut ws Client) send_message_event(msg &Message) { + ws.debug_log('sending on_message event') + for ev_handler in ws.message_callbacks { + if !ev_handler.is_ref { + ev_handler.handler(ws, msg) or { ws.logger.error('send_message_event error: $err') } + } else { + ev_handler.handler2(ws, msg, ev_handler.ref) or { + ws.logger.error('send_message_event error: $err') + } + } + } +} + +// send_error_event invokes the on_error callback +fn (mut ws Client) send_error_event(error string) { + ws.debug_log('sending on_error event') + for ev_handler in ws.error_callbacks { + if !ev_handler.is_ref { + ev_handler.handler(mut ws, error) or { + ws.logger.error('send_error_event error: $error, err: $err') + } + } else { + ev_handler.handler2(mut ws, error, ev_handler.ref) or { + ws.logger.error('send_error_event error: $error, err: $err') + } + } + } +} + +// send_close_event invokes the on_close callback +fn (mut ws Client) send_close_event(code int, reason string) { + ws.debug_log('sending on_close event') + for ev_handler in ws.close_callbacks { + if !ev_handler.is_ref { + ev_handler.handler(mut ws, code, reason) or { + ws.logger.error('send_close_event error: $err') + } + } else { + ev_handler.handler2(mut ws, code, reason, ev_handler.ref) or { + ws.logger.error('send_close_event error: $err') + } + } + } +} + +// send_open_event invokes the on_open callback +fn (mut ws Client) send_open_event() { + ws.debug_log('sending on_open event') + for ev_handler in ws.open_callbacks { + if !ev_handler.is_ref { + ev_handler.handler(mut ws) or { ws.logger.error('send_open_event error: $err') } + } else { + ev_handler.handler2(mut ws, ev_handler.ref) or { + ws.logger.error('send_open_event error: $err') + } + } + } +} diff --git a/v_windows/v/vlib/net/websocket/handshake.v b/v_windows/v/vlib/net/websocket/handshake.v new file mode 100644 index 0000000..9f3ab00 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/handshake.v @@ -0,0 +1,185 @@ +[manualfree] +module websocket + +import encoding.base64 +import strings + +// handshake manages the websocket handshake process +fn (mut ws Client) handshake() ? { + nonce := get_nonce(ws.nonce_size) + seckey := base64.encode_str(nonce) + mut sb := strings.new_builder(1024) + defer { + unsafe { sb.free() } + } + sb.write_string('GET ') + sb.write_string(ws.uri.resource) + sb.write_string(ws.uri.querystring) + sb.write_string(' HTTP/1.1\r\nHost: ') + sb.write_string(ws.uri.hostname) + sb.write_string(':') + sb.write_string(ws.uri.port) + sb.write_string('\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n') + sb.write_string('Sec-WebSocket-Key: ') + sb.write_string(seckey) + sb.write_string('\r\nSec-WebSocket-Version: 13') + for key in ws.header.keys() { + val := ws.header.custom_values(key).join(',') + sb.write_string('\r\n$key:$val') + } + sb.write_string('\r\n\r\n') + handshake := sb.str() + defer { + unsafe { handshake.free() } + } + handshake_bytes := handshake.bytes() + ws.debug_log('sending handshake: $handshake') + ws.socket_write(handshake_bytes) ? + ws.read_handshake(seckey) ? + unsafe { handshake_bytes.free() } +} + +// handle_server_handshake manages websocket server handshake process +fn (mut s Server) handle_server_handshake(mut c Client) ?(string, &ServerClient) { + msg := c.read_handshake_str() ? + handshake_response, client := s.parse_client_handshake(msg, mut c) ? + unsafe { msg.free() } + return handshake_response, client +} + +// parse_client_handshake parses result from handshake process +fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) ?(string, &ServerClient) { + s.logger.debug('server-> client handshake:\n$client_handshake') + lines := client_handshake.split_into_lines() + get_tokens := lines[0].split(' ') + if get_tokens.len < 3 { + return error_with_code('unexpected get operation, $get_tokens', 1) + } + if get_tokens[0].trim_space() != 'GET' { + return error_with_code("unexpected request '${get_tokens[0]}', expected 'GET'", + 2) + } + if get_tokens[2].trim_space() != 'HTTP/1.1' { + return error_with_code("unexpected request $get_tokens, expected 'HTTP/1.1'", + 3) + } + mut seckey := '' + mut flags := []Flag{} + mut key := '' + for i in 1 .. lines.len { + if lines[i].len <= 0 || lines[i] == '\r\n' { + continue + } + keys := lines[i].split(':') + match keys[0] { + 'Upgrade', 'upgrade' { + flags << .has_upgrade + } + 'Connection', 'connection' { + flags << .has_connection + } + 'Sec-WebSocket-Key', 'sec-websocket-key' { + key = keys[1].trim_space() + s.logger.debug('server-> got key: $key') + seckey = create_key_challenge_response(key) ? + s.logger.debug('server-> challenge: $seckey, response: ${keys[1]}') + flags << .has_accept + } + else { + // we ignore other headers like protocol for now + } + } + unsafe { keys.free() } + } + if flags.len < 3 { + return error_with_code('invalid client handshake, $client_handshake', 4) + } + server_handshake := 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $seckey\r\n\r\n' + server_client := &ServerClient{ + resource_name: get_tokens[1] + client_key: key + client: unsafe { c } + server: unsafe { s } + } + unsafe { + lines.free() + flags.free() + get_tokens.free() + seckey.free() + key.free() + } + return server_handshake, server_client +} + +// read_handshake_str returns the handshake response +fn (mut ws Client) read_handshake_str() ?string { + mut total_bytes_read := 0 + mut msg := [1024]byte{} + mut buffer := [1]byte{} + for total_bytes_read < 1024 { + bytes_read := ws.socket_read_ptr(&buffer[0], 1) ? + if bytes_read == 0 { + return error_with_code('unexpected no response from handshake', 5) + } + msg[total_bytes_read] = buffer[0] + total_bytes_read++ + if total_bytes_read > 5 && msg[total_bytes_read - 1] == `\n` + && msg[total_bytes_read - 2] == `\r` && msg[total_bytes_read - 3] == `\n` + && msg[total_bytes_read - 4] == `\r` { + break + } + } + res := msg[..total_bytes_read].bytestr() + return res +} + +// read_handshake reads the handshake result and check if valid +fn (mut ws Client) read_handshake(seckey string) ? { + mut msg := ws.read_handshake_str() ? + ws.check_handshake_response(msg, seckey) ? + unsafe { msg.free() } +} + +// check_handshake_response checks the response from handshake and returns +// the response and secure key provided by the websocket client +fn (mut ws Client) check_handshake_response(handshake_response string, seckey string) ? { + ws.debug_log('handshake response:\n$handshake_response') + lines := handshake_response.split_into_lines() + header := lines[0] + if !header.starts_with('HTTP/1.1 101') && !header.starts_with('HTTP/1.0 101') { + return error_with_code('handshake_handler: invalid HTTP status response code, $header', + 6) + } + for i in 1 .. lines.len { + if lines[i].len <= 0 || lines[i] == '\r\n' { + continue + } + keys := lines[i].split(':') + match keys[0] { + 'Upgrade', 'upgrade' { + ws.flags << .has_upgrade + } + 'Connection', 'connection' { + ws.flags << .has_connection + } + 'Sec-WebSocket-Accept', 'sec-websocket-accept' { + ws.debug_log('seckey: $seckey') + challenge := create_key_challenge_response(seckey) ? + ws.debug_log('challenge: $challenge, response: ${keys[1]}') + if keys[1].trim_space() != challenge { + return error_with_code('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.', + 7) + } + ws.flags << .has_accept + unsafe { challenge.free() } + } + else {} + } + unsafe { keys.free() } + } + unsafe { lines.free() } + if ws.flags.len < 3 { + ws.close(1002, 'invalid websocket HTTP headers') ? + return error_with_code('invalid websocket HTTP headers', 8) + } +} diff --git a/v_windows/v/vlib/net/websocket/io.v b/v_windows/v/vlib/net/websocket/io.v new file mode 100644 index 0000000..5408a4e --- /dev/null +++ b/v_windows/v/vlib/net/websocket/io.v @@ -0,0 +1,100 @@ +module websocket + +import net +import time + +// socket_read reads from socket into the provided buffer +fn (mut ws Client) socket_read(mut buffer []byte) ?int { + lock { + if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { + return error('socket_read: trying to read a closed socket') + } + if ws.is_ssl { + r := ws.ssl_conn.read_into(mut buffer) ? + return r + } else { + for { + r := ws.conn.read(mut buffer) or { + if err.code == net.err_timed_out_code { + continue + } + return err + } + return r + } + } + } + return none +} + +// socket_read reads from socket into the provided byte pointer and length +fn (mut ws Client) socket_read_ptr(buf_ptr &byte, len int) ?int { + lock { + if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { + return error('socket_read_ptr: trying to read a closed socket') + } + if ws.is_ssl { + r := ws.ssl_conn.socket_read_into_ptr(buf_ptr, len) ? + return r + } else { + for { + r := ws.conn.read_ptr(buf_ptr, len) or { + if err.code == net.err_timed_out_code { + continue + } + return err + } + return r + } + } + } + return none +} + +// socket_write writes the provided byte array to the socket +fn (mut ws Client) socket_write(bytes []byte) ?int { + lock { + if ws.state == .closed || ws.conn.sock.handle <= 1 { + ws.debug_log('socket_write: Socket allready closed') + return error('socket_write: trying to write on a closed socket') + } + if ws.is_ssl { + return ws.ssl_conn.write(bytes) + } else { + for { + n := ws.conn.write(bytes) or { + if err.code == net.err_timed_out_code { + continue + } + return err + } + return n + } + panic('reached unreachable code') + } + } +} + +// shutdown_socket shuts down the socket properly when connection is closed +fn (mut ws Client) shutdown_socket() ? { + ws.debug_log('shutting down socket') + if ws.is_ssl { + ws.ssl_conn.shutdown() ? + } else { + ws.conn.close() ? + } +} + +// dial_socket connects tcp socket and initializes default configurations +fn (mut ws Client) dial_socket() ?&net.TcpConn { + tcp_address := '$ws.uri.hostname:$ws.uri.port' + mut t := net.dial_tcp(tcp_address) ? + optval := int(1) + t.sock.set_option_int(.keep_alive, optval) ? + t.set_read_timeout(30 * time.second) + t.set_write_timeout(30 * time.second) + if ws.is_ssl { + ws.ssl_conn.connect(mut t, ws.uri.hostname) ? + } + return t +} diff --git a/v_windows/v/vlib/net/websocket/message.v b/v_windows/v/vlib/net/websocket/message.v new file mode 100644 index 0000000..4c57232 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/message.v @@ -0,0 +1,295 @@ +module websocket + +import encoding.utf8 + +const ( + header_len_offset = 2 // offset for lengthpart of websocket header + buffer_size = 256 // default buffer size + extended_payload16_end_byte = 4 // header length with 16-bit extended payload + extended_payload64_end_byte = 10 // header length with 64-bit extended payload +) + +// Fragment represents a websocket data fragment +struct Fragment { + data []byte // included data payload data in a fragment + opcode OPCode // interpretation of the payload data +} + +// Frame represents a data frame header +struct Frame { +mut: + // length of the websocket header part + header_len int = 2 + // size of total frame + frame_size int = 2 + fin bool // true if final fragment of message + rsv1 bool // reserved for future use in websocket RFC + rsv2 bool // reserved for future use in websocket RFC + rsv3 bool // reserved for future use in websocket RFC + opcode OPCode // interpretation of the payload data + has_mask bool // true if the payload data is masked + payload_len int // payload length + masking_key [4]byte // all frames from client to server is masked with this key +} + +const ( + invalid_close_codes = [999, 1004, 1005, 1006, 1014, 1015, 1016, 1100, 2000, 2999, 5000, 65536] +) + +// validate_client validates client frame rules from RFC6455 +pub fn (mut ws Client) validate_frame(frame &Frame) ? { + if frame.rsv1 || frame.rsv2 || frame.rsv3 { + ws.close(1002, 'rsv cannot be other than 0, not negotiated') ? + return error('rsv cannot be other than 0, not negotiated') + } + if (int(frame.opcode) >= 3 && int(frame.opcode) <= 7) + || (int(frame.opcode) >= 11 && int(frame.opcode) <= 15) { + ws.close(1002, 'use of reserved opcode') ? + return error('use of reserved opcode') + } + if frame.has_mask && !ws.is_server { + // server should never send masked frames + // to client, close connection + ws.close(1002, 'client got masked frame') ? + return error('client sent masked frame') + } + if is_control_frame(frame.opcode) { + if !frame.fin { + ws.close(1002, 'control message must not be fragmented') ? + return error('unexpected control frame with no fin') + } + if frame.payload_len > 125 { + ws.close(1002, 'control frames must not exceed 125 bytes') ? + return error('unexpected control frame payload length') + } + } + if frame.fin == false && ws.fragments.len == 0 && frame.opcode == .continuation { + err_msg := 'unexecpected continuation, there are no frames to continue, $frame' + ws.close(1002, err_msg) ? + return error(err_msg) + } +} + +// is_control_frame returns true if the frame is a control frame +fn is_control_frame(opcode OPCode) bool { + return opcode !in [.text_frame, .binary_frame, .continuation] +} + +// is_data_frame returns true if the frame is a control frame +fn is_data_frame(opcode OPCode) bool { + return opcode in [.text_frame, .binary_frame] +} + +// read_payload reads the message payload from the socket +fn (mut ws Client) read_payload(frame &Frame) ?[]byte { + if frame.payload_len == 0 { + return []byte{} + } + mut buffer := []byte{cap: frame.payload_len} + mut read_buf := [1]byte{} + mut bytes_read := 0 + for bytes_read < frame.payload_len { + len := ws.socket_read_ptr(&read_buf[0], 1) ? + if len != 1 { + return error('expected read all message, got zero') + } + bytes_read += len + buffer << read_buf[0] + } + if bytes_read != frame.payload_len { + return error('failed to read payload') + } + if frame.has_mask { + for i in 0 .. frame.payload_len { + buffer[i] ^= frame.masking_key[i % 4] & 0xff + } + } + return buffer +} + +// validate_utf_8 validates payload for valid utf8 encoding +// - Future implementation needs to support fail fast utf errors for strict autobahn conformance +fn (mut ws Client) validate_utf_8(opcode OPCode, payload []byte) ? { + if opcode in [.text_frame, .close] && !utf8.validate(payload.data, payload.len) { + ws.logger.error('malformed utf8 payload, payload len: ($payload.len)') + ws.send_error_event('Recieved malformed utf8.') + ws.close(1007, 'malformed utf8 payload') ? + return error('malformed utf8 payload') + } +} + +// read_next_message reads 1 to n frames to compose a message +pub fn (mut ws Client) read_next_message() ?Message { + for { + frame := ws.parse_frame_header() ? + ws.validate_frame(&frame) ? + frame_payload := ws.read_payload(&frame) ? + if is_control_frame(frame.opcode) { + // Control frames can interject other frames + // and need to be returned immediately + msg := Message{ + opcode: OPCode(frame.opcode) + payload: frame_payload.clone() + } + unsafe { frame_payload.free() } + return msg + } + // if the message is fragmented we just put it on fragments + // a fragment is allowed to have zero size payload + if !frame.fin { + ws.fragments << &Fragment{ + data: frame_payload.clone() + opcode: frame.opcode + } + unsafe { frame_payload.free() } + continue + } + if ws.fragments.len == 0 { + ws.validate_utf_8(frame.opcode, frame_payload) or { + ws.logger.error('UTF8 validation error: $err, len of payload($frame_payload.len)') + ws.send_error_event('UTF8 validation error: $err, len of payload($frame_payload.len)') + return err + } + msg := Message{ + opcode: OPCode(frame.opcode) + payload: frame_payload.clone() + } + unsafe { frame_payload.free() } + return msg + } + defer { + ws.fragments = [] + } + if is_data_frame(frame.opcode) { + ws.close(0, '') ? + return error('Unexpected frame opcode') + } + payload := ws.payload_from_fragments(frame_payload) ? + opcode := ws.opcode_from_fragments() + ws.validate_utf_8(opcode, payload) ? + msg := Message{ + opcode: opcode + payload: payload.clone() + } + unsafe { + frame_payload.free() + payload.free() + } + return msg + } + return none +} + +// payload_from_fragments returs the whole paylaod from fragmented message +fn (ws Client) payload_from_fragments(fin_payload []byte) ?[]byte { + mut total_size := 0 + for f in ws.fragments { + if f.data.len > 0 { + total_size += f.data.len + } + } + total_size += fin_payload.len + if total_size == 0 { + return []byte{} + } + mut total_buffer := []byte{cap: total_size} + for f in ws.fragments { + if f.data.len > 0 { + total_buffer << f.data + } + } + total_buffer << fin_payload + return total_buffer +} + +// opcode_from_fragments returns the opcode for message from the first fragment sent +fn (ws Client) opcode_from_fragments() OPCode { + return OPCode(ws.fragments[0].opcode) +} + +// parse_frame_header parses next message by decoding the incoming frames +pub fn (mut ws Client) parse_frame_header() ?Frame { + mut buffer := [256]byte{} + mut bytes_read := 0 + mut frame := Frame{} + mut rbuff := [1]byte{} + mut mask_end_byte := 0 + for ws.state == .open { + read_bytes := ws.socket_read_ptr(&rbuff[0], 1) ? + if read_bytes == 0 { + // this is probably a timeout or close + continue + } + buffer[bytes_read] = rbuff[0] + bytes_read++ + // parses the first two header bytes to get basic frame information + if bytes_read == u64(websocket.header_len_offset) { + frame.fin = (buffer[0] & 0x80) == 0x80 + frame.rsv1 = (buffer[0] & 0x40) == 0x40 + frame.rsv2 = (buffer[0] & 0x20) == 0x20 + frame.rsv3 = (buffer[0] & 0x10) == 0x10 + frame.opcode = OPCode(int(buffer[0] & 0x7F)) + frame.has_mask = (buffer[1] & 0x80) == 0x80 + frame.payload_len = buffer[1] & 0x7F + // if has mask set the byte postition where mask ends + if frame.has_mask { + mask_end_byte = if frame.payload_len < 126 { + websocket.header_len_offset + 4 + } else if frame.payload_len == 126 { + websocket.header_len_offset + 6 + } else if frame.payload_len == 127 { + websocket.header_len_offset + 12 + } else { + 0 + } // impossible + } + frame.payload_len = frame.payload_len + frame.frame_size = frame.header_len + frame.payload_len + if !frame.has_mask && frame.payload_len < 126 { + break + } + } + if frame.payload_len == 126 && bytes_read == u64(websocket.extended_payload16_end_byte) { + frame.header_len += 2 + frame.payload_len = 0 + frame.payload_len |= buffer[2] << 8 + frame.payload_len |= buffer[3] + frame.frame_size = frame.header_len + frame.payload_len + if !frame.has_mask { + break + } + } + if frame.payload_len == 127 && bytes_read == u64(websocket.extended_payload64_end_byte) { + frame.header_len += 8 + // these shift operators needs 64 bit on clang with -prod flag + mut payload_len := u64(0) + payload_len |= u64(buffer[2]) << 56 + payload_len |= u64(buffer[3]) << 48 + payload_len |= u64(buffer[4]) << 40 + payload_len |= u64(buffer[5]) << 32 + payload_len |= u64(buffer[6]) << 24 + payload_len |= u64(buffer[7]) << 16 + payload_len |= u64(buffer[8]) << 8 + payload_len |= u64(buffer[9]) + frame.payload_len = int(payload_len) + if !frame.has_mask { + break + } + } + if frame.has_mask && bytes_read == mask_end_byte { + frame.masking_key[0] = buffer[mask_end_byte - 4] + frame.masking_key[1] = buffer[mask_end_byte - 3] + frame.masking_key[2] = buffer[mask_end_byte - 2] + frame.masking_key[3] = buffer[mask_end_byte - 1] + break + } + } + return frame +} + +// unmask_sequence unmask any given sequence +fn (f Frame) unmask_sequence(mut buffer []byte) { + for i in 0 .. buffer.len { + buffer[i] ^= f.masking_key[i % 4] & 0xff + } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/README.md b/v_windows/v/vlib/net/websocket/tests/autobahn/README.md new file mode 100644 index 0000000..40724ee --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/README.md @@ -0,0 +1,20 @@ +# Autobahn tests + +This is the autobahn automatic tests on build. +The performance tests are skipped due to timeouts in Github actions. + +## Run it locally + +### Test the client + +This is how to test the client: + +1. Run the docker autobahn test suite by running the `docker-compose up` +2. From the `local_run` folder, compile and run `autobahn_client.v` to test non ws (no TLS) and +`autobahn_client_wss.v` to run the TLS tests +3. Open `http://localhost:8080` and browse client test results for non TLS and `https://localhost:8081` +if you ran the wss tests (it uses local certificat so you will get trust error but just accept use) + +### Test the server + +Todo: add information here \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client.v b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client.v new file mode 100644 index 0000000..c65fdab --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client.v @@ -0,0 +1,33 @@ +// use this test to test the websocket client in the autobahn test +module main + +import net.websocket + +fn main() { + for i in 1 .. 304 { + println('\ncase: $i') + handle_case(i) or { println('error should be ok: $err') } + } + // update the reports + uri := 'ws://autobahn_server:9001/updateReports?agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.connect() ? + ws.listen() ? +} + +fn handle_case(case_nr int) ? { + uri := 'ws://autobahn_server:9001/runCase?case=$case_nr&agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.on_message(on_message) + ws.connect() ? + ws.listen() ? +} + +fn on_message(mut ws websocket.Client, msg &websocket.Message) ? { + // autobahn tests expects to send same message back + if msg.opcode == .pong { + // We just wanna pass text and binary message back to autobahn + return + } + ws.write(msg.payload, msg.opcode) or { panic(err) } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client_wss.v b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client_wss.v new file mode 100644 index 0000000..c7a3c25 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_client_wss.v @@ -0,0 +1,35 @@ +// use this test to test the websocket client in the autobahn test +module main + +import net.websocket + +fn main() { + for i in 1 .. 304 { + println('\ncase: $i') + handle_case(i) or { println('error should be ok: $err') } + } + // update the reports + // uri := 'wss://localhost:9002/updateReports?agent=v-client' + uri := 'wss://autobahn_server_wss:9002/updateReports?agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.connect() ? + ws.listen() ? +} + +fn handle_case(case_nr int) ? { + uri := 'wss://autobahn_server_wss:9002/runCase?case=$case_nr&agent=v-client' + // uri := 'wss://localhost:9002/runCase?case=$case_nr&agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.on_message(on_message) + ws.connect() ? + ws.listen() ? +} + +fn on_message(mut ws websocket.Client, msg &websocket.Message) ? { + // autobahn tests expects to send same message back + if msg.opcode == .pong { + // We just wanna pass text and binary message back to autobahn + return + } + ws.write(msg.payload, msg.opcode) or { panic(err) } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_server.v b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_server.v new file mode 100644 index 0000000..0493ca9 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/autobahn_server.v @@ -0,0 +1,27 @@ +// use this to test websocket server to the autobahn test +module main + +import net.websocket + +fn main() { + mut s := websocket.new_server(.ip6, 9002, '/') + s.on_message(on_message) + s.listen() or { panic(err) } +} + +fn handle_case(case_nr int) ? { + uri := 'ws://localhost:9002/runCase?case=$case_nr&agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.on_message(on_message) + ws.connect() ? + ws.listen() ? +} + +fn on_message(mut ws websocket.Client, msg &websocket.Message) ? { + // autobahn tests expects to send same message back + if msg.opcode == .pong { + // We just wanna pass text and binary message back to autobahn + return + } + ws.write(msg.payload, msg.opcode) or { panic(err) } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/docker-compose.yml b/v_windows/v/vlib/net/websocket/tests/autobahn/docker-compose.yml new file mode 100644 index 0000000..30b58ec --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3' +services: + server: + container_name: autobahn_server + build: fuzzing_server + + ports: + - "9001:9001" + - "8080:8080" + server_wss: + container_name: autobahn_server_wss + build: fuzzing_server_wss + + ports: + - "9002:9002" + - "8081:8080" + client: + container_name: autobahn_client + build: + dockerfile: vlib/net/websocket/tests/autobahn/ws_test/Dockerfile + context: ../../../../../ diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/Dockerfile b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/Dockerfile new file mode 100644 index 0000000..ca5201b --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/Dockerfile @@ -0,0 +1,5 @@ +FROM crossbario/autobahn-testsuite +COPY check_results.py /check_results.py +RUN chmod +x /check_results.py + +COPY config /config diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/check_results.py b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/check_results.py new file mode 100644 index 0000000..9275c3c --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/check_results.py @@ -0,0 +1,46 @@ +import json + +nr_of_client_errs = 0 +nr_of_client_tests = 0 + +nr_of_server_errs = 0 +nr_of_server_tests = 0 + +with open("/reports/clients/index.json") as f: + data = json.load(f) + + for i in data["v-client"]: + # Count errors + if ( + data["v-client"][i]["behavior"] == "FAILED" + or data["v-client"][i]["behaviorClose"] == "FAILED" + ): + nr_of_client_errs = nr_of_client_errs + 1 + + nr_of_client_tests = nr_of_client_tests + 1 + +with open("/reports/servers/index.json") as f: + data = json.load(f) + + for i in data["AutobahnServer"]: + if ( + data["AutobahnServer"][i]["behavior"] == "FAILED" + or data["AutobahnServer"][i]["behaviorClose"] == "FAILED" + ): + nr_of_server_errs = nr_of_server_errs + 1 + + nr_of_server_tests = nr_of_server_tests + 1 + +if nr_of_client_errs > 0 or nr_of_server_errs > 0: + print( + "FAILED AUTOBAHN TESTS, CLIENT ERRORS {0}(of {1}), SERVER ERRORS {2}(of {3})".format( + nr_of_client_errs, nr_of_client_tests, nr_of_server_errs, nr_of_server_tests + ) + ) + exit(1) + +print( + "TEST SUCCESS!, CLIENT TESTS({0}), SERVER TESTS ({1})".format( + nr_of_client_tests, nr_of_server_tests + ) +) diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingclient.json b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingclient.json new file mode 100644 index 0000000..b5efbb8 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingclient.json @@ -0,0 +1,22 @@ +{ + "options": { + "failByDrop": false + }, + "outdir": "./reports/servers", + "servers": [ + { + "agent": "AutobahnServer", + "url": "ws://autobahn_client:9002" + } + ], + "cases": [ + "*" + ], + "exclude-cases": [ + "9.*", + "11.*", + "12.*", + "13.*" + ], + "exclude-agent-cases": {} +} \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingserver.json b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingserver.json new file mode 100644 index 0000000..3b044a1 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server/config/fuzzingserver.json @@ -0,0 +1,14 @@ +{ + "url": "ws://127.0.0.1:9001", + "outdir": "./reports/clients", + "cases": [ + "*" + ], + "exclude-cases": [ + "9.*", + "11.*", + "12.*", + "13.*" + ], + "exclude-agent-cases": {} +} \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/Dockerfile b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/Dockerfile new file mode 100644 index 0000000..67114c4 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/Dockerfile @@ -0,0 +1,9 @@ +FROM crossbario/autobahn-testsuite +COPY check_results.py /check_results.py +RUN chmod +x /check_results.py + +COPY config /config +RUN chmod +rx /config/server.crt +RUN chmod +rx /config/server.key + +EXPOSE 9002 9002 \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/check_results.py b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/check_results.py new file mode 100644 index 0000000..d75904c --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/check_results.py @@ -0,0 +1,35 @@ +import json + +nr_of_client_errs = 0 +nr_of_client_tests = 0 + +nr_of_server_errs = 0 +nr_of_server_tests = 0 + +with open("/reports/clients/index.json") as f: + data = json.load(f) + + for i in data["v-client"]: + # Count errors + if ( + data["v-client"][i]["behavior"] == "FAILED" + or data["v-client"][i]["behaviorClose"] == "FAILED" + ): + nr_of_client_errs = nr_of_client_errs + 1 + + nr_of_client_tests = nr_of_client_tests + 1 + + +if nr_of_client_errs > 0 or nr_of_server_errs > 0: + print( + "FAILED AUTOBAHN TESTS, CLIENT ERRORS {0}(of {1}), SERVER ERRORS {2}(of {3})".format( + nr_of_client_errs, nr_of_client_tests, nr_of_server_errs, nr_of_server_tests + ) + ) + exit(1) + +print( + "TEST SUCCESS!, CLIENT TESTS({0}), SERVER TESTS ({1})".format( + nr_of_client_tests, nr_of_server_tests + ) +) diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/fuzzingserver.json b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/fuzzingserver.json new file mode 100644 index 0000000..494dfff --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/fuzzingserver.json @@ -0,0 +1,16 @@ +{ + "url": "wss://127.0.0.1:9002", + "outdir": "./reports/clients", + "key": "/config/server.key", + "cert": "/config/server.crt", + "cases": [ + "*" + ], + "exclude-cases": [ + "9.*", + "11.*", + "12.*", + "13.*" + ], + "exclude-agent-cases": {} +} \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.crt b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.crt new file mode 100644 index 0000000..d4071d1 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFAtFKlcdB3jhD+AXPul81dwmZcs/MA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAxMTIxMDgyNjQ5WhcNMzAxMTE5MDgy +NjQ5WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnbysLfcIr9+wpoJjb5r728j2e07agedOzh8VLuGnHqmKOUPN +f8Ik707kEoBcFY7UM2A9G/1RMIysGp8eleQLMtNdeYc3KlKHBGFrOM3i4gCd7G44 +lERuKP1PKzRQ6RdVNUXn51XjfxjHWo7kHCEVvZowxvzxLxhwbSwmEmgzcQ1T6vj6 +Cdop87sdq00F+eOCfTdy+cl+R65sbImVdfY4EQ0QWAVdF3X6njLjpdmteppggbEa +ECv3R3qNIV7/rflIPm1efbqp7R1ugvjLPJZ1u12ovtqkgsWbnEyzST8hbEEjsOTJ +/cPkH2DaLdh7fMgfcVmqnYXd9T+gpsNGv98DjwIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQBG9GxUOjcrFd1ept9AOTzbxvIUvBiqIEzrL2/+3T1yPPAWQzOmBfZhIVVm +EZeeU3xcvd7+AmX+2FPCAD+evjSHjKY048X1YksQS7mYChSgeJiknoJi3mAEAyw6 +oYGVkETksZLQfXtWTjgljbIQrwTA1s+EW0jvmvaJnWD3/8nFqmfly2/kxVsTcGEa +wJGEUS53Cq6y6lLZ+ojjjj1iVCQ94U6L/0xPB9hgXOyL2+iQj+n38ruatnUNF77C +UKS7N9BFF42eqVY83Xab0m25s93m8Z7J/63qu0eeA8p5t7+8lbGvOYpwReknLRMf +pJfgSEWqWfSaetihbJl2Fmzg2SeJ +-----END CERTIFICATE----- diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.csr b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.csr new file mode 100644 index 0000000..6013ea9 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJ28rC33CK/fsKaCY2+a+9vI9ntO2oHnTs4fFS7h +px6pijlDzX/CJO9O5BKAXBWO1DNgPRv9UTCMrBqfHpXkCzLTXXmHNypShwRhazjN +4uIAnexuOJREbij9Tys0UOkXVTVF5+dV438Yx1qO5BwhFb2aMMb88S8YcG0sJhJo +M3ENU+r4+gnaKfO7HatNBfnjgn03cvnJfkeubGyJlXX2OBENEFgFXRd1+p4y46XZ +rXqaYIGxGhAr90d6jSFe/635SD5tXn26qe0dboL4yzyWdbtdqL7apILFm5xMs0k/ +IWxBI7Dkyf3D5B9g2i3Ye3zIH3FZqp2F3fU/oKbDRr/fA48CAwEAAaAAMA0GCSqG +SIb3DQEBCwUAA4IBAQARfNhaiioyJPZZ8Hkf9UPbi85djYLDYCC9EqBPHpYpGh15 +WdRsTModg/X5DeGwtWwRyGSP2ROMWa1NB5RHZ9buIgCIOeszhAvXVaQMlHmpNhSD +/hWKGGpAEq12TKHxgi9eTOE2u9MhoJf1G6iGffVsHc8r52THvGqKBp3Bi8G1Pl6L +2J1f5qX42K1DEnCx0gGnQkydO6E4UnMbsaDSFSODQwg5LpzSYoYUfpYHstMpqAqL +rcEt869YKjemKuTCzHODWxfqlvVr9GctNjKG2WtoqnX+10x3tw/9lsNRKUelCQxb +E56eujAoQdMxQ4OjwSnc/gbpWa5gXKYjpgAfx2kY +-----END CERTIFICATE REQUEST----- diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.key b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.key new file mode 100644 index 0000000..05c9d77 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAnbysLfcIr9+wpoJjb5r728j2e07agedOzh8VLuGnHqmKOUPN +f8Ik707kEoBcFY7UM2A9G/1RMIysGp8eleQLMtNdeYc3KlKHBGFrOM3i4gCd7G44 +lERuKP1PKzRQ6RdVNUXn51XjfxjHWo7kHCEVvZowxvzxLxhwbSwmEmgzcQ1T6vj6 +Cdop87sdq00F+eOCfTdy+cl+R65sbImVdfY4EQ0QWAVdF3X6njLjpdmteppggbEa +ECv3R3qNIV7/rflIPm1efbqp7R1ugvjLPJZ1u12ovtqkgsWbnEyzST8hbEEjsOTJ +/cPkH2DaLdh7fMgfcVmqnYXd9T+gpsNGv98DjwIDAQABAoIBAE+IFfiHGiYzT0pl +a+WV62+CAGVj+OCO1Dkxiui8dhsLuNnuyeqk5SKUUILTnZpxDaVp3OYD76/e/dfe +avmApfTWhccE2lfIjLM0u29EwCTb0sSnPnfjmPep4QUTt8gPL7NQsAEAWVh4Eewj +J/jW5bNXz0hFuQXZ+LXTEM8vIuDY4M0RX/jhEcCVr3QH8Sp/6JEeRY2Mbn5Z6LZ+ +BVuu8e4sCpamWOOWfoIQq3e3TbATFSNP9vzPLKvxwwAw9g5dAKPn3dvem8ofzaaF +MeJ6T485mnx0naBrI+1qHLb3QcRpSZp6uEOp/4uvkCFm9S3dBGIwOGwHcybWFfFr +StPfccECgYEAzN2f1BcvL3rt4970lG/MGNeLMpF7h7aWca0DzUNY5sCh+kvENHrD +U4nH5EHoqxB1c036LKBhsrrrk5F/eQ8M+QEqpKUfqAYUrfy+HRAAeTYbhLkCysrL ++X/mlqYeyzMHj4Pjy5rqoy2TnJFnfIZYwYOL/OfA9IPwGpW2rxVSk1cCgYEAxRul +9j0Ii3Te08TprfriDpAFQyLH54vqVwe8mkox3cdOyYvUNHdEmDNh3/7dadxVKsIx +gIkPdGcizOw4elLKWnNFQN3+dCc3LN/zhsop0a6Ow2IatWQ8qOSqNYtD2DGj0w3j +cJ/BZfacpr/OkAv0kjanYw4+ZSIH/r3Vjdli5okCgYBXltni4Ba4giJ7rrN7U2E7 +rcxBzpm2KIaiC4r4k7bK0clvLj2xAlvIt7vTB6rmmJ7esZQoyFl9BRX7fdW2eIzf +WXRV+JNUT2VADjNqUZEiQdP6Ju/erF4RSnHYLyYzUpoE7irSvmVbZv0Zj8FjKD2C +Xy/W7W8+G7roYuI8cS1g+QKBgQCDoHwK3SU4o9ouB0CZ64FMgkbRV4exi9D5P3Rm +gIeed/uYQiV6x+7pyN5ijDtl9zp0rGwMTvsgG8O0n0b0AReaoYGs2NKU1J9W+1MQ +Py8AFJbHyVrWqVKM4u77hL3QwQ2K4qpwym6HXdGs1UfnD+TKQ28yig+Gz9wQ9MqI +yJPwKQKBgQCmZxhmX1SUe3DVnVulMHDLUldbRbFns0VZLiSDhY+hjOAEmnvEdEHp +6L8/gvdTqUPF/VZQSQiZlii1oTIapQClI2oLfHcGytSorB+bpL7PxAKABp0pA6BS +JkXzEiV1h5anbxiwid5ZICt6QGQvGvBF7b1VSb+8p9WglLBWZo36pw== +-----END RSA PRIVATE KEY----- diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.pem b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.pem new file mode 100644 index 0000000..d4071d1 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/fuzzing_server_wss/config/server.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFAtFKlcdB3jhD+AXPul81dwmZcs/MA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAxMTIxMDgyNjQ5WhcNMzAxMTE5MDgy +NjQ5WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnbysLfcIr9+wpoJjb5r728j2e07agedOzh8VLuGnHqmKOUPN +f8Ik707kEoBcFY7UM2A9G/1RMIysGp8eleQLMtNdeYc3KlKHBGFrOM3i4gCd7G44 +lERuKP1PKzRQ6RdVNUXn51XjfxjHWo7kHCEVvZowxvzxLxhwbSwmEmgzcQ1T6vj6 +Cdop87sdq00F+eOCfTdy+cl+R65sbImVdfY4EQ0QWAVdF3X6njLjpdmteppggbEa +ECv3R3qNIV7/rflIPm1efbqp7R1ugvjLPJZ1u12ovtqkgsWbnEyzST8hbEEjsOTJ +/cPkH2DaLdh7fMgfcVmqnYXd9T+gpsNGv98DjwIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQBG9GxUOjcrFd1ept9AOTzbxvIUvBiqIEzrL2/+3T1yPPAWQzOmBfZhIVVm +EZeeU3xcvd7+AmX+2FPCAD+evjSHjKY048X1YksQS7mYChSgeJiknoJi3mAEAyw6 +oYGVkETksZLQfXtWTjgljbIQrwTA1s+EW0jvmvaJnWD3/8nFqmfly2/kxVsTcGEa +wJGEUS53Cq6y6lLZ+ojjjj1iVCQ94U6L/0xPB9hgXOyL2+iQj+n38ruatnUNF77C +UKS7N9BFF42eqVY83Xab0m25s93m8Z7J/63qu0eeA8p5t7+8lbGvOYpwReknLRMf +pJfgSEWqWfSaetihbJl2Fmzg2SeJ +-----END CERTIFICATE----- diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/Dockerfile b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/Dockerfile new file mode 100644 index 0000000..ee39644 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/Dockerfile @@ -0,0 +1,12 @@ +# Use this as docker builder with https://github.com/nektos/act +# build with: docker build tests/autobahn/. -t myimage +# use in act: act -P ubuntu-latest=myimage + +FROM node:12.6-buster-slim + +COPY config/fuzzingserver.json /config/fuzzingserver.json +RUN chmod +775 /config/fuzzingserver.json +RUN apt-get update && \ + apt-get install -y \ + docker \ + docker-compose \ No newline at end of file diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client.v b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client.v new file mode 100644 index 0000000..ef5b281 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client.v @@ -0,0 +1,33 @@ +// use this test to test the websocket client in the autobahn test +module main + +import net.websocket + +fn main() { + for i in 1 .. 304 { + println('\ncase: $i') + handle_case(i) or { println('error should be ok: $err') } + } + // update the reports + uri := 'ws://localhost:9001/updateReports?agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.connect() ? + ws.listen() ? +} + +fn handle_case(case_nr int) ? { + uri := 'ws://localhost:9001/runCase?case=$case_nr&agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.on_message(on_message) + ws.connect() ? + ws.listen() ? +} + +fn on_message(mut ws websocket.Client, msg &websocket.Message) ? { + // autobahn tests expects to send same message back + if msg.opcode == .pong { + // We just wanna pass text and binary message back to autobahn + return + } + ws.write(msg.payload, msg.opcode) or { panic(err) } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client_wss.v b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client_wss.v new file mode 100644 index 0000000..c7a3c25 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/local_run/autobahn_client_wss.v @@ -0,0 +1,35 @@ +// use this test to test the websocket client in the autobahn test +module main + +import net.websocket + +fn main() { + for i in 1 .. 304 { + println('\ncase: $i') + handle_case(i) or { println('error should be ok: $err') } + } + // update the reports + // uri := 'wss://localhost:9002/updateReports?agent=v-client' + uri := 'wss://autobahn_server_wss:9002/updateReports?agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.connect() ? + ws.listen() ? +} + +fn handle_case(case_nr int) ? { + uri := 'wss://autobahn_server_wss:9002/runCase?case=$case_nr&agent=v-client' + // uri := 'wss://localhost:9002/runCase?case=$case_nr&agent=v-client' + mut ws := websocket.new_client(uri) ? + ws.on_message(on_message) + ws.connect() ? + ws.listen() ? +} + +fn on_message(mut ws websocket.Client, msg &websocket.Message) ? { + // autobahn tests expects to send same message back + if msg.opcode == .pong { + // We just wanna pass text and binary message back to autobahn + return + } + ws.write(msg.payload, msg.opcode) or { panic(err) } +} diff --git a/v_windows/v/vlib/net/websocket/tests/autobahn/ws_test/Dockerfile b/v_windows/v/vlib/net/websocket/tests/autobahn/ws_test/Dockerfile new file mode 100644 index 0000000..b57cffd --- /dev/null +++ b/v_windows/v/vlib/net/websocket/tests/autobahn/ws_test/Dockerfile @@ -0,0 +1,12 @@ +FROM thevlang/vlang:buster-build + + +COPY ./ /src/ + +WORKDIR /src + +RUN make CC=clang + +RUN /src/v /src/vlib/net/websocket/tests/autobahn/autobahn_server.v +RUN chmod +x /src/vlib/net/websocket/tests/autobahn/autobahn_server +ENTRYPOINT [ "/src/vlib/net/websocket/tests/autobahn/autobahn_server" ] diff --git a/v_windows/v/vlib/net/websocket/uri.v b/v_windows/v/vlib/net/websocket/uri.v new file mode 100644 index 0000000..7d388e1 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/uri.v @@ -0,0 +1,16 @@ +module websocket + +// Uri represents an Uri for websocket connections +struct Uri { +mut: + url string // url to the websocket endpoint + hostname string // hostname of the websocket endpoint + port string // port of the websocket endpoint + resource string // resource of the websocket endpoint + querystring string // query string of the websocket endpoint +} + +// str returns the string representation of the Uri +pub fn (u Uri) str() string { + return u.url +} diff --git a/v_windows/v/vlib/net/websocket/utils.v b/v_windows/v/vlib/net/websocket/utils.v new file mode 100644 index 0000000..4e48359 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/utils.v @@ -0,0 +1,54 @@ +module websocket + +import rand +import crypto.sha1 +import encoding.base64 + +// htonl64 converts payload length to header bits +fn htonl64(payload_len u64) []byte { + mut ret := []byte{len: 8} + ret[0] = byte(((payload_len & (u64(0xff) << 56)) >> 56) & 0xff) + ret[1] = byte(((payload_len & (u64(0xff) << 48)) >> 48) & 0xff) + ret[2] = byte(((payload_len & (u64(0xff) << 40)) >> 40) & 0xff) + ret[3] = byte(((payload_len & (u64(0xff) << 32)) >> 32) & 0xff) + ret[4] = byte(((payload_len & (u64(0xff) << 24)) >> 24) & 0xff) + ret[5] = byte(((payload_len & (u64(0xff) << 16)) >> 16) & 0xff) + ret[6] = byte(((payload_len & (u64(0xff) << 8)) >> 8) & 0xff) + ret[7] = byte(((payload_len & (u64(0xff) << 0)) >> 0) & 0xff) + return ret +} + +// create_masking_key returs a new masking key to use when masking websocket messages +fn create_masking_key() []byte { + mask_bit := byte(rand.intn(255)) + buf := []byte{len: 4, init: `0`} + unsafe { C.memcpy(buf.data, &mask_bit, 4) } + return buf +} + +// create_key_challenge_response creates a key challange response from security key +fn create_key_challenge_response(seckey string) ?string { + if seckey.len == 0 { + return error('unexpected seckey lengt zero') + } + guid := '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + sha1buf := seckey + guid + shabytes := sha1buf.bytes() + hash := sha1.sum(shabytes) + b64 := base64.encode(hash) + unsafe { + hash.free() + shabytes.free() + } + return b64 +} + +// get_nonce creates a randomized array used in handshake process +fn get_nonce(nonce_size int) string { + mut nonce := []byte{len: nonce_size, cap: nonce_size} + alphanum := '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz' + for i in 0 .. nonce_size { + nonce[i] = alphanum[rand.intn(alphanum.len)] + } + return unsafe { tos(nonce.data, nonce.len) }.clone() +} diff --git a/v_windows/v/vlib/net/websocket/websocket_client.v b/v_windows/v/vlib/net/websocket/websocket_client.v new file mode 100644 index 0000000..48f8c5c --- /dev/null +++ b/v_windows/v/vlib/net/websocket/websocket_client.v @@ -0,0 +1,488 @@ +// websocket module implements websocket client and a websocket server +// attribution: @thecoderr the author of original websocket client +[manualfree] +module websocket + +import net +import net.http +import net.openssl +import net.urllib +import time +import log +import rand + +const ( + empty_bytearr = []byte{} // used as empty response to avoid allocation +) + +// Client represents websocket client +pub struct Client { + is_server bool +mut: + ssl_conn &openssl.SSLConn // secure connection used when wss is used + flags []Flag // flags used in handshake + fragments []Fragment // current fragments + message_callbacks []MessageEventHandler // all callbacks on_message + error_callbacks []ErrorEventHandler // all callbacks on_error + open_callbacks []OpenEventHandler // all callbacks on_open + close_callbacks []CloseEventHandler // all callbacks on_close +pub: + is_ssl bool // true if secure socket is used + uri Uri // uri of current connection + id string // unique id of client +pub mut: + header http.Header // headers that will be passed when connecting + conn &net.TcpConn // underlying TCP socket connection + nonce_size int = 16 // size of nounce used for masking + panic_on_callback bool // set to true of callbacks can panic + state State // current state of connection + logger &log.Log // logger used to log messages + resource_name string // name of current resource + last_pong_ut i64 // last time in unix time we got a pong message +} + +// Flag represents different types of headers in websocket handshake +enum Flag { + has_accept // Webs + has_connection + has_upgrade +} + +// State represents the state of the websocket connection. +pub enum State { + connecting = 0 + open + closing + closed +} + +// Message represents a whole message combined from 1 to n frames +pub struct Message { +pub: + opcode OPCode // websocket frame type of this message + payload []byte // payload of the message +} + +// OPCode represents the supported websocket frame types +pub enum OPCode { + continuation = 0x00 + text_frame = 0x01 + binary_frame = 0x02 + close = 0x08 + ping = 0x09 + pong = 0x0A +} + +// new_client instance a new websocket client +pub fn new_client(address string) ?&Client { + uri := parse_uri(address) ? + return &Client{ + conn: 0 + is_server: false + ssl_conn: openssl.new_ssl_conn() + is_ssl: address.starts_with('wss') + logger: &log.Log{ + level: .info + } + uri: uri + state: .closed + id: rand.uuid_v4() + header: http.new_header() + } +} + +// connect connects to remote websocket server +pub fn (mut ws Client) connect() ? { + ws.assert_not_connected() ? + ws.set_state(.connecting) + ws.logger.info('connecting to host $ws.uri') + ws.conn = ws.dial_socket() ? + // Todo: make setting configurable + ws.conn.set_read_timeout(time.second * 30) + ws.conn.set_write_timeout(time.second * 30) + ws.handshake() ? + ws.set_state(.open) + ws.logger.info('successfully connected to host $ws.uri') + ws.send_open_event() +} + +// listen listens and processes incoming messages +pub fn (mut ws Client) listen() ? { + mut log := 'Starting client listener, server($ws.is_server)...' + ws.logger.info(log) + unsafe { log.free() } + defer { + ws.logger.info('Quit client listener, server($ws.is_server)...') + if ws.state == .open { + ws.close(1000, 'closed by client') or {} + } + } + for ws.state == .open { + msg := ws.read_next_message() or { + if ws.state in [.closed, .closing] { + return + } + ws.debug_log('failed to read next message: $err') + ws.send_error_event('failed to read next message: $err') + return err + } + if ws.state in [.closed, .closing] { + return + } + ws.debug_log('got message: $msg.opcode') + match msg.opcode { + .text_frame { + log = 'read: text' + ws.debug_log(log) + unsafe { log.free() } + ws.send_message_event(msg) + unsafe { msg.free() } + } + .binary_frame { + ws.debug_log('read: binary') + ws.send_message_event(msg) + unsafe { msg.free() } + } + .ping { + ws.debug_log('read: ping, sending pong') + ws.send_control_frame(.pong, 'PONG', msg.payload) or { + ws.logger.error('error in message callback sending PONG: $err') + ws.send_error_event('error in message callback sending PONG: $err') + if ws.panic_on_callback { + panic(err) + } + continue + } + if msg.payload.len > 0 { + unsafe { msg.free() } + } + } + .pong { + ws.debug_log('read: pong') + ws.last_pong_ut = time.now().unix + ws.send_message_event(msg) + if msg.payload.len > 0 { + unsafe { msg.free() } + } + } + .close { + log = 'read: close' + ws.debug_log(log) + unsafe { log.free() } + defer { + ws.manage_clean_close() + } + if msg.payload.len > 0 { + if msg.payload.len == 1 { + ws.close(1002, 'close payload cannot be 1 byte') ? + return error('close payload cannot be 1 byte') + } + code := (int(msg.payload[0]) << 8) + int(msg.payload[1]) + if code in invalid_close_codes { + ws.close(1002, 'invalid close code: $code') ? + return error('invalid close code: $code') + } + reason := if msg.payload.len > 2 { msg.payload[2..] } else { []byte{} } + if reason.len > 0 { + ws.validate_utf_8(.close, reason) ? + } + if ws.state !in [.closing, .closed] { + // sending close back according to spec + ws.debug_log('close with reason, code: $code, reason: $reason') + r := reason.bytestr() + ws.close(code, r) ? + } + unsafe { msg.free() } + } else { + if ws.state !in [.closing, .closed] { + ws.debug_log('close with reason, no code') + // sending close back according to spec + ws.close(1000, 'normal') ? + } + unsafe { msg.free() } + } + return + } + .continuation { + ws.logger.error('unexpected opcode continuation, nothing to continue') + ws.send_error_event('unexpected opcode continuation, nothing to continue') + ws.close(1002, 'nothing to continue') ? + return error('unexpected opcode continuation, nothing to continue') + } + } + } +} + +// manage_clean_close closes connection in a clean websocket way +fn (mut ws Client) manage_clean_close() { + ws.send_close_event(1000, 'closed by client') +} + +// ping sends ping message to server +pub fn (mut ws Client) ping() ? { + ws.send_control_frame(.ping, 'PING', []) ? +} + +// pong sends pong message to server, +pub fn (mut ws Client) pong() ? { + ws.send_control_frame(.pong, 'PONG', []) ? +} + +// write_ptr writes len bytes provided a byteptr with a websocket messagetype +pub fn (mut ws Client) write_ptr(bytes &byte, payload_len int, code OPCode) ?int { + // ws.debug_log('write_ptr code: $code') + if ws.state != .open || ws.conn.sock.handle < 1 { + // todo: send error here later + return error('trying to write on a closed socket!') + } + mut header_len := 2 + if payload_len > 125 { 2 } else { 0 } + + if payload_len > 0xffff { 6 } else { 0 } + if !ws.is_server { + header_len += 4 + } + mut header := []byte{len: header_len, init: `0`} // [`0`].repeat(header_len) + header[0] = byte(int(code)) | 0x80 + masking_key := create_masking_key() + if ws.is_server { + if payload_len <= 125 { + header[1] = byte(payload_len) + } else if payload_len > 125 && payload_len <= 0xffff { + len16 := C.htons(payload_len) + header[1] = 126 + unsafe { C.memcpy(&header[2], &len16, 2) } + } else if payload_len > 0xffff && payload_len <= 0x7fffffff { + len_bytes := htonl64(u64(payload_len)) + header[1] = 127 + unsafe { C.memcpy(&header[2], len_bytes.data, 8) } + } + } else { + if payload_len <= 125 { + header[1] = byte(payload_len | 0x80) + header[2] = masking_key[0] + header[3] = masking_key[1] + header[4] = masking_key[2] + header[5] = masking_key[3] + } else if payload_len > 125 && payload_len <= 0xffff { + len16 := C.htons(payload_len) + header[1] = (126 | 0x80) + unsafe { C.memcpy(&header[2], &len16, 2) } + header[4] = masking_key[0] + header[5] = masking_key[1] + header[6] = masking_key[2] + header[7] = masking_key[3] + } else if payload_len > 0xffff && payload_len <= 0x7fffffff { + len64 := htonl64(u64(payload_len)) + header[1] = (127 | 0x80) + unsafe { C.memcpy(&header[2], len64.data, 8) } + header[10] = masking_key[0] + header[11] = masking_key[1] + header[12] = masking_key[2] + header[13] = masking_key[3] + } else { + ws.close(1009, 'frame too large') ? + return error('frame too large') + } + } + len := header.len + payload_len + mut frame_buf := []byte{len: len} + unsafe { + C.memcpy(&frame_buf[0], &byte(header.data), header.len) + if payload_len > 0 { + C.memcpy(&frame_buf[header.len], bytes, payload_len) + } + } + if !ws.is_server { + for i in 0 .. payload_len { + frame_buf[header_len + i] ^= masking_key[i % 4] & 0xff + } + } + written_len := ws.socket_write(frame_buf) ? + unsafe { + frame_buf.free() + masking_key.free() + header.free() + } + return written_len +} + +// write writes a byte array with a websocket messagetype to socket +pub fn (mut ws Client) write(bytes []byte, code OPCode) ?int { + return ws.write_ptr(&byte(bytes.data), bytes.len, code) +} + +// write_str, writes a string with a websocket texttype to socket +pub fn (mut ws Client) write_string(str string) ?int { + return ws.write_ptr(str.str, str.len, .text_frame) +} + +// close closes the websocket connection +pub fn (mut ws Client) close(code int, message string) ? { + ws.debug_log('sending close, $code, $message') + if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { + ws.debug_log('close: Websocket allready closed ($ws.state), $message, $code handle($ws.conn.sock.handle)') + err_msg := 'Socket allready closed: $code' + return error(err_msg) + } + defer { + ws.shutdown_socket() or {} + ws.reset_state() + } + ws.set_state(.closing) + // mut code32 := 0 + if code > 0 { + code_ := C.htons(code) + message_len := message.len + 2 + mut close_frame := []byte{len: message_len} + close_frame[0] = byte(code_ & 0xFF) + close_frame[1] = byte(code_ >> 8) + // code32 = (close_frame[0] << 8) + close_frame[1] + for i in 0 .. message.len { + close_frame[i + 2] = message[i] + } + ws.send_control_frame(.close, 'CLOSE', close_frame) ? + unsafe { close_frame.free() } + } else { + ws.send_control_frame(.close, 'CLOSE', []) ? + } + ws.fragments = [] +} + +// send_control_frame sends a control frame to the server +fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []byte) ? { + ws.debug_log('send control frame $code, frame_type: $frame_typ') + if ws.state !in [.open, .closing] && ws.conn.sock.handle > 1 { + return error('socket is not connected') + } + header_len := if ws.is_server { 2 } else { 6 } + frame_len := header_len + payload.len + mut control_frame := []byte{len: frame_len} + mut masking_key := if !ws.is_server { create_masking_key() } else { websocket.empty_bytearr } + defer { + unsafe { + control_frame.free() + if masking_key.len > 0 { + masking_key.free() + } + } + } + control_frame[0] = byte(int(code) | 0x80) + if !ws.is_server { + control_frame[1] = byte(payload.len | 0x80) + control_frame[2] = masking_key[0] + control_frame[3] = masking_key[1] + control_frame[4] = masking_key[2] + control_frame[5] = masking_key[3] + } else { + control_frame[1] = byte(payload.len) + } + if code == .close { + if payload.len >= 2 { + if !ws.is_server { + mut parsed_payload := []byte{len: payload.len + 1} + unsafe { C.memcpy(parsed_payload.data, &payload[0], payload.len) } + parsed_payload[payload.len] = `\0` + for i in 0 .. payload.len { + control_frame[6 + i] = (parsed_payload[i] ^ masking_key[i % 4]) & 0xff + } + unsafe { parsed_payload.free() } + } else { + unsafe { C.memcpy(&control_frame[2], &payload[0], payload.len) } + } + } + } else { + if !ws.is_server { + if payload.len > 0 { + for i in 0 .. payload.len { + control_frame[header_len + i] = (payload[i] ^ masking_key[i % 4]) & 0xff + } + } + } else { + if payload.len > 0 { + unsafe { C.memcpy(&control_frame[2], &payload[0], payload.len) } + } + } + } + ws.socket_write(control_frame) or { + return error('send_control_frame: error sending $frame_typ control frame.') + } +} + +// parse_uri parses the url to a Uri +fn parse_uri(url string) ?&Uri { + u := urllib.parse(url) ? + request_uri := u.request_uri() + v := request_uri.split('?') + mut port := u.port() + uri := u.str() + if port == '' { + port = if uri.starts_with('ws://') { + '80' + } else if uri.starts_with('wss://') { + '443' + } else { + u.port() + } + } + querystring := if v.len > 1 { '?' + v[1] } else { '' } + return &Uri{ + url: url + hostname: u.hostname() + port: port + resource: v[0] + querystring: querystring + } +} + +// set_state sets current state of the websocket connection +fn (mut ws Client) set_state(state State) { + lock { + ws.state = state + } +} + +// assert_not_connected returns error if the connection is not connected +fn (ws Client) assert_not_connected() ? { + match ws.state { + .connecting { return error('connect: websocket is connecting') } + .open { return error('connect: websocket already open') } + .closing { return error('connect: reconnect on closing websocket not supported, please use new client') } + else {} + } +} + +// reset_state resets the websocket and initialize default settings +fn (mut ws Client) reset_state() { + lock { + ws.state = .closed + ws.ssl_conn = openssl.new_ssl_conn() + ws.flags = [] + ws.fragments = [] + } +} + +// debug_log handles debug logging output for client and server +fn (mut ws Client) debug_log(text string) { + if ws.is_server { + ws.logger.debug('server-> $text') + } else { + ws.logger.debug('client-> $text') + } +} + +// free handles manual free memory of Message struct +pub fn (m &Message) free() { + unsafe { m.payload.free() } +} + +// free handles manual free memory of Client struct +pub fn (c &Client) free() { + unsafe { + c.flags.free() + c.fragments.free() + c.message_callbacks.free() + c.error_callbacks.free() + c.open_callbacks.free() + c.close_callbacks.free() + c.header.free() + } +} diff --git a/v_windows/v/vlib/net/websocket/websocket_nix.c.v b/v_windows/v/vlib/net/websocket/websocket_nix.c.v new file mode 100644 index 0000000..f986b98 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/websocket_nix.c.v @@ -0,0 +1,10 @@ +module websocket + +// error_code returns the error code +fn error_code() int { + return C.errno +} + +const ( + error_ewouldblock = C.EWOULDBLOCK // blocking error code +) diff --git a/v_windows/v/vlib/net/websocket/websocket_server.v b/v_windows/v/vlib/net/websocket/websocket_server.v new file mode 100644 index 0000000..99af3e0 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/websocket_server.v @@ -0,0 +1,189 @@ +module websocket + +import net +import net.openssl +import log +import time +import rand + +// Server represents a websocket server connection +pub struct Server { +mut: + logger &log.Log // logger used to log + ls &net.TcpListener // listener used to get incoming connection to socket + accept_client_callbacks []AcceptClientFn // accept client callback functions + message_callbacks []MessageEventHandler // new message callback functions + close_callbacks []CloseEventHandler // close message callback functions +pub: + family net.AddrFamily = .ip + port int // port used as listen to incoming connections + is_ssl bool // true if secure connection (not supported yet on server) +pub mut: + clients map[string]&ServerClient // clients connected to this server + ping_interval int = 30 // interval for sending ping to clients (seconds) + state State // current state of connection +} + +// ServerClient represents a connected client +struct ServerClient { +pub: + resource_name string // resource that the client access + client_key string // unique key of client +pub mut: + server &Server + client &Client +} + +// new_server instance a new websocket server on provided port and route +pub fn new_server(family net.AddrFamily, port int, route string) &Server { + return &Server{ + ls: 0 + family: family + port: port + logger: &log.Log{ + level: .info + } + state: .closed + } +} + +// set_ping_interval sets the interval that the server will send ping messages to clients +pub fn (mut s Server) set_ping_interval(seconds int) { + s.ping_interval = seconds +} + +// listen start listen and process to incoming connections from websocket clients +pub fn (mut s Server) listen() ? { + s.logger.info('websocket server: start listen on port $s.port') + s.ls = net.listen_tcp(s.family, ':$s.port') ? + s.set_state(.open) + go s.handle_ping() + for { + mut c := s.accept_new_client() or { continue } + go s.serve_client(mut c) + } + s.logger.info('websocket server: end listen on port $s.port') +} + +// Close closes server (not implemented yet) +fn (mut s Server) close() { + // TODO: implement close when moving to net from x.net +} + +// handle_ping sends ping to all clients every set interval +fn (mut s Server) handle_ping() { + mut clients_to_remove := []string{} + for s.state == .open { + time.sleep(s.ping_interval * time.second) + for i, _ in s.clients { + mut c := s.clients[i] + if c.client.state == .open { + c.client.ping() or { + s.logger.debug('server-> error sending ping to client') + c.client.close(1002, 'Closing connection: ping send error') or { + // we want to continue even if error + continue + } + clients_to_remove << c.client.id + } + if (time.now().unix - c.client.last_pong_ut) > s.ping_interval * 2 { + clients_to_remove << c.client.id + c.client.close(1000, 'no pong received') or { continue } + } + } + } + // TODO: replace for with s.clients.delete_all(clients_to_remove) if (https://github.com/vlang/v/pull/6020) merges + for client in clients_to_remove { + lock { + s.clients.delete(client) + } + } + clients_to_remove.clear() + } +} + +// serve_client accepts incoming connection and sets up the callbacks +fn (mut s Server) serve_client(mut c Client) ? { + c.logger.debug('server-> Start serve client ($c.id)') + defer { + c.logger.debug('server-> End serve client ($c.id)') + } + mut handshake_response, mut server_client := s.handle_server_handshake(mut c) ? + accept := s.send_connect_event(mut server_client) ? + if !accept { + s.logger.debug('server-> client not accepted') + c.shutdown_socket() ? + return + } + // the client is accepted + c.socket_write(handshake_response.bytes()) ? + lock { + s.clients[server_client.client.id] = server_client + } + s.setup_callbacks(mut server_client) + c.listen() or { + s.logger.error(err.msg) + return err + } +} + +// setup_callbacks initialize all callback functions +fn (mut s Server) setup_callbacks(mut sc ServerClient) { + if s.message_callbacks.len > 0 { + for cb in s.message_callbacks { + if cb.is_ref { + sc.client.on_message_ref(cb.handler2, cb.ref) + } else { + sc.client.on_message(cb.handler) + } + } + } + if s.close_callbacks.len > 0 { + for cb in s.close_callbacks { + if cb.is_ref { + sc.client.on_close_ref(cb.handler2, cb.ref) + } else { + sc.client.on_close(cb.handler) + } + } + } + // set standard close so we can remove client if closed + sc.client.on_close_ref(fn (mut c Client, code int, reason string, mut sc ServerClient) ? { + c.logger.debug('server-> Delete client') + lock { + sc.server.clients.delete(sc.client.id) + } + }, sc) +} + +// accept_new_client creates a new client instance for client that connects to the socket +fn (mut s Server) accept_new_client() ?&Client { + mut new_conn := s.ls.accept() ? + c := &Client{ + is_server: true + conn: new_conn + ssl_conn: openssl.new_ssl_conn() + logger: s.logger + state: .open + last_pong_ut: time.now().unix + id: rand.uuid_v4() + } + return c +} + +// set_state sets current state in a thread safe way +fn (mut s Server) set_state(state State) { + lock { + s.state = state + } +} + +// free manages manual free of memory for Server instance +pub fn (mut s Server) free() { + unsafe { + s.clients.free() + s.accept_client_callbacks.free() + s.message_callbacks.free() + s.close_callbacks.free() + } +} diff --git a/v_windows/v/vlib/net/websocket/websocket_test.v b/v_windows/v/vlib/net/websocket/websocket_test.v new file mode 100644 index 0000000..35e15d3 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/websocket_test.v @@ -0,0 +1,122 @@ +import os +import net +import net.websocket +import time +import rand + +// TODO: fix connecting to ipv4 websockets +// (the server seems to work with .ip, but +// Client can not connect, it needs to be passed +// .ip too?) + +struct WebsocketTestResults { +pub mut: + nr_messages int + nr_pong_received int +} + +// Do not run these tests everytime, since they are flaky. +// They have their own specialized CI runner. +const github_job = os.getenv('GITHUB_JOB') + +const should_skip = github_job != '' && github_job != 'websocket_tests' + +// tests with internal ws servers +fn test_ws_ipv6() { + if should_skip { + return + } + port := 30000 + rand.intn(1024) + go start_server(.ip6, port) + time.sleep(500 * time.millisecond) + ws_test(.ip6, 'ws://localhost:$port') or { assert false } +} + +// tests with internal ws servers +fn test_ws_ipv4() { + // TODO: fix client + if true || should_skip { + return + } + port := 30000 + rand.intn(1024) + go start_server(.ip, port) + time.sleep(500 * time.millisecond) + ws_test(.ip, 'ws://localhost:$port') or { assert false } +} + +fn start_server(family net.AddrFamily, listen_port int) ? { + mut s := websocket.new_server(family, listen_port, '') + // make that in execution test time give time to execute at least one time + s.ping_interval = 1 + + s.on_connect(fn (mut s websocket.ServerClient) ?bool { + // here you can look att the client info and accept or not accept + // just returning a true/false + if s.resource_name != '/' { + panic('unexpected resource name in test') + return false + } + return true + }) ? + s.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ? { + match msg.opcode { + .pong { ws.write_string('pong') or { panic(err) } } + else { ws.write(msg.payload, msg.opcode) or { panic(err) } } + } + }) + + s.on_close(fn (mut ws websocket.Client, code int, reason string) ? { + // not used + }) + s.listen() or {} +} + +// ws_test tests connect to the websocket server from websocket client +fn ws_test(family net.AddrFamily, uri string) ? { + eprintln('connecting to $uri ...') + + mut test_results := WebsocketTestResults{} + mut ws := websocket.new_client(uri) ? + ws.on_open(fn (mut ws websocket.Client) ? { + ws.pong() ? + assert true + }) + ws.on_error(fn (mut ws websocket.Client, err string) ? { + println('error: $err') + // this can be thrown by internet connection problems + assert false + }) + + ws.on_message_ref(fn (mut ws websocket.Client, msg &websocket.Message, mut res WebsocketTestResults) ? { + println('client got type: $msg.opcode payload:\n$msg.payload') + if msg.opcode == .text_frame { + smessage := msg.payload.bytestr() + match smessage { + 'pong' { + res.nr_pong_received++ + } + 'a' { + res.nr_messages++ + } + else { + assert false + } + } + } else { + println('Binary message: $msg') + } + }, test_results) + ws.connect() or { panic('fail to connect') } + go ws.listen() + text := ['a'].repeat(2) + for msg in text { + ws.write(msg.bytes(), .text_frame) or { panic('fail to write to websocket') } + // sleep to give time to recieve response before send a new one + time.sleep(100 * time.millisecond) + } + // sleep to give time to recieve response before asserts + time.sleep(1500 * time.millisecond) + // We expect at least 2 pongs, one sent directly and one indirectly + assert test_results.nr_pong_received >= 2 + assert test_results.nr_messages == 2 +} diff --git a/v_windows/v/vlib/net/websocket/websocket_windows.c.v b/v_windows/v/vlib/net/websocket/websocket_windows.c.v new file mode 100644 index 0000000..e9f4fc3 --- /dev/null +++ b/v_windows/v/vlib/net/websocket/websocket_windows.c.v @@ -0,0 +1,12 @@ +module websocket + +import net + +// error_code returns the error code +fn error_code() int { + return C.WSAGetLastError() +} + +const ( + error_ewouldblock = net.WsaError.wsaewouldblock // blocking error code +) diff --git a/v_windows/v/vlib/orm/README.md b/v_windows/v/vlib/orm/README.md new file mode 100644 index 0000000..5cfa1fd --- /dev/null +++ b/v_windows/v/vlib/orm/README.md @@ -0,0 +1,85 @@ +# ORM + +## Attributes + +### Structs + +- `[table: 'name']` sets a custom table name + +### Fields + +- `[primary]` sets the field as the primary key +- `[unique]` sets the field as unique +- `[unique: 'foo']` adds the field to a unique group +- `[skip]` field will be skipped +- `[sql: type]` sets the type which is used in sql (special type `serial`) +- `[sql: 'name']` sets a custom column name for the field + +## Usage + +```v ignore +struct Foo { + id int [primary; sql: serial] + name string [nonull] +} +``` + +### Create + +```v ignore +sql db { + create table Foo +} +``` + +### Drop + +```v ignore +sql db { + drop table Foo +} +``` + +### Insert + +```v ignore +var := Foo{ + name: 'abc' +} + +sql db { + insert var into Foo +} +``` + +### Update + +```v ignore +sql db { + update Foo set name = 'cde' where name == 'abc' +} +``` + +### Delete +```v ignore +sql db { + delete from Foo where id > 10 +} +``` + +### Select +```v ignore +result := sql db { + select from Foo where id == 1 +} +``` +```v ignore +result := sql db { + select from Foo where id > 1 limit 5 +} +``` +```v ignore +result := sql db { + select from Foo where id > 1 order by id +} +``` diff --git a/v_windows/v/vlib/orm/orm.v b/v_windows/v/vlib/orm/orm.v new file mode 100644 index 0000000..960e176 --- /dev/null +++ b/v_windows/v/vlib/orm/orm.v @@ -0,0 +1,473 @@ +module orm + +import time + +pub const ( + num64 = [8, 12] + nums = [5, 6, 7, 9, 10, 11, 16] + float = [13, 14] + string = 18 + time = -2 + type_idx = { + 'i8': 5 + 'i16': 6 + 'int': 7 + 'i64': 8 + 'byte': 9 + 'u16': 10 + 'u32': 11 + 'u64': 12 + 'f32': 13 + 'f64': 14 + 'bool': 16 + 'string': 18 + } + string_max_len = 2048 +) + +pub type Primitive = InfixType | bool | byte | f32 | f64 | i16 | i64 | i8 | int | string | + time.Time | u16 | u32 | u64 + +pub enum OperationKind { + neq // != + eq // == + gt // > + lt // < + ge // >= + le // <= +} + +pub enum MathOperationKind { + add // + + sub // - + mul // * + div // / +} + +pub enum StmtKind { + insert + update + delete +} + +pub enum OrderType { + asc + desc +} + +fn (kind OperationKind) to_str() string { + str := match kind { + .neq { '!=' } + .eq { '=' } + .gt { '>' } + .lt { '<' } + .ge { '>=' } + .le { '<=' } + } + return str +} + +fn (kind OrderType) to_str() string { + return match kind { + .desc { + 'DESC' + } + .asc { + 'ASC' + } + } +} + +pub struct QueryData { +pub: + fields []string + data []Primitive + types []int + kinds []OperationKind + is_and []bool +} + +pub struct InfixType { +pub: + name string + operator MathOperationKind + right Primitive +} + +pub struct TableField { +pub: + name string + typ int + is_time bool + default_val string + is_arr bool + attrs []StructAttribute +} + +pub struct SelectConfig { +pub: + table string + is_count bool + has_where bool + has_order bool + order string + order_type OrderType + has_limit bool + primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false + has_offset bool + fields []string + types []int +} + +pub interface Connection { + @select(config SelectConfig, data QueryData, where QueryData) ?[][]Primitive + insert(table string, data QueryData) ? + update(table string, data QueryData, where QueryData) ? + delete(table string, where QueryData) ? + create(table string, fields []TableField) ? + drop(talbe string) ? + last_id() Primitive +} + +pub fn orm_stmt_gen(table string, para string, kind StmtKind, num bool, qm string, start_pos int, data QueryData, where QueryData) string { + mut str := '' + + mut c := start_pos + + match kind { + .insert { + mut values := []string{} + + for _ in 0 .. data.fields.len { + // loop over the length of data.field and generate ?0, ?1 or just ? based on the $num parameter for value placeholders + if num { + values << '$qm$c' + c++ + } else { + values << '$qm' + } + } + + str += 'INSERT INTO $para$table$para (' + str += data.fields.map('$para$it$para').join(', ') + str += ') VALUES (' + str += values.join(', ') + str += ')' + } + .update { + str += 'UPDATE $para$table$para SET ' + for i, field in data.fields { + str += '$para$field$para = ' + if data.data.len > i { + d := data.data[i] + if d is InfixType { + op := match d.operator { + .add { + '+' + } + .sub { + '-' + } + .mul { + '*' + } + .div { + '/' + } + } + str += '$d.name $op $qm' + } else { + str += '$qm' + } + } else { + str += '$qm' + } + if num { + str += '$c' + c++ + } + if i < data.fields.len - 1 { + str += ', ' + } + } + str += ' WHERE ' + } + .delete { + str += 'DELETE FROM $para$table$para WHERE ' + } + } + if kind == .update || kind == .delete { + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + str += ' AND ' + } + } + } + str += ';' + return str +} + +pub fn orm_select_gen(orm SelectConfig, para string, num bool, qm string, start_pos int, where QueryData) string { + mut str := 'SELECT ' + + if orm.is_count { + str += 'COUNT(*)' + } else { + for i, field in orm.fields { + str += '$para$field$para' + if i < orm.fields.len - 1 { + str += ', ' + } + } + } + + str += ' FROM $para$orm.table$para' + + mut c := start_pos + + if orm.has_where { + str += ' WHERE ' + for i, field in where.fields { + str += '$para$field$para ${where.kinds[i].to_str()} $qm' + if num { + str += '$c' + c++ + } + if i < where.fields.len - 1 { + if where.is_and[i] { + str += ' AND ' + } else { + str += ' OR ' + } + } + } + } + + str += ' ORDER BY ' + if orm.has_order { + str += '$para$orm.order$para ' + str += orm.order_type.to_str() + } else { + str += '$para$orm.primary$para ' + str += orm.order_type.to_str() + } + + if orm.has_limit { + str += ' LIMIT ?' + if num { + str += '$c' + c++ + } + } + + if orm.has_offset { + str += ' OFFSET ?' + if num { + str += '$c' + c++ + } + } + + str += ';' + return str +} + +pub fn orm_table_gen(table string, para string, defaults bool, def_unique_len int, fields []TableField, sql_from_v fn (int) ?string, alternative bool) ?string { + mut str := 'CREATE TABLE IF NOT EXISTS $para$table$para (' + + if alternative { + str = 'IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=$para$table$para and xtype=${para}U$para) CREATE TABLE $para$table$para (' + } + + mut fs := []string{} + mut unique_fields := []string{} + mut unique := map[string][]string{} + mut primary := '' + + for field in fields { + if field.is_arr { + continue + } + mut no_null := false + mut is_unique := false + mut is_skip := false + mut unique_len := 0 + // mut fkey := '' + mut field_name := sql_field_name(field) + for attr in field.attrs { + match attr.name { + 'primary' { + primary = field.name + } + 'unique' { + if attr.arg != '' { + if attr.kind == .string { + unique[attr.arg] << field_name + continue + } else if attr.kind == .number { + unique_len = attr.arg.int() + is_unique = true + continue + } + } + is_unique = true + } + 'nonull' { + no_null = true + } + 'skip' { + is_skip = true + } + /*'fkey' { + if attr.arg != '' { + if attr.kind == .string { + fkey = attr.arg + continue + } + } + }*/ + else {} + } + } + if is_skip { + continue + } + mut stmt := '' + mut ctyp := sql_from_v(sql_field_type(field)) or { + field_name = '${field_name}_id' + sql_from_v(7) ? + } + if ctyp == '' { + return error('Unknown type ($field.typ) for field $field.name in struct $table') + } + stmt = '$para$field_name$para $ctyp' + if defaults && field.default_val != '' { + stmt += ' DEFAULT $field.default_val' + } + if no_null { + stmt += ' NOT NULL' + } + if is_unique { + mut f := 'UNIQUE($para$field_name$para' + if ctyp == 'TEXT' && def_unique_len > 0 { + if unique_len > 0 { + f += '($unique_len)' + } else { + f += '($def_unique_len)' + } + } + f += ')' + unique_fields << f + } + fs << stmt + } + if primary == '' { + return error('A primary key is required for $table') + } + if unique.len > 0 { + for k, v in unique { + mut tmp := []string{} + for f in v { + tmp << '$para$f$para' + } + fs << '/* $k */UNIQUE(${tmp.join(', ')})' + } + } + fs << 'PRIMARY KEY($para$primary$para)' + fs << unique_fields + str += fs.join(', ') + str += ');' + return str +} + +fn sql_field_type(field TableField) int { + mut typ := field.typ + if field.is_time { + return -2 + } + for attr in field.attrs { + if attr.kind == .plain && attr.name == 'sql' && attr.arg != '' { + if attr.arg.to_lower() == 'serial' { + typ = -1 + break + } + typ = orm.type_idx[attr.arg] + break + } + } + return typ +} + +fn sql_field_name(field TableField) string { + mut name := field.name + for attr in field.attrs { + if attr.name == 'sql' && attr.has_arg && attr.kind == .string { + name = attr.arg + break + } + } + return name +} + +// needed for backend functions + +pub fn bool_to_primitive(b bool) Primitive { + return Primitive(b) +} + +pub fn f32_to_primitive(b f32) Primitive { + return Primitive(b) +} + +pub fn f64_to_primitive(b f64) Primitive { + return Primitive(b) +} + +pub fn i8_to_primitive(b i8) Primitive { + return Primitive(b) +} + +pub fn i16_to_primitive(b i16) Primitive { + return Primitive(b) +} + +pub fn int_to_primitive(b int) Primitive { + return Primitive(b) +} + +pub fn i64_to_primitive(b i64) Primitive { + return Primitive(b) +} + +pub fn byte_to_primitive(b byte) Primitive { + return Primitive(b) +} + +pub fn u16_to_primitive(b u16) Primitive { + return Primitive(b) +} + +pub fn u32_to_primitive(b u32) Primitive { + return Primitive(b) +} + +pub fn u64_to_primitive(b u64) Primitive { + return Primitive(b) +} + +pub fn string_to_primitive(b string) Primitive { + return Primitive(b) +} + +pub fn time_to_primitive(b time.Time) Primitive { + return Primitive(b) +} + +pub fn infix_to_primitive(b InfixType) Primitive { + return Primitive(b) +} diff --git a/v_windows/v/vlib/orm/orm_fn_test.v b/v_windows/v/vlib/orm/orm_fn_test.v new file mode 100644 index 0000000..e79b175 --- /dev/null +++ b/v_windows/v/vlib/orm/orm_fn_test.v @@ -0,0 +1,272 @@ +import orm + +fn test_orm_stmt_gen_update() { + query := orm.orm_stmt_gen('Test', "'", .update, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "UPDATE 'Test' SET 'test' = ?0, 'a' = ?1 WHERE 'id' >= ?2 AND 'name' = ?3;" +} + +fn test_orm_stmt_gen_insert() { + query := orm.orm_stmt_gen('Test', "'", .insert, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{}) + assert query == "INSERT INTO 'Test' ('test', 'a') VALUES (?0, ?1);" +} + +fn test_orm_stmt_gen_delete() { + query := orm.orm_stmt_gen('Test', "'", .delete, true, '?', 0, orm.QueryData{ + fields: ['test', 'a'] + data: [] + types: [] + kinds: [] + }, orm.QueryData{ + fields: ['id', 'name'] + data: [] + types: [] + kinds: [.ge, .eq] + }) + assert query == "DELETE FROM 'Test' WHERE 'id' >= ?0 AND 'name' = ?1;" +} + +fn get_select_fields() []string { + return ['id', 'test', 'abc'] +} + +fn test_orm_select_gen() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_limit() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC LIMIT ?0;" +} + +fn test_orm_select_gen_with_where() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY 'id' ASC;" +} + +fn test_orm_select_gen_with_order() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_order: true + order_type: .desc + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY '' DESC;" +} + +fn test_orm_select_gen_with_offset() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_offset: true + }, "'", true, '?', 0, orm.QueryData{}) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' ORDER BY 'id' ASC OFFSET ?0;" +} + +fn test_orm_select_gen_with_all() { + query := orm.orm_select_gen(orm.SelectConfig{ + table: 'test_table' + fields: get_select_fields() + has_limit: true + has_order: true + order_type: .desc + has_offset: true + has_where: true + }, "'", true, '?', 0, orm.QueryData{ + fields: ['abc', 'test'] + kinds: [.eq, .gt] + is_and: [true] + }) + + assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY '' DESC LIMIT ?2 OFFSET ?3;" +} + +fn test_orm_table_gen() { + query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, false) or { panic(err) } + assert query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" + + alt_query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, true) or { panic(err) } + assert alt_query == "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='test_table' and xtype='U') CREATE TABLE 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'));" + + unique_query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + attrs: [ + StructAttribute{ + name: 'unique' + }, + ] + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + }, + ], sql_type_from_v, false) or { panic(err) } + assert unique_query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, PRIMARY KEY('id'), UNIQUE('test'));" + + mult_unique_query := orm.orm_table_gen('test_table', "'", true, 0, [ + orm.TableField{ + name: 'id' + typ: 7 + default_val: '10' + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + arg: 'serial' + kind: .plain + }, + ] + }, + orm.TableField{ + name: 'test' + typ: 18 + attrs: [ + StructAttribute{ + name: 'unique' + has_arg: true + arg: 'test' + kind: .string + }, + ] + }, + orm.TableField{ + name: 'abc' + typ: 8 + default_val: '6754' + attrs: [ + StructAttribute{ + name: 'unique' + has_arg: true + arg: 'test' + kind: .string + }, + ] + }, + ], sql_type_from_v, false) or { panic(err) } + assert mult_unique_query == "CREATE TABLE IF NOT EXISTS 'test_table' ('id' SERIAL DEFAULT 10, 'test' TEXT, 'abc' INT64 DEFAULT 6754, /* test */UNIQUE('test', 'abc'), PRIMARY KEY('id'));" +} + +fn sql_type_from_v(typ int) ?string { + return if typ in orm.nums { + 'INT' + } else if typ in orm.num64 { + 'INT64' + } else if typ in orm.float { + 'DOUBLE' + } else if typ == orm.string { + 'TEXT' + } else if typ == -1 { + 'SERIAL' + } else { + error('Unknown type $typ') + } +} diff --git a/v_windows/v/vlib/orm/orm_test.v b/v_windows/v/vlib/orm/orm_test.v new file mode 100644 index 0000000..c5e4fe1 --- /dev/null +++ b/v_windows/v/vlib/orm/orm_test.v @@ -0,0 +1,316 @@ +// import os +// import pg +// import term +import time +import sqlite + +struct Module { + id int [primary; sql: serial] + name string + nr_downloads int + user User +} + +[table: 'userlist'] +struct User { + id int [primary; sql: serial] + age int + name string [sql: 'username'] + is_customer bool + skipped_string string [skip] +} + +struct Foo { + age int +} + +struct TestTime { + id int [primary; sql: serial] + create time.Time +} + +fn test_orm_sqlite() { + db := sqlite.connect(':memory:') or { panic(err) } + db.exec('drop table if exists User') + sql db { + create table User + } + + name := 'Peter' + + sam := User{ + age: 29 + name: 'Sam' + } + + peter := User{ + age: 31 + name: 'Peter' + } + + k := User{ + age: 30 + name: 'Kate' + is_customer: true + } + + sql db { + insert sam into User + insert peter into User + insert k into User + } + + c := sql db { + select count from User where id != 1 + } + assert c == 2 + + nr_all_users := sql db { + select count from User + } + assert nr_all_users == 3 + println('nr_all_users=$nr_all_users') + // + nr_users1 := sql db { + select count from User where id == 1 + } + assert nr_users1 == 1 + println('nr_users1=$nr_users1') + // + nr_peters := sql db { + select count from User where id == 2 && name == 'Peter' + } + assert nr_peters == 1 + println('nr_peters=$nr_peters') + // + nr_peters2 := sql db { + select count from User where id == 2 && name == name + } + assert nr_peters2 == 1 + nr_peters3 := sql db { + select count from User where name == name + } + assert nr_peters3 == 1 + peters := sql db { + select from User where name == name + } + assert peters.len == 1 + assert peters[0].name == 'Peter' + one_peter := sql db { + select from User where name == name limit 1 + } + assert one_peter.name == 'Peter' + assert one_peter.id == 2 + // + user := sql db { + select from User where id == 1 + } + println(user) + assert user.name == 'Sam' + assert user.id == 1 + assert user.age == 29 + // + users := sql db { + select from User where id > 0 + } + println(users) + assert users.len == 3 + assert users[0].name == 'Sam' + assert users[1].name == 'Peter' + assert users[1].age == 31 + // + users2 := sql db { + select from User where id < 0 + } + println(users2) + assert users2.len == 0 + // + users3 := sql db { + select from User where age == 29 || age == 31 + } + println(users3) + assert users3.len == 2 + assert users3[0].age == 29 + assert users3[1].age == 31 + // + missing_user := sql db { + select from User where id == 8777 + } + println('missing_user:') + println(missing_user) // zero struct + // + new_user := User{ + name: 'New user' + age: 30 + } + sql db { + insert new_user into User + } + + // db.insert(user2) + x := sql db { + select from User where id == 4 + } + println(x) + assert x.age == 30 + assert x.id == 4 + assert x.name == 'New user' + // + kate := sql db { + select from User where id == 3 + } + assert kate.is_customer == true + // + customer := sql db { + select from User where is_customer == true limit 1 + } + assert customer.is_customer == true + assert customer.name == 'Kate' + // + sql db { + update User set age = 31 where name == 'Kate' + } + + kate2 := sql db { + select from User where id == 3 + } + assert kate2.age == 31 + assert kate2.name == 'Kate' + // + sql db { + update User set age = 32, name = 'Kate N' where name == 'Kate' + } + + mut kate3 := sql db { + select from User where id == 3 + } + assert kate3.age == 32 + assert kate3.name == 'Kate N' + // + /* + sql db { + update User set age = age + 1, name = 'Kate N' where name == 'Kate' + } + kate3 = sql db { + select from User where id == 3 + } + println(kate3) + assert kate3.age == 32 + assert kate3.name == 'Kate N' + */ + new_age := 33 + sql db { + update User set age = new_age, name = 'Kate N' where id == 3 + } + + kate3 = sql db { + select from User where id == 3 + } + assert kate3.age == 33 + assert kate3.name == 'Kate N' + // + foo := Foo{34} + sql db { + update User set age = foo.age, name = 'Kate N' where id == 3 + } + + kate3 = sql db { + select from User where id == 3 + } + assert kate3.age == 34 + assert kate3.name == 'Kate N' + // + no_user := sql db { + select from User where id == 30 + } + assert no_user.name == '' // TODO optional + assert no_user.age == 0 + // + two_users := sql db { + select from User limit 2 + } + assert two_users.len == 2 + assert two_users[0].id == 1 + // + y := sql db { + select from User limit 2 offset 1 + } + assert y.len == 2 + assert y[0].id == 2 + // + offset_const := 2 + z := sql db { + select from User limit 2 offset offset_const + } + assert z.len == 2 + assert z[0].id == 3 + oldest := sql db { + select from User order by age desc limit 1 + } + assert oldest.age == 34 + offs := 1 + second_oldest := sql db { + select from User order by age desc limit 1 offset offs + } + assert second_oldest.age == 31 + sql db { + delete from User where age == 34 + } + + updated_oldest := sql db { + select from User order by age desc limit 1 + } + assert updated_oldest.age == 31 + + db.exec('insert into User (name, age) values (NULL, 31)') + null_user := sql db { + select from User where id == 5 + } + assert null_user.name == '' + + age_test := sql db { + select from User where id == 1 + } + + assert age_test.age == 29 + + sql db { + update User set age = age + 1 where id == 1 + } + + mut first := sql db { + select from User where id == 1 + } + + assert first.age == 30 + + sql db { + update User set age = age * 2 where id == 1 + } + + first = sql db { + select from User where id == 1 + } + + assert first.age == 60 + + sql db { + create table TestTime + } + + tnow := time.now() + + time_test := TestTime{ + create: tnow + } + + sql db { + insert time_test into TestTime + } + + data := sql db { + select from TestTime where create == tnow + } + + assert data.len == 1 +} diff --git a/v_windows/v/vlib/os/args.v b/v_windows/v/vlib/os/args.v new file mode 100644 index 0000000..597637c --- /dev/null +++ b/v_windows/v/vlib/os/args.v @@ -0,0 +1,51 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +// args_after returns all os.args, located *after* a specified `cut_word`. +// When `cut_word` is NOT found, os.args is returned unmodified. +pub fn args_after(cut_word string) []string { + if args.len == 0 { + return []string{} + } + mut cargs := []string{} + if cut_word !in args { + cargs = args.clone() + } else { + mut found := false + cargs << args[0] + for a in args[1..] { + if a == cut_word { + found = true + continue + } + if !found { + continue + } + cargs << a + } + } + return cargs +} + +// args_after returns all os.args, located *before* a specified `cut_word`. +// When `cut_word` is NOT found, os.args is returned unmodified. +pub fn args_before(cut_word string) []string { + if args.len == 0 { + return []string{} + } + mut cargs := []string{} + if cut_word !in args { + cargs = args.clone() + } else { + cargs << args[0] + for a in args[1..] { + if a == cut_word { + break + } + cargs << a + } + } + return cargs +} diff --git a/v_windows/v/vlib/os/bare/bare_example_linux.v b/v_windows/v/vlib/os/bare/bare_example_linux.v new file mode 100644 index 0000000..0aa92dd --- /dev/null +++ b/v_windows/v/vlib/os/bare/bare_example_linux.v @@ -0,0 +1,8 @@ +fn main() { + sys_write(1, 'hello\n'.str, 6) + s := 'test string\n' + sys_write(1, s.str, u64(s.len)) + a := s[0] + println('Hello freestanding!') + println(a) +} diff --git a/v_windows/v/vlib/os/cmdline/cmdline.v b/v_windows/v/vlib/os/cmdline/cmdline.v new file mode 100644 index 0000000..96bcb27 --- /dev/null +++ b/v_windows/v/vlib/os/cmdline/cmdline.v @@ -0,0 +1,82 @@ +module cmdline + +// Fetch multiple option by param, e.g. +// args: ['v', '-d', 'aa', '-d', 'bb', '-d', 'cc'] +// param: '-d' +// ret: ['aa', 'bb', 'cc'] +pub fn options(args []string, param string) []string { + mut flags := []string{} + for i, v in args { + if v == param { + if i + 1 < args.len { + flags << args[i + 1] + } + } + } + return flags +} + +// Fetch option by param, e.g. +// args: ['v', '-d', 'aa'] +// param: '-d' +// def: '' +// ret: 'aa' +pub fn option(args []string, param string, def string) string { + mut found := false + for arg in args { + if found { + return arg + } else if param == arg { + found = true + } + } + return def +} + +// Fetch all options before what params, e.g. +// args: ['-stat', 'test', 'aaa.v'] +// what: ['test'] +// ret: ['-stat'] +pub fn options_before(args []string, what []string) []string { + mut args_before := []string{} + for a in args { + if a in what { + break + } + args_before << a + } + return args_before +} + +// Fetch all options after what params, e.g. +// args: ['-stat', 'test', 'aaa.v'] +// what: ['test'] +// ret: ['aaa.v'] +pub fn options_after(args []string, what []string) []string { + mut found := false + mut args_after := []string{} + for a in args { + if a in what { + found = true + continue + } + if found { + args_after << a + } + } + return args_after +} + +// Fetch all options not start with '-', e.g. +// args: ['-d', 'aa', '--help', 'bb'] +// ret: ['aa', 'bb'] +pub fn only_non_options(args []string) []string { + return args.filter(!it.starts_with('-')) +} + +// Fetch all options start with '-', e.g. +// args: ['-d', 'aa', '--help', 'bb'] +// ret: ['-d', '--help'] +pub fn only_options(args []string) []string { + return args.filter(it.starts_with('-')) +} diff --git a/v_windows/v/vlib/os/cmdline/cmdline_test.v b/v_windows/v/vlib/os/cmdline/cmdline_test.v new file mode 100644 index 0000000..50c99e2 --- /dev/null +++ b/v_windows/v/vlib/os/cmdline/cmdline_test.v @@ -0,0 +1,37 @@ +import os.cmdline + +fn test_options() { + args := ['v', '-d', 'aa', '-d', 'bb', '-d', 'cc'] + ret := cmdline.options(args, '-d') + assert ret == ['aa', 'bb', 'cc'] +} + +fn test_option() { + args := ['v', '-d', 'aa'] + ret := cmdline.option(args, '-d', '') + assert ret == 'aa' +} + +fn test_options_before() { + args := ['-stat', 'test', 'aaa.v'] + ret := cmdline.options_before(args, ['test']) + assert ret == ['-stat'] +} + +fn test_options_after() { + args := ['-stat', 'test', 'aaa.v'] + ret := cmdline.options_after(args, ['test']) + assert ret == ['aaa.v'] +} + +fn test_only_non_options() { + args := ['-d', 'aa', '--help', 'bb'] + ret := cmdline.only_non_options(args) + assert ret == ['aa', 'bb'] +} + +fn test_only_options() { + args := ['-d', 'aa', '--help', 'bb'] + ret := cmdline.only_options(args) + assert ret == ['-d', '--help'] +} diff --git a/v_windows/v/vlib/os/const.v b/v_windows/v/vlib/os/const.v new file mode 100644 index 0000000..bcf59cf --- /dev/null +++ b/v_windows/v/vlib/os/const.v @@ -0,0 +1 @@ +module os diff --git a/v_windows/v/vlib/os/const_nix.c.v b/v_windows/v/vlib/os/const_nix.c.v new file mode 100644 index 0000000..275df70 --- /dev/null +++ b/v_windows/v/vlib/os/const_nix.c.v @@ -0,0 +1,16 @@ +module os + +// File modes +const ( + o_rdonly = 0o00000000 // open the file read-only. + o_wronly = 0o00000001 // open the file write-only. + o_rdwr = 0o00000002 // open the file read-write. + o_binary = 0o00000000 // input and output is not translated; the default on unix + o_create = 0o00000100 // create a new file if none exists. + o_excl = 0o00000200 // used with o_create, file must not exist. + o_noctty = 0o00000400 // if file is terminal, don't make it the controller terminal + o_trunc = 0o00001000 // truncate regular writable file when opened. + o_append = 0o00002000 // append data to the file when writing. + o_nonblock = 0o00004000 // prevents blocking when opening files + o_sync = 0o04010000 // open for synchronous I/O. +) diff --git a/v_windows/v/vlib/os/const_windows.c.v b/v_windows/v/vlib/os/const_windows.c.v new file mode 100644 index 0000000..4b87c8b --- /dev/null +++ b/v_windows/v/vlib/os/const_windows.c.v @@ -0,0 +1,161 @@ +module os + +// Ref - winnt.h +const ( + success = 0x0000 // ERROR_SUCCESS + error_insufficient_buffer = 0x0082 +) + +const ( + handle_generic_read = 0x80000000 + handle_open_existing = 0x00000003 +) + +const ( + file_share_read = 0x01 + file_share_write = 0x02 + file_share_delete = 0x04 +) + +const ( + file_notify_change_file_name = 0x01 + file_notify_change_dir_name = 0x02 + file_notify_change_attributes = 0x04 + file_notify_change_size = 0x08 + file_notify_change_last_write = 0x10 + file_notify_change_last_access = 0x20 + file_notify_change_creation = 0x40 + file_notify_change_security = 0x80 +) + +const ( + file_action_added = 0x01 + file_action_removed = 0x02 + file_action_modified = 0x03 + file_action_renamed_old_name = 0x04 + file_action_renamed_new_name = 0x05 +) + +const ( + file_attr_readonly = 0x00000001 + file_attr_hidden = 0x00000002 + file_attr_system = 0x00000004 + file_attr_directory = 0x00000010 + file_attr_archive = 0x00000020 + file_attr_device = 0x00000040 + file_attr_normal = 0x00000080 + file_attr_temporary = 0x00000100 + file_attr_sparse_file = 0x00000200 + file_attr_reparse_point = 0x00000400 + file_attr_compressed = 0x00000800 + file_attr_offline = 0x00001000 + file_attr_not_content_indexed = 0x00002000 + file_attr_encrypted = 0x00004000 + file_attr_integrity_stream = 0x00008000 + file_attr_virtual = 0x00010000 + file_attr_no_scrub_data = 0x00020000 + // file_attr_recall_on_open = u32(0x...) + // file_attr_recall_on_data_access = u32(0x...) +) + +const ( + file_type_unknown = 0x00 + file_type_disk = 0x01 + file_type_char = 0x02 + file_type_pipe = 0x03 +) + +const ( + file_invalid_file_id = (-1) +) + +const ( + invalid_handle_value = voidptr(-1) +) + +// https://docs.microsoft.com/en-us/windows/console/setconsolemode +const ( + // Input Buffer + enable_echo_input = 0x0004 + enable_extended_flags = 0x0080 + enable_insert_mode = 0x0020 + enable_line_input = 0x0002 + enable_mouse_input = 0x0010 + enable_processed_input = 0x0001 + enable_quick_edit_mode = 0x0040 + enable_window_input = 0x0008 + enable_virtual_terminal_input = 0x0200 + // Output Screen Buffer + enable_processed_output = 0x01 + enable_wrap_at_eol_output = 0x02 + enable_virtual_terminal_processing = 0x04 + disable_newline_auto_return = 0x08 + enable_lvb_grid_worldwide = 0x10 +) + +// File modes +const ( + o_rdonly = 0x0000 // open the file read-only. + o_wronly = 0x0001 // open the file write-only. + o_rdwr = 0x0002 // open the file read-write. + o_append = 0x0008 // append data to the file when writing. + o_create = 0x0100 // create a new file if none exists. + o_binary = 0x8000 // input and output is not translated. + o_trunc = 0x0200 // truncate regular writable file when opened. + o_excl = 0x0400 // used with o_create, file must not exist. + o_sync = 0x0000 // open for synchronous I/O (ignored on Windows) + o_noctty = 0x0000 // make file non-controlling tty (ignored on Windows) + o_nonblock = 0x0000 // don't block on opening file (ignored on Windows) +) + +const ( + status_access_violation = 0xC0000005 + status_in_page_error = 0xC0000006 + status_invalid_handle = 0xC0000008 + status_invalid_parameter = 0xC000000D + status_no_memory = 0xC0000017 + status_illegal_instruction = 0xC000001D + status_noncontinuable_exception = 0xC0000025 + status_invalid_disposition = 0xC0000026 + status_array_bounds_exceeded = 0xC000008C + status_float_denormal_operand = 0xC000008D + status_float_divide_by_zero = 0xC000008E + status_float_inexact_result = 0xC000008F + status_float_invalid_operation = 0xC0000090 + status_float_overflow = 0xC0000091 + status_float_stack_check = 0xC0000092 + status_float_underflow = 0xC0000093 + status_integer_divide_by_zero = 0xC0000094 + status_integer_overflow = 0xC0000095 + status_privileged_instruction = 0xC0000096 + status_stack_overflow = 0xC00000FD + status_dll_not_found = 0xC0000135 + status_ordinal_not_found = 0xC0000138 + status_entrypoint_not_found = 0xC0000139 + status_control_c_exit = 0xC000013A + status_dll_init_failed = 0xC0000142 + status_float_multiple_faults = 0xC00002B4 + status_float_multiple_traps = 0xC00002B5 + status_reg_nat_consumption = 0xC00002C9 + status_heap_corruption = 0xC0000374 + status_stack_buffer_overrun = 0xC0000409 + status_invalid_cruntime_parameter = 0xC0000417 + status_assertion_failure = 0xC0000420 +) + +// Windows Registry Constants +pub const ( + hkey_local_machine = voidptr(0x80000002) + hkey_current_user = voidptr(0x80000001) + key_query_value = 0x0001 + key_set_value = 0x0002 + key_enumerate_sub_keys = 0x0008 + key_wow64_32key = 0x0200 +) + +// Windows Messages +pub const ( + hwnd_broadcast = voidptr(0xFFFF) + wm_settingchange = 0x001A + smto_abortifhung = 0x0002 +) diff --git a/v_windows/v/vlib/os/environment.c.v b/v_windows/v/vlib/os/environment.c.v new file mode 100644 index 0000000..1b65a27 --- /dev/null +++ b/v_windows/v/vlib/os/environment.c.v @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +fn C.getenv(&char) &char + +// C.GetEnvironmentStringsW & C.FreeEnvironmentStringsW are defined only on windows +fn C.GetEnvironmentStringsW() &u16 + +fn C.FreeEnvironmentStringsW(&u16) int + +// `getenv` returns the value of the environment variable named by the key. +pub fn getenv(key string) string { + unsafe { + $if windows { + s := C._wgetenv(key.to_wide()) + if s == 0 { + return '' + } + return string_from_wide(s) + } $else { + s := C.getenv(&char(key.str)) + if s == voidptr(0) { + return '' + } + // NB: C.getenv *requires* that the result be copied. + return cstring_to_vstring(s) + } + } +} + +// os.setenv sets the value of an environment variable with `name` to `value`. +pub fn setenv(name string, value string, overwrite bool) int { + $if windows { + format := '$name=$value' + if overwrite { + unsafe { + return C._putenv(&char(format.str)) + } + } else { + if getenv(name).len == 0 { + unsafe { + return C._putenv(&char(format.str)) + } + } + } + return -1 + } $else { + unsafe { + return C.setenv(&char(name.str), &char(value.str), overwrite) + } + } +} + +// os.unsetenv clears an environment variable with `name`. +pub fn unsetenv(name string) int { + $if windows { + format := '$name=' + return C._putenv(&char(format.str)) + } $else { + return C.unsetenv(&char(name.str)) + } +} + +// See: https://linux.die.net/man/5/environ for unix platforms. +// See: https://docs.microsoft.com/bg-bg/windows/win32/api/processenv/nf-processenv-getenvironmentstrings +// os.environ returns a map of all the current environment variables + +fn unix_environ() &&char { + // TODO: remove this helper function, when `&&char(C.environ)` works properly + return voidptr(C.environ) +} + +pub fn environ() map[string]string { + mut res := map[string]string{} + $if windows { + mut estrings := C.GetEnvironmentStringsW() + mut eline := '' + for c := estrings; *c != 0; { + eline = unsafe { string_from_wide(c) } + eq_index := eline.index_byte(`=`) + if eq_index > 0 { + res[eline[0..eq_index]] = eline[eq_index + 1..] + } + unsafe { + c = c + eline.len + 1 + } + } + C.FreeEnvironmentStringsW(estrings) + } $else { + start := unix_environ() + mut i := 0 + for { + x := unsafe { start[i] } + if x == 0 { + break + } + eline := unsafe { cstring_to_vstring(x) } + eq_index := eline.index_byte(`=`) + if eq_index > 0 { + res[eline[0..eq_index]] = eline[eq_index + 1..] + } + i++ + } + } + return res +} diff --git a/v_windows/v/vlib/os/environment.js.v b/v_windows/v/vlib/os/environment.js.v new file mode 100644 index 0000000..ac760a5 --- /dev/null +++ b/v_windows/v/vlib/os/environment.js.v @@ -0,0 +1,37 @@ +module os + +$if js_node { + #global.$ENV = $process.env +} $else { + #global.$ENV = {} +} + +// setenv sets the value of an environment variable with `name` to `value`. +pub fn setenv(key string, val string, overwrite bool) { + #if ($ENV[key] && !(overwrite.valueOf())) return; + #$ENV[key] = val + ''; +} + +// `getenv` returns the value of the environment variable named by the key. +pub fn getenv(key string) string { + mut res := '' + #if ($ENV[key]) res = new builtin.string($ENV[key]) + + return res +} + +// unsetenv clears an environment variable with `name`. +pub fn unsetenv(name string) int { + #$ENV[name] = "" + + return 1 +} + +pub fn environ() map[string]string { + mut res := map[string]string{} + #for (const key in $ENV) { + #res.map.set(key,$ENV[key]) + #} + + return res +} diff --git a/v_windows/v/vlib/os/environment_test.v b/v_windows/v/vlib/os/environment_test.v new file mode 100644 index 0000000..5324371 --- /dev/null +++ b/v_windows/v/vlib/os/environment_test.v @@ -0,0 +1,49 @@ +import os +import time + +fn test_getenv() { + // VEXE is set by the V builtin test runner + assert os.getenv('VEXE').len > 0 + assert os.getenv('PATH').len > 0 +} + +fn test_setenv() { + os.setenv('foo', 'bar', true) + assert os.getenv('foo') == 'bar' + // `setenv` should not set if `overwrite` is false + os.setenv('foo', 'bar2', false) + assert os.getenv('foo') == 'bar' + // `setenv` should overwrite if `overwrite` is true + os.setenv('foo', 'bar2', true) + assert os.getenv('foo') == 'bar2' +} + +fn test_unsetenv() { + os.setenv('foo', 'bar', true) + os.unsetenv('foo') + assert os.getenv('foo') == '' +} + +fn test_environ() { + os.setenv('myvar1', 'bar1', true) + os.setenv('myvar2', 'bar2', true) + assert os.getenv('myvar1') == 'bar1' + assert os.getenv('myvar2') == 'bar2' + assert os.getenv('myvar_not_defined') == '' + all := os.environ() + assert all['myvar1'] == 'bar1' + assert all['myvar2'] == 'bar2' + assert all['myvar_not_defined'] == '' +} + +fn test_setenv_var_not_exists() { + key := time.new_time(time.now()).unix + os.setenv('foo$key', 'bar', false) + assert os.getenv('foo$key') == 'bar' +} + +fn test_getenv_empty_var() { + key := time.new_time(time.now()).unix + os.setenv('empty$key', '""', false) + assert os.getenv('empty$key') == '""' +} diff --git a/v_windows/v/vlib/os/fd.c.v b/v_windows/v/vlib/os/fd.c.v new file mode 100644 index 0000000..de69e38 --- /dev/null +++ b/v_windows/v/vlib/os/fd.c.v @@ -0,0 +1,61 @@ +module os + +// file descriptor based operations: + +// close filedescriptor +pub fn fd_close(fd int) int { + if fd == -1 { + return 0 + } + return C.close(fd) +} + +pub fn fd_write(fd int, s string) { + if fd == -1 { + return + } + mut sp := s.str + mut remaining := s.len + for remaining > 0 { + written := C.write(fd, sp, remaining) + if written < 0 { + return + } + remaining = remaining - written + sp = unsafe { sp + written } + } +} + +// read from filedescriptor, block until data +pub fn fd_slurp(fd int) []string { + mut res := []string{} + if fd == -1 { + return res + } + for { + s, b := fd_read(fd, 4096) + if b <= 0 { + break + } + res << s + } + return res +} + +// read from filedescriptor, don't block +// return [bytestring,nrbytes] +pub fn fd_read(fd int, maxbytes int) (string, int) { + if fd == -1 { + return '', 0 + } + unsafe { + mut buf := malloc_noscan(maxbytes + 1) + nbytes := C.read(fd, buf, maxbytes) + if nbytes < 0 { + free(buf) + return '', nbytes + } + buf[nbytes] = 0 + return tos(buf, nbytes), nbytes + } +} diff --git a/v_windows/v/vlib/os/file.c.v b/v_windows/v/vlib/os/file.c.v new file mode 100644 index 0000000..3de2279 --- /dev/null +++ b/v_windows/v/vlib/os/file.c.v @@ -0,0 +1,787 @@ +module os + +pub struct File { + cfile voidptr // Using void* instead of FILE* +pub: + fd int +pub mut: + is_opened bool +} + +struct FileInfo { + name string + size int +} + +fn C.fseeko(&C.FILE, u64, int) int + +fn C._fseeki64(&C.FILE, u64, int) int + +fn C.getc(&C.FILE) int + +// open_file can be used to open or create a file with custom flags and permissions and returns a `File` object. +pub fn open_file(path string, mode string, options ...int) ?File { + mut flags := 0 + for m in mode { + match m { + `w` { flags |= o_create | o_trunc } + `a` { flags |= o_create | o_append } + `r` { flags |= o_rdonly } + `b` { flags |= o_binary } + `s` { flags |= o_sync } + `n` { flags |= o_nonblock } + `c` { flags |= o_noctty } + `+` { flags |= o_rdwr } + else {} + } + } + if mode == 'r+' { + flags = o_rdwr + } + if mode == 'w' { + flags = o_wronly | o_create | o_trunc + } + if mode == 'a' { + flags = o_wronly | o_create | o_append + } + mut permission := 0o666 + if options.len > 0 { + permission = options[0] + } + $if windows { + if permission < 0o600 { + permission = 0x0100 + } else { + permission = 0x0100 | 0x0080 + } + } + mut p := path + $if windows { + p = path.replace('/', '\\') + } + fd := C.open(&char(p.str), flags, permission) + if fd == -1 { + return error(posix_get_error_msg(C.errno)) + } + cfile := C.fdopen(fd, &char(mode.str)) + if isnil(cfile) { + return error('Failed to open or create file "$path"') + } + return File{ + cfile: cfile + fd: fd + is_opened: true + } +} + +// open tries to open a file for reading and returns back a read-only `File` object. +pub fn open(path string) ?File { + /* + $if linux { + $if !android { + fd := C.syscall(sys_open, path.str, 511) + if fd == -1 { + return error('failed to open file "$path"') + } + return File{ + fd: fd + is_opened: true + } + } + } + */ + cfile := vfopen(path, 'rb') ? + fd := fileno(cfile) + return File{ + cfile: cfile + fd: fd + is_opened: true + } +} + +// create creates or opens a file at a specified location and returns a write-only `File` object. +pub fn create(path string) ?File { + /* + // NB: android/termux/bionic is also a kind of linux, + // but linux syscalls there sometimes fail, + // while the libc version should work. + $if linux { + $if !android { + //$if macos { + // fd = C.syscall(398, path.str, 0x601, 0x1b6) + //} + //$if linux { + fd = C.syscall(sys_creat, path.str, 511) + //} + if fd == -1 { + return error('failed to create file "$path"') + } + file = File{ + fd: fd + is_opened: true + } + return file + } + } + */ + cfile := vfopen(path, 'wb') ? + fd := fileno(cfile) + return File{ + cfile: cfile + fd: fd + is_opened: true + } +} + +// stdin - return an os.File for stdin, so that you can use .get_line on it too. +pub fn stdin() File { + return File{ + fd: 0 + cfile: C.stdin + is_opened: true + } +} + +// stdout - return an os.File for stdout +pub fn stdout() File { + return File{ + fd: 1 + cfile: C.stdout + is_opened: true + } +} + +// stderr - return an os.File for stderr +pub fn stderr() File { + return File{ + fd: 2 + cfile: C.stderr + is_opened: true + } +} + +// read implements the Reader interface. +pub fn (f &File) read(mut buf []byte) ?int { + if buf.len == 0 { + return 0 + } + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + return nbytes +} + +// **************************** Write ops *************************** +// write implements the Writer interface. +// It returns how many bytes were actually written. +pub fn (mut f File) write(buf []byte) ?int { + if !f.is_opened { + return error_file_not_opened() + } + /* + $if linux { + $if !android { + res := C.syscall(sys_write, f.fd, s.str, s.len) + return res + } + } + */ + written := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) + if written == 0 && buf.len != 0 { + return error('0 bytes written') + } + return written +} + +// writeln writes the string `s` into the file, and appends a \n character. +// It returns how many bytes were written, including the \n character. +pub fn (mut f File) writeln(s string) ?int { + if !f.is_opened { + return error_file_not_opened() + } + /* + $if linux { + $if !android { + snl := s + '\n' + C.syscall(sys_write, f.fd, snl.str, snl.len) + return + } + } + */ + // TODO perf + written := int(C.fwrite(s.str, 1, s.len, f.cfile)) + if written == 0 && s.len != 0 { + return error('0 bytes written') + } + x := C.fputs(c'\n', f.cfile) + if x < 0 { + return error('could not add newline') + } + return (written + 1) +} + +// write_string writes the string `s` into the file +// It returns how many bytes were actually written. +pub fn (mut f File) write_string(s string) ?int { + unsafe { f.write_full_buffer(s.str, size_t(s.len)) ? } + return s.len +} + +// write_to implements the RandomWriter interface. +// It returns how many bytes were actually written. +// It resets the seek position to the end of the file. +pub fn (mut f File) write_to(pos u64, buf []byte) ?int { + if !f.is_opened { + return error_file_not_opened() + } + $if x64 { + $if windows { + C._fseeki64(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) + if res == 0 && buf.len != 0 { + return error('0 bytes written') + } + C._fseeki64(f.cfile, 0, C.SEEK_END) + return res + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) + if res == 0 && buf.len != 0 { + return error('0 bytes written') + } + C.fseeko(f.cfile, 0, C.SEEK_END) + return res + } + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) + if res == 0 && buf.len != 0 { + return error('0 bytes written') + } + C.fseek(f.cfile, 0, C.SEEK_END) + return res + } + return error('Could not write to file') +} + +// write_ptr writes `size` bytes to the file, starting from the address in `data`. +// NB: write_ptr is unsafe and should be used carefully, since if you pass invalid +// pointers to it, it will cause your programs to segfault. +[unsafe] +pub fn (mut f File) write_ptr(data voidptr, size int) int { + return int(C.fwrite(data, 1, size, f.cfile)) +} + +// write_full_buffer writes a whole buffer of data to the file, starting from the +// address in `buffer`, no matter how many tries/partial writes it would take. +[unsafe] +pub fn (mut f File) write_full_buffer(buffer voidptr, buffer_len size_t) ? { + if buffer_len <= size_t(0) { + return + } + if !f.is_opened { + return error_file_not_opened() + } + mut ptr := &byte(buffer) + mut remaining_bytes := i64(buffer_len) + for remaining_bytes > 0 { + unsafe { + x := i64(C.fwrite(ptr, 1, remaining_bytes, f.cfile)) + ptr += x + remaining_bytes -= x + if x <= 0 { + return error('C.fwrite returned 0') + } + } + } +} + +// write_ptr_at writes `size` bytes to the file, starting from the address in `data`, +// at byte offset `pos`, counting from the start of the file (pos 0). +// NB: write_ptr_at is unsafe and should be used carefully, since if you pass invalid +// pointers to it, it will cause your programs to segfault. +[unsafe] +pub fn (mut f File) write_ptr_at(data voidptr, size int, pos u64) int { + $if x64 { + $if windows { + C._fseeki64(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(data, 1, size, f.cfile)) + C._fseeki64(f.cfile, 0, C.SEEK_END) + return res + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(data, 1, size, f.cfile)) + C.fseeko(f.cfile, 0, C.SEEK_END) + return res + } + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + res := int(C.fwrite(data, 1, size, f.cfile)) + C.fseek(f.cfile, 0, C.SEEK_END) + return res + } + return 0 +} + +// **************************** Read ops *************************** + +// fread wraps C.fread and handles error and end-of-file detection. +fn fread(ptr voidptr, item_size int, items int, stream &C.FILE) ?int { + nbytes := int(C.fread(ptr, item_size, items, stream)) + // If no bytes were read, check for errors and end-of-file. + if nbytes <= 0 { + // If fread encountered end-of-file return the none error. Note that fread + // may read data and encounter the end-of-file, but we shouldn't return none + // in that case which is why we only check for end-of-file if no data was + // read. The caller will get none on their next call because there will be + // no data available and the end-of-file will be encountered again. + if C.feof(stream) != 0 { + return none + } + // If fread encountered an error, return it. Note that fread and ferror do + // not tell us what the error was, so we can't return anything more specific + // than there was an error. This is because fread and ferror do not set + // errno. + if C.ferror(stream) != 0 { + return error('file read error') + } + } + return nbytes +} + +// read_bytes reads bytes from the beginning of the file. +// Utility method, same as .read_bytes_at(size, 0). +pub fn (f &File) read_bytes(size int) []byte { + return f.read_bytes_at(size, 0) +} + +// read_bytes_at reads `size` bytes at the given position in the file. +pub fn (f &File) read_bytes_at(size int, pos u64) []byte { + mut arr := []byte{len: size} + nreadbytes := f.read_bytes_into(pos, mut arr) or { + // return err + return [] + } + return arr[0..nreadbytes] +} + +// read_bytes_into_newline reads from the beginning of the file into the provided buffer. +// Each consecutive call on the same file continues reading where it previously ended. +// A read call is either stopped, if the buffer is full, a newline was read or EOF. +pub fn (f &File) read_bytes_into_newline(mut buf []byte) ?int { + if buf.len == 0 { + panic(@FN + ': `buf.len` == 0') + } + newline := 10 + mut c := 0 + mut buf_ptr := 0 + mut nbytes := 0 + + stream := &C.FILE(f.cfile) + for (buf_ptr < buf.len) { + c = C.getc(stream) + match c { + C.EOF { + if C.feof(stream) != 0 { + return nbytes + } + if C.ferror(stream) != 0 { + return error('file read error') + } + } + newline { + buf[buf_ptr] = byte(c) + nbytes++ + return nbytes + } + else { + buf[buf_ptr] = byte(c) + buf_ptr++ + nbytes++ + } + } + } + return nbytes +} + +// read_bytes_into fills `buf` with bytes at the given position in the file. +// `buf` *must* have length greater than zero. +// Returns the number of read bytes, or an error. +pub fn (f &File) read_bytes_into(pos u64, mut buf []byte) ?int { + if buf.len == 0 { + panic(@FN + ': `buf.len` == 0') + } + $if x64 { + $if windows { + // Note: fseek errors if pos == os.file_size, which we accept + C._fseeki64(f.cfile, pos, C.SEEK_SET) + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + $if debug { + C._fseeki64(f.cfile, 0, C.SEEK_SET) + } + return nbytes + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + $if debug { + C.fseeko(f.cfile, 0, C.SEEK_SET) + } + return nbytes + } + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + $if debug { + C.fseek(f.cfile, 0, C.SEEK_SET) + } + return nbytes + } + return error('Could not read file') +} + +// read_from implements the RandomReader interface. +pub fn (f &File) read_from(pos u64, mut buf []byte) ?int { + if buf.len == 0 { + return 0 + } + $if x64 { + $if windows { + C._fseeki64(f.cfile, pos, C.SEEK_SET) + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + } + + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + return nbytes + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + nbytes := fread(buf.data, 1, buf.len, f.cfile) ? + return nbytes + } + return error('Could not read file') +} + +// read_into_ptr reads at most max_size bytes from the file and writes it into ptr. +// Returns the amount of bytes read or an error. +pub fn (f &File) read_into_ptr(ptr &byte, max_size int) ?int { + return fread(ptr, 1, max_size, f.cfile) +} + +// **************************** Utility ops *********************** +// flush writes any buffered unwritten data left in the file stream. +pub fn (mut f File) flush() { + if !f.is_opened { + return + } + C.fflush(f.cfile) +} + +pub struct ErrFileNotOpened { + msg string = 'os: file not opened' + code int +} + +pub struct ErrSizeOfTypeIs0 { + msg string = 'os: size of type is 0' + code int +} + +fn error_file_not_opened() IError { + return IError(&ErrFileNotOpened{}) +} + +fn error_size_of_type_0() IError { + return IError(&ErrSizeOfTypeIs0{}) +} + +// read_struct reads a single struct of type `T` +pub fn (mut f File) read_struct(mut t T) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(*t)) + if tsize == 0 { + return error_size_of_type_0() + } + nbytes := fread(t, 1, tsize, f.cfile) ? + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } +} + +// read_struct_at reads a single struct of type `T` at position specified in file +pub fn (mut f File) read_struct_at(mut t T, pos u64) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(*t)) + if tsize == 0 { + return error_size_of_type_0() + } + mut nbytes := 0 + $if x64 { + $if windows { + C._fseeki64(f.cfile, pos, C.SEEK_SET) + nbytes = fread(t, 1, tsize, f.cfile) ? + C._fseeki64(f.cfile, 0, C.SEEK_END) + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + nbytes = fread(t, 1, tsize, f.cfile) ? + C.fseeko(f.cfile, 0, C.SEEK_END) + } + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + nbytes = fread(t, 1, tsize, f.cfile) ? + C.fseek(f.cfile, 0, C.SEEK_END) + } + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } +} + +// read_raw reads and returns a single instance of type `T` +pub fn (mut f File) read_raw() ?T { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + mut t := T{} + nbytes := fread(&t, 1, tsize, f.cfile) ? + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } + return t +} + +// read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos` +pub fn (mut f File) read_raw_at(pos u64) ?T { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + mut nbytes := 0 + mut t := T{} + $if x64 { + $if windows { + if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = fread(&t, 1, tsize, f.cfile) ? + if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } $else { + if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = fread(&t, 1, tsize, f.cfile) ? + if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } + } + $if x32 { + if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = fread(&t, 1, tsize, f.cfile) ? + if C.fseek(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } + + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } + return t +} + +// write_struct writes a single struct of type `T` +pub fn (mut f File) write_struct(t &T) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + C.errno = 0 + nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} + +// write_struct_at writes a single struct of type `T` at position specified in file +pub fn (mut f File) write_struct_at(t &T, pos u64) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + C.errno = 0 + mut nbytes := 0 + $if x64 { + $if windows { + C._fseeki64(f.cfile, pos, C.SEEK_SET) + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + C._fseeki64(f.cfile, 0, C.SEEK_END) + } $else { + C.fseeko(f.cfile, pos, C.SEEK_SET) + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + C.fseeko(f.cfile, 0, C.SEEK_END) + } + } + $if x32 { + C.fseek(f.cfile, pos, C.SEEK_SET) + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + C.fseek(f.cfile, 0, C.SEEK_END) + } + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} + +// TODO `write_raw[_at]` implementations are copy-pasted from `write_struct[_at]` + +// write_raw writes a single instance of type `T` +pub fn (mut f File) write_raw(t &T) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + C.errno = 0 + nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} + +// write_raw_at writes a single instance of type `T` starting at file byte offset `pos` +pub fn (mut f File) write_raw_at(t &T, pos u64) ? { + if !f.is_opened { + return error_file_not_opened() + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error_size_of_type_0() + } + mut nbytes := 0 + + $if x64 { + $if windows { + if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } $else { + if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } + } + $if x32 { + if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if C.fseek(f.cfile, 0, C.SEEK_END) != 0 { + return error(posix_get_error_msg(C.errno)) + } + } + + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} + +pub enum SeekMode { + start + current + end +} + +// seek moves the file cursor (if any) associated with a file +// to a new location, offset `pos` bytes from the origin. The origin +// is dependent on the `mode` and can be: +// .start -> the origin is the start of the file +// .current -> the current position/cursor in the file +// .end -> the end of the file +// If the file is not seek-able, or an error occures, the error will +// be returned to the caller. +// A successful call to the fseek() function clears the end-of-file +// indicator for the file. +pub fn (mut f File) seek(pos i64, mode SeekMode) ? { + if !f.is_opened { + return error_file_not_opened() + } + whence := int(mode) + mut res := 0 + $if x64 { + $if windows { + res = C._fseeki64(f.cfile, pos, whence) + } $else { + res = C.fseeko(f.cfile, pos, whence) + } + } + $if x32 { + res = C.fseek(f.cfile, pos, whence) + } + if res == -1 { + return error(posix_get_error_msg(C.errno)) + } +} + +// tell will return the current offset of the file cursor measured from +// the start of the file, in bytes. It is complementary to seek, i.e. +// you can use the return value as the `pos` parameter to .seek( pos, .start ), +// so that your next read will happen from the same place. +pub fn (f &File) tell() ?i64 { + if !f.is_opened { + return error_file_not_opened() + } + pos := C.ftell(f.cfile) + if pos == -1 { + return error(posix_get_error_msg(C.errno)) + } + return pos +} diff --git a/v_windows/v/vlib/os/file.js.v b/v_windows/v/vlib/os/file.js.v new file mode 100644 index 0000000..abaeeab --- /dev/null +++ b/v_windows/v/vlib/os/file.js.v @@ -0,0 +1,136 @@ +module os + +pub struct File { +pub: + fd int +pub mut: + is_opened bool +} + +#const $buffer = require('buffer'); + +// todo(playX): __as_cast is broken here +/* +pub struct ErrFileNotOpened { + msg string = 'os: file not opened' + code int +} +pub struct ErrSizeOfTypeIs0 { + msg string = 'os: size of type is 0' + code int +} +fn error_file_not_opened() IError { + return IError(&ErrFileNotOpened{}) +} +fn error_size_of_type_0() IError { + return IError(&ErrSizeOfTypeIs0{}) +} +*/ +pub fn open_file(path string, mode string, options ...int) ?File { + mut res := File{} + $if js_node { + #if (!options) { options = new array([]); } + #let permissions = 0o666 + #if (options.arr.length > 0) { permissions = options.arr[0]; } + #try { + #res.fd = new int($fs.openSync(''+path,''+mode,permissions)) + #} catch (e) { + #return builtin.error('' + e); + #} + + res.is_opened = true + } $else { + error('cannot open file on non NodeJS runtime') + } + return res +} + +// open tries to open a file for reading and returns back a read-only `File` object. +pub fn open(path string) ?File { + f := open_file(path, 'r') ? + return f +} + +pub fn create(path string) ?File { + f := open_file(path, 'w') ? + return f +} + +pub fn stdin() File { + return File{ + fd: 0 + is_opened: true + } +} + +pub fn stdout() File { + return File{ + fd: 1 + is_opened: true + } +} + +pub fn stderr() File { + return File{ + fd: 2 + is_opened: true + } +} + +pub fn (f &File) read(mut buf []byte) ?int { + if buf.len == 0 { + return 0 + } + mut nbytes := 0 + #try { + #let buffer = $fs.readFileSync(f.fd.valueOf()); + # + #for (const val of buffer.values()) { buf.arr[nbytes++] = val; } + #} + #catch (e) { return builtin.error('' + e); } + + return nbytes +} + +pub fn (mut f File) write(buf []byte) ?int { + if !f.is_opened { + return error('file is not opened') + } + mut nbytes := 0 + #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf())) + #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),0); } catch (e) { return builtin.error('' + e); } + + return nbytes +} + +// writeln writes the string `s` into the file, and appends a \n character. +// It returns how many bytes were written, including the \n character. +pub fn (mut f File) writeln(s string) ?int { + mut nbytes := f.write(s.bytes()) ? + nbytes += f.write('\n'.bytes()) ? + return nbytes +} + +pub fn (mut f File) write_to(pos u64, buf []byte) ?int { + if !f.is_opened { + return error('file is not opened') + } + mut nbytes := 0 + #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf())) + #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),pos.valueOf()); } catch (e) { return builtin.error('' + e); } + + return nbytes +} + +// write_string writes the string `s` into the file +// It returns how many bytes were actually written. +pub fn (mut f File) write_string(s string) ?int { + nbytes := f.write(s.bytes()) ? + return nbytes +} + +pub fn (mut f File) close() { + #f.valueOf().fd.close() +} + +pub fn (mut f File) write_full_buffer(s voidptr, buffer_len size_t) ? {} diff --git a/v_windows/v/vlib/os/file_test.v b/v_windows/v/vlib/os/file_test.v new file mode 100644 index 0000000..3339ad8 --- /dev/null +++ b/v_windows/v/vlib/os/file_test.v @@ -0,0 +1,372 @@ +import os + +struct Point { + x f64 + y f64 + z f64 +} + +struct Extended_Point { + a f64 + b f64 + c f64 + d f64 + e f64 + f f64 + g f64 + h f64 + i f64 +} + +enum Color { + red + green + blue +} + +[flag] +enum Permissions { + read + write + execute +} + +const ( + unit_point = Point{1.0, 1.0, 1.0} + another_point = Point{0.25, 2.25, 6.25} + extended_point = Extended_Point{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} + another_byte = byte(123) + another_color = Color.red + another_permission = Permissions.read | .write +) + +const ( + tfolder = os.join_path(os.temp_dir(), 'os_file_test') + tfile = os.join_path(tfolder, 'test_file') +) + +fn testsuite_begin() ? { + os.rmdir_all(tfolder) or {} + assert !os.is_dir(tfolder) + os.mkdir_all(tfolder) ? + os.chdir(tfolder) ? + assert os.is_dir(tfolder) +} + +fn testsuite_end() ? { + os.chdir(os.wd_at_startup) ? + os.rmdir_all(tfolder) ? + assert !os.is_dir(tfolder) +} + +// test_read_bytes_into_newline_text tests reading text from a file with newlines. +// This test simulates reading a larger text file step by step into a buffer and +// returning on each newline, even before the buffer is full, and reaching EOF before +// the buffer is completely filled. +fn test_read_bytes_into_newline_text() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_string('Hello World!\nGood\r morning.') ? + f.close() + + f = os.open_file(tfile, 'r') ? + mut buf := []byte{len: 8} + + n0 := f.read_bytes_into_newline(mut buf) ? + assert n0 == 8 + + n1 := f.read_bytes_into_newline(mut buf) ? + assert n1 == 5 + + n2 := f.read_bytes_into_newline(mut buf) ? + assert n2 == 8 + + n3 := f.read_bytes_into_newline(mut buf) ? + assert n3 == 6 + + f.close() +} + +// test_read_bytes_into_newline_binary tests reading a binary file with NUL bytes. +// This test simulates the scenario when a byte stream is read and a newline byte +// appears in that stream and an EOF occurs before the buffer is full. +fn test_read_bytes_into_newline_binary() ? { + os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' + mut bw := []byte{len: 15} + bw[9] = 0xff + bw[12] = 10 // newline + + n0_bytes := bw[0..10] + n1_bytes := bw[10..13] + n2_bytes := bw[13..] + + mut f := os.open_file(tfile, 'w') ? + f.write(bw) ? + f.close() + + f = os.open_file(tfile, 'r') ? + mut buf := []byte{len: 10} + + n0 := f.read_bytes_into_newline(mut buf) ? + assert n0 == 10 + assert buf[..n0] == n0_bytes + + n1 := f.read_bytes_into_newline(mut buf) ? + assert n1 == 3 + assert buf[..n1] == n1_bytes + + n2 := f.read_bytes_into_newline(mut buf) ? + assert n2 == 2 + assert buf[..n2] == n2_bytes + f.close() +} + +// test_read_eof_last_read_partial_buffer_fill tests that when reading a file +// the end-of-file is detected and results in a none error being returned. This +// test simulates file reading where the end-of-file is reached inside an fread +// containing data. +fn test_read_eof_last_read_partial_buffer_fill() ? { + mut f := os.open_file(tfile, 'w') ? + bw := []byte{len: 199, init: 5} + f.write(bw) ? + f.close() + + f = os.open_file(tfile, 'r') ? + mut br := []byte{len: 100} + // Read first 100 bytes of 199 byte file, should fill buffer with no error. + n0 := f.read(mut br) ? + assert n0 == 100 + // Read remaining 99 bytes of 199 byte file, should fill buffer with no + // error, even though end-of-file was reached. + n1 := f.read(mut br) ? + assert n1 == 99 + // Read again, end-of-file was previously reached so should return none + // error. + if _ := f.read(mut br) { + // This is not intended behavior because the read function should + // not return a number of bytes read when end-of-file is reached. + assert false + } else { + // Expect none to have been returned when end-of-file. + assert err is none + } + f.close() +} + +// test_read_eof_last_read_full_buffer_fill tests that when reading a file the +// end-of-file is detected and results in a none error being returned. This test +// simulates file reading where the end-of-file is reached at the beinning of an +// fread that returns no data. +fn test_read_eof_last_read_full_buffer_fill() ? { + mut f := os.open_file(tfile, 'w') ? + bw := []byte{len: 200, init: 5} + f.write(bw) ? + f.close() + + f = os.open_file(tfile, 'r') ? + mut br := []byte{len: 100} + // Read first 100 bytes of 200 byte file, should fill buffer with no error. + n0 := f.read(mut br) ? + assert n0 == 100 + // Read remaining 100 bytes of 200 byte file, should fill buffer with no + // error. The end-of-file isn't reached yet, but there is no more data. + n1 := f.read(mut br) ? + assert n1 == 100 + // Read again, end-of-file was previously reached so should return none + // error. + if _ := f.read(mut br) { + // This is not intended behavior because the read function should + // not return a number of bytes read when end-of-file is reached. + assert false + } else { + // Expect none to have been returned when end-of-file. + assert err is none + } + f.close() +} + +fn test_write_struct() ? { + os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' + size_of_point := int(sizeof(Point)) + mut f := os.open_file(tfile, 'w') ? + f.write_struct(another_point) ? + f.close() + x := os.read_file(tfile) ? + pcopy := unsafe { &byte(memdup(&another_point, size_of_point)) } + y := unsafe { pcopy.vstring_with_len(size_of_point) } + assert x == y + $if debug { + eprintln(x.bytes()) + eprintln(y.bytes()) + } +} + +fn test_write_struct_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_struct(extended_point) ? + f.write_struct_at(another_point, 3) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut p := Point{} + f.read_struct_at(mut p, 3) ? + f.close() + + assert p == another_point +} + +fn test_read_struct() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_struct(another_point) ? + f.close() + + f = os.open_file(tfile, 'r') ? + mut p := Point{} + f.read_struct(mut p) ? + f.close() + + assert p == another_point +} + +fn test_read_struct_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write([byte(1), 2, 3]) ? + f.write_struct(another_point) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut p := Point{} + f.read_struct_at(mut p, 3) ? + f.close() + + assert p == another_point +} + +fn test_write_raw() ? { + os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w' + size_of_point := int(sizeof(Point)) + mut f := os.open_file(tfile, 'w') ? + f.write_raw(another_point) ? + f.close() + x := os.read_file(tfile) ? + pcopy := unsafe { &byte(memdup(&another_point, size_of_point)) } + y := unsafe { pcopy.vstring_with_len(size_of_point) } + assert x == y + $if debug { + eprintln(x.bytes()) + eprintln(y.bytes()) + } +} + +fn test_write_raw_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_raw(extended_point) ? + f.write_raw_at(another_point, 3) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut p := Point{} + f.read_struct_at(mut p, 3) ? + f.close() + + assert p == another_point +} + +fn test_write_raw_at_negative_pos() ? { + mut f := os.open_file(tfile, 'w') ? + if _ := f.write_raw_at(another_point, -1) { + assert false + } + f.write_raw_at(another_point, -234) or { assert err.msg == 'Invalid argument' } + f.close() +} + +fn test_read_raw() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_raw(another_point) ? + f.write_raw(another_byte) ? + f.write_raw(another_color) ? + f.write_raw(another_permission) ? + f.close() + f = os.open_file(tfile, 'r') ? + p := f.read_raw() ? + b := f.read_raw() ? + c := f.read_raw() ? + x := f.read_raw() ? + f.close() + + assert p == another_point + assert b == another_byte + assert c == another_color + assert x == another_permission +} + +fn test_read_raw_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write([byte(1), 2, 3]) ? + f.write_raw(another_point) ? + f.write_raw(another_byte) ? + f.write_raw(another_color) ? + f.write_raw(another_permission) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut at := u64(3) + p := f.read_raw_at(at) ? + at += sizeof(Point) + b := f.read_raw_at(at) ? + at += sizeof(byte) + c := f.read_raw_at(at) ? + at += sizeof(Color) + x := f.read_raw_at(at) ? + at += sizeof(Permissions) + f.close() + + assert p == another_point + assert b == another_byte + assert c == another_color + assert x == another_permission +} + +fn test_read_raw_at_negative_pos() ? { + mut f := os.open_file(tfile, 'r') ? + if _ := f.read_raw_at(-1) { + assert false + } + f.read_raw_at(-234) or { assert err.msg == 'Invalid argument' } + f.close() +} + +fn test_seek() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_raw(another_point) ? + f.write_raw(another_byte) ? + f.write_raw(another_color) ? + f.write_raw(another_permission) ? + f.close() + + // println('> ${sizeof(Point)} ${sizeof(byte)} ${sizeof(Color)} ${sizeof(Permissions)}') + f = os.open_file(tfile, 'r') ? + // + f.seek(i64(sizeof(Point)), .start) ? + assert f.tell() ? == sizeof(Point) + b := f.read_raw() ? + assert b == another_byte + + f.seek(i64(sizeof(Color)), .current) ? + x := f.read_raw() ? + assert x == another_permission + // + f.close() +} + +fn test_tell() ? { + for size in 10 .. 30 { + s := 'x'.repeat(size) + os.write_file(tfile, s) ? + fs := os.file_size(tfile) + assert int(fs) == size + // + mut f := os.open_file(tfile, 'r') ? + f.seek(-5, .end) ? + pos := f.tell() ? + f.close() + // dump(pos) + assert pos == size - 5 + } +} diff --git a/v_windows/v/vlib/os/filelock/filelock_test.v b/v_windows/v/vlib/os/filelock/filelock_test.v new file mode 100644 index 0000000..658d3aa --- /dev/null +++ b/v_windows/v/vlib/os/filelock/filelock_test.v @@ -0,0 +1,27 @@ +import os +import os.filelock + +fn test_flock() { + lockfile := 'test.lock' + mut l := filelock.new(lockfile) + assert !os.exists(lockfile) + l.acquire() or { panic(err) } + assert os.exists(lockfile) + // do stuff + l.release() + assert !os.exists(lockfile) +} + +fn test_flock_try() { + lockfile := 'test-try.lock' + mut l := filelock.new(lockfile) + assert l.try_acquire() + l.release() + assert !os.exists(lockfile) + assert l.try_acquire() + assert os.exists(lockfile) + l.release() + assert l.try_acquire() + l.release() + assert !os.exists(lockfile) +} diff --git a/v_windows/v/vlib/os/filelock/lib.v b/v_windows/v/vlib/os/filelock/lib.v new file mode 100644 index 0000000..5a89ad8 --- /dev/null +++ b/v_windows/v/vlib/os/filelock/lib.v @@ -0,0 +1,14 @@ +module filelock + +pub struct FileLock { + name string +mut: + fd int +} + +pub fn new(fileName string) FileLock { + return FileLock{ + name: fileName + fd: -1 + } +} diff --git a/v_windows/v/vlib/os/filelock/lib_nix.c.v b/v_windows/v/vlib/os/filelock/lib_nix.c.v new file mode 100644 index 0000000..1af9916 --- /dev/null +++ b/v_windows/v/vlib/os/filelock/lib_nix.c.v @@ -0,0 +1,82 @@ +module filelock + +import time + +#include + +fn C.unlink(&char) int +fn C.open(&char, int, int) int +fn C.flock(int, int) int + +[unsafe] +pub fn (mut l FileLock) unlink() { + if l.fd != -1 { + C.close(l.fd) + l.fd = -1 + } + C.unlink(&char(l.name.str)) +} + +pub fn (mut l FileLock) acquire() ?bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open_lockfile(l.name) + if fd == -1 { + return error('cannot create lock file $l.name') + } + if C.flock(fd, C.LOCK_EX) == -1 { + C.close(fd) + return error('cannot lock') + } + l.fd = fd + return true +} + +pub fn (mut l FileLock) release() bool { + if l.fd != -1 { + unsafe { + l.unlink() + } + return true + } + return false +} + +pub fn (mut l FileLock) wait_acquire(s int) ?bool { + fin := time.now().add(s) + for time.now() < fin { + if l.try_acquire() { + return true + } + C.usleep(1000) + } + return false +} + +fn open_lockfile(f string) int { + mut fd := C.open(&char(f.str), C.O_CREAT, 0o644) + if fd == -1 { + // if stat is too old delete lockfile + fd = C.open(&char(f.str), C.O_RDONLY, 0) + } + return fd +} + +pub fn (mut l FileLock) try_acquire() bool { + if l.fd != -1 { + return true + } + fd := open_lockfile('$l.name') + if fd != -1 { + err := C.flock(fd, C.LOCK_EX | C.LOCK_NB) + if err == -1 { + C.close(fd) + return false + } + l.fd = fd + return true + } + return false +} diff --git a/v_windows/v/vlib/os/filelock/lib_windows.c.v b/v_windows/v/vlib/os/filelock/lib_windows.c.v new file mode 100644 index 0000000..56cbace --- /dev/null +++ b/v_windows/v/vlib/os/filelock/lib_windows.c.v @@ -0,0 +1,75 @@ +module filelock + +import time + +fn C.DeleteFileW(&u16) bool +fn C.CreateFileW(&u16, u32, u32, voidptr, u32, u32, voidptr) voidptr +fn C.CloseHandle(voidptr) bool + +pub fn (mut l FileLock) unlink() { + if l.fd != -1 { + C.CloseHandle(l.fd) + l.fd = -1 + } + t_wide := l.name.to_wide() + C.DeleteFileW(t_wide) +} + +pub fn (mut l FileLock) acquire() ?bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open(l.name) + if fd == -1 { + return error('cannot create lock file $l.name') + } + l.fd = fd + return true +} + +pub fn (mut l FileLock) release() bool { + if l.fd != -1 { + C.CloseHandle(l.fd) + l.fd = -1 + t_wide := l.name.to_wide() + C.DeleteFileW(t_wide) + return true + } + return false +} + +pub fn (mut l FileLock) wait_acquire(s int) ?bool { + fin := time.now().add(s) + for time.now() < fin { + if l.try_acquire() { + return true + } + time.sleep(1 * time.millisecond) + } + return false +} + +fn open(f string) voidptr { + f_wide := f.to_wide() + // locking it + fd := C.CreateFileW(f_wide, C.GENERIC_READ | C.GENERIC_WRITE, 0, 0, C.OPEN_ALWAYS, + C.FILE_ATTRIBUTE_NORMAL, 0) + if fd == C.INVALID_HANDLE_VALUE { + fd == -1 + } + return fd +} + +pub fn (mut l FileLock) try_acquire() bool { + if l.fd != -1 { + // lock already acquired by this instance + return false + } + fd := open(l.name) + if fd == -1 { + return false + } + l.fd = fd + return true +} diff --git a/v_windows/v/vlib/os/glob_test.v b/v_windows/v/vlib/os/glob_test.v new file mode 100644 index 0000000..889900c --- /dev/null +++ b/v_windows/v/vlib/os/glob_test.v @@ -0,0 +1,80 @@ +import os + +fn deep_glob() ? { + os.chdir(@VMODROOT) ? + matches := os.glob('vlib/v/*/*.v') or { panic(err) } + assert matches.len > 10 + assert 'vlib/v/ast/ast.v' in matches + assert 'vlib/v/ast/table.v' in matches + assert 'vlib/v/token/token.v' in matches + for f in matches { + if !f.starts_with('vlib/v/') { + assert false + } + assert f.ends_with('.v') + } +} + +fn redeep_glob() ? { + os.chdir(@VMODROOT) ? + matches := os.glob('vlib/v/**/*.v') or { panic(err) } + assert matches.len > 10 + assert 'vlib/v/ast/ast.v' in matches + assert 'vlib/v/ast/table.v' in matches + assert 'vlib/v/token/token.v' in matches + for f in matches { + if !f.starts_with('vlib/v/') { + assert false + } + assert f.ends_with('.v') + } +} + +fn test_glob_can_find_v_files_3_levels_deep() ? { + $if !windows { + deep_glob() ? + redeep_glob() ? + } + assert true +} + +fn test_glob_can_find_files_in_current_folder() ? { + os.chdir(@VMODROOT) ? + matches := os.glob('*') ? + assert '.gitignore' in matches + assert 'make.bat' in matches + assert 'Makefile' in matches + assert 'Dockerfile' in matches + assert 'README.md' in matches + assert 'v.mod' in matches + assert 'cmd/' in matches + assert 'vlib/' in matches + assert 'thirdparty/' in matches +} + +fn test_glob_can_be_used_with_multiple_patterns() ? { + os.chdir(@VMODROOT) ? + matches := os.glob('*', 'cmd/tools/*') ? + assert 'README.md' in matches + assert 'Makefile' in matches + $if !windows { + assert 'cmd/tools/test_if_v_test_system_works.v' in matches + } + $if windows { + assert 'test_if_v_test_system_works.v' in matches + } +} + +fn test_glob_star() ? { + os.chdir(@VMODROOT) ? + matches := os.glob('*ake*') ? + assert 'Makefile' in matches + assert 'make.bat' in matches +} + +fn test_glob_not_found() ? { + os.glob('an_unknown_folder/*.v') or { + assert true + return + } +} diff --git a/v_windows/v/vlib/os/inode.c.v b/v_windows/v/vlib/os/inode.c.v new file mode 100644 index 0000000..3c6b19b --- /dev/null +++ b/v_windows/v/vlib/os/inode.c.v @@ -0,0 +1,92 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +enum FileType { + regular + directory + character_device + block_device + fifo + symbolic_link + socket +} + +struct FilePermission { +pub: + read bool + write bool + execute bool +} + +struct FileMode { +pub: + typ FileType + owner FilePermission + group FilePermission + others FilePermission +} + +// inode returns the mode of the file/inode containing inode type and permission information +// it supports windows for regular files but it doesn't matter if you use owner, group or others when checking permissions on windows +pub fn inode(path string) FileMode { + mut attr := C.stat{} + unsafe { C.stat(&char(path.str), &attr) } + mut typ := FileType.regular + if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFDIR) { + typ = .directory + } + $if !windows { + if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFCHR) { + typ = .character_device + } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFBLK) { + typ = .block_device + } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFIFO) { + typ = .fifo + } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFLNK) { + typ = .symbolic_link + } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFSOCK) { + typ = .socket + } + } + $if windows { + return FileMode{ + typ: typ + owner: FilePermission{ + read: (attr.st_mode & u32(C.S_IREAD)) != 0 + write: (attr.st_mode & u32(C.S_IWRITE)) != 0 + execute: (attr.st_mode & u32(C.S_IEXEC)) != 0 + } + group: FilePermission{ + read: (attr.st_mode & u32(C.S_IREAD)) != 0 + write: (attr.st_mode & u32(C.S_IWRITE)) != 0 + execute: (attr.st_mode & u32(C.S_IEXEC)) != 0 + } + others: FilePermission{ + read: (attr.st_mode & u32(C.S_IREAD)) != 0 + write: (attr.st_mode & u32(C.S_IWRITE)) != 0 + execute: (attr.st_mode & u32(C.S_IEXEC)) != 0 + } + } + } $else { + return FileMode{ + typ: typ + owner: FilePermission{ + read: (attr.st_mode & u32(C.S_IRUSR)) != 0 + write: (attr.st_mode & u32(C.S_IWUSR)) != 0 + execute: (attr.st_mode & u32(C.S_IXUSR)) != 0 + } + group: FilePermission{ + read: (attr.st_mode & u32(C.S_IRGRP)) != 0 + write: (attr.st_mode & u32(C.S_IWGRP)) != 0 + execute: (attr.st_mode & u32(C.S_IXGRP)) != 0 + } + others: FilePermission{ + read: (attr.st_mode & u32(C.S_IROTH)) != 0 + write: (attr.st_mode & u32(C.S_IWOTH)) != 0 + execute: (attr.st_mode & u32(C.S_IXOTH)) != 0 + } + } + } +} diff --git a/v_windows/v/vlib/os/inode_test.v b/v_windows/v/vlib/os/inode_test.v new file mode 100644 index 0000000..f716bc2 --- /dev/null +++ b/v_windows/v/vlib/os/inode_test.v @@ -0,0 +1,43 @@ +module os + +const ( + // tfolder will contain all the temporary files/subfolders made by + // the different tests. It would be removed in testsuite_end(), so + // individual os tests do not need to clean up after themselves. + tfolder = join_path(temp_dir(), 'v', 'tests', 'inode_test') +) + +fn testsuite_begin() { + eprintln('testsuite_begin, tfolder = $os.tfolder') + rmdir_all(os.tfolder) or {} + assert !is_dir(os.tfolder) + mkdir_all(os.tfolder) or { panic(err) } + chdir(os.tfolder) or {} + assert is_dir(os.tfolder) +} + +fn testsuite_end() { + chdir(wd_at_startup) or {} + rmdir_all(os.tfolder) or { panic(err) } + assert !is_dir(os.tfolder) +} + +fn test_inode_file_type() { + filename := './test1.txt' + mut file := open_file(filename, 'w', 0o600) or { return } + file.close() + mode := inode(filename) + rm(filename) or { panic(err) } + assert mode.typ == .regular +} + +fn test_inode_file_owner_permission() { + filename := './test2.txt' + mut file := open_file(filename, 'w', 0o600) or { return } + file.close() + mode := inode(filename) + rm(filename) or {} + assert mode.owner.read + assert mode.owner.write + assert !mode.owner.execute +} diff --git a/v_windows/v/vlib/os/notify/backend_default.c.v b/v_windows/v/vlib/os/notify/backend_default.c.v new file mode 100644 index 0000000..1a35c50 --- /dev/null +++ b/v_windows/v/vlib/os/notify/backend_default.c.v @@ -0,0 +1,6 @@ +module notify + +// Implement the API +pub fn new() ?FdNotifier { + panic('unsupported') +} diff --git a/v_windows/v/vlib/os/notify/backend_linux.c.v b/v_windows/v/vlib/os/notify/backend_linux.c.v new file mode 100644 index 0000000..1913913 --- /dev/null +++ b/v_windows/v/vlib/os/notify/backend_linux.c.v @@ -0,0 +1,206 @@ +module notify + +import time +import os + +#include + +struct C.epoll_event { + events u32 + data C.epoll_data_t +} + +[typedef] +union C.epoll_data_t { + ptr voidptr + fd int + u32 u32 + u64 u64 +} + +fn C.epoll_create1(int) int + +fn C.epoll_ctl(int, int, int, &C.epoll_event) int + +fn C.epoll_wait(int, &C.epoll_event, int, int) int + +// EpollNotifier provides methods that implement FdNotifier using the +// epoll I/O event notification facility (linux only) +[noinit] +struct EpollNotifier { + epoll_fd int +} + +// EpollEvent describes an event that occurred for a file descriptor in +// the watch list +[noinit] +struct EpollEvent { +pub: + fd int + kind FdEventType +} + +// new creates a new EpollNotifier +// The FdNotifier interface is returned to allow OS specific +// implementations without exposing the concrete type +pub fn new() ?FdNotifier { + fd := C.epoll_create1(0) // 0 indicates default behavior + if fd == -1 { + return error(os.posix_get_error_msg(C.errno)) + } + // Needed to circumvent V limitations + x := &EpollNotifier{ + epoll_fd: fd + } + return x +} + +const ( + epoll_read = u32(C.EPOLLIN) + epoll_write = u32(C.EPOLLOUT) + epoll_peer_hangup = u32(C.EPOLLRDHUP) + epoll_exception = u32(C.EPOLLPRI) + epoll_error = u32(C.EPOLLERR) + epoll_hangup = u32(C.EPOLLHUP) + epoll_edge_trigger = u32(C.EPOLLET) + epoll_one_shot = u32(C.EPOLLONESHOT) + epoll_wake_up = u32(C.EPOLLWAKEUP) + epoll_exclusive = u32(C.EPOLLEXCLUSIVE) +) + +// ctl is a helper method for add, modify, and remove +fn (mut en EpollNotifier) ctl(fd int, op int, mask u32) ? { + event := C.epoll_event{ + events: mask + data: C.epoll_data_t{ + fd: fd + } + } + if C.epoll_ctl(en.epoll_fd, op, fd, &event) == -1 { + return error(os.posix_get_error_msg(C.errno)) + } +} + +// add adds a file descriptor to the watch list +fn (mut en EpollNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ? { + mask := flags_to_mask(events, ...conf) + en.ctl(fd, C.EPOLL_CTL_ADD, mask) ? +} + +// modify sets an existing entry in the watch list to the provided events and configuration +fn (mut en EpollNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ? { + mask := flags_to_mask(events, ...conf) + en.ctl(fd, C.EPOLL_CTL_MOD, mask) ? +} + +// remove removes a file descriptor from the watch list +fn (mut en EpollNotifier) remove(fd int) ? { + en.ctl(fd, C.EPOLL_CTL_DEL, 0) ? +} + +// wait waits to be notified of events on the watch list, +// returns at most 512 events +fn (mut en EpollNotifier) wait(timeout time.Duration) []FdEvent { + // arbitrary 512 limit; events will round robin on successive + // waits if the number exceeds this + // NOTE: we use a fixed size array here for stack allocation; this has + // the added bonus of making EpollNotifier thread safe + events := [512]C.epoll_event{} + // populate events with the new events + to := timeout.sys_milliseconds() + count := C.epoll_wait(en.epoll_fd, &events[0], events.len, to) + + if count > 0 { + mut arr := []FdEvent{cap: count} + for i := 0; i < count; i++ { + fd := unsafe { events[i].data.fd } + kind := event_mask_to_flag(events[i].events) + if kind.is_empty() { + // NOTE: tcc only reports the first event for some + // reason, leaving subsequent structs in the array as 0 + // (or possibly garbage) + panic('encountered an empty event kind; this is most likely due to using tcc') + } + arr << &EpollEvent{ + fd: fd + kind: kind + } + } + return arr + } + return [] +} + +// close closes the EpollNotifier, +// any successive calls to add, modify, remove, and wait should fail +fn (mut en EpollNotifier) close() ? { + if C.close(en.epoll_fd) == -1 { + return error(os.posix_get_error_msg(C.errno)) + } +} + +// event_mask_to_flag is a helper function that converts a bitmask +// returned by epoll_wait to FdEventType +fn event_mask_to_flag(mask u32) FdEventType { + mut flags := FdEventType{} + + if mask & notify.epoll_read != 0 { + flags.set(.read) + } + if mask & notify.epoll_write != 0 { + flags.set(.write) + } + if mask & notify.epoll_peer_hangup != 0 { + flags.set(.peer_hangup) + } + if mask & notify.epoll_exception != 0 { + flags.set(.exception) + } + if mask & notify.epoll_error != 0 { + flags.set(.error) + } + if mask & notify.epoll_hangup != 0 { + flags.set(.hangup) + } + + return flags +} + +// flags_to_mask is a helper function that converts FdEventType and +// FdConfigFlags to a bitmask used by the C functions +fn flags_to_mask(events FdEventType, confs ...FdConfigFlags) u32 { + mut mask := u32(0) + if events.has(.read) { + mask |= notify.epoll_read + } + if events.has(.write) { + mask |= notify.epoll_write + } + if events.has(.peer_hangup) { + mask |= notify.epoll_peer_hangup + } + if events.has(.exception) { + mask |= notify.epoll_exception + } + if events.has(.error) { + mask |= notify.epoll_error + } + if events.has(.hangup) { + mask |= notify.epoll_hangup + } + for conf in confs { + if conf.has(.edge_trigger) { + mask |= notify.epoll_edge_trigger + } + if conf.has(.one_shot) { + mask |= notify.epoll_one_shot + } + if conf.has(.wake_up) { + mask |= notify.epoll_wake_up + } + if conf.has(.exclusive) { + mask |= notify.epoll_exclusive + } + } + return mask +} diff --git a/v_windows/v/vlib/os/notify/notify.v b/v_windows/v/vlib/os/notify/notify.v new file mode 100644 index 0000000..b49dad3 --- /dev/null +++ b/v_windows/v/vlib/os/notify/notify.v @@ -0,0 +1,35 @@ +module notify + +import time + +// Backends should provide a `new() ?FdNotifier` function +pub interface FdNotifier { + add(fd int, events FdEventType, conf ...FdConfigFlags) ? + modify(fd int, events FdEventType, conf ...FdConfigFlags) ? + remove(fd int) ? + wait(timeout time.Duration) []FdEvent + close() ? +} + +pub interface FdEvent { + fd int + kind FdEventType +} + +[flag] +pub enum FdEventType { + read + write + peer_hangup + exception + error + hangup +} + +[flag] +pub enum FdConfigFlags { + edge_trigger + one_shot + wake_up + exclusive +} diff --git a/v_windows/v/vlib/os/notify/notify_test.v b/v_windows/v/vlib/os/notify/notify_test.v new file mode 100644 index 0000000..253c94f --- /dev/null +++ b/v_windows/v/vlib/os/notify/notify_test.v @@ -0,0 +1,155 @@ +import os +import os.notify + +// make a pipe and return the (read, write) file descriptors +fn make_pipe() ?(int, int) { + $if linux { + pipefd := [2]int{} + if C.pipe(&pipefd[0]) != 0 { + return error('error $C.errno: ' + os.posix_get_error_msg(C.errno)) + } + return pipefd[0], pipefd[1] + } + return -1, -1 +} + +fn test_level_trigger() ? { + // currently only linux is supported + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + os.fd_close(writer) + notifier.close() or {} + } + notifier.add(reader, .read) ? + + os.fd_write(writer, 'foobar') + check_read_event(notifier, reader, 'foo') + check_read_event(notifier, reader, 'bar') + + assert notifier.wait(0).len == 0 + } +} + +fn test_edge_trigger() ? { + // currently only linux is supported + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + os.fd_close(writer) + notifier.close() or {} + } + notifier.add(reader, .read, .edge_trigger) ? + + os.fd_write(writer, 'foobar') + check_read_event(notifier, reader, 'foo') + + assert notifier.wait(0).len == 0 + + os.fd_write(writer, 'baz') + // we do not get an event because there is still data + // to be read + assert notifier.wait(0).len == 0 + } +} + +fn test_one_shot() ? { + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + os.fd_close(writer) + notifier.close() or {} + } + notifier.add(reader, .read, .one_shot) ? + + os.fd_write(writer, 'foobar') + check_read_event(notifier, reader, 'foo') + os.fd_write(writer, 'baz') + + assert notifier.wait(0).len == 0 + + // rearm + notifier.modify(reader, .read) ? + check_read_event(notifier, reader, 'barbaz') + } +} + +fn test_hangup() ? { + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + notifier.close() or {} + } + notifier.add(reader, .hangup) ? + + assert notifier.wait(0).len == 0 + + // closing on the writer end of the pipe will + // cause a hangup on the reader end + os.fd_close(writer) + events := notifier.wait(0) + assert events.len == 1 + assert events[0].fd == reader + assert events[0].kind.has(.hangup) + } +} + +fn test_write() ? { + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + os.fd_close(writer) + notifier.close() or {} + } + + notifier.add(reader, .write) ? + assert notifier.wait(0).len == 0 + + notifier.add(writer, .write) ? + events := notifier.wait(0) + assert events.len == 1 + assert events[0].fd == writer + assert events[0].kind.has(.write) + } +} + +fn test_remove() ? { + $if linux { + mut notifier := notify.new() ? + reader, writer := make_pipe() ? + defer { + os.fd_close(reader) + os.fd_close(writer) + notifier.close() or {} + } + + // level triggered - will keep getting events while + // there is data to read + notifier.add(reader, .read) ? + os.fd_write(writer, 'foobar') + assert notifier.wait(0).len == 1 + assert notifier.wait(0).len == 1 + + notifier.remove(reader) ? + assert notifier.wait(0).len == 0 + } +} + +fn check_read_event(notifier notify.FdNotifier, reader_fd int, expected string) { + events := notifier.wait(0) + assert events.len == 1 + assert events[0].fd == reader_fd + assert events[0].kind.has(.read) + s, _ := os.fd_read(events[0].fd, expected.len) + assert s == expected +} diff --git a/v_windows/v/vlib/os/os.c.v b/v_windows/v/vlib/os/os.c.v new file mode 100644 index 0000000..12369d0 --- /dev/null +++ b/v_windows/v/vlib/os/os.c.v @@ -0,0 +1,1010 @@ +module os + +#include // #include +#include + +pub const ( + args = []string{} +) + +struct C.dirent { + d_name [256]char +} + +fn C.readdir(voidptr) &C.dirent + +fn C.readlink(pathname &char, buf &char, bufsiz size_t) int + +fn C.getline(voidptr, voidptr, voidptr) int + +fn C.ftell(fp voidptr) i64 + +fn C.sigaction(int, voidptr, int) int + +fn C.open(&char, int, ...int) int + +fn C.fdopen(fd int, mode &char) &C.FILE + +fn C.ferror(stream &C.FILE) int + +fn C.feof(stream &C.FILE) int + +fn C.CopyFile(&u16, &u16, bool) int + +// fn C.lstat(charptr, voidptr) u64 + +fn C._wstat64(&u16, voidptr) u64 + +fn C.chown(&char, int, int) int + +fn C.ftruncate(voidptr, u64) int + +fn C._chsize_s(voidptr, u64) int + +// fn C.proc_pidpath(int, byteptr, int) int +struct C.stat { + st_size u64 + st_mode u32 + st_mtime int +} + +struct C.__stat64 { + st_size u64 + st_mode u32 + st_mtime int +} + +struct C.DIR { +} + +type FN_SA_Handler = fn (sig int) + +struct C.sigaction { +mut: + sa_mask int + sa_sigaction int + sa_flags int + sa_handler FN_SA_Handler +} + +struct C.dirent { + d_name &byte +} + +// read_bytes returns all bytes read from file in `path`. +[manualfree] +pub fn read_bytes(path string) ?[]byte { + mut fp := vfopen(path, 'rb') ? + defer { + C.fclose(fp) + } + cseek := C.fseek(fp, 0, C.SEEK_END) + if cseek != 0 { + return error('fseek failed') + } + fsize := C.ftell(fp) + if fsize < 0 { + return error('ftell failed') + } + C.rewind(fp) + mut res := []byte{len: int(fsize)} + nr_read_elements := int(C.fread(res.data, fsize, 1, fp)) + if nr_read_elements == 0 && fsize > 0 { + return error('fread failed') + } + res.trim(nr_read_elements * int(fsize)) + return res +} + +// read_file reads the file in `path` and returns the contents. +pub fn read_file(path string) ?string { + mode := 'rb' + mut fp := vfopen(path, mode) ? + defer { + C.fclose(fp) + } + cseek := C.fseek(fp, 0, C.SEEK_END) + if cseek != 0 { + return error('fseek failed') + } + fsize := C.ftell(fp) + if fsize < 0 { + return error('ftell failed') + } + // C.fseek(fp, 0, SEEK_SET) // same as `C.rewind(fp)` below + C.rewind(fp) + unsafe { + mut str := malloc_noscan(int(fsize) + 1) + nelements := int(C.fread(str, 1, fsize, fp)) + is_eof := int(C.feof(fp)) + is_error := int(C.ferror(fp)) + if is_eof == 0 && is_error != 0 { + free(str) + return error('fread failed') + } + str[nelements] = 0 + if nelements == 0 { + // It is highly likely that the file was a virtual file from + // /sys or /proc, with information generated on the fly, so + // fsize was not reliably reported. Using vstring() here is + // slower (it calls strlen internally), but will return more + // consistent results. + // For example reading from /sys/class/sound/card0/id produces + // a `PCH\n` string, but fsize is 4096, and otherwise you would + // get a V string with .len = 4096 and .str = "PCH\n\\000". + return str.vstring() + } + return str.vstring_with_len(nelements) + } +} + +// ***************************** OS ops ************************ +// +// truncate changes the size of the file located in `path` to `len`. +// Note that changing symbolic links on Windows only works as admin. +pub fn truncate(path string, len u64) ? { + fp := C.open(&char(path.str), o_wronly | o_trunc, 0) + defer { + C.close(fp) + } + if fp < 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + $if windows { + if C._chsize_s(fp, len) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + } $else { + if C.ftruncate(fp, len) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + } +} + +fn eprintln_unknown_file_size() { + eprintln('os.file_size() Cannot determine file-size: ' + posix_get_error_msg(C.errno)) +} + +// file_size returns the size of the file located in `path`. +// If an error occurs it returns 0. +// Note that use of this on symbolic links on Windows returns always 0. +pub fn file_size(path string) u64 { + mut s := C.stat{} + unsafe { + $if x64 { + $if windows { + mut swin := C.__stat64{} + if C._wstat64(path.to_wide(), voidptr(&swin)) != 0 { + eprintln_unknown_file_size() + return 0 + } + return swin.st_size + } $else { + if C.stat(&char(path.str), &s) != 0 { + eprintln_unknown_file_size() + return 0 + } + return u64(s.st_size) + } + } + $if x32 { + $if debug { + eprintln('Using os.file_size() on 32bit systems may not work on big files.') + } + $if windows { + if C._wstat(path.to_wide(), voidptr(&s)) != 0 { + eprintln_unknown_file_size() + return 0 + } + return u64(s.st_size) + } $else { + if C.stat(&char(path.str), &s) != 0 { + eprintln_unknown_file_size() + return 0 + } + return u64(s.st_size) + } + } + } + return 0 +} + +// mv moves files or folders from `src` to `dst`. +pub fn mv(src string, dst string) ? { + mut rdst := dst + if is_dir(rdst) { + rdst = join_path(rdst.trim_right(path_separator), file_name(src.trim_right(path_separator))) + } + $if windows { + w_src := src.replace('/', '\\') + w_dst := rdst.replace('/', '\\') + ret := C._wrename(w_src.to_wide(), w_dst.to_wide()) + if ret != 0 { + return error_with_code('failed to rename $src to $dst', int(ret)) + } + } $else { + ret := C.rename(&char(src.str), &char(rdst.str)) + if ret != 0 { + return error_with_code('failed to rename $src to $dst', int(ret)) + } + } +} + +// cp copies files or folders from `src` to `dst`. +pub fn cp(src string, dst string) ? { + $if windows { + w_src := src.replace('/', '\\') + w_dst := dst.replace('/', '\\') + if C.CopyFile(w_src.to_wide(), w_dst.to_wide(), false) == 0 { + result := C.GetLastError() + return error_with_code('failed to copy $src to $dst', int(result)) + } + } $else { + fp_from := C.open(&char(src.str), C.O_RDONLY, 0) + if fp_from < 0 { // Check if file opened + return error_with_code('cp: failed to open $src', int(fp_from)) + } + fp_to := C.open(&char(dst.str), C.O_WRONLY | C.O_CREAT | C.O_TRUNC, C.S_IWUSR | C.S_IRUSR) + if fp_to < 0 { // Check if file opened (permissions problems ...) + C.close(fp_from) + return error_with_code('cp (permission): failed to write to $dst (fp_to: $fp_to)', + int(fp_to)) + } + // TODO use defer{} to close files in case of error or return. + // Currently there is a C-Error when building. + mut buf := [1024]byte{} + mut count := 0 + for { + count = C.read(fp_from, &buf[0], sizeof(buf)) + if count == 0 { + break + } + if C.write(fp_to, &buf[0], count) < 0 { + C.close(fp_to) + C.close(fp_from) + return error_with_code('cp: failed to write to $dst', int(-1)) + } + } + from_attr := C.stat{} + unsafe { + C.stat(&char(src.str), &from_attr) + } + if C.chmod(&char(dst.str), from_attr.st_mode) < 0 { + C.close(fp_to) + C.close(fp_from) + return error_with_code('failed to set permissions for $dst', int(-1)) + } + C.close(fp_to) + C.close(fp_from) + } +} + +// vfopen returns an opened C file, given its path and open mode. +// NB: os.vfopen is useful for compatibility with C libraries, that expect `FILE *`. +// If you write pure V code, os.create or os.open are more convenient. +pub fn vfopen(path string, mode string) ?&C.FILE { + if path.len == 0 { + return error('vfopen called with ""') + } + mut fp := voidptr(0) + $if windows { + fp = C._wfopen(path.to_wide(), mode.to_wide()) + } $else { + fp = C.fopen(&char(path.str), &char(mode.str)) + } + if isnil(fp) { + return error('failed to open file "$path"') + } else { + return fp + } +} + +// fileno returns the file descriptor of an opened C file. +pub fn fileno(cfile voidptr) int { + $if windows { + return C._fileno(cfile) + } $else { + mut cfile_casted := &C.FILE(0) // FILE* cfile_casted = 0; + cfile_casted = cfile + // Required on FreeBSD/OpenBSD/NetBSD as stdio.h defines fileno(..) with a macro + // that performs a field access on its argument without casting from void*. + return C.fileno(cfile_casted) + } +} + +// vpopen system starts the specified command, waits for it to complete, and returns its code. +fn vpopen(path string) voidptr { + // *C.FILE { + $if windows { + mode := 'rb' + wpath := path.to_wide() + return C._wpopen(wpath, mode.to_wide()) + } $else { + cpath := path.str + return C.popen(&char(cpath), c'r') + } +} + +fn posix_wait4_to_exit_status(waitret int) (int, bool) { + $if windows { + return waitret, false + } $else { + mut ret := 0 + mut is_signaled := true + // (see man system, man 2 waitpid: C macro WEXITSTATUS section) + if C.WIFEXITED(waitret) { + ret = C.WEXITSTATUS(waitret) + is_signaled = false + } else if C.WIFSIGNALED(waitret) { + ret = C.WTERMSIG(waitret) + is_signaled = true + } + return ret, is_signaled + } +} + +// posix_get_error_msg return error code representation in string. +pub fn posix_get_error_msg(code int) string { + ptr_text := C.strerror(code) // voidptr? + if ptr_text == 0 { + return '' + } + return unsafe { tos3(ptr_text) } +} + +// vpclose will close a file pointer opened with `vpopen`. +fn vpclose(f voidptr) int { + $if windows { + return C._pclose(f) + } $else { + ret, _ := posix_wait4_to_exit_status(C.pclose(f)) + return ret + } +} + +// system works like `exec`, but only returns a return code. +pub fn system(cmd string) int { + // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + // TODO remove panic + // panic(';, &&, || and \\n are not allowed in shell commands') + // } + mut ret := 0 + $if windows { + // overcome bug in system & _wsystem (cmd) when first char is quote `"` + wcmd := if cmd.len > 1 && cmd[0] == `"` && cmd[1] != `"` { '"$cmd"' } else { cmd } + unsafe { + ret = C._wsystem(wcmd.to_wide()) + } + } $else { + $if ios { + unsafe { + arg := [c'/bin/sh', c'-c', &byte(cmd.str), 0] + pid := 0 + ret = C.posix_spawn(&pid, c'/bin/sh', 0, 0, arg.data, 0) + status := 0 + ret = C.waitpid(pid, &status, 0) + if C.WIFEXITED(status) { + ret = C.WEXITSTATUS(status) + } + } + } $else { + unsafe { + ret = C.system(&char(cmd.str)) + } + } + } + if ret == -1 { + print_c_errno() + } + $if !windows { + pret, is_signaled := posix_wait4_to_exit_status(ret) + if is_signaled { + println('Terminated by signal ${ret:2d} (' + sigint_to_signal_name(pret) + ')') + } + ret = pret + } + return ret +} + +// exists returns true if `path` (file or directory) exists. +pub fn exists(path string) bool { + $if windows { + p := path.replace('/', '\\') + return C._waccess(p.to_wide(), f_ok) != -1 + } $else { + return C.access(&char(path.str), f_ok) != -1 + } +} + +// is_executable returns `true` if `path` is executable. +pub fn is_executable(path string) bool { + $if windows { + // NB: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=vs-2019 + // i.e. there is no X bit there, the modes can be: + // 00 Existence only + // 02 Write-only + // 04 Read-only + // 06 Read and write + p := real_path(path) + return (exists(p) && p.ends_with('.exe')) + } + $if solaris { + statbuf := C.stat{} + unsafe { + if C.stat(&char(path.str), &statbuf) != 0 { + return false + } + } + return (int(statbuf.st_mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0 + } + return C.access(&char(path.str), x_ok) != -1 +} + +// is_writable returns `true` if `path` is writable. +pub fn is_writable(path string) bool { + $if windows { + p := path.replace('/', '\\') + return C._waccess(p.to_wide(), w_ok) != -1 + } $else { + return C.access(&char(path.str), w_ok) != -1 + } +} + +// is_readable returns `true` if `path` is readable. +pub fn is_readable(path string) bool { + $if windows { + p := path.replace('/', '\\') + return C._waccess(p.to_wide(), r_ok) != -1 + } $else { + return C.access(&char(path.str), r_ok) != -1 + } +} + +// rm removes file in `path`. +pub fn rm(path string) ? { + mut rc := 0 + $if windows { + rc = C._wremove(path.to_wide()) + } $else { + rc = C.remove(&char(path.str)) + } + if rc == -1 { + return error('Failed to remove "$path": ' + posix_get_error_msg(C.errno)) + } + // C.unlink(path.cstr()) +} + +// rmdir removes a specified directory. +pub fn rmdir(path string) ? { + $if windows { + rc := C.RemoveDirectory(path.to_wide()) + if rc == 0 { + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya - 0 is failure + return error('Failed to remove "$path": ' + posix_get_error_msg(C.errno)) + } + } $else { + rc := C.rmdir(&char(path.str)) + if rc == -1 { + return error(posix_get_error_msg(C.errno)) + } + } +} + +// print_c_errno will print the current value of `C.errno`. +fn print_c_errno() { + e := C.errno + se := unsafe { tos_clone(&byte(C.strerror(e))) } + println('errno=$e err=$se') +} + +// get_raw_line returns a one-line string from stdin along with '\n' if there is any. +pub fn get_raw_line() string { + $if windows { + unsafe { + max_line_chars := 256 + buf := malloc_noscan(max_line_chars * 2) + h_input := C.GetStdHandle(C.STD_INPUT_HANDLE) + mut bytes_read := u32(0) + if is_atty(0) > 0 { + x := C.ReadConsole(h_input, buf, max_line_chars * 2, &bytes_read, 0) + if !x { + return tos(buf, 0) + } + return string_from_wide2(&u16(buf), int(bytes_read)) + } + mut offset := 0 + for { + pos := buf + offset + res := C.ReadFile(h_input, pos, 1, C.LPDWORD(&bytes_read), 0) + if !res && offset == 0 { + return tos(buf, 0) + } + if !res || bytes_read == 0 { + break + } + if *pos == `\n` || *pos == `\r` { + offset++ + break + } + offset++ + } + return buf.vstring_with_len(offset) + } + } $else { + max := size_t(0) + buf := &char(0) + nr_chars := unsafe { C.getline(&buf, &max, C.stdin) } + return unsafe { tos(&byte(buf), if nr_chars < 0 { 0 } else { nr_chars }) } + } +} + +// get_raw_stdin will get the raw input from stdin. +pub fn get_raw_stdin() []byte { + $if windows { + unsafe { + block_bytes := 512 + mut old_size := block_bytes + mut buf := malloc_noscan(block_bytes) + h_input := C.GetStdHandle(C.STD_INPUT_HANDLE) + mut bytes_read := 0 + mut offset := 0 + for { + pos := buf + offset + res := C.ReadFile(h_input, pos, block_bytes, C.LPDWORD(&bytes_read), 0) + offset += bytes_read + if !res { + break + } + new_size := offset + block_bytes + (block_bytes - bytes_read) + buf = realloc_data(buf, old_size, new_size) + old_size = new_size + } + return array{ + element_size: 1 + data: voidptr(buf) + len: offset + cap: offset + } + } + } $else { + max := size_t(0) + buf := &char(0) + nr_chars := unsafe { C.getline(&buf, &max, C.stdin) } + return array{ + element_size: 1 + data: voidptr(buf) + len: if nr_chars < 0 { 0 } else { nr_chars } + cap: int(max) + } + } +} + +// read_file_array reads an array of `T` values from file `path`. +pub fn read_file_array(path string) []T { + a := T{} + tsize := int(sizeof(a)) + // prepare for reading, get current file size + mut fp := vfopen(path, 'rb') or { return []T{} } + C.fseek(fp, 0, C.SEEK_END) + fsize := C.ftell(fp) + C.rewind(fp) + // read the actual data from the file + len := fsize / tsize + buf := unsafe { malloc_noscan(int(fsize)) } + nread := C.fread(buf, tsize, len, fp) + C.fclose(fp) + return unsafe { + array{ + element_size: tsize + data: buf + len: int(nread) + cap: int(len) + } + } +} + +pub fn on_segfault(f voidptr) { + $if windows { + return + } + $if macos { + C.printf(c'TODO') + /* + mut sa := C.sigaction{} + C.memset(&sa, 0, sizeof(C.sigaction_size)) + C.sigemptyset(&sa.sa_mask) + sa.sa_sigaction = f + sa.sa_flags = C.SA_SIGINFO + C.sigaction(C.SIGSEGV, &sa, 0) + */ + } +} + +// executable returns the path name of the executable that started the current +// process. +[manualfree] +pub fn executable() string { + $if linux { + mut xresult := vcalloc_noscan(max_path_len) + count := C.readlink(c'/proc/self/exe', &char(xresult), max_path_len) + if count < 0 { + eprintln('os.executable() failed at reading /proc/self/exe to get exe path') + return executable_fallback() + } + return unsafe { xresult.vstring() } + } + $if windows { + max := 512 + size := max * 2 // max_path_len * sizeof(wchar_t) + mut result := unsafe { &u16(vcalloc_noscan(size)) } + len := C.GetModuleFileName(0, result, max) + // determine if the file is a windows symlink + attrs := C.GetFileAttributesW(result) + is_set := attrs & 0x400 // FILE_ATTRIBUTE_REPARSE_POINT + if is_set != 0 { // it's a windows symlink + // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + file := C.CreateFile(result, 0x80000000, 1, 0, 3, 0x80, 0) + if file != voidptr(-1) { + final_path := unsafe { &u16(vcalloc_noscan(size)) } + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew + final_len := C.GetFinalPathNameByHandleW(file, final_path, size, 0) + if final_len < size { + ret := unsafe { string_from_wide2(final_path, final_len) } + // remove '\\?\' from beginning (see link above) + return ret[4..] + } else { + eprintln('os.executable() saw that the executable file path was too long') + } + } + C.CloseHandle(file) + } + return unsafe { string_from_wide2(result, len) } + } + $if macos { + mut result := vcalloc_noscan(max_path_len) + pid := C.getpid() + ret := proc_pidpath(pid, result, max_path_len) + if ret <= 0 { + eprintln('os.executable() failed at calling proc_pidpath with pid: $pid . proc_pidpath returned $ret ') + return executable_fallback() + } + return unsafe { result.vstring() } + } + $if freebsd { + mut result := vcalloc_noscan(max_path_len) + mib := [1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1] + size := max_path_len + unsafe { C.sysctl(mib.data, 4, result, &size, 0, 0) } + return unsafe { result.vstring() } + } + // "Sadly there is no way to get the full path of the executed file in OpenBSD." + $if openbsd { + } + $if solaris { + } + $if haiku { + } + $if netbsd { + mut result := vcalloc_noscan(max_path_len) + count := C.readlink(c'/proc/curproc/exe', &char(result), max_path_len) + if count < 0 { + eprintln('os.executable() failed at reading /proc/curproc/exe to get exe path') + return executable_fallback() + } + return unsafe { result.vstring_with_len(count) } + } + $if dragonfly { + mut result := vcalloc_noscan(max_path_len) + count := C.readlink(c'/proc/curproc/file', &char(result), max_path_len) + if count < 0 { + eprintln('os.executable() failed at reading /proc/curproc/file to get exe path') + return executable_fallback() + } + return unsafe { result.vstring_with_len(count) } + } + return executable_fallback() +} + +// is_dir returns a `bool` indicating whether the given `path` is a directory. +pub fn is_dir(path string) bool { + $if windows { + w_path := path.replace('/', '\\') + attr := C.GetFileAttributesW(w_path.to_wide()) + if attr == u32(C.INVALID_FILE_ATTRIBUTES) { + return false + } + if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 { + return true + } + return false + } $else { + statbuf := C.stat{} + if unsafe { C.stat(&char(path.str), &statbuf) } != 0 { + return false + } + // ref: https://code.woboq.org/gcc/include/sys/stat.h.html + val := int(statbuf.st_mode) & s_ifmt + return val == s_ifdir + } +} + +// is_link returns a boolean indicating whether `path` is a link. +pub fn is_link(path string) bool { + $if windows { + path_ := path.replace('/', '\\') + attr := C.GetFileAttributesW(path_.to_wide()) + return int(attr) != int(C.INVALID_FILE_ATTRIBUTES) && (attr & 0x400) != 0 + } $else { + statbuf := C.stat{} + if C.lstat(&char(path.str), &statbuf) != 0 { + return false + } + return int(statbuf.st_mode) & s_ifmt == s_iflnk + } +} + +// chdir changes the current working directory to the new directory in `path`. +pub fn chdir(path string) ? { + ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) } + if ret == -1 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } +} + +// getwd returns the absolute path of the current directory. +pub fn getwd() string { + $if windows { + max := 512 // max_path_len * sizeof(wchar_t) + unsafe { + buf := &u16(vcalloc_noscan(max * 2)) + if C._wgetcwd(buf, max) == 0 { + free(buf) + return '' + } + return string_from_wide(buf) + } + } $else { + buf := vcalloc_noscan(max_path_len) + unsafe { + if C.getcwd(&char(buf), max_path_len) == 0 { + free(buf) + return '' + } + return buf.vstring() + } + } +} + +// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved. +// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html +// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html +// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html +// NB: this particular rabbit hole is *deep* ... +[manualfree] +pub fn real_path(fpath string) string { + mut res := '' + $if windows { + size := max_path_len * 2 + // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + // use C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) instead of get_file_handle + // try to open the file to get symbolic link path + file := C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) + if file != voidptr(-1) { + mut fullpath := unsafe { &u16(vcalloc_noscan(size)) } + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew + final_len := C.GetFinalPathNameByHandleW(file, fullpath, size, 0) + C.CloseHandle(file) + if final_len < size { + rt := unsafe { string_from_wide2(fullpath, final_len) } + res = rt[4..] + } else { + unsafe { free(fullpath) } + eprintln('os.real_path() saw that the file path was too long') + return fpath.clone() + } + } else { + // if it is not a file C.CreateFile doesn't gets a file handle, use GetFullPath instead + mut fullpath := unsafe { &u16(vcalloc_noscan(max_path_len * 2)) } + // TODO: check errors if path len is not enough + ret := C.GetFullPathName(fpath.to_wide(), max_path_len, fullpath, 0) + if ret == 0 { + unsafe { free(fullpath) } + return fpath.clone() + } + res = unsafe { string_from_wide(fullpath) } + } + } $else { + mut fullpath := vcalloc_noscan(max_path_len) + ret := &char(C.realpath(&char(fpath.str), &char(fullpath))) + if ret == 0 { + unsafe { free(fullpath) } + return fpath.clone() + } + res = unsafe { fullpath.vstring() } + } + unsafe { normalize_drive_letter(res) } + return res +} + +[direct_array_access; manualfree; unsafe] +fn normalize_drive_letter(path string) { + $if !windows { + return + } + // normalize_drive_letter is needed, because + // a path like c:\nv\.bin (note the small `c`) in %PATH, + // is NOT recognized by cmd.exe (and probably other programs too)... + // Capital drive letters do work fine. + if path.len > 2 && path[0] >= `a` && path[0] <= `z` && path[1] == `:` + && path[2] == path_separator[0] { + unsafe { + x := &path.str[0] + (*x) = *x - 32 + } + } +} + +// fork will fork the current system process and return the pid of the fork. +pub fn fork() int { + mut pid := -1 + $if !windows { + pid = C.fork() + } + $if windows { + panic('os.fork not supported in windows') // TODO + } + return pid +} + +// wait blocks the calling process until one of its child processes exits or a signal is received. +// After child process terminates, parent continues its execution after wait system call instruction. +pub fn wait() int { + mut pid := -1 + $if !windows { + pid = C.wait(0) + } + $if windows { + panic('os.wait not supported in windows') // TODO + } + return pid +} + +// file_last_mod_unix returns the "last modified" time stamp of file in `path`. +pub fn file_last_mod_unix(path string) int { + attr := C.stat{} + // # struct stat attr; + unsafe { C.stat(&char(path.str), &attr) } + // # stat(path.str, &attr); + return attr.st_mtime + // # return attr.st_mtime ; +} + +// flush will flush the stdout buffer. +pub fn flush() { + C.fflush(C.stdout) +} + +// chmod change file access attributes of `path` to `mode`. +// Octals like `0o600` can be used. +pub fn chmod(path string, mode int) ? { + if C.chmod(&char(path.str), mode) != 0 { + return error_with_code('chmod failed: ' + posix_get_error_msg(C.errno), C.errno) + } +} + +// chown changes the owner and group attributes of `path` to `owner` and `group`. +pub fn chown(path string, owner int, group int) ? { + $if windows { + return error('os.chown() not implemented for Windows') + } $else { + if C.chown(&char(path.str), owner, group) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + } +} + +// open_append opens `path` file for appending. +pub fn open_append(path string) ?File { + mut file := File{} + $if windows { + wpath := path.replace('/', '\\').to_wide() + mode := 'ab' + file = File{ + cfile: C._wfopen(wpath, mode.to_wide()) + } + } $else { + cpath := path.str + file = File{ + cfile: C.fopen(&char(cpath), c'ab') + } + } + if isnil(file.cfile) { + return error('failed to create(append) file "$path"') + } + file.is_opened = true + return file +} + +// execvp - loads and executes a new child process, *in place* of the current process. +// The child process executable is located in `cmdpath`. +// The arguments, that will be passed to it are in `args`. +// NB: this function will NOT return when successfull, since +// the child process will take control over execution. +pub fn execvp(cmdpath string, cmdargs []string) ? { + mut cargs := []&char{} + cargs << &char(cmdpath.str) + for i in 0 .. cmdargs.len { + cargs << &char(cmdargs[i].str) + } + cargs << &char(0) + mut res := int(0) + $if windows { + res = C._execvp(&char(cmdpath.str), cargs.data) + } $else { + res = C.execvp(&char(cmdpath.str), cargs.data) + } + if res == -1 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + // just in case C._execvp returned ... that happens on windows ... + exit(res) +} + +// execve - loads and executes a new child process, *in place* of the current process. +// The child process executable is located in `cmdpath`. +// The arguments, that will be passed to it are in `args`. +// You can pass environment variables to through `envs`. +// NB: this function will NOT return when successfull, since +// the child process will take control over execution. +pub fn execve(cmdpath string, cmdargs []string, envs []string) ? { + mut cargv := []&char{} + mut cenvs := []&char{} + cargv << &char(cmdpath.str) + for i in 0 .. cmdargs.len { + cargv << &char(cmdargs[i].str) + } + for i in 0 .. envs.len { + cenvs << &char(envs[i].str) + } + cargv << &char(0) + cenvs << &char(0) + mut res := int(0) + $if windows { + res = C._execve(&char(cmdpath.str), cargv.data, cenvs.data) + } $else { + res = C.execve(&char(cmdpath.str), cargv.data, cenvs.data) + } + // NB: normally execve does not return at all. + // If it returns, then something went wrong... + if res == -1 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } +} + +// is_atty returns 1 if the `fd` file descriptor is open and refers to a terminal +pub fn is_atty(fd int) int { + $if windows { + mut mode := u32(0) + osfh := voidptr(C._get_osfhandle(fd)) + C.GetConsoleMode(osfh, voidptr(&mode)) + return int(mode) + } $else { + return C.isatty(fd) + } +} + +// write_file_array writes the data in `buffer` to a file in `path`. +pub fn write_file_array(path string, buffer array) ? { + mut f := create(path) ? + unsafe { f.write_full_buffer(buffer.data, size_t(buffer.len * buffer.element_size)) ? } + f.close() +} + +pub fn glob(patterns ...string) ?[]string { + mut matches := []string{} + for pattern in patterns { + native_glob_pattern(pattern, mut matches) ? + } + matches.sort() + return matches +} diff --git a/v_windows/v/vlib/os/os.js.v b/v_windows/v/vlib/os/os.js.v new file mode 100644 index 0000000..6ba3c77 --- /dev/null +++ b/v_windows/v/vlib/os/os.js.v @@ -0,0 +1,97 @@ +module os + +#const $fs = require('fs'); +#const $path = require('path'); + +pub const ( + path_delimiter = '/' + path_separator = '/' + args = []string{} +) + +$if js_node { + #$process.argv.forEach(function(val,index) { args.arr[index] = new string(val); }) +} + +// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved. +// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html +// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html +// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html +// NB: this particular rabbit hole is *deep* ... +pub fn real_path(fpath string) string { + $if js_node { + mut res := '' + #res = new string( $fs.realpathSync(fpath)) + + return res + } $else { + return fpath + } +} + +// flush will flush the stdout buffer. +pub fn flush() { + $if js_node { + #$process.stdout.write('') + } +} + +// chmod change file access attributes of `path` to `mode`. +// Octals like `0o600` can be used. +pub fn chmod(path string, mode int) { + $if js_node { + #$fs.chmodSync(''+path,mode.valueOf()) + } +} + +// chown changes the owner and group attributes of `path` to `owner` and `group`. +// Octals like `0o600` can be used. +pub fn chown(path string, owner int, group int) { + $if js_node { + #$fs.chownSync(''+path,owner.valueOf(),group.valueOf()) + } +} + +pub fn temp_dir() string { + mut res := '' + $if js_node { + #res = new builtin.string($os.tmpdir()) + } + return res +} + +pub fn home_dir() string { + mut res := '' + $if js_node { + #res = new builtin.string($os.homedir()) + } + return res +} + +// join_path returns a path as string from input string parameter(s). +pub fn join_path(base string, dirs ...string) string { + mut result := []string{} + result << base.trim_right('\\/') + for d in dirs { + result << d + } + mut path_sep := '' + #path_sep = $path.sep; + + res := result.join(path_sep) + return res +} + +pub fn execute(cmd string) Result { + mut exit_code := 0 + mut stdout := '' + #let commands = cmd.str.split(' '); + #let output = $child_process.spawnSync(commands[0],commands.slice(1,commands.length)); + #exit_code = new builtin.int(output.status) + #stdout = new builtin.string(output.stdout + '') + + return Result{ + exit_code: exit_code + output: stdout + } +} diff --git a/v_windows/v/vlib/os/os.v b/v_windows/v/vlib/os/os.v new file mode 100644 index 0000000..4574156 --- /dev/null +++ b/v_windows/v/vlib/os/os.v @@ -0,0 +1,633 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +pub const ( + max_path_len = 4096 + wd_at_startup = getwd() +) + +const ( + f_ok = 0 + x_ok = 1 + w_ok = 2 + r_ok = 4 +) + +pub struct Result { +pub: + exit_code int + output string + // stderr string // TODO +} + +[unsafe] +pub fn (mut result Result) free() { + unsafe { result.output.free() } +} + +// cp_all will recursively copy `src` to `dst`, +// optionally overwriting files or dirs in `dst`. +pub fn cp_all(src string, dst string, overwrite bool) ? { + source_path := real_path(src) + dest_path := real_path(dst) + if !exists(source_path) { + return error("Source path doesn't exist") + } + // single file copy + if !is_dir(source_path) { + adjusted_path := if is_dir(dest_path) { + join_path(dest_path, file_name(source_path)) + } else { + dest_path + } + if exists(adjusted_path) { + if overwrite { + rm(adjusted_path) ? + } else { + return error('Destination file path already exist') + } + } + cp(source_path, adjusted_path) ? + return + } + if !exists(dest_path) { + mkdir(dest_path) ? + } + if !is_dir(dest_path) { + return error('Destination path is not a valid directory') + } + files := ls(source_path) ? + for file in files { + sp := join_path(source_path, file) + dp := join_path(dest_path, file) + if is_dir(sp) { + if !exists(dp) { + mkdir(dp) ? + } + } + cp_all(sp, dp, overwrite) or { + rmdir(dp) or { return err } + return err + } + } +} + +// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file. +// may be used when you are not sure that the source and target are on the same mount/partition. +pub fn mv_by_cp(source string, target string) ? { + cp(source, target) ? + rm(source) ? +} + +// read_lines reads the file in `path` into an array of lines. +pub fn read_lines(path string) ?[]string { + buf := read_file(path) ? + res := buf.split_into_lines() + unsafe { buf.free() } + return res +} + +// sigint_to_signal_name will translate `si` signal integer code to it's string code representation. +pub fn sigint_to_signal_name(si int) string { + // POSIX signals: + match si { + 1 { return 'SIGHUP' } + 2 { return 'SIGINT' } + 3 { return 'SIGQUIT' } + 4 { return 'SIGILL' } + 6 { return 'SIGABRT' } + 8 { return 'SIGFPE' } + 9 { return 'SIGKILL' } + 11 { return 'SIGSEGV' } + 13 { return 'SIGPIPE' } + 14 { return 'SIGALRM' } + 15 { return 'SIGTERM' } + else {} + } + $if linux { + // From `man 7 signal` on linux: + match si { + // TODO dependent on platform + // works only on x86/ARM/most others + 10 /* , 30, 16 */ { return 'SIGUSR1' } + 12 /* , 31, 17 */ { return 'SIGUSR2' } + 17 /* , 20, 18 */ { return 'SIGCHLD' } + 18 /* , 19, 25 */ { return 'SIGCONT' } + 19 /* , 17, 23 */ { return 'SIGSTOP' } + 20 /* , 18, 24 */ { return 'SIGTSTP' } + 21 /* , 26 */ { return 'SIGTTIN' } + 22 /* , 27 */ { return 'SIGTTOU' } + // ///////////////////////////// + 5 { return 'SIGTRAP' } + 7 { return 'SIGBUS' } + else {} + } + } + return 'unknown' +} + +// rmdir_all recursively removes the specified directory. +pub fn rmdir_all(path string) ? { + mut ret_err := '' + items := ls(path) ? + for item in items { + fullpath := join_path(path, item) + if is_dir(fullpath) { + rmdir_all(fullpath) or { ret_err = err.msg } + } else { + rm(fullpath) or { ret_err = err.msg } + } + } + rmdir(path) or { ret_err = err.msg } + if ret_err.len > 0 { + return error(ret_err) + } +} + +// is_dir_empty will return a `bool` whether or not `path` is empty. +pub fn is_dir_empty(path string) bool { + items := ls(path) or { return true } + return items.len == 0 +} + +// file_ext will return the part after the last occurence of `.` in `path`. +// The `.` is included. +pub fn file_ext(path string) string { + pos := path.last_index('.') or { return '' } + return path[pos..] +} + +// dir returns all but the last element of path, typically the path's directory. +// After dropping the final element, trailing slashes are removed. +// If the path is empty, dir returns ".". If the path consists entirely of separators, +// dir returns a single separator. +// The returned path does not end in a separator unless it is the root directory. +pub fn dir(opath string) string { + if opath == '' { + return '.' + } + path := opath.replace_each(['/', path_separator, r'\', path_separator]) + pos := path.last_index(path_separator) or { return '.' } + if pos == 0 && path_separator == '/' { + return '/' + } + return path[..pos] +} + +// base returns the last element of path. +// Trailing path separators are removed before extracting the last element. +// If the path is empty, base returns ".". If the path consists entirely of separators, base returns a +// single separator. +pub fn base(opath string) string { + if opath == '' { + return '.' + } + path := opath.replace_each(['/', path_separator, r'\', path_separator]) + if path == path_separator { + return path_separator + } + if path.ends_with(path_separator) { + path2 := path[..path.len - 1] + pos := path2.last_index(path_separator) or { return path2.clone() } + return path2[pos + 1..] + } + pos := path.last_index(path_separator) or { return path.clone() } + return path[pos + 1..] +} + +// file_name will return all characters found after the last occurence of `path_separator`. +// file extension is included. +pub fn file_name(opath string) string { + path := opath.replace_each(['/', path_separator, r'\', path_separator]) + return path.all_after_last(path_separator) +} + +// input_opt returns a one-line string from stdin, after printing a prompt. +// In the event of error (end of input), it returns `none`. +pub fn input_opt(prompt string) ?string { + print(prompt) + flush() + res := get_raw_line() + if res.len > 0 { + return res.trim_right('\r\n') + } + return none +} + +// input returns a one-line string from stdin, after printing a prompt. +// In the event of error (end of input), it returns ''. +pub fn input(prompt string) string { + res := input_opt(prompt) or { return '' } + return res +} + +// get_line returns a one-line string from stdin +pub fn get_line() string { + str := get_raw_line() + $if windows { + return str.trim_right('\r\n') + } + return str.trim_right('\n') +} + +// get_lines returns an array of strings read from from stdin. +// reading is stopped when an empty line is read. +pub fn get_lines() []string { + mut line := '' + mut inputstr := []string{} + for { + line = get_line() + if line.len <= 0 { + break + } + line = line.trim_space() + inputstr << line + } + return inputstr +} + +// get_lines_joined returns a string of the values read from from stdin. +// reading is stopped when an empty line is read. +pub fn get_lines_joined() string { + mut line := '' + mut inputstr := '' + for { + line = get_line() + if line.len <= 0 { + break + } + line = line.trim_space() + inputstr += line + } + return inputstr +} + +// get_raw_lines_joined reads *all* input lines from stdin. +// It returns them as one large string. NB: unlike os.get_lines_joined, +// empty lines (that contain only `\r\n` or `\n`), will be present in +// the output. +// Reading is stopped, only on EOF of stdin. +pub fn get_raw_lines_joined() string { + mut line := '' + mut lines := []string{} + for { + line = get_raw_line() + if line.len <= 0 { + break + } + lines << line + } + res := lines.join('') + return res +} + +// user_os returns current user operating system name. +pub fn user_os() string { + $if linux { + return 'linux' + } + $if macos { + return 'macos' + } + $if windows { + return 'windows' + } + $if freebsd { + return 'freebsd' + } + $if openbsd { + return 'openbsd' + } + $if netbsd { + return 'netbsd' + } + $if dragonfly { + return 'dragonfly' + } + $if android { + return 'android' + } + $if solaris { + return 'solaris' + } + $if haiku { + return 'haiku' + } + $if serenity { + return 'serenity' + } + $if vinix { + return 'vinix' + } + return 'unknown' +} + +// home_dir returns path to the user's home directory. +pub fn home_dir() string { + $if windows { + return getenv('USERPROFILE') + } $else { + // println('home_dir() call') + // res:= os.getenv('HOME') + // println('res="$res"') + return getenv('HOME') + } +} + +// write_file writes `text` data to a file in `path`. +pub fn write_file(path string, text string) ? { + mut f := create(path) ? + unsafe { f.write_full_buffer(text.str, size_t(text.len)) ? } + f.close() +} + +// executable_fallback is used when there is not a more platform specific and accurate implementation. +// It relies on path manipulation of os.args[0] and os.wd_at_startup, so it may not work properly in +// all cases, but it should be better, than just using os.args[0] directly. +fn executable_fallback() string { + if args.len == 0 { + // we are early in the bootstrap, os.args has not been initialized yet :-| + return '' + } + mut exepath := args[0] + $if windows { + if !exepath.contains('.exe') { + exepath += '.exe' + } + } + if !is_abs_path(exepath) { + rexepath := exepath.replace_each(['/', path_separator, r'\', path_separator]) + if rexepath.contains(path_separator) { + exepath = join_path(os.wd_at_startup, exepath) + } else { + // no choice but to try to walk the PATH folders :-| ... + foundpath := find_abs_path_of_executable(exepath) or { '' } + if foundpath.len > 0 { + exepath = foundpath + } + } + } + exepath = real_path(exepath) + return exepath +} + +// find_exe_path walks the environment PATH, just like most shell do, it returns +// the absolute path of the executable if found +pub fn find_abs_path_of_executable(exepath string) ?string { + if exepath == '' { + return error('expected non empty `exepath`') + } + if is_abs_path(exepath) { + return real_path(exepath) + } + mut res := '' + paths := getenv('PATH').split(path_delimiter) + for p in paths { + found_abs_path := join_path(p, exepath) + if exists(found_abs_path) && is_executable(found_abs_path) { + res = found_abs_path + break + } + } + if res.len > 0 { + return real_path(res) + } + return error('failed to find executable') +} + +// exists_in_system_path returns `true` if `prog` exists in the system's PATH +pub fn exists_in_system_path(prog string) bool { + find_abs_path_of_executable(prog) or { return false } + return true +} + +// is_file returns a `bool` indicating whether the given `path` is a file. +pub fn is_file(path string) bool { + return exists(path) && !is_dir(path) +} + +// is_abs_path returns `true` if `path` is absolute. +pub fn is_abs_path(path string) bool { + if path.len == 0 { + return false + } + $if windows { + return path[0] == `/` || // incase we're in MingGW bash + (path[0].is_letter() && path.len > 1 && path[1] == `:`) + } + return path[0] == `/` +} + +// join_path returns a path as string from input string parameter(s). +[manualfree] +pub fn join_path(base string, dirs ...string) string { + mut result := []string{} + result << base.trim_right('\\/') + for d in dirs { + result << d + } + res := result.join(path_separator) + unsafe { result.free() } + return res +} + +// walk_ext returns a recursive list of all files in `path` ending with `ext`. +pub fn walk_ext(path string, ext string) []string { + if !is_dir(path) { + return [] + } + mut files := ls(path) or { return [] } + mut res := []string{} + separator := if path.ends_with(path_separator) { '' } else { path_separator } + for file in files { + if file.starts_with('.') { + continue + } + p := path + separator + file + if is_dir(p) && !is_link(p) { + res << walk_ext(p, ext) + } else if file.ends_with(ext) { + res << p + } + } + return res +} + +// walk recursively traverses the given directory `path`. +// When a file is encountred it will call the callback function with current file as argument. +pub fn walk(path string, f fn (string)) { + if !is_dir(path) { + return + } + mut files := ls(path) or { return } + mut local_path_separator := path_separator + if path.ends_with(path_separator) { + local_path_separator = '' + } + for file in files { + p := path + local_path_separator + file + if is_dir(p) && !is_link(p) { + walk(p, f) + } else if exists(p) { + f(p) + } + } + return +} + +// log will print "os.log: "+`s` ... +pub fn log(s string) { + //$if macos { + // Use NSLog() on macos + // C.darwin_log(s) + //} $else { + println('os.log: ' + s) + //} +} + +// mkdir_all will create a valid full path of all directories given in `path`. +pub fn mkdir_all(path string) ? { + mut p := if path.starts_with(path_separator) { path_separator } else { '' } + path_parts := path.trim_left(path_separator).split(path_separator) + for subdir in path_parts { + p += subdir + path_separator + if exists(p) && is_dir(p) { + continue + } + mkdir(p) or { return error('folder: $p, error: $err') } + } +} + +// cache_dir returns the path to a *writable* user specific folder, suitable for writing non-essential data. +pub fn cache_dir() string { + // See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + // There is a single base directory relative to which user-specific non-essential + // (cached) data should be written. This directory is defined by the environment + // variable $XDG_CACHE_HOME. + // $XDG_CACHE_HOME defines the base directory relative to which user specific + // non-essential data files should be stored. If $XDG_CACHE_HOME is either not set + // or empty, a default equal to $HOME/.cache should be used. + $if !windows { + xdg_cache_home := getenv('XDG_CACHE_HOME') + if xdg_cache_home != '' { + return xdg_cache_home + } + } + cdir := join_path(home_dir(), '.cache') + if !is_dir(cdir) && !is_link(cdir) { + mkdir(cdir) or { panic(err) } + } + return cdir +} + +// temp_dir returns the path to a folder, that is suitable for storing temporary files. +pub fn temp_dir() string { + mut path := getenv('TMPDIR') + $if windows { + if path == '' { + // TODO see Qt's implementation? + // https://doc.qt.io/qt-5/qdir.html#tempPath + // https://github.com/qt/qtbase/blob/e164d61ca8263fc4b46fdd916e1ea77c7dd2b735/src/corelib/io/qfilesystemengine_win.cpp#L1275 + path = getenv('TEMP') + if path == '' { + path = getenv('TMP') + } + if path == '' { + path = 'C:/tmp' + } + } + } + $if macos { + // avoid /var/folders/6j/cmsk8gd90pd.... on macs + return '/tmp' + } + $if android { + // TODO test+use '/data/local/tmp' on Android before using cache_dir() + if path == '' { + path = cache_dir() + } + } + if path == '' { + path = '/tmp' + } + return path +} + +fn default_vmodules_path() string { + return join_path(home_dir(), '.vmodules') +} + +// vmodules_dir returns the path to a folder, where v stores its global modules. +pub fn vmodules_dir() string { + paths := vmodules_paths() + if paths.len > 0 { + return paths[0] + } + return default_vmodules_path() +} + +// vmodules_paths returns a list of paths, where v looks up for modules. +// You can customize it through setting the environment variable VMODULES +pub fn vmodules_paths() []string { + mut path := getenv('VMODULES') + if path == '' { + path = default_vmodules_path() + } + list := path.split(path_delimiter).map(it.trim_right(path_separator)) + return list +} + +// resource_abs_path returns an absolute path, for the given `path`. +// (the path is expected to be relative to the executable program) +// See https://discordapp.com/channels/592103645835821068/592294828432424960/630806741373943808 +// It gives a convenient way to access program resources like images, fonts, sounds and so on, +// *no matter* how the program was started, and what is the current working directory. +[manualfree] +pub fn resource_abs_path(path string) string { + exe := executable() + dexe := dir(exe) + mut base_path := real_path(dexe) + vresource := getenv('V_RESOURCE_PATH') + if vresource.len != 0 { + base_path = vresource + } + fp := join_path(base_path, path) + res := real_path(fp) + unsafe { + fp.free() + base_path.free() + } + return res +} + +pub struct Uname { +pub mut: + sysname string + nodename string + release string + version string + machine string +} + +pub fn execute_or_panic(cmd string) Result { + res := execute(cmd) + if res.exit_code != 0 { + eprintln('failed cmd: $cmd') + eprintln('failed code: $res.exit_code') + panic(res.output) + } + return res +} + +pub fn execute_or_exit(cmd string) Result { + res := execute(cmd) + if res.exit_code != 0 { + eprintln('failed cmd: $cmd') + eprintln('failed code: $res.exit_code') + eprintln(res.output) + exit(1) + } + return res +} diff --git a/v_windows/v/vlib/os/os_android.c.v b/v_windows/v/vlib/os/os_android.c.v new file mode 100644 index 0000000..30825ea --- /dev/null +++ b/v_windows/v/vlib/os/os_android.c.v @@ -0,0 +1,39 @@ +module os + +struct C.AAsset { +} + +struct C.AAssetManager { +} + +struct C.ANativeActivity { + assetManager voidptr +} + +fn C.AAssetManager_open(&C.AAssetManager, &char, int) &C.AAsset + +fn C.AAsset_getLength(&C.AAsset) int + +fn C.AAsset_read(&C.AAsset, voidptr, int) int + +fn C.AAsset_close(&C.AAsset) + +pub fn read_apk_asset(file string) ?[]byte { + act := &C.ANativeActivity(C.sapp_android_get_native_activity()) + if isnil(act) { + return error('Could not get reference to Android activity') + } + asset := C.AAssetManager_open(&C.AAssetManager(act.assetManager), file.str, C.AASSET_MODE_STREAMING) + if isnil(asset) { + return error('File `$file` not found') + } + len := C.AAsset_getLength(asset) + buf := []byte{len: len} + for { + if C.AAsset_read(asset, buf.data, len) > 0 { + break + } + } + C.AAsset_close(asset) + return buf +} diff --git a/v_windows/v/vlib/os/os_darwin.c.v b/v_windows/v/vlib/os/os_darwin.c.v new file mode 100644 index 0000000..8635c63 --- /dev/null +++ b/v_windows/v/vlib/os/os_darwin.c.v @@ -0,0 +1,18 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +#include "@VROOT/vlib/os/os_darwin.m" + +pub const ( + sys_write = 4 + sys_open = 5 + sys_close = 6 + sys_mkdir = 136 + sys_creat = 8 + sys_open_nocancel = 398 + sys_stat64 = 338 +) + +fn C.darwin_log(s string) diff --git a/v_windows/v/vlib/os/os_darwin.m b/v_windows/v/vlib/os/os_darwin.m new file mode 100644 index 0000000..a1de752 --- /dev/null +++ b/v_windows/v/vlib/os/os_darwin.m @@ -0,0 +1,7 @@ +/* +NSString* nsstring(string s); + +void darwin_log(string s) { + NSLog(nsstring(s)); +} +*/ diff --git a/v_windows/v/vlib/os/os_js.js.v b/v_windows/v/vlib/os/os_js.js.v new file mode 100644 index 0000000..008a7c1 --- /dev/null +++ b/v_windows/v/vlib/os/os_js.js.v @@ -0,0 +1,127 @@ +module os + +pub fn mkdir(path string) ?bool { + $if js_node { + if path == '.' { + return true + } + #$fs.mkdirSync(path.valueOf()) + + return true + } $else { + return false + } +} + +pub fn is_dir(path string) bool { + res := false + $if js_node { + #res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory() + } + return res +} + +pub fn is_link(path string) bool { + res := false + $if js_node { + #res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink() + } + return res +} + +pub fn exists(path string) bool { + res := false + $if js_node { + #res.val = $fs.existsSync(path.str) + } + return res +} + +pub fn ls(path string) ?[]string { + if !is_dir(path) { + return error('ls(): cannot open dir $dir') + } + + result := []string{} + $if js_node { + #let i = 0 + #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path)) + } + return result +} + +pub fn get_raw_line() string { + return '' +} + +pub fn executable() string { + return '' +} + +pub fn is_executable(path string) bool { + eprintln('TODO: There is no isExecutable on fs.stats') + return false +} + +pub fn rmdir(path string) ? { + $if js_node { + err := '' + #try { + #$fs.rmdirSync(path.str) + #return; + #} catch (e) { + #err.str = 'Failed to remove "' + path.str + '": ' + e.toString() + #} + + return error(err) + } +} + +pub fn rm(path string) ? { + $if js_node { + err := '' + #try { + #$fs.rmSync(path.str) + #return; + #} catch (e) { + #err.str = 'Failed to remove "' + path.str + '": ' + e.toString() + #} + + return error(err) + } +} + +pub fn cp(src string, dst string) ? { + $if js_node { + err := '' + #try { + #$fs.cpSync(src.str,dst.str); + #return; + #} catch (e) { + #err.str = 'failed to copy ' + src.str + ' to ' + dst.str + ': ' + e.toString(); + #} + + return error(err) + } +} + +pub fn read_file(s string) ?string { + mut err := '' + err = err + res := '' + #try { + #res.str = $fs.readFileSync(s.str).toString() + #} catch (e) { + #err.str = 'Failed to read file: ' + e.toString() + #return error(err) + #} + + return res +} + +pub fn getwd() string { + res := '' + #res.str = $process.cwd() + + return res +} diff --git a/v_windows/v/vlib/os/os_linux.c.v b/v_windows/v/vlib/os/os_linux.c.v new file mode 100644 index 0000000..e178795 --- /dev/null +++ b/v_windows/v/vlib/os/os_linux.c.v @@ -0,0 +1,19 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module os + +const ( + prot_read = 1 + prot_write = 2 + map_private = 0x02 + map_anonymous = 0x20 +) + +pub const ( + sys_write = 1 + sys_open = 2 + sys_close = 3 + sys_mkdir = 83 + sys_creat = 85 +) diff --git a/v_windows/v/vlib/os/os_nix.c.v b/v_windows/v/vlib/os/os_nix.c.v new file mode 100644 index 0000000..6640ec8 --- /dev/null +++ b/v_windows/v/vlib/os/os_nix.c.v @@ -0,0 +1,549 @@ +module os + +import strings + +#include +#include +#include +#include +#include +#include +$if !solaris && !haiku { + #include +} + +pub const ( + path_separator = '/' + path_delimiter = ':' +) + +const ( + stdin_value = 0 + stdout_value = 1 + stderr_value = 2 +) + +// (Must be realized in Syscall) (Must be specified) +// ref: http://www.ccfit.nsu.ru/~deviv/courses/unix/unix/ng7c229.html +pub const ( + s_ifmt = 0xF000 // type of file + s_ifdir = 0x4000 // directory + s_iflnk = 0xa000 // link + s_isuid = 0o4000 // SUID + s_isgid = 0o2000 // SGID + s_isvtx = 0o1000 // Sticky + s_irusr = 0o0400 // Read by owner + s_iwusr = 0o0200 // Write by owner + s_ixusr = 0o0100 // Execute by owner + s_irgrp = 0o0040 // Read by group + s_iwgrp = 0o0020 // Write by group + s_ixgrp = 0o0010 // Execute by group + s_iroth = 0o0004 // Read by others + s_iwoth = 0o0002 // Write by others + s_ixoth = 0o0001 // Execute by others +) + +struct C.utsname { +mut: + sysname &char + nodename &char + release &char + version &char + machine &char +} + +struct C.utimbuf { + actime int + modtime int +} + +fn C.utime(&char, voidptr) int + +fn C.uname(name voidptr) int + +fn C.symlink(&char, &char) int + +fn C.link(&char, &char) int + +fn C.gethostname(&char, int) int + +// NB: not available on Android fn C.getlogin_r(&char, int) int +fn C.getlogin() &char + +fn C.getppid() int + +fn C.getgid() int + +fn C.getegid() int + +fn C.ptrace(u32, u32, voidptr, int) u64 + +enum GlobMatch { + exact + ends_with + starts_with + start_and_ends_with + contains + any +} + +fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string { + mut subdirs := []string{} + if is_file(dir) { + return subdirs + } + mut files := ls(dir) or { return subdirs } + mut mode := GlobMatch.exact + mut pat := pattern + if pat == '*' { + mode = GlobMatch.any + if next_pattern != pattern && next_pattern != '' { + for file in files { + if is_dir('$dir/$file') { + subdirs << '$dir/$file' + } + } + return subdirs + } + } + if pat == '**' { + files = walk_ext(dir, '') + pat = next_pattern + } + if pat.starts_with('*') { + mode = .ends_with + pat = pat[1..] + } + if pat.ends_with('*') { + mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with } + pat = pat[..pat.len - 1] + } + if pat.contains('*') { + mode = .start_and_ends_with + } + for file in files { + mut fpath := file + f := if file.contains(os.path_separator) { + pathwalk := file.split(os.path_separator) + pathwalk[pathwalk.len - 1] + } else { + fpath = if dir == '.' { file } else { '$dir/$file' } + file + } + if f in ['.', '..'] || f == '' { + continue + } + hit := match mode { + .any { + true + } + .exact { + f == pat + } + .starts_with { + f.starts_with(pat) + } + .ends_with { + f.ends_with(pat) + } + .start_and_ends_with { + p := pat.split('*') + f.starts_with(p[0]) && f.ends_with(p[1]) + } + .contains { + f.contains(pat) + } + } + if hit { + if is_dir(fpath) { + subdirs << fpath + if next_pattern == pattern && next_pattern != '' { + matches << '$fpath$os.path_separator' + } + } else { + matches << fpath + } + } + } + return subdirs +} + +fn native_glob_pattern(pattern string, mut matches []string) ? { + steps := pattern.split(os.path_separator) + mut cwd := if pattern.starts_with(os.path_separator) { os.path_separator } else { '.' } + mut subdirs := [cwd] + for i := 0; i < steps.len; i++ { + step := steps[i] + step2 := if i + 1 == steps.len { step } else { steps[i + 1] } + if step == '' { + continue + } + if is_dir('$cwd$os.path_separator$step') { + dd := if cwd == '/' { + step + } else { + if cwd == '.' || cwd == '' { + step + } else { + if step == '.' || step == '/' { cwd } else { '$cwd/$step' } + } + } + if i + 1 != steps.len { + if dd !in subdirs { + subdirs << dd + } + } + } + mut subs := []string{} + for sd in subdirs { + d := if cwd == '/' { + sd + } else { + if cwd == '.' || cwd == '' { + sd + } else { + if sd == '.' || sd == '/' { cwd } else { '$cwd/$sd' } + } + } + subs << glob_match(d.replace('//', '/'), step, step2, mut matches) + } + subdirs = subs.clone() + } +} + +pub fn utime(path string, actime int, modtime int) ? { + mut u := C.utimbuf{actime, modtime} + if C.utime(&char(path.str), voidptr(&u)) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } +} + +pub fn uname() Uname { + mut u := Uname{} + utsize := sizeof(C.utsname) + unsafe { + x := malloc_noscan(int(utsize)) + d := &C.utsname(x) + if C.uname(d) == 0 { + u.sysname = cstring_to_vstring(d.sysname) + u.nodename = cstring_to_vstring(d.nodename) + u.release = cstring_to_vstring(d.release) + u.version = cstring_to_vstring(d.version) + u.machine = cstring_to_vstring(d.machine) + } + free(d) + } + return u +} + +pub fn hostname() string { + mut hstnme := '' + size := 256 + mut buf := unsafe { &char(malloc_noscan(size)) } + if C.gethostname(buf, size) == 0 { + hstnme = unsafe { cstring_to_vstring(buf) } + unsafe { free(buf) } + return hstnme + } + return '' +} + +pub fn loginname() string { + x := C.getlogin() + if !isnil(x) { + return unsafe { cstring_to_vstring(x) } + } + return '' +} + +fn init_os_args(argc int, argv &&byte) []string { + mut args_ := []string{} + // mut args := []string(make(0, argc, sizeof(string))) + // mut args := []string{len:argc} + for i in 0 .. argc { + // args [i] = argv[i].vstring() + unsafe { args_ << (&byte(argv[i])).vstring_literal() } + } + return args_ +} + +pub fn ls(path string) ?[]string { + mut res := []string{} + dir := unsafe { C.opendir(&char(path.str)) } + if isnil(dir) { + return error('ls() couldnt open dir "$path"') + } + mut ent := &C.dirent(0) + // mut ent := &C.dirent{!} + for { + ent = C.readdir(dir) + if isnil(ent) { + break + } + unsafe { + bptr := &byte(&ent.d_name[0]) + if bptr[0] == 0 || (bptr[0] == `.` && bptr[1] == 0) + || (bptr[0] == `.` && bptr[1] == `.` && bptr[2] == 0) { + continue + } + res << tos_clone(bptr) + } + } + C.closedir(dir) + return res +} + +/* +pub fn is_dir(path string) bool { + //$if linux { + //C.syscall(4, path.str) // sys_newstat + //} + dir := C.opendir(path.str) + res := !isnil(dir) + if res { + C.closedir(dir) + } + return res +} +*/ + +// mkdir creates a new directory with the specified path. +pub fn mkdir(path string) ?bool { + if path == '.' { + return true + } + /* + mut k := 0 + defer { + k = 1 + } + */ + apath := real_path(path) + // defer { + // apath.free() + //} + /* + $if linux { + $if !android { + ret := C.syscall(sys_mkdir, apath.str, 511) + if ret == -1 { + return error(posix_get_error_msg(C.errno)) + } + return true + } + } + */ + r := unsafe { C.mkdir(&char(apath.str), 511) } + if r == -1 { + return error(posix_get_error_msg(C.errno)) + } + return true +} + +// execute starts the specified command, waits for it to complete, and returns its output. +[manualfree] +pub fn execute(cmd string) Result { + // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + // return Result{ exit_code: -1, output: ';, &&, || and \\n are not allowed in shell commands' } + // } + pcmd := if cmd.contains('2>') { cmd } else { '$cmd 2>&1' } + f := vpopen(pcmd) + if isnil(f) { + return Result{ + exit_code: -1 + output: 'exec("$cmd") failed' + } + } + buf := unsafe { malloc_noscan(4096) } + mut res := strings.new_builder(1024) + defer { + unsafe { res.free() } + } + unsafe { + bufbp := buf + for C.fgets(&char(bufbp), 4096, f) != 0 { + buflen := vstrlen(bufbp) + res.write_ptr(bufbp, buflen) + } + } + soutput := res.str() + exit_code := vpclose(f) + unsafe { free(buf) } + return Result{ + exit_code: exit_code + output: soutput + } +} + +pub struct Command { +mut: + f voidptr +pub mut: + eof bool +pub: + path string + redirect_stdout bool +} + +[manualfree] +pub fn (mut c Command) start() ? { + pcmd := c.path + ' 2>&1' + defer { + unsafe { pcmd.free() } + } + c.f = vpopen(pcmd) + if isnil(c.f) { + return error('exec("$c.path") failed') + } +} + +[manualfree] +pub fn (mut c Command) read_line() string { + buf := [4096]byte{} + mut res := strings.new_builder(1024) + defer { + unsafe { res.free() } + } + unsafe { + bufbp := &buf[0] + for C.fgets(&char(bufbp), 4096, c.f) != 0 { + len := vstrlen(bufbp) + for i in 0 .. len { + if bufbp[i] == `\n` { + res.write_ptr(bufbp, i) + final := res.str() + return final + } + } + res.write_ptr(bufbp, len) + } + } + c.eof = true + final := res.str() + return final +} + +pub fn (c &Command) close() ? { + exit_code := vpclose(c.f) + if exit_code == 127 { + return error_with_code('error', 127) + } +} + +pub fn symlink(origin string, target string) ?bool { + res := C.symlink(&char(origin.str), &char(target.str)) + if res == 0 { + return true + } + return error(posix_get_error_msg(C.errno)) +} + +pub fn link(origin string, target string) ?bool { + res := C.link(&char(origin.str), &char(target.str)) + if res == 0 { + return true + } + return error(posix_get_error_msg(C.errno)) +} + +// get_error_msg return error code representation in string. +pub fn get_error_msg(code int) string { + return posix_get_error_msg(code) +} + +pub fn (mut f File) close() { + if !f.is_opened { + return + } + f.is_opened = false + /* + $if linux { + $if !android { + C.syscall(sys_close, f.fd) + return + } + } + */ + C.fflush(f.cfile) + C.fclose(f.cfile) +} + +[inline] +pub fn debugger_present() bool { + // check if the parent could trace its process, + // if not a debugger must be present + $if linux { + return C.ptrace(C.PTRACE_TRACEME, 0, 1, 0) == -1 + } $else $if macos { + return C.ptrace(C.PT_TRACE_ME, 0, voidptr(1), 0) == -1 + } + return false +} + +fn C.mkstemp(stemplate &byte) int + +// `is_writable_folder` - `folder` exists and is writable to the process +pub fn is_writable_folder(folder string) ?bool { + if !exists(folder) { + return error('`$folder` does not exist') + } + if !is_dir(folder) { + return error('`folder` is not a folder') + } + tmp_perm_check := join_path(folder, 'XXXXXX') + unsafe { + x := C.mkstemp(&char(tmp_perm_check.str)) + if -1 == x { + return error('folder `$folder` is not writable') + } + C.close(x) + } + rm(tmp_perm_check) ? + return true +} + +[inline] +pub fn getpid() int { + return C.getpid() +} + +[inline] +pub fn getppid() int { + return C.getppid() +} + +[inline] +pub fn getuid() int { + return C.getuid() +} + +[inline] +pub fn geteuid() int { + return C.geteuid() +} + +[inline] +pub fn getgid() int { + return C.getgid() +} + +[inline] +pub fn getegid() int { + return C.getegid() +} + +// Turns the given bit on or off, depending on the `enable` parameter +pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { + mut s := C.stat{} + mut new_mode := u32(0) + path := &char(path_s.str) + unsafe { + C.stat(path, &s) + new_mode = s.st_mode + } + match enable { + true { new_mode |= mode } + false { new_mode &= (0o7777 - mode) } + } + C.chmod(path, int(new_mode)) +} diff --git a/v_windows/v/vlib/os/os_test.v b/v_windows/v/vlib/os/os_test.v new file mode 100644 index 0000000..2fff68e --- /dev/null +++ b/v_windows/v/vlib/os/os_test.v @@ -0,0 +1,752 @@ +import os +import time + +const ( + // tfolder will contain all the temporary files/subfolders made by + // the different tests. It would be removed in testsuite_end(), so + // individual os tests do not need to clean up after themselves. + tfolder = os.join_path(os.temp_dir(), 'v', 'tests', 'os_test') +) + +// os.args has to be *already initialized* with the program's argc/argv at this point +// thus it can be used for other consts too: +const args_at_start = os.args.clone() + +fn testsuite_begin() { + eprintln('testsuite_begin, tfolder = $tfolder') + os.rmdir_all(tfolder) or {} + assert !os.is_dir(tfolder) + os.mkdir_all(tfolder) or { panic(err) } + os.chdir(tfolder) or {} + assert os.is_dir(tfolder) + // println('args_at_start: $args_at_start') + assert args_at_start.len > 0 + assert args_at_start == os.args +} + +fn testsuite_end() { + os.chdir(os.wd_at_startup) or {} + os.rmdir_all(tfolder) or {} + assert !os.is_dir(tfolder) + // eprintln('testsuite_end , tfolder = $tfolder removed.') +} + +fn test_open_file() { + filename := './test1.txt' + hello := 'hello world!' + os.open_file(filename, 'r+', 0o666) or { + assert err.msg == 'No such file or directory' + os.File{} + } + mut file := os.open_file(filename, 'w+', 0o666) or { panic(err) } + file.write_string(hello) or { panic(err) } + file.close() + assert hello.len == os.file_size(filename) + read_hello := os.read_file(filename) or { panic('error reading file $filename') } + assert hello == read_hello + os.rm(filename) or { panic(err) } +} + +fn test_open_file_binary() { + filename := './test1.dat' + hello := 'hello \n world!' + os.open_file(filename, 'r+', 0o666) or { + assert err.msg == 'No such file or directory' + os.File{} + } + mut file := os.open_file(filename, 'wb+', 0o666) or { panic(err) } + bytes := hello.bytes() + unsafe { file.write_ptr(bytes.data, bytes.len) } + file.close() + assert hello.len == os.file_size(filename) + read_hello := os.read_bytes(filename) or { panic('error reading file $filename') } + assert bytes == read_hello + os.rm(filename) or { panic(err) } +} + +// fn test_file_get_line() { +// filename := './fgetline.txt' +// os.write_file(filename, 'line 1\nline 2') +// mut f := os.open_file(filename, 'r', 0) or { +// assert false +// return +// } +// line1 := f.get_line() or { +// '' +// } +// line2 := f.get_line() or { +// '' +// } +// f.close() +// // +// eprintln('line1: $line1 $line1.bytes()') +// eprintln('line2: $line2 $line2.bytes()') +// assert line1 == 'line 1\n' +// assert line2 == 'line 2' +// } +fn test_create_file() { + filename := './test1.txt' + hello := 'hello world!' + mut f := os.create(filename) or { panic(err) } + f.write_string(hello) or { panic(err) } + f.close() + assert hello.len == os.file_size(filename) + os.rm(filename) or { panic(err) } +} + +fn test_is_file() { + // Setup + work_dir := os.join_path(os.getwd(), 'is_file_test') + os.mkdir_all(work_dir) or { panic(err) } + tfile := os.join_path(work_dir, 'tmp_file') + // Test things that shouldn't be a file + assert os.is_file(work_dir) == false + assert os.is_file('non-existent_file.tmp') == false + // Test file + tfile_content := 'temporary file' + os.write_file(tfile, tfile_content) or { panic(err) } + assert os.is_file(tfile) + // Test dir symlinks + $if windows { + assert true + } $else { + dsymlink := os.join_path(work_dir, 'dir_symlink') + os.symlink(work_dir, dsymlink) or { panic(err) } + assert os.is_file(dsymlink) == false + } + // Test file symlinks + $if windows { + assert true + } $else { + fsymlink := os.join_path(work_dir, 'file_symlink') + os.symlink(tfile, fsymlink) or { panic(err) } + assert os.is_file(fsymlink) + } +} + +fn test_write_and_read_string_to_file() { + filename := './test1.txt' + hello := 'hello world!' + os.write_file(filename, hello) or { panic(err) } + assert hello.len == os.file_size(filename) + read_hello := os.read_file(filename) or { panic('error reading file $filename') } + assert hello == read_hello + os.rm(filename) or { panic(err) } +} + +// test_write_and_read_bytes checks for regressions made in the functions +// read_bytes, read_bytes_at and write_bytes. +fn test_write_and_read_bytes() { + file_name := './byte_reader_writer.tst' + payload := [byte(`I`), `D`, `D`, `Q`, `D`] + mut file_write := os.create(os.real_path(file_name)) or { + eprintln('failed to create file $file_name') + return + } + // We use the standard write_bytes function to write the payload and + // compare the length of the array with the file size (have to match). + unsafe { file_write.write_ptr(payload.data, 5) } + file_write.close() + assert payload.len == os.file_size(file_name) + mut file_read := os.open(os.real_path(file_name)) or { + eprintln('failed to open file $file_name') + return + } + // We only need to test read_bytes because this function calls + // read_bytes_at with second parameter zeroed (size, 0). + rbytes := file_read.read_bytes(5) + // eprintln('rbytes: $rbytes') + // eprintln('payload: $payload') + assert rbytes == payload + // check that trying to read data from EOF doesn't error and returns 0 + mut a := []byte{len: 5} + nread := file_read.read_bytes_into(5, mut a) or { + n := if err is none { + int(0) + } else { + eprintln(err) + int(-1) + } + n + } + assert nread == 0 + file_read.close() + // We finally delete the test file. + os.rm(file_name) or { panic(err) } +} + +fn test_create_and_delete_folder() { + folder := './test1' + os.mkdir(folder) or { panic(err) } + assert os.is_dir(folder) + folder_contents := os.ls(folder) or { panic(err) } + assert folder_contents.len == 0 + os.rmdir(folder) or { panic(err) } + folder_exists := os.is_dir(folder) + assert folder_exists == false +} + +fn walk_callback(file string) { + if file == '.' || file == '..' { + return + } + assert file == 'test_walk' + os.path_separator + 'test1' +} + +fn test_walk() { + folder := 'test_walk' + os.mkdir(folder) or { panic(err) } + file1 := folder + os.path_separator + 'test1' + os.write_file(file1, 'test-1') or { panic(err) } + os.walk(folder, walk_callback) + os.rm(file1) or { panic(err) } + os.rmdir(folder) or { panic(err) } +} + +fn test_cp() { + old_file_name := 'cp_example.txt' + new_file_name := 'cp_new_example.txt' + os.write_file(old_file_name, 'Test data 1 2 3, V is awesome #$%^[]!~⭐') or { panic(err) } + os.cp(old_file_name, new_file_name) or { panic('$err') } + old_file := os.read_file(old_file_name) or { panic(err) } + new_file := os.read_file(new_file_name) or { panic(err) } + assert old_file == new_file + os.rm(old_file_name) or { panic(err) } + os.rm(new_file_name) or { panic(err) } +} + +fn test_mv() { + work_dir := os.join_path(os.getwd(), 'mv_test') + os.mkdir_all(work_dir) or { panic(err) } + // Setup test files + tfile1 := os.join_path(work_dir, 'file') + tfile2 := os.join_path(work_dir, 'file.test') + tfile3 := os.join_path(work_dir, 'file.3') + tfile_content := 'temporary file' + os.write_file(tfile1, tfile_content) or { panic(err) } + os.write_file(tfile2, tfile_content) or { panic(err) } + // Setup test dirs + tdir1 := os.join_path(work_dir, 'dir') + tdir2 := os.join_path(work_dir, 'dir2') + tdir3 := os.join_path(work_dir, 'dir3') + os.mkdir(tdir1) or { panic(err) } + os.mkdir(tdir2) or { panic(err) } + // Move file with no extension to dir + os.mv(tfile1, tdir1) or { panic(err) } + mut expected := os.join_path(tdir1, 'file') + assert os.exists(expected) + assert !os.is_dir(expected) + // Move dir with contents to other dir + os.mv(tdir1, tdir2) or { panic(err) } + expected = os.join_path(tdir2, 'dir') + assert os.exists(expected) + assert os.is_dir(expected) + expected = os.join_path(tdir2, 'dir', 'file') + assert os.exists(expected) + assert !os.is_dir(expected) + // Move dir with contents to other dir (by renaming) + os.mv(os.join_path(tdir2, 'dir'), tdir3) or { panic(err) } + expected = tdir3 + assert os.exists(expected) + assert os.is_dir(expected) + assert os.is_dir_empty(tdir2) + // Move file with extension to dir + os.mv(tfile2, tdir2) or { panic(err) } + expected = os.join_path(tdir2, 'file.test') + assert os.exists(expected) + assert !os.is_dir(expected) + // Move file to dir (by renaming) + os.mv(os.join_path(tdir2, 'file.test'), tfile3) or { panic(err) } + expected = tfile3 + assert os.exists(expected) + assert !os.is_dir(expected) +} + +fn test_cp_all() { + // fileX -> dir/fileX + // NB: clean up of the files happens inside the cleanup_leftovers function + os.write_file('ex1.txt', 'wow!') or { panic(err) } + os.mkdir('ex') or { panic(err) } + os.cp_all('ex1.txt', 'ex', false) or { panic(err) } + old := os.read_file('ex1.txt') or { panic(err) } + new := os.read_file('ex/ex1.txt') or { panic(err) } + assert old == new + os.mkdir('ex/ex2') or { panic(err) } + os.write_file('ex2.txt', 'great!') or { panic(err) } + os.cp_all('ex2.txt', 'ex/ex2', false) or { panic(err) } + old2 := os.read_file('ex2.txt') or { panic(err) } + new2 := os.read_file('ex/ex2/ex2.txt') or { panic(err) } + assert old2 == new2 + // recurring on dir -> local dir + os.cp_all('ex', './', true) or { panic(err) } + // regression test for executive runs with overwrite := true + os.cp_all('ex', './', true) or { panic(err) } + os.cp_all('ex', 'nonexisting', true) or { panic(err) } + assert os.exists(os.join_path('nonexisting', 'ex1.txt')) +} + +fn test_realpath_of_empty_string_works() { + assert os.real_path('') == '' +} + +fn test_realpath_non_existing() { + non_existing_path := 'sdyfuisd_non_existing_file' + rpath := os.real_path(non_existing_path) + $if windows { + // on windows, the workdir is prepended, so the result is absolute: + assert rpath.len > non_existing_path.len + } + $if !windows { + // on unix, the workdir is NOT prepended for now, so the result remains the same. + // TODO: the windows behaviour seems saner, think about normalising the unix case to do the same. + assert os.real_path(non_existing_path) == non_existing_path + } +} + +fn test_realpath_existing() { + existing_file_name := 'existing_file.txt' + existing_file := os.join_path(os.temp_dir(), existing_file_name) + os.rm(existing_file) or {} + os.write_file(existing_file, 'abc') or {} + assert os.exists(existing_file) + rpath := os.real_path(existing_file) + assert os.is_abs_path(rpath) + assert rpath.ends_with(existing_file_name) + os.rm(existing_file) or {} +} + +fn test_realpath_removes_dots() { + examples_folder := os.join_path(@VEXEROOT, 'vlib', 'v', '..', '..', 'cmd', '.', '..', + 'examples') + real_path_of_examples_folder := os.real_path(examples_folder) + assert real_path_of_examples_folder.len < examples_folder.len + assert !real_path_of_examples_folder.contains('..') +} + +fn test_realpath_absolutizes_existing_relative_paths() { + old_wd := os.getwd() + defer { + os.chdir(old_wd) or { panic(err) } + } + os.chdir(@VEXEROOT) or { panic(err) } + examples_folder := os.join_path('vlib', 'v', '..', '..', 'cmd', '.', '..', 'examples') + real_path_of_examples_folder := os.real_path(examples_folder) + assert os.is_abs_path(real_path_of_examples_folder) +} + +// TODO: think much more about whether this is desirable: +fn test_realpath_does_not_absolutize_non_existing_relative_paths() { + relative_path := os.join_path('one', 'nonexisting_folder', '..', 'something') + $if !windows { + assert os.real_path(relative_path).contains('..') + assert os.real_path(relative_path) == relative_path + } +} + +fn test_realpath_absolutepath_symlink() ? { + file_name := 'tolink_file.txt' + symlink_name := 'symlink.txt' + mut f := os.create(file_name) ? + f.close() + assert os.symlink(file_name, symlink_name) ? + rpath := os.real_path(symlink_name) + println(rpath) + assert os.is_abs_path(rpath) + assert rpath.ends_with(file_name) + os.rm(symlink_name) or {} + os.rm(file_name) or {} +} + +fn test_tmpdir() { + t := os.temp_dir() + assert t.len > 0 + assert os.is_dir(t) + tfile := t + os.path_separator + 'tmpfile.txt' + os.rm(tfile) or {} // just in case + tfile_content := 'this is a temporary file' + os.write_file(tfile, tfile_content) or { panic(err) } + tfile_content_read := os.read_file(tfile) or { panic(err) } + assert tfile_content_read == tfile_content + os.rm(tfile) or { panic(err) } +} + +fn test_is_writable_folder() { + tmp := os.temp_dir() + f := os.is_writable_folder(tmp) or { + eprintln('err: $err') + false + } + assert f +} + +fn test_make_symlink_check_is_link_and_remove_symlink() { + folder := 'tfolder' + symlink := 'tsymlink' + // windows creates a directory symlink, so delete it with rmdir() + $if windows { + os.rmdir(symlink) or {} + } $else { + os.rm(symlink) or {} + } + os.rmdir(folder) or {} + os.mkdir(folder) or { panic(err) } + folder_contents := os.ls(folder) or { panic(err) } + assert folder_contents.len == 0 + os.symlink(folder, symlink) or { panic(err) } + assert os.is_link(symlink) + $if windows { + os.rmdir(symlink) or { panic(err) } + } $else { + os.rm(symlink) or { panic(err) } + } + os.rmdir(folder) or { panic(err) } + folder_exists := os.is_dir(folder) + assert folder_exists == false + symlink_exists := os.is_link(symlink) + assert symlink_exists == false +} + +fn test_make_symlink_check_is_link_and_remove_symlink_with_file() { + file := 'tfile' + symlink := 'tsymlink' + os.rm(symlink) or {} + os.rm(file) or {} + mut f := os.create(file) or { panic(err) } + f.close() + os.symlink(file, symlink) or { panic(err) } + assert os.is_link(symlink) + os.rm(symlink) or { panic(err) } + os.rm(file) or { panic(err) } + symlink_exists := os.is_link(symlink) + assert symlink_exists == false +} + +fn test_make_hardlink_check_is_link_and_remove_hardlink_with_file() { + file := 'tfile' + symlink := 'tsymlink' + os.rm(symlink) or {} + os.rm(file) or {} + mut f := os.create(file) or { panic(err) } + f.close() + os.link(file, symlink) or { panic(err) } + assert os.exists(symlink) + os.rm(symlink) or { panic(err) } + os.rm(file) or { panic(err) } + symlink_exists := os.is_link(symlink) + assert symlink_exists == false +} + +// fn test_fork() { +// pid := os.fork() +// if pid == 0 { +// println('Child') +// } +// else { +// println('Parent') +// } +// } +// fn test_wait() { +// pid := os.fork() +// if pid == 0 { +// println('Child') +// exit(0) +// } +// else { +// cpid := os.wait() +// println('Parent') +// println(cpid) +// } +// } +fn test_symlink() { + os.mkdir('symlink') or { panic(err) } + os.symlink('symlink', 'symlink2') or { panic(err) } + assert os.exists('symlink2') + // cleanup + os.rmdir('symlink') or { panic(err) } + $if windows { + os.rmdir('symlink2') or { panic(err) } + } $else { + os.rm('symlink2') or { panic(err) } + } +} + +fn test_is_executable_writable_readable() { + file_name := 'rwxfile.exe' + mut f := os.create(file_name) or { + eprintln('failed to create file $file_name') + return + } + f.close() + $if !windows { + os.chmod(file_name, 0o600) or {} // mark as readable && writable, but NOT executable + assert os.is_writable(file_name) + assert os.is_readable(file_name) + assert !os.is_executable(file_name) + os.chmod(file_name, 0o700) or {} // mark as executable too + assert os.is_executable(file_name) + } $else { + assert os.is_writable(file_name) + assert os.is_readable(file_name) + assert os.is_executable(file_name) + } + // We finally delete the test file. + os.rm(file_name) or { panic(err) } +} + +fn test_ext() { + assert os.file_ext('file.v') == '.v' + assert os.file_ext('file') == '' +} + +fn test_is_abs() { + assert os.is_abs_path('/home/user') + assert os.is_abs_path('v/vlib') == false + $if windows { + assert os.is_abs_path('C:\\Windows\\') + } +} + +fn test_join() { + $if windows { + assert os.join_path('v', 'vlib', 'os') == 'v\\vlib\\os' + } $else { + assert os.join_path('v', 'vlib', 'os') == 'v/vlib/os' + } +} + +fn test_rmdir_all() { + mut dirs := ['some/dir', 'some/.hidden/directory'] + $if windows { + for mut d in dirs { + d = d.replace('/', '\\') + } + } + for d in dirs { + os.mkdir_all(d) or { panic(err) } + assert os.is_dir(d) + } + os.rmdir_all('some') or { assert false } + assert !os.exists('some') +} + +fn test_dir() { + $if windows { + assert os.dir('C:\\a\\b\\c') == 'C:\\a\\b' + assert os.dir('C:\\a\\b\\') == 'C:\\a\\b' + assert os.dir('C:/a/b/c') == 'C:\\a\\b' + assert os.dir('C:/a/b/') == 'C:\\a\\b' + } $else { + assert os.dir('/') == '/' + assert os.dir('/abc') == '/' + assert os.dir('/var/tmp/foo') == '/var/tmp' + assert os.dir('/var/tmp/') == '/var/tmp' + assert os.dir('C:\\a\\b\\c') == 'C:/a/b' + assert os.dir('C:\\a\\b\\') == 'C:/a/b' + } + assert os.dir('os') == '.' +} + +fn test_base() { + $if windows { + assert os.base('v\\vlib\\os') == 'os' + assert os.base('v\\vlib\\os\\') == 'os' + assert os.base('v/vlib/os') == 'os' + assert os.base('v/vlib/os/') == 'os' + } $else { + assert os.base('v/vlib/os') == 'os' + assert os.base('v/vlib/os/') == 'os' + assert os.base('v\\vlib\\os') == 'os' + assert os.base('v\\vlib\\os\\') == 'os' + } + assert os.base('filename') == 'filename' +} + +fn test_file_name() { + $if windows { + assert os.file_name('v\\vlib\\os\\os.v') == 'os.v' + assert os.file_name('v\\vlib\\os\\') == '' + assert os.file_name('v\\vlib\\os') == 'os' + } $else { + assert os.file_name('v/vlib/os/os.v') == 'os.v' + assert os.file_name('v/vlib/os/') == '' + assert os.file_name('v/vlib/os') == 'os' + } + assert os.file_name('filename') == 'filename' +} + +fn test_uname() { + u := os.uname() + assert u.sysname.len > 0 + assert u.nodename.len > 0 + assert u.release.len > 0 + assert u.version.len > 0 + assert u.machine.len > 0 +} + +// tests for write_file_array and read_file_array: +const ( + maxn = 3 +) + +struct IntPoint { + x int + y int +} + +fn test_write_file_array_bytes() { + fpath := './abytes.bin' + mut arr := []byte{len: maxn} + for i in 0 .. maxn { + arr[i] = 65 + byte(i) + } + os.write_file_array(fpath, arr) or { panic(err) } + rarr := os.read_bytes(fpath) or { panic(err) } + assert arr == rarr + // eprintln(arr.str()) + // eprintln(rarr.str()) +} + +fn test_write_file_array_structs() { + fpath := './astructs.bin' + mut arr := []IntPoint{len: maxn} + for i in 0 .. maxn { + arr[i] = IntPoint{65 + i, 65 + i + 10} + } + os.write_file_array(fpath, arr) or { panic(err) } + rarr := os.read_file_array(fpath) + assert rarr == arr + assert rarr.len == maxn + // eprintln( rarr.str().replace('\n', ' ').replace('},', '},\n')) +} + +fn test_stdout_capture() { + /* + mut cmd := os.Command{ + path:'cat' + redirect_stdout: true +} +cmd.start() +for !cmd.eof { + line := cmd.read_line() + println('line="$line"') +} +cmd.close() + */ +} + +fn test_posix_set_bit() { + $if windows { + assert true + } $else { + fpath := '/tmp/permtest' + os.create(fpath) or { panic("Couldn't create file") } + os.chmod(fpath, 0o0777) or { panic(err) } + c_fpath := &char(fpath.str) + mut s := C.stat{} + unsafe { + C.stat(c_fpath, &s) + } + // Take the permissions part of the mode + mut mode := u32(s.st_mode) & 0o0777 + assert mode == 0o0777 + // `chmod u-r` + os.posix_set_permission_bit(fpath, os.s_irusr, false) + unsafe { + C.stat(c_fpath, &s) + } + mode = u32(s.st_mode) & 0o0777 + assert mode == 0o0377 + // `chmod u+r` + os.posix_set_permission_bit(fpath, os.s_irusr, true) + unsafe { + C.stat(c_fpath, &s) + } + mode = u32(s.st_mode) & 0o0777 + assert mode == 0o0777 + // NB: setting the sticky bit is platform dependend + // `chmod -s -g -t` + os.posix_set_permission_bit(fpath, os.s_isuid, false) + os.posix_set_permission_bit(fpath, os.s_isgid, false) + os.posix_set_permission_bit(fpath, os.s_isvtx, false) + unsafe { + C.stat(c_fpath, &s) + } + mode = u32(s.st_mode) & 0o0777 + assert mode == 0o0777 + // `chmod g-w o-w` + os.posix_set_permission_bit(fpath, os.s_iwgrp, false) + os.posix_set_permission_bit(fpath, os.s_iwoth, false) + unsafe { + C.stat(c_fpath, &s) + } + mode = u32(s.st_mode) & 0o7777 + assert mode == 0o0755 + os.rm(fpath) or {} + } +} + +fn test_exists_in_system_path() { + assert os.exists_in_system_path('') == false + $if windows { + assert os.exists_in_system_path('cmd.exe') + return + } + assert os.exists_in_system_path('ls') +} + +fn test_truncate() { + filename := './test_trunc.txt' + hello := 'hello world!' + mut f := os.create(filename) or { panic(err) } + f.write_string(hello) or { panic(err) } + f.close() + assert hello.len == os.file_size(filename) + newlen := u64(40000) + os.truncate(filename, newlen) or { panic(err) } + assert newlen == os.file_size(filename) + os.rm(filename) or { panic(err) } +} + +fn test_hostname() { + assert os.hostname().len > 2 +} + +fn test_glob() { + os.mkdir('test_dir') or { panic(err) } + for i in 0 .. 4 { + if i == 3 { + mut f := os.create('test_dir/test0_another') or { panic(err) } + f.close() + mut f1 := os.create('test_dir/test') or { panic(err) } + f1.close() + } else { + mut f := os.create('test_dir/test' + i.str()) or { panic(err) } + f.close() + } + } + files := os.glob('test_dir/t*') or { panic(err) } + assert files.len == 5 + assert os.base(files[0]) == 'test' + + for i in 0 .. 3 { + os.rm('test_dir/test' + i.str()) or { panic(err) } + } + os.rm('test_dir/test0_another') or { panic(err) } + os.rm('test_dir/test') or { panic(err) } + os.rmdir_all('test_dir') or { panic(err) } +} + +fn test_utime() { + filename := './test_utime.txt' + hello := 'hello world!' + mut f := os.create(filename) or { panic(err) } + defer { + f.close() + os.rm(filename) or { panic(err) } + } + f.write_string(hello) or { panic(err) } + atime := time.now().add_days(2).unix_time() + mtime := time.now().add_days(4).unix_time() + os.utime(filename, int(atime), int(mtime)) or { panic(err) } + assert os.file_last_mod_unix(filename) == mtime +} diff --git a/v_windows/v/vlib/os/os_windows.c.v b/v_windows/v/vlib/os/os_windows.c.v new file mode 100644 index 0000000..a920601 --- /dev/null +++ b/v_windows/v/vlib/os/os_windows.c.v @@ -0,0 +1,544 @@ +module os + +import strings + +#flag windows -l advapi32 +#include +#include + +// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw +fn C.CreateSymbolicLinkW(&u16, &u16, u32) int + +// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw +fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int + +fn C._getpid() int + +pub const ( + path_separator = '\\' + path_delimiter = ';' +) + +// Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// A handle to an object. +pub type HANDLE = voidptr +pub type HMODULE = voidptr + +// win: FILETIME +// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime +struct Filetime { + dw_low_date_time u32 + dw_high_date_time u32 +} + +// win: WIN32_FIND_DATA +// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw +struct Win32finddata { +mut: + dw_file_attributes u32 + ft_creation_time Filetime + ft_last_access_time Filetime + ft_last_write_time Filetime + n_file_size_high u32 + n_file_size_low u32 + dw_reserved0 u32 + dw_reserved1 u32 + c_file_name [260]u16 // max_path_len = 260 + c_alternate_file_name [14]u16 // 14 + dw_file_type u32 + dw_creator_type u32 + w_finder_flags u16 +} + +struct ProcessInformation { +mut: + h_process voidptr + h_thread voidptr + dw_process_id u32 + dw_thread_id u32 +} + +struct StartupInfo { +mut: + cb u32 + lp_reserved &u16 + lp_desktop &u16 + lp_title &u16 + dw_x u32 + dw_y u32 + dw_x_size u32 + dw_y_size u32 + dw_x_count_chars u32 + dw_y_count_chars u32 + dw_fill_attributes u32 + dw_flags u32 + w_show_window u16 + cb_reserved2 u16 + lp_reserved2 &byte + h_std_input voidptr + h_std_output voidptr + h_std_error voidptr +} + +struct SecurityAttributes { +mut: + n_length u32 + lp_security_descriptor voidptr + b_inherit_handle bool +} + +struct C._utimbuf { + actime int + modtime int +} + +fn C._utime(&char, voidptr) int + +fn init_os_args_wide(argc int, argv &&byte) []string { + mut args_ := []string{} + for i in 0 .. argc { + args_ << unsafe { string_from_wide(&u16(argv[i])) } + } + return args_ +} + +fn native_glob_pattern(pattern string, mut matches []string) ? { + $if debug { + // FindFirstFile() and FindNextFile() both have a globbing function. + // Unfortunately this is not as pronounced as under Unix, but should provide some functionality + eprintln('os.glob() does not have all the features on Windows as it has on Unix operating systems') + } + mut find_file_data := Win32finddata{} + wpattern := pattern.replace('/', '\\').to_wide() + h_find_files := C.FindFirstFile(wpattern, voidptr(&find_file_data)) + + defer { + C.FindClose(h_find_files) + } + + if h_find_files == C.INVALID_HANDLE_VALUE { + return error('os.glob(): Could not get a file handle: ' + + get_error_msg(int(C.GetLastError()))) + } + + // save first finding + fname := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if fname !in ['.', '..'] { + mut fp := fname.replace('\\', '/') + if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 { + fp += '/' + } + matches << fp + } + + // check and save next findings + for i := 0; C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0; i++ { + filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if filename in ['.', '..'] { + continue + } + mut fpath := filename.replace('\\', '/') + if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 { + fpath += '/' + } + matches << fpath + } +} + +pub fn utime(path string, actime int, modtime int) ? { + mut u := C._utimbuf{actime, modtime} + if C._utime(&char(path.str), voidptr(&u)) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } +} + +pub fn ls(path string) ?[]string { + mut find_file_data := Win32finddata{} + mut dir_files := []string{} + // We can also check if the handle is valid. but using is_dir instead + // h_find_dir := C.FindFirstFile(path.str, &find_file_data) + // if (invalid_handle_value == h_find_dir) { + // return dir_files + // } + // C.FindClose(h_find_dir) + if !is_dir(path) { + return error('ls() couldnt open dir "$path": directory does not exist') + } + // NOTE: Should eventually have path struct & os dependant path seperator (eg os.PATH_SEPERATOR) + // we need to add files to path eg. c:\windows\*.dll or :\windows\* + path_files := '$path\\*' + // NOTE:TODO: once we have a way to convert utf16 wide character to utf8 + // we should use FindFirstFileW and FindNextFileW + h_find_files := C.FindFirstFile(path_files.to_wide(), voidptr(&find_file_data)) + first_filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if first_filename != '.' && first_filename != '..' { + dir_files << first_filename + } + for C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0 { + filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if filename != '.' && filename != '..' { + dir_files << filename.clone() + } + } + C.FindClose(h_find_files) + return dir_files +} + +/* +pub fn is_dir(path string) bool { + _path := path.replace('/', '\\') + attr := C.GetFileAttributesW(_path.to_wide()) + if int(attr) == int(C.INVALID_FILE_ATTRIBUTES) { + return false + } + if (int(attr) & C.FILE_ATTRIBUTE_DIRECTORY) != 0 { + return true + } + return false +} +*/ +// mkdir creates a new directory with the specified path. +pub fn mkdir(path string) ?bool { + if path == '.' { + return true + } + apath := real_path(path) + if !C.CreateDirectory(apath.to_wide(), 0) { + return error('mkdir failed for "$apath", because CreateDirectory returned: ' + + get_error_msg(int(C.GetLastError()))) + } + return true +} + +// Ref - https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019 +// get_file_handle retrieves the operating-system file handle that is associated with the specified file descriptor. +pub fn get_file_handle(path string) HANDLE { + cfile := vfopen(path, 'rb') or { return HANDLE(invalid_handle_value) } + handle := HANDLE(C._get_osfhandle(fileno(cfile))) // CreateFile? - hah, no -_- + return handle +} + +// Ref - https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea +// get_module_filename retrieves the fully qualified path for the file that contains the specified module. +// The module must have been loaded by the current process. +pub fn get_module_filename(handle HANDLE) ?string { + unsafe { + mut sz := 4096 // Optimized length + mut buf := &u16(malloc_noscan(4096)) + for { + status := int(C.GetModuleFileNameW(handle, voidptr(&buf), sz)) + match status { + success { + return string_from_wide2(buf, sz) + } + else { + // Must handled with GetLastError and converted by FormatMessage + return error('Cannot get file name from handle') + } + } + } + } + panic('this should be unreachable') // TODO remove unreachable after loop +} + +// Ref - https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea#parameters +const ( + format_message_allocate_buffer = 0x00000100 + format_message_argument_array = 0x00002000 + format_message_from_hmodule = 0x00000800 + format_message_from_string = 0x00000400 + format_message_from_system = 0x00001000 + format_message_ignore_inserts = 0x00000200 +) + +// Ref - winnt.h +const ( + sublang_neutral = 0x00 + sublang_default = 0x01 + lang_neutral = sublang_neutral +) + +// Ref - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--12000-15999- +const ( + max_error_code = 15841 // ERROR_API_UNAVAILABLE +) + +// ptr_win_get_error_msg return string (voidptr) +// representation of error, only for windows. +fn ptr_win_get_error_msg(code u32) voidptr { + mut buf := voidptr(0) + // Check for code overflow + if code > u32(os.max_error_code) { + return buf + } + C.FormatMessage(os.format_message_allocate_buffer | os.format_message_from_system | os.format_message_ignore_inserts, + 0, code, C.MAKELANGID(os.lang_neutral, os.sublang_default), voidptr(&buf), 0, + 0) + return buf +} + +// get_error_msg return error code representation in string. +pub fn get_error_msg(code int) string { + if code < 0 { // skip negative + return '' + } + ptr_text := ptr_win_get_error_msg(u32(code)) + if ptr_text == 0 { // compare with null + return '' + } + return unsafe { string_from_wide(ptr_text) } +} + +// execute starts the specified command, waits for it to complete, and returns its output. +pub fn execute(cmd string) Result { + if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + return Result{ + exit_code: -1 + output: ';, &&, || and \\n are not allowed in shell commands' + } + } + mut child_stdin := &u32(0) + mut child_stdout_read := &u32(0) + mut child_stdout_write := &u32(0) + mut sa := SecurityAttributes{} + sa.n_length = sizeof(C.SECURITY_ATTRIBUTES) + sa.b_inherit_handle = true + create_pipe_ok := C.CreatePipe(voidptr(&child_stdout_read), voidptr(&child_stdout_write), + voidptr(&sa), 0) + if !create_pipe_ok { + error_num := int(C.GetLastError()) + error_msg := get_error_msg(error_num) + return Result{ + exit_code: error_num + output: 'exec failed (CreatePipe): $error_msg' + } + } + set_handle_info_ok := C.SetHandleInformation(child_stdout_read, C.HANDLE_FLAG_INHERIT, + 0) + if !set_handle_info_ok { + error_num := int(C.GetLastError()) + error_msg := get_error_msg(error_num) + return Result{ + exit_code: error_num + output: 'exec failed (SetHandleInformation): $error_msg' + } + } + proc_info := ProcessInformation{} + start_info := StartupInfo{ + lp_reserved2: 0 + lp_reserved: 0 + lp_desktop: 0 + lp_title: 0 + cb: sizeof(C.PROCESS_INFORMATION) + h_std_input: child_stdin + h_std_output: child_stdout_write + h_std_error: child_stdout_write + dw_flags: u32(C.STARTF_USESTDHANDLES) + } + command_line := [32768]u16{} + C.ExpandEnvironmentStringsW(cmd.to_wide(), voidptr(&command_line), 32768) + create_process_ok := C.CreateProcessW(0, &command_line[0], 0, 0, C.TRUE, 0, 0, 0, + voidptr(&start_info), voidptr(&proc_info)) + if !create_process_ok { + error_num := int(C.GetLastError()) + error_msg := get_error_msg(error_num) + return Result{ + exit_code: error_num + output: 'exec failed (CreateProcess) with code $error_num: $error_msg cmd: $cmd' + } + } + C.CloseHandle(child_stdin) + C.CloseHandle(child_stdout_write) + buf := [4096]byte{} + mut bytes_read := u32(0) + mut read_data := strings.new_builder(1024) + for { + mut result := false + unsafe { + result = C.ReadFile(child_stdout_read, &buf[0], 1000, voidptr(&bytes_read), + 0) + read_data.write_ptr(&buf[0], int(bytes_read)) + } + if result == false || int(bytes_read) == 0 { + break + } + } + soutput := read_data.str().trim_space() + unsafe { read_data.free() } + exit_code := u32(0) + C.WaitForSingleObject(proc_info.h_process, C.INFINITE) + C.GetExitCodeProcess(proc_info.h_process, voidptr(&exit_code)) + C.CloseHandle(proc_info.h_process) + C.CloseHandle(proc_info.h_thread) + return Result{ + output: soutput + exit_code: int(exit_code) + } +} + +pub fn symlink(origin string, target string) ?bool { + // this is a temporary fix for TCC32 due to runtime error + // TODO: find the cause why TCC32 for Windows does not work without the compiletime option + $if x64 || x32 { + mut flags := 0 + if is_dir(origin) { + flags ^= 1 + } + + flags ^= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE + res := C.CreateSymbolicLinkW(target.to_wide(), origin.to_wide(), flags) + + // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10 + if res != 1 { + return error(get_error_msg(int(C.GetLastError()))) + } + if !exists(target) { + return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist') + } + return true + } + return false +} + +pub fn link(origin string, target string) ?bool { + res := C.CreateHardLinkW(target.to_wide(), origin.to_wide(), C.NULL) + // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10 + if res != 1 { + return error(get_error_msg(int(C.GetLastError()))) + } + if !exists(target) { + return error('C.CreateHardLinkW reported success, but link still does not exist') + } + return true +} + +pub fn (mut f File) close() { + if !f.is_opened { + return + } + f.is_opened = false + C.fflush(f.cfile) + C.fclose(f.cfile) +} + +pub struct ExceptionRecord { +pub: + // status_ constants + code u32 + flags u32 + record &ExceptionRecord + address voidptr + param_count u32 + // params []voidptr +} + +pub struct ContextRecord { + // TODO +} + +pub struct ExceptionPointers { +pub: + exception_record &ExceptionRecord + context_record &ContextRecord +} + +pub type VectoredExceptionHandler = fn (&ExceptionPointers) u32 + +// This is defined in builtin because we use vectored exception handling +// for our unhandled exception handler on windows +// As a result this definition is commented out to prevent +// duplicate definitions from displeasing the compiler +// fn C.AddVectoredExceptionHandler(u32, VectoredExceptionHandler) +pub fn add_vectored_exception_handler(first bool, handler VectoredExceptionHandler) { + C.AddVectoredExceptionHandler(u32(first), C.PVECTORED_EXCEPTION_HANDLER(handler)) +} + +// this is defined in builtin_windows.c.v in builtin +// fn C.IsDebuggerPresent() bool +pub fn debugger_present() bool { + return C.IsDebuggerPresent() +} + +pub fn uname() Uname { + sys_and_ver := execute('cmd /c ver').output.split('[') + nodename := hostname() + machine := getenv('PROCESSOR_ARCHITECTURE') + return Uname{ + sysname: sys_and_ver[0].trim_space() + nodename: nodename + release: sys_and_ver[1].replace(']', '') + version: sys_and_ver[0] + '[' + sys_and_ver[1] + machine: machine + } +} + +pub fn hostname() string { + hostname := [255]u16{} + size := u32(255) + res := C.GetComputerNameW(&hostname[0], &size) + if !res { + return get_error_msg(int(C.GetLastError())) + } + return unsafe { string_from_wide(&hostname[0]) } +} + +pub fn loginname() string { + loginname := [255]u16{} + size := u32(255) + res := C.GetUserNameW(&loginname[0], &size) + if !res { + return get_error_msg(int(C.GetLastError())) + } + return unsafe { string_from_wide(&loginname[0]) } +} + +// `is_writable_folder` - `folder` exists and is writable to the process +pub fn is_writable_folder(folder string) ?bool { + if !exists(folder) { + return error('`$folder` does not exist') + } + if !is_dir(folder) { + return error('`folder` is not a folder') + } + tmp_perm_check := join_path(folder, 'tmp_perm_check_pid_' + getpid().str()) + mut f := open_file(tmp_perm_check, 'w+', 0o700) or { + return error('cannot write to folder $folder: $err') + } + f.close() + rm(tmp_perm_check) ? + return true +} + +[inline] +pub fn getpid() int { + return C._getpid() +} + +[inline] +pub fn getppid() int { + return 0 +} + +[inline] +pub fn getuid() int { + return 0 +} + +[inline] +pub fn geteuid() int { + return 0 +} + +[inline] +pub fn getgid() int { + return 0 +} + +[inline] +pub fn getegid() int { + return 0 +} + +pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) { + // windows has no concept of a permission mask, so do nothing +} diff --git a/v_windows/v/vlib/os/process.c.v b/v_windows/v/vlib/os/process.c.v new file mode 100644 index 0000000..c7e2b98 --- /dev/null +++ b/v_windows/v/vlib/os/process.c.v @@ -0,0 +1,248 @@ +module os + +// signal_kill - kills the process, after that it is no longer running +pub fn (mut p Process) signal_kill() { + if p.status !in [.running, .stopped] { + return + } + p._signal_kill() + p.status = .aborted + return +} + +// signal_pgkill - kills the whole process group +pub fn (mut p Process) signal_pgkill() { + if p.status !in [.running, .stopped] { + return + } + p._signal_pgkill() + return +} + +// signal_stop - stops the process, you can resume it with p.signal_continue() +pub fn (mut p Process) signal_stop() { + if p.status != .running { + return + } + p._signal_stop() + p.status = .stopped + return +} + +// signal_continue - tell a stopped process to continue/resume its work +pub fn (mut p Process) signal_continue() { + if p.status != .stopped { + return + } + p._signal_continue() + p.status = .running + return +} + +// wait - wait for a process to finish. +// NB: You have to call p.wait(), otherwise a finished process +// would get to a zombie state, and its resources will not get +// released fully, until its parent process exits. +// NB: This call will block the calling process until the child +// process is finished. +pub fn (mut p Process) wait() { + if p.status == .not_started { + p._spawn() + } + if p.status !in [.running, .stopped] { + return + } + p._wait() + return +} + +// close - free the OS resources associated with the process. +// Can be called multiple times, but will free the resources just once. +// This sets the process state to .closed, which is final. +pub fn (mut p Process) close() { + if p.status in [.not_started, .closed] { + return + } + p.status = .closed + $if !windows { + for i in 0 .. 3 { + if p.stdio_fd[i] != 0 { + fd_close(p.stdio_fd[i]) + } + } + } +} + +[unsafe] +pub fn (mut p Process) free() { + p.close() + unsafe { + p.filename.free() + p.err.free() + p.args.free() + p.env.free() + } +} + +// +// _spawn - should not be called directly, but only by p.run()/p.wait() . +// It encapsulates the fork/execve mechanism that allows the +// asynchronous starting of the new child process. +fn (mut p Process) _spawn() int { + if !p.env_is_custom { + p.env = []string{} + current_environment := environ() + for k, v in current_environment { + p.env << '$k=$v' + } + } + mut pid := 0 + $if windows { + pid = p.win_spawn_process() + } $else { + pid = p.unix_spawn_process() + } + p.pid = pid + p.status = .running + return 0 +} + +// is_alive - query whether the process p.pid is still alive +pub fn (mut p Process) is_alive() bool { + if p.status in [.running, .stopped] { + return p._is_alive() + } + return false +} + +// +pub fn (mut p Process) set_redirect_stdio() { + p.use_stdio_ctl = true + return +} + +pub fn (mut p Process) stdin_write(s string) { + p._check_redirection_call('stdin_write') + $if windows { + p.win_write_string(0, s) + } $else { + fd_write(p.stdio_fd[0], s) + } +} + +// will read from stdout pipe, will only return when EOF (end of file) or data +// means this will block unless there is data +pub fn (mut p Process) stdout_slurp() string { + p._check_redirection_call('stdout_slurp') + $if windows { + return p.win_slurp(1) + } $else { + return fd_slurp(p.stdio_fd[1]).join('') + } +} + +// read from stderr pipe, wait for data or EOF +pub fn (mut p Process) stderr_slurp() string { + p._check_redirection_call('stderr_slurp') + $if windows { + return p.win_slurp(2) + } $else { + return fd_slurp(p.stdio_fd[2]).join('') + } +} + +// read from stdout, return if data or not +pub fn (mut p Process) stdout_read() string { + p._check_redirection_call('stdout_read') + $if windows { + s, _ := p.win_read_string(1, 4096) + return s + } $else { + s, _ := fd_read(p.stdio_fd[1], 4096) + return s + } +} + +pub fn (mut p Process) stderr_read() string { + p._check_redirection_call('stderr_read') + $if windows { + s, _ := p.win_read_string(2, 4096) + return s + } $else { + s, _ := fd_read(p.stdio_fd[2], 4096) + return s + } +} + +// _check_redirection_call - should be called just by stdxxx methods +fn (mut p Process) _check_redirection_call(fn_name string) { + if !p.use_stdio_ctl { + panic('Call p.set_redirect_stdio() before calling p.$fn_name') + } + if p.status == .not_started { + panic('Call p.${fn_name}() after you have called p.run()') + } +} + +// _signal_stop - should not be called directly, except by p.signal_stop +fn (mut p Process) _signal_stop() { + $if windows { + p.win_stop_process() + } $else { + p.unix_stop_process() + } +} + +// _signal_continue - should not be called directly, just by p.signal_continue +fn (mut p Process) _signal_continue() { + $if windows { + p.win_resume_process() + } $else { + p.unix_resume_process() + } +} + +// _signal_kill - should not be called directly, except by p.signal_kill +fn (mut p Process) _signal_kill() { + $if windows { + p.win_kill_process() + } $else { + p.unix_kill_process() + } +} + +// _signal_pgkill - should not be called directly, except by p.signal_pgkill +fn (mut p Process) _signal_pgkill() { + $if windows { + p.win_kill_pgroup() + } $else { + p.unix_kill_pgroup() + } +} + +// _wait - should not be called directly, except by p.wait() +fn (mut p Process) _wait() { + $if windows { + p.win_wait() + } $else { + p.unix_wait() + } +} + +// _is_alive - should not be called directly, except by p.is_alive() +fn (mut p Process) _is_alive() bool { + $if windows { + return p.win_is_alive() + } $else { + return p.unix_is_alive() + } +} + +// run - starts the new process +pub fn (mut p Process) run() { + if p.status != .not_started { + return + } + p._spawn() + return +} diff --git a/v_windows/v/vlib/os/process.js.v b/v_windows/v/vlib/os/process.js.v new file mode 100644 index 0000000..dc15c8b --- /dev/null +++ b/v_windows/v/vlib/os/process.js.v @@ -0,0 +1,117 @@ +module os + +#const $child_process = require('child_process') + +// new_process - create a new process descriptor +// NB: new does NOT start the new process. +// That is done because you may want to customize it first, +// by calling different set_ methods on it. +// In order to start it, call p.run() or p.wait() +pub fn new_process(filename string) &Process { + return &Process{ + filename: filename + stdio_fd: [-1, -1, -1]! + } +} + +fn (mut p Process) spawn_internal() { + #p.val.pid = $child_process.spawn( + #p.val.filename+'', + #p.val.args.arr.map((x) => x.valueOf() + ''), + #{ + #env: (p.val.env_is_custom ? p.val.env : $process.env), + #}) + #p.val.pid.on('error', function (err) { builtin.panic('Failed to start subprocess') }) + + p.status = .running + // todo(playX): stderr,stdin + if p.use_stdio_ctl { + #p.val.pid.stdout.pipe(process.stdout) + #p.val.pid.stdin.pipe(process.stdin) + #p.val.pid.stderr.pipe(process.stderr) + } +} + +pub fn (mut p Process) run() { + if p.status != .not_started { + return + } + p.spawn_internal() + return +} + +pub fn (mut p Process) signal_kill() { + if p.status !in [.running, .stopped] { + return + } + #p.val.pid.kill('SIGKILL'); + + p.status = .aborted +} + +pub fn (mut p Process) signal_stop() { + if p.status !in [.running, .stopped] { + return + } + #p.val.pid.kill('SIGSTOP'); + + p.status = .aborted +} + +pub fn (mut p Process) signal_continue() { + if p.status != .stopped { + return + } + #p.val.pid.kill('SIGCONT'); + + p.status = .running + return +} + +pub fn (mut p Process) wait() { + if p.status == .not_started { + p.spawn_internal() + } + if p.status !in [.running, .stopped] { + return + } + + p.wait_internal() + return +} + +fn (mut p Process) wait_internal() { + #p.val.pid.on('exit', function (code) { console.log(code) }) +} + +pub fn (mut p Process) set_redirect_stdio() { + p.use_stdio_ctl = true + return +} + +pub fn (mut p Process) stdin_write(s string) { + p.check_redirection_call('stdin_write') + #p.val.pid.stdin.write(s) +} + +// todo(playX): probably does not work + +// will read from stdout pipe, will only return when EOF (end of file) or data +// means this will block unless there is data +pub fn (mut p Process) stdout_slurp() string { + p.check_redirection_call('stdout_slurp') + mut res := '' + #p.val.pid.stdout.on('data', function (data) { res = new builtin.string(data) }) + + return res +} + +// _check_redirection_call - should be called just by stdxxx methods +fn (mut p Process) check_redirection_call(fn_name string) { + if !p.use_stdio_ctl { + panic('Call p.set_redirect_stdio() before calling p.$fn_name') + } + if p.status == .not_started { + panic('Call p.${fn_name}() after you have called p.run()') + } +} diff --git a/v_windows/v/vlib/os/process.v b/v_windows/v/vlib/os/process.v new file mode 100644 index 0000000..3f88398 --- /dev/null +++ b/v_windows/v/vlib/os/process.v @@ -0,0 +1,70 @@ +module os + +// ProcessState.not_started - the process has not yet started +// ProcessState.running - the process is currently running +// ProcessState.stopped - the process was running, but was stopped temporarily +// ProcessState.exited - the process has finished/exited +// ProcessState.aborted - the process was terminated by a signal +// ProcessState.closed - the process resources like opened file descriptors were freed/discarded, final state. +pub enum ProcessState { + not_started + running + stopped + exited + aborted + closed +} + +[heap] +pub struct Process { +pub: + filename string // the process's command file path +pub mut: + pid int // the PID of the process + code int = -1 + // the exit code of the process, != -1 *only* when status is .exited *and* the process was not aborted + status ProcessState = .not_started + // the current status of the process + err string // if the process fails, contains the reason why + args []string // the arguments that the command takes + env_is_custom bool // true, when the environment was customized with .set_environment + env []string // the environment with which the process was started (list of 'var=val') + use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp() + use_pgroup bool // when true, the process will create a new process group, enabling .signal_pgkill() + stdio_fd [3]int // the stdio file descriptors for the child process, used only by the nix implementation + wdata voidptr // the WProcess; used only by the windows implementation +} + +// new_process - create a new process descriptor +// NB: new does NOT start the new process. +// That is done because you may want to customize it first, +// by calling different set_ methods on it. +// In order to start it, call p.run() or p.wait() +pub fn new_process(filename string) &Process { + return &Process{ + filename: filename + stdio_fd: [-1, -1, -1]! + } +} + +// set_args - set the arguments for the new process +pub fn (mut p Process) set_args(pargs []string) { + if p.status != .not_started { + return + } + p.args = pargs + return +} + +// set_environment - set a custom environment variable mapping for the new process +pub fn (mut p Process) set_environment(envs map[string]string) { + if p.status != .not_started { + return + } + p.env_is_custom = true + p.env = []string{} + for k, v in envs { + p.env << '$k=$v' + } + return +} diff --git a/v_windows/v/vlib/os/process_nix.c.v b/v_windows/v/vlib/os/process_nix.c.v new file mode 100644 index 0000000..74140d1 --- /dev/null +++ b/v_windows/v/vlib/os/process_nix.c.v @@ -0,0 +1,146 @@ +module os + +fn C.setpgid(pid int, pgid int) int + +fn (mut p Process) unix_spawn_process() int { + mut pipeset := [6]int{} + if p.use_stdio_ctl { + _ = C.pipe(&pipeset[0]) // pipe read end 0 <- 1 pipe write end + _ = C.pipe(&pipeset[2]) // pipe read end 2 <- 3 pipe write end + _ = C.pipe(&pipeset[4]) // pipe read end 4 <- 5 pipe write end + } + pid := fork() + if pid != 0 { + // This is the parent process after the fork. + // NB: pid contains the process ID of the child process + if p.use_stdio_ctl { + p.stdio_fd[0] = pipeset[1] // store the write end of child's in + p.stdio_fd[1] = pipeset[2] // store the read end of child's out + p.stdio_fd[2] = pipeset[4] // store the read end of child's err + // close the rest of the pipe fds, the parent does not need them + fd_close(pipeset[0]) + fd_close(pipeset[3]) + fd_close(pipeset[5]) + } + return pid + } + // + // Here, we are in the child process. + // It still shares file descriptors with the parent process, + // but it is otherwise independant and can do stuff *without* + // affecting the parent process. + // + if p.use_pgroup { + C.setpgid(0, 0) + } + if p.use_stdio_ctl { + // Redirect the child standart in/out/err to the pipes that + // were created in the parent. + // Close the parent's pipe fds, the child do not need them: + fd_close(pipeset[1]) + fd_close(pipeset[2]) + fd_close(pipeset[4]) + // redirect the pipe fds to the child's in/out/err fds: + C.dup2(pipeset[0], 0) + C.dup2(pipeset[3], 1) + C.dup2(pipeset[5], 2) + // close the pipe fdsx after the redirection + fd_close(pipeset[0]) + fd_close(pipeset[3]) + fd_close(pipeset[5]) + } + execve(p.filename, p.args, p.env) or { + eprintln(err) + exit(1) + } + return 0 +} + +fn (mut p Process) unix_stop_process() { + C.kill(p.pid, C.SIGSTOP) +} + +fn (mut p Process) unix_resume_process() { + C.kill(p.pid, C.SIGCONT) +} + +fn (mut p Process) unix_kill_process() { + C.kill(p.pid, C.SIGKILL) +} + +fn (mut p Process) unix_kill_pgroup() { + C.kill(-p.pid, C.SIGKILL) +} + +fn (mut p Process) unix_wait() { + cstatus := 0 + ret := C.waitpid(p.pid, &cstatus, 0) + if ret == -1 { + p.err = posix_get_error_msg(C.errno) + return + } + pret, is_signaled := posix_wait4_to_exit_status(cstatus) + if is_signaled { + p.status = .aborted + p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})' + } else { + p.status = .exited + } + p.code = pret +} + +fn (mut p Process) unix_is_alive() bool { + cstatus := 0 + ret := C.waitpid(p.pid, &cstatus, C.WNOHANG) + if ret == -1 { + p.err = posix_get_error_msg(C.errno) + return false + } + if ret == 0 { + return true + } + pret, is_signaled := posix_wait4_to_exit_status(cstatus) + if is_signaled { + p.status = .aborted + p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})' + } else { + p.status = .exited + } + p.code = pret + return false +} + +// these are here to make v_win.c/v.c generation work in all cases: +fn (mut p Process) win_spawn_process() int { + return 0 +} + +fn (mut p Process) win_stop_process() { +} + +fn (mut p Process) win_resume_process() { +} + +fn (mut p Process) win_kill_process() { +} + +fn (mut p Process) win_kill_pgroup() { +} + +fn (mut p Process) win_wait() { +} + +fn (mut p Process) win_is_alive() bool { + return false +} + +fn (mut p Process) win_write_string(idx int, s string) { +} + +fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) { + return '', 0 +} + +fn (mut p Process) win_slurp(idx int) string { + return '' +} diff --git a/v_windows/v/vlib/os/process_test.v b/v_windows/v/vlib/os/process_test.v new file mode 100644 index 0000000..5301472 --- /dev/null +++ b/v_windows/v/vlib/os/process_test.v @@ -0,0 +1,96 @@ +import os +import time + +const ( + vexe = os.getenv('VEXE') + vroot = os.dir(vexe) + test_os_process = os.join_path(os.temp_dir(), 'v', 'test_os_process.exe') + test_os_process_source = os.join_path(vroot, 'cmd/tools/test_os_process.v') +) + +fn testsuite_begin() ? { + os.rm(test_os_process) or {} + if os.getenv('WINE_TEST_OS_PROCESS_EXE') != '' { + // Make it easier to run the test under wine emulation, by just + // prebuilding the executable with: + // v -os windows -o x.exe cmd/tools/test_os_process.v + // WINE_TEST_OS_PROCESS_EXE=x.exe ./v -os windows vlib/os/process_test.v + os.cp(os.getenv('WINE_TEST_OS_PROCESS_EXE'), test_os_process) ? + } else { + os.system('$vexe -o $test_os_process $test_os_process_source') + } + assert os.exists(test_os_process) +} + +fn test_getpid() { + pid := os.getpid() + eprintln('current pid: $pid') + assert pid != 0 +} + +fn test_run() { + mut p := os.new_process(test_os_process) + p.set_args(['-timeout_ms', '150', '-period_ms', '50']) + p.run() + assert p.status == .running + assert p.pid > 0 + assert p.pid != os.getpid() + mut i := 0 + for { + if !p.is_alive() { + break + } + $if trace_process_output ? { + os.system('ps -opid= -oppid= -ouser= -onice= -of= -ovsz= -orss= -otime= -oargs= -p $p.pid') + } + time.sleep(50 * time.millisecond) + i++ + } + p.wait() + assert p.code == 0 + assert p.status == .exited + // + eprintln('polling iterations: $i') + assert i < 50 + p.close() +} + +fn test_wait() { + mut p := os.new_process(test_os_process) + assert p.status != .exited + p.wait() + assert p.status == .exited + assert p.code == 0 + assert p.pid != os.getpid() + p.close() +} + +fn test_slurping_output() { + mut p := os.new_process(test_os_process) + p.set_args(['-timeout_ms', '500', '-period_ms', '50']) + p.set_redirect_stdio() + assert p.status != .exited + p.wait() + assert p.status == .exited + assert p.code == 0 + output := p.stdout_slurp().trim_space() + errors := p.stderr_slurp().trim_space() + p.close() + $if trace_process_output ? { + eprintln('---------------------------') + eprintln('p output: "$output"') + eprintln('p errors: "$errors"') + eprintln('---------------------------') + } + // dump(output) + assert output.contains('stdout, 1') + assert output.contains('stdout, 2') + assert output.contains('stdout, 3') + assert output.contains('stdout, 4') + // + // dump(errors) + assert errors.contains('stderr, 1') + assert errors.contains('stderr, 2') + assert errors.contains('stderr, 3') + assert errors.contains('stderr, 4') +} diff --git a/v_windows/v/vlib/os/process_windows.c.v b/v_windows/v/vlib/os/process_windows.c.v new file mode 100644 index 0000000..bcdf971 --- /dev/null +++ b/v_windows/v/vlib/os/process_windows.c.v @@ -0,0 +1,243 @@ +module os + +import strings + +fn C.GenerateConsoleCtrlEvent(event u32, pgid u32) bool +fn C.GetModuleHandleA(name &char) HMODULE +fn C.GetProcAddress(handle voidptr, procname &byte) voidptr +fn C.TerminateProcess(process HANDLE, exit_code u32) bool + +type FN_NTSuspendResume = fn (voidptr) + +fn ntdll_fn(name &char) FN_NTSuspendResume { + ntdll := C.GetModuleHandleA(c'NTDLL') + if ntdll == 0 { + return FN_NTSuspendResume(0) + } + the_fn := FN_NTSuspendResume(C.GetProcAddress(ntdll, name)) + return the_fn +} + +fn failed_cfn_report_error(ok bool, label string) { + if ok { + return + } + error_num := int(C.GetLastError()) + error_msg := get_error_msg(error_num) + eprintln('failed $label: $error_msg') + exit(1) +} + +type PU32 = &u32 + +// TODO: the PU32 alias is used to compensate for the wrong number of &/* +// that V does when doing: `h := &&u32(p)`, which should have casted +// p to a double pointer. +fn close_valid_handle(p voidptr) { + h := &PU32(p) + if *h != &u32(0) { + C.CloseHandle(*h) + unsafe { + *h = &u32(0) + } + } +} + +pub struct WProcess { +pub mut: + proc_info ProcessInformation + command_line [65536]byte + child_stdin &u32 + // + child_stdout_read &u32 + child_stdout_write &u32 + // + child_stderr_read &u32 + child_stderr_write &u32 +} + +fn (mut p Process) win_spawn_process() int { + mut wdata := &WProcess{ + child_stdin: 0 + child_stdout_read: 0 + child_stdout_write: 0 + child_stderr_read: 0 + child_stderr_write: 0 + } + p.wdata = voidptr(wdata) + mut start_info := StartupInfo{ + lp_reserved2: 0 + lp_reserved: 0 + lp_desktop: 0 + lp_title: 0 + cb: sizeof(C.PROCESS_INFORMATION) + } + if p.use_stdio_ctl { + mut sa := SecurityAttributes{} + sa.n_length = sizeof(C.SECURITY_ATTRIBUTES) + sa.b_inherit_handle = true + create_pipe_ok1 := C.CreatePipe(voidptr(&wdata.child_stdout_read), voidptr(&wdata.child_stdout_write), + voidptr(&sa), 0) + failed_cfn_report_error(create_pipe_ok1, 'CreatePipe stdout') + set_handle_info_ok1 := C.SetHandleInformation(wdata.child_stdout_read, C.HANDLE_FLAG_INHERIT, + 0) + failed_cfn_report_error(set_handle_info_ok1, 'SetHandleInformation') + create_pipe_ok2 := C.CreatePipe(voidptr(&wdata.child_stderr_read), voidptr(&wdata.child_stderr_write), + voidptr(&sa), 0) + failed_cfn_report_error(create_pipe_ok2, 'CreatePipe stderr') + set_handle_info_ok2 := C.SetHandleInformation(wdata.child_stderr_read, C.HANDLE_FLAG_INHERIT, + 0) + failed_cfn_report_error(set_handle_info_ok2, 'SetHandleInformation stderr') + start_info.h_std_input = wdata.child_stdin + start_info.h_std_output = wdata.child_stdout_write + start_info.h_std_error = wdata.child_stderr_write + start_info.dw_flags = u32(C.STARTF_USESTDHANDLES) + } + cmd := '$p.filename ' + p.args.join(' ') + C.ExpandEnvironmentStringsW(cmd.to_wide(), voidptr(&wdata.command_line[0]), 32768) + + mut creation_flags := int(C.NORMAL_PRIORITY_CLASS) + if p.use_pgroup { + creation_flags |= C.CREATE_NEW_PROCESS_GROUP + } + create_process_ok := C.CreateProcessW(0, &wdata.command_line[0], 0, 0, C.TRUE, creation_flags, + 0, 0, voidptr(&start_info), voidptr(&wdata.proc_info)) + failed_cfn_report_error(create_process_ok, 'CreateProcess') + if p.use_stdio_ctl { + close_valid_handle(&wdata.child_stdout_write) + close_valid_handle(&wdata.child_stderr_write) + } + p.pid = int(wdata.proc_info.dw_process_id) + return p.pid +} + +fn (mut p Process) win_stop_process() { + the_fn := ntdll_fn(c'NtSuspendProcess') + if voidptr(the_fn) == 0 { + return + } + wdata := &WProcess(p.wdata) + the_fn(wdata.proc_info.h_process) +} + +fn (mut p Process) win_resume_process() { + the_fn := ntdll_fn(c'NtResumeProcess') + if voidptr(the_fn) == 0 { + return + } + wdata := &WProcess(p.wdata) + the_fn(wdata.proc_info.h_process) +} + +fn (mut p Process) win_kill_process() { + wdata := &WProcess(p.wdata) + C.TerminateProcess(wdata.proc_info.h_process, 3) +} + +fn (mut p Process) win_kill_pgroup() { + wdata := &WProcess(p.wdata) + C.GenerateConsoleCtrlEvent(C.CTRL_BREAK_EVENT, wdata.proc_info.dw_process_id) + C.Sleep(20) + C.TerminateProcess(wdata.proc_info.h_process, 3) +} + +fn (mut p Process) win_wait() { + exit_code := u32(1) + mut wdata := &WProcess(p.wdata) + if p.wdata != 0 { + C.WaitForSingleObject(wdata.proc_info.h_process, C.INFINITE) + C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) + close_valid_handle(&wdata.child_stdin) + close_valid_handle(&wdata.child_stdout_write) + close_valid_handle(&wdata.child_stderr_write) + close_valid_handle(&wdata.proc_info.h_process) + close_valid_handle(&wdata.proc_info.h_thread) + } + p.status = .exited + p.code = int(exit_code) +} + +fn (mut p Process) win_is_alive() bool { + exit_code := u32(0) + wdata := &WProcess(p.wdata) + C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) + if exit_code == C.STILL_ACTIVE { + return true + } + return false +} + +/////////////// + +fn (mut p Process) win_write_string(idx int, s string) { + panic('Process.write_string $idx is not implemented yet') +} + +fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) { + panic('WProcess.read_string $idx is not implemented yet') + return '', 0 +} + +fn (mut p Process) win_slurp(idx int) string { + mut wdata := &WProcess(p.wdata) + if wdata == 0 { + return '' + } + mut rhandle := &u32(0) + if idx == 1 { + rhandle = wdata.child_stdout_read + } + if idx == 2 { + rhandle = wdata.child_stderr_read + } + if rhandle == 0 { + return '' + } + mut bytes_read := u32(0) + buf := [4096]byte{} + mut read_data := strings.new_builder(1024) + for { + mut result := false + unsafe { + result = C.ReadFile(rhandle, &buf[0], 1000, voidptr(&bytes_read), 0) + read_data.write_ptr(&buf[0], int(bytes_read)) + } + if result == false || int(bytes_read) == 0 { + break + } + } + soutput := read_data.str() + unsafe { read_data.free() } + if idx == 1 { + close_valid_handle(&wdata.child_stdout_read) + } + if idx == 2 { + close_valid_handle(&wdata.child_stderr_read) + } + return soutput +} + +// +// these are here to make v_win.c/v.c generation work in all cases: +fn (mut p Process) unix_spawn_process() int { + return 0 +} + +fn (mut p Process) unix_stop_process() { +} + +fn (mut p Process) unix_resume_process() { +} + +fn (mut p Process) unix_kill_process() { +} + +fn (mut p Process) unix_kill_pgroup() { +} + +fn (mut p Process) unix_wait() { +} + +fn (mut p Process) unix_is_alive() bool { + return false +} diff --git a/v_windows/v/vlib/os/signal.c.v b/v_windows/v/vlib/os/signal.c.v new file mode 100644 index 0000000..5dda3bd --- /dev/null +++ b/v_windows/v/vlib/os/signal.c.v @@ -0,0 +1,58 @@ +module os + +#include + +// os.Signal - enumerate possible POSIX signals and +// their integer codes. +// NB: the integer codes are given here explicitly, +// to make it easier to lookup, without needing to +// consult man pages / signal.h . + +pub enum Signal { + hup = 1 + int = 2 + quit = 3 + ill = 4 + trap = 5 + abrt = 6 + bus = 7 + fpe = 8 + kill = 9 + usr1 = 10 + segv = 11 + usr2 = 12 + pipe = 13 + alrm = 14 + term = 15 + stkflt = 16 + chld = 17 + cont = 18 + stop = 19 + tstp = 20 + ttin = 21 + ttou = 22 + urg = 23 + xcpu = 24 + xfsz = 25 + vtalrm = 26 + prof = 27 + winch = 28 + poll = 29 + pwr = 30 + sys = 31 +} + +type SignalHandler = fn (Signal) + +fn C.signal(signal int, handlercb SignalHandler) voidptr + +// signal will assign `handler` callback to be called when `signum` signal is received. +pub fn signal_opt(signum Signal, handler SignalHandler) ?SignalHandler { + C.errno = 0 + prev_handler := C.signal(int(signum), handler) + if prev_handler == C.SIG_ERR { + // errno isn't correctly set on Windows, but EINVAL is this only possible value it can take anyway + return error_with_code(posix_get_error_msg(C.EINVAL), C.EINVAL) + } + return SignalHandler(prev_handler) +} diff --git a/v_windows/v/vlib/os/signal_test.v b/v_windows/v/vlib/os/signal_test.v new file mode 100644 index 0000000..1c56540 --- /dev/null +++ b/v_windows/v/vlib/os/signal_test.v @@ -0,0 +1,35 @@ +import os + +fn former_handler(signal os.Signal) { + println('former_handler') + exit(0) +} + +fn default_handler(signal os.Signal) { + println('default_handler') + exit(0) +} + +fn test_signal_opt() { + os.signal_opt(.int, default_handler) or { assert false } +} + +fn test_signal_opt_invalid_argument() { + // Can't register a signal on SIGKILL + if _ := os.signal_opt(.kill, default_handler) { + assert false + } + os.signal_opt(.kill, default_handler) or { + assert err.msg == 'Invalid argument' + assert err.code == 22 + } +} + +fn test_signal_opt_return_former_handler() { + func1 := os.signal_opt(.term, former_handler) or { panic('unexpected error') } + assert isnil(func1) + func2 := os.signal_opt(.term, default_handler) or { panic('unexpected error') } + assert !isnil(func2) + // this should work, but makes the CI fail because of a bug in clang -fsanitize=memory + // assert func2 == former_handler +} diff --git a/v_windows/v/vlib/pg/README.md b/v_windows/v/vlib/pg/README.md new file mode 100644 index 0000000..bcdae98 --- /dev/null +++ b/v_windows/v/vlib/pg/README.md @@ -0,0 +1,25 @@ +Before you can use this module, you must first have PostgreSQL installed on +your system. To do this, find your OS and perform the actions listed. + +**NOTE**: These instructions are meant only as a convenience. If your OS is not +listed or you need extra help, [go here](https://www.postgresql.org/download/). + +### Fedora 31 +``` +sudo dnf install postgresql-server postgresql-contrib +sudo systemctl enable postgresql # to autostart on startup +sudo systemctl start postgresql +``` + +### Debian 10/11 +``` +sudo apt-get install postgresql postgresql-client +sudo systemctl enable postgresql # to autostart on startup +sudo systemctl start postgresql +``` + +### MacOSX (Homebrew) +``` +brew install postgresql +brew services start postgresql +``` diff --git a/v_windows/v/vlib/pg/oid.v b/v_windows/v/vlib/pg/oid.v new file mode 100644 index 0000000..2f8004d --- /dev/null +++ b/v_windows/v/vlib/pg/oid.v @@ -0,0 +1,171 @@ +module pg + +pub enum Oid { + t_bool = 16 + t_bytea = 17 + t_char = 18 + t_name = 19 + t_int8 = 20 + t_int2 = 21 + t_int2vector = 22 + t_int4 = 23 + t_regproc = 24 + t_text = 25 + t_oid = 26 + t_tid = 27 + t_xid = 28 + t_cid = 29 + t_vector = 30 + t_pg_ddl_command = 32 + t_pg_type = 71 + t_pg_attribute = 75 + t_pg_proc = 81 + t_pg_class = 83 + t_json = 114 + t_xml = 142 + t__xml = 143 + t_pg_node_tree = 194 + t__json = 199 + t_smgr = 210 + t_index_am_handler = 325 + t_point = 600 + t_lseg = 601 + t_path = 602 + t_box = 603 + t_polygon = 604 + t_line = 628 + t__line = 629 + t_cidr = 650 + t__cidr = 651 + t_float4 = 700 + t_float8 = 701 + t_abstime = 702 + t_reltime = 703 + t_tinterval = 704 + t_unknown = 705 + t_circle = 718 + t__circle = 719 + t_money = 790 + t__money = 791 + t_macaddr = 829 + t_inet = 869 + t__bool = 1000 + t__bytea = 1001 + t__char = 1002 + t__name = 1003 + t__int2 = 1005 + t__int2vector = 1006 + t__int4 = 1007 + t__regproc = 1008 + t__text = 1009 + t__tid = 1010 + t__xid = 1011 + t__cid = 1012 + t__vector = 1013 + t__bpchar = 1014 + t__varchar = 1015 + t__int8 = 1016 + t__point = 1017 + t__lseg = 1018 + t__path = 1019 + t__box = 1020 + t__float4 = 1021 + t__float8 = 1022 + t__abstime = 1023 + t__reltime = 1024 + t__tinterval = 1025 + t__polygon = 1027 + t__ = 1028 + t_aclitem = 1033 + t__aclitem = 1034 + t__macaddr = 1040 + t__inet = 1041 + t_bpchar = 1042 + t_varchar = 1043 + t_date = 1082 + t_time = 1083 + t_timestamp = 1114 + t__timestamp = 1115 + t__date = 1182 + t__time = 1183 + t_timestamptz = 1184 + t__timestamptz = 1185 + t_interval = 1186 + t__interval = 1187 + t__numeric = 1231 + t_pg_database = 1248 + t__cstring = 1263 + t_timetz = 1266 + t__timetz = 1270 + t_bit = 1560 + t__bit = 1561 + t_varbit = 1562 + t__varbit = 1563 + t_numeric = 1700 + t_refcursor = 1790 + t__refcursor = 2201 + t_regprocedure = 2202 + t_regoper = 2203 + t_regoperator = 2204 + t_regclass = 2205 + t_regtype = 2206 + t__regprocedure = 2207 + t__regoper = 2208 + t__regoperator = 2209 + t__regclass = 2210 + t__regtype = 2211 + t_record = 2249 + t_cstring = 2275 + t_any = 2276 + t_anyarray = 2277 + t_v = 2278 + t_trigger = 2279 + t_language_handler = 2280 + t_internal = 2281 + t_opaque = 2282 + t_anyelement = 2283 + t__record = 2287 + t_anynonarray = 2776 + t_pg_authid = 2842 + t_pg_auth_members = 2843 + t__txid_snapshot = 2949 + t_uuid = 2950 + t__uuid = 2951 + t_txid_snapshot = 2970 + t_fdw_handler = 3115 + t_pg_lsn = 3220 + t__pg_lsn = 3221 + t_tsm_handler = 3310 + t_anyenum = 3500 + t_tsvector = 3614 + t_tsquery = 3615 + t_gtsvector = 3642 + t__tsvector = 3643 + t__gtsvector = 3644 + t__tsquery = 3645 + t_regconfig = 3734 + t__regconfig = 3735 + t_regdictionary = 3769 + t__regdictionary = 3770 + t_jsonb = 3802 + t__jsonb = 3807 + t_anyrange = 3831 + t_event_trigger = 3838 + t_int4range = 3904 + t__int4range = 3905 + t_numrange = 3906 + t__numrange = 3907 + t_tsrange = 3908 + t__tsrange = 3909 + t_tstzrange = 3910 + t__tstzrange = 3911 + t_daterange = 3912 + t__daterange = 3913 + t_int8range = 3926 + t__int8range = 3927 + t_pg_shseclabel = 4066 + t_regnamespace = 4089 + t__regnamespace = 4090 + t_regrole = 4096 + t__regrole = 4097 +} diff --git a/v_windows/v/vlib/pg/orm.v b/v_windows/v/vlib/pg/orm.v new file mode 100644 index 0000000..b08992f --- /dev/null +++ b/v_windows/v/vlib/pg/orm.v @@ -0,0 +1,272 @@ +module pg + +import orm +import time +import net.conv + +// sql expr + +pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive { + query := orm.orm_select_gen(config, '"', true, '$', 1, where) + mut ret := [][]orm.Primitive{} + + res := pg_stmt_worker(db, query, orm.QueryData{}, where) ? + + for row in res { + mut row_data := []orm.Primitive{} + for i, val in row.vals { + field := str_to_primitive(val, config.types[i]) ? + row_data << field + } + ret << row_data + } + + return ret +} + +// sql stmt + +pub fn (db DB) insert(table string, data orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '"', .insert, true, '$', 1, data, orm.QueryData{}) + pg_stmt_worker(db, query, data, orm.QueryData{}) ? +} + +pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '"', .update, true, '$', 1, data, where) + pg_stmt_worker(db, query, data, where) ? +} + +pub fn (db DB) delete(table string, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '"', .delete, true, '$', 1, orm.QueryData{}, where) + pg_stmt_worker(db, query, orm.QueryData{}, where) ? +} + +pub fn (db DB) last_id() orm.Primitive { + query := 'SELECT LASTVAL();' + id := db.q_int(query) or { 0 } + return orm.Primitive(id) +} + +// table + +pub fn (db DB) create(table string, fields []orm.TableField) ? { + query := orm.orm_table_gen(table, '"', true, 0, fields, pg_type_from_v, false) or { return err } + pg_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +pub fn (db DB) drop(table string) ? { + query := 'DROP TABLE "$table";' + pg_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +// utils + +fn pg_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ?[]Row { + mut param_types := []u32{} + mut param_vals := []&char{} + mut param_lens := []int{} + mut param_formats := []int{} + + pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats, + data) + pg_stmt_binder(mut param_types, mut param_vals, mut param_lens, mut param_formats, + where) + + res := C.PQexecParams(db.conn, query.str, param_vals.len, param_types.data, param_vals.data, + param_lens.data, param_formats.data, 0) + return db.handle_error_or_result(res, 'orm_stmt_worker') +} + +fn pg_stmt_binder(mut types []u32, mut vals []&char, mut lens []int, mut formats []int, d orm.QueryData) { + for data in d.data { + pg_stmt_match(mut types, mut vals, mut lens, mut formats, data) + } +} + +fn pg_stmt_match(mut types []u32, mut vals []&char, mut lens []int, mut formats []int, data orm.Primitive) { + d := data + match data { + bool { + types << u32(Oid.t_bool) + vals << &char(&(d as bool)) + lens << int(sizeof(bool)) + formats << 1 + } + byte { + types << u32(Oid.t_char) + vals << &char(&(d as byte)) + lens << int(sizeof(byte)) + formats << 1 + } + u16 { + types << u32(Oid.t_int2) + num := conv.htn16(&data) + vals << &char(&num) + lens << int(sizeof(u16)) + formats << 1 + } + u32 { + types << u32(Oid.t_int4) + num := conv.htn32(&data) + vals << &char(&num) + lens << int(sizeof(u32)) + formats << 1 + } + u64 { + types << u32(Oid.t_int8) + num := conv.htn64(&data) + vals << &char(&num) + lens << int(sizeof(u64)) + formats << 1 + } + i8 { + types << u32(Oid.t_char) + vals << &char(&(d as i8)) + lens << int(sizeof(i8)) + formats << 1 + } + i16 { + types << u32(Oid.t_int2) + num := conv.htn16(unsafe { &u16(&data) }) + vals << &char(&num) + lens << int(sizeof(i16)) + formats << 1 + } + int { + types << u32(Oid.t_int4) + num := conv.htn32(unsafe { &u32(&data) }) + vals << &char(&num) + lens << int(sizeof(int)) + formats << 1 + } + i64 { + types << u32(Oid.t_int8) + num := conv.htn64(unsafe { &u64(&data) }) + vals << &char(&num) + lens << int(sizeof(i64)) + formats << 1 + } + f32 { + types << u32(Oid.t_float4) + vals << &char(unsafe { &f32(&(d as f32)) }) + lens << int(sizeof(f32)) + formats << 1 + } + f64 { + types << u32(Oid.t_float8) + vals << &char(unsafe { &f64(&(d as f64)) }) + lens << int(sizeof(f64)) + formats << 1 + } + string { + types << u32(Oid.t_text) + vals << data.str + lens << data.len + formats << 0 + } + time.Time { + types << u32(Oid.t_int4) + vals << &char(&int(data.unix)) + lens << int(sizeof(u32)) + formats << 1 + } + orm.InfixType { + pg_stmt_match(mut types, mut vals, mut lens, mut formats, data.right) + } + } +} + +fn pg_type_from_v(typ int) ?string { + str := match typ { + 6, 10 { + 'SMALLINT' + } + 7, 11, orm.time { + 'INT' + } + 8, 12 { + 'BIGINT' + } + 13 { + 'REAL' + } + 14 { + 'DOUBLE PRECISION' + } + orm.string { + 'TEXT' + } + -1 { + 'SERIAL' + } + else { + '' + } + } + if str == '' { + return error('Unknown type $typ') + } + return str +} + +fn str_to_primitive(str string, typ int) ?orm.Primitive { + match typ { + // bool + 16 { + return orm.Primitive(str.i8() == 1) + } + // i8 + 5 { + return orm.Primitive(str.i8()) + } + // i16 + 6 { + return orm.Primitive(str.i16()) + } + // int + 7 { + return orm.Primitive(str.int()) + } + // i64 + 8 { + return orm.Primitive(str.i64()) + } + // byte + 9 { + data := str.i8() + return orm.Primitive(*unsafe { &byte(&data) }) + } + // u16 + 10 { + data := str.i16() + return orm.Primitive(*unsafe { &u16(&data) }) + } + // u32 + 11 { + data := str.int() + return orm.Primitive(*unsafe { &u32(&data) }) + } + // u64 + 12 { + data := str.i64() + return orm.Primitive(*unsafe { &u64(&data) }) + } + // f32 + 13 { + return orm.Primitive(str.f32()) + } + // f64 + 14 { + return orm.Primitive(str.f64()) + } + orm.string { + return orm.Primitive(str) + } + orm.time { + timestamp := str.int() + return orm.Primitive(time.unix(timestamp)) + } + else {} + } + return error('Unknown field type $typ') +} diff --git a/v_windows/v/vlib/pg/pg.v b/v_windows/v/vlib/pg/pg.v new file mode 100644 index 0000000..9e2d7e7 --- /dev/null +++ b/v_windows/v/vlib/pg/pg.v @@ -0,0 +1,277 @@ +module pg + +import io + +#flag -lpq +#flag linux -I/usr/include/postgresql +#flag darwin -I/opt/local/include/postgresql11 +#flag windows -I @VEXEROOT/thirdparty/pg/include +#flag windows -L @VEXEROOT/thirdparty/pg/win64 + +// PostgreSQL Source Code +// https://doxygen.postgresql.org/libpq-fe_8h.html +#include +// for orm +#include + +pub struct DB { +mut: + conn &C.PGconn +} + +pub struct Row { +pub mut: + vals []string +} + +struct C.PGResult { +} + +pub struct Config { +pub: + host string + port int = 5432 + user string + password string + dbname string +} + +fn C.PQconnectdb(a &byte) &C.PGconn + +fn C.PQerrorMessage(voidptr) &byte + +fn C.PQgetvalue(&C.PGResult, int, int) &byte + +fn C.PQstatus(voidptr) int + +fn C.PQresultStatus(voidptr) int + +fn C.PQntuples(&C.PGResult) int + +fn C.PQnfields(&C.PGResult) int + +fn C.PQexec(voidptr, &byte) &C.PGResult + +// Params: +// const Oid *paramTypes +// const char *const *paramValues +// const int *paramLengths +// const int *paramFormats +fn C.PQexecParams(conn voidptr, command &byte, nParams int, paramTypes int, paramValues &byte, paramLengths int, paramFormats int, resultFormat int) &C.PGResult + +fn C.PQputCopyData(conn voidptr, buffer &byte, nbytes int) int + +fn C.PQputCopyEnd(voidptr, &byte) int + +fn C.PQgetCopyData(conn voidptr, buffer &&byte, async int) int + +fn C.PQclear(&C.PGResult) voidptr + +fn C.PQfreemem(voidptr) + +fn C.PQfinish(voidptr) + +// connect makes a new connection to the database server using +// the parameters from the `Config` structure, returning +// a connection error when something goes wrong +pub fn connect(config Config) ?DB { + conninfo := 'host=$config.host port=$config.port user=$config.user dbname=$config.dbname password=$config.password' + conn := C.PQconnectdb(conninfo.str) + if conn == 0 { + return error('libpq memory allocation error') + } + status := C.PQstatus(conn) + if status != C.CONNECTION_OK { + // We force the construction of a new string as the + // error message will be freed by the next `PQfinish` + // call + c_error_msg := unsafe { C.PQerrorMessage(conn).vstring() } + error_msg := '$c_error_msg' + C.PQfinish(conn) + return error('Connection to a PG database failed: $error_msg') + } + return DB{ + conn: conn + } +} + +fn res_to_rows(res voidptr) []Row { + nr_rows := C.PQntuples(res) + nr_cols := C.PQnfields(res) + + mut rows := []Row{} + for i in 0 .. nr_rows { + mut row := Row{} + for j in 0 .. nr_cols { + val := C.PQgetvalue(res, i, j) + sval := unsafe { val.vstring() } + row.vals << sval + } + rows << row + } + + C.PQclear(res) + return rows +} + +// close frees the underlying resource allocated by the database connection +pub fn (db DB) close() { + C.PQfinish(db.conn) +} + +// q_int submit a command to the database server and +// returns an the first field in the first tuple +// converted to an int. If no row is found or on +// command failure, an error is returned +pub fn (db DB) q_int(query string) ?int { + rows := db.exec(query) ? + if rows.len == 0 { + return error('q_int "$query" not found') + } + row := rows[0] + if row.vals.len == 0 { + return 0 + } + val := row.vals[0] + return val.int() +} + +// q_string submit a command to the database server and +// returns an the first field in the first tuple +// as a string. If no row is found or on +// command failure, an error is returned +pub fn (db DB) q_string(query string) ?string { + rows := db.exec(query) ? + if rows.len == 0 { + return error('q_string "$query" not found') + } + row := rows[0] + if row.vals.len == 0 { + return '' + } + val := row.vals[0] + return val +} + +// q_strings submit a command to the database server and +// returns the resulting row set. Alias of `exec` +pub fn (db DB) q_strings(query string) ?[]Row { + return db.exec(query) +} + +// exec submit a command to the database server and wait +// for the result, returning an error on failure and a +// row set on success +pub fn (db DB) exec(query string) ?[]Row { + res := C.PQexec(db.conn, query.str) + return db.handle_error_or_result(res, 'exec') +} + +fn rows_first_or_empty(rows []Row) ?Row { + if rows.len == 0 { + return error('no row') + } + return rows[0] +} + +pub fn (db DB) exec_one(query string) ?Row { + res := C.PQexec(db.conn, query.str) + e := unsafe { C.PQerrorMessage(db.conn).vstring() } + if e != '' { + return error('pg exec error: "$e"') + } + row := rows_first_or_empty(res_to_rows(res)) ? + return row +} + +// exec_param_many executes a query with the provided parameters +pub fn (db DB) exec_param_many(query string, params []string) ?[]Row { + mut param_vals := []&char{len: params.len} + for i in 0 .. params.len { + param_vals[i] = params[i].str + } + + res := C.PQexecParams(db.conn, query.str, params.len, 0, param_vals.data, 0, 0, 0) + return db.handle_error_or_result(res, 'exec_param_many') +} + +pub fn (db DB) exec_param2(query string, param string, param2 string) ?[]Row { + return db.exec_param_many(query, [param, param2]) +} + +pub fn (db DB) exec_param(query string, param string) ?[]Row { + return db.exec_param_many(query, [param]) +} + +fn (db DB) handle_error_or_result(res voidptr, elabel string) ?[]Row { + e := unsafe { C.PQerrorMessage(db.conn).vstring() } + if e != '' { + C.PQclear(res) + return error('pg $elabel error:\n$e') + } + return res_to_rows(res) +} + +// copy_expert execute COPY commands +// https://www.postgresql.org/docs/9.5/libpq-copy.html +pub fn (db DB) copy_expert(query string, file io.ReaderWriter) ?int { + res := C.PQexec(db.conn, query.str) + status := C.PQresultStatus(res) + + defer { + C.PQclear(res) + } + + e := unsafe { C.PQerrorMessage(db.conn).vstring() } + if e != '' { + return error('pg copy error:\n$e') + } + + if status == C.PGRES_COPY_IN { + mut buf := []byte{len: 4 * 1024} + for { + n := file.read(mut buf) or { + msg := 'pg copy error: Failed to read from input' + C.PQputCopyEnd(db.conn, msg.str) + return err + } + if n <= 0 { + break + } + + code := C.PQputCopyData(db.conn, buf.data, n) + if code == -1 { + return error('pg copy error: Failed to send data, code=$code') + } + } + + code := C.PQputCopyEnd(db.conn, 0) + + if code != 1 { + return error('pg copy error: Failed to finish copy command, code: $code') + } + } else if status == C.PGRES_COPY_OUT { + for { + address := &byte(0) + n_bytes := C.PQgetCopyData(db.conn, &address, 0) + if n_bytes > 0 { + mut local_buf := []byte{len: n_bytes} + unsafe { C.memcpy(&byte(local_buf.data), address, n_bytes) } + file.write(local_buf) or { + C.PQfreemem(address) + return err + } + } else if n_bytes == -1 { + break + } else if n_bytes == -2 { + // consult PQerrorMessage for the reason + return error('pg copy error: read error') + } + if address != 0 { + C.PQfreemem(address) + } + } + } + + return 0 +} diff --git a/v_windows/v/vlib/pg/pg_orm_test.v b/v_windows/v/vlib/pg/pg_orm_test.v new file mode 100644 index 0000000..eb0fd12 --- /dev/null +++ b/v_windows/v/vlib/pg/pg_orm_test.v @@ -0,0 +1,77 @@ +module main + +import orm +import pg + +fn test_pg_orm() { + mut db := pg.connect( + host: 'localhost' + user: 'postgres' + password: '' + dbname: 'postgres' + ) or { panic(err) } + + db.create('Test', [ + orm.TableField{ + name: 'id' + typ: 7 + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + kind: .plain + arg: 'serial' + }, + ] + }, + orm.TableField{ + name: 'name' + typ: 18 + attrs: [] + }, + orm.TableField{ + name: 'age' + typ: 7 + }, + ]) or { panic(err) } + + db.insert('Test', orm.QueryData{ + fields: ['name', 'age'] + data: [orm.string_to_primitive('Louis'), orm.int_to_primitive(101)] + }) or { panic(err) } + + res := db.@select(orm.SelectConfig{ + table: 'Test' + has_where: true + fields: ['id', 'name', 'age'] + types: [7, 18, 8] + }, orm.QueryData{}, orm.QueryData{ + fields: ['name'] + data: [orm.Primitive('Louis'), i64(101)] + types: [18] + is_and: [true] + kinds: [.eq] + }) or { panic(err) } + + id := res[0][0] + name := res[0][1] + age := res[0][2] + + assert id is int + if id is int { + assert id == 1 + } + + assert name is string + if name is string { + assert name == 'Louis' + } + + assert age is i64 + if age is i64 { + assert age == 101 + } +} diff --git a/v_windows/v/vlib/picoev/picoev.v b/v_windows/v/vlib/picoev/picoev.v new file mode 100644 index 0000000..7c11d03 --- /dev/null +++ b/v_windows/v/vlib/picoev/picoev.v @@ -0,0 +1,265 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module picoev + +import net +import picohttpparser + +#include +#include +#include +#flag -I @VEXEROOT/thirdparty/picoev +#flag -L @VEXEROOT/thirdparty/picoev +#flag @VEXEROOT/thirdparty/picoev/picoev.o +#include "src/picoev.h" + +struct C.in_addr { +mut: + s_addr int +} + +struct C.sockaddr_in { +mut: + sin_family int + sin_port int + sin_addr C.in_addr +} + +struct C.sockaddr_storage {} + +fn C.atoi() int + +fn C.strncasecmp(s1 &char, s2 &char, n size_t) int + +struct C.picoev_loop {} + +fn C.picoev_del(&C.picoev_loop, int) int + +fn C.picoev_set_timeout(&C.picoev_loop, int, int) + +// fn C.picoev_handler(loop &C.picoev_loop, fd int, revents int, cb_arg voidptr) +// TODO: (sponge) update to C.picoev_handler with C type def update +type Cpicoev_handler = fn (loop &C.picoev_loop, fd int, revents int, context voidptr) + +fn C.picoev_add(&C.picoev_loop, int, int, int, &Cpicoev_handler, voidptr) int + +fn C.picoev_init(int) int + +fn C.picoev_create_loop(int) &C.picoev_loop + +fn C.picoev_loop_once(&C.picoev_loop, int) int + +fn C.picoev_destroy_loop(&C.picoev_loop) int + +fn C.picoev_deinit() int + +const ( + max_fds = 1024 + max_timeout = 10 + max_read = 4096 + max_write = 8192 +) + +enum Event { + read = C.PICOEV_READ + write = C.PICOEV_WRITE + timeout = C.PICOEV_TIMEOUT + add = C.PICOEV_ADD + del = C.PICOEV_DEL + readwrite = C.PICOEV_READWRITE +} + +pub struct Config { +pub: + port int = 8080 + cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) + err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb + user_data voidptr = voidptr(0) + timeout_secs int = 8 + max_headers int = 100 +} + +struct Picoev { + loop &C.picoev_loop + cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) + err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) + user_data voidptr + timeout_secs int + max_headers int +mut: + date &byte + buf &byte + idx [1024]int + out &byte +} + +[inline] +fn setup_sock(fd int) ? { + flag := 1 + if C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_NODELAY, &flag, sizeof(int)) < 0 { + return error('setup_sock.setup_sock failed') + } + if C.fcntl(fd, C.F_SETFL, C.O_NONBLOCK) != 0 { + return error('fcntl failed') + } +} + +[inline] +fn close_conn(loop &C.picoev_loop, fd int) { + C.picoev_del(loop, fd) + C.close(fd) +} + +[inline] +fn req_read(fd int, b &byte, max_len int, idx int) int { + unsafe { + return C.read(fd, b + idx, max_len - idx) + } +} + +fn rw_callback(loop &C.picoev_loop, fd int, events int, context voidptr) { + mut p := unsafe { &Picoev(context) } + defer { + p.idx[fd] = 0 + } + if (events & int(Event.timeout)) != 0 { + close_conn(loop, fd) + return + } else if (events & int(Event.read)) != 0 { + C.picoev_set_timeout(loop, fd, p.timeout_secs) + + // Request init + mut buf := p.buf + unsafe { + buf += fd * picoev.max_read // pointer magic + } + mut req := picohttpparser.Request{} + + // Response init + mut out := p.out + unsafe { + out += fd * picoev.max_write // pointer magic + } + mut res := picohttpparser.Response{ + fd: fd + date: p.date + buf_start: out + buf: out + } + + for { + // Request parsing loop + r := req_read(fd, buf, picoev.max_read, p.idx[fd]) // Get data from socket + if r == 0 { + // connection closed by peer + close_conn(loop, fd) + return + } else if r == -1 { + // error + if C.errno == C.EAGAIN || C.errno == C.EWOULDBLOCK { + // try again later + return + } else { + // fatal error + close_conn(loop, fd) + return + } + } + p.idx[fd] += r + + mut s := unsafe { tos(buf, p.idx[fd]) } + pret := req.parse_request(s, p.max_headers) // Parse request via picohttpparser + if pret > 0 { // Success + break + } else if pret == -1 { // Parse error + p.err_cb(mut p.user_data, req, mut &res, error('ParseError')) + return + } + + assert pret == -2 + // request is incomplete, continue the loop + if p.idx[fd] == sizeof(buf) { + p.err_cb(mut p.user_data, req, mut &res, error('RequestIsTooLongError')) + return + } + } + + // Callback (should call .end() itself) + p.cb(mut p.user_data, req, mut &res) + } +} + +fn accept_callback(loop &C.picoev_loop, fd int, events int, cb_arg voidptr) { + mut p := unsafe { &Picoev(cb_arg) } + newfd := C.accept(fd, 0, 0) + if newfd != -1 { + setup_sock(newfd) or { + p.err_cb(mut p.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{}, + err) + } + C.picoev_add(loop, newfd, int(Event.read), p.timeout_secs, rw_callback, cb_arg) + } +} + +fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response, error IError) { + eprintln('picoev: $error') + res.end() +} + +pub fn new(config Config) &Picoev { + fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0) + assert fd != -1 + flag := 1 + assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0 + assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEPORT, &flag, sizeof(int)) == 0 + $if linux { + assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_QUICKACK, &flag, sizeof(int)) == 0 + timeout := 10 + assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_DEFER_ACCEPT, &timeout, sizeof(int)) == 0 + queue_len := 4096 + assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_FASTOPEN, &queue_len, sizeof(int)) == 0 + } + mut addr := C.sockaddr_in{} + addr.sin_family = C.AF_INET + addr.sin_port = C.htons(config.port) + addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) + size := 16 // sizeof(C.sockaddr_in) + bind_res := C.bind(fd, unsafe { &net.Addr(&addr) }, size) + assert bind_res == 0 + listen_res := C.listen(fd, C.SOMAXCONN) + assert listen_res == 0 + setup_sock(fd) or { + config.err_cb(mut config.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{}, + err) + } + C.picoev_init(picoev.max_fds) + loop := C.picoev_create_loop(picoev.max_timeout) + mut pv := &Picoev{ + loop: loop + cb: config.cb + err_cb: config.err_cb + user_data: config.user_data + timeout_secs: config.timeout_secs + max_headers: config.max_headers + date: C.get_date() + buf: unsafe { malloc_noscan(picoev.max_fds * picoev.max_read + 1) } + out: unsafe { malloc_noscan(picoev.max_fds * picoev.max_write + 1) } + } + C.picoev_add(loop, fd, int(Event.read), 0, accept_callback, pv) + go update_date(mut pv) + return pv +} + +pub fn (p Picoev) serve() { + for { + C.picoev_loop_once(p.loop, 1) + } +} + +fn update_date(mut p Picoev) { + for { + p.date = C.get_date() + C.usleep(1000000) + } +} diff --git a/v_windows/v/vlib/picohttpparser/misc.v b/v_windows/v/vlib/picohttpparser/misc.v new file mode 100644 index 0000000..6c8e1e7 --- /dev/null +++ b/v_windows/v/vlib/picohttpparser/misc.v @@ -0,0 +1,20 @@ +module picohttpparser + +[inline; unsafe] +fn cpy(dst &byte, src &byte, len int) int { + unsafe { C.memcpy(dst, src, len) } + return len +} + +[inline] +pub fn cmp(dst string, src string) bool { + if dst.len != src.len { + return false + } + return unsafe { C.memcmp(dst.str, src.str, src.len) == 0 } +} + +[inline] +pub fn cmpn(dst string, src string, n int) bool { + return unsafe { C.memcmp(dst.str, src.str, n) == 0 } +} diff --git a/v_windows/v/vlib/picohttpparser/picohttpparser.v b/v_windows/v/vlib/picohttpparser/picohttpparser.v new file mode 100644 index 0000000..a8c93e6 --- /dev/null +++ b/v_windows/v/vlib/picohttpparser/picohttpparser.v @@ -0,0 +1,35 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module picohttpparser + +#flag -I @VEXEROOT/thirdparty/picohttpparser +#flag -L @VEXEROOT/thirdparty/picohttpparser +#flag @VEXEROOT/thirdparty/picohttpparser/picohttpparser.o + +#include "picohttpparser.h" + +struct C.phr_header { +pub: + name &char + name_len int + value &char + value_len int +} + +type PPchar = &&char + +struct C.phr_header_t {} + +fn C.phr_parse_request(buf &char, len size_t, method PPchar, method_len &size_t, path PPchar, path_len &size_t, minor_version &int, headers &C.phr_header, num_headers &size_t, last_len size_t) int + +fn C.phr_parse_response(buf &char, len size_t, minor_version &int, status &int, msg PPchar, msg_len &size_t, headers &C.phr_header, num_headers &size_t, last_len size_t) int + +fn C.phr_parse_headers(buf &char, len size_t, headers &C.phr_header, num_headers &size_t, last_len size_t) int + +fn C.phr_parse_request_path(buf_start &char, len size_t, method PPchar, method_len &size_t, path PPchar, path_len &size_t) int +fn C.phr_parse_request_path_pipeline(buf_start &char, len size_t, method PPchar, method_len &size_t, path PPchar, path_len &size_t) int +fn C.get_date() &byte + +// static inline int u64toa(char* buf, uint64_t value) { +fn C.u64toa(buffer &char, value u64) int diff --git a/v_windows/v/vlib/picohttpparser/request.v b/v_windows/v/vlib/picohttpparser/request.v new file mode 100644 index 0000000..98667be --- /dev/null +++ b/v_windows/v/vlib/picohttpparser/request.v @@ -0,0 +1,65 @@ +module picohttpparser + +pub struct Request { +mut: + prev_len int +pub mut: + method string + path string + headers [100]C.phr_header + num_headers u64 + body string +} + +[inline] +pub fn (mut r Request) parse_request(s string, max_headers int) int { + method_len := size_t(0) + path_len := size_t(0) + minor_version := 0 + num_headers := size_t(max_headers) + + pret := C.phr_parse_request(s.str, s.len, PPchar(&r.method.str), &method_len, PPchar(&r.path.str), + &path_len, &minor_version, &r.headers[0], &num_headers, r.prev_len) + if pret > 0 { + unsafe { + r.method = tos(r.method.str, int(method_len)) + r.path = tos(r.path.str, int(path_len)) + } + r.num_headers = u64(num_headers) + } + r.body = unsafe { (&s.str[pret]).vstring_literal_with_len(s.len - pret) } + r.prev_len = s.len + return pret +} + +[inline] +pub fn (mut r Request) parse_request_path(s string) int { + method_len := size_t(0) + path_len := size_t(0) + + pret := C.phr_parse_request_path(s.str, s.len, PPchar(&r.method.str), &method_len, + PPchar(&r.path.str), &path_len) + if pret > 0 { + unsafe { + r.method = tos(r.method.str, int(method_len)) + r.path = tos(r.path.str, int(path_len)) + } + } + return pret +} + +[inline] +pub fn (mut r Request) parse_request_path_pipeline(s string) int { + method_len := size_t(0) + path_len := size_t(0) + + pret := C.phr_parse_request_path_pipeline(s.str, s.len, PPchar(&r.method.str), &method_len, + PPchar(&r.path.str), &path_len) + if pret > 0 { + unsafe { + r.method = tos(r.method.str, int(method_len)) + r.path = tos(r.path.str, int(path_len)) + } + } + return pret +} diff --git a/v_windows/v/vlib/picohttpparser/response.v b/v_windows/v/vlib/picohttpparser/response.v new file mode 100644 index 0000000..5c490ea --- /dev/null +++ b/v_windows/v/vlib/picohttpparser/response.v @@ -0,0 +1,114 @@ +module picohttpparser + +pub struct Response { + fd int +pub: + date &byte = 0 + buf_start &byte = 0 +pub mut: + buf &byte = 0 +} + +[inline] +pub fn (mut r Response) write_string(s string) { + unsafe { + C.memcpy(r.buf, s.str, s.len) + r.buf += s.len + } +} + +[inline] +pub fn (mut r Response) http_ok() &Response { + r.write_string('HTTP/1.1 200 OK\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) header(k string, v string) &Response { + r.write_string(k) + r.write_string(': ') + r.write_string(v) + r.write_string('\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) header_date() &Response { + r.write_string('Date: ') + unsafe { + r.buf += cpy(r.buf, r.date, 29) + } + r.write_string('\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) header_server() &Response { + r.write_string('Server: V\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) content_type(s string) &Response { + r.write_string('Content-Type: ') + r.write_string(s) + r.write_string('\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) html() &Response { + r.write_string('Content-Type: text/html\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) plain() &Response { + r.write_string('Content-Type: text/plain\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) json() &Response { + r.write_string('Content-Type: application/json\r\n') + return unsafe { r } +} + +[inline] +pub fn (mut r Response) body(body string) { + r.write_string('Content-Length: ') + unsafe { + r.buf += C.u64toa(r.buf, body.len) + } + r.write_string('\r\n\r\n') + r.write_string(body) +} + +[inline] +pub fn (mut r Response) http_404() { + r.write_string('HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n') +} + +[inline] +pub fn (mut r Response) http_405() { + r.write_string('HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n') +} + +[inline] +pub fn (mut r Response) http_500() { + r.write_string('HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n') +} + +[inline] +pub fn (mut r Response) raw(response string) { + r.write_string(response) +} + +[inline] +pub fn (mut r Response) end() int { + n := int(r.buf) - int(r.buf_start) + if C.write(r.fd, r.buf_start, n) != n { + return -1 + } + return n +} diff --git a/v_windows/v/vlib/rand/README.md b/v_windows/v/vlib/rand/README.md new file mode 100644 index 0000000..b5b917f --- /dev/null +++ b/v_windows/v/vlib/rand/README.md @@ -0,0 +1,87 @@ +# Quickstart + +The V `rand` module provides two main ways in which users can generate pseudorandom numbers: + +1. Through top-level functions in the `rand` module. + - `import rand` - Import the `rand` module. + - `rand.seed(seed_data)` to seed (optional). + - Use `rand.int()`, `rand.u32n(max)`, etc. +2. Through a generator of choice. The PRNGs are included in their respective submodules. + - `import rand.pcg32` - Import the module of the PRNG required. + - `mut rng := pcg32.PCG32RNG{}` - Initialize the struct. Note that the **`mut`** is important. + - `rng.seed(seed_data)` - optionally seed it with an array of `u32` values. + - Use `rng.int()`, `rng.u32n(max)`, etc. + +You can change the default generator to a different one. The only requirement is that +the generator must implement the `PRNG` interface. See `get_current_rng()` and `set_rng()`. + +For non-uniform distributions, refer to the `rand.dist` module which defined functions for +sampling from non-uniform distributions. These functions make use of the global RNG. + +**Note:** The global PRNG is not thread safe. It is recommended to use separate generators for +separate threads in multi-threaded applications. If you need to use non-uniform sampling functions, +it is recommended to generate them before use in a multi-threaded context. + +For sampling functions and generating random strings, see `string_from_set()` and other related +functions defined in this top-level module. + +For arrays, see `rand.util`. + +# General Background + +A PRNG is a Pseudo Random Number Generator. +Computers cannot generate truly random numbers without an external source of noise or entropy. +We can use algorithms to generate sequences of seemingly random numbers, +but their outputs will always be deterministic. +This is often useful for simulations that need the same starting seed. + +If you need truly random numbers that are going to be used for cryptography, +use the `crypto.rand` module. + +# Guaranteed functions + +The following 21 functions are guaranteed to be supported by `rand` +as well as the individual PRNGs. + +- `seed(seed_data)` where `seed_data` is an array of `u32` values. + Different generators require different number of bits as the initial seed. + The smallest is 32-bits, required by `sys.SysRNG`. + Most others require 64-bits or 2 `u32` values. +- `u32()`, `u64()`, `int()`, `i64()`, `f32()`, `f64()` +- `u32n(max)`, `u64n(max)`, `intn(max)`, `i64n(max)`, `f32n(max)`, `f64n(max)` +- `u32_in_range(min, max)`, `u64_in_range(min, max)`, `int_in_range(min, max)`, + `i64_in_range(min, max)`, `f32_in_range(min, max)`, `f64_in_range(min, max)` +- `int31()`, `int63()` + +There are several additional functions defined in the top-level module that rely +on the global RNG. If you want to make use of those functions with a different +PRNG, you can can change the global RNG to do so. + +# Seeding Functions + +All the generators are time-seeded. +The helper functions publicly available in `rand.seed` module are: + +1. `time_seed_array()` - returns a `[]u32` that can be directly plugged into the `seed()` functions. +2. `time_seed_32()` and `time_seed_64()` - 32-bit and 64-bit values respectively + that are generated from the current time. + +# Caveats + +Note that the `sys.SysRNG` struct (in the C backend) uses `C.srand()` which sets the seed globally. +Consequently, all instances of the RNG will be affected. +This problem does not arise for the other RNGs. +A workaround (if you _must_ use the libc RNG) is to: + +1. Seed the first instance. +2. Generate all values required. +3. Seed the second instance. +4. Generate all values required. +5. And so on... + +# Notes + +Please note that [math interval](https://en.wikipedia.org/wiki/Interval_(mathematics)#Including_or_excluding_endpoints) notation is used throughout +the function documentation to denote what numbers ranges include. +An example of `[0, max)` thus denotes a range with all posible values +between `0` and `max` **including** 0 but **excluding** `max`. diff --git a/v_windows/v/vlib/rand/config/config.v b/v_windows/v/vlib/rand/config/config.v new file mode 100644 index 0000000..b11e77c --- /dev/null +++ b/v_windows/v/vlib/rand/config/config.v @@ -0,0 +1,13 @@ +module config + +import rand.seed + +// PRNGConfigStruct is a configuration struct for creating a new instance of the default RNG. +// Note that the RNGs may have a different number of u32s required for seeding. The default +// generator WyRand used 64 bits, ie. 2 u32s so that is the default. In case your desired generator +// uses a different number of u32s, use the `seed.time_seed_array()` method with the correct +// number of u32s. +pub struct PRNGConfigStruct { +pub: + seed_ []u32 = seed.time_seed_array(2) +} diff --git a/v_windows/v/vlib/rand/constants/constants.v b/v_windows/v/vlib/rand/constants/constants.v new file mode 100644 index 0000000..76fe211 --- /dev/null +++ b/v_windows/v/vlib/rand/constants/constants.v @@ -0,0 +1,12 @@ +module constants + +// Commonly used constants across RNGs - some taken from "Numerical Recipes". +pub const ( + lower_mask = u64(0x00000000FFFFFFFF) + max_u32 = 0xFFFFFFFF + max_u64 = 0xFFFFFFFFFFFFFFFF + max_u32_as_f32 = f32(max_u32) + 1 + max_u64_as_f64 = f64(max_u64) + 1 + u31_mask = u32(0x7FFFFFFF) + u63_mask = u64(0x7FFFFFFFFFFFFFFF) +) diff --git a/v_windows/v/vlib/rand/dist/README.md b/v_windows/v/vlib/rand/dist/README.md new file mode 100644 index 0000000..85b7177 --- /dev/null +++ b/v_windows/v/vlib/rand/dist/README.md @@ -0,0 +1,10 @@ +# Non-Uniform Distribution Functions + +This module contains functions for sampling from non-uniform distributions. + +All implementations of the `rand.PRNG` interface generate numbers from uniform +distributions. This library exists to allow the generation of pseudorandom numbers +sampled from non-uniform distributions. Additionally, it allows the user to use any +PRNG of their choice. This is because the default RNG can be reassigned to a different +generator. It can either be one of the pre-existing one (which are well-tested and +recommended) or a custom user-defined one. See `rand.set_rng()`. \ No newline at end of file diff --git a/v_windows/v/vlib/rand/dist/dist.v b/v_windows/v/vlib/rand/dist/dist.v new file mode 100644 index 0000000..b2d9055 --- /dev/null +++ b/v_windows/v/vlib/rand/dist/dist.v @@ -0,0 +1,85 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module dist + +import math +import rand + +fn check_probability_range(p f64) { + if p < 0 || p > 1 { + panic('$p is not a valid probability value.') + } +} + +// bernoulli returns true with a probability p. Note that 0 <= p <= 1. +pub fn bernoulli(p f64) bool { + check_probability_range(p) + return rand.f64() <= p +} + +// binomial returns the number of successful trials out of n when the +// probability of success for each trial is p. +pub fn binomial(n int, p f64) int { + check_probability_range(p) + mut count := 0 + for _ in 0 .. n { + if bernoulli(p) { + count++ + } + } + return count +} + +// Configuration struct for the `normal_pair` function. The default value for +// `mu` is 0 and the default value for `sigma` is 1. +pub struct NormalConfigStruct { + mu f64 = 0.0 + sigma f64 = 1.0 +} + +// normal_pair returns a pair of normally distributed random numbers with the mean mu +// and standard deviation sigma. If not specified, mu is 0 and sigma is 1. Intended usage is +// `x, y := normal_pair(mu: mean, sigma: stdev)`, or `x, y := normal_pair()`. +pub fn normal_pair(config NormalConfigStruct) (f64, f64) { + if config.sigma <= 0 { + panic('The standard deviation has to be positive.') + } + // This is an implementation of the Marsaglia polar method + // See: https://doi.org/10.1137%2F1006063 + // Also: https://en.wikipedia.org/wiki/Marsaglia_polar_method + for { + u := rand.f64_in_range(-1, 1) + v := rand.f64_in_range(-1, 1) + + s := u * u + v * v + if s >= 1 || s == 0 { + continue + } + t := math.sqrt(-2 * math.log(s) / s) + x := config.mu + config.sigma * t * u + y := config.mu + config.sigma * t * v + return x, y + } + return config.mu, config.mu +} + +// normal returns a normally distributed random number with the mean mu and standard deviation +// sigma. If not specified, mu is 0 and sigma is 1. Intended usage is +// `x := normal(mu: mean, sigma: etdev)` or `x := normal()`. +// **NOTE:** If you are generating a lot of normal variates, use `the normal_pair` function +// instead. This function discards one of the two variates generated by the `normal_pair` function. +pub fn normal(config NormalConfigStruct) f64 { + x, _ := normal_pair(config) + return x +} + +// exponential returns an exponentially distributed random number with the rate paremeter +// lambda. It is expected that lambda is positive. +pub fn exponential(lambda f64) f64 { + if lambda <= 0 { + panic('The rate (lambda) must be positive.') + } + // Use the inverse transform sampling method + return -math.log(rand.f64()) / lambda +} diff --git a/v_windows/v/vlib/rand/dist/dist_test.v b/v_windows/v/vlib/rand/dist/dist_test.v new file mode 100644 index 0000000..a7c565e --- /dev/null +++ b/v_windows/v/vlib/rand/dist/dist_test.v @@ -0,0 +1,134 @@ +import math +import rand +import rand.dist + +const ( + // The sample size to be used + count = 2000 + // Accepted error is within 5% of the actual values. + error = 0.05 + // The seeds used (for reproducible testing) + seeds = [[u32(0xffff24), 0xabcd], [u32(0x141024), 0x42851], + [u32(0x1452), 0x90cd], + ] +) + +fn test_bernoulli() { + ps := [0.0, 0.1, 1.0 / 3.0, 0.5, 0.8, 17.0 / 18.0, 1.0] + + for seed in seeds { + rand.seed(seed) + for p in ps { + mut successes := 0 + for _ in 0 .. count { + if dist.bernoulli(p) { + successes++ + } + } + assert math.abs(f64(successes) / count - p) < error + } + } +} + +fn test_binomial() { + ns := [100, 200, 1000] + ps := [0.0, 0.5, 0.95, 1.0] + + for seed in seeds { + rand.seed(seed) + for n in ns { + for p in ps { + np := n * p + npq := np * (1 - p) + + mut sum := 0 + mut var := 0.0 + for _ in 0 .. count { + x := dist.binomial(n, p) + sum += x + dist := (x - np) + var += dist * dist + } + + assert math.abs(f64(sum / count) - np) / n < error + assert math.abs(f64(var / count) - npq) / n < error + } + } + } +} + +fn test_normal_pair() { + mus := [0, 10, 100, -40] + sigmas := [1, 2, 40, 5] + total := 2 * count + + for seed in seeds { + rand.seed(seed) + for mu in mus { + for sigma in sigmas { + mut sum := 0.0 + mut var := 0.0 + for _ in 0 .. count { + x, y := dist.normal_pair(mu: mu, sigma: sigma) + sum += x + y + dist_x := x - mu + dist_y := y - mu + var += dist_x * dist_x + var += dist_y * dist_y + } + + variance := sigma * sigma + assert math.abs(f64(sum / total) - mu) / sigma < 1 + assert math.abs(f64(var / total) - variance) / variance < 2 * error + } + } + } +} + +fn test_normal() { + mus := [0, 10, 100, -40, 20] + sigmas := [1, 2, 5] + + for seed in seeds { + rand.seed(seed) + for mu in mus { + for sigma in sigmas { + mut sum := 0.0 + mut var := 0.0 + for _ in 0 .. count { + x := dist.normal(mu: mu, sigma: sigma) + sum += x + dist := x - mu + var += dist * dist + } + + variance := sigma * sigma + assert math.abs(f64(sum / count) - mu) / sigma < 1 + assert math.abs(f64(var / count) - variance) / variance < 2 * error + } + } + } +} + +fn test_exponential() { + lambdas := [1.0, 10, 1 / 20.0, 1 / 10000.0, 1 / 524.0, 200] + + for seed in seeds { + rand.seed(seed) + for lambda in lambdas { + mu := 1 / lambda + variance := mu * mu + mut sum := 0.0 + mut var := 0.0 + for _ in 0 .. count { + x := dist.exponential(lambda) + sum += x + dist := x - mu + var += dist * dist + } + + assert math.abs((f64(sum / count) - mu) / mu) < error + assert math.abs((f64(var / count) - variance) / variance) < 2 * error + } + } +} diff --git a/v_windows/v/vlib/rand/mt19937/mt19937.v b/v_windows/v/vlib/rand/mt19937/mt19937.v new file mode 100644 index 0000000..256fc06 --- /dev/null +++ b/v_windows/v/vlib/rand/mt19937/mt19937.v @@ -0,0 +1,325 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module mt19937 + +import math.bits + +/* +C++ functions for MT19937, with initialization improved 2002/2/10. + Coded by Takuji Nishimura and Makoto Matsumoto. + This is a faster version by taking Shawn Cokus's optimization, + Matthe Bellew's simplification, Isaku Wada's real version. + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ +const ( + nn = 312 + mm = 156 + matrix_a = 0xB5026F5AA96619E9 + um = 0xFFFFFFFF80000000 + lm = 0x7FFFFFFF + inv_f64_limit = 1.0 / 9007199254740992.0 +) + +// MT19937RNG is generator that uses the Mersenne Twister algorithm with period 2^19937. +// **NOTE**: The RNG is not seeded when instantiated so remember to seed it before use. +pub struct MT19937RNG { +mut: + state []u64 = []u64{len: mt19937.nn} + mti int = mt19937.nn + next_rnd u32 + has_next bool +} + +// calculate_state returns a random state array calculated from the `seed_data`. +fn calculate_state(seed_data []u32, mut state []u64) []u64 { + lo := u64(seed_data[0]) + hi := u64(seed_data[1]) + state[0] = u64((hi << 32) | lo) + for j := 1; j < mt19937.nn; j++ { + state[j] = u64(6364136223846793005) * (state[j - 1] ^ (state[j - 1] >> 62)) + u64(j) + } + return *state +} + +// seed sets the current random state based on `seed_data`. +// seed expects `seed_data` to be only two `u32`s in little-endian format as [lower, higher]. +pub fn (mut rng MT19937RNG) seed(seed_data []u32) { + if seed_data.len != 2 { + eprintln('mt19937 needs only two 32bit integers as seed: [lower, higher]') + exit(1) + } + // calculate 2 times because MT19937RNG init didn't call calculate_state. + rng.state = calculate_state(seed_data, mut rng.state) + rng.state = calculate_state(seed_data, mut rng.state) + rng.mti = mt19937.nn + rng.next_rnd = 0 + rng.has_next = false +} + +// u32 returns a pseudorandom 32bit int in range `[0, 2³²)`. +[inline] +pub fn (mut rng MT19937RNG) u32() u32 { + if rng.has_next { + rng.has_next = false + return rng.next_rnd + } + ans := rng.u64() + rng.next_rnd = u32(ans >> 32) + rng.has_next = true + return u32(ans & 0xffffffff) +} + +// u64 returns a pseudorandom 64bit int in range `[0, 2⁶⁴)`. +[inline] +pub fn (mut rng MT19937RNG) u64() u64 { + mag01 := [u64(0), u64(mt19937.matrix_a)] + mut x := u64(0) + mut i := int(0) + if rng.mti >= mt19937.nn { + for i = 0; i < mt19937.nn - mt19937.mm; i++ { + x = (rng.state[i] & mt19937.um) | (rng.state[i + 1] & mt19937.lm) + rng.state[i] = rng.state[i + mt19937.mm] ^ (x >> 1) ^ mag01[int(x & 1)] + } + for i < mt19937.nn - 1 { + x = (rng.state[i] & mt19937.um) | (rng.state[i + 1] & mt19937.lm) + rng.state[i] = rng.state[i + (mt19937.mm - mt19937.nn)] ^ (x >> 1) ^ mag01[int(x & 1)] + i++ + } + x = (rng.state[mt19937.nn - 1] & mt19937.um) | (rng.state[0] & mt19937.lm) + rng.state[mt19937.nn - 1] = rng.state[mt19937.mm - 1] ^ (x >> 1) ^ mag01[int(x & 1)] + rng.mti = 0 + } + x = rng.state[rng.mti] + rng.mti++ + x ^= (x >> 29) & 0x5555555555555555 + x ^= (x << 17) & 0x71D67FFFEDA60000 + x ^= (x << 37) & 0xFFF7EEE000000000 + x ^= (x >> 43) + return x +} + +// int returns a 32-bit signed (possibly negative) `int`. +[inline] +pub fn (mut rng MT19937RNG) int() int { + return int(rng.u32()) +} + +// i64 returns a 64-bit signed (possibly negative) `i64`. +[inline] +pub fn (mut rng MT19937RNG) i64() i64 { + return i64(rng.u64()) +} + +// int31 returns a 31bit positive pseudorandom `int`. +[inline] +pub fn (mut rng MT19937RNG) int31() int { + return int(rng.u32() >> 1) +} + +// int63 returns a 63bit positive pseudorandom `i64`. +[inline] +pub fn (mut rng MT19937RNG) int63() i64 { + return i64(rng.u64() >> 1) +} + +// u32n returns a 32bit `u32` in range `[0, max)`. +[inline] +pub fn (mut rng MT19937RNG) u32n(max u32) u32 { + if max == 0 { + eprintln('max must be positive integer.') + exit(1) + } + // Check SysRNG in system_rng.c.v for explanation + bit_len := bits.len_32(max) + if bit_len == 32 { + for { + value := rng.u32() + if value < max { + return value + } + } + } else { + mask := (u32(1) << (bit_len + 1)) - 1 + for { + value := rng.u32() & mask + if value < max { + return value + } + } + } + return u32(0) +} + +// u64n returns a 64bit `u64` in range `[0, max)`. +[inline] +pub fn (mut rng MT19937RNG) u64n(max u64) u64 { + if max == 0 { + eprintln('max must be positive integer.') + exit(1) + } + bit_len := bits.len_64(max) + if bit_len == 64 { + for { + value := rng.u64() + if value < max { + return value + } + } + } else { + mask := (u64(1) << (bit_len + 1)) - 1 + for { + value := rng.u64() & mask + if value < max { + return value + } + } + } + return u64(0) +} + +// u32n returns a pseudorandom `u32` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.u32n(max - min) +} + +// u64n returns a pseudorandom `u64` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.u64n(max - min) +} + +// intn returns a 32bit positive `int` in range `[0, max)`. +[inline] +pub fn (mut rng MT19937RNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(rng.u32n(u32(max))) +} + +// i64n returns a 64bit positive `i64` in range `[0, max)`. +[inline] +pub fn (mut rng MT19937RNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(rng.u64n(u64(max))) +} + +// int_in_range returns a 32bit positive `int` in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.intn(max - min) +} + +// i64_in_range returns a 64bit positive `i64` in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.i64n(max - min) +} + +// f32 returns a 32bit real (`f32`) in range `[0, 1)`. +[inline] +pub fn (mut rng MT19937RNG) f32() f32 { + return f32(rng.f64()) +} + +// f64 returns 64bit real (`f64`) in range `[0, 1)`. +[inline] +pub fn (mut rng MT19937RNG) f64() f64 { + return f64(rng.u64() >> 11) * mt19937.inv_f64_limit +} + +// f32n returns a 32bit real (`f32`) in range [0, max)`. +[inline] +pub fn (mut rng MT19937RNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f32() * max +} + +// f64n returns a 64bit real (`f64`) in range `[0, max)`. +[inline] +pub fn (mut rng MT19937RNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f64() * max +} + +// f32_in_range returns a pseudorandom `f32` that lies in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.f32n(max - min) +} + +// i64_in_range returns a pseudorandom `i64` that lies in range `[min, max)`. +[inline] +pub fn (mut rng MT19937RNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/mt19937/mt19937_test.v b/v_windows/v/vlib/rand/mt19937/mt19937_test.v new file mode 100644 index 0000000..f2a41de --- /dev/null +++ b/v_windows/v/vlib/rand/mt19937/mt19937_test.v @@ -0,0 +1,341 @@ +import math +import rand.mt19937 +import rand.seed + +const ( + range_limit = 40 + value_count = 1000 + seeds = [[u32(0xcafebabe), u32(0xdeadbeef)], [u32(0xc0de), u32(0xfeed)]] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn mt19937_basic_test() { + mut rng := mt19937.MT19937RNG{} + rng.seed([u32(0xdeadbeef)]) + target := [u32(956529277), 3842322136, 3319553134, 1843186657, 2704993644, 595827513, 938518626, + 1676224337, 3221315650, 1819026461] + for i := 0; i < 10; i++ { + assert target[i] == rng.u32() + } +} + +fn gen_randoms(seed_data []u32, bound int) []u64 { + bound_u64 := u64(bound) + mut randoms := []u64{len: (20)} + mut rnd := mt19937.MT19937RNG{} + rnd.seed(seed_data) + for i in 0 .. 20 { + randoms[i] = rnd.u64n(bound_u64) + } + return randoms +} + +fn test_mt19937_reproducibility() { + seed_data := seed.time_seed_array(2) + randoms1 := gen_randoms(seed_data, 1000) + randoms2 := gen_randoms(seed_data, 1000) + assert randoms1.len == randoms2.len + len := randoms1.len + for i in 0 .. len { + assert randoms1[i] == randoms2[i] + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_mt19937_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(mut rng mt19937.MT19937RNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_mt19937_uniformity_u64() { + ranges := [14019545, 80240, 130] + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for range in ranges { + check_uniformity_u64(mut rng, u64(range)) + } + } +} + +fn check_uniformity_f64(mut rng mt19937.MT19937RNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_mt19937_uniformity_f64() { + // The f64 version + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + check_uniformity_f64(mut rng) + } +} + +fn test_mt19937_u32n() { + max := u32(16384) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_mt19937_u64n() { + max := u64(379091181005) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_mt19937_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_mt19937_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_mt19937_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_mt19937_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_mt19937_intn() { + max := 2525642 + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_mt19937_i64n() { + max := i64(3246727724653636) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_mt19937_int_in_range() { + min := -4252 + max := 1034 + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_mt19937_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_mt19937_f32() { + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_mt19937_f64() { + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_mt19937_f32n() { + max := f32(357.0) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_mt19937_f64n() { + max := 1.52e6 + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_mt19937_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_mt19937_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + mut rng := mt19937.MT19937RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} diff --git a/v_windows/v/vlib/rand/musl/musl_rng.v b/v_windows/v/vlib/rand/musl/musl_rng.v new file mode 100644 index 0000000..fdfb733 --- /dev/null +++ b/v_windows/v/vlib/rand/musl/musl_rng.v @@ -0,0 +1,241 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module musl + +import math.bits +import rand.seed +import rand.constants + +// MuslRNG ported from https://git.musl-libc.org/cgit/musl/tree/src/prng/rand_r.c +pub struct MuslRNG { +mut: + state u32 = seed.time_seed_32() +} + +// seed sets the current random state based on `seed_data`. +// seed expects `seed_data` to be only one `u32`. +pub fn (mut rng MuslRNG) seed(seed_data []u32) { + if seed_data.len != 1 { + eprintln('MuslRNG needs only one unsigned 32-bit integer as a seed.') + exit(1) + } + rng.state = seed_data[0] +} + +// temper returns a tempered value based on `prev` value. +[inline] +fn temper(prev u32) u32 { + mut x := prev + x ^= x >> 11 + x ^= (x << 7) & 0x9D2C5680 + x ^= (x << 15) & 0xEFC60000 + x ^= (x >> 18) + return x +} + +// u32 returns a pseudorandom 32-bit unsigned integer (`u32`). +[inline] +pub fn (mut rng MuslRNG) u32() u32 { + rng.state = rng.state * 1103515245 + 12345 + // We are not dividing by 2 (or shifting right by 1) + // because we want all 32-bits of random data + return temper(rng.state) +} + +// u64 returns a pseudorandom 64-bit unsigned integer (`u64`). +[inline] +pub fn (mut rng MuslRNG) u64() u64 { + return u64(rng.u32()) | (u64(rng.u32()) << 32) +} + +// u32n returns a pseudorandom 32-bit unsigned integer `u32` in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) u32n(max u32) u32 { + if max == 0 { + eprintln('max must be positive integer.') + exit(1) + } + // Check SysRNG in system_rng.c.v for explanation + bit_len := bits.len_32(max) + if bit_len == 32 { + for { + value := rng.u32() + if value < max { + return value + } + } + } else { + mask := (u32(1) << (bit_len + 1)) - 1 + for { + value := rng.u32() & mask + if value < max { + return value + } + } + } + return u32(0) +} + +// u64n returns a pseudorandom 64-bit unsigned integer (`u64`) in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) u64n(max u64) u64 { + if max == 0 { + eprintln('max must be positive integer.') + exit(1) + } + bit_len := bits.len_64(max) + if bit_len == 64 { + for { + value := rng.u64() + if value < max { + return value + } + } + } else { + mask := (u64(1) << (bit_len + 1)) - 1 + for { + value := rng.u64() & mask + if value < max { + return value + } + } + } + return u64(0) +} + +// u32_in_range returns a pseudorandom 32-bit unsigned integer (`u32`) in range `[min, max)`. +[inline] +pub fn (mut rng MuslRNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.u32n(u32(max - min)) +} + +// u64_in_range returns a pseudorandom 64-bit unsigned integer (`u64`) in range `[min, max)`. +[inline] +pub fn (mut rng MuslRNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.u64n(max - min) +} + +// int returns a 32-bit signed (possibly negative) integer (`int`). +[inline] +pub fn (mut rng MuslRNG) int() int { + return int(rng.u32()) +} + +// i64 returns a 64-bit signed (possibly negative) integer (`i64`). +[inline] +pub fn (mut rng MuslRNG) i64() i64 { + return i64(rng.u64()) +} + +// int31 returns a 31-bit positive pseudorandom integer (`int`). +[inline] +pub fn (mut rng MuslRNG) int31() int { + return int(rng.u32() >> 1) +} + +// int63 returns a 63-bit positive pseudorandom integer (`i64`). +[inline] +pub fn (mut rng MuslRNG) int63() i64 { + return i64(rng.u64() >> 1) +} + +// intn returns a 32-bit positive int in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(rng.u32n(u32(max))) +} + +// i64n returns a 64-bit positive integer `i64` in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(rng.u64n(u64(max))) +} + +// int_in_range returns a 32-bit positive integer `int` in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.intn(max - min) +} + +// i64_in_range returns a 64-bit positive integer `i64` in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.i64n(max - min) +} + +// f32 returns a pseudorandom `f32` value in range `[0, 1)`. +[inline] +pub fn (mut rng MuslRNG) f32() f32 { + return f32(rng.u32()) / constants.max_u32_as_f32 +} + +// f64 returns a pseudorandom `f64` value in range `[0, 1)`. +[inline] +pub fn (mut rng MuslRNG) f64() f64 { + return f64(rng.u64()) / constants.max_u64_as_f64 +} + +// f32n returns a pseudorandom `f32` value in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f32() * max +} + +// f64n returns a pseudorandom `f64` value in range `[0, max)`. +[inline] +pub fn (mut rng MuslRNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f64() * max +} + +// f32_in_range returns a pseudorandom `f32` in range `[min, max)`. +[inline] +pub fn (mut rng MuslRNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.f32n(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng MuslRNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/musl/musl_rng_test.v b/v_windows/v/vlib/rand/musl/musl_rng_test.v new file mode 100644 index 0000000..e6406f9 --- /dev/null +++ b/v_windows/v/vlib/rand/musl/musl_rng_test.v @@ -0,0 +1,331 @@ +import math +import rand.musl +import rand.seed + +const ( + range_limit = 40 + value_count = 1000 + seeds = [[u32(42)], [u32(256)]] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn gen_randoms(seed_data []u32, bound int) []u64 { + bound_u64 := u64(bound) + mut randoms := []u64{len: (20)} + mut rnd := musl.MuslRNG{} + rnd.seed(seed_data) + for i in 0 .. 20 { + randoms[i] = rnd.u64n(bound_u64) + } + return randoms +} + +fn test_musl_reproducibility() { + seed_data := seed.time_seed_array(1) + randoms1 := gen_randoms(seed_data, 1000) + randoms2 := gen_randoms(seed_data, 1000) + assert randoms1.len == randoms2.len + len := randoms1.len + for i in 0 .. len { + assert randoms1[i] == randoms2[i] + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_musl_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(mut rng musl.MuslRNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_musl_uniformity_u64() { + ranges := [14019545, 80240, 130] + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for range in ranges { + check_uniformity_u64(mut rng, u64(range)) + } + } +} + +fn check_uniformity_f64(mut rng musl.MuslRNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_musl_uniformity_f64() { + // The f64 version + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + check_uniformity_f64(mut rng) + } +} + +fn test_musl_u32n() { + max := u32(16384) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_musl_u64n() { + max := u64(379091181005) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_musl_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_musl_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_musl_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_musl_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_musl_intn() { + max := 2525642 + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_musl_i64n() { + max := i64(3246727724653636) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_musl_int_in_range() { + min := -4252 + max := 1034 + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_musl_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_musl_f32() { + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_musl_f64() { + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_musl_f32n() { + max := f32(357.0) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_musl_f64n() { + max := 1.52e6 + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_musl_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_musl_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + mut rng := musl.MuslRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} diff --git a/v_windows/v/vlib/rand/pcg32/pcg32.v b/v_windows/v/vlib/rand/pcg32/pcg32.v new file mode 100644 index 0000000..25a83f7 --- /dev/null +++ b/v_windows/v/vlib/rand/pcg32/pcg32.v @@ -0,0 +1,226 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module pcg32 + +import rand.seed +import rand.constants + +// PCG32RNG ported from http://www.pcg-random.org/download.html, +// https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.c, and +// https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.h +pub struct PCG32RNG { +mut: + state u64 = u64(0x853c49e6748fea9b) ^ seed.time_seed_64() + inc u64 = u64(0xda3e39cb94b95bdb) ^ seed.time_seed_64() +} + +// seed seeds the PCG32RNG with 4 `u32` values. +// The first 2 represent the 64-bit initial state as `[lower 32 bits, higher 32 bits]` +// The last 2 represent the 64-bit stream/step of the PRNG. +pub fn (mut rng PCG32RNG) seed(seed_data []u32) { + if seed_data.len != 4 { + eprintln('PCG32RNG needs 4 u32s to be seeded. First two the initial state and the last two the stream/step. Both in little endian format: [lower, higher].') + exit(1) + } + init_state := u64(seed_data[0]) | (u64(seed_data[1]) << 32) + init_seq := u64(seed_data[2]) | (u64(seed_data[3]) << 32) + rng.state = u64(0) + rng.inc = (init_seq << u64(1)) | u64(1) + rng.u32() + rng.state += init_state + rng.u32() +} + +// u32 returns a pseudorandom unsigned `u32`. +[inline] +pub fn (mut rng PCG32RNG) u32() u32 { + oldstate := rng.state + rng.state = oldstate * (6364136223846793005) + rng.inc + xorshifted := u32(((oldstate >> u64(18)) ^ oldstate) >> u64(27)) + rot := u32(oldstate >> u64(59)) + return ((xorshifted >> rot) | (xorshifted << ((-rot) & u32(31)))) +} + +// u64 returns a pseudorandom 64-bit unsigned `u64`. +[inline] +pub fn (mut rng PCG32RNG) u64() u64 { + return u64(rng.u32()) | (u64(rng.u32()) << 32) +} + +// u32n returns a pseudorandom 32-bit unsigned `u32` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) u32n(max u32) u32 { + if max == 0 { + eprintln('max must be positive') + exit(1) + } + // To avoid bias, we need to make the range of the RNG a multiple of + // max, which we do by dropping output less than a threshold. + threshold := (-max % max) + // Uniformity guarantees that loop below will terminate. In practice, it + // should usually terminate quickly; on average (assuming all max's are + // equally likely), 82.25% of the time, we can expect it to require just + // one iteration. In practice, max's are typically small and only a + // tiny amount of the range is eliminated. + for { + r := rng.u32() + if r >= threshold { + return (r % max) + } + } + return u32(0) +} + +// u64n returns a pseudorandom 64-bit unsigned `u64` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) u64n(max u64) u64 { + if max == 0 { + eprintln('max must be positive') + exit(1) + } + threshold := (-max % max) + for { + r := rng.u64() + if r >= threshold { + return (r % max) + } + } + return u64(0) +} + +// u32_in_range returns a pseudorandom 32-bit unsigned `u32` in range `[min, max)`. +[inline] +pub fn (mut rng PCG32RNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u32n(u32(max - min)) +} + +// u64_in_range returns a pseudorandom 64-bit unsigned `u64` in range `[min, max)`. +[inline] +pub fn (mut rng PCG32RNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u64n(max - min) +} + +// int returns a 32-bit signed (possibly negative) `int`. +[inline] +pub fn (mut rng PCG32RNG) int() int { + return int(rng.u32()) +} + +// i64 returns a 64-bit signed (possibly negative) `i64`. +[inline] +pub fn (mut rng PCG32RNG) i64() i64 { + return i64(rng.u64()) +} + +// int31 returns a 31-bit positive pseudorandom `int`. +[inline] +pub fn (mut rng PCG32RNG) int31() int { + return int(rng.u32() >> 1) +} + +// int63 returns a 63-bit positive pseudorandom `i64`. +[inline] +pub fn (mut rng PCG32RNG) int63() i64 { + return i64(rng.u64() >> 1) +} + +// intn returns a 32-bit positive `int` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(rng.u32n(u32(max))) +} + +// i64n returns a 64-bit positive `i64` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(rng.u64n(u64(max))) +} + +// int_in_range returns a 32-bit positive `int` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.intn(max - min) +} + +// i64_in_range returns a 64-bit positive `i64` in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min.') + exit(1) + } + return min + rng.i64n(max - min) +} + +// f32 returns a pseudorandom `f32` value in range `[0, 1)`. +[inline] +pub fn (mut rng PCG32RNG) f32() f32 { + return f32(rng.u32()) / constants.max_u32_as_f32 +} + +// f64 returns a pseudorandom `f64` value in range `[0, 1)`. +[inline] +pub fn (mut rng PCG32RNG) f64() f64 { + return f64(rng.u64()) / constants.max_u64_as_f64 +} + +// f32n returns a pseudorandom `f32` value in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f32() * max +} + +// f64n returns a pseudorandom `f64` value in range `[0, max)`. +[inline] +pub fn (mut rng PCG32RNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f64() * max +} + +// f32_in_range returns a pseudorandom `f32` in range `[min, max)`. +[inline] +pub fn (mut rng PCG32RNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f32n(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng PCG32RNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/pcg32/pcg32_test.v b/v_windows/v/vlib/rand/pcg32/pcg32_test.v new file mode 100644 index 0000000..17048a0 --- /dev/null +++ b/v_windows/v/vlib/rand/pcg32/pcg32_test.v @@ -0,0 +1,337 @@ +import math +import rand +import rand.pcg32 +import rand.seed + +const ( + range_limit = 40 + value_count = 1000 + seeds = [[u32(42), 242, 267, 14195], [u32(256), 340, 1451, 1505]] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn gen_randoms(seed_data []u32, bound int) []u32 { + mut randoms := []u32{len: 20} + mut rng := pcg32.PCG32RNG{} + rng.seed(seed_data) + for i in 0 .. 20 { + randoms[i] = rng.u32n(u32(bound)) + } + return randoms +} + +fn test_pcg32_reproducibility() { + seed_data := seed.time_seed_array(4) + randoms1 := gen_randoms(seed_data, 1000) + randoms2 := gen_randoms(seed_data, 1000) + assert randoms1.len == randoms2.len + len := randoms1.len + for i in 0 .. len { + r1 := randoms1[i] + r2 := randoms2[i] + assert r1 == r2 + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_pcg32_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(mut rng pcg32.PCG32RNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_pcg32_uniformity_u64() { + ranges := [14019545, 80240, 130] + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for range in ranges { + check_uniformity_u64(mut rng, u64(range)) + } + } +} + +fn check_uniformity_f64(mut rng pcg32.PCG32RNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_pcg32_uniformity_f64() { + // The f64 version + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + check_uniformity_f64(mut rng) + } +} + +fn test_pcg32_u32n() { + max := u32(16384) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_pcg32_u64n() { + max := u64(379091181005) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_pcg32_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32_in_range(u32(min), u32(max)) + assert value >= min + assert value < max + } + } +} + +fn test_pcg32_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_pcg32_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_pcg32_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_pcg32_intn() { + max := 2525642 + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_pcg32_i64n() { + max := i64(3246727724653636) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_pcg32_int_in_range() { + min := -4252 + max := 1034 + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_pcg32_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_pcg32_f32() { + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_pcg32_f64() { + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_pcg32_f32n() { + max := f32(357.0) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_pcg32_f64n() { + max := 1.52e6 + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_pcg32_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_pcg32_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + mut rng := pcg32.PCG32RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_change_default_random_generator() { + rand.set_rng(pcg32.PCG32RNG{}) +} diff --git a/v_windows/v/vlib/rand/rand.c.v b/v_windows/v/vlib/rand/rand.c.v new file mode 100644 index 0000000..066d8eb --- /dev/null +++ b/v_windows/v/vlib/rand/rand.c.v @@ -0,0 +1,127 @@ +module rand + +import time +// uuid_v4 generates a random (v4) UUID +// See https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) + +pub fn uuid_v4() string { + buflen := 36 + mut buf := unsafe { malloc_noscan(37) } + mut i_buf := 0 + mut x := u64(0) + mut d := byte(0) + for i_buf < buflen { + mut c := 0 + x = default_rng.u64() + // do most of the bit manipulation at once: + x &= 0x0F0F0F0F0F0F0F0F + x += 0x3030303030303030 + // write the ASCII codes to the buffer: + for c < 8 && i_buf < buflen { + d = byte(x) + unsafe { + buf[i_buf] = if d > 0x39 { d + 0x27 } else { d } + } + i_buf++ + c++ + x = x >> 8 + } + } + // there are still some random bits in x: + x = x >> 8 + d = byte(x) + unsafe { + buf[19] = if d > 0x39 { d + 0x27 } else { d } + buf[8] = `-` + buf[13] = `-` + buf[18] = `-` + buf[23] = `-` + buf[14] = `4` + buf[buflen] = 0 + return buf.vstring_with_len(buflen) + } +} + +const ( + ulid_encoding = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' +) + +// ulid generates an Unique Lexicographically sortable IDentifier. +// See https://github.com/ulid/spec . +// NB: ULIDs can leak timing information, if you make them public, because +// you can infer the rate at which some resource is being created, like +// users or business transactions. +// (https://news.ycombinator.com/item?id=14526173) +pub fn ulid() string { + return ulid_at_millisecond(u64(time.utc().unix_time_milli())) +} + +// ulid_at_millisecond does the same as `ulid` but takes a custom Unix millisecond timestamp via `unix_time_milli`. +pub fn ulid_at_millisecond(unix_time_milli u64) string { + buflen := 26 + mut buf := unsafe { malloc_noscan(27) } + mut t := unix_time_milli + mut i := 9 + for i >= 0 { + unsafe { + buf[i] = rand.ulid_encoding[t & 0x1F] + } + t = t >> 5 + i-- + } + // first rand set + mut x := default_rng.u64() + i = 10 + for i < 19 { + unsafe { + buf[i] = rand.ulid_encoding[x & 0x1F] + } + x = x >> 5 + i++ + } + // second rand set + x = default_rng.u64() + for i < 26 { + unsafe { + buf[i] = rand.ulid_encoding[x & 0x1F] + } + x = x >> 5 + i++ + } + unsafe { + buf[26] = 0 + return buf.vstring_with_len(buflen) + } +} + +// string_from_set returns a string of length `len` containing random characters sampled from the given `charset` +pub fn string_from_set(charset string, len int) string { + if len == 0 { + return '' + } + mut buf := unsafe { malloc_noscan(len + 1) } + for i in 0 .. len { + unsafe { + buf[i] = charset[intn(charset.len)] + } + } + unsafe { + buf[len] = 0 + } + return unsafe { buf.vstring_with_len(len) } +} + +// string returns a string of length `len` containing random characters in range `[a-zA-Z]`. +pub fn string(len int) string { + return string_from_set(english_letters, len) +} + +// hex returns a hexadecimal number of length `len` containing random characters in range `[a-f0-9]`. +pub fn hex(len int) string { + return string_from_set(hex_chars, len) +} + +// ascii returns a random string of the printable ASCII characters with length `len`. +pub fn ascii(len int) string { + return string_from_set(ascii_chars, len) +} diff --git a/v_windows/v/vlib/rand/rand.v b/v_windows/v/vlib/rand/rand.v new file mode 100644 index 0000000..d790dd5 --- /dev/null +++ b/v_windows/v/vlib/rand/rand.v @@ -0,0 +1,183 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module rand + +import rand.config +import rand.wyrand + +// PRNG is a common interface for all PRNGs that can be used seamlessly with the rand +// modules's API. It defines all the methods that a PRNG (in the vlib or custom made) must +// implement in order to ensure that _all_ functions can be used with the generator. +pub interface PRNG { + seed(seed_data []u32) + u32() u32 + u64() u64 + u32n(max u32) u32 + u64n(max u64) u64 + u32_in_range(min u32, max u32) u32 + u64_in_range(min u64, max u64) u64 + int() int + i64() i64 + int31() int + int63() i64 + intn(max int) int + i64n(max i64) i64 + int_in_range(min int, max int) int + i64_in_range(min i64, max i64) i64 + f32() f32 + f64() f64 + f32n(max f32) f32 + f64n(max f64) f64 + f32_in_range(min f32, max f32) f32 + f64_in_range(min f64, max f64) f64 +} + +__global ( + default_rng &PRNG +) + +// init initializes the default RNG. +fn init() { + default_rng = new_default() +} + +// new_default returns a new instance of the default RNG. If the seed is not provided, the current time will be used to seed the instance. +pub fn new_default(config config.PRNGConfigStruct) &PRNG { + mut rng := &wyrand.WyRandRNG{} + rng.seed(config.seed_) + return rng +} + +// get_current_rng returns the PRNG instance currently in use. If it is not changed, it will be an instance of wyrand.WyRandRNG. +pub fn get_current_rng() &PRNG { + return default_rng +} + +// set_rng changes the default RNG from wyrand.WyRandRNG (or whatever the last RNG was) to the one +// provided by the user. Note that this new RNG must be seeded manually with a constant seed or the +// `seed.time_seed_array()` method. Also, it is recommended to store the old RNG in a variable and +// should be restored if work with the custom RNG is complete. It is not necessary to restore if the +// program terminates soon afterwards. +pub fn set_rng(rng &PRNG) { + default_rng = unsafe { rng } +} + +// seed sets the given array of `u32` values as the seed for the `default_rng`. The default_rng is +// an instance of WyRandRNG which takes 2 u32 values. When using a custom RNG, make sure to use +// the correct number of u32s. +pub fn seed(seed []u32) { + default_rng.seed(seed) +} + +// u32 returns a uniformly distributed `u32` in range `[0, 2³²)`. +pub fn u32() u32 { + return default_rng.u32() +} + +// u64 returns a uniformly distributed `u64` in range `[0, 2⁶⁴)`. +pub fn u64() u64 { + return default_rng.u64() +} + +// u32n returns a uniformly distributed pseudorandom 32-bit signed positive `u32` in range `[0, max)`. +pub fn u32n(max u32) u32 { + return default_rng.u32n(max) +} + +// u64n returns a uniformly distributed pseudorandom 64-bit signed positive `u64` in range `[0, max)`. +pub fn u64n(max u64) u64 { + return default_rng.u64n(max) +} + +// u32_in_range returns a uniformly distributed pseudorandom 32-bit unsigned `u32` in range `[min, max)`. +pub fn u32_in_range(min u32, max u32) u32 { + return default_rng.u32_in_range(min, max) +} + +// u64_in_range returns a uniformly distributed pseudorandom 64-bit unsigned `u64` in range `[min, max)`. +pub fn u64_in_range(min u64, max u64) u64 { + return default_rng.u64_in_range(min, max) +} + +// int returns a uniformly distributed pseudorandom 32-bit signed (possibly negative) `int`. +pub fn int() int { + return default_rng.int() +} + +// intn returns a uniformly distributed pseudorandom 32-bit signed positive `int` in range `[0, max)`. +pub fn intn(max int) int { + return default_rng.intn(max) +} + +// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`. +pub fn byte() byte { + return byte(default_rng.u32() & 0xff) +} + +// int_in_range returns a uniformly distributed pseudorandom 32-bit signed int in range `[min, max)`. +// Both `min` and `max` can be negative, but we must have `min < max`. +pub fn int_in_range(min int, max int) int { + return default_rng.int_in_range(min, max) +} + +// int31 returns a uniformly distributed pseudorandom 31-bit signed positive `int`. +pub fn int31() int { + return default_rng.int31() +} + +// i64 returns a uniformly distributed pseudorandom 64-bit signed (possibly negative) `i64`. +pub fn i64() i64 { + return default_rng.i64() +} + +// i64n returns a uniformly distributed pseudorandom 64-bit signed positive `i64` in range `[0, max)`. +pub fn i64n(max i64) i64 { + return default_rng.i64n(max) +} + +// i64_in_range returns a uniformly distributed pseudorandom 64-bit signed `i64` in range `[min, max)`. +pub fn i64_in_range(min i64, max i64) i64 { + return default_rng.i64_in_range(min, max) +} + +// int63 returns a uniformly distributed pseudorandom 63-bit signed positive `i64`. +pub fn int63() i64 { + return default_rng.int63() +} + +// f32 returns a uniformly distributed 32-bit floating point in range `[0, 1)`. +pub fn f32() f32 { + return default_rng.f32() +} + +// f64 returns a uniformly distributed 64-bit floating point in range `[0, 1)`. +pub fn f64() f64 { + return default_rng.f64() +} + +// f32n returns a uniformly distributed 32-bit floating point in range `[0, max)`. +pub fn f32n(max f32) f32 { + return default_rng.f32n(max) +} + +// f64n returns a uniformly distributed 64-bit floating point in range `[0, max)`. +pub fn f64n(max f64) f64 { + return default_rng.f64n(max) +} + +// f32_in_range returns a uniformly distributed 32-bit floating point in range `[min, max)`. +pub fn f32_in_range(min f32, max f32) f32 { + return default_rng.f32_in_range(min, max) +} + +// f64_in_range returns a uniformly distributed 64-bit floating point in range `[min, max)`. +pub fn f64_in_range(min f64, max f64) f64 { + return default_rng.f64_in_range(min, max) +} + +const ( + english_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + hex_chars = 'abcdef0123456789' + ascii_chars = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_`abcdefghijklmnopqrstuvwxyz{|}~' +) diff --git a/v_windows/v/vlib/rand/random_identifiers_test.v b/v_windows/v/vlib/rand/random_identifiers_test.v new file mode 100644 index 0000000..d54fa84 --- /dev/null +++ b/v_windows/v/vlib/rand/random_identifiers_test.v @@ -0,0 +1,70 @@ +import time +import rand + +// uuid_v4: +fn test_rand_uuid_v4() { + uuid1 := rand.uuid_v4() + uuid2 := rand.uuid_v4() + uuid3 := rand.uuid_v4() + assert uuid1 != uuid2 + assert uuid1 != uuid3 + assert uuid2 != uuid3 + assert uuid1.len == 36 + assert uuid2.len == 36 + assert uuid3.len == 36 + assert uuid1[14] == `4` + assert uuid2[14] == `4` + assert uuid3[14] == `4` +} + +// ulids: +fn test_ulids_are_unique() { + ulid1 := rand.ulid() + ulid2 := rand.ulid() + ulid3 := rand.ulid() + assert ulid1.len == 26 + assert ulid2.len == 26 + assert ulid3.len == 26 + assert ulid1 != ulid2 + assert ulid1 != ulid3 + assert ulid2 != ulid3 +} + +fn test_ulids_max_start_character_is_ok() { + ulid1 := rand.ulid() + // the largest valid ULID encoded in Base32 is 7ZZZZZZZZZZZZZZZZZZZZZZZZZ + assert (int(ulid1[0]) - 48) <= 7 +} + +fn test_ulids_generated_in_the_same_millisecond_have_the_same_prefix() { + t := u64(time.utc().unix_time_milli()) + mut ulid1 := '' + mut ulid2 := '' + mut ulid3 := '' + ulid1 = rand.ulid_at_millisecond(t) + ulid2 = rand.ulid_at_millisecond(t) + ulid3 = rand.ulid_at_millisecond(t) + ulid1_prefix := ulid1[0..10] + ulid2_prefix := ulid2[0..10] + ulid3_prefix := ulid3[0..10] + assert ulid1_prefix == ulid2_prefix + assert ulid1_prefix == ulid3_prefix +} + +fn test_ulids_should_be_lexicographically_ordered_when_not_in_same_millisecond() { + ulid1 := rand.ulid() + time.sleep(1 * time.millisecond) + ulid2 := rand.ulid() + time.sleep(1 * time.millisecond) + ulid3 := rand.ulid() + mut all := [ulid3, ulid2, ulid1] + // eprintln('all before: $all') + all.sort() + // eprintln('all after: $all') + s1 := all[0] + s2 := all[1] + s3 := all[2] + assert s1 == ulid1 + assert s2 == ulid2 + assert s3 == ulid3 +} diff --git a/v_windows/v/vlib/rand/random_numbers_test.v b/v_windows/v/vlib/rand/random_numbers_test.v new file mode 100644 index 0000000..f09fa3c --- /dev/null +++ b/v_windows/v/vlib/rand/random_numbers_test.v @@ -0,0 +1,319 @@ +import rand +import rand.splitmix64 +import rand.musl +import rand.mt19937 + +const ( + rnd_count = 40 + seeds = [[u32(42), 0], [u32(256), 0]] +) + +fn get_n_random_ints(seed_data []u32, n int) []int { + mut values := []int{cap: n} + rand.seed(seed_data) + for _ in 0 .. n { + values << rand.intn(n) + } + return values +} + +fn test_rand_reproducibility() { + for seed in seeds { + array1 := get_n_random_ints(seed, 1000) + array2 := get_n_random_ints(seed, 1000) + assert array1.len == array2.len + assert array1 == array2 + } +} + +fn test_rand_u32n() { + max := u32(16384) + for _ in 0 .. rnd_count { + value := rand.u32n(max) + assert value >= 0 + assert value < max + } +} + +fn test_rand_u64n() { + max := u64(379091181005) + for _ in 0 .. rnd_count { + value := rand.u64n(max) + assert value >= 0 + assert value < max + } +} + +fn test_rand_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for _ in 0 .. rnd_count { + value := rand.u32_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for _ in 0 .. rnd_count { + value := rand.u64_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_intn() { + max := 2525642 + for _ in 0 .. rnd_count { + value := rand.intn(max) + assert value >= 0 + assert value < max + } +} + +fn test_rand_i64n() { + max := i64(3246727724653636) + for _ in 0 .. rnd_count { + value := rand.i64n(max) + assert value >= 0 + assert value < max + } +} + +fn test_rand_int_in_range() { + min := -4252 + max := 23054962 + for _ in 0 .. rnd_count { + value := rand.int_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for _ in 0 .. rnd_count { + value := rand.i64_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for _ in 0 .. rnd_count { + value := rand.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } +} + +fn test_rand_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for _ in 0 .. rnd_count { + value := rand.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } +} + +fn test_rand_f32() { + for _ in 0 .. rnd_count { + value := rand.f32() + assert value >= 0.0 + assert value < 1.0 + } +} + +fn test_rand_f64() { + for _ in 0 .. rnd_count { + value := rand.f64() + assert value >= 0.0 + assert value < 1.0 + } +} + +fn test_rand_f32n() { + max := f32(357.0) + for _ in 0 .. rnd_count { + value := rand.f32n(max) + assert value >= 0.0 + assert value < max + } +} + +fn test_rand_f64n() { + max := f64(1.52e6) + for _ in 0 .. rnd_count { + value := rand.f64n(max) + assert value >= 0.0 + assert value < max + } +} + +fn test_rand_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for _ in 0 .. rnd_count { + value := rand.f32_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_f64_in_range() { + min := f64(-548.7) + max := f64(5015.2) + for _ in 0 .. rnd_count { + value := rand.f64_in_range(min, max) + assert value >= min + assert value < max + } +} + +fn test_rand_byte() { + mut all := []byte{} + for _ in 0 .. 256 { + x := rand.byte() + assert x >= 0 + assert x <= 255 + all << x + } + all.sort(a < b) + assert all[0] != all[255] + assert all[0] != all[128] +} + +const ( + string_count = 25 +) + +fn test_rand_string_from_set() { + sets := [ + '0123456789', + 'qwertyuiop', + 'abcdefghijklmnopqrstuvwxyz', + ] + for charset in sets { + for _ in 0 .. string_count { + len := rand.intn(rnd_count) + str := rand.string_from_set(charset, len) + assert str.len == len + for character in str { + position := charset.index(character.ascii_str()) or { -1 } + assert position > -1 + } + } + } +} + +fn test_rand_string() { + rand.seed([u32(0), 1]) + outputs := [ + 'rzJfVBJgvAyCNpEdXIteDQezg', + 'AJOeswgoelDOCfcrSUWzVPjeL', + 'NQfKauQqsXYXSUMFPGnXXPJIn', + 'vfBGUKbpLoBMQVYXfkvRplWih', + 'aYHLjMJqvUJmJJHGxEnrEmQGl', + 'rBJXkQZcembAteaRFoxXmECJo', + 'HYVLfHmDOCTlSbiSzHrsAIaBH', + 'zgOiwyISjLSdLGhLzJsSKHVBi', + 'UiAtobWXGcHsEtgzuNatxfkoI', + 'NisnYlffJgFEcIdcgzWcGjnHy', + ] + for output in outputs { + assert rand.string(25) == output + } +} + +fn test_rand_hex() { + rand.seed([u32(0), 1]) + outputs := [ + 'fc30e495deee09e008e15ffc3', + '4320efa837788397fb59b28f4', + '4995210abf33b6765c240ce62', + 'f3d20dbe0a8aa6b9c88cd1f6f', + '8d7d58b256ab00213dd519cf7', + 'fa2251284bc20a21eff48127c', + '5fef90cdc0c37143117599092', + '2a6170531c76dfb50c54126bc', + 'a686dfd536042d1c1a9afdaf4', + '7f12013f6e1177e2d63726de3', + ] + for output in outputs { + assert rand.hex(25) == output + } +} + +fn test_rand_ascii() { + rand.seed([u32(0), 1]) + outputs := [ + "2Z:&PeD'V;9=mn\$C>yKg'DIr%", + 'Ub7ix,}>I=QJki{%FHKv&K', + '1WStRylMO|p.R~qqRtr&AOEsd', + 'yka> 32 ^ (ctime & 0x0000_0000_FFFF_FFFF)) + mut seed_data := []u32{cap: count} + for _ in 0 .. count { + seed = nr_next(seed) + seed_data << nr_next(seed) + } + return seed_data +} + +// time_seed_32 returns a 32-bit seed generated from system time. +[inline] +pub fn time_seed_32() u32 { + return time_seed_array(1)[0] +} + +// time_seed_64 returns a 64-bit seed generated from system time. +[inline] +pub fn time_seed_64() u64 { + seed_data := time_seed_array(2) + lower := u64(seed_data[0]) + upper := u64(seed_data[1]) + return lower | (upper << 32) +} diff --git a/v_windows/v/vlib/rand/splitmix64/splitmix64.v b/v_windows/v/vlib/rand/splitmix64/splitmix64.v new file mode 100644 index 0000000..d3cb9d1 --- /dev/null +++ b/v_windows/v/vlib/rand/splitmix64/splitmix64.v @@ -0,0 +1,225 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module splitmix64 + +import rand.seed +import rand.constants + +// SplitMix64RNG ported from http://xoshiro.di.unimi.it/splitmix64.c +pub struct SplitMix64RNG { +mut: + state u64 = seed.time_seed_64() + has_extra bool + extra u32 +} + +// seed sets the seed of the accepting SplitMix64RNG to the given data +// in little-endian format (i.e. lower 32 bits are in [0] and higher 32 bits in [1]). +pub fn (mut rng SplitMix64RNG) seed(seed_data []u32) { + if seed_data.len != 2 { + eprintln('SplitMix64RNG needs 2 32-bit unsigned integers as the seed.') + exit(1) + } + rng.state = seed_data[0] | (u64(seed_data[1]) << 32) + rng.has_extra = false +} + +// u32 updates the PRNG state and returns the next pseudorandom `u32`. +[inline] +pub fn (mut rng SplitMix64RNG) u32() u32 { + if rng.has_extra { + rng.has_extra = false + return rng.extra + } + full_value := rng.u64() + lower := u32(full_value & constants.lower_mask) + upper := u32(full_value >> 32) + rng.extra = upper + rng.has_extra = true + return lower +} + +// u64 updates the PRNG state and returns the next pseudorandom `u64`. +[inline] +pub fn (mut rng SplitMix64RNG) u64() u64 { + rng.state += (0x9e3779b97f4a7c15) + mut z := rng.state + z = (z ^ ((z >> u64(30)))) * (0xbf58476d1ce4e5b9) + z = (z ^ ((z >> u64(27)))) * (0x94d049bb133111eb) + return z ^ (z >> (31)) +} + +// u32n returns a pseudorandom `u32` less than `bound`. +[inline] +pub fn (mut rng SplitMix64RNG) u32n(bound u32) u32 { + // This function is kept similar to the u64 version + if bound == 0 { + eprintln('max must be non-zero') + exit(1) + } + threshold := -bound % bound + for { + r := rng.u32() + if r >= threshold { + return r % bound + } + } + return u32(0) +} + +// u64n returns a pseudorandom `u64` less than `bound`. +[inline] +pub fn (mut rng SplitMix64RNG) u64n(bound u64) u64 { + // See pcg32.v for explanation of comment. This algorithm + // existed before the refactoring. + if bound == 0 { + eprintln('max must be non-zero') + exit(1) + } + threshold := -bound % bound + for { + r := rng.u64() + if r >= threshold { + return r % bound + } + } + return u64(0) +} + +// u32n returns a pseudorandom `u32` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u32n(max - min) +} + +// u64n returns a pseudorandom `u64` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u64n(max - min) +} + +// int returns a pseudorandom 32-bit (possibly negative) `int`. +[inline] +pub fn (mut rng SplitMix64RNG) int() int { + return int(rng.u32()) +} + +// i64 returns a pseudorandom 64-bit (possibly negative) `i64`. +[inline] +pub fn (mut rng SplitMix64RNG) i64() i64 { + return i64(rng.u64()) +} + +// int31 returns a positive pseudorandom 31-bit `int`. +[inline] +pub fn (mut rng SplitMix64RNG) int31() int { + return int(rng.u32() & constants.u31_mask) // Set the 32nd bit to 0. +} + +// int63 returns a positive pseudorandom 63-bit `i64`. +[inline] +pub fn (mut rng SplitMix64RNG) int63() i64 { + return i64(rng.u64() & constants.u63_mask) // Set the 64th bit to 0. +} + +// intn returns a pseudorandom `int` in range `[0, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(rng.u32n(u32(max))) +} + +// i64n returns a pseudorandom `i64` in range `[0, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(rng.u64n(u64(max))) +} + +// int_in_range returns a pseudorandom `int` in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + // This supports negative ranges like [-10, -5) because the difference is positive + return min + rng.intn(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.i64n(max - min) +} + +// f32 returns a pseudorandom `f32` value in range `[0, 1)`. +[inline] +pub fn (mut rng SplitMix64RNG) f32() f32 { + return f32(rng.u32()) / constants.max_u32_as_f32 +} + +// f64 returns a pseudorandom `f64` value in range `[0, 1)`. +[inline] +pub fn (mut rng SplitMix64RNG) f64() f64 { + return f64(rng.u64()) / constants.max_u64_as_f64 +} + +// f32n returns a pseudorandom `f32` value in range `[0, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f32() * max +} + +// f64n returns a pseudorandom `f64` value in range `[0, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f64() * max +} + +// f32_in_range returns a pseudorandom `f32` in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f32n(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng SplitMix64RNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/splitmix64/splitmix64_test.v b/v_windows/v/vlib/rand/splitmix64/splitmix64_test.v new file mode 100644 index 0000000..669da3c --- /dev/null +++ b/v_windows/v/vlib/rand/splitmix64/splitmix64_test.v @@ -0,0 +1,331 @@ +import math +import rand.splitmix64 +import rand.seed + +const ( + range_limit = 40 + value_count = 1000 + seeds = [[u32(42), 0], [u32(256), 0]] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn gen_randoms(seed_data []u32, bound int) []u64 { + bound_u64 := u64(bound) + mut randoms := []u64{len: (20)} + mut rnd := splitmix64.SplitMix64RNG{} + rnd.seed(seed_data) + for i in 0 .. 20 { + randoms[i] = rnd.u64n(bound_u64) + } + return randoms +} + +fn test_splitmix64_reproducibility() { + seed_data := seed.time_seed_array(2) + randoms1 := gen_randoms(seed_data, 1000) + randoms2 := gen_randoms(seed_data, 1000) + assert randoms1.len == randoms2.len + len := randoms1.len + for i in 0 .. len { + assert randoms1[i] == randoms2[i] + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_splitmix64_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(mut rng splitmix64.SplitMix64RNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_splitmix64_uniformity_u64() { + ranges := [14019545, 80240, 130] + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for range in ranges { + check_uniformity_u64(mut rng, u64(range)) + } + } +} + +fn check_uniformity_f64(mut rng splitmix64.SplitMix64RNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_splitmix64_uniformity_f64() { + // The f64 version + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + check_uniformity_f64(mut rng) + } +} + +fn test_splitmix64_u32n() { + max := u32(16384) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_splitmix64_u64n() { + max := u64(379091181005) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_splitmix64_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_splitmix64_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_splitmix64_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_splitmix64_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_splitmix64_intn() { + max := 2525642 + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_splitmix64_i64n() { + max := i64(3246727724653636) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_splitmix64_int_in_range() { + min := -4252 + max := 230549862 + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_splitmix64_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_splitmix64_f32() { + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_splitmix64_f64() { + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_splitmix64_f32n() { + max := f32(357.0) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_splitmix64_f64n() { + max := 1.52e6 + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_splitmix64_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_splitmix64_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + mut rng := splitmix64.SplitMix64RNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} diff --git a/v_windows/v/vlib/rand/sys/system_rng.c.v b/v_windows/v/vlib/rand/sys/system_rng.c.v new file mode 100644 index 0000000..f1c701d --- /dev/null +++ b/v_windows/v/vlib/rand/sys/system_rng.c.v @@ -0,0 +1,275 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sys + +import math.bits +import rand.seed +import rand.constants + +// Implementation note: +// ==================== +// C.rand returns a pseudorandom integer from 0 (inclusive) to C.RAND_MAX (exclusive) +// C.rand() is okay to use within its defined range. +// (See: https://web.archive.org/web/20180801210127/http://eternallyconfuzzled.com/arts/jsw_art_rand.aspx) +// The problem is, this value varies with the libc implementation. On windows, +// for example, RAND_MAX is usually a measly 32767, whereas on (newer) linux it's generally +// 2147483647. The repetition period also varies wildly. In order to provide more entropy +// without altering the underlying algorithm too much, this implementation simply +// requests for more random bits until the necessary width for the integers is achieved. +const ( + rand_limit = u64(C.RAND_MAX) + rand_bitsize = bits.len_64(rand_limit) + u32_iter_count = calculate_iterations_for(32) + u64_iter_count = calculate_iterations_for(64) +) + +fn calculate_iterations_for(bits int) int { + base := bits / sys.rand_bitsize + extra := if bits % sys.rand_bitsize == 0 { 0 } else { 1 } + return base + extra +} + +// SysRNG is the PRNG provided by default in the libc implementiation that V uses. +pub struct SysRNG { +mut: + seed u32 = seed.time_seed_32() +} + +// r.seed() sets the seed of the accepting SysRNG to the given data. +pub fn (mut r SysRNG) seed(seed_data []u32) { + if seed_data.len != 1 { + eprintln('SysRNG needs one 32-bit unsigned integer as the seed.') + exit(1) + } + r.seed = seed_data[0] + C.srand(r.seed) +} + +// r.default_rand() exposes the default behavior of the system's RNG +// (equivalent to calling C.rand()). Recommended for testing/comparison +// b/w V and other languages using libc and not for regular use. +// This is also a one-off feature of SysRNG, similar to the global seed +// situation. Other generators will not have this. +[inline] +pub fn (r SysRNG) default_rand() int { + return C.rand() +} + +// r.u32() returns a pseudorandom u32 value less than 2^32 +[inline] +pub fn (r SysRNG) u32() u32 { + mut result := u32(C.rand()) + for i in 1 .. sys.u32_iter_count { + result = result ^ (u32(C.rand()) << (sys.rand_bitsize * i)) + } + return result +} + +// r.u64() returns a pseudorandom u64 value less than 2^64 +[inline] +pub fn (r SysRNG) u64() u64 { + mut result := u64(C.rand()) + for i in 1 .. sys.u64_iter_count { + result = result ^ (u64(C.rand()) << (sys.rand_bitsize * i)) + } + return result +} + +// r.u32n(max) returns a pseudorandom u32 value that is guaranteed to be less than max +[inline] +pub fn (r SysRNG) u32n(max u32) u32 { + if max == 0 { + eprintln('max must be positive integer') + exit(1) + } + // Owing to the pigeon-hole principle, we can't simply do + // val := rng.u32() % max. + // It'll wreck the properties of the distribution unless + // max evenly divides 2^32. So we divide evenly to + // the closest power of two. Then we loop until we find + // an int in the required range + bit_len := bits.len_32(max) + if bit_len == 32 { + for { + value := r.u32() + if value < max { + return value + } + } + } else { + mask := (u32(1) << (bit_len + 1)) - 1 + for { + value := r.u32() & mask + if value < max { + return value + } + } + } + return u32(0) +} + +// r.u64n(max) returns a pseudorandom u64 value that is guaranteed to be less than max +[inline] +pub fn (r SysRNG) u64n(max u64) u64 { + if max == 0 { + eprintln('max must be positive integer') + exit(1) + } + // Similar procedure for u64s + bit_len := bits.len_64(max) + if bit_len == 64 { + for { + value := r.u64() + if value < max { + return value + } + } + } else { + mask := (u64(1) << (bit_len + 1)) - 1 + for { + value := r.u64() & mask + if value < max { + return value + } + } + } + return u64(0) +} + +// r.u32n(min, max) returns a pseudorandom u32 value that is guaranteed to be in [min, max) +[inline] +pub fn (r SysRNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + r.u32n(max - min) +} + +// r.u64n(min, max) returns a pseudorandom u64 value that is guaranteed to be in [min, max) +[inline] +pub fn (r SysRNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + r.u64n(max - min) +} + +// r.int() returns a pseudorandom 32-bit int (which may be negative) +[inline] +pub fn (r SysRNG) int() int { + return int(r.u32()) +} + +// r.i64() returns a pseudorandom 64-bit i64 (which may be negative) +[inline] +pub fn (r SysRNG) i64() i64 { + return i64(r.u64()) +} + +// r.int31() returns a pseudorandom 31-bit int which is non-negative +[inline] +pub fn (r SysRNG) int31() int { + return int(r.u32() & constants.u31_mask) // Set the 32nd bit to 0. +} + +// r.int63() returns a pseudorandom 63-bit int which is non-negative +[inline] +pub fn (r SysRNG) int63() i64 { + return i64(r.u64() & constants.u63_mask) // Set the 64th bit to 0. +} + +// r.intn(max) returns a pseudorandom int that lies in [0, max) +[inline] +pub fn (r SysRNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(r.u32n(u32(max))) +} + +// r.i64n(max) returns a pseudorandom i64 that lies in [0, max) +[inline] +pub fn (r SysRNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(r.u64n(u64(max))) +} + +// r.int_in_range(min, max) returns a pseudorandom int that lies in [min, max) +[inline] +pub fn (r SysRNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + // This supports negative ranges like [-10, -5) because the difference is positive + return min + r.intn(max - min) +} + +// r.i64_in_range(min, max) returns a pseudorandom i64 that lies in [min, max) +[inline] +pub fn (r SysRNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + r.i64n(max - min) +} + +// r.f32() returns a pseudorandom f32 value between 0.0 (inclusive) and 1.0 (exclusive) i.e [0, 1) +[inline] +pub fn (r SysRNG) f32() f32 { + return f32(r.u32()) / constants.max_u32_as_f32 +} + +// r.f64() returns a pseudorandom f64 value between 0.0 (inclusive) and 1.0 (exclusive) i.e [0, 1) +[inline] +pub fn (r SysRNG) f64() f64 { + return f64(r.u64()) / constants.max_u64_as_f64 +} + +// r.f32n() returns a pseudorandom f32 value in [0, max) +[inline] +pub fn (r SysRNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return r.f32() * max +} + +// r.f64n() returns a pseudorandom f64 value in [0, max) +[inline] +pub fn (r SysRNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return r.f64() * max +} + +// r.f32_in_range(min, max) returns a pseudorandom f32 that lies in [min, max) +[inline] +pub fn (r SysRNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + r.f32n(max - min) +} + +// r.i64_in_range(min, max) returns a pseudorandom i64 that lies in [min, max) +[inline] +pub fn (r SysRNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + r.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/sys/system_rng.js.v b/v_windows/v/vlib/rand/sys/system_rng.js.v new file mode 100644 index 0000000..496794d --- /dev/null +++ b/v_windows/v/vlib/rand/sys/system_rng.js.v @@ -0,0 +1,15 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sys + +// Until there's a portable, JS has a seeded way to produce random numbers +// and not just Math.random(), use any of the existing implementations +// as the System's RNG +type SysRNG = WyRandRNG + +// In the JS version, we simply return the same int as is normally generated. +[inline] +pub fn (r SysRNG) default_rand() int { + return r.int() +} diff --git a/v_windows/v/vlib/rand/sys/system_rng_test.v b/v_windows/v/vlib/rand/sys/system_rng_test.v new file mode 100644 index 0000000..638ed10 --- /dev/null +++ b/v_windows/v/vlib/rand/sys/system_rng_test.v @@ -0,0 +1,354 @@ +import math +import rand.sys + +const ( + range_limit = 40 + value_count = 1000 + seeds = [u32(42), 256] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn get_n_randoms(n int, r sys.SysRNG) []int { + mut ints := []int{cap: n} + for _ in 0 .. n { + ints << r.int() + } + return ints +} + +fn test_sys_rng_reproducibility() { + // Note that C.srand() sets the seed globally. + // So the order of seeding matters. It is recommended + // to obtain all necessary data first, then set the + // seed for another batch of data. + for seed in seeds { + seed_data := [seed] + mut r1 := sys.SysRNG{} + mut r2 := sys.SysRNG{} + r1.seed(seed_data) + ints1 := get_n_randoms(value_count, r1) + r2.seed(seed_data) + ints2 := get_n_randoms(value_count, r2) + assert ints1 == ints2 + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_sys_rng_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(rng sys.SysRNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_sys_rng_uniformity_u64() { + // This assumes that C.rand() produces uniform results to begin with. + // If the failure persists, report an issue on GitHub + ranges := [14019545, 80240, 130] + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for range in ranges { + check_uniformity_u64(rng, u64(range)) + } + } +} + +fn check_uniformity_f64(rng sys.SysRNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_sys_rng_uniformity_f64() { + // The f64 version + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + check_uniformity_f64(rng) + } +} + +fn test_sys_rng_u32n() { + max := u32(16384) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_sys_rng_u64n() { + max := u64(379091181005) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_sys_rng_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.u32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_sys_rng_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_sys_rng_intn() { + max := 2525642 + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_sys_rng_i64n() { + max := i64(3246727724653636) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_sys_rng_int_in_range() { + min := -4252 + max := 23054962 + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_sys_rng_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_sys_rng_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_sys_rng_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_sys_rng_f32() { + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_sys_rng_f64() { + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_sys_rng_f32n() { + max := f32(357.0) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_sys_rng_f64n() { + max := 1.52e6 + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_sys_rng_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_sys_rng_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + seed_data := [seed] + mut rng := sys.SysRNG{} + rng.seed(seed_data) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} diff --git a/v_windows/v/vlib/rand/util/util.v b/v_windows/v/vlib/rand/util/util.v new file mode 100644 index 0000000..e1c30ca --- /dev/null +++ b/v_windows/v/vlib/rand/util/util.v @@ -0,0 +1,52 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module util + +import rand + +// sample_nr returns a sample of the array without replacement. This means the indices cannot repeat and it restricts the sample size to be less than or equal to the size of the given array. Note that if the array has repeating elements, then the sample may have repeats as well. +pub fn sample_nr(array []T, k int) []T { + n := array.len + if k > n { + panic('Cannot sample $k elements without replacement from a $n-element array.') + } + mut results := []T{len: k} + mut indices := []int{len: n} + // Initialize with all indices + // temporary workaround for issue #10343 + for i in 0 .. indices.len { + indices[i] = i + } + shuffle(mut indices, k) + for i in 0 .. k { + results[i] = array[indices[i]] + } + return results +} + +// sample_r returns a sample of the array with replacement. This means the elements can repeat and the size of the sample may exceed the size of the array +pub fn sample_r(array []T, k int) []T { + n := array.len + mut results := []T{len: k} + for i in 0 .. k { + results[i] = array[rand.intn(n)] + } + return results +} + +// shuffle randomizes the first `n` items of an array in place (all if `n` is 0) +[direct_array_access] +pub fn shuffle(mut a []T, n int) { + if n < 0 || n > a.len { + panic("argument 'n' must be in range [0, a.len]") + } + cnt := if n == 0 { a.len - 1 } else { n } + for i in 0 .. cnt { + x := rand.int_in_range(i, a.len) + // swap + a_i := a[i] + a[i] = a[x] + a[x] = a_i + } +} diff --git a/v_windows/v/vlib/rand/util/util_test.v b/v_windows/v/vlib/rand/util/util_test.v new file mode 100644 index 0000000..f92e70b --- /dev/null +++ b/v_windows/v/vlib/rand/util/util_test.v @@ -0,0 +1,57 @@ +import rand +import rand.util + +fn test_sample_nr() { + lengths := [1, 3, 4, 5, 6, 7] + a := ['one', 'two', 'three', 'four', 'five', 'six', 'seven'] + for length in lengths { + b := util.sample_nr(a, length) + assert b.len == length + for element in b { + assert element in a + // make sure every element occurs once + mut count := 0 + for e in b { + if e == element { + count++ + } + } + assert count == 1 + } + } +} + +fn test_sample_r() { + k := 20 + a := ['heads', 'tails'] + b := util.sample_r(a, k) + assert b.len == k + for element in b { + assert element in a + } +} + +fn test_shuffle() { + rand.seed([u32(1), 2]) // set seed to produce same results in order + a := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + mut b := a.clone() + mut c := a.clone() + util.shuffle(mut b, 0) + util.shuffle(mut c, 0) + assert b == [6, 4, 5, 1, 9, 2, 10, 3, 8, 7] + assert c == [1, 6, 5, 8, 7, 2, 10, 9, 3, 4] + // test shuffling a slice + mut d := a.clone() + util.shuffle(mut d[..5], 0) + assert d == [5, 2, 1, 3, 4, 6, 7, 8, 9, 10] + assert d[5..] == a[5..] + // test shuffling n items + mut e := a.clone() + util.shuffle(mut e, 5) + assert e[..5] == [10, 3, 1, 8, 4] + assert e[5..] == [6, 7, 5, 9, 2] + // test shuffling empty array + mut f := a[..0] + util.shuffle(mut f, 0) + assert f == []int{} +} diff --git a/v_windows/v/vlib/rand/wyrand/wyrand.v b/v_windows/v/vlib/rand/wyrand/wyrand.v new file mode 100644 index 0000000..22b71af --- /dev/null +++ b/v_windows/v/vlib/rand/wyrand/wyrand.v @@ -0,0 +1,252 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module wyrand + +import math.bits +import rand.seed +import rand.constants +import hash + +// Redefinition of some constants that we will need for pseudorandom number generation. +const ( + wyp0 = u64(0xa0761d6478bd642f) + wyp1 = u64(0xe7037ed1a0b428db) +) + +// WyRandRNG is a RNG based on the WyHash hashing algorithm. +pub struct WyRandRNG { +mut: + state u64 = seed.time_seed_64() + has_extra bool + extra u32 +} + +// seed sets the seed, needs only two `u32`s in little-endian format as [lower, higher]. +pub fn (mut rng WyRandRNG) seed(seed_data []u32) { + if seed_data.len != 2 { + eprintln('WyRandRNG needs 2 32-bit unsigned integers as the seed.') + exit(1) + } + rng.state = seed_data[0] | (u64(seed_data[1]) << 32) + rng.has_extra = false +} + +// u32 updates the PRNG state and returns the next pseudorandom `u32`. +[inline] +pub fn (mut rng WyRandRNG) u32() u32 { + if rng.has_extra { + rng.has_extra = false + return rng.extra + } + full_value := rng.u64() + lower := u32(full_value & constants.lower_mask) + upper := u32(full_value >> 32) + rng.extra = upper + rng.has_extra = true + return lower +} + +// u64 updates the PRNG state and returns the next pseudorandom `u64`. +[inline] +pub fn (mut rng WyRandRNG) u64() u64 { + unsafe { + mut seed1 := rng.state + seed1 += wyrand.wyp0 + rng.state = seed1 + return hash.wymum(seed1 ^ wyrand.wyp1, seed1) + } + return 0 +} + +// u32n returns a pseudorandom `u32` less than `max`. +[inline] +pub fn (mut rng WyRandRNG) u32n(max u32) u32 { + if max == 0 { + eprintln('max must be positive integer') + exit(1) + } + // Check SysRNG in system_rng.c.v for explanation + bit_len := bits.len_32(max) + if bit_len == 32 { + for { + value := rng.u32() + if value < max { + return value + } + } + } else { + mask := (u32(1) << (bit_len + 1)) - 1 + for { + value := rng.u32() & mask + if value < max { + return value + } + } + } + return u32(0) +} + +// u64n returns a pseudorandom `u64` less than `max`. +[inline] +pub fn (mut rng WyRandRNG) u64n(max u64) u64 { + if max == 0 { + eprintln('max must be positive integer') + exit(1) + } + bit_len := bits.len_64(max) + if bit_len == 64 { + for { + value := rng.u64() + if value < max { + return value + } + } + } else { + mask := (u64(1) << (bit_len + 1)) - 1 + for { + value := rng.u64() & mask + if value < max { + return value + } + } + } + return u64(0) +} + +// u32n returns a pseudorandom `u32` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) u32_in_range(min u32, max u32) u32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u32n(max - min) +} + +// u64n returns a pseudorandom `u64` value that is guaranteed to be in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) u64_in_range(min u64, max u64) u64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.u64n(max - min) +} + +// int returns a (possibly negative) pseudorandom 32-bit `int`. +[inline] +pub fn (mut rng WyRandRNG) int() int { + return int(rng.u32()) +} + +// i64 returns a (possibly negative) pseudorandom 64-bit `i64`. +[inline] +pub fn (mut rng WyRandRNG) i64() i64 { + return i64(rng.u64()) +} + +// int31 returns a positive pseudorandom 31-bit `int`. +[inline] +pub fn (mut rng WyRandRNG) int31() int { + return int(rng.u32() & constants.u31_mask) // Set the 32nd bit to 0. +} + +// int63 returns a positive pseudorandom 63-bit `i64`. +[inline] +pub fn (mut rng WyRandRNG) int63() i64 { + return i64(rng.u64() & constants.u63_mask) // Set the 64th bit to 0. +} + +// intn returns a pseudorandom `int` in range `[0, max)`. +[inline] +pub fn (mut rng WyRandRNG) intn(max int) int { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return int(rng.u32n(u32(max))) +} + +// i64n returns a pseudorandom int that lies in `[0, max)`. +[inline] +pub fn (mut rng WyRandRNG) i64n(max i64) i64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return i64(rng.u64n(u64(max))) +} + +// int_in_range returns a pseudorandom `int` in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) int_in_range(min int, max int) int { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + // This supports negative ranges like [-10, -5) because the difference is positive + return min + rng.intn(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) i64_in_range(min i64, max i64) i64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.i64n(max - min) +} + +// f32 returns a pseudorandom `f32` value in range `[0, 1)`. +[inline] +pub fn (mut rng WyRandRNG) f32() f32 { + return f32(rng.u32()) / constants.max_u32_as_f32 +} + +// f64 returns a pseudorandom `f64` value in range `[0, 1)`. +[inline] +pub fn (mut rng WyRandRNG) f64() f64 { + return f64(rng.u64()) / constants.max_u64_as_f64 +} + +// f32n returns a pseudorandom `f32` value in range `[0, max)`. +[inline] +pub fn (mut rng WyRandRNG) f32n(max f32) f32 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f32() * max +} + +// f64n returns a pseudorandom `f64` value in range `[0, max)`. +[inline] +pub fn (mut rng WyRandRNG) f64n(max f64) f64 { + if max <= 0 { + eprintln('max has to be positive.') + exit(1) + } + return rng.f64() * max +} + +// f32_in_range returns a pseudorandom `f32` in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) f32_in_range(min f32, max f32) f32 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f32n(max - min) +} + +// i64_in_range returns a pseudorandom `i64` in range `[min, max)`. +[inline] +pub fn (mut rng WyRandRNG) f64_in_range(min f64, max f64) f64 { + if max <= min { + eprintln('max must be greater than min') + exit(1) + } + return min + rng.f64n(max - min) +} diff --git a/v_windows/v/vlib/rand/wyrand/wyrand_test.v b/v_windows/v/vlib/rand/wyrand/wyrand_test.v new file mode 100644 index 0000000..4cdfdb6 --- /dev/null +++ b/v_windows/v/vlib/rand/wyrand/wyrand_test.v @@ -0,0 +1,331 @@ +import math +import rand.seed +import rand.wyrand + +const ( + range_limit = 40 + value_count = 1000 + seeds = [[u32(42), 0], [u32(256), 0]] +) + +const ( + sample_size = 1000 + stats_epsilon = 0.05 + inv_sqrt_12 = 1.0 / math.sqrt(12) +) + +fn gen_randoms(seed_data []u32, bound int) []u64 { + bound_u64 := u64(bound) + mut randoms := []u64{len: (20)} + mut rnd := wyrand.WyRandRNG{} + rnd.seed(seed_data) + for i in 0 .. 20 { + randoms[i] = rnd.u64n(bound_u64) + } + return randoms +} + +fn test_wyrand_reproducibility() { + seed_data := seed.time_seed_array(2) + randoms1 := gen_randoms(seed_data, 1000) + randoms2 := gen_randoms(seed_data, 1000) + assert randoms1.len == randoms2.len + len := randoms1.len + for i in 0 .. len { + assert randoms1[i] == randoms2[i] + } +} + +// TODO: use the `in` syntax and remove this function +// after generics has been completely implemented +fn found(value u64, arr []u64) bool { + for item in arr { + if value == item { + return true + } + } + return false +} + +fn test_wyrand_variability() { + // If this test fails and if it is certainly not the implementation + // at fault, try changing the seed values. Repeated values are + // improbable but not impossible. + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + mut values := []u64{cap: value_count} + for i in 0 .. value_count { + value := rng.u64() + assert !found(value, values) + assert values.len == i + values << value + } + } +} + +fn check_uniformity_u64(mut rng wyrand.WyRandRNG, range u64) { + range_f64 := f64(range) + expected_mean := range_f64 / 2.0 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := f64(rng.u64n(range)) - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := range_f64 * inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_wyrand_uniformity_u64() { + ranges := [14019545, 80240, 130] + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for range in ranges { + check_uniformity_u64(mut rng, u64(range)) + } + } +} + +fn check_uniformity_f64(mut rng wyrand.WyRandRNG) { + expected_mean := 0.5 + mut variance := 0.0 + for _ in 0 .. sample_size { + diff := rng.f64() - expected_mean + variance += diff * diff + } + variance /= sample_size - 1 + sigma := math.sqrt(variance) + expected_sigma := inv_sqrt_12 + error := (sigma - expected_sigma) / expected_sigma + assert math.abs(error) < stats_epsilon +} + +fn test_wyrand_uniformity_f64() { + // The f64 version + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + check_uniformity_f64(mut rng) + } +} + +fn test_wyrand_u32n() { + max := u32(16384) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_wyrand_u64n() { + max := u64(379091181005) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_wyrand_u32_in_range() { + max := u32(484468466) + min := u32(316846) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_wyrand_u64_in_range() { + max := u64(216468454685163) + min := u64(6848646868) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.u64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_wyrand_int31() { + max_u31 := int(0x7FFFFFFF) + sign_mask := int(0x80000000) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int31() + assert value >= 0 + assert value <= max_u31 + // This statement ensures that the sign bit is zero + assert (value & sign_mask) == 0 + } + } +} + +fn test_wyrand_int63() { + max_u63 := i64(0x7FFFFFFFFFFFFFFF) + sign_mask := i64(0x8000000000000000) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int63() + assert value >= 0 + assert value <= max_u63 + assert (value & sign_mask) == 0 + } + } +} + +fn test_wyrand_intn() { + max := 2525642 + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.intn(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_wyrand_i64n() { + max := i64(3246727724653636) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64n(max) + assert value >= 0 + assert value < max + } + } +} + +fn test_wyrand_int_in_range() { + min := -4252 + max := 1034 + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.int_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_wyrand_i64_in_range() { + min := i64(-24095) + max := i64(324058) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.i64_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_wyrand_f32() { + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_wyrand_f64() { + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64() + assert value >= 0.0 + assert value < 1.0 + } + } +} + +fn test_wyrand_f32n() { + max := f32(357.0) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_wyrand_f64n() { + max := 1.52e6 + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64n(max) + assert value >= 0.0 + assert value < max + } + } +} + +fn test_wyrand_f32_in_range() { + min := f32(-24.0) + max := f32(125.0) + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f32_in_range(min, max) + assert value >= min + assert value < max + } + } +} + +fn test_wyrand_f64_in_range() { + min := -548.7 + max := 5015.2 + for seed in seeds { + mut rng := wyrand.WyRandRNG{} + rng.seed(seed) + for _ in 0 .. range_limit { + value := rng.f64_in_range(min, max) + assert value >= min + assert value < max + } + } +} diff --git a/v_windows/v/vlib/readline/README.md b/v_windows/v/vlib/readline/README.md new file mode 100644 index 0000000..edd4682 --- /dev/null +++ b/v_windows/v/vlib/readline/README.md @@ -0,0 +1,20 @@ +# Readline + +The `readline` module let you await and read user input +from a terminal in an easy and structured manner. + +The module provides an easy way to prompt the user for +questions or even make a REPL or an embedded console. + +Use `readline.Readline` if you want to include more +advanced features such as history or simply use +`readline.read_line('Please confirm (y/n):')` directly +for one-off user interactions. + +# Usage + +```v ignore +import readline { Readline } + +Readline.read_line('Continue?: (y/n)') +``` diff --git a/v_windows/v/vlib/readline/readline.v b/v_windows/v/vlib/readline/readline.v new file mode 100644 index 0000000..4f0ebf7 --- /dev/null +++ b/v_windows/v/vlib/readline/readline.v @@ -0,0 +1,45 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as a more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module readline + +// Termios stores the terminal options on Linux. +struct C.termios {} + +struct Termios { +mut: + c_iflag int + c_oflag int + c_cflag int + c_lflag int + c_cc [12]int // NCCS == 12. Can't use the defined value here +} + +// Winsize stores the screen information on Linux. +struct Winsize { + ws_row u16 + ws_col u16 + ws_xpixel u16 + ws_ypixel u16 +} + +// Readline is the key struct for reading and holding user input via a terminal. +// Example: import readline { Readline } +pub struct Readline { +mut: + is_raw bool + orig_termios Termios // Linux + current []rune // Line being edited + cursor int // Cursor position + overwrite bool + cursor_row_offset int + prompt string + prompt_offset int + previous_lines [][]rune + search_index int + is_tty bool +} diff --git a/v_windows/v/vlib/readline/readline_default.c.v b/v_windows/v/vlib/readline/readline_default.c.v new file mode 100644 index 0000000..fcbbbb6 --- /dev/null +++ b/v_windows/v/vlib/readline/readline_default.c.v @@ -0,0 +1,78 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// TODO Mac version needs to be implemented +// Will serve as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module readline + +import os + +#include +// Only use standard os.get_line +// Need implementation for readline capabilities +// +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +pub fn (mut r Readline) read_line_utf8(prompt string) ?[]rune { + r.current = []rune{} + r.cursor = 0 + r.prompt = prompt + r.search_index = 0 + if r.previous_lines.len <= 1 { + r.previous_lines << []rune{} + r.previous_lines << []rune{} + } else { + r.previous_lines[0] = []rune{} + } + print(r.prompt) + line := os.get_raw_line() + if line.len >= 0 { + r.current = line.runes() + } + r.previous_lines[0] = []rune{} + r.search_index = 0 + if r.current.len == 0 { + return error('empty line') + } + return r.current +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` returned by `read_line_utf8`). +pub fn (mut r Readline) read_line(prompt string) ?string { + s := r.read_line_utf8(prompt) ? + return s.string() +} + +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +// NOTE that this version of `read_line_utf8` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line_utf8(prompt string) ?[]rune { + mut r := Readline{} + s := r.read_line_utf8(prompt) ? + return s +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` as returned by `read_line_utf8`). +// NOTE that this version of `read_line` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line(prompt string) ?string { + mut r := Readline{} + s := r.read_line(prompt) ? + return s +} diff --git a/v_windows/v/vlib/readline/readline_linux.c.v b/v_windows/v/vlib/readline/readline_linux.c.v new file mode 100644 index 0000000..eb0fda9 --- /dev/null +++ b/v_windows/v/vlib/readline/readline_linux.c.v @@ -0,0 +1,554 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Serves as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module readline + +import term +import os + +#include +#include + +fn C.tcgetattr(fd int, termios_p &C.termios) int + +fn C.tcsetattr(fd int, optional_actions int, termios_p &C.termios) int + +fn C.raise(sig int) + +fn C.getppid() int + +// Action defines what actions to be executed. +enum Action { + eof + nothing + insert_character + commit_line + delete_left + delete_right + move_cursor_left + move_cursor_right + move_cursor_begining + move_cursor_end + move_cursor_word_left + move_cursor_word_right + history_previous + history_next + overwrite + clear_screen + suspend +} + +// enable_raw_mode enables the raw mode of the terminal. +// In raw mode all key presses are directly sent to the program and no interpretation is done. +// Please note that `enable_raw_mode` catches the `SIGUSER` (CTRL + C) signal. +// For a method that does please see `enable_raw_mode_nosig`. +pub fn (mut r Readline) enable_raw_mode() { + if C.tcgetattr(0, unsafe { &C.termios(&r.orig_termios) }) == -1 { + r.is_tty = false + r.is_raw = false + return + } + mut raw := r.orig_termios + raw.c_iflag &= ~(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) + raw.c_cflag |= C.CS8 + raw.c_lflag &= ~(C.ECHO | C.ICANON | C.IEXTEN | C.ISIG) + raw.c_cc[C.VMIN] = 1 + raw.c_cc[C.VTIME] = 0 + C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&raw) }) + r.is_raw = true + r.is_tty = true +} + +// enable_raw_mode_nosig enables the raw mode of the terminal. +// In raw mode all key presses are directly sent to the program and no interpretation is done. +// Please note that `enable_raw_mode_nosig` does not catch the `SIGUSER` (CTRL + C) signal +// as opposed to `enable_raw_mode`. +pub fn (mut r Readline) enable_raw_mode_nosig() { + if C.tcgetattr(0, unsafe { &C.termios(&r.orig_termios) }) == -1 { + r.is_tty = false + r.is_raw = false + return + } + mut raw := r.orig_termios + raw.c_iflag &= ~(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXON) + raw.c_cflag |= C.CS8 + raw.c_lflag &= ~(C.ECHO | C.ICANON | C.IEXTEN) + raw.c_cc[C.VMIN] = 1 + raw.c_cc[C.VTIME] = 0 + C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&raw) }) + r.is_raw = true + r.is_tty = true +} + +// disable_raw_mode disables the raw mode of the terminal. +// For a description of raw mode please see the `enable_raw_mode` method. +pub fn (mut r Readline) disable_raw_mode() { + if r.is_raw { + C.tcsetattr(0, C.TCSADRAIN, unsafe { &C.termios(&r.orig_termios) }) + r.is_raw = false + } +} + +// read_char reads a single character. +pub fn (r Readline) read_char() int { + return utf8_getchar() +} + +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +pub fn (mut r Readline) read_line_utf8(prompt string) ?[]rune { + r.current = []rune{} + r.cursor = 0 + r.prompt = prompt + r.search_index = 0 + r.prompt_offset = get_prompt_offset(prompt) + if r.previous_lines.len <= 1 { + r.previous_lines << []rune{} + r.previous_lines << []rune{} + } else { + r.previous_lines[0] = []rune{} + } + if !r.is_raw { + r.enable_raw_mode() + } + print(r.prompt) + for { + C.fflush(C.stdout) + c := r.read_char() + a := r.analyse(c) + if r.execute(a, c) { + break + } + } + r.previous_lines[0] = []rune{} + r.search_index = 0 + r.disable_raw_mode() + if r.current.len == 0 { + return error('empty line') + } + return r.current +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` returned by `read_line_utf8`). +pub fn (mut r Readline) read_line(prompt string) ?string { + s := r.read_line_utf8(prompt) ? + return s.string() +} + +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +// NOTE that this version of `read_line_utf8` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line_utf8(prompt string) ?[]rune { + mut r := Readline{} + s := r.read_line_utf8(prompt) ? + return s +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` as returned by `read_line_utf8`). +// NOTE that this version of `read_line` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line(prompt string) ?string { + mut r := Readline{} + s := r.read_line(prompt) ? + return s +} + +// analyse returns an `Action` based on the type of input byte given in `c`. +fn (r Readline) analyse(c int) Action { + match byte(c) { + `\0`, 0x3, 0x4, 255 { + return .eof + } // NUL, End of Text, End of Transmission + `\n`, `\r` { + return .commit_line + } + `\f` { + return .clear_screen + } // CTRL + L + `\b`, 127 { + return .delete_left + } // BS, DEL + 27 { + return r.analyse_control() + } // ESC + 1 { + return .move_cursor_begining + } // ^A + 5 { + return .move_cursor_end + } // ^E + 26 { + return .suspend + } // CTRL + Z, SUB + else { + if c >= ` ` { + return Action.insert_character + } + return Action.nothing + } + } +} + +// analyse_control returns an `Action` based on the type of input read by `read_char`. +fn (r Readline) analyse_control() Action { + c := r.read_char() + match byte(c) { + `[` { + sequence := r.read_char() + match byte(sequence) { + `C` { return .move_cursor_right } + `D` { return .move_cursor_left } + `B` { return .history_next } + `A` { return .history_previous } + `1` { return r.analyse_extended_control() } + `2`, `3` { return r.analyse_extended_control_no_eat(byte(sequence)) } + else {} + } + } + else {} + } + /* + //TODO +match c { + case `[`: + sequence := r.read_char() + match sequence { + case `C`: return .move_cursor_right + case `D`: return .move_cursor_left + case `B`: return .history_next + case `A`: return .history_previous + case `1`: return r.analyse_extended_control() + case `2`: return r.analyse_extended_control_no_eat(sequence) + case `3`: return r.analyse_extended_control_no_eat(sequence) + case `9`: + foo() + bar() + else: + } + else: +} + */ + return .nothing +} + +// analyse_extended_control returns an `Action` based on the type of input read by `read_char`. +// analyse_extended_control specialises in cursor control. +fn (r Readline) analyse_extended_control() Action { + r.read_char() // Removes ; + c := r.read_char() + match byte(c) { + `5` { + direction := r.read_char() + match byte(direction) { + `C` { return .move_cursor_word_right } + `D` { return .move_cursor_word_left } + else {} + } + } + else {} + } + return .nothing +} + +// analyse_extended_control_no_eat returns an `Action` based on the type of input byte given in `c`. +// analyse_extended_control_no_eat specialises in detection of delete and insert keys. +fn (r Readline) analyse_extended_control_no_eat(last_c byte) Action { + c := r.read_char() + match byte(c) { + `~` { + match last_c { + `3` { return .delete_right } // Suppr key + `2` { return .overwrite } + else {} + } + } + else {} + } + return .nothing +} + +// execute executes the corresponding methods on `Readline` based on `a Action` and `c int` arguments. +fn (mut r Readline) execute(a Action, c int) bool { + match a { + .eof { return r.eof() } + .insert_character { r.insert_character(c) } + .commit_line { return r.commit_line() } + .delete_left { r.delete_character() } + .delete_right { r.suppr_character() } + .move_cursor_left { r.move_cursor_left() } + .move_cursor_right { r.move_cursor_right() } + .move_cursor_begining { r.move_cursor_begining() } + .move_cursor_end { r.move_cursor_end() } + .move_cursor_word_left { r.move_cursor_word_left() } + .move_cursor_word_right { r.move_cursor_word_right() } + .history_previous { r.history_previous() } + .history_next { r.history_next() } + .overwrite { r.switch_overwrite() } + .clear_screen { r.clear_screen() } + .suspend { r.suspend() } + else {} + } + return false +} + +// get_screen_columns returns the number of columns (`width`) in the terminal. +fn get_screen_columns() int { + ws := Winsize{} + cols := if C.ioctl(1, C.TIOCGWINSZ, &ws) == -1 { 80 } else { int(ws.ws_col) } + return cols +} + +// shift_cursor warps the cursor to `xpos` with `yoffset`. +fn shift_cursor(xpos int, yoffset int) { + if yoffset != 0 { + if yoffset > 0 { + term.cursor_down(yoffset) + } else { + term.cursor_up(-yoffset) + } + } + // Absolute X position + print('\x1b[${xpos + 1}G') +} + +// calculate_screen_position returns a position `[x, y]int` based on various terminal attributes. +fn calculate_screen_position(x_in int, y_in int, screen_columns int, char_count int, inp []int) []int { + mut out := inp.clone() + mut x := x_in + mut y := y_in + out[0] = x + out[1] = y + for chars_remaining := char_count; chars_remaining > 0; { + chars_this_row := if (x + chars_remaining) < screen_columns { + chars_remaining + } else { + screen_columns - x + } + out[0] = x + chars_this_row + out[1] = y + chars_remaining -= chars_this_row + x = 0 + y++ + } + if out[0] == screen_columns { + out[0] = 0 + out[1]++ + } + return out +} + +// get_prompt_offset computes the length of the `prompt` `string` argument. +fn get_prompt_offset(prompt string) int { + mut len := 0 + for i := 0; i < prompt.len; i++ { + if prompt[i] == `\e` { + for ; i < prompt.len && prompt[i] != `m`; i++ { + } + } else { + len = len + 1 + } + } + return prompt.len - len +} + +// refresh_line redraws the current line, including the prompt. +fn (mut r Readline) refresh_line() { + mut end_of_input := [0, 0] + end_of_input = calculate_screen_position(r.prompt.len, 0, get_screen_columns(), r.current.len, + end_of_input) + end_of_input[1] += r.current.filter(it == `\n`).len + mut cursor_pos := [0, 0] + cursor_pos = calculate_screen_position(r.prompt.len, 0, get_screen_columns(), r.cursor, + cursor_pos) + shift_cursor(0, -r.cursor_row_offset) + term.erase_toend() + print(r.prompt) + print(r.current.string()) + if end_of_input[0] == 0 && end_of_input[1] > 0 { + print('\n') + } + shift_cursor(cursor_pos[0] - r.prompt_offset, -(end_of_input[1] - cursor_pos[1])) + r.cursor_row_offset = cursor_pos[1] +} + +// eof ends the line *without* a newline. +fn (mut r Readline) eof() bool { + r.previous_lines.insert(1, r.current) + r.cursor = r.current.len + if r.is_tty { + r.refresh_line() + } + return true +} + +// insert_character inserts the character `c` at current cursor position. +fn (mut r Readline) insert_character(c int) { + if !r.overwrite || r.cursor == r.current.len { + r.current.insert(r.cursor, c) + } else { + r.current[r.cursor] = rune(c) + } + r.cursor++ + // Refresh the line to add the new character + if r.is_tty { + r.refresh_line() + } +} + +// Removes the character behind cursor. +fn (mut r Readline) delete_character() { + if r.cursor <= 0 { + return + } + r.cursor-- + r.current.delete(r.cursor) + r.refresh_line() +} + +// suppr_character removes (suppresses) the character in front of the cursor. +fn (mut r Readline) suppr_character() { + if r.cursor >= r.current.len { + return + } + r.current.delete(r.cursor) + r.refresh_line() +} + +// commit_line adds a line break and then stops the main loop. +fn (mut r Readline) commit_line() bool { + r.previous_lines.insert(1, r.current) + r.current << `\n` + r.cursor = r.current.len + if r.is_tty { + r.refresh_line() + println('') + } + return true +} + +// move_cursor_left moves the cursor relative one cell to the left. +fn (mut r Readline) move_cursor_left() { + if r.cursor > 0 { + r.cursor-- + r.refresh_line() + } +} + +// move_cursor_right moves the cursor relative one cell to the right. +fn (mut r Readline) move_cursor_right() { + if r.cursor < r.current.len { + r.cursor++ + r.refresh_line() + } +} + +// move_cursor_begining moves the cursor to the beginning of the current line. +fn (mut r Readline) move_cursor_begining() { + r.cursor = 0 + r.refresh_line() +} + +// move_cursor_end moves the cursor to the end of the current line. +fn (mut r Readline) move_cursor_end() { + r.cursor = r.current.len + r.refresh_line() +} + +// is_break_character returns true if the character is considered as a word-breaking character. +fn (r Readline) is_break_character(c string) bool { + break_characters := ' \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:\'",<.>/?' + return break_characters.contains(c) +} + +// move_cursor_word_left moves the cursor relative one word length worth to the left. +fn (mut r Readline) move_cursor_word_left() { + if r.cursor > 0 { + for ; r.cursor > 0 && r.is_break_character(r.current[r.cursor - 1].str()); r.cursor-- { + } + for ; r.cursor > 0 && !r.is_break_character(r.current[r.cursor - 1].str()); r.cursor-- { + } + r.refresh_line() + } +} + +// move_cursor_word_right moves the cursor relative one word length worth to the right. +fn (mut r Readline) move_cursor_word_right() { + if r.cursor < r.current.len { + for ; r.cursor < r.current.len && r.is_break_character(r.current[r.cursor].str()); r.cursor++ { + } + for ; r.cursor < r.current.len && !r.is_break_character(r.current[r.cursor].str()); r.cursor++ { + } + r.refresh_line() + } +} + +// switch_overwrite toggles Readline `overwrite` mode on/off. +fn (mut r Readline) switch_overwrite() { + r.overwrite = !r.overwrite +} + +// clear_screen clears the current terminal window contents and positions the cursor at top left. +fn (mut r Readline) clear_screen() { + term.set_cursor_position(x: 1, y: 1) + term.erase_clear() + r.refresh_line() +} + +// history_previous sets current line to the content of the previous line in the history buffer. +fn (mut r Readline) history_previous() { + if r.search_index + 2 >= r.previous_lines.len { + return + } + if r.search_index == 0 { + r.previous_lines[0] = r.current + } + r.search_index++ + r.current = r.previous_lines[r.search_index] + r.cursor = r.current.len + r.refresh_line() +} + +// history_next sets current line to the content of the next line in the history buffer. +fn (mut r Readline) history_next() { + if r.search_index <= 0 { + return + } + r.search_index-- + r.current = r.previous_lines[r.search_index] + r.cursor = r.current.len + r.refresh_line() +} + +// suspend sends the `SIGSTOP` signal to the terminal. +fn (mut r Readline) suspend() { + is_standalone := os.getenv('VCHILD') != 'true' + r.disable_raw_mode() + if !is_standalone { + // We have to SIGSTOP the parent v process + ppid := C.getppid() + C.kill(ppid, C.SIGSTOP) + } + C.raise(C.SIGSTOP) + r.enable_raw_mode() + r.refresh_line() + if r.is_tty { + r.refresh_line() + } +} diff --git a/v_windows/v/vlib/readline/readline_test.v b/v_windows/v/vlib/readline/readline_test.v new file mode 100644 index 0000000..ab5154f --- /dev/null +++ b/v_windows/v/vlib/readline/readline_test.v @@ -0,0 +1,20 @@ +import readline { Readline } + +fn no_lines(s string) string { + return s.replace('\n', ' ') +} + +fn test_struct_readline() { + // mut rl := Readline{} + // eprintln('rl: $rl') + // line := rl.read_line('Please, enter your name: ') or { panic(err) } + // eprintln('line: $line') + mut methods := []string{} + $for method in Readline.methods { + // eprintln(' method: $method.name | ' + no_lines('$method')) + methods << method.name + } + // eprintln('methods: $methods') + assert 'read_line_utf8' in methods + assert 'read_line' in methods +} diff --git a/v_windows/v/vlib/readline/readline_windows.c.v b/v_windows/v/vlib/readline/readline_windows.c.v new file mode 100644 index 0000000..887efdb --- /dev/null +++ b/v_windows/v/vlib/readline/readline_windows.c.v @@ -0,0 +1,74 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// TODO Windows version needs to be implemented. +// Will serve as more advanced input method +// based on the work of https://github.com/AmokHuginnsson/replxx +// +module readline + +import os + +// Only use standard os.get_line +// Need implementation for readline capabilities +// +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +pub fn (mut r Readline) read_line_utf8(prompt string) ?[]rune { + r.current = []rune{} + r.cursor = 0 + r.prompt = prompt + r.search_index = 0 + if r.previous_lines.len <= 1 { + r.previous_lines << []rune{} + r.previous_lines << []rune{} + } else { + r.previous_lines[0] = []rune{} + } + print(r.prompt) + r.current = os.get_raw_line().runes() + r.previous_lines[0] = []rune{} + r.search_index = 0 + if r.current.len == 0 { + return error('empty line') + } + return r.current +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` returned by `read_line_utf8`). +pub fn (mut r Readline) read_line(prompt string) ?string { + s := r.read_line_utf8(prompt) ? + return s.string() +} + +// read_line_utf8 blocks execution in a loop and awaits user input +// characters from a terminal until `EOF` or `Enter` key is encountered +// in the input stream. +// read_line_utf8 returns the complete input line as an UTF-8 encoded `[]rune` or +// an error if the line is empty. +// The `prompt` `string` is output as a prefix text for the input capturing. +// read_line_utf8 is the main method of the `readline` module and `Readline` struct. +// NOTE that this version of `read_line_utf8` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line_utf8(prompt string) ?[]rune { + mut r := Readline{} + s := r.read_line_utf8(prompt) ? + return s +} + +// read_line does the same as `read_line_utf8` but returns user input as a `string`. +// (As opposed to `[]rune` as returned by `read_line_utf8`). +// NOTE that this version of `read_line` is a standalone function without +// persistent functionalities (e.g. history). +pub fn read_line(prompt string) ?string { + mut r := Readline{} + s := r.read_line(prompt) ? + return s +} diff --git a/v_windows/v/vlib/regex/README.md b/v_windows/v/vlib/regex/README.md new file mode 100644 index 0000000..0faa833 --- /dev/null +++ b/v_windows/v/vlib/regex/README.md @@ -0,0 +1,874 @@ +# V RegEx (Regular expression) 1.0 alpha + +[TOC] + +## Introduction + +Here are the assumptions made during the writing of the implementation, that +are valid for all the `regex` module features: + +1. The matching stops at the end of the string, *not* at newline characters. + +2. The basic atomic elements of this regex engine are the tokens. +In a query string a simple character is a token. + + +## Differences with PCRE: + +NB: We must point out that the **V-Regex module is not PCRE compliant** and thus +some behaviour will be different. This difference is due to the V philosophy, +to have one way and keep it simple. + +The main differences can be summarized in the following points: + +- The basic element **is the token not the sequence of symbols**, and the most +simple token, is a single character. + +- `|` **the OR operator acts on tokens,** for example `abc|ebc` is not +`abc` OR `ebc`. Instead it is evaluated like `ab`, followed by `c OR e`, +followed by `bc`, because the **token is the base element**, +not the sequence of symbols. + +- The **match operation stops at the end of the string**. It does *NOT* stop +at new line characters. + + +## Tokens + +The tokens are the atomic units, used by this regex engine. +They can be one of the following: + + +### Simple char + +This token is a simple single character like `a` or `b` etc. + + +### Match positional delimiters + +`^` Matches the start of the string. + +`$` Matches the end of the string. + + +### Char class (cc) + +The character classes match all the chars specified inside. Use square +brackets `[ ]` to enclose them. + +The sequence of the chars in the character class, is evaluated with an OR op. + +For example, the cc `[abc]`, matches any character, that is `a` or `b` or `c`, +but it doesn't match `C` or `z`. + +Inside a cc, it is possible to specify a "range" of characters, for example +`[ad-h]` is equivalent to writing `[adefgh]`. + +A cc can have different ranges at the same time, for example `[a-zA-z0-9]` +matches all the latin lowercase, uppercase and numeric characters. + +It is possible to negate the meaning of a cc, using the caret char at the +start of the cc like this: `[^abc]` . That matches every char that is NOT +`a` or `b` or `c`. + +A cc can contain meta-chars like: `[a-z\d]`, that match all the lowercase +latin chars `a-z` and all the digits `\d`. + +It is possible to mix all the properties of the char class together. + +NB: In order to match the `-` (minus) char, it must be preceded by + a backslash in the cc, for example `[\-_\d\a]` will match: + `-` minus, + `_` underscore, + `\d` numeric chars, + `\a` lower case chars. + +### Meta-chars + +A meta-char is specified by a backslash, before a character. +For example `\w` is the meta-char `w`. + +A meta-char can match different types of characters. + +* `\w` matches a word char char `[a-zA-Z0-9_]` +* `\W` matches a non word char +* `\d` matches a digit `[0-9]` +* `\D` matches a non digit +* `\s` matches a space char, one of `[' ','\t','\n','\r','\v','\f']` +* `\S` matches a non space char +* `\a` matches only a lowercase char `[a-z]` +* `\A` matches only an uppercase char `[A-Z]` + +### Quantifier + +Each token can have a quantifier, that specifies how many times the character +must be matched. + +#### **Short quantifiers** + +- `?` matches 0 or 1 time, `a?b` matches both `ab` or `b` +- `+` matches *at least* 1 time, for example, `a+` matches both `aaa` or `a` +- `*` matches 0 or more times, for example, `a*b` matches `aaab`, `ab` or `b` + +#### **Long quantifiers** + +- `{x}` matches exactly x times, `a{2}` matches `aa`, but not `aaa` or `a` +- `{min,}` matches at least min times, `a{2,}` matches `aaa` or `aa`, not `a` +- `{,max}` matches at least 0 times and at maximum max times, + for example, `a{,2}` matches `a` and `aa`, but doesn't match `aaa` +- `{min,max}` matches from min times, to max times, for example + `a{2,3}` matches `aa` and `aaa`, but doesn't match `a` or `aaaa` + +A long quantifier, may have a `greedy off` flag, that is the `?` +character after the brackets. `{2,4}?` means to match the minimum +number of possible tokens, in this case 2. + +### Dot char + +The dot is a particular meta-char, that matches "any char". + +It is simpler to explain it with an example: + +Suppose you have `abccc ddeef` as a source string, that you want to parse +with a regex. The following table show the query strings and the result of +parsing source string. + +| query string | result | +|--------------|-------------| +| `.*c` | `abc` | +| `.*dd` | `abcc dd` | +| `ab.*e` | `abccc dde` | +| `ab.{3} .*e` | `abccc dde` | +The dot matches any character, until the next token match is satisfied. + +**Important Note:** *Consecutive dots, for example `...`, are not allowed.* +*This will cause a syntax error. Use a quantifier instead.* + +### OR token + +The token `|`, means a logic OR operation between two consecutive tokens, +i.e. `a|b` matches a character that is `a` or `b`. + +The OR token can work in a "chained way": `a|(b)|cd ` means test first `a`, +if the char is not `a`, then test the group `(b)`, and if the group doesn't +match too, finally test the token `c`. + +NB: ** unlike in PCRE, the OR operation works at token level!** +It doesn't work at concatenation level! + +That also means, that a query string like `abc|bde` is not equal to +`(abc)|(bde)`, but instead to `ab(c|b)de. +The OR operation works only for `c|b`, not at char concatenation level. + +### Groups + +Groups are a method to create complex patterns with repetitions of blocks +of tokens. The groups are delimited by round brackets `( )`. Groups can be +nested. Like all other tokens, groups can have a quantifier too. + +`c(pa)+z` match `cpapaz` or `cpaz` or `cpapapaz` . + +`(c(pa)+z ?)+` matches `cpaz cpapaz cpapapaz` or `cpapaz` + +Lets analyze this last case, first we have the group `#0`, that is the most +outer round brackets `(...)+`. This group has a quantifier `+`, that say to +match its content *at least one time*. + +Then we have a simple char token `c`, and a second group `#1`: `(pa)+`. +This group also tries to match the sequence `pa`, *at least one time*, +as specified by the `+` quantifier. + +Then, we have another simple token `z` and another simple token ` ?`, +i.e. the space char (ascii code 32) followed by the `?` quantifier, +which means that the preceding space should be matched 0 or 1 time. + +This explains why the `(c(pa)+z ?)+` query string, +can match `cpaz cpapaz cpapapaz` . + +In this implementation the groups are "capture groups". This means that the +last temporal result for each group, can be retrieved from the `RE` struct. + +The "capture groups" are stored as indexes in the field `groups`, +that is an `[]int` inside the `RE` struct. + +**example:** + +```v oksyntax +text := 'cpaz cpapaz cpapapaz' +query := r'(c(pa)+z ?)+' +mut re := regex.regex_opt(query) or { panic(err) } +println(re.get_query()) +// #0(c#1(pa)+z ?)+ +// #0 and #1 are the ids of the groups, are shown if re.debug is 1 or 2 +start, end := re.match_string(text) +// [start=0, end=20] match => [cpaz cpapaz cpapapaz] +mut gi := 0 +for gi < re.groups.len { + if re.groups[gi] >= 0 { + println('${gi / 2} :[${text[re.groups[gi]..re.groups[gi + 1]]}]') + } + gi += 2 +} +// groups captured +// 0 :[cpapapaz] +// 1 :[pa] +``` + +**note:** *to show the `group id number` in the result of the `get_query()`* +*the flag `debug` of the RE object must be `1` or `2`* + +In order to simplify the use of the captured groups, it possible to use the +utility function: `get_group_list`. + +This function return a list of groups using this support struct: + +```v oksyntax +pub struct Re_group { +pub: + start int = -1 + end int = -1 +} +``` + +Here an example of use: + +```v oksyntax +/* +This simple function converts an HTML RGB value with 3 or 6 hex digits to +an u32 value, this function is not optimized and it is only for didatical +purpose. Example: #A0B0CC #A9F +*/ +fn convert_html_rgb(in_col string) u32 { + mut n_digit := if in_col.len == 4 { 1 } else { 2 } + mut col_mul := if in_col.len == 4 { 4 } else { 0 } + // this is the regex query, it use the V string interpolation to customize the regex query + // NOTE: if you want use escaped code you must use the r"" (raw) strings, + // *** please remember that the V interpoaltion doesn't work on raw strings. *** + query := '#([a-fA-F0-9]{$n_digit})([a-fA-F0-9]{$n_digit})([a-fA-F0-9]{$n_digit})' + mut re := regex.regex_opt(query) or { panic(err) } + start, end := re.match_string(in_col) + println('start: $start, end: $end') + mut res := u32(0) + if start >= 0 { + group_list := re.get_group_list() // this is the utility function + r := ('0x' + in_col[group_list[0].start..group_list[0].end]).int() << col_mul + g := ('0x' + in_col[group_list[1].start..group_list[1].end]).int() << col_mul + b := ('0x' + in_col[group_list[2].start..group_list[2].end]).int() << col_mul + println('r: $r g: $g b: $b') + res = u32(r) << 16 | u32(g) << 8 | u32(b) + } + return res +} +``` + +Others utility functions are `get_group_by_id` and `get_group_bounds_by_id` +that get directly the string of a group using its `id`: + +```v ignore +txt := "my used string...." +for g_index := 0; g_index < re.group_count ; g_index++ { + println("#${g_index} [${re.get_group_by_id(txt, g_index)}] \ + bounds: ${re.get_group_bounds_by_id(g_index)}") +} +``` + +More helper functions are listed in the **Groups query functions** section. + +### Groups Continuous saving + +In particular situations, it is useful to have a continuous group saving. +This is possible by initializing the `group_csave` field in the `RE` struct. + +This feature allows you to collect data in a continuous/streaming way. + +In the example, we can pass a text, followed by an integer list, +that we wish to collect. To achieve this task, we can use the continuous +group saving, by enabling the right flag: `re.group_csave_flag = true`. + +The `.group_csave` array will be filled then, following this logic: + +`re.group_csave[0]` - number of total saved records +`re.group_csave[1+n*3]` - id of the saved group +`re.group_csave[1+n*3]` - start index in the source string of the saved group +`re.group_csave[1+n*3]` - end index in the source string of the saved group + +The regex will save groups, until it finishes, or finds that the array has no +more space. If the space ends, no error is raised, and further records will +not be saved. + +```v ignore +import regex +fn main(){ + txt := "http://www.ciao.mondo/hello/pippo12_/pera.html" + query := r"(?Phttps?)|(?Pftps?)://(?P[\w_]+.)+" + + mut re := regex.regex_opt(query) or { panic(err) } + //println(re.get_code()) // uncomment to see the print of the regex execution code + re.debug=2 // enable maximum log + println("String: ${txt}") + println("Query : ${re.get_query()}") + re.debug=0 // disable log + re.group_csave_flag = true + start, end := re.match_string(txt) + if start >= 0 { + println("Match ($start, $end) => [${txt[start..end]}]") + } else { + println("No Match") + } + + if re.group_csave_flag == true && start >= 0 && re.group_csave.len > 0{ + println("cg: $re.group_csave") + mut cs_i := 1 + for cs_i < re.group_csave[0]*3 { + g_id := re.group_csave[cs_i] + st := re.group_csave[cs_i+1] + en := re.group_csave[cs_i+2] + println("cg[$g_id] $st $en:[${txt[st..en]}]") + cs_i += 3 + } + } +} +``` + +The output will be: + +``` +String: http://www.ciao.mondo/hello/pippo12_/pera.html +Query : #0(?Phttps?)|{8,14}#0(?Pftps?)://#1(?P[\w_]+.)+ +Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html] +cg: [8, 0, 0, 4, 1, 7, 11, 1, 11, 16, 1, 16, 22, 1, 22, 28, 1, 28, 37, 1, 37, 42, 1, 42, 46] +cg[0] 0 4:[http] +cg[1] 7 11:[www.] +cg[1] 11 16:[ciao.] +cg[1] 16 22:[mondo/] +cg[1] 22 28:[hello/] +cg[1] 28 37:[pippo12_/] +cg[1] 37 42:[pera.] +cg[1] 42 46:[html] +``` + +### Named capturing groups + +This regex module supports partially the question mark `?` PCRE syntax for groups. + +`(?:abcd)` **non capturing group**: the content of the group will not be saved. + +`(?Pabcdef)` **named group:** the group content is saved and labeled +as `mygroup`. + +The label of the groups is saved in the `group_map` of the `RE` struct, +that is a map from `string` to `int`, where the value is the index in +`group_csave` list of indexes. + +Here is an example for how to use them: +```v ignore +import regex +fn main(){ + txt := "http://www.ciao.mondo/hello/pippo12_/pera.html" + query := r"(?Phttps?)|(?Pftps?)://(?P[\w_]+.)+" + + mut re := regex.regex_opt(query) or { panic(err) } + //println(re.get_code()) // uncomment to see the print of the regex execution code + re.debug=2 // enable maximum log + println("String: ${txt}") + println("Query : ${re.get_query()}") + re.debug=0 // disable log + start, end := re.match_string(txt) + if start >= 0 { + println("Match ($start, $end) => [${txt[start..end]}]") + } else { + println("No Match") + } + + for name in re.group_map.keys() { + println("group:'$name' \t=> [${re.get_group_by_name(txt, name)}] \ + bounds: ${re.get_group_bounds_by_name(name)}") + } +} +``` + +Output: + +``` +String: http://www.ciao.mondo/hello/pippo12_/pera.html +Query : #0(?Phttps?)|{8,14}#0(?Pftps?)://#1(?P[\w_]+.)+ +Match (0, 46) => [http://www.ciao.mondo/hello/pippo12_/pera.html] +group:'format' => [http] bounds: (0, 4) +group:'token' => [html] bounds: (42, 46) +``` + +In order to simplify the use of the named groups, it is possible to +use a name map in the `re` struct, using the function `re.get_group_by_name`. + +Here is a more complex example of using them: +```v oksyntax +// This function demostrate the use of the named groups +fn convert_html_rgb_n(in_col string) u32 { + mut n_digit := if in_col.len == 4 { 1 } else { 2 } + mut col_mul := if in_col.len == 4 { 4 } else { 0 } + query := '#(?P[a-fA-F0-9]{$n_digit})' + '(?P[a-fA-F0-9]{$n_digit})' + + '(?P[a-fA-F0-9]{$n_digit})' + mut re := regex.regex_opt(query) or { panic(err) } + start, end := re.match_string(in_col) + println('start: $start, end: $end') + mut res := u32(0) + if start >= 0 { + red_s, red_e := re.get_group_by_name('red') + r := ('0x' + in_col[red_s..red_e]).int() << col_mul + green_s, green_e := re.get_group_by_name('green') + g := ('0x' + in_col[green_s..green_e]).int() << col_mul + blue_s, blue_e := re.get_group_by_name('blue') + b := ('0x' + in_col[blue_s..blue_e]).int() << col_mul + println('r: $r g: $g b: $b') + res = u32(r) << 16 | u32(g) << 8 | u32(b) + } + return res +} +``` + +Other utilities are `get_group_by_name` and `get_group_bounds_by_name`, +that return the string of a group using its `name`: + +```v ignore +txt := "my used string...." +for name in re.group_map.keys() { + println("group:'$name' \t=> [${re.get_group_by_name(txt, name)}] \ + bounds: ${re.get_group_bounds_by_name(name)}") +} +``` + + + +### Groups query functions + +These functions are helpers to query the captured groups + +```v ignore +// get_group_bounds_by_name get a group boundaries by its name +pub fn (re RE) get_group_bounds_by_name(group_name string) (int, int) + +// get_group_by_name get a group string by its name +pub fn (re RE) get_group_by_name(group_name string) string + +// get_group_by_id get a group boundaries by its id +pub fn (re RE) get_group_bounds_by_id(group_id int) (int,int) + +// get_group_by_id get a group string by its id +pub fn (re RE) get_group_by_id(in_txt string, group_id int) string + +struct Re_group { +pub: + start int = -1 + end int = -1 +} + +// get_group_list return a list of Re_group for the found groups +pub fn (re RE) get_group_list() []Re_group +``` + +## Flags + +It is possible to set some flags in the regex parser, that change +the behavior of the parser itself. + +```v ignore +// example of flag settings +mut re := regex.new() +re.flag = regex.F_BIN +``` + +- `F_BIN`: parse a string as bytes, utf-8 management disabled. + +- `F_EFM`: exit on the first char matches in the query, used by the + find function. + +- `F_MS`: matches only if the index of the start match is 0, + same as `^` at the start of the query string. + +- `F_ME`: matches only if the end index of the match is the last char + of the input string, same as `$` end of query string. + +- `F_NL`: stop the matching if found a new line char `\n` or `\r` + +## Functions + +### Initializer + +These functions are helper that create the `RE` struct, +a `RE` struct can be created manually if you needed. + +#### **Simplified initializer** + +```v ignore +// regex create a regex object from the query string and compile it +pub fn regex_opt(in_query string) ?RE +``` + +#### **Base initializer** + +```v ignore +// new_regex create a REgex of small size, usually sufficient for ordinary use +pub fn new() RE + +``` +#### **Custom initialization** +For some particular needs, it is possible to initialize a fully customized regex: +```v ignore +pattern = r"ab(.*)(ac)" +// init custom regex +mut re := regex.RE{} +// max program length, can not be longer then the pattern +re.prog = []Token {len: pattern.len + 1} +// can not be more char class the the length of the pattern +re.cc = []CharClass{len: pattern.len} + +re.group_csave_flag = false // true enable continuos group saving if needed +re.group_max_nested = 128 // set max 128 group nested possible +re.group_max = pattern.len>>1 // we can't have more groups than the half of the pattern legth +re.group_stack = []int{len: re.group_max, init: -1} +re.group_data = []int{len: re.group_max, init: -1} +``` +### Compiling + +After an initializer is used, the regex expression must be compiled with: + +```v ignore +// compile compiles the REgex returning an error if the compilation fails +pub fn (re mut RE) compile_opt(in_txt string) ? +``` + +### Matching Functions + +These are the matching functions + +```v ignore +// match_string try to match the input string, return start and end index if found else start is -1 +pub fn (re mut RE) match_string(in_txt string) (int,int) + +``` + +## Find and Replace + +There are the following find and replace functions: + +#### Find functions + +```v ignore +// find try to find the first match in the input string +// return start and end index if found else start is -1 +pub fn (re mut RE) find(in_txt string) (int,int) + +// find_all find all the "non overlapping" occurrences of the matching pattern +// return a list of start end indexes like: [3,4,6,8] +// the matches are [3,4] and [6,8] +pub fn (re mut RE) find_all(in_txt string) []int + +// find_all find all the "non overlapping" occurrences of the matching pattern +// return a list of strings +// the result is like ["first match","secon match"] +pub fn (mut re RE) find_all_str(in_txt string) []string +``` + +#### Replace functions + +```v ignore +// replace return a string where the matches are replaced with the repl_str string, +// this function support groups in the replace string +pub fn (re mut RE) replace(in_txt string, repl string) string +``` + +replace string can include groups references: + +```v ignore +txt := "Today it is a good day." +query := r'(a\w)[ ,.]' +mut re := regex.regex_opt(query)? +res := re.replace(txt, r"__[\0]__") +``` + +in this example we used the group `0` in the replace string: `\0`, the result will be: + +``` +Today it is a good day. => Tod__[ay]__it is a good d__[ay]__ +``` + +**Note:** in the replace strings can be used only groups from `0` to `9`. + +If the usage of `groups` in the replace process, is not needed, it is possible +to use a quick function: + +```v ignore +// replace_simple return a string where the matches are replaced with the replace string +pub fn (mut re RE) replace_simple(in_txt string, repl string) string +``` + +#### Custom replace function + +For complex find and replace operations, you can use `replace_by_fn` . +The `replace_by_fn`, uses a custom replace callback function, thus +allowing customizations. The custom callback function is called for +every non overlapped find. + +The custom callback function must be of the type: + +```v ignore +// type of function used for custom replace +// in_txt source text +// start index of the start of the match in in_txt +// end index of the end of the match in in_txt +// --- the match is in in_txt[start..end] --- +fn (re RE, in_txt string, start int, end int) string +``` + +The following example will clarify its usage: + +```v ignore +import regex +// customized replace functions +// it will be called on each non overlapped find +fn my_repl(re regex.RE, in_txt string, start int, end int) string { + g0 := re.get_group_by_id(in_txt, 0) + g1 := re.get_group_by_id(in_txt, 1) + g2 := re.get_group_by_id(in_txt, 2) + return "*$g0*$g1*$g2*" +} + +fn main(){ + txt := "today [John] is gone to his house with (Jack) and [Marie]." + query := r"(.)(\A\w+)(.)" + + mut re := regex.regex_opt(query) or { panic(err) } + + result := re.replace_by_fn(txt, my_repl) + println(result) +} +``` + +Output: + +``` +today *[*John*]* is gone to his house with *(*Jack*)* and *[*Marie*]*. +``` + + + +## Debugging + +This module has few small utilities to you write regex patterns. + +### **Syntax errors highlight** + +The next example code shows how to visualize regex pattern syntax errors +in the compilation phase: + +```v oksyntax +query := r'ciao da ab[ab-]' +// there is an error, a range not closed!! +mut re := new() +re.compile_opt(query) or { println(err) } +// output!! +// query: ciao da ab[ab-] +// err : ----------^ +// ERROR: ERR_SYNTAX_ERROR +``` + +### **Compiled code** + +It is possible to view the compiled code calling the function `get_query()`. +The result will be something like this: + +``` +======================================== +v RegEx compiler v 1.0 alpha output: +PC: 0 ist: 92000000 ( GROUP_START #:0 { 1, 1} +PC: 1 ist: 98000000 . DOT_CHAR nx chk: 4 { 1, 1} +PC: 2 ist: 94000000 ) GROUP_END #:0 { 1, 1} +PC: 3 ist: 92000000 ( GROUP_START #:1 { 1, 1} +PC: 4 ist: 90000000 [\A] BSLS { 1, 1} +PC: 5 ist: 90000000 [\w] BSLS { 1,MAX} +PC: 6 ist: 94000000 ) GROUP_END #:1 { 1, 1} +PC: 7 ist: 92000000 ( GROUP_START #:2 { 1, 1} +PC: 8 ist: 98000000 . DOT_CHAR nx chk: -1 last! { 1, 1} +PC: 9 ist: 94000000 ) GROUP_END #:2 { 1, 1} +PC: 10 ist: 88000000 PROG_END { 0, 0} +======================================== + +``` + +`PC`:`int` is the program counter or step of execution, each single step is a token. + +`ist`:`hex` is the token instruction id. + +`[a]` is the char used by the token. + +`query_ch` is the type of token. + +`{m,n}` is the quantifier, the greedy off flag `?` will be showed if present in the token + +### **Log debug** + +The log debugger allow to print the status of the regex parser when the +parser is running. It is possible to have two different levels of +debug information: 1 is normal, while 2 is verbose. + +Here is an example: + +*normal* - list only the token instruction with their values + +```ignore +// re.flag = 1 // log level normal +flags: 00000000 +# 2 s: ist_load PC: i,ch,len:[ 0,'a',1] f.m:[ -1, -1] query_ch: [a]{1,1}:0 (#-1) +# 5 s: ist_load PC: i,ch,len:[ 1,'b',1] f.m:[ 0, 0] query_ch: [b]{2,3}:0? (#-1) +# 7 s: ist_load PC: i,ch,len:[ 2,'b',1] f.m:[ 0, 1] query_ch: [b]{2,3}:1? (#-1) +# 10 PROG_END +``` + +*verbose* - list all the instructions and states of the parser + +```ignore +flags: 00000000 +# 0 s: start PC: NA +# 1 s: ist_next PC: NA +# 2 s: ist_load PC: i,ch,len:[ 0,'a',1] f.m:[ -1, -1] query_ch: [a]{1,1}:0 (#-1) +# 3 s: ist_quant_p PC: i,ch,len:[ 1,'b',1] f.m:[ 0, 0] query_ch: [a]{1,1}:1 (#-1) +# 4 s: ist_next PC: NA +# 5 s: ist_load PC: i,ch,len:[ 1,'b',1] f.m:[ 0, 0] query_ch: [b]{2,3}:0? (#-1) +# 6 s: ist_quant_p PC: i,ch,len:[ 2,'b',1] f.m:[ 0, 1] query_ch: [b]{2,3}:1? (#-1) +# 7 s: ist_load PC: i,ch,len:[ 2,'b',1] f.m:[ 0, 1] query_ch: [b]{2,3}:1? (#-1) +# 8 s: ist_quant_p PC: i,ch,len:[ 3,'b',1] f.m:[ 0, 2] query_ch: [b]{2,3}:2? (#-1) +# 9 s: ist_next PC: NA +# 10 PROG_END +# 11 PROG_END +``` + +the columns have the following meaning: + +`# 2` number of actual steps from the start of parsing + +`s: ist_next` state of the present step + +`PC: 1` program counter of the step + +`=>7fffffff ` hex code of the instruction + +`i,ch,len:[ 0,'a',1]` `i` index in the source string, `ch` the char parsed, +`len` the length in byte of the char parsed + +`f.m:[ 0, 1]` `f` index of the first match in the source string, `m` index that is actual matching + +`query_ch: [b]` token in use and its char + +`{2,3}:1?` quantifier `{min,max}`, `:1` is the actual counter of repetition, +`?` is the greedy off flag if present. + +### **Custom Logger output** + +The debug functions output uses the `stdout` as default, +it is possible to provide an alternative output, by setting a custom +output function: + +```v oksyntax +// custom print function, the input will be the regex debug string +fn custom_print(txt string) { + println('my log: $txt') +} + +mut re := new() +re.log_func = custom_print +// every debug output from now will call this function +``` + +## Example code + +Here an example that perform some basically match of strings + +```v ignore +import regex + +fn main(){ + txt := "http://www.ciao.mondo/hello/pippo12_/pera.html" + query := r"(?Phttps?)|(?Pftps?)://(?P[\w_]+.)+" + + mut re := regex.regex_opt(query) or { panic(err) } + + start, end := re.match_string(txt) + if start >= 0 { + println("Match ($start, $end) => [${txt[start..end]}]") + for g_index := 0; g_index < re.group_count ; g_index++ { + println("#${g_index} [${re.get_group_by_id(txt, g_index)}] \ + bounds: ${re.get_group_bounds_by_id(g_index)}") + } + for name in re.group_map.keys() { + println("group:'$name' \t=> [${re.get_group_by_name(txt, name)}] \ + bounds: ${re.get_group_bounds_by_name(name)}") + } + } else { + println("No Match") + } +} +``` +Here an example of total customization of the regex environment creation: +```v ignore +import regex + +fn main(){ + txt := "today John is gone to his house with Jack and Marie." + query := r"(?:(?P\A\w+)|(?:\a\w+)[\s.]?)+" + + // init regex + mut re := regex.RE{} + // max program length, can not be longer then the query + re.prog = []regex.Token {len: query.len + 1} + // can not be more char class the the length of the query + re.cc = []regex.CharClass{len: query.len} + re.prog = []regex.Token {len: query.len+1} + // enable continuos group saving + re.group_csave_flag = true + // set max 128 group nested + re.group_max_nested = 128 + // we can't have more groups than the half of the query legth + re.group_max = query.len>>1 + + // compile the query + re.compile_opt(query) or { panic(err) } + + start, end := re.match_string(txt) + if start >= 0 { + println("Match ($start, $end) => [${txt[start..end]}]") + } else { + println("No Match") + } + + // show results for continuos group saving + if re.group_csave_flag == true && start >= 0 && re.group_csave.len > 0{ + println("cg: $re.group_csave") + mut cs_i := 1 + for cs_i < re.group_csave[0]*3 { + g_id := re.group_csave[cs_i] + st := re.group_csave[cs_i+1] + en := re.group_csave[cs_i+2] + println("cg[$g_id] $st $en:[${txt[st..en]}]") + cs_i += 3 + } + } + + // show results for captured groups + if start >= 0 { + println("Match ($start, $end) => [${txt[start..end]}]") + for g_index := 0; g_index < re.group_count ; g_index++ { + println("#${g_index} [${re.get_group_by_id(txt, g_index)}] \ + bounds: ${re.get_group_bounds_by_id(g_index)}") + } + for name in re.group_map.keys() { + println("group:'$name' \t=> [${re.get_group_by_name(txt, name)}] \ + bounds: ${re.get_group_bounds_by_name(name)}") + } + } else { + println("No Match") + } +} +``` + +More examples are available in the test code for the `regex` module, +see `vlib/regex/regex_test.v`. diff --git a/v_windows/v/vlib/regex/regex.v b/v_windows/v/vlib/regex/regex.v new file mode 100644 index 0000000..9e630e1 --- /dev/null +++ b/v_windows/v/vlib/regex/regex.v @@ -0,0 +1,2324 @@ +/* +regex 1.0 alpha + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains regex module + +Know limitation: +- find is implemented in a trivial way +- not full compliant PCRE +- not compliant POSIX ERE +*/ +module regex + +import strings + +pub const ( + v_regex_version = '1.0 alpha' // regex module version + + max_code_len = 256 // default small base code len for the regex programs + max_quantifier = 1073741824 // default max repetitions allowed for the quantifiers = 2^30 + // spaces chars (here only westerns!!) TODO: manage all the spaces from unicode + spaces = [` `, `\t`, `\n`, `\r`, `\v`, `\f`] + // new line chars for now only '\n' + new_line_list = [`\n`, `\r`] + + // Results + no_match_found = -1 + + // Errors + compile_ok = 0 // the regex string compiled, all ok + err_char_unknown = -2 // the char used is unknow to the system + err_undefined = -3 // the compiler symbol is undefined + err_internal_error = -4 // Bug in the regex system!! + err_cc_alloc_overflow = -5 // memory for char class full!! + err_syntax_error = -6 // syntax error in regex compiling + err_groups_overflow = -7 // max number of groups reached + err_groups_max_nested = -8 // max number of nested group reached + err_group_not_balanced = -9 // group not balanced + err_group_qm_notation = -10 // group invalid notation +) + +const ( + //************************************* + // regex program instructions + //************************************* + ist_simple_char = u32(0x7FFFFFFF) // single char instruction, 31 bit available to char + // char class 11 0100 AA xxxxxxxx + // AA = 00 regular class + // AA = 01 Negated class ^ char + ist_char_class = 0xD1000000 // MASK + ist_char_class_pos = 0xD0000000 // char class normal [abc] + ist_char_class_neg = 0xD1000000 // char class negate [^abc] + // dot char 10 0110 xx xxxxxxxx + ist_dot_char = 0x98000000 // match any char except \n + // backslash chars 10 0100 xx xxxxxxxx + ist_bsls_char = 0x90000000 // backslash char + // OR | 10 010Y xx xxxxxxxx + ist_or_branch = 0x91000000 // OR case + // groups 10 010Y xx xxxxxxxx + ist_group_start = 0x92000000 // group start ( + ist_group_end = 0x94000000 // group end ) + // control instructions + ist_prog_end = u32(0x88000000) // 10 0010 xx xxxxxxxx + //************************************* +) + +/* +General Utilities +*/ +// utf8util_char_len calculate the length in bytes of a utf8 char +[inline] +fn utf8util_char_len(b byte) int { + return ((0xe5000000 >> ((b >> 3) & 0x1e)) & 3) + 1 +} + +// get_char get a char from position i and return an u32 with the unicode code +[direct_array_access; inline] +fn (re RE) get_char(in_txt string, i int) (u32, int) { + ini := unsafe { in_txt.str[i] } + // ascii 8 bit + if (re.flag & regex.f_bin) != 0 || ini & 0x80 == 0 { + return u32(ini), 1 + } + // unicode char + char_len := utf8util_char_len(ini) + mut tmp := 0 + mut ch := u32(0) + for tmp < char_len { + ch = (ch << 8) | unsafe { in_txt.str[i + tmp] } + tmp++ + } + return ch, char_len +} + +// get_charb get a char from position i and return an u32 with the unicode code +[direct_array_access; inline] +fn (re RE) get_charb(in_txt &byte, i int) (u32, int) { + // ascii 8 bit + if (re.flag & regex.f_bin) != 0 || unsafe { in_txt[i] } & 0x80 == 0 { + return u32(unsafe { in_txt[i] }), 1 + } + // unicode char + char_len := utf8util_char_len(unsafe { in_txt[i] }) + mut tmp := 0 + mut ch := u32(0) + for tmp < char_len { + ch = (ch << 8) | unsafe { in_txt[i + tmp] } + tmp++ + } + return ch, char_len +} + +[inline] +fn is_alnum(in_char byte) bool { + mut tmp := in_char - `A` + if tmp <= 25 { + return true + } + tmp = in_char - `a` + if tmp <= 25 { + return true + } + tmp = in_char - `0` + if tmp <= 9 { + return true + } + if in_char == `_` { + return true + } + return false +} + +[inline] +fn is_not_alnum(in_char byte) bool { + return !is_alnum(in_char) +} + +[inline] +fn is_space(in_char byte) bool { + return in_char in regex.spaces +} + +[inline] +fn is_not_space(in_char byte) bool { + return !is_space(in_char) +} + +[inline] +fn is_digit(in_char byte) bool { + tmp := in_char - `0` + return tmp <= 0x09 +} + +[inline] +fn is_not_digit(in_char byte) bool { + return !is_digit(in_char) +} + +/* +[inline] +fn is_wordchar(in_char byte) bool { + return is_alnum(in_char) || in_char == `_` +} + +[inline] +fn is_not_wordchar(in_char byte) bool { + return !is_alnum(in_char) +} +*/ + +[inline] +fn is_lower(in_char byte) bool { + tmp := in_char - `a` + return tmp <= 25 +} + +[inline] +fn is_upper(in_char byte) bool { + tmp := in_char - `A` + return tmp <= 25 +} + +pub fn (re RE) get_parse_error_string(err int) string { + match err { + regex.compile_ok { return 'compile_ok' } + regex.no_match_found { return 'no_match_found' } + regex.err_char_unknown { return 'err_char_unknown' } + regex.err_undefined { return 'err_undefined' } + regex.err_internal_error { return 'err_internal_error' } + regex.err_cc_alloc_overflow { return 'err_cc_alloc_overflow' } + regex.err_syntax_error { return 'err_syntax_error' } + regex.err_groups_overflow { return 'err_groups_overflow' } + regex.err_groups_max_nested { return 'err_groups_max_nested' } + regex.err_group_not_balanced { return 'err_group_not_balanced' } + regex.err_group_qm_notation { return 'err_group_qm_notation' } + else { return 'err_unknown' } + } +} + +// utf8_str convert and utf8 sequence to a printable string +[inline] +fn utf8_str(ch rune) string { + mut i := 4 + mut res := '' + for i > 0 { + v := byte((ch >> ((i - 1) * 8)) & 0xFF) + if v != 0 { + res += '${v:1c}' + } + i-- + } + return res +} + +// simple_log default log function +fn simple_log(txt string) { + print(txt) +} + +/****************************************************************************** +* +* Token Structs +* +******************************************************************************/ +pub type FnValidator = fn (byte) bool + +struct Token { +mut: + ist rune + // char + ch rune // char of the token if any + ch_len byte // char len + // Quantifiers / branch + rep_min int // used also for jump next in the OR branch [no match] pc jump + rep_max int // used also for jump next in the OR branch [ match] pc jump + greedy bool // greedy quantifier flag + // Char class + cc_index int = -1 + // counters for quantifier check (repetitions) + rep int + // validator function pointer + validator FnValidator + // groups variables + group_rep int // repetition of the group + group_id int = -1 // id of the group + goto_pc int = -1 // jump to this PC if is needed + // OR flag for the token + next_is_or bool // true if the next token is an OR + // dot_char token variables + dot_check_pc int = -1 // pc of the next token to check + last_dot_flag bool // if true indicate that is the last dot_char in the regex +} + +[inline] +fn (mut tok Token) reset() { + tok.rep = 0 +} + +/****************************************************************************** +* +* Regex struct +* +******************************************************************************/ +pub const ( + f_nl = 0x00000001 // end the match when find a new line symbol + f_ms = 0x00000002 // match true only if the match is at the start of the string + f_me = 0x00000004 // match true only if the match is at the end of the string + + f_efm = 0x00000100 // exit on first token matched, used by search + f_bin = 0x00000200 // work only on bytes, ignore utf-8 + // behaviour modifier flags + f_src = 0x00020000 // search mode enabled +) + +struct StateDotObj { +mut: + i int = -1 // char index in the input buffer + pc int = -1 // program counter saved + mi int = -1 // match_index saved + group_stack_index int = -1 // continuous save on capturing groups +} + +pub type FnLog = fn (string) + +pub struct RE { +pub mut: + prog []Token + prog_len int // regex program len + // char classes storage + cc []CharClass // char class list + cc_index int // index + // groups + group_count int // number of groups in this regex struct + groups []int // groups index results + group_max_nested int = 3 // max nested group + group_max int = 8 // max allowed number of different groups + + state_list []StateObj + + group_csave_flag bool // flag to enable continuous saving + group_csave []int //= []int{} // groups continuous save list + + group_map map[string]int // groups names map + + group_stack []int + group_data []int + // flags + flag int // flag for optional parameters + // Debug/log + debug int // enable in order to have the unroll of the code 0 = NO_DEBUG, 1 = LIGHT 2 = VERBOSE + log_func FnLog = simple_log // log function, can be customized by the user + query string // query string +} + +// Reset RE object +[direct_array_access; inline] +fn (mut re RE) reset() { + re.cc_index = 0 + + mut i := 0 + for i < re.prog_len { + re.prog[i].group_rep = 0 // clear repetition of the group + re.prog[i].rep = 0 // clear repetition of the token + i++ + } + + // init groups array + if re.group_count > 0 { + re.groups = []int{len: re.group_count * 2, init: -1} + } + + // reset group_csave + if re.group_csave_flag == true { + re.group_csave.clear() // = []int{} + } + + // reset state list + re.state_list.clear() + re.group_stack.clear() +} + +// reset for search mode fail +// gcc bug, dont use [inline] or go 5 time slower +//[inline] +[direct_array_access] +fn (mut re RE) reset_src() { + mut i := 0 + for i < re.prog_len { + re.prog[i].group_rep = 0 // clear repetition of the group + re.prog[i].rep = 0 // clear repetition of the token + i++ + } +} + +/****************************************************************************** +* +* Backslashes chars +* +******************************************************************************/ +struct BslsStruct { + ch rune // meta char + validator FnValidator // validator function pointer +} + +const ( + bsls_validator_array = [ + BslsStruct{`w`, is_alnum}, + BslsStruct{`W`, is_not_alnum}, + BslsStruct{`s`, is_space}, + BslsStruct{`S`, is_not_space}, + BslsStruct{`d`, is_digit}, + BslsStruct{`D`, is_not_digit}, + BslsStruct{`a`, is_lower}, + BslsStruct{`A`, is_upper}, + ] + + // these chars are escape if preceded by a \ + bsls_escape_list = [`\\`, `|`, `.`, `:`, `*`, `+`, `-`, `{`, `}`, `[`, `]`, `(`, `)`, `?`, + `^`, `!`] +) + +enum BSLS_parse_state { + start + bsls_found + bsls_char + normal_char +} + +// parse_bsls return (index, str_len) bsls_validator_array index, len of the backslash sequence if present +fn (re RE) parse_bsls(in_txt string, in_i int) (int, int) { + mut status := BSLS_parse_state.start + mut i := in_i + + for i < in_txt.len { + // get our char + char_tmp, char_len := re.get_char(in_txt, i) + ch := byte(char_tmp) + + if status == .start && ch == `\\` { + status = .bsls_found + i += char_len + continue + } + + // check if is our bsls char, for now only one length sequence + if status == .bsls_found { + for c, x in regex.bsls_validator_array { + if x.ch == ch { + return c, i - in_i + 1 + } + } + status = .normal_char + continue + } + + // no BSLS validator, manage as normal escape char char + if status == .normal_char { + if ch in regex.bsls_escape_list { + return regex.no_match_found, i - in_i + 1 + } + return regex.err_syntax_error, i - in_i + 1 + } + + // at the present time we manage only one char after the \ + break + } + // not our bsls return KO + return regex.err_syntax_error, i +} + +/****************************************************************************** +* +* Char class +* +******************************************************************************/ +const ( + cc_null = 0 // empty cc token + cc_char = 1 // simple char: a + cc_int = 2 // char interval: a-z + cc_bsls = 3 // backslash char + cc_end = 4 // cc sequence terminator +) + +struct CharClass { +mut: + cc_type int = regex.cc_null // type of cc token + ch0 rune // first char of the interval a-b a in this case + ch1 rune // second char of the interval a-b b in this case + validator FnValidator // validator function pointer +} + +enum CharClass_parse_state { + start + in_char + in_bsls + separator + finish +} + +fn (re RE) get_char_class(pc int) string { + buf := []byte{len: (re.cc.len)} + mut buf_ptr := unsafe { &byte(&buf) } + + mut cc_i := re.prog[pc].cc_index + mut i := 0 + mut tmp := 0 + for cc_i >= 0 && cc_i < re.cc.len && re.cc[cc_i].cc_type != regex.cc_end { + if re.cc[cc_i].cc_type == regex.cc_bsls { + unsafe { + buf_ptr[i] = `\\` + i++ + buf_ptr[i] = byte(re.cc[cc_i].ch0) + i++ + } + } else if re.cc[cc_i].ch0 == re.cc[cc_i].ch1 { + tmp = 3 + for tmp >= 0 { + x := byte((re.cc[cc_i].ch0 >> (tmp * 8)) & 0xFF) + if x != 0 { + unsafe { + buf_ptr[i] = x + i++ + } + } + tmp-- + } + } else { + tmp = 3 + for tmp >= 0 { + x := byte((re.cc[cc_i].ch0 >> (tmp * 8)) & 0xFF) + if x != 0 { + unsafe { + buf_ptr[i] = x + i++ + } + } + tmp-- + } + unsafe { + buf_ptr[i] = `-` + i++ + } + tmp = 3 + for tmp >= 0 { + x := byte((re.cc[cc_i].ch1 >> (tmp * 8)) & 0xFF) + if x != 0 { + unsafe { + buf_ptr[i] = x + i++ + } + } + tmp-- + } + } + cc_i++ + } + unsafe { + buf_ptr[i] = byte(0) + } + return unsafe { tos_clone(buf_ptr) } +} + +fn (re RE) check_char_class(pc int, ch rune) bool { + mut cc_i := re.prog[pc].cc_index + for cc_i >= 0 && cc_i < re.cc.len && re.cc[cc_i].cc_type != regex.cc_end { + if re.cc[cc_i].cc_type == regex.cc_bsls { + if re.cc[cc_i].validator(byte(ch)) { + return true + } + } else if ch >= re.cc[cc_i].ch0 && ch <= re.cc[cc_i].ch1 { + return true + } + cc_i++ + } + return false +} + +// parse_char_class return (index, str_len, cc_type) of a char class [abcm-p], char class start after the [ char +fn (mut re RE) parse_char_class(in_txt string, in_i int) (int, int, rune) { + mut status := CharClass_parse_state.start + mut i := in_i + + mut tmp_index := re.cc_index + res_index := re.cc_index + + mut cc_type := u32(regex.ist_char_class_pos) + + for i < in_txt.len { + // check if we are out of memory for char classes + if tmp_index >= re.cc.len { + return regex.err_cc_alloc_overflow, 0, u32(0) + } + + // get our char + char_tmp, char_len := re.get_char(in_txt, i) + ch := byte(char_tmp) + + // println("CC #${i:3d} ch: ${ch:c}") + + // negation + if status == .start && ch == `^` { + cc_type = u32(regex.ist_char_class_neg) + i += char_len + continue + } + + // minus symbol + if status == .start && ch == `-` { + re.cc[tmp_index].cc_type = regex.cc_char + re.cc[tmp_index].ch0 = char_tmp + re.cc[tmp_index].ch1 = char_tmp + i += char_len + tmp_index++ + continue + } + + // bsls + if (status == .start || status == .in_char) && ch == `\\` { + // println("CC bsls.") + status = .in_bsls + i += char_len + continue + } + + if status == .in_bsls { + // println("CC bsls validation.") + for c, x in regex.bsls_validator_array { + if x.ch == ch { + // println("CC bsls found [${ch:c}]") + re.cc[tmp_index].cc_type = regex.cc_bsls + re.cc[tmp_index].ch0 = regex.bsls_validator_array[c].ch + re.cc[tmp_index].ch1 = regex.bsls_validator_array[c].ch + re.cc[tmp_index].validator = regex.bsls_validator_array[c].validator + i += char_len + tmp_index++ + status = .in_char + break + } + } + if status == .in_bsls { + // manage as a simple char + // println("CC bsls not found [${ch:c}]") + re.cc[tmp_index].cc_type = regex.cc_char + re.cc[tmp_index].ch0 = char_tmp + re.cc[tmp_index].ch1 = char_tmp + i += char_len + tmp_index++ + status = .in_char + continue + } else { + continue + } + } + + // simple char + if (status == .start || status == .in_char) && ch != `-` && ch != `]` { + status = .in_char + + re.cc[tmp_index].cc_type = regex.cc_char + re.cc[tmp_index].ch0 = char_tmp + re.cc[tmp_index].ch1 = char_tmp + + i += char_len + tmp_index++ + continue + } + + // check range separator + if status == .in_char && ch == `-` { + status = .separator + i += char_len + continue + } + + // check range end + if status == .separator && ch != `]` && ch != `-` { + status = .in_char + re.cc[tmp_index - 1].cc_type = regex.cc_int + re.cc[tmp_index - 1].ch1 = char_tmp + i += char_len + continue + } + + // char class end + if status == .in_char && ch == `]` { + re.cc[tmp_index].cc_type = regex.cc_end + re.cc[tmp_index].ch0 = 0 + re.cc[tmp_index].ch1 = 0 + re.cc_index = tmp_index + 1 + + return res_index, i - in_i + 2, cc_type + } + + i++ + } + return regex.err_syntax_error, 0, u32(0) +} + +/****************************************************************************** +* +* Re Compiler +* +******************************************************************************/ +// +// Quantifier +// +enum Quant_parse_state { + start + min_parse + comma_checked + max_parse + greedy + gredy_parse + finish +} + +// parse_quantifier return (min, max, str_len, greedy_flag) of a {min,max}? quantifier starting after the { char +fn (re RE) parse_quantifier(in_txt string, in_i int) (int, int, int, bool) { + mut status := Quant_parse_state.start + mut i := in_i + + mut q_min := 0 // default min in a {} quantifier is 1 + mut q_max := 0 // deafult max in a {} quantifier is max_quantifier + + mut ch := byte(0) + + for i < in_txt.len { + unsafe { + ch = in_txt.str[i] + } + // println("${ch:c} status: $status") + + // exit on no compatible char with {} quantifier + if utf8util_char_len(ch) != 1 { + return regex.err_syntax_error, i, 0, false + } + + // min parsing skip if comma present + if status == .start && ch == `,` { + q_min = 0 // default min in a {} quantifier is 0 + status = .comma_checked + i++ + continue + } + + if status == .start && is_digit(ch) { + status = .min_parse + q_min *= 10 + q_min += int(ch - `0`) + i++ + continue + } + + if status == .min_parse && is_digit(ch) { + q_min *= 10 + q_min += int(ch - `0`) + i++ + continue + } + + // we have parsed the min, now check the max + if status == .min_parse && ch == `,` { + status = .comma_checked + i++ + continue + } + + // single value {4} + if status == .min_parse && ch == `}` { + q_max = q_min + status = .greedy + continue + } + + // end without max + if status == .comma_checked && ch == `}` { + q_max = regex.max_quantifier + status = .greedy + continue + } + + // start max parsing + if status == .comma_checked && is_digit(ch) { + status = .max_parse + q_max *= 10 + q_max += int(ch - `0`) + i++ + continue + } + + // parse the max + if status == .max_parse && is_digit(ch) { + q_max *= 10 + q_max += int(ch - `0`) + i++ + continue + } + + // finished the quantifier + if status == .max_parse && ch == `}` { + status = .greedy + continue + } + + // check if greedy flag char ? is present + if status == .greedy { + if i + 1 < in_txt.len { + i++ + status = .gredy_parse + continue + } + return q_min, q_max, i - in_i + 2, false + } + + // check the greedy flag + if status == .gredy_parse { + if ch == `?` { + return q_min, q_max, i - in_i + 2, true + } else { + i-- + return q_min, q_max, i - in_i + 2, false + } + } + + // not a {} quantifier, exit + return regex.err_syntax_error, i, 0, false + } + + // not a conform {} quantifier + return regex.err_syntax_error, i, 0, false +} + +// +// Groups +// +enum Group_parse_state { + start + q_mark // (? + q_mark1 // (?:|P checking + p_status // (?P + p_start // (?P< + p_end // (?P<...> + p_in_name // (?P<... + finish +} + +// parse_groups parse a group for ? (question mark) syntax, if found, return (error, capture_flag, name_of_the_group, next_index) +fn (re RE) parse_groups(in_txt string, in_i int) (int, bool, string, int) { + mut status := Group_parse_state.start + mut i := in_i + mut name := '' + + for i < in_txt.len && status != .finish { + // get our char + char_tmp, char_len := re.get_char(in_txt, i) + ch := byte(char_tmp) + + // start + if status == .start && ch == `(` { + status = .q_mark + i += char_len + continue + } + + // check for question marks + if status == .q_mark && ch == `?` { + status = .q_mark1 + i += char_len + continue + } + + // non capturing group + if status == .q_mark1 && ch == `:` { + i += char_len + return 0, false, name, i + } + + // enter in P section + if status == .q_mark1 && ch == `P` { + status = .p_status + i += char_len + continue + } + + // not a valid q mark found + if status == .q_mark1 { + // println("NO VALID Q MARK") + return -2, true, name, i + } + + if status == .p_status && ch == `<` { + status = .p_start + i += char_len + continue + } + + if status == .p_start && ch != `>` { + status = .p_in_name + name += '${ch:1c}' // TODO: manage utf8 chars + i += char_len + continue + } + + // colect name + if status == .p_in_name && ch != `>` && is_alnum(ch) { + name += '${ch:1c}' // TODO: manage utf8 chars + i += char_len + continue + } + + // end name + if status == .p_in_name && ch == `>` { + i += char_len + return 0, true, name, i + } + + // error on name group + if status == .p_in_name { + return -2, true, name, i + } + + // normal group, nothig to do, exit + return 0, true, name, i + } + // UNREACHABLE + // println("ERROR!! NOT MEANT TO BE HERE!!1") + return -2, true, name, i +} + +// +// main compiler +// +// compile return (return code, index) where index is the index of the error in the query string if return code is an error code +fn (mut re RE) impl_compile(in_txt string) (int, int) { + mut i := 0 // input string index + mut pc := 0 // program counter + + // group management variables + mut group_count := -1 + mut group_stack := []int{len: re.group_max_nested, init: 0} + mut group_stack_txt_index := []int{len: re.group_max_nested, init: -1} + mut group_stack_index := -1 + + re.query = in_txt // save the query string + + i = 0 + for i < in_txt.len { + mut char_tmp := u32(0) + mut char_len := 0 + // println("i: ${i:3d} ch: ${in_txt.str[i]:c}") + + char_tmp, char_len = re.get_char(in_txt, i) + + // + // check special cases: $ ^ + // + if char_len == 1 && i == 0 && byte(char_tmp) == `^` { + re.flag = regex.f_ms + i = i + char_len + continue + } + if char_len == 1 && i == (in_txt.len - 1) && byte(char_tmp) == `$` { + re.flag = regex.f_me + i = i + char_len + continue + } + + // ist_group_start + if char_len == 1 && pc >= 0 && byte(char_tmp) == `(` { + // check max groups allowed + if group_count > re.group_max { + return regex.err_groups_overflow, i + 1 + } + group_stack_index++ + + // check max nested groups allowed + if group_stack_index > re.group_max_nested { + return regex.err_groups_max_nested, i + 1 + } + + tmp_res, cgroup_flag, cgroup_name, next_i := re.parse_groups(in_txt, i) + + // manage question mark format error + if tmp_res < -1 { + return regex.err_group_qm_notation, next_i + } + + // println("Parse group: [$tmp_res, $cgroup_flag, ($i,$next_i), '${in_txt[i..next_i]}' ]") + i = next_i + + if cgroup_flag == true { + group_count++ + } + + // calculate the group id + // if it is a named group, recycle the group id + // NOTE: **** the group index is +1 because map return 0 when not found!! **** + mut group_id := group_count + if cgroup_name.len > 0 { + // println("GROUP NAME: ${cgroup_name}") + if cgroup_name in re.group_map { + group_id = re.group_map[cgroup_name] - 1 + group_count-- + } else { + re.group_map[cgroup_name] = group_id + 1 + } + } + + group_stack_txt_index[group_stack_index] = i + group_stack[group_stack_index] = pc + + re.prog[pc].ist = u32(0) | regex.ist_group_start + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + + // set the group id + if cgroup_flag == false { + // println("NO CAPTURE GROUP") + re.prog[pc].group_id = -1 + } else { + re.prog[pc].group_id = group_id + } + + pc = pc + 1 + continue + } + + // ist_group_end + if char_len == 1 && pc > 0 && byte(char_tmp) == `)` { + if group_stack_index < 0 { + return regex.err_group_not_balanced, i + 1 + } + + goto_pc := group_stack[group_stack_index] + group_stack_index-- + + re.prog[pc].ist = u32(0) | regex.ist_group_end + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + + re.prog[pc].goto_pc = goto_pc // PC where to jump if a group need + re.prog[pc].group_id = re.prog[goto_pc].group_id // id of this group, used for storing data + + re.prog[goto_pc].goto_pc = pc // start goto point to the end group pc + // re.prog[goto_pc].group_id = group_count // id of this group, used for storing data + + pc = pc + 1 + i = i + char_len + continue + } + + // ist_dot_char match any char except the following token + if char_len == 1 && pc >= 0 && byte(char_tmp) == `.` { + re.prog[pc].ist = u32(0) | regex.ist_dot_char + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + pc = pc + 1 + i = i + char_len + continue + } + + // OR branch + if char_len == 1 && pc > 0 && byte(char_tmp) == `|` { + // two consecutive ist_dot_char are an error + if pc > 0 && re.prog[pc - 1].ist == regex.ist_or_branch { + return regex.err_syntax_error, i + } + re.prog[pc].ist = u32(0) | regex.ist_or_branch + pc = pc + 1 + i = i + char_len + continue + } + + // Quantifiers + if char_len == 1 && pc > 0 { + mut quant_flag := true + match byte(char_tmp) { + `?` { + // println("q: ${char_tmp:c}") + re.prog[pc - 1].rep_min = 0 + re.prog[pc - 1].rep_max = 1 + } + `+` { + // println("q: ${char_tmp:c}") + re.prog[pc - 1].rep_min = 1 + re.prog[pc - 1].rep_max = regex.max_quantifier + } + `*` { + // println("q: ${char_tmp:c}") + re.prog[pc - 1].rep_min = 0 + re.prog[pc - 1].rep_max = regex.max_quantifier + } + `{` { + min, max, tmp, greedy := re.parse_quantifier(in_txt, i + 1) + // it is a quantifier + if min >= 0 { + // println("{$min,$max}\n str:[${in_txt[i..i+tmp]}] greedy:$greedy") + i = i + tmp + re.prog[pc - 1].rep_min = min + re.prog[pc - 1].rep_max = max + re.prog[pc - 1].greedy = greedy + continue + } else { + return min, i + } + // TODO: decide if the open bracket can be conform without the close bracket + /* + // no conform, parse as normal char + else { + quant_flag = false + } + */ + } + else { + quant_flag = false + } + } + + if quant_flag { + i = i + char_len + continue + } + } + + // IST_CHAR_CLASS_* + if char_len == 1 && pc >= 0 { + if byte(char_tmp) == `[` { + cc_index, tmp, cc_type := re.parse_char_class(in_txt, i + 1) + if cc_index >= 0 { + // println("index: $cc_index str:${in_txt[i..i+tmp]}") + i = i + tmp + re.prog[pc].ist = u32(0) | cc_type + re.prog[pc].cc_index = cc_index + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + pc = pc + 1 + continue + } + // cc_class vector memory full + else if cc_index < 0 { + return cc_index, i + } + } + } + + // ist_bsls_char + if char_len == 1 && pc >= 0 { + if byte(char_tmp) == `\\` { + bsls_index, tmp := re.parse_bsls(in_txt, i) + // println("index: $bsls_index str:${in_txt[i..i+tmp]}") + if bsls_index >= 0 { + i = i + tmp + re.prog[pc].ist = u32(0) | regex.ist_bsls_char + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + re.prog[pc].validator = regex.bsls_validator_array[bsls_index].validator + re.prog[pc].ch = regex.bsls_validator_array[bsls_index].ch + pc = pc + 1 + continue + } + // this is an escape char, skip the bsls and continue as a normal char + else if bsls_index == regex.no_match_found { + i += char_len + char_tmp, char_len = re.get_char(in_txt, i) + // continue as simple char + } + // if not an escape or a bsls char then it is an error (at least for now!) + else { + return bsls_index, i + tmp + } + } + } + + // ist_simple_char + re.prog[pc].ist = regex.ist_simple_char + re.prog[pc].ch = char_tmp + re.prog[pc].ch_len = byte(char_len) + re.prog[pc].rep_min = 1 + re.prog[pc].rep_max = 1 + // println("char: ${char_tmp:c}") + pc = pc + 1 + + i += char_len + } + + // add end of the program + re.prog[pc].ist = regex.ist_prog_end + re.prog_len = pc + + // check for unbalanced groups + if group_stack_index != -1 { + return regex.err_group_not_balanced, group_stack_txt_index[group_stack_index] + 1 + } + + // check for OR at the end of the program + if pc > 0 && re.prog[pc - 1].ist == regex.ist_or_branch { + return regex.err_syntax_error, in_txt.len + } + + // store the number of groups in the query + re.group_count = group_count + 1 + + //****************************************** + // Post processing + //****************************************** + + // + // manage ist_dot_char + // + + // find the checks for dot chars, if any... + mut pc1 := 0 + mut dot_char_count := 0 + mut last_dot_char_pc := -1 + for pc1 < pc { + if re.prog[pc1].ist == regex.ist_dot_char { + // println("Dot_char pc: $pc1") + last_dot_char_pc = pc1 + dot_char_count++ + mut pc2 := pc1 + 1 + for pc2 < pc { + if re.prog[pc2].ist == regex.ist_dot_char { + return regex.err_syntax_error, 0 + } + if re.prog[pc2].ist !in [rune(regex.ist_prog_end), regex.ist_group_end, + regex.ist_group_start, + ] { + // println("Next dot char check is PC: ${pc2}") + re.prog[pc1].dot_check_pc = pc2 + break + } + pc2++ + } + } + pc1++ + } + + // println("last_dot_char_pc: $last_dot_char_pc") + if last_dot_char_pc >= 0 { + pc1 = last_dot_char_pc + 1 + mut is_last_dot := true + for pc1 < pc { + if re.prog[pc1].ist !in [rune(regex.ist_prog_end), regex.ist_group_end] { + is_last_dot = false + break + } + pc1++ + } + if is_last_dot { + re.prog[last_dot_char_pc].last_dot_flag = true + } + } + + //****************************************** + + // OR branch + // a|b|cd + // d exit point + // a,b,c branches + // set the jump in the right places + pc1 = 0 + for pc1 < pc - 2 { + // println("Here $pc1 ${pc-2}") + // two consecutive OR are a syntax error + if re.prog[pc1 + 1].ist == regex.ist_or_branch + && re.prog[pc1 + 2].ist == regex.ist_or_branch { + return regex.err_syntax_error, i + } + + // manange a|b chains like a|(b)|c|d... + // standard solution + if re.prog[pc1].ist != regex.ist_or_branch && re.prog[pc1 + 1].ist == regex.ist_or_branch + && re.prog[pc1 + 2].ist != regex.ist_or_branch { + re.prog[pc1].next_is_or = true // set that the next token is an OR + re.prog[pc1 + 1].rep_min = pc1 + 2 // failed match jump + + // match jump, if an OR chain the next token will be an OR token + mut pc2 := pc1 + 2 + for pc2 < pc - 1 { + ist := re.prog[pc2].ist + if ist == regex.ist_group_start { + re.prog[pc1 + 1].rep_max = re.prog[pc2].goto_pc + 1 + break + } + if ist != regex.ist_or_branch { + re.prog[pc1 + 1].rep_max = pc2 + 1 + break + } + + pc2++ + } + // special case query of few chars, teh true can't go on the first instruction + if re.prog[pc1 + 1].rep_max == pc1 { + re.prog[pc1 + 1].rep_max = 3 + } + // println("Compile OR postproc. [$pc1,OR ${pc1+1},$pc2]") + pc1 = pc2 + continue + } + + pc1++ + } + + //****************************************** + // DEBUG PRINT REGEX GENERATED CODE + //****************************************** + if re.debug > 0 { + gc := re.get_code() + re.log_func(gc) + } + //****************************************** + + return regex.compile_ok, 0 +} + +// get_code return the compiled code as regex string, note: may be different from the source! +pub fn (re RE) get_code() string { + mut pc1 := 0 + mut res := strings.new_builder(re.cc.len * 2 * re.prog.len) + res.write_string('========================================\nv RegEx compiler v $regex.v_regex_version output:\n') + + mut stop_flag := false + + for pc1 <= re.prog.len { + tk := re.prog[pc1] + res.write_string('PC:${pc1:3d}') + + res.write_string(' ist: ') + res.write_string('${tk.ist:8x}'.replace(' ', '0')) + res.write_string(' ') + ist := tk.ist + if ist == regex.ist_bsls_char { + res.write_string('[\\${tk.ch:1c}] BSLS') + } else if ist == regex.ist_prog_end { + res.write_string('PROG_END') + stop_flag = true + } else if ist == regex.ist_or_branch { + res.write_string('OR ') + } else if ist == regex.ist_char_class_pos { + res.write_string('[${re.get_char_class(pc1)}] CHAR_CLASS_POS') + } else if ist == regex.ist_char_class_neg { + res.write_string('[^${re.get_char_class(pc1)}] CHAR_CLASS_NEG') + } else if ist == regex.ist_dot_char { + res.write_string('. DOT_CHAR nx chk: $tk.dot_check_pc') + if tk.last_dot_flag == true { + res.write_string(' last!') + } + } else if ist == regex.ist_group_start { + res.write_string('( GROUP_START #:$tk.group_id') + if tk.group_id == -1 { + res.write_string(' ?:') + } else { + for x in re.group_map.keys() { + if re.group_map[x] == (tk.group_id + 1) { + res.write_string(' ?P<$x>') + break + } + } + } + } else if ist == regex.ist_group_end { + res.write_string(') GROUP_END #:$tk.group_id') + } else if ist == regex.ist_simple_char { + res.write_string('[${tk.ch:1c}] query_ch') + } + + if tk.rep_max == regex.max_quantifier { + res.write_string(' {${tk.rep_min:3d},MAX}') + } else { + if ist == regex.ist_or_branch { + res.write_string(' if false go: ${tk.rep_min:3d} if true go: ${tk.rep_max:3d}') + } else { + res.write_string(' {${tk.rep_min:3d},${tk.rep_max:3d}}') + } + if tk.greedy == true { + res.write_string('?') + } + } + + res.write_string('\n') + if stop_flag { + break + } + pc1++ + } + + res.write_string('========================================\n') + return res.str() +} + +// get_query return a string with a reconstruction of the query starting from the regex program code +pub fn (re RE) get_query() string { + mut res := strings.new_builder(re.query.len * 2) + + if (re.flag & regex.f_ms) != 0 { + res.write_string('^') + } + + mut i := 0 + for i < re.prog.len && re.prog[i].ist != regex.ist_prog_end && re.prog[i].ist != 0 { + tk := unsafe { &re.prog[i] } + ch := tk.ist + + // GROUP start + if ch == regex.ist_group_start { + if re.debug == 0 { + res.write_string('(') + } else { + if tk.group_id == -1 { + res.write_string('(?:') // non capturing group + } else { + res.write_string('#${tk.group_id}(') + } + } + + for x in re.group_map.keys() { + if re.group_map[x] == (tk.group_id + 1) { + res.write_string('?P<$x>') + break + } + } + + i++ + continue + } + + // GROUP end + if ch == regex.ist_group_end { + res.write_string(')') + } + + // OR branch + if ch == regex.ist_or_branch { + res.write_string('|') + if re.debug > 0 { + res.write_string('{$tk.rep_min,$tk.rep_max}') + } + i++ + continue + } + + // char class + if ch == regex.ist_char_class_neg || ch == regex.ist_char_class_pos { + res.write_string('[') + if ch == regex.ist_char_class_neg { + res.write_string('^') + } + res.write_string('${re.get_char_class(i)}') + res.write_string(']') + } + + // bsls char + if ch == regex.ist_bsls_char { + res.write_string('\\${tk.ch:1c}') + } + + // ist_dot_char + if ch == regex.ist_dot_char { + res.write_string('.') + } + + // char alone + if ch == regex.ist_simple_char { + if byte(ch) in regex.bsls_escape_list { + res.write_string('\\') + } + res.write_string('${tk.ch:c}') + } + + // quantifier + if !(tk.rep_min == 1 && tk.rep_max == 1) { + if tk.rep_min == 0 && tk.rep_max == 1 { + res.write_string('?') + } else if tk.rep_min == 1 && tk.rep_max == regex.max_quantifier { + res.write_string('+') + } else if tk.rep_min == 0 && tk.rep_max == regex.max_quantifier { + res.write_string('*') + } else { + if tk.rep_max == regex.max_quantifier { + res.write_string('{$tk.rep_min,MAX}') + } else { + res.write_string('{$tk.rep_min,$tk.rep_max}') + } + if tk.greedy == true { + res.write_string('?') + } + } + } + i++ + } + if (re.flag & regex.f_me) != 0 { + res.write_string('$') + } + + return res.str() +} + +/****************************************************************************** +* +* Groups saving utilities +* +******************************************************************************/ +[direct_array_access] +fn (mut re RE) group_continuous_save(g_index int) { + if re.group_csave_flag == true { + // continuous save, save until we have space + + // init the first element as counter + if re.group_csave.len == 0 { + re.group_csave << 0 + } + + gi := g_index >> 1 + start := re.groups[g_index] + end := re.groups[g_index + 1] + + // check if we are simply increasing the size ot the found group + if re.group_csave.len >= 4 && gi == re.group_csave[re.group_csave.len - 3] + && start == re.group_csave[re.group_csave.len - 2] { + re.group_csave[re.group_csave.len - 1] = end + return + } + + // otherwise append a new group to the list + + // increment counter + re.group_csave[0]++ + // save the record + re.group_csave << (g_index >> 1) // group id + re.group_csave << re.groups[g_index] // start + re.group_csave << re.groups[g_index + 1] // end + } +} + +/****************************************************************************** +* +* Matching +* +******************************************************************************/ +enum Match_state { + start = 0 + stop + end + new_line + ist_load // load and execute instruction + ist_next // go to next instruction + ist_next_ks // go to next instruction without clenaning the state + ist_quant_p // match positive ,quantifier check + ist_quant_n // match negative, quantifier check + ist_quant_pg // match positive ,group quantifier check + ist_quant_ng // match negative ,group quantifier check +} + +fn state_str(s Match_state) string { + match s { + .start { return 'start' } + .stop { return 'stop' } + .end { return 'end' } + .new_line { return 'new line' } + .ist_load { return 'ist_load' } + .ist_next { return 'ist_next' } + .ist_next_ks { return 'ist_next_ks' } + .ist_quant_p { return 'ist_quant_p' } + .ist_quant_n { return 'ist_quant_n' } + .ist_quant_pg { return 'ist_quant_pg' } + .ist_quant_ng { return 'ist_quant_ng' } + } +} + +struct StateObj { +pub mut: + group_index int = -1 // group id used to know how many groups are open + match_flag bool // indicate if we are in a match condition + match_index int = -1 // index of the last match + first_match int = -1 // index of the first match + pc int = -1 // program counter + i int = -1 // source string index + char_len int // last char legth + last_dot_pc int = -1 // last dot chat pc +} + +[direct_array_access] +pub fn (mut re RE) match_base(in_txt &byte, in_txt_len int) (int, int) { + // result status + mut result := regex.no_match_found // function return + + mut ch := rune(0) // examinated char + mut char_len := 0 // utf8 examinated char len + mut m_state := Match_state.start // start point for the matcher FSM + mut src_end := false + mut last_fnd_pc := -1 + + mut state := StateObj{} // actual state + mut ist := rune(0) // actual instruction + mut l_ist := rune(0) // last matched instruction + + mut step_count := 0 // stats for debug + mut dbg_line := 0 // count debug line printed + + re.reset() + + if re.debug > 0 { + // print header + mut h_buf := strings.new_builder(32) + h_buf.write_string('flags: ') + h_buf.write_string('${re.flag:8x}'.replace(' ', '0')) + h_buf.write_string('\n') + sss := h_buf.str() + re.log_func(sss) + } + + for m_state != .end { + if state.pc >= 0 && state.pc < re.prog.len { + ist = re.prog[state.pc].ist + } else if state.pc >= re.prog.len { + // println("ERROR!! PC overflow!!") + return regex.err_internal_error, state.i + } + + //****************************************** + // DEBUG LOG + //****************************************** + if re.debug > 0 { + mut buf2 := strings.new_builder(re.cc.len + 128) + + // print all the instructions + + // end of the input text + if state.i >= in_txt_len { + buf2.write_string('# ${step_count:3d} END OF INPUT TEXT\n') + sss := buf2.str() + re.log_func(sss) + } else { + // print only the exe instruction + if (re.debug == 1 && m_state == .ist_load) || re.debug == 2 { + if ist == regex.ist_prog_end { + buf2.write_string('# ${step_count:3d} PROG_END\n') + } else if ist == 0 || m_state in [.start, .ist_next, .stop] { + buf2.write_string('# ${step_count:3d} s: ${state_str(m_state):12s} PC: NA\n') + } else { + ch, char_len = re.get_charb(in_txt, state.i) + + buf2.write_string('# ${step_count:3d} s: ${state_str(m_state):12s} PC: ${state.pc:3d}=>') + buf2.write_string('${ist:8x}'.replace(' ', '0')) + buf2.write_string(" i,ch,len:[${state.i:3d},'${utf8_str(ch)}',$char_len] f.m:[${state.first_match:3d},${state.match_index:3d}] ") + + if ist == regex.ist_simple_char { + buf2.write_string('query_ch: [${re.prog[state.pc].ch:1c}]') + } else { + if ist == regex.ist_bsls_char { + buf2.write_string('BSLS [\\${re.prog[state.pc].ch:1c}]') + } else if ist == regex.ist_prog_end { + buf2.write_string('PROG_END') + } else if ist == regex.ist_or_branch { + buf2.write_string('OR') + } else if ist == regex.ist_char_class_pos { + buf2.write_string('CHAR_CLASS_POS[${re.get_char_class(state.pc)}]') + } else if ist == regex.ist_char_class_neg { + buf2.write_string('CHAR_CLASS_NEG[${re.get_char_class(state.pc)}]') + } else if ist == regex.ist_dot_char { + buf2.write_string('DOT_CHAR') + } else if ist == regex.ist_group_start { + tmp_gi := re.prog[state.pc].group_id + tmp_gr := re.prog[re.prog[state.pc].goto_pc].group_rep + buf2.write_string('GROUP_START #:$tmp_gi rep:$tmp_gr ') + } else if ist == regex.ist_group_end { + buf2.write_string('GROUP_END #:${re.prog[state.pc].group_id} deep:$state.group_index') + } + } + if re.prog[state.pc].rep_max == regex.max_quantifier { + buf2.write_string('{${re.prog[state.pc].rep_min},MAX}:${re.prog[state.pc].rep}') + } else { + buf2.write_string('{${re.prog[state.pc].rep_min},${re.prog[state.pc].rep_max}}:${re.prog[state.pc].rep}') + } + if re.prog[state.pc].greedy == true { + buf2.write_string('?') + } + buf2.write_string(' (#$state.group_index)') + + if ist == regex.ist_dot_char { + buf2.write_string(' last!') + } + + buf2.write_string('\n') + } + sss2 := buf2.str() + re.log_func(sss2) + } + } + step_count++ + dbg_line++ + } + //****************************************** + + if ist == regex.ist_prog_end { + // println("HERE we end!") + break + } + + // we're out of text, manage it + if state.i >= in_txt_len || m_state == .new_line { + // println("Finished text!!") + src_end = true + + // manage groups + if state.group_index >= 0 && state.match_index >= 0 { + // println("End text with open groups!") + // close the groups + for state.group_index >= 0 { + tmp_pc := re.group_data[state.group_index] + re.prog[tmp_pc].group_rep++ + // println("Closing group $state.group_index {${re.prog[tmp_pc].rep_min},${re.prog[tmp_pc].rep_max}}:${re.prog[tmp_pc].group_rep}") + + if re.prog[tmp_pc].group_rep >= re.prog[tmp_pc].rep_min + && re.prog[tmp_pc].group_id >= 0 { + start_i := re.group_stack[state.group_index] + re.group_stack[state.group_index] = -1 + + // save group results + g_index := re.prog[tmp_pc].group_id * 2 + if start_i >= 0 { + re.groups[g_index] = start_i + } else { + re.groups[g_index] = 0 + } + // we have fished the text, we must manage out pf bound indexes + if state.i >= in_txt_len { + state.i = in_txt_len - 1 + } + re.groups[g_index + 1] = state.i + + if re.groups[g_index + 1] >= in_txt_len { + // println("clamp group on stop!") + re.groups[g_index + 1] = in_txt_len - 1 + } + + // continuous save, save until we have space + re.group_continuous_save(g_index) + } + state.group_index-- + } + } + + // the text is finished and the groups closed and we are the last group, ok exit + if ist == regex.ist_group_end && re.prog[state.pc + 1].ist == regex.ist_prog_end { + // println("Last group end") + return state.first_match, state.i + } + + if state.pc == -1 { + state.pc = last_fnd_pc + } + + // println("Finished text!!") + // println("Instruction: ${ist:08x} pc: $state.pc") + // println("min_rep: ${re.prog[state.pc].rep_min} max_rep: ${re.prog[state.pc].rep_max} rep: ${re.prog[state.pc].rep}") + + // program end + if ist == regex.ist_prog_end { + // println("Program end on end of text!") + return state.first_match, state.i + } + + // we are in a last dot_ char case + if l_ist == regex.ist_dot_char { + // println("***** We have a last dot_char") + // println("PC: ${state.pc} last_dot_flag:${re.prog[state.pc].last_dot_flag}") + // println("rep: ${re.prog[state.pc].group_rep} min: ${re.prog[state.pc].rep_min} max: ${re.prog[state.pc].rep_max}") + // println("first match: ${state.first_match}") + if re.prog[state.pc].last_dot_flag == true + && re.prog[state.pc].rep >= re.prog[state.pc].rep_min + && re.prog[state.pc].rep <= re.prog[state.pc].rep_max { + return state.first_match, state.i + } + // println("Not fitted!!") + } + + // m_state = .end + // break + return regex.no_match_found, 0 + } + + // starting and init + if m_state == .start { + state.pc = -1 + state.i = 0 + m_state = .ist_next + continue + } + // ist_next, next instruction reseting its state + else if m_state == .ist_next { + state.pc = state.pc + 1 + re.prog[state.pc].reset() + // check if we are in the program bounds + if state.pc < 0 || state.pc > re.prog.len { + // println("ERROR!! PC overflow!!") + return regex.err_internal_error, state.i + } + m_state = .ist_load + continue + } + // ist_next_ks, next instruction keeping its state + else if m_state == .ist_next_ks { + state.pc = state.pc + 1 + // check if we are in the program bounds + if state.pc < 0 || state.pc > re.prog.len { + // println("ERROR!! PC overflow!!") + return regex.err_internal_error, state.i + } + m_state = .ist_load + continue + } + + // load the char + ch, char_len = re.get_charb(in_txt, state.i) + + // check new line if flag f_nl enabled + if (re.flag & regex.f_nl) != 0 && char_len == 1 && byte(ch) in regex.new_line_list { + m_state = .new_line + continue + } + // check if stop + else if m_state == .stop { + // we are in search mode, don't exit until the end + if ((re.flag & regex.f_src) != 0) && (ist != regex.ist_prog_end) { + last_fnd_pc = state.pc + state.pc = -1 + state.i += char_len + + m_state = .ist_next + re.reset_src() + state.match_index = -1 + state.first_match = -1 + + // reset state list + re.reset() + + continue + } + + if ist == regex.ist_prog_end { + return state.first_match, state.i + } + + // manage here dot char + + if re.state_list.len > 0 { + // println("Here we are, with stop: state buffer: [${re.state_list.len}]") + state = re.state_list.pop() + + state.match_flag = true + l_ist = u32(regex.ist_dot_char) + + if state.first_match < 0 { + state.first_match = state.i + } + state.match_index = state.i + re.prog[state.pc].rep++ // increase repetitions + + state.i += char_len + m_state = .ist_quant_p + continue + } + + // exit on no match + return result, 0 + } + // ist_load + else if m_state == .ist_load { + // program end + if ist == regex.ist_prog_end { + // if we are in match exit well + + if state.group_index >= 0 && state.match_index >= 0 { + state.group_index = -1 + } + + m_state = .stop + continue + } + // check GROUP start, no quantifier is checkd for this token!! + else if ist == regex.ist_group_start { + state.group_index++ + re.group_data[state.group_index] = re.prog[state.pc].goto_pc // save where is ist_group_end, we will use it for escape + re.group_stack[state.group_index] = state.i // index where we start to manage + // println("group_index $state.group_index rep ${re.prog[re.prog[state.pc].goto_pc].group_rep}") + + m_state = .ist_next + continue + } + // check GROUP end + else if ist == regex.ist_group_end { + // we are in matching streak + // println("Group END!! last ist: ${l_ist:08x}") + if state.match_index >= 0 { + // restore txt index stack and save the group data + + // println("g.id: ${re.prog[state.pc].group_id} group_index: ${state.group_index}") + if state.group_index >= 0 && re.prog[state.pc].group_id >= 0 { + start_i := re.group_stack[state.group_index] + + // save group results + g_index := re.prog[state.pc].group_id * 2 + + if start_i >= 0 { + re.groups[g_index] = start_i + } else { + re.groups[g_index] = 0 + } + + re.groups[g_index + 1] = state.i + + if g_index > 0 && re.groups[g_index] <= re.groups[g_index - 1] { + re.groups[g_index] = re.groups[g_index - 1] + } + + if re.groups[g_index + 1] >= in_txt_len { + // println("clamp group!") + re.groups[g_index + 1] = in_txt_len - 1 + } + + // println("GROUP ${re.prog[state.pc].group_id} END [${re.groups[g_index]}, ${re.groups[g_index+1]}] i: $state.i in_txt_len: $in_txt_len") + + // continuous save, save until we have space + re.group_continuous_save(g_index) + } + + re.prog[state.pc].group_rep++ // increase repetitions + // println("GROUP $group_index END ${re.prog[state.pc].group_rep}") + m_state = .ist_quant_pg + continue + } + + m_state = .ist_quant_ng + continue + } + // check OR + else if ist == regex.ist_or_branch { + if state.match_index >= 0 { + state.pc = re.prog[state.pc].rep_max + // println("ist_or_branch True pc: $state.pc") + } else { + state.pc = re.prog[state.pc].rep_min + // println("ist_or_branch False pc: $state.pc") + } + re.prog[state.pc].reset() + m_state = .ist_load + continue + } + // check ist_dot_char + else if ist == regex.ist_dot_char { + // println("ist_dot_char rep: ${re.prog[state.pc].rep}") + + // check next token to be false + mut next_check_flag := false + + // if we are done with max go on dot char are dedicated case!! + if re.prog[state.pc].rep >= re.prog[state.pc].rep_max { + re.state_list.pop() + m_state = .ist_next + continue + } + + if re.prog[state.pc].dot_check_pc >= 0 + && re.prog[state.pc].rep >= re.prog[state.pc].rep_min { + // load the char + // ch_t, _ := re.get_charb(in_txt, state.i+char_len) + ch_t := ch + chk_pc := re.prog[state.pc].dot_check_pc + + // simple char + if re.prog[chk_pc].ist == regex.ist_simple_char { + if re.prog[chk_pc].ch == ch_t { + next_check_flag = true + } + // println("Check [ist_simple_char] [${re.prog[chk_pc].ch}]==[${ch_t:c}] => $next_check_flag") + } + // char char_class + else if re.prog[chk_pc].ist == regex.ist_char_class_pos + || re.prog[chk_pc].ist == regex.ist_char_class_neg { + mut cc_neg := false + if re.prog[chk_pc].ist == regex.ist_char_class_neg { + cc_neg = true + } + mut cc_res := re.check_char_class(chk_pc, ch_t) + + if cc_neg { + cc_res = !cc_res + } + next_check_flag = cc_res + // println("Check [ist_char_class] => $next_check_flag") + } + // check bsls + else if re.prog[chk_pc].ist == regex.ist_bsls_char { + next_check_flag = re.prog[chk_pc].validator(byte(ch_t)) + // println("Check [ist_bsls_char] => $next_check_flag") + } + } + + // check if we must continue or pass to the next IST + if next_check_flag == true && re.prog[state.pc + 1].ist != regex.ist_prog_end { + // println("save the state!!") + mut dot_state := StateObj{ + group_index: state.group_index + match_flag: state.match_flag + match_index: state.match_index + first_match: state.first_match + pc: state.pc + i: state.i + char_len + char_len: char_len + last_dot_pc: state.pc + } + // if we are mananging a .* stay on the same char on return + if re.prog[state.pc].rep_min == 0 { + dot_state.i -= char_len + } + + re.state_list << dot_state + + m_state = .ist_quant_n + // println("dot_char stack len: ${re.state_list.len}") + continue + } + + state.match_flag = true + l_ist = u32(regex.ist_dot_char) + + if state.first_match < 0 { + state.first_match = state.i + } + state.match_index = state.i + re.prog[state.pc].rep++ // increase repetitions + + state.i += char_len + m_state = .ist_quant_p + continue + } + // char class IST + else if ist == regex.ist_char_class_pos || ist == regex.ist_char_class_neg { + state.match_flag = false + mut cc_neg := false + + if ist == regex.ist_char_class_neg { + cc_neg = true + } + mut cc_res := re.check_char_class(state.pc, ch) + + if cc_neg { + cc_res = !cc_res + } + + if cc_res { + state.match_flag = true + l_ist = u32(regex.ist_char_class_pos) + + if state.first_match < 0 { + state.first_match = state.i + } + + state.match_index = state.i + + re.prog[state.pc].rep++ // increase repetitions + state.i += char_len // next char + m_state = .ist_quant_p + continue + } + m_state = .ist_quant_n + continue + } + // check bsls + else if ist == regex.ist_bsls_char { + state.match_flag = false + tmp_res := re.prog[state.pc].validator(byte(ch)) + // println("BSLS in_ch: ${ch:c} res: $tmp_res") + if tmp_res { + state.match_flag = true + l_ist = u32(regex.ist_bsls_char) + + if state.first_match < 0 { + state.first_match = state.i + } + + state.match_index = state.i + + re.prog[state.pc].rep++ // increase repetitions + state.i += char_len // next char + m_state = .ist_quant_p + continue + } + m_state = .ist_quant_n + continue + } + // simple char IST + else if ist == regex.ist_simple_char { + // println("ist_simple_char") + state.match_flag = false + + if re.prog[state.pc].ch == ch { + state.match_flag = true + l_ist = regex.ist_simple_char + + if state.first_match < 0 { + state.first_match = state.i + } + // println("state.match_index: ${state.match_index}") + state.match_index = state.i + + re.prog[state.pc].rep++ // increase repetitions + state.i += char_len // next char + m_state = .ist_quant_p + continue + } + m_state = .ist_quant_n + continue + } + // UNREACHABLE + // println("PANIC2!! state: $m_state") + return regex.err_internal_error, state.i + } + /*********************************** + * Quantifier management + ***********************************/ + // ist_quant_ng => quantifier negative test on group + else if m_state == .ist_quant_ng { + // we are finished here + if state.group_index < 0 { + // println("Early stop!") + result = regex.no_match_found + m_state = .stop + continue + } + + tmp_pc := re.group_data[state.group_index] // PC to the end of the group token + rep := re.prog[tmp_pc].group_rep // use a temp variable + re.prog[tmp_pc].group_rep = 0 // clear the repetitions + + // println(".ist_quant_ng group_pc_end: $tmp_pc rep: $rep") + + if rep >= re.prog[tmp_pc].rep_min { + // println("ist_quant_ng GROUP CLOSED OK group_index: $state.group_index") + + state.i = re.group_stack[state.group_index] + state.pc = tmp_pc + state.group_index-- + m_state = .ist_next + continue + } else if re.prog[tmp_pc].next_is_or { + // println("ist_quant_ng OR Negative branch") + + state.i = re.group_stack[state.group_index] + state.pc = re.prog[tmp_pc + 1].rep_min - 1 + state.group_index-- + m_state = .ist_next + continue + } else if rep > 0 && rep < re.prog[tmp_pc].rep_min { + // println("ist_quant_ng UNDER THE MINIMUM g.i: $state.group_index") + + // check if we are inside a group, if yes exit from the nested groups + if state.group_index > 0 { + state.group_index-- + state.pc = tmp_pc + m_state = .ist_quant_ng //.ist_next + continue + } + + if state.group_index == 0 { + state.group_index-- + state.pc = tmp_pc // TEST + m_state = .ist_next + continue + } + + result = regex.no_match_found + m_state = .stop + continue + } else if rep == 0 && rep < re.prog[tmp_pc].rep_min { + // println("ist_quant_ng c_zero UNDER THE MINIMUM g.i: $state.group_index") + + if state.group_index > 0 { + state.group_index-- + state.pc = tmp_pc + m_state = .ist_quant_ng //.ist_next + continue + } + + result = regex.no_match_found + m_state = .stop + continue + } + + // println("DO NOT STAY HERE!! {${re.prog[tmp_pc].rep_min},${re.prog[tmp_pc].rep_max}}:$rep") + // UNREACHABLE + return regex.err_internal_error, state.i + } + // ist_quant_pg => quantifier positive test on group + else if m_state == .ist_quant_pg { + // println(".ist_quant_pg") + mut tmp_pc := state.pc + if state.group_index >= 0 { + tmp_pc = re.group_data[state.group_index] + } + + rep := re.prog[tmp_pc].group_rep + + if rep < re.prog[tmp_pc].rep_min { + // println("ist_quant_pg UNDER RANGE") + state.pc = re.prog[tmp_pc].goto_pc + m_state = .ist_next + continue + } else if rep == re.prog[tmp_pc].rep_max { + // println("ist_quant_pg MAX RANGE") + re.prog[tmp_pc].group_rep = 0 // clear the repetitions + state.group_index-- + m_state = .ist_next + + continue + } else if rep >= re.prog[tmp_pc].rep_min { + // println("ist_quant_pg IN RANGE group_index:$state.group_index") + + // check greedy flag, if true exit on minimum + if re.prog[tmp_pc].greedy == true { + re.prog[tmp_pc].group_rep = 0 // clear the repetitions + state.group_index-- + m_state = .ist_next + continue + } + + state.pc = re.prog[tmp_pc].goto_pc - 1 + state.group_index-- + m_state = .ist_next + continue + } + + // UNREACHABLE + // println("PANIC3!! state: $m_state") + return regex.err_internal_error, state.i + } + // ist_quant_n => quantifier negative test on token + else if m_state == .ist_quant_n { + rep := re.prog[state.pc].rep + // println("Here!! PC $state.pc is_next_or: ${re.prog[state.pc].next_is_or}") + + // zero quantifier * or ? + if rep == 0 && re.prog[state.pc].rep_min == 0 { + // println("ist_quant_n c_zero RANGE MIN") + m_state = .ist_next // go to next ist + continue + } + // match + or * + else if rep >= re.prog[state.pc].rep_min { + // println("ist_quant_n MATCH RANGE") + m_state = .ist_next + continue + } + + // check the OR if present + if re.prog[state.pc].next_is_or { + // println("OR present on failing") + state.match_index = -1 + m_state = .ist_next + continue + } + + // we are in a group manage no match from here + if state.group_index >= 0 { + // println("ist_quant_n FAILED insied a GROUP group_index:$state.group_index") + m_state = .ist_quant_ng + continue + } + + // no other options + // println("ist_quant_n no_match_found") + result = regex.no_match_found + m_state = .stop + continue + // return no_match_found, 0 + } + // ist_quant_p => quantifier positive test on token + else if m_state == .ist_quant_p { + // exit on first match + if (re.flag & regex.f_efm) != 0 { + return state.i, state.i + 1 + } + + rep := re.prog[state.pc].rep + + // under range + if rep > 0 && rep < re.prog[state.pc].rep_min { + // println("ist_quant_p UNDER RANGE") + m_state = .ist_load // continue the loop + continue + } + // range ok, continue loop + else if rep >= re.prog[state.pc].rep_min && rep < re.prog[state.pc].rep_max { + // println("ist_quant_p IN RANGE") + + // check greedy flag, if true exit on minimum + if re.prog[state.pc].greedy == true { + m_state = .ist_next + continue + } + m_state = .ist_load + continue + } + // max reached + else if rep == re.prog[state.pc].rep_max { + // println("ist_quant_p MAX RANGE") + m_state = .ist_next + continue + } + } + // UNREACHABLE + // println("PANIC4!! state: $m_state") + return regex.err_internal_error, state.i + } + + // println("Check end of text!") + // Check the results + if state.match_index >= 0 { + if state.group_index < 0 { + if re.prog[state.pc].ist == regex.ist_prog_end { + // println("program ended!!") + + if (re.flag & regex.f_src) != 0 { + // println("find return") + return state.first_match, state.i + } else { + // println("Here!!") + return 0, state.i + } + } + + // println("No Group here, natural end [$state.first_match,$state.i] state: ${state_str(m_state)} ist: $ist pgr_end: $re.prog.len") + + if re.prog[state.pc + 1].ist == regex.ist_prog_end + || re.prog[state.pc].ist == regex.ist_prog_end { + rep := re.prog[state.pc].rep + // println("rep: $rep re.prog[state.pc].rep_min: ${re.prog[state.pc].rep_min} re.prog[state.pc].rep_max: ${re.prog[state.pc].rep_max}") + if rep >= re.prog[state.pc].rep_min && rep <= re.prog[state.pc].rep_max { + return state.first_match, state.i + } + // println("Program not finished! ") + return regex.no_match_found, 0 + } + if src_end { + // println("program end") + return state.first_match, state.i + } + // print("No match found!!") + return regex.no_match_found, 0 + } else { + // println("Group match! OK") + // println("first_match: $state.first_match, i: $state.i") + + // println("Skip last group") + return state.first_match, state.i + // return state.first_match,re.group_stack[state.group_index--] + } + } + // println("no_match_found, natural end") + return regex.no_match_found, 0 +} diff --git a/v_windows/v/vlib/regex/regex_opt.v b/v_windows/v/vlib/regex/regex_opt.v new file mode 100644 index 0000000..2aaa88f --- /dev/null +++ b/v_windows/v/vlib/regex/regex_opt.v @@ -0,0 +1,53 @@ +module regex + +import strings + +// compile_opt compile RE pattern string +pub fn (mut re RE) compile_opt(pattern string) ? { + re_err, err_pos := re.impl_compile(pattern) + + if re_err != compile_ok { + mut err_msg := strings.new_builder(300) + err_msg.write_string('\nquery: $pattern\n') + line := '-'.repeat(err_pos) + err_msg.write_string('err : $line^\n') + err_str := re.get_parse_error_string(re_err) + err_msg.write_string('ERROR: $err_str\n') + return error_with_code(err_msg.str(), re_err) + } +} + +// new_regex create a RE of small size, usually sufficient for ordinary use +pub fn new() RE { + // init regex + mut re := RE{} + re.prog = []Token{len: max_code_len + 1} // max program length, can not be longer then the pattern + re.cc = []CharClass{len: max_code_len} // can not be more char class the the length of the pattern + re.group_csave_flag = false // enable continuos group saving + re.group_max_nested = 128 // set max 128 group nested + re.group_max = max_code_len >> 1 // we can't have more groups than the half of the pattern legth + + re.group_stack = []int{len: re.group_max, init: -1} + re.group_data = []int{len: re.group_max, init: -1} + + return re +} + +// regex_opt create new RE object from RE pattern string +pub fn regex_opt(pattern string) ?RE { + // init regex + mut re := RE{} + re.prog = []Token{len: pattern.len + 1} // max program length, can not be longer then the pattern + re.cc = []CharClass{len: pattern.len} // can not be more char class the the length of the pattern + re.group_csave_flag = false // enable continuos group saving + re.group_max_nested = 128 // set max 128 group nested + re.group_max = pattern.len >> 1 // we can't have more groups than the half of the pattern legth + + re.group_stack = []int{len: re.group_max, init: -1} + re.group_data = []int{len: re.group_max, init: -1} + + // compile the pattern + re.compile_opt(pattern) ? + + return re +} diff --git a/v_windows/v/vlib/regex/regex_test.v b/v_windows/v/vlib/regex/regex_test.v new file mode 100644 index 0000000..aa6bf79 --- /dev/null +++ b/v_windows/v/vlib/regex/regex_test.v @@ -0,0 +1,608 @@ +import regex +import rand + +/****************************************************************************** +* +* Test section +* +******************************************************************************/ +struct TestItem { + src string + q string + s int + e int +} + +const( +match_test_suite = [ + // minus in CC + TestItem{"d.def",r"abc.\.[\w\-]{,100}",-1,0}, + TestItem{"abc12345.asd",r"abc.\.[\w\-]{,100}",-1,0}, + TestItem{"abca.exe",r"abc.\.[\w\-]{,100}",0,8}, + TestItem{"abc2.exe-test_12",r"abc.\.[\w\-]{,100}",0,16}, + TestItem{"abcdefGHK",r"[a-f]+\A+",0,9}, + TestItem{"ab-cd-efGHK",r"[a-f\-g]+\A+",0,11}, + + // base OR + TestItem{"a",r"a|b",0,1}, + TestItem{"a",r"b|a",0,1}, + TestItem{"b",r"a|b",0,1}, + TestItem{"b",r"b|a",0,1}, + TestItem{"c",r"b|a",-1,0}, + + // test base + TestItem{"[ciao]",r"(.)ciao(.)",0,6}, + TestItem{"[ciao] da me",r"(.)ciao(.)",0,6}, + + // positive + TestItem{"this is a good.",r"this",0,4}, + TestItem{"this is a good.",r"good",10,14}, + TestItem{"this is a good.",r"go+d",10,14}, + TestItem{"this is a good.",r"g[oae]+d",10,14}, + TestItem{"this is a goed.",r"g[oae]+d",10,14}, + TestItem{"this is a good.",r"g[oae]*d",10,14}, + TestItem{"this is a goaezd.",r"g[ea-cm-z]*d",10,16}, + TestItem{"this is a good.",r"this (\w+) a",0,9}, + TestItem{"this is a good.",r"this( \w+){2} g",0,11}, + TestItem{"this is a good.",r"( ?\w+){,1}",0,4}, + TestItem{"this is a good.",r"( ?\w+)+",0,14}, + TestItem{"this is a good.",r"this( \w+)+",0,14}, + TestItem{"this is a good sample.",r"( ?\w+){,2}",0,7}, + TestItem{"this is a good sample.",r"( ?\w+){,3}",0,9}, + TestItem{"this is a good sample.",r"( ?\w+){,4}",0,14}, + TestItem{"this is a good sample.",r"( ?\w+){,5}",0,21}, + TestItem{"this is a good sample.",r"( ?\w+){2,3}",0,9}, + TestItem{"this is a good sample.",r"(\s?\w+){2,3}",0,9}, + TestItem{"this these those.",r"(th[ei]se?\s|\.)+",0,11}, + TestItem{"this these those ",r"(th[eio]se? ?)+",0,17}, + TestItem{"this these those ",r"(th[eio]se? )+",0,17}, + TestItem{"this,these,those. over",r"(th[eio]se?[,. ])+",0,17}, + TestItem{"soday,this,these,those. over",r".+(th[eio]se?[,. ])+",0,23}, + + TestItem{"cpapaz",r"(c(pa)+z)",0,6}, + TestItem{"this is a cpapaz over",r"(c(pa)+z)",10,16}, + TestItem{"this is a cpapapez over",r"(c(p[ae])+z)",10,18}, + TestItem{"test@post.pip.com",r"[a-z0-9_]+@([a-z0-9_]+\.?)+",0,17}, + TestItem{"test1@post.pip.com, pera",r"[\w]+@([\w]+\.)+\w+",0,18}, + TestItem{"pippo@pera.com ",r"[a-z0-9_]+@([a-z0-9_]+\.?)+",0,14}, + TestItem{"adce aabe",r"(a(ab)+)|(a(dc)+)e",0,4}, + TestItem{"zadce aabe",r"(a(ab)+)|(a(dc)+)e",1,5}, + TestItem{"abbz accz addz.",r"c|(d)|e|(ab+)",0,3}, + TestItem{"this those these ciao",r"((t[hieo]+se?)\s*)+",0,17}, + TestItem{"this ciao",r"((t[hieo]+se?)\s*)+",0,5}, + TestItem{"this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}",5,21}, + TestItem{"1234this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}$",9,25}, + TestItem{"this cpapaz adce aabe third",r"(c(pa)+z)(\s[\a]+){2}",5,21}, + TestItem{"123cpapaz ole. pippo",r"(c(pa)+z)(\s+\a+[\.,]?)+",3,20}, + + TestItem{"this is a good sample.",r".*i(\w)+",0,4}, + TestItem{"soday,this,these,those. over",r".*,(th[eio]se?[,. ])+",0,23}, + TestItem{"soday,this,these,thesa.thesi over",r".*,(th[ei]se?[,. ])+(thes[ai][,. ])+",0,29}, + TestItem{"cpapaz ole. pippo,",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18}, + TestItem{"cpapaz ole. pippo",r"(c(pa)+z)(\s+\a+[\.,]?)+",0,17}, + TestItem{"cpapaz ole. pippo, 852",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18}, + TestItem{"123cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20}, + TestItem{"...cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20}, + + TestItem{"cpapaz ole. pippo,",r".*c.+ole.*pi",0,14}, + TestItem{"cpapaz ole. pipipo,",r".*c.+ole.*p([ip])+o",0,18}, + TestItem{"cpapaz ole. pipipo",r"^.*c.+ol?e.*p([ip])+o$",0,18}, + TestItem{"abbb",r"ab{2,3}?",0,3}, + TestItem{" pippo pera",r"\s(.*)pe(.*)",0,11}, + TestItem{" abb",r"\s(.*)",0,4}, + + TestItem{"/home/us_er/pippo/info-01.txt", r"(/?[-\w_]+)*\.txt$",0,29} + + // negative + TestItem{"zthis ciao",r"((t[hieo]+se?)\s*)+",-1,0}, + TestItem{"this is a good.",r"thes",-1,0}, + TestItem{"test1post.pip.com, pera",r"[\w]+@([\w]+\.)+\w+",-1,0}, + TestItem{"this cpapaz adce",r"(c(pa)+z)(\s[\a]+){2}",-1,0}, + TestItem{"this cpapaz adce aabe third",r"(c(pa)+z)(\s[\a]+){2}$",-1,0}, + TestItem{"1234this cpapaz adce aabe ter",r"(c(pa)+z)(\s[\a]+){2}$",-1,0}, + TestItem{"cpapaz ole. pipipo,",r"^.*c.+ol?e.*p([ip])+o$",-1,0}, + TestItem{"/home/us_er/pippo/info-01.jpeg", r"(/?[-\w_]+)*\.txt$",-1,0} + + // check unicode + TestItem{"this is a Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ test",r".*a [Ⅰ-Ⅵ ]+",0,34}, + TestItem{"123Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ test",r"[Ⅰ-Ⅴ\s]+",3,23}, + + // new edge cases + TestItem{"12345678", r"[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",-1,0}, + TestItem{"12345678", r"[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",0,8}, + TestItem{"123456789", r"^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$",0,9} + TestItem{"12345678", r"^\d{8}$",0,8}, + TestItem{"12345678", r"^\d{7}$",-1,0}, + TestItem{"12345678", r"^\d{9}$",-1,0}, + + TestItem{"eth", r"(oth)|(eth)",0,3}, + TestItem{"et", r"(oth)|(eth)",-1,0}, + TestItem{"et", r".*(oth)|(eth)",-1,0}, + TestItem{"peoth", r".*(ith)|(eth)",-1,0}, + + TestItem{"poth", r"(eth)|(oth)",1,4}, + TestItem{"poth", r"(oth)|(eth)",1,4}, + TestItem{"poth", r".(oth)|(eth)$",0,4}, + TestItem{"poth", r"^.(oth)|(eth)$",0,4}, + TestItem{"poth", r"^\w+$",0,4}, + + // test dot_char + TestItem{"8-11 l: qllllqllklhlvtl", r"^(\d+)-(\d+) ([a-z]): (.*)$",0,23}, + TestItem{"accccb deer", r"^a(.*)b d(.+)r",0,11}, + TestItem{"accccb deer", r"^a(.*)b d(.+)",0,11}, + TestItem{"accccb deer", r"^(.*)$",0,11}, + TestItem{"accccb deer", r"^a(.*)b d(.+)p",-1,0}, + TestItem{"##.#....#.##.####...#.##", r".{18}[.#]",0,19}, + TestItem{"#.#......##.#..#..##........##....###...##...######.......#.....#..#......#...#........###.#..#.", r'.*#[.#]{4}##[.#]{4}##[.#]{4}###',0,49}, + + // test bcksls chars + TestItem{"[ an s. s! ]( wi4ki:something )", r"\[.*\]\( *(\w*:*\w+) *\)",0,31}, + TestItem{"[ an s. s! ](wiki:something)", r"\[.*\]\( *(\w*:*\w+) *\)",0,28}, + TestItem{"p_p", r"\w+",0,3}, + TestItem{"p_é", r"\w+",0,2}, + + // Crazywulf tests (?:^|[()])(\d+)(*)(\d+)(?:$|[()]) + TestItem{"1*1", r"(\d+)([*])(\d+)",0,3}, + TestItem{"+1*1", r"^(\d+)([*])(\d+)",-1,0}, + TestItem{"*1*1", r"(?:^|[*])(\d+)([*])(\d+)",0,4}, + TestItem{"*1*1", r"(?:^|[*()])(\d+)([*])(\d+)",0,4}, + TestItem{")1*1", r"(?:^|[*()])(\d+)([*])(\d+)",0,4}, + TestItem{"(1*1", r"(?:^|[*()])(\d+)([*])(\d+)",0,4}, + TestItem{"*1*1(", r"(?:^|[*()])(\d+)([*])(\d+)(?:$|[*()])",0,5}, + TestItem{" 1*1(", r"(?:^|[*()])(\d+)([*])(\d+)(?:$|[*()])",-1,0}, + TestItem{"1*1 ", r"(?:^|[*()])(\d+)([*])(\d+)(?:$|[*()])",-1,0}, + + // particular groups + TestItem{"ababababac", r"ab(.*)(ac)",0,10}, + +] +) + +struct TestItemRe { + src string + q string + rep string + r string +} +const ( +match_test_suite_replace = [ + // replace tests + TestItemRe{ + "oggi pibao è andato a casa di pbababao ed ha trovato pibabababao", + r"(pi?(ba)+o)", + "CIAO", + "oggi CIAO è andato a casa di CIAO ed ha trovato CIAO" + }, + TestItemRe{ + "Today is a good day and tomorrow will be for sure.", + r"[Tt]o\w+", + "CIAO", + "CIAO is a good day and CIAO will be for sure." + }, + TestItemRe{ + "Today is a good day and tomorrow will be for sure.", + r"(a\w) ", + r"[\0] ", + "Tod[ay] is a good d[ay] and tomorrow will be for sure." + }, + TestItemRe{ + "Today is a good day and tomorrow will be for sure.", + r"(a\w) ", + r"[\0_\0] ", + "Tod[ay_ay] is a good d[ay_ay] and tomorrow will be for sure." + }, + TestItemRe{ + "Today is a good day and tomorrow will be for sure.", + r"(a\w) ", + r"[\0\1] ", + "Tod[ay] is a good d[ay] and tomorrow will be for sure." + }, +] + +match_test_suite_replace_simple = [ + // replace tests + TestItemRe{ + "oggi pibao è andato a casa di pbababao ed ha trovato pibabababao", + r"(pi?(ba)+o)", + "CIAO", + "oggi CIAO è andato a casa di CIAO ed ha trovato CIAO" + }, + TestItemRe{ + "Today is a good day and tomorrow will be for sure.", + r"[Tt]o\w+", + "CIAO", + "CIAO is a good day and CIAO will be for sure." + }, +] +) + +struct TestItemCGroup { + src string + q string + s int + e int + cg []int // [number of items (3*# item), id_group_0, start_0, end_0, id_group_1, start1, start2,... ] + cgn map[string]int +} +const ( +cgroups_test_suite = [ + TestItemCGroup{ + "http://www.ciao.mondo/hello/pippo12_/pera.html", + r"(?Phttps?)|(?:ftps?)://(?P[\w_]+[\.|/])+",0,42, + [7, 0, 0, 4, 1, 7, 11, 1, 11, 16, 1, 16, 22, 1, 22, 28, 1, 28, 37, 1, 37, 42], + {'format':int(0),'token':1} + }, + TestItemCGroup{ + "http://www.ciao.mondo/hello/pippo12_/pera.html", + r"(?Phttps?)|(?Pftps?)://(?P[\w_]+.)+",0,46, + [8, 0, 0, 4, 1, 7, 11, 1, 11, 16, 1, 16, 22, 1, 22, 28, 1, 28, 37, 1, 37, 42, 1, 42, 46] + //[8, 0, 0, 4, 1, 7, 10, 1, 11, 15, 1, 16, 21, 1, 22, 27, 1, 28, 36, 1, 37, 41, 1, 42, 46], + {'format':int(0),'token':1} + }, + TestItemCGroup{ + "http://www.ciao.mondo/hello/pippo12_/pera.html", + r"(?Phttps?)|(?Pftps?)://([\w_]+\.)+",0,16, + [3, 0, 0, 4, 1, 7, 11, 1, 11, 16], + {'format':int(0)} + }, + TestItemCGroup{ + "acc +13 pippo", + r"(\w+)\s(.)([0-9]+) \w+",0,13, + [0, 3, 4, 5, 5, 7], + map[string]int{} + }, + TestItemCGroup{ + "acc +13", + r"(\w+)\s(.)([0-9]+)",0,7, + [0, 3, 4, 5, 5, 7], + map[string]int{} + }, + TestItemCGroup{ + "ababababac", + r"ab(.*)(ac)",0,10, + [2, 8, 8, 10], + map[string]int{} + }, +] +) + + +struct Test_find_all { + src string + q string + res []int // [0,4,5,6...] + res_str []string // ['find0','find1'...] +} +const ( +find_all_test_suite = [ + Test_find_all{ + "abcd 1234 efgh 1234 ghkl1234 ab34546df", + r"\d+", + [5, 9, 15, 19, 24, 28, 31, 36], + ['1234', '1234', '1234', '34546'] + }, + Test_find_all{ + "abcd 1234 efgh 1234 ghkl1234 ab34546df", + r"\a+", + [0, 4, 10, 14, 20, 24, 29, 31, 36, 38], + ['abcd', 'efgh', 'ghkl', 'ab', 'df'] + }, + Test_find_all{ + "oggi pippo è andato a casa di pluto ed ha trovato pippo", + r"p[iplut]+o", + [5, 10, 31, 36, 51, 56], + ['pippo', 'pluto', 'pippo'] + }, + Test_find_all{ + "oggi pibao è andato a casa di pbababao ed ha trovato pibabababao", + r"(pi?(ba)+o)", + [5, 10, 31, 39, 54, 65], + ['pibao', 'pbababao', 'pibabababao'] + }, + Test_find_all{ + "Today is a good day and tomorrow will be for sure.", + r"[Tt]o\w+", + [0, 5, 24, 32], + ['Today', 'tomorrow'] + }, + Test_find_all{ + "pera\nurl = https://github.com/dario/pig.html\npippo", + r"url *= *https?://[\w./]+", + [5, 44], + ['url = https://github.com/dario/pig.html'] + }, + Test_find_all{ + "pera\nurl = https://github.com/dario/pig.html\npippo", + r"url *= *https?://.*"+'\n', + [5, 45], + ['url = https://github.com/dario/pig.html\n'] + }, + Test_find_all{ + "#.#......##.#..#..##........##....###...##...######.......#.....#..#......#...#........###.#..#.", + r"#[.#]{4}##[.#]{4}##[.#]{4}###", + [29, 49], + ['#....###...##...####'] + }, + Test_find_all{ + "#.#......##.#..#..##........##....###...##...######.......#.....#..#......#...#........###.#..#.", + r".*#[.#]{4}##[.#]{4}##[.#]{4}###", + [0, 49], + ['#.#......##.#..#..##........##....###...##...####'] + }, + Test_find_all{ + "1234 Aa dddd Aaf 12334 Aa opopo Aaf", + r"Aa.+Aaf", + [5, 16, 23, 35], + ['Aa dddd Aaf', 'Aa opopo Aaf'] + }, + Test_find_all{ + "@for something @endfor @for something else @endfor altro testo @for body @endfor uno due @for senza dire più @endfor pippo", + r"@for.+@endfor", + [0, 22, 23, 50, 63, 80, 89, 117], + ['@for something @endfor', '@for something else @endfor', '@for body @endfor', '@for senza dire più @endfor'] + } + +] +) + +const ( + debug = true // true for debug println +) + +fn test_regex(){ + // check capturing groups + for c,to in cgroups_test_suite { + // debug print + if debug { + println("$c [${to.src}] [q${to.q}] (${to.s}, ${to.e})") + } + + mut re := regex.regex_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + + if to.cgn.len > 0 { + re.group_csave_flag = true + //re.group_csave = [-1].repeat(3*20+1) + if debug { println("continuous save")} + } else { + if debug { println("NO continuous save")} + } + + start, end := re.match_string(to.src) + + mut tmp_str := "" + if start >= 0 && end > start{ + tmp_str = to.src[start..end] + } + + if start != to.s || end != to.e { + //println("#$c [$to.src] q[$to.q] res[$tmp_str] $start, $end") + eprintln("ERROR!") + //C.printf("ERROR!! res:(%d, %d) refh:(%d, %d)\n",start, end, to.s, to.e) + assert false + continue + } + + // check cgroups + if to.cgn.len > 0 { + if re.group_csave.len == 0 || re.group_csave[0] != to.cg[0] { + eprintln("Capturing group len error! found: ${re.group_csave[0]} true ground: ${to.cg[0]}") + assert false + continue + } + + // check captured groups + mut ln := re.group_csave[0]*3 + for ln > 0 { + if re.group_csave[ln] != to.cg[ln] { + eprintln("Capturing group failed on $ln item!") + assert false + } + ln-- + } + + // check named captured groups + for k in to.cgn.keys() { + if to.cgn[k] != (re.group_map[k]-1) { // we have -1 because the map not found is 0, in groups we start from 0 and we store using +1 + eprintln("Named capturing group error! [$k]") + assert false + continue + } + } + } else { + // check normal captured groups + if re.groups.len != to.cg.len { + assert false + } + for ln:=0; ln < re.groups.len; ln++ { + if re.groups[ln] != to.cg[ln] { + eprintln("Capture group doesn't match:") + eprintln("true ground: [${to.cg}]") + eprintln("elaborated : [${re.groups}]") + assert false + } + } + } + } + + // check find_all + for c,to in find_all_test_suite { + // debug print + if debug { println("#$c [$to.src] q[$to.q] ($to.res, $to.res_str)") } + + mut re := regex.regex_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + + re.reset() + res := re.find_all(to.src) + if res != to.res { + eprintln('err: find_all !!') + if debug { println("#$c exp: $to.res calculated: $res") } + assert false + } + + res_str := re.find_all_str(to.src) + if res_str != to.res_str { + eprintln('err: find_all_str !!') + if debug { println("#$c exp: $to.res_str calculated: $res_str") } + assert false + } + } + + // check replace + for c,to in match_test_suite_replace{ + // debug print + if debug { println("#$c [$to.src] q[$to.q] $to.r") } + + mut re := regex.regex_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + + res := re.replace(to.src,to.rep) + if res != to.r { + eprintln("ERROR: replace.") + assert false + continue + } + } + + // check replace simple + for c,to in match_test_suite_replace_simple{ + // debug print + if debug { println("#$c [$to.src] q[$to.q] $to.r") } + + mut re := regex.regex_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + + res := re.replace_simple(to.src,to.rep) + if res != to.r { + eprintln("ERROR: replace.") + assert false + continue + } + } + + // check match and find + for c,to in match_test_suite { + // debug print + if debug { println("#$c [$to.src] q[$to.q] $to.s $to.e") } + + // test the find + if to.s > 0 { + mut re := regex.regex_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + // q_str := re.get_query() + // eprintln("Query: $q_str") + start,end := re.find(to.src) + + if start != to.s || end != to.e { + err_str := re.get_parse_error_string(start) + eprintln("ERROR : $err_str start: ${start} end: ${end}") + assert false + } else { + //tmp_str := text[start..end] + //println("found in [$start, $end] => [$tmp_str]") + assert true + } + continue + } + + // test the match + mut re := regex.new() + //re.debug = true + + re.compile_opt(to.q) or { + eprintln('err: $err') + assert false + continue + } + //println("#$c [$to.src] q[$to.q]") + start, end := re.match_string(to.src) + + mut tmp_str := "" + if start >= 0 && end > start{ + tmp_str = to.src[start..end] + } + + if start != to.s || end != to.e { + eprintln("#$c [$to.src] q[$to.q] res[$tmp_str] $start, $end") + eprintln("ERROR!") + //C.printf("ERROR!! res:(%d, %d) refh:(%d, %d)\n",start, end, to.s, to.e) + assert false + continue + } + + // rerun to test consistency + tmp_str1 := to.src.clone() + start1, end1 := re.match_string(tmp_str1) + if start1 != start || end1 != end { + eprintln("two run ERROR!!") + assert false + continue + } + + } + + if debug { println("DONE!") } +} + +// test regex_base function +fn test_regex_func(){ + query := r"\d\dabcd" + test_str := "78abcd" + mut re, re_err, err_pos := regex.regex_base(query) + if re_err == regex.compile_ok { + start, end := re.match_string(test_str) + assert (start == 0) && (end == 6) + } else { + eprintln("Error in query string in pos ${err_pos}") + eprintln("Error: ${re.get_parse_error_string(re_err)}") + assert false + } +} + +fn my_repl(re regex.RE, in_txt string, start int, end int) string { + s0 := re.get_group_by_id(in_txt,0)[0..1] + "X" + s1 := re.get_group_by_id(in_txt,1)[0..1] + "X" + s2 := re.get_group_by_id(in_txt,2)[0..1] + "X" + return "${s0}${s1}${s2}" +} + + +// test regex replace function +fn test_regex_func_replace(){ + filler := "E il primo dei tre regni dell'Oltretomba cristiano visitato da Dante nel corso del viaggio, con la guida di Virgilio." + txt := r'"content": "They dont necessarily flag "you will be buying these shares on margin!"", "channel_id"' + query := r'"(content":\s+")(.*)(, "channel_id")' + mut re := regex.regex_opt(query) or { panic(err) } + + mut txt1 := "" + mut txt2 := "" + + for _ in 0..3 { + rnd := int(10+rand.u32() % 20) + txt1 += txt + filler[0..rnd] + "\n" + txt2 += "cXTX,X" + filler[0..rnd] + "\n" + } + + result := re.replace_by_fn(txt1, my_repl) + if debug { + eprintln(result) + eprintln(txt2) + } + assert result == txt2 +} \ No newline at end of file diff --git a/v_windows/v/vlib/regex/regex_util.v b/v_windows/v/vlib/regex/regex_util.v new file mode 100644 index 0000000..0bf1a81 --- /dev/null +++ b/v_windows/v/vlib/regex/regex_util.v @@ -0,0 +1,436 @@ +/* +regex 1.0 alpha + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. +*/ +module regex + +import strings + +/****************************************************************************** +* +* Inits +* +******************************************************************************/ +// regex create a regex object from the query string, retunr RE object and errors as re_err, err_pos +pub fn regex_base(pattern string) (RE, int, int) { + // init regex + mut re := RE{} + re.prog = []Token{len: pattern.len + 1} // max program length, can not be longer then the pattern + re.cc = []CharClass{len: pattern.len} // can not be more char class the the length of the pattern + re.group_csave_flag = false // enable continuos group saving + re.group_max_nested = 128 // set max 128 group nested + re.group_max = pattern.len >> 1 // we can't have more groups than the half of the pattern legth + + re.group_stack = []int{len: re.group_max, init: -1} + re.group_data = []int{len: re.group_max, init: -1} + + re_err, err_pos := re.impl_compile(pattern) + return re, re_err, err_pos +} + +/****************************************************************************** +* +* Utilities +* +******************************************************************************/ +// get_group_bounds_by_name get a group boundaries by its name +pub fn (re RE) get_group_bounds_by_name(group_name string) (int, int) { + if group_name in re.group_map { + tmp_index := re.group_map[group_name] - 1 + start := re.groups[tmp_index * 2] + end := re.groups[tmp_index * 2 + 1] + return start, end + } + return -1, -1 +} + +// get_group_by_name get a group boundaries by its name +pub fn (re RE) get_group_by_name(in_txt string, group_name string) string { + if group_name in re.group_map { + tmp_index := re.group_map[group_name] - 1 + start := re.groups[tmp_index * 2] + end := re.groups[tmp_index * 2 + 1] + if start >= 0 && end > start { + return in_txt[start..end] + } + } + return '' +} + +// get_group_by_id get a group string by its id +pub fn (re RE) get_group_by_id(in_txt string, group_id int) string { + if group_id < (re.groups.len >> 1) { + index := group_id << 1 + start := re.groups[index] + end := re.groups[index + 1] + if start >= 0 && end > start { + return in_txt[start..end] + } + } + return '' +} + +// get_group_by_id get a group boundaries by its id +pub fn (re RE) get_group_bounds_by_id(group_id int) (int, int) { + if group_id < re.group_count { + index := group_id << 1 + return re.groups[index], re.groups[index + 1] + } + return -1, -1 +} + +pub struct Re_group { +pub: + start int = -1 + end int = -1 +} + +// get_group_list return a list of Re_group for the found groups +pub fn (re RE) get_group_list() []Re_group { + mut res := []Re_group{len: re.groups.len >> 1} + mut gi := 0 + // println("len: ${re.groups.len} groups: ${re.groups}") + + for gi < re.groups.len { + if re.groups[gi] >= 0 { + txt_st := re.groups[gi] + txt_en := re.groups[gi + 1] + + // println("#${gi/2} start: ${re.groups[gi]} end: ${re.groups[gi + 1]} ") + if txt_st >= 0 && txt_en > txt_st { + tmp := Re_group{ + start: re.groups[gi] + end: re.groups[gi + 1] + } + // println(tmp) + res[gi >> 1] = tmp + } else { + res[gi >> 1] = Re_group{} + } + } + gi += 2 + } + return res +} + +/****************************************************************************** +* +* Matchers +* +******************************************************************************/ +// match_string Match the pattern with the in_txt string +[direct_array_access] +pub fn (mut re RE) match_string(in_txt string) (int, int) { + start, mut end := re.match_base(in_txt.str, in_txt.len + 1) + if end > in_txt.len { + end = in_txt.len + } + + if start >= 0 && end > start { + if (re.flag & f_ms) != 0 && start > 0 { + return no_match_found, 0 + } + if (re.flag & f_me) != 0 && end < in_txt.len { + if in_txt[end] in new_line_list { + return start, end + } + return no_match_found, 0 + } + return start, end + } + return start, end +} + +/****************************************************************************** +* +* Finders +* +******************************************************************************/ +/* +// find internal implementation HERE for reference do not remove!! +[direct_array_access] +fn (mut re RE) find_imp(in_txt string) (int,int) { + old_flag := re.flag + re.flag |= f_src // enable search mode + + start, mut end := re.match_base(in_txt.str, in_txt.len + 1) + //print("Find [$start,$end] '${in_txt[start..end]}'") + if end > in_txt.len { + end = in_txt.len + } + re.flag = old_flag + + if start >= 0 && end > start { + return start, end + } + return no_match_found, 0 +} +*/ + +// find try to find the first match in the input string +[direct_array_access] +pub fn (mut re RE) find(in_txt string) (int, int) { + // old_flag := re.flag + // re.flag |= f_src // enable search mode + + mut i := 0 + for i < in_txt.len { + mut s := -1 + mut e := -1 + unsafe { + // tmp_str := tos(in_txt.str + i, in_txt.len - i) + // println("Check: [$tmp_str]") + s, e = re.match_base(in_txt.str + i, in_txt.len - i + 1) + + if s >= 0 && e > s { + // println("find match in: ${i+s},${i+e} [${in_txt[i+s..i+e]}]") + // re.flag = old_flag + return i + s, i + e + } + i++ + } + } + // re.flag = old_flag + return -1, -1 +} + +// find try to find the first match in the input string strarting from start index +[direct_array_access] +pub fn (mut re RE) find_from(in_txt string, start int) (int, int) { + old_flag := re.flag + re.flag |= f_src // enable search mode + + mut i := start + if i < 0 { + return -1, -1 + } + for i < in_txt.len { + //--- speed references --- + + mut s := -1 + mut e := -1 + + unsafe { + tmp_str := tos(in_txt.str + i, in_txt.len - i) + s, e = re.match_string(tmp_str) + } + //------------------------ + // s,e = re.find_imp(in_txt[i..]) + //------------------------ + if s >= 0 && e > s { + // println("find match in: ${i+s},${i+e} [${in_txt[i+s..i+e]}]") + re.flag = old_flag + return i + s, i + e + } else { + i++ + } + } + re.flag = old_flag + return -1, -1 +} + +// find_all find all the non overlapping occurrences of the match pattern +[direct_array_access] +pub fn (mut re RE) find_all(in_txt string) []int { + // old_flag := re.flag + // re.flag |= f_src // enable search mode + + mut i := 0 + mut res := []int{} + + for i < in_txt.len { + mut s := -1 + mut e := -1 + unsafe { + // tmp_str := in_txt[i..] + // tmp_str := tos(in_txt.str + i, in_txt.len - i) + // println("Check: [$tmp_str]") + s, e = re.match_base(in_txt.str + i, in_txt.len + 1 - i) + + if s >= 0 && e > s { + res << i + s + res << i + e + i += e + continue + } + } + i++ + } + // re.flag = old_flag + return res +} + +// find_all_str find all the non overlapping occurrences of the match pattern, return a string list +[direct_array_access] +pub fn (mut re RE) find_all_str(in_txt string) []string { + // old_flag := re.flag + // re.flag |= f_src // enable search mode + + mut i := 0 + mut res := []string{} + + for i < in_txt.len { + mut s := -1 + mut e := -1 + unsafe { + // tmp_str := in_txt[i..] + // tmp_str := tos(in_txt.str + i, in_txt.len - i) + // println("Check: [$tmp_str]") + s, e = re.match_base(in_txt.str + i, in_txt.len + 1 - i) + + if s >= 0 && e > s { + tmp_str := tos(in_txt.str + i, in_txt.len - i) + // println("Found: $s:$e [${tmp_str[s..e]}]") + res << tmp_str[..e] + i += e + continue + } + } + i++ + } + // re.flag = old_flag + return res +} + +/****************************************************************************** +* +* Replacers +* +******************************************************************************/ +// replace_simple return a string where the matches are replaced with the replace string +pub fn (mut re RE) replace_simple(in_txt string, repl string) string { + pos := re.find_all(in_txt) + + if pos.len > 0 { + mut res := '' + mut i := 0 + + mut s1 := 0 + mut e1 := in_txt.len + + for i < pos.len { + e1 = pos[i] + res += in_txt[s1..e1] + repl + s1 = pos[i + 1] + i += 2 + } + + res += in_txt[s1..] + return res + } + return in_txt +} + +// type of function used for custom replace +// in_txt source text +// start index of the start of the match in in_txt +// end index of the end of the match in in_txt +// the match is in in_txt[start..end] +pub type FnReplace = fn (re RE, in_txt string, start int, end int) string + +// replace_by_fn return a string where the matches are replaced with the string from the repl_fn callback function +pub fn (mut re RE) replace_by_fn(in_txt string, repl_fn FnReplace) string { + mut i := 0 + mut res := strings.new_builder(in_txt.len) + mut last_end := 0 + + for i < in_txt.len { + // println("Find Start. $i [${in_txt[i..]}]") + s, e := re.find_from(in_txt, i) + // println("Find End.") + if s >= 0 && e > s { + // println("find match in: ${s},${e} [${in_txt[s..e]}]") + + if last_end < s { + res.write_string(in_txt[last_end..s]) + } + + for g_i in 0 .. re.group_count { + re.groups[g_i << 1] += i + re.groups[(g_i << 1) + 1] += i + } + + repl := repl_fn(re, in_txt, s, e) + // println("repl res: $repl") + res.write_string(repl) + // res.write_string("[[${in_txt[s..e]}]]") + + last_end = e + i = e + } else { + break + // i++ + } + // println(i) + } + if last_end >= 0 && last_end < in_txt.len { + res.write_string(in_txt[last_end..]) + } + return res.str() +} + +fn (re RE) parsed_replace_string(in_txt string, repl string) string { + str_lst := repl.split('\\') + mut res := str_lst[0] + mut i := 1 + for i < str_lst.len { + tmp := str_lst[i] + // println("tmp: ${tmp}") + if tmp.len > 0 && tmp[0] >= `0` && tmp[0] <= `9` { + group_id := int(tmp[0] - `0`) + group := re.get_group_by_id(in_txt, group_id) + // println("group: $group_id [$group]") + res += '$group${tmp[1..]}' + } else { + res += '\\' + tmp + } + i++ + } + return res +} + +// replace return a string where the matches are replaced with the repl_str string, +// this function support use groups in the replace string +pub fn (mut re RE) replace(in_txt string, repl_str string) string { + mut i := 0 + mut res := strings.new_builder(in_txt.len) + mut last_end := 0 + + for i < in_txt.len { + // println("Find Start. $i [${in_txt[i..]}]") + s, e := re.find_from(in_txt, i) + // println("Find End.") + if s >= 0 && e > s { + // println("find match in: ${s},${e} [${in_txt[s..e]}]") + + if last_end < s { + res.write_string(in_txt[last_end..s]) + } + + for g_i in 0 .. re.group_count { + re.groups[g_i << 1] += i + re.groups[(g_i << 1) + 1] += i + } + + // repl := repl_fn(re, in_txt, s, e) + repl := re.parsed_replace_string(in_txt, repl_str) + // println("repl res: $repl") + res.write_string(repl) + // res.write_string("[[${in_txt[s..e]}]]") + + last_end = e + i = e + } else { + break + // i++ + } + // println(i) + } + if last_end >= 0 && last_end < in_txt.len { + res.write_string(in_txt[last_end..]) + } + return res.str() +} diff --git a/v_windows/v/vlib/runtime/runtime.v b/v_windows/v/vlib/runtime/runtime.v new file mode 100644 index 0000000..4f92fe1 --- /dev/null +++ b/v_windows/v/vlib/runtime/runtime.v @@ -0,0 +1,56 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module runtime + +import os + +// nr_jobs returns the same as `nr_cpus` with the difference that if an +// environment variable `VJOBS` is set, and has a value > 0, +// then `nr_jobs` will return that number instead. +// This is useful for runtime tweaking of e.g. threaded or concurrent code. +pub fn nr_jobs() int { + mut cpus := nr_cpus() - 1 + // allow for overrides, for example using `VJOBS=32 ./v test .` + vjobs := os.getenv('VJOBS').int() + if vjobs > 0 { + cpus = vjobs + } + if cpus == 0 { + return 1 + } + return cpus +} + +// is_32bit returns true if the current executable is running on a 32 bit system. +pub fn is_32bit() bool { + $if x32 { + return true + } + return false +} + +// is_64bit returns true if the current executable is running on a 64 bit system. +pub fn is_64bit() bool { + $if x64 { + return true + } + return false +} + +// is_little_endian returns true if the current executable is running on a little-endian system. +pub fn is_little_endian() bool { + $if little_endian { + return true + } + return false +} + +// is_big_endian returns true if the current executable is running on a big-endian system. +pub fn is_big_endian() bool { + $if big_endian { + return true + } + return false +} diff --git a/v_windows/v/vlib/runtime/runtime_nix.c.v b/v_windows/v/vlib/runtime/runtime_nix.c.v new file mode 100644 index 0000000..a5c69b0 --- /dev/null +++ b/v_windows/v/vlib/runtime/runtime_nix.c.v @@ -0,0 +1,11 @@ +module runtime + +fn C.sysconf(name int) i64 + +// nr_cpus returns the number of virtual CPU cores found on the system. +pub fn nr_cpus() int { + $if linux || macos || solaris { + return int(C.sysconf(C._SC_NPROCESSORS_ONLN)) + } + return 1 +} diff --git a/v_windows/v/vlib/runtime/runtime_test.v b/v_windows/v/vlib/runtime/runtime_test.v new file mode 100644 index 0000000..d180890 --- /dev/null +++ b/v_windows/v/vlib/runtime/runtime_test.v @@ -0,0 +1,39 @@ +import runtime + +fn test_nr_cpus() { + nr_cpus := runtime.nr_cpus() + assert nr_cpus > 0 +} + +fn test_nr_jobs() { + nr_jobs := runtime.nr_jobs() + assert nr_jobs > 0 +} + +fn test_is_32bit() { + x := runtime.is_32bit().str() + assert x == 'true' || x == 'false' +} + +fn test_is_64bit() { + x := runtime.is_64bit().str() + assert x == 'true' || x == 'false' +} + +fn test_is_little_endian() { + x := runtime.is_little_endian().str() + assert x == 'true' || x == 'false' +} + +fn test_is_big_endian() { + x := runtime.is_big_endian().str() + assert x == 'true' || x == 'false' +} + +fn test_is_big_endian_different_than_is_little_endian() { + assert runtime.is_big_endian() != runtime.is_little_endian() +} + +fn test_is_32bit_different_than_is_64bit() { + assert runtime.is_32bit() != runtime.is_64bit() +} diff --git a/v_windows/v/vlib/runtime/runtime_windows.c.v b/v_windows/v/vlib/runtime/runtime_windows.c.v new file mode 100644 index 0000000..de5b2ce --- /dev/null +++ b/v_windows/v/vlib/runtime/runtime_windows.c.v @@ -0,0 +1,21 @@ +module runtime + +import os + +[typedef] +struct C.SYSTEM_INFO { + dwNumberOfProcessors u32 +} + +fn C.GetSystemInfo(&C.SYSTEM_INFO) + +// nr_cpus returns the number of virtual CPU cores found on the system. +pub fn nr_cpus() int { + sinfo := C.SYSTEM_INFO{} + C.GetSystemInfo(&sinfo) + mut nr := int(sinfo.dwNumberOfProcessors) + if nr == 0 { + nr = os.getenv('NUMBER_OF_PROCESSORS').int() + } + return nr +} diff --git a/v_windows/v/vlib/semver/LICENSE.md b/v_windows/v/vlib/semver/LICENSE.md new file mode 100644 index 0000000..8d5ff71 --- /dev/null +++ b/v_windows/v/vlib/semver/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 alexesprit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v_windows/v/vlib/semver/README.md b/v_windows/v/vlib/semver/README.md new file mode 100644 index 0000000..e7c8d20 --- /dev/null +++ b/v_windows/v/vlib/semver/README.md @@ -0,0 +1,37 @@ +# semver + +A library for working with versions in [semver][semver] format. + +## Usage + +```v +import semver + +fn main() { + ver1 := semver.from('1.2.4') or { + println('Invalid version') + return + } + ver2 := semver.from('2.3.4') or { + println('Invalid version') + return + } + println(ver1.gt(ver2)) + println(ver2.gt(ver1)) + println(ver1.satisfies('>=1.1.0 <2.0.0')) + println(ver2.satisfies('>=1.1.0 <2.0.0')) + println(ver2.satisfies('>=1.1.0 <2.0.0 || >2.2.0')) +} +``` + +``` +false +true +true +false +true +``` + +For more details see `semver.v` file. + +[semver]: https://semver.org/ diff --git a/v_windows/v/vlib/semver/compare.v b/v_windows/v/vlib/semver/compare.v new file mode 100644 index 0000000..0b51d29 --- /dev/null +++ b/v_windows/v/vlib/semver/compare.v @@ -0,0 +1,59 @@ +module semver + +// * Private functions. +[inline] +fn version_satisfies(ver Version, input string) bool { + range := parse_range(input) or { return false } + return range.satisfies(ver) +} + +fn compare_eq(v1 Version, v2 Version) bool { + return v1.major == v2.major && v1.minor == v2.minor && v1.patch == v2.patch + && v1.prerelease == v2.prerelease +} + +fn compare_gt(v1 Version, v2 Version) bool { + if v1.major < v2.major { + return false + } + if v1.major > v2.major { + return true + } + if v1.minor < v2.minor { + return false + } + if v1.minor > v2.minor { + return true + } + return v1.patch > v2.patch +} + +fn compare_lt(v1 Version, v2 Version) bool { + if v1.major > v2.major { + return false + } + if v1.major < v2.major { + return true + } + if v1.minor > v2.minor { + return false + } + if v1.minor < v2.minor { + return true + } + return v1.patch < v2.patch +} + +fn compare_ge(v1 Version, v2 Version) bool { + if compare_eq(v1, v2) { + return true + } + return compare_gt(v1, v2) +} + +fn compare_le(v1 Version, v2 Version) bool { + if compare_eq(v1, v2) { + return true + } + return compare_lt(v1, v2) +} diff --git a/v_windows/v/vlib/semver/parse.v b/v_windows/v/vlib/semver/parse.v new file mode 100644 index 0000000..3443f59 --- /dev/null +++ b/v_windows/v/vlib/semver/parse.v @@ -0,0 +1,85 @@ +module semver + +// * Private structs and functions. +struct RawVersion { + prerelease string + metadata string +mut: + raw_ints []string +} + +const ( + ver_major = 0 + ver_minor = 1 + ver_patch = 2 + versions = [ver_major, ver_minor, ver_patch] +) + +// TODO: Rewrite using regexps? +// /(\d+)\.(\d+)\.(\d+)(?:\-([0-9A-Za-z-.]+))?(?:\+([0-9A-Za-z-]+))?/ +fn parse(input string) RawVersion { + mut raw_version := input + mut prerelease := '' + mut metadata := '' + plus_idx := raw_version.last_index('+') or { -1 } + if plus_idx > 0 { + metadata = raw_version[(plus_idx + 1)..] + raw_version = raw_version[0..plus_idx] + } + hyphen_idx := raw_version.index('-') or { -1 } + if hyphen_idx > 0 { + prerelease = raw_version[(hyphen_idx + 1)..] + raw_version = raw_version[0..hyphen_idx] + } + raw_ints := raw_version.split('.') + return RawVersion{ + prerelease: prerelease + metadata: metadata + raw_ints: raw_ints + } +} + +fn (ver RawVersion) is_valid() bool { + if ver.raw_ints.len != 3 { + return false + } + return is_valid_number(ver.raw_ints[semver.ver_major]) + && is_valid_number(ver.raw_ints[semver.ver_minor]) + && is_valid_number(ver.raw_ints[semver.ver_patch]) && is_valid_string(ver.prerelease) + && is_valid_string(ver.metadata) +} + +fn (ver RawVersion) is_missing(typ int) bool { + return typ >= ver.raw_ints.len - 1 +} + +fn (raw_ver RawVersion) coerce() ?Version { + ver := raw_ver.complete() + if !is_valid_number(ver.raw_ints[semver.ver_major]) { + return error('Invalid major version: $ver.raw_ints[ver_major]') + } + return ver.to_version() +} + +fn (raw_ver RawVersion) complete() RawVersion { + mut raw_ints := raw_ver.raw_ints + for raw_ints.len < 3 { + raw_ints << '0' + } + return RawVersion{ + prerelease: raw_ver.prerelease + metadata: raw_ver.metadata + raw_ints: raw_ints + } +} + +fn (raw_ver RawVersion) validate() ?Version { + if !raw_ver.is_valid() { + return none + } + return raw_ver.to_version() +} + +fn (raw_ver RawVersion) to_version() Version { + return Version{raw_ver.raw_ints[semver.ver_major].int(), raw_ver.raw_ints[semver.ver_minor].int(), raw_ver.raw_ints[semver.ver_patch].int(), raw_ver.prerelease, raw_ver.metadata} +} diff --git a/v_windows/v/vlib/semver/range.v b/v_windows/v/vlib/semver/range.v new file mode 100644 index 0000000..fd0a769 --- /dev/null +++ b/v_windows/v/vlib/semver/range.v @@ -0,0 +1,252 @@ +module semver + +// * Private functions. +const ( + comparator_sep = ' ' + comparator_set_sep = ' || ' + hyphen_range_sep = ' - ' + x_range_symbols = 'Xx*' +) + +enum Operator { + gt + lt + ge + le + eq +} + +struct Comparator { + ver Version + op Operator +} + +struct ComparatorSet { + comparators []Comparator +} + +struct Range { + comparator_sets []ComparatorSet +} + +struct InvalidComparatorCountError { + msg string + code int +} + +struct InvalidComparatorFormatError { + msg string + code int +} + +fn (r Range) satisfies(ver Version) bool { + mut final_result := false + for set in r.comparator_sets { + final_result = final_result || set.satisfies(ver) + } + return final_result +} + +fn (set ComparatorSet) satisfies(ver Version) bool { + for comp in set.comparators { + if !comp.satisfies(ver) { + return false + } + } + return true +} + +fn (c Comparator) satisfies(ver Version) bool { + if c.op == .gt { + return ver.gt(c.ver) + } + if c.op == .lt { + return ver.lt(c.ver) + } + if c.op == .ge { + return ver.ge(c.ver) + } + if c.op == .le { + return ver.le(c.ver) + } + if c.op == .eq { + return ver.eq(c.ver) + } + return false +} + +fn parse_range(input string) ?Range { + raw_comparator_sets := input.split(semver.comparator_set_sep) + mut comparator_sets := []ComparatorSet{} + for raw_comp_set in raw_comparator_sets { + if can_expand(raw_comp_set) { + s := expand_comparator_set(raw_comp_set) or { return err } + comparator_sets << s + } else { + s := parse_comparator_set(raw_comp_set) or { return err } + comparator_sets << s + } + } + return Range{comparator_sets} +} + +fn parse_comparator_set(input string) ?ComparatorSet { + raw_comparators := input.split(semver.comparator_sep) + if raw_comparators.len > 2 { + return IError(&InvalidComparatorFormatError{ + msg: 'Invalid format of comparator set for input "$input"' + }) + } + mut comparators := []Comparator{} + for raw_comp in raw_comparators { + c := parse_comparator(raw_comp) or { + return IError(&InvalidComparatorFormatError{ + msg: 'Invalid comparator "$raw_comp" in input "$input"' + }) + } + comparators << c + } + return ComparatorSet{comparators} +} + +fn parse_comparator(input string) ?Comparator { + mut op := Operator.eq + mut raw_version := '' + if input.starts_with('>=') { + op = .ge + raw_version = input[2..] + } else if input.starts_with('<=') { + op = .le + raw_version = input[2..] + } else if input.starts_with('>') { + op = .gt + raw_version = input[1..] + } else if input.starts_with('<') { + op = .lt + raw_version = input[1..] + } else if input.starts_with('=') { + raw_version = input[1..] + } else { + raw_version = input + } + version := coerce_version(raw_version) or { return none } + return Comparator{version, op} +} + +fn parse_xrange(input string) ?Version { + mut raw_ver := parse(input).complete() + for typ in versions { + if raw_ver.raw_ints[typ].index_any(semver.x_range_symbols) == -1 { + continue + } + match typ { + ver_major { + raw_ver.raw_ints[ver_major] = '0' + raw_ver.raw_ints[ver_minor] = '0' + raw_ver.raw_ints[ver_patch] = '0' + } + ver_minor { + raw_ver.raw_ints[ver_minor] = '0' + raw_ver.raw_ints[ver_patch] = '0' + } + ver_patch { + raw_ver.raw_ints[ver_patch] = '0' + } + else {} + } + } + if !raw_ver.is_valid() { + return none + } + return raw_ver.to_version() +} + +fn can_expand(input string) bool { + return input[0] == `~` || input[0] == `^` || input.contains(semver.hyphen_range_sep) + || input.index_any(semver.x_range_symbols) > -1 +} + +fn expand_comparator_set(input string) ?ComparatorSet { + match input[0] { + `~` { return expand_tilda(input[1..]) } + `^` { return expand_caret(input[1..]) } + else {} + } + if input.contains(semver.hyphen_range_sep) { + return expand_hyphen(input) + } + return expand_xrange(input) +} + +fn expand_tilda(raw_version string) ?ComparatorSet { + min_ver := coerce_version(raw_version) or { return none } + mut max_ver := min_ver + if min_ver.minor == 0 && min_ver.patch == 0 { + max_ver = min_ver.increment(.major) + } else { + max_ver = min_ver.increment(.minor) + } + return make_comparator_set_ge_lt(min_ver, max_ver) +} + +fn expand_caret(raw_version string) ?ComparatorSet { + min_ver := coerce_version(raw_version) or { return none } + mut max_ver := min_ver + if min_ver.major == 0 { + max_ver = min_ver.increment(.minor) + } else { + max_ver = min_ver.increment(.major) + } + return make_comparator_set_ge_lt(min_ver, max_ver) +} + +fn expand_hyphen(raw_range string) ?ComparatorSet { + raw_versions := raw_range.split(semver.hyphen_range_sep) + if raw_versions.len != 2 { + return none + } + min_ver := coerce_version(raw_versions[0]) or { return none } + raw_max_ver := parse(raw_versions[1]) + if raw_max_ver.is_missing(ver_major) { + return none + } + mut max_ver := raw_max_ver.coerce() or { return none } + if raw_max_ver.is_missing(ver_minor) { + max_ver = max_ver.increment(.minor) + return make_comparator_set_ge_lt(min_ver, max_ver) + } + return make_comparator_set_ge_le(min_ver, max_ver) +} + +fn expand_xrange(raw_range string) ?ComparatorSet { + min_ver := parse_xrange(raw_range) or { return none } + if min_ver.major == 0 { + comparators := [ + Comparator{min_ver, Operator.ge}, + ] + return ComparatorSet{comparators} + } + mut max_ver := min_ver + if min_ver.minor == 0 { + max_ver = min_ver.increment(.major) + } else { + max_ver = min_ver.increment(.minor) + } + return make_comparator_set_ge_lt(min_ver, max_ver) +} + +fn make_comparator_set_ge_lt(min Version, max Version) ComparatorSet { + comparators := [ + Comparator{min, Operator.ge}, + Comparator{max, Operator.lt}, + ] + return ComparatorSet{comparators} +} + +fn make_comparator_set_ge_le(min Version, max Version) ComparatorSet { + comparators := [ + Comparator{min, Operator.ge}, + Comparator{max, Operator.le}, + ] + return ComparatorSet{comparators} +} diff --git a/v_windows/v/vlib/semver/semver.v b/v_windows/v/vlib/semver/semver.v new file mode 100644 index 0000000..c35bbfb --- /dev/null +++ b/v_windows/v/vlib/semver/semver.v @@ -0,0 +1,110 @@ +// * Documentation: https://docs.npmjs.com/misc/semver +module semver + +// * Structures. +// `Version` represents a semantic version in semver format. +pub struct Version { +pub: + major int + minor int + patch int + prerelease string + metadata string +} + +// Increment represents the different types of version increments. +pub enum Increment { + major + minor + patch +} + +struct EmptyInputError { + msg string = 'Empty input' + code int +} + +struct InvalidVersionFormatError { + msg string + code int +} + +// * Constructor. +// from returns a `Version` structure parsed from `input` `string`. +pub fn from(input string) ?Version { + if input.len == 0 { + return IError(&EmptyInputError{}) + } + raw_version := parse(input) + version := raw_version.validate() or { + return IError(&InvalidVersionFormatError{ + msg: 'Invalid version format for input "$input"' + }) + } + return version +} + +// build returns a `Version` structure with given `major`, `minor` and `patch` versions. +pub fn build(major int, minor int, patch int) Version { + // TODO Check if versions are greater than zero. + return Version{major, minor, patch, '', ''} +} + +// * Transformation. +// increment returns a `Version` structure with incremented values. +pub fn (ver Version) increment(typ Increment) Version { + return increment_version(ver, typ) +} + +// * Comparison. +// satisfies returns `true` if the `input` expression can be validated to `true` +// when run against this `Version`. +// Example: assert semver.build(1,0,0).satisfies('<=2.0.0') == true +// Example: assert semver.build(1,0,0).satisfies('>=2.0.0') == false +pub fn (ver Version) satisfies(input string) bool { + return version_satisfies(ver, input) +} + +// eq returns `true` if `v1` is equal to `v2`. +pub fn (v1 Version) eq(v2 Version) bool { + return compare_eq(v1, v2) +} + +// gt returns `true` if `v1` is greater than `v2`. +pub fn (v1 Version) gt(v2 Version) bool { + return compare_gt(v1, v2) +} + +// lt returns `true` if `v1` is less than `v2`. +pub fn (v1 Version) lt(v2 Version) bool { + return compare_lt(v1, v2) +} + +// ge returns `true` if `v1` is greater than or equal to `v2`. +pub fn (v1 Version) ge(v2 Version) bool { + return compare_ge(v1, v2) +} + +// le returns `true` if `v1` is less than or equal to `v2`. +pub fn (v1 Version) le(v2 Version) bool { + return compare_le(v1, v2) +} + +// * Utilites. +// coerce converts the `input` version to a `Version` struct. +// coerce will strip any contents *after* the parsed version string: +/* +Example: +import semver +v := semver.coerce('1.3-RC1-b2') or { semver.Version{} } +assert v.satisfies('>1.0 <2.0') == true // 1.3.0 +*/ +pub fn coerce(input string) ?Version { + return coerce_version(input) +} + +// is_valid returns `true` if the `input` `string` can be converted to +// a (semantic) `Version` struct. +pub fn is_valid(input string) bool { + return is_version_valid(input) +} diff --git a/v_windows/v/vlib/semver/semver_test.v b/v_windows/v/vlib/semver/semver_test.v new file mode 100644 index 0000000..77c8cd7 --- /dev/null +++ b/v_windows/v/vlib/semver/semver_test.v @@ -0,0 +1,192 @@ +import semver + +struct TestVersion { + raw string + major int + minor int + patch int + prerelease string + metadata string +} + +struct TestRange { + raw_version string + range_satisfied string + range_unsatisfied string +} + +struct TestCoerce { + invalid string + valid string +} + +const ( + versions_to_test = [ + TestVersion{'1.2.4', 1, 2, 4, '', ''}, + TestVersion{'1.2.4-prerelease-1', 1, 2, 4, 'prerelease-1', ''}, + TestVersion{'1.2.4+20191231', 1, 2, 4, '', '20191231'}, + TestVersion{'1.2.4-prerelease-1+20191231', 1, 2, 4, 'prerelease-1', '20191231'}, + TestVersion{'1.2.4+20191231-prerelease-1', 1, 2, 4, '', '20191231-prerelease-1'}, + ] + ranges_to_test = [ + TestRange{'1.1.0', '1.1.0', '1.1.1'}, + TestRange{'1.1.0', '=1.1.0', '=1.1.1'}, + TestRange{'1.1.0', '>=1.0.0', '<1.1.0'}, + TestRange{'1.1.0', '>=1.0.0 <=1.1.0', '>=1.0.0 <1.1.0'}, + TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0'}, + TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0 || >4.0.0 <5.0.0'}, + TestRange{'2.3.1', '~2.3.0', '~2.4.0'}, + TestRange{'3.0.0', '~3.0.0', '~4.0.0'}, + TestRange{'2.3.1', '^2.0.0', '^2.4.0'}, + TestRange{'0.3.1', '^0.3.0', '^2.4.0'}, + TestRange{'0.0.4', '^0.0.1', '^0.1.0'}, + TestRange{'2.3.4', '^0.0.1 || ^2.3.0', '^3.1.0 || ^4.2.0'}, + TestRange{'2.3.4', '>2 || <3', '>3 || >4'}, + TestRange{'2.3.4', '2.3.4 - 2.3.5', '2.5.1 - 2.8.3'}, + TestRange{'2.3.4', '2.2 - 2.3', '2.4 - 2.8'}, + TestRange{'2.3.4', '2.3.x', '2.4.x'}, + TestRange{'2.3.4', '2.x', '3.x'}, + TestRange{'2.3.4', '*', '3.x'}, + ] + coerce_to_test = [ + TestCoerce{'1.2.0.4', '1.2.0'}, + TestCoerce{'1.2.0', '1.2.0'}, + TestCoerce{'1.2', '1.2.0'}, + TestCoerce{'1', '1.0.0'}, + TestCoerce{'1-alpha', '1.0.0-alpha'}, + TestCoerce{'1+meta', '1.0.0+meta'}, + TestCoerce{'1-alpha+meta', '1.0.0-alpha+meta'}, + ] + invalid_versions_to_test = [ + 'a.b.c', + '1.2', + '1.2.x', + '1.2.3.4', + '1.2.3-alpha@', + '1.2.3+meta%', + ] + invalid_ranges_to_test = [ + '^a', + '~b', + 'a - c', + '>a', + 'a', + 'a.x', + ] +) + +fn test_from() { + for item in versions_to_test { + ver := semver.from(item.raw) or { + assert false + return + } + assert ver.major == item.major + assert ver.minor == item.minor + assert ver.patch == item.patch + assert ver.metadata == item.metadata + assert ver.prerelease == item.prerelease + } + for ver in invalid_versions_to_test { + semver.from(ver) or { + assert true + continue + } + assert false + } +} + +fn test_increment() { + version1 := semver.build(1, 2, 3) + version1_inc := version1.increment(.major) + assert version1_inc.major == 2 + assert version1_inc.minor == 0 + assert version1_inc.patch == 0 + version2_inc := version1.increment(.minor) + assert version2_inc.major == 1 + assert version2_inc.minor == 3 + assert version2_inc.patch == 0 + version3_inc := version1.increment(.patch) + assert version3_inc.major == 1 + assert version3_inc.minor == 2 + assert version3_inc.patch == 4 +} + +fn test_compare() { + first := semver.build(1, 0, 0) + patch := semver.build(1, 0, 1) + minor := semver.build(1, 2, 3) + major := semver.build(2, 0, 0) + assert first.le(first) + assert first.ge(first) + assert !first.lt(first) + assert !first.gt(first) + assert patch.ge(first) + assert first.le(patch) + assert !first.ge(patch) + assert !patch.le(first) + assert patch.gt(first) + assert first.lt(patch) + assert !first.gt(patch) + assert !patch.lt(first) + assert minor.gt(patch) + assert patch.lt(minor) + assert !patch.gt(minor) + assert !minor.lt(patch) + assert major.gt(minor) + assert minor.lt(major) + assert !minor.gt(major) + assert !major.lt(minor) +} + +fn test_satisfies() { + for item in ranges_to_test { + ver := semver.from(item.raw_version) or { + assert false + return + } + assert ver.satisfies(item.range_satisfied) + assert !ver.satisfies(item.range_unsatisfied) + } +} + +fn test_satisfies_invalid() { + ver := semver.from('1.0.0') or { + assert false + return + } + for item in invalid_ranges_to_test { + assert ver.satisfies(item) == false + } +} + +fn test_coerce() { + for item in coerce_to_test { + valid := semver.from(item.valid) or { + assert false + return + } + fixed := semver.coerce(item.invalid) or { + assert false + return + } + assert fixed.eq(valid) + } +} + +fn test_coerce_invalid() { + semver.coerce('a') or { + assert true + return + } + assert false +} + +fn test_is_valid() { + for item in versions_to_test { + assert semver.is_valid(item.raw) + } + for item in invalid_versions_to_test { + assert semver.is_valid(item) == false + } +} diff --git a/v_windows/v/vlib/semver/util.v b/v_windows/v/vlib/semver/util.v new file mode 100644 index 0000000..142ce19 --- /dev/null +++ b/v_windows/v/vlib/semver/util.v @@ -0,0 +1,55 @@ +module semver + +// * Private functions. +[inline] +fn is_version_valid(input string) bool { + raw_ver := parse(input) + return raw_ver.is_valid() +} + +[inline] +fn coerce_version(input string) ?Version { + raw_ver := parse(input) + ver := raw_ver.coerce() or { return error('Invalid version for input "$input"') } + return ver +} + +[inline] +fn increment_version(ver Version, typ Increment) Version { + mut major := ver.major + mut minor := ver.minor + mut patch := ver.patch + match typ { + .major { + major++ + minor = 0 + patch = 0 + } + .minor { + minor++ + patch = 0 + } + .patch { + patch++ + } + } + return Version{major, minor, patch, ver.prerelease, ver.metadata} +} + +fn is_valid_string(input string) bool { + for c in input { + if !(c.is_letter() || c.is_digit() || c == `.` || c == `-`) { + return false + } + } + return true +} + +fn is_valid_number(input string) bool { + for c in input { + if !c.is_digit() { + return false + } + } + return true +} diff --git a/v_windows/v/vlib/semver/v.mod b/v_windows/v/vlib/semver/v.mod new file mode 100644 index 0000000..dd7671c --- /dev/null +++ b/v_windows/v/vlib/semver/v.mod @@ -0,0 +1,5 @@ +Module { + name: 'semver' + version: '0.3.0' + deps: [] +} diff --git a/v_windows/v/vlib/sokol/audio/audio.v b/v_windows/v/vlib/sokol/audio/audio.v new file mode 100644 index 0000000..50acb7f --- /dev/null +++ b/v_windows/v/vlib/sokol/audio/audio.v @@ -0,0 +1,134 @@ +module audio + +$if linux { + // provide a nicer error for the user that does not have ALSA installed + #include # Please install the `libasound2-dev` package +} + +#flag -I @VEXEROOT/thirdparty/sokol +#define SOKOL_IMPL +#include "sokol_audio.h" +#flag linux -lasound +#flag darwin -framework AudioToolbox +#flag windows -lole32 + +// +pub type FNStreamingCB = fn (buffer &f32, num_frames int, num_channels int) + +pub type FnStreamingCBWithUserData = fn (buffer &f32, num_frames int, num_channels int, user_data voidptr) + +pub fn (x FNStreamingCB) str() string { + return '&FNStreamingCB{ ${ptr_str(x)} }' +} + +pub fn (x FnStreamingCBWithUserData) str() string { + return '&FnStreamingCBWithUserData{ ${ptr_str(x)} }' +} + +// +pub struct C.saudio_desc { + sample_rate int + num_channels int + buffer_frames int + packet_frames int + num_packets int + stream_cb FNStreamingCB + stream_userdata_cb FnStreamingCBWithUserData + user_data voidptr +} + +fn C.saudio_setup(desc &C.saudio_desc) + +fn C.saudio_shutdown() + +fn C.saudio_isvalid() bool + +fn C.saudio_userdata() voidptr + +fn C.saudio_query_desc() C.saudio_desc + +fn C.saudio_sample_rate() int + +fn C.saudio_buffer_frames() int + +fn C.saudio_channels() int + +fn C.saudio_expect() int + +fn C.saudio_push(frames &f32, num_frames int) int + +// audio.setup - setup sokol-audio +pub fn setup(desc C.saudio_desc) { + C.saudio_setup(&desc) +} + +// audio.shutdown - shutdown sokol-audio +pub fn shutdown() { + C.saudio_shutdown() +} + +// audio.is_valid - true after setup if audio backend was successfully initialized +pub fn is_valid() bool { + return C.saudio_isvalid() +} + +// audio.userdata - return the saudio_desc.user_data pointer +pub fn user_data() voidptr { + return C.saudio_userdata() +} + +// audio.query - return a copy of the original saudio_desc struct +pub fn query() C.saudio_desc { + return C.saudio_query_desc() +} + +// audio.sample_rate - actual sample rate +pub fn sample_rate() int { + return C.saudio_sample_rate() +} + +// audio.buffer_frames - return actual backend buffer size in number of frames +pub fn buffer_frames() int { + return C.saudio_buffer_frames() +} + +// audio.channels - actual number of channels +pub fn channels() int { + return C.saudio_channels() +} + +// audio.expect - get current number of frames to fill packet queue; use in combination with audio.push/2 +pub fn expect() int { + return C.saudio_expect() +} + +// audio.push - push sample frames from main thread, returns number of frames actually pushed +pub fn push(frames &f32, num_frames int) int { + return C.saudio_push(frames, num_frames) +} + +// +[inline] +pub fn fclamp(x f32, flo f32, fhi f32) f32 { + if x > fhi { + return fhi + } + if x < flo { + return flo + } + return x +} + +pub fn min(x int, y int) int { + if x < y { + return x + } + return y +} + +pub fn max(x int, y int) int { + if x < y { + return y + } + return x +} diff --git a/v_windows/v/vlib/sokol/c/declaration.c.v b/v_windows/v/vlib/sokol/c/declaration.c.v new file mode 100644 index 0000000..c2e435a --- /dev/null +++ b/v_windows/v/vlib/sokol/c/declaration.c.v @@ -0,0 +1,58 @@ +module c + +pub const ( + used_import = 1 +) + +#flag -I @VEXEROOT/thirdparty/sokol +#flag -I @VEXEROOT/thirdparty/sokol/util +#flag freebsd -I /usr/local/include +#flag darwin -fobjc-arc +#flag linux -lX11 -lGL -lXcursor -lXi -lpthread +#flag freebsd -L/usr/local/lib -lX11 -lGL -lXcursor -lXi +#flag windows -lgdi32 +// METAL +$if macos { + #flag -DSOKOL_METAL + #flag -framework Metal -framework Cocoa -framework MetalKit -framework QuartzCore +} +$if ios { + #flag -DSOKOL_METAL + #flag -framework Foundation -framework Metal -framework MetalKit -framework UIKit +} +// OPENGL +#flag linux -DSOKOL_GLCORE33 +#flag freebsd -DSOKOL_GLCORE33 +//#flag darwin -framework OpenGL -framework Cocoa -framework QuartzCore +// D3D +#flag windows -DSOKOL_GLCORE33 +//#flag windows -DSOKOL_D3D11 +// for simplicity, all header includes are here because import order matters and we dont have any way +// to ensure import order with V yet +#define SOKOL_IMPL +// TODO should not be defined for android graphic (apk/aab using sokol) builds, but we have no ways to undefine +//#define SOKOL_NO_ENTRY +#flag linux -DSOKOL_NO_ENTRY +#flag darwin -DSOKOL_NO_ENTRY +#flag windows -DSOKOL_NO_ENTRY +#flag windows -DSOKOL_WIN32_FORCE_MAIN +#flag freebsd -DSOKOL_NO_ENTRY +#flag solaris -DSOKOL_NO_ENTRY +// TODO end + +#flag linux -ldl + +$if gcboehm ? { + #define SOKOL_MALLOC GC_MALLOC + #define SOKOL_CALLOC(n,m) GC_MALLOC((n)*(m)) + #define SOKOL_REALLOC GC_REALLOC + #define SOKOL_FREE GC_FREE +} + +#include "sokol_v.h" +#include "sokol_app.h" +#define SOKOL_IMPL +#define SOKOL_NO_DEPRECATED +#include "sokol_gfx.h" +#define SOKOL_GL_IMPL +#include "util/sokol_gl.h" diff --git a/v_windows/v/vlib/sokol/f/f.v b/v_windows/v/vlib/sokol/f/f.v new file mode 100644 index 0000000..5c5e714 --- /dev/null +++ b/v_windows/v/vlib/sokol/f/f.v @@ -0,0 +1,15 @@ +module f + +import fontstash +import sokol.c + +pub const ( + used_import = fontstash.used_import + c.used_import +) + +#flag linux -I. + +//#include "ft2build.h" + +#define SOKOL_FONTSTASH_IMPL +#include "util/sokol_fontstash.h" diff --git a/v_windows/v/vlib/sokol/gfx/enums.v b/v_windows/v/vlib/sokol/gfx/enums.v new file mode 100644 index 0000000..fbe336e --- /dev/null +++ b/v_windows/v/vlib/sokol/gfx/enums.v @@ -0,0 +1,297 @@ +module gfx + +pub enum Backend { + glcore33 + gles2 + gles3 + d3d11 + metal_ios + metal_macos + metal_simulator + dummy +} + +pub enum PixelFormat { + _default // value 0 reserved for default-init + @none + r8 + r8sn + r8ui + r8si + r16 + r16sn + r16ui + r16si + r16f + rg8 + rg8sn + rg8ui + rg8si + r32ui + r32si + r32f + rg16 + rg16sn + rg16ui + rg16si + rg16f + rgba8 + rgba8sn + rgba8ui + rgba8si + bgra8 + rgb10a2 + rg11b10f + rg32ui + rg32si + rg32f + rgba16 + rgba16sn + rgba16ui + rgba16si + rgba16f + rgba32ui + rgba32si + rgba32f + depth + depth_stencil + bc1_rgba + bc2_rgba + bc3_rgba + bc4_r + bc4_rsn + bc5_rg + bc5_rgsn + bc6h_rgbf + bc6h_rgbuf + bc7_rgba + pvrtc_rgb_2bpp + pvrtc_rgb_4bpp + pvrtc_rgba_2bpp + pvrtc_rgba_4bpp + etc2_rgb8 + etc2_rgb8a1 + etc2_rgba8 + etc2_rg11 + etc2_rg11sn + _num +} + +pub enum ResourceState { + initial + alloc + valid + failed + invalid +} + +pub enum Usage { + _default // value 0 reserved for default-init + immutable + dynamic + stream + _num +} + +pub enum BufferType { + _default // value 0 reserved for default-init + vertexbuffer + indexbuffer + _num +} + +pub enum IndexType { + _default // value 0 reserved for default-init + @none + uint16 + uint32 + _num +} + +pub enum ImageType { + _default // value 0 reserved for default-init + _2d + cube + _3d + array + _num +} + +pub enum CubeFace { + pos_x + neg_x + pos_y + neg_y + pos_z + neg_z + num + _force_u32 = 0x7fffffff +} + +pub enum ShaderStage { + vs + fs +} + +pub enum PrimitiveType { + _default // value 0 reserved for default-init + points + lines + line_strip + triangles + triangle_strip + _num +} + +pub enum Filter { + _default // value 0 reserved for default-init + nearest + linear + nearest_mipmap_nearest + nearest_mipmap_linear + linear_mipmap_nearest + linear_mipmap_linear + _num +} + +pub enum Wrap { + _default // value 0 reserved for default-init + repeat + clamp_to_edge + clamp_to_border + mirrored_repeat + _num +} + +pub enum BorderColor { + _default // value 0 reserved for default-init + transparent_black + opaque_black + opaque_white + _num +} + +pub enum VertexFormat { + invalid + float + float2 + float3 + float4 + byte4 + byte4n + ubyte4 + ubyte4n + short2 + short2n + ushort2n + short4 + short4n + ushort4n + uint10_n2 + _num +} + +pub enum VertexStep { + _default // value 0 reserved for default-init + per_vertex + per_instance + _num +} + +pub enum UniformType { + invalid + float + float2 + float3 + float4 + mat4 + _num +} + +pub enum CullMode { + _default // value 0 reserved for default-init + @none + front + back + _num +} + +pub enum FaceWinding { + _facewinding_default // value 0 reserved for default-init + facewinding_ccw + facewinding_cw + _facewinding_num +} + +pub enum CompareFunc { + _default // value 0 reserved for default-init + never + less + equal + less_equal + greater + not_equal + greater_equal + always + _num +} + +pub enum StencilOp { + _default // value 0 reserved for default-init + keep + zero + replace + incr_clamp + decr_clamp + invert + incr_wrap + decr_wrap + _num +} + +pub enum BlendFactor { + _default // value 0 reserved for default-init + zero + one + src_color + one_minus_src_color + src_alpha + one_minus_src_alpha + dst_color + one_minus_dst_color + dst_alpha + one_minus_dst_alpha + src_alpha_saturated + blend_color + one_minus_blend_color + blend_alpha + one_minus_blend_alpha + _num +} + +pub enum BlendOp { + _default // value 0 reserved for default-init + add + subtract + reverse_subtract + _num +} + +pub enum ColorMask { + _default = 0 // value 0 reserved for default-init + @none = 0x10 // special value for 'all channels disabled + r = 1 + g = 2 + b = 4 + a = 8 + rgb = 0x7 + rgba = 0xF +} + +pub enum Action { + _default + clear + load + dontcare + _num +} diff --git a/v_windows/v/vlib/sokol/gfx/gfx.c.v b/v_windows/v/vlib/sokol/gfx/gfx.c.v new file mode 100644 index 0000000..9480173 --- /dev/null +++ b/v_windows/v/vlib/sokol/gfx/gfx.c.v @@ -0,0 +1,266 @@ +module gfx + +import sokol.c + +pub const ( + version = 1 + used_import = c.used_import +) + +// setup and misc functions +[inline] +pub fn setup(desc &C.sg_desc) { + C.sg_setup(desc) +} + +[inline] +pub fn shutdown() { + C.sg_shutdown() +} + +[inline] +pub fn reset_state_cache() { + C.sg_reset_state_cache() +} + +// resource creation, destruction and updating +[inline] +pub fn make_buffer(desc &C.sg_buffer_desc) C.sg_buffer { + return C.sg_make_buffer(desc) +} + +[inline] +pub fn make_image(desc &C.sg_image_desc) C.sg_image { + return C.sg_make_image(desc) +} + +[inline] +pub fn make_shader(desc &C.sg_shader_desc) C.sg_shader { + return C.sg_make_shader(desc) +} + +[inline] +pub fn make_pipeline(desc &C.sg_pipeline_desc) C.sg_pipeline { + return C.sg_make_pipeline(desc) +} + +[inline] +pub fn make_pass(desc &C.sg_pass_desc) C.sg_pass { + return C.sg_make_pass(desc) +} + +[inline] +pub fn destroy_buffer(buf C.sg_buffer) { + C.sg_destroy_buffer(buf) +} + +[inline] +pub fn destroy_image(img C.sg_image) { + C.sg_destroy_image(img) +} + +[inline] +pub fn destroy_shader(shd C.sg_shader) { + C.sg_destroy_shader(shd) +} + +[inline] +pub fn destroy_pipeline(pip C.sg_pipeline) { + C.sg_destroy_pipeline(pip) +} + +[inline] +pub fn destroy_pass(pass C.sg_pass) { + C.sg_destroy_pass(pass) +} + +[inline] +pub fn update_buffer(buf C.sg_buffer, data &C.sg_range) { + C.sg_update_buffer(buf, data) +} + +[inline] +pub fn update_image(img C.sg_image, data &C.sg_image_data) { + C.sg_update_image(img, data) +} + +[inline] +pub fn append_buffer(buf C.sg_buffer, data &C.sg_range) int { + return C.sg_append_buffer(buf, data) +} + +[inline] +pub fn query_buffer_overflow(buf C.sg_buffer) bool { + return C.sg_query_buffer_overflow(buf) +} + +// rendering functions +[inline] +pub fn begin_default_pass(actions &C.sg_pass_action, width int, height int) { + C.sg_begin_default_pass(actions, width, height) +} + +[inline] +pub fn begin_pass(pass C.sg_pass, actions &C.sg_pass_action) { + C.sg_begin_pass(pass, actions) +} + +[inline] +pub fn apply_viewport(x int, y int, width int, height int, origin_top_left bool) { + C.sg_apply_viewport(x, y, width, height, origin_top_left) +} + +[inline] +pub fn apply_scissor_rect(x int, y int, width int, height int, origin_top_left bool) { + C.sg_apply_scissor_rect(x, y, width, height, origin_top_left) +} + +[inline] +pub fn apply_pipeline(pip C.sg_pipeline) { + C.sg_apply_pipeline(pip) +} + +[inline] +pub fn apply_bindings(bindings &C.sg_bindings) { + C.sg_apply_bindings(bindings) +} + +[inline] +pub fn apply_uniforms(stage int, ub_index int, data &C.sg_range) { + C.sg_apply_uniforms(stage, ub_index, data) +} + +[inline] +pub fn draw(base_element int, num_elements int, num_instances int) { + C.sg_draw(base_element, num_elements, num_instances) +} + +[inline] +pub fn end_pass() { + C.sg_end_pass() +} + +[inline] +pub fn commit() { + C.sg_commit() +} + +// getting information +[inline] +pub fn query_desc() C.sg_desc { + return C.sg_query_desc() +} + +[inline] +pub fn query_backend() Backend { + return Backend(C.sg_query_backend()) +} + +[inline] +pub fn query_features() C.sg_features { + return C.sg_query_features() +} + +[inline] +pub fn query_limits() C.sg_limits { + return C.sg_query_limits() +} + +[inline] +pub fn query_pixelformat(fmt PixelFormat) C.sg_pixelformat_info { + return C.sg_query_pixelformat(fmt) +} + +// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) +[inline] +pub fn query_buffer_state(buf C.sg_buffer) C.sg_resource_state { + return C.sg_query_buffer_state(buf) +} + +[inline] +pub fn query_image_state(img C.sg_image) C.sg_resource_state { + return C.sg_query_image_state(img) +} + +[inline] +pub fn query_shader_state(shd C.sg_shader) C.sg_resource_state { + return C.sg_query_shader_state(shd) +} + +[inline] +pub fn query_pipeline_state(pip C.sg_pipeline) C.sg_resource_state { + return C.sg_query_pipeline_state(pip) +} + +[inline] +pub fn query_pass_state(pass C.sg_pass) C.sg_resource_state { + return C.sg_query_pass_state(pass) +} + +// get runtime information about a resource +[inline] +pub fn query_buffer_info(buf C.sg_buffer) C.sg_buffer_info { + return C.sg_query_buffer_info(buf) +} + +[inline] +pub fn query_image_info(img C.sg_image) C.sg_image_info { + return C.sg_query_image_info(img) +} + +[inline] +pub fn query_shader_info(shd C.sg_shader) C.sg_shader_info { + return C.sg_query_shader_info(shd) +} + +[inline] +pub fn query_pipeline_info(pip C.sg_pipeline) C.sg_pipeline_info { + return C.sg_query_pipeline_info(pip) +} + +[inline] +pub fn query_pass_info(pass C.sg_pass) C.sg_pass_info { + return C.sg_query_pass_info(pass) +} + +// get resource creation desc struct with their default values replaced +[inline] +pub fn query_buffer_defaults(desc &C.sg_buffer) C.sg_buffer_desc { + return C.sg_query_buffer_defaults(unsafe { &C.sg_buffer_desc(desc) }) +} + +[inline] +pub fn query_image_defaults(desc &C.sg_image) C.sg_image_desc { + return C.sg_query_image_defaults(unsafe { &C.sg_image_desc(desc) }) +} + +[inline] +pub fn query_shader_defaults(desc &C.sg_shader) C.sg_shader_desc { + return C.sg_query_shader_defaults(unsafe { &C.sg_shader_desc(desc) }) +} + +[inline] +pub fn query_pipeline_defaults(desc &C.sg_pipeline) C.sg_pipeline_desc { + return C.sg_query_pipeline_defaults(unsafe { &C.sg_pipeline_desc(desc) }) +} + +[inline] +pub fn query_pass_defaults(desc &C.sg_pass) C.sg_pass_desc { + return C.sg_query_pass_defaults(unsafe { &C.sg_pass_desc(desc) }) +} + +// rendering contexts (optional) +[inline] +pub fn setup_context() C.sg_context { + return C.sg_setup_context() +} + +[inline] +pub fn activate_context(ctx_id C.sg_context) { + C.sg_activate_context(ctx_id) +} + +[inline] +pub fn discard_context(ctx_id C.sg_context) { + C.sg_discard_context(ctx_id) +} diff --git a/v_windows/v/vlib/sokol/gfx/gfx_funcs.c.v b/v_windows/v/vlib/sokol/gfx/gfx_funcs.c.v new file mode 100644 index 0000000..eb4e99b --- /dev/null +++ b/v_windows/v/vlib/sokol/gfx/gfx_funcs.c.v @@ -0,0 +1,71 @@ +module gfx + +// setup and misc functions +fn C.sg_setup(desc &C.sg_desc) +fn C.sg_shutdown() +fn C.sg_reset_state_cache() + +// resource creation, destruction and updating +fn C.sg_make_buffer(desc &C.sg_buffer_desc) C.sg_buffer +fn C.sg_make_image(desc &C.sg_image_desc) C.sg_image +fn C.sg_make_shader(desc &C.sg_shader_desc) C.sg_shader +fn C.sg_make_pipeline(desc &C.sg_pipeline_desc) C.sg_pipeline +fn C.sg_make_pass(desc &C.sg_pass_desc) C.sg_pass +fn C.sg_destroy_buffer(buf C.sg_buffer) +fn C.sg_destroy_image(img C.sg_image) +fn C.sg_destroy_shader(shd C.sg_shader) +fn C.sg_destroy_pipeline(pip C.sg_pipeline) +fn C.sg_destroy_pass(pass C.sg_pass) +fn C.sg_update_buffer(buf C.sg_buffer, data &C.sg_range) +fn C.sg_update_image(img C.sg_image, data &C.sg_image_data) +fn C.sg_append_buffer(buf C.sg_buffer, data &C.sg_range) int +fn C.sg_query_buffer_overflow(buf C.sg_buffer) bool + +// rendering functions +fn C.sg_begin_default_pass(actions &C.sg_pass_action, width int, height int) +fn C.sg_begin_pass(pass C.sg_pass, actions &C.sg_pass_action) +fn C.sg_apply_viewport(x int, y int, width int, height int, origin_top_left bool) +fn C.sg_apply_viewportf(x f32, y f32, width f32, height f32, origin_top_left bool) +fn C.sg_apply_scissor_rect(x int, y int, width int, height int, origin_top_left bool) +fn C.sg_apply_scissor_rectf(x f32, y f32, width f32, height f32, origin_top_left bool) +fn C.sg_apply_pipeline(pip C.sg_pipeline) +fn C.sg_apply_bindings(bindings &C.sg_bindings) + +// stage == sg_shader_stage +fn C.sg_apply_uniforms(stage int, ub_index int, data &C.sg_range) +fn C.sg_draw(base_element int, num_elements int, num_instances int) +fn C.sg_end_pass() +fn C.sg_commit() + +// getting information +fn C.sg_query_desc() C.sg_desc +fn C.sg_query_backend() Backend +fn C.sg_query_features() C.sg_features +fn C.sg_query_limits() C.sg_limits +fn C.sg_query_pixelformat(fmt PixelFormat) C.sg_pixelformat_info + +// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) +fn C.sg_query_buffer_state(buf C.sg_buffer) C.sg_resource_state +fn C.sg_query_image_state(img C.sg_image) C.sg_resource_state +fn C.sg_query_shader_state(shd C.sg_shader) C.sg_resource_state +fn C.sg_query_pipeline_state(pip C.sg_pipeline) C.sg_resource_state +fn C.sg_query_pass_state(pass C.sg_pass) C.sg_resource_state + +// get runtime information about a resource +fn C.sg_query_buffer_info(buf C.sg_buffer) C.sg_buffer_info +fn C.sg_query_image_info(img C.sg_image) C.sg_image_info +fn C.sg_query_shader_info(shd C.sg_shader) C.sg_shader_info +fn C.sg_query_pipeline_info(pip C.sg_pipeline) C.sg_pipeline_info +fn C.sg_query_pass_info(pass C.sg_pass) C.sg_pass_info + +// get resource creation desc struct with their default values replaced +fn C.sg_query_buffer_defaults(desc &C.sg_buffer_desc) C.sg_buffer_desc +fn C.sg_query_image_defaults(desc &C.sg_image_desc) C.sg_image_desc +fn C.sg_query_shader_defaults(desc &C.sg_shader_desc) C.sg_shader_desc +fn C.sg_query_pipeline_defaults(desc &C.sg_pipeline_desc) C.sg_pipeline_desc +fn C.sg_query_pass_defaults(desc &C.sg_pass_desc) C.sg_pass_desc + +// rendering contexts (optional) +fn C.sg_setup_context() C.sg_context +fn C.sg_activate_context(ctx_id C.sg_context) +fn C.sg_discard_context(ctx_id C.sg_context) diff --git a/v_windows/v/vlib/sokol/gfx/gfx_structs.c.v b/v_windows/v/vlib/sokol/gfx/gfx_structs.c.v new file mode 100644 index 0000000..1bf6c3a --- /dev/null +++ b/v_windows/v/vlib/sokol/gfx/gfx_structs.c.v @@ -0,0 +1,535 @@ +module gfx + +pub struct C.sg_desc { + _start_canary u32 + buffer_pool_size int + image_pool_size int + shader_pool_size int + pipeline_pool_size int + pass_pool_size int + context_pool_size int + context C.sg_context_desc + /* + // GL specific + gl_force_gles2 bool + // Metal-specific + mtl_device voidptr + mtl_renderpass_descriptor_cb fn() voidptr + mtl_drawable_cb fn() voidptr + mtl_global_uniform_buffer_size int + mtl_sampler_cache_size int + // D3D11-specific + d3d11_device voidptr + d3d11_device_context voidptr + d3d11_render_target_view_cb fn() voidptr + d3d11_depth_stencil_view_cb fn() voidptr + */ + _end_canary u32 +} + +pub struct C.sg_context_desc { + /* + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_wgpu_context_desc wgpu; + */ + sample_count int + gl C.sg_gl_context_desc + metal C.sg_metal_context_desc + d3d11 C.sg_d3d11_context_desc + color_format PixelFormat + depth_format PixelFormat +} + +pub struct C.sg_gl_context_desc { + force_gles2 bool +} + +pub struct C.sg_metal_context_desc { + device voidptr + renderpass_descriptor_cb fn () voidptr + drawable_cb fn () voidptr +} + +pub struct C.sg_d3d11_context_desc { + device voidptr + device_context voidptr + render_target_view_cb fn () voidptr + depth_stencil_view_cb fn () voidptr +} + +pub struct C.sg_color_state { +pub mut: + pixel_format PixelFormat + write_mask ColorMask + blend C.sg_blend_state +} + +pub struct C.sg_pipeline_desc { +pub mut: + _start_canary u32 + shader C.sg_shader + layout C.sg_layout_desc + depth C.sg_depth_state + stencil C.sg_stencil_state + color_count int + colors [4]C.sg_color_state // C.SG_MAX_COLOR_ATTACHMENTS + primitive_type PrimitiveType + index_type IndexType + cull_mode CullMode + face_winding FaceWinding + sample_count int + blend_color C.sg_color + alpha_to_coverage_enabled bool + label &char = &char(0) + _end_canary u32 +} + +pub struct C.sg_pipeline_info { +} + +pub struct C.sg_pipeline { +pub: + id u32 +} + +pub fn (p C.sg_pipeline) free() { + C.sg_destroy_pipeline(p) +} + +pub struct C.sg_bindings { +pub mut: + _start_canary u32 + vertex_buffers [8]C.sg_buffer + vertex_buffer_offsets [8]int + index_buffer C.sg_buffer + index_buffer_offset int + vs_images [8]C.sg_image + fs_images [8]C.sg_image + _end_canary u32 +} + +pub fn (mut b C.sg_bindings) set_vert_image(index int, img C.sg_image) { + b.vs_images[index] = img +} + +pub fn (mut b C.sg_bindings) set_frag_image(index int, img C.sg_image) { + b.fs_images[index] = img +} + +pub fn (b &C.sg_bindings) update_vert_buffer(index int, data voidptr, element_size int, element_count int) { + range := C.sg_range{ + ptr: data + size: size_t(element_size * element_count) + } + C.sg_update_buffer(b.vertex_buffers[index], &range) +} + +pub fn (b &C.sg_bindings) append_vert_buffer(index int, data voidptr, element_size int, element_count int) int { + range := C.sg_range{ + ptr: data + size: size_t(element_size * element_count) + } + return C.sg_append_buffer(b.vertex_buffers[index], &range) +} + +pub fn (b &C.sg_bindings) update_index_buffer(data voidptr, element_size int, element_count int) { + range := C.sg_range{ + ptr: data + size: size_t(element_size * element_count) + } + C.sg_update_buffer(b.index_buffer, &range) +} + +pub fn (b &C.sg_bindings) append_index_buffer(data voidptr, element_size int, element_count int) int { + range := C.sg_range{ + ptr: data + size: size_t(element_size * element_count) + } + return C.sg_append_buffer(b.index_buffer, &range) +} + +[heap] +pub struct C.sg_shader_desc { +pub mut: + _start_canary u32 + attrs [16]C.sg_shader_attr_desc + vs C.sg_shader_stage_desc + fs C.sg_shader_stage_desc + label &char + _end_canary u32 +} + +pub fn (mut desc C.sg_shader_desc) set_vert_src(src string) &C.sg_shader_desc { + desc.vs.source = &char(src.str) + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_frag_src(src string) &C.sg_shader_desc { + desc.fs.source = &char(src.str) + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_vert_image(index int, name string) &C.sg_shader_desc { + desc.vs.images[index].name = &char(name.str) + desc.vs.images[index].image_type = ._2d + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_frag_image(index int, name string) &C.sg_shader_desc { + desc.fs.images[index].name = &char(name.str) + desc.fs.images[index].image_type = ._2d + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_vert_uniform_block_size(block_index int, size size_t) &C.sg_shader_desc { + desc.vs.uniform_blocks[block_index].size = size + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_frag_uniform_block_size(block_index int, size size_t) &C.sg_shader_desc { + desc.fs.uniform_blocks[block_index].size = size + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_vert_uniform(block_index int, uniform_index int, name string, @type UniformType, array_count int) &C.sg_shader_desc { + desc.vs.uniform_blocks[block_index].uniforms[uniform_index].name = &char(name.str) + desc.vs.uniform_blocks[block_index].uniforms[uniform_index].@type = @type + return desc +} + +pub fn (mut desc C.sg_shader_desc) set_frag_uniform(block_index int, uniform_index int, name string, @type UniformType, array_count int) &C.sg_shader_desc { + desc.fs.uniform_blocks[block_index].uniforms[uniform_index].name = &char(name.str) + desc.fs.uniform_blocks[block_index].uniforms[uniform_index].@type = @type + return desc +} + +pub fn (desc &C.sg_shader_desc) make_shader() C.sg_shader { + return C.sg_make_shader(desc) +} + +pub struct C.sg_shader_attr_desc { +pub mut: + name &char // GLSL vertex attribute name (only required for GLES2) + sem_name &char // HLSL semantic name + sem_index int // HLSL semantic index +} + +pub struct C.sg_shader_stage_desc { +pub mut: + source &char + bytecode C.sg_range + entry &char + uniform_blocks [4]C.sg_shader_uniform_block_desc + images [12]C.sg_shader_image_desc +} + +pub fn (mut desc C.sg_shader_stage_desc) set_image(index int, name string) C.sg_shader_stage_desc { + desc.images[index].name = &char(name.str) + desc.images[index].image_type = ._2d + return *desc +} + +pub struct C.sg_shader_uniform_block_desc { +pub mut: + size size_t + uniforms [16]C.sg_shader_uniform_desc +} + +pub struct C.sg_shader_uniform_desc { +pub mut: + name &char + @type UniformType + array_count int +} + +pub struct C.sg_shader_image_desc { +pub mut: + name &char + image_type ImageType +} + +pub struct C.sg_shader_info { +} + +pub struct C.sg_context { + id u32 +} + +pub struct C.sg_range { +pub mut: + ptr voidptr + size size_t +} + +pub struct C.sg_color { +pub mut: + r f32 + g f32 + b f32 + a f32 +} + +pub struct C.sg_shader { +pub: + id u32 +} + +pub fn (s C.sg_shader) free() { + C.sg_destroy_shader(s) +} + +pub struct C.sg_pass_desc { +pub mut: + _start_canary u32 + color_attachments [4]C.sg_pass_attachment_desc + depth_stencil_attachment C.sg_pass_attachment_desc + label &char + _end_canary u32 +} + +pub struct C.sg_pass_info { + info C.sg_slot_info +} + +pub struct C.sg_pass_action { +pub mut: + _start_canary u32 + colors [4]C.sg_color_attachment_action + depth C.sg_depth_attachment_action + stencil C.sg_stencil_attachment_action + _end_canary u32 +} + +pub struct C.sg_pass { + id u32 +} + +pub fn (p C.sg_pass) free() { + C.sg_destroy_pass(p) +} + +pub struct C.sg_buffer_desc { +pub mut: + _start_canary u32 + size size_t + @type BufferType + usage Usage + data C.sg_range + label &char + // GL specific + gl_buffers [2]u32 + // Metal specific + mtl_buffers [2]voidptr + // D3D11 specific + d3d11_buffer voidptr + _end_canary u32 +} + +pub struct C.sg_buffer_info { +} + +pub struct C.sg_buffer { + id u32 +} + +pub fn (b C.sg_buffer) free() { + C.sg_destroy_buffer(b) +} + +pub struct DepthLayers { + depth int + layers int +} + +pub struct C.sg_image_desc { +pub mut: + _start_canary u32 + @type ImageType + render_target bool + width int + height int + num_slices int + num_mipmaps int + usage Usage + pixel_format PixelFormat + sample_count int + min_filter Filter + mag_filter Filter + wrap_u Wrap + wrap_v Wrap + wrap_w Wrap + border_color BorderColor + max_anisotropy u32 + min_lod f32 + max_lod f32 + data C.sg_image_data + label &char + // GL specific + gl_textures [2]u32 + gl_texture_target u32 + // Metal specific + mtl_textures [2]voidptr + // D3D11 specific + d3d11_texture voidptr + d3d11_shader_resource_view voidptr + // WebGPU specific + wgpu_texture voidptr + _end_canary u32 +} + +pub struct C.sg_image_info { +pub mut: + slot C.sg_slot_info // resource pool slot info + upd_frame_index u32 // frame index of last sg_update_image() + num_slots int // number of renaming-slots for dynamically updated images + active_slot int // currently active write-slot for dynamically updated images +} + +pub struct C.sg_image { +pub: + id u32 +} + +pub fn (i C.sg_image) free() { + C.sg_destroy_image(i) +} + +pub const sg_cubeface_num = 6 + +pub const sg_max_mipmaps = 16 + +pub struct C.sg_image_data { +pub mut: + subimage [sg_cubeface_num][sg_max_mipmaps]C.sg_range +} + +pub struct C.sg_features { +pub: + instancing bool // hardware instancing supported + origin_top_left bool // framebuffer and texture origin is in top left corner + multiple_render_targets bool // offscreen render passes can have multiple render targets attached + msaa_render_targets bool // offscreen render passes support MSAA antialiasing + imagetype_3d bool // creation of SG_IMAGETYPE_3D images is supported + imagetype_array bool // creation of SG_IMAGETYPE_ARRAY images is supported + image_clamp_to_border bool // border color and clamp-to-border UV-wrap mode is supported + mrt_independent_blend_state bool // multiple-render-target rendering can use per-render-target blend state + mrt_independent_write_mask bool // multiple-render-target rendering can use per-render-target color write masks +} + +pub struct C.sg_limits { +pub: + max_image_size_2d u32 // max width/height of SG_IMAGETYPE_2D images + max_image_size_cube u32 // max width/height of SG_IMAGETYPE_CUBE images + max_image_size_3d u32 // max width/height/depth of SG_IMAGETYPE_3D images + max_image_size_array u32 // max width/height pf SG_IMAGETYPE_ARRAY images + max_image_array_layers u32 // max number of layers in SG_IMAGETYPE_ARRAY images + max_vertex_attrs u32 // <= SG_MAX_VERTEX_ATTRIBUTES (only on some GLES2 impls) +} + +pub struct C.sg_layout_desc { +pub mut: + buffers [8]C.sg_buffer_layout_desc + attrs [16]C.sg_vertex_attr_desc +} + +pub struct C.sg_buffer_layout_desc { +pub mut: + stride int + step_func VertexStep + step_rate int +} + +pub struct C.sg_vertex_attr_desc { +pub mut: + buffer_index int + offset int + format VertexFormat +} + +pub struct C.sg_stencil_state { + enabled bool + front C.sg_stencil_face_state + back C.sg_stencil_face_state + read_mask byte + write_mask byte + ref byte +} + +pub struct C.sg_depth_state { + pixel_format PixelFormat + compare CompareFunc + write_enabled bool + bias f32 + bias_slope_scale f32 + bias_clamp f32 +} + +pub struct C.sg_stencil_face_state { + fail_op StencilOp + depth_fail_op StencilOp + pass_op StencilOp + compare_func CompareFunc +} + +pub struct C.sg_blend_state { +pub mut: + enabled bool + src_factor_rgb BlendFactor + dst_factor_rgb BlendFactor + op_rgb BlendOp + src_factor_alpha BlendFactor + dst_factor_alpha BlendFactor + op_alpha BlendOp +} + +pub struct C.sg_color_attachment_action { +pub mut: + action Action + value C.sg_color +} + +/* +pub fn (mut action C.sg_color_attachment_action) set_color_values(r, g, b, a f32) { + action.val[0] = r + action.val[1] = g + action.val[2] = b + action.val[3] = a +} +*/ +pub struct C.sg_depth_attachment_action { +pub mut: + action Action + value f32 +} + +pub struct C.sg_stencil_attachment_action { +pub mut: + action Action + value byte +} + +pub struct C.sg_pixelformat_info { +pub: + sample bool // pixel format can be sampled in shaders + filter bool // pixel format can be sampled with filtering + render bool // pixel format can be used as render target + blend bool // alpha-blending is supported + msaa bool // pixel format can be used as MSAA render target + depth bool // pixel format is a depth format +} + +pub struct C.sg_pass_attachment_desc { +pub mut: + image C.sg_image + mip_level int + face int + // image sg_image + // mip_level int + // union { + // face int + // layer int + // slice int + // } +} diff --git a/v_windows/v/vlib/sokol/gfx/gfx_utils.c.v b/v_windows/v/vlib/sokol/gfx/gfx_utils.c.v new file mode 100644 index 0000000..c95ff69 --- /dev/null +++ b/v_windows/v/vlib/sokol/gfx/gfx_utils.c.v @@ -0,0 +1,17 @@ +module gfx + +pub fn create_clear_pass(r f32, g f32, b f32, a f32) C.sg_pass_action { + mut color_action := C.sg_color_attachment_action{ + action: Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: r + g: g + b: b + a: a + } + } + // color_action.set_color_values(r, g, b, a) + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + return pass_action +} diff --git a/v_windows/v/vlib/sokol/sapp/enums.v b/v_windows/v/vlib/sokol/sapp/enums.v new file mode 100644 index 0000000..8e0efd7 --- /dev/null +++ b/v_windows/v/vlib/sokol/sapp/enums.v @@ -0,0 +1,165 @@ +module sapp + +pub enum EventType { + invalid + key_down + key_up + char + mouse_down + mouse_up + mouse_scroll + mouse_move + mouse_enter + mouse_leave + touches_began + touches_moved + touches_ended + touches_cancelled + resized + iconified + restored + suspended + resumed + update_cursor + quit_requested + clipboard_pasted + num +} + +pub enum MouseButton { + invalid = -1 + left = 0 + right = 1 + middle = 2 +} + +pub enum Modifier { + shift = 1 //(1<<0) + ctrl = 2 //(1<<1) + alt = 4 //(1<<2) + super = 8 //(1<<3) +} + +pub enum KeyCode { + invalid = 0 + space = 32 + apostrophe = 39 //' + comma = 44 //, + minus = 45 //- + period = 46 //. + slash = 47 /// + _0 = 48 + _1 = 49 + _2 = 50 + _3 = 51 + _4 = 52 + _5 = 53 + _6 = 54 + _7 = 55 + _8 = 56 + _9 = 57 + semicolon = 59 //; + equal = 61 //= + a = 65 + b = 66 + c = 67 + d = 68 + e = 69 + f = 70 + g = 71 + h = 72 + i = 73 + j = 74 + k = 75 + l = 76 + m = 77 + n = 78 + o = 79 + p = 80 + q = 81 + r = 82 + s = 83 + t = 84 + u = 85 + v = 86 + w = 87 + x = 88 + y = 89 + z = 90 + left_bracket = 91 //[ + backslash = 92 //\ + right_bracket = 93 //] + grave_accent = 96 //` + world_1 = 161 // non-us #1 + world_2 = 162 // non-us #2 + escape = 256 + enter = 257 + tab = 258 + backspace = 259 + insert = 260 + delete = 261 + right = 262 + left = 263 + down = 264 + up = 265 + page_up = 266 + page_down = 267 + home = 268 + end = 269 + caps_lock = 280 + scroll_lock = 281 + num_lock = 282 + print_screen = 283 + pause = 284 + f1 = 290 + f2 = 291 + f3 = 292 + f4 = 293 + f5 = 294 + f6 = 295 + f7 = 296 + f8 = 297 + f9 = 298 + f10 = 299 + f11 = 300 + f12 = 301 + f13 = 302 + f14 = 303 + f15 = 304 + f16 = 305 + f17 = 306 + f18 = 307 + f19 = 308 + f20 = 309 + f21 = 310 + f22 = 311 + f23 = 312 + f24 = 313 + f25 = 314 + kp_0 = 320 + kp_1 = 321 + kp_2 = 322 + kp_3 = 323 + kp_4 = 324 + kp_5 = 325 + kp_6 = 326 + kp_7 = 327 + kp_8 = 328 + kp_9 = 329 + kp_decimal = 330 + kp_divide = 331 + kp_multiply = 332 + kp_subtract = 333 + kp_add = 334 + kp_enter = 335 + kp_equal = 336 + left_shift = 340 + left_control = 341 + left_alt = 342 + left_super = 343 + right_shift = 344 + right_control = 345 + right_alt = 346 + right_super = 347 + menu = 348 +} diff --git a/v_windows/v/vlib/sokol/sapp/sapp.c.v b/v_windows/v/vlib/sokol/sapp/sapp.c.v new file mode 100644 index 0000000..28b7562 --- /dev/null +++ b/v_windows/v/vlib/sokol/sapp/sapp.c.v @@ -0,0 +1,247 @@ +module sapp + +import sokol.gfx + +pub const used_import = gfx.used_import + +// Android needs a global reference to `g_desc` +__global ( + g_desc C.sapp_desc +) + +pub fn create_desc() C.sg_desc { + metal_desc := C.sg_metal_context_desc{ + device: metal_get_device() + renderpass_descriptor_cb: metal_get_renderpass_descriptor + drawable_cb: metal_get_drawable + } + d3d11_desc := C.sg_d3d11_context_desc{ + device: d3d11_get_device() + device_context: d3d11_get_device_context() + render_target_view_cb: d3d11_get_render_target_view + depth_stencil_view_cb: d3d11_get_depth_stencil_view + } + return C.sg_desc{ + context: C.sg_context_desc{ + metal: metal_desc + d3d11: d3d11_desc + color_format: .bgra8 + } + image_pool_size: 1000 + } +} + +// returns true after sokol-app has been initialized +[inline] +pub fn isvalid() bool { + return C.sapp_isvalid() +} + +// returns the current framebuffer width in pixels +[inline] +pub fn width() int { + return C.sapp_width() +} + +// returns the current framebuffer height in pixels +[inline] +pub fn height() int { + return C.sapp_height() +} + +// returns true when high_dpi was requested and actually running in a high-dpi scenario +[inline] +pub fn high_dpi() bool { + return C.sapp_high_dpi() +} + +// returns the dpi scaling factor (window pixels to framebuffer pixels) +[inline] +pub fn dpi_scale() f32 { + return C.sapp_dpi_scale() +} + +// show or hide the mobile device onscreen keyboard +[inline] +pub fn show_keyboard(visible bool) { + C.sapp_show_keyboard(visible) +} + +// return true if the mobile device onscreen keyboard is currently shown +[inline] +pub fn keyboard_shown() bool { + return C.sapp_keyboard_shown() +} + +// show or hide the mouse cursor +[inline] +pub fn show_mouse(visible bool) { + C.sapp_show_mouse(visible) +} + +// show or hide the mouse cursor +[inline] +pub fn mouse_shown() bool { + return C.sapp_mouse_shown() +} + +[inline] +pub fn lock_mouse(locked bool) { + C.sapp_lock_mouse(locked) +} + +[inline] +pub fn mouse_locked() bool { + return C.sapp_mouse_locked() +} + +// return the userdata pointer optionally provided in sapp_desc +[inline] +pub fn userdata() voidptr { + return C.sapp_userdata() +} + +// return a copy of the sapp_desc structure +[inline] +pub fn query_desc() C.sapp_desc { + return C.sapp_query_desc() +} + +// initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) +[inline] +pub fn request_quit() { + C.sapp_request_quit() +} + +// cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) +[inline] +pub fn cancel_quit() { + C.sapp_cancel_quit() +} + +// intiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) +[inline] +pub fn quit() { + C.sapp_quit() +} + +// call from inside event callback to consume the current event (don't forward to platform) +[inline] +pub fn consume_event() { + C.sapp_consume_event() +} + +// get the current frame counter (for comparison with sapp_event.frame_count) +[inline] +pub fn frame_count() u64 { + return C.sapp_frame_count() +} + +// write string into clipboard +[inline] +pub fn set_clipboard_string(str &char) { + C.sapp_set_clipboard_string(str) +} + +// read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) +[inline] +pub fn get_clipboard_string() &char { + return &char(C.sapp_get_clipboard_string()) +} + +// special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) +[inline] +pub fn run(desc &C.sapp_desc) { + g_desc = desc + C.sapp_run(desc) +} + +// GL: return true when GLES2 fallback is active (to detect fallback from GLES3) +[inline] +pub fn gles2() bool { + return C.sapp_gles2() +} + +// HTML5: enable or disable the hardwired "Leave Site?" dialog box +[inline] +pub fn html5_ask_leave_site(ask bool) { + C.sapp_html5_ask_leave_site(ask) +} + +// Metal: get ARC-bridged pointer to Metal device object +[inline] +pub fn metal_get_device() voidptr { + return voidptr(C.sapp_metal_get_device()) +} + +// Metal: get ARC-bridged pointer to this frame's renderpass descriptor +[inline] +pub fn metal_get_renderpass_descriptor() voidptr { + return voidptr(C.sapp_metal_get_renderpass_descriptor()) +} + +// Metal: get ARC-bridged pointer to current drawable +[inline] +pub fn metal_get_drawable() voidptr { + return voidptr(C.sapp_metal_get_drawable()) +} + +// macOS: get ARC-bridged pointer to macOS NSWindow +[inline] +pub fn macos_get_window() voidptr { + return voidptr(C.sapp_macos_get_window()) +} + +// iOS: get ARC-bridged pointer to iOS UIWindow +[inline] +pub fn ios_get_window() voidptr { + return voidptr(C.sapp_ios_get_window()) +} + +// D3D11: get pointer to ID3D11Device object +[inline] +pub fn d3d11_get_device() voidptr { + return voidptr(C.sapp_d3d11_get_device()) +} + +// D3D11: get pointer to ID3D11DeviceContext object +[inline] +pub fn d3d11_get_device_context() voidptr { + return voidptr(C.sapp_d3d11_get_device_context()) +} + +// D3D11: get pointer to ID3D11RenderTargetView object +[inline] +pub fn d3d11_get_render_target_view() voidptr { + return voidptr(C.sapp_d3d11_get_render_target_view()) +} + +// D3D11: get pointer to ID3D11DepthStencilView +[inline] +pub fn d3d11_get_depth_stencil_view() voidptr { + return voidptr(C.sapp_d3d11_get_depth_stencil_view()) +} + +// Win32: get the HWND window handle +[inline] +pub fn win32_get_hwnd() voidptr { + return voidptr(C.sapp_win32_get_hwnd()) +} + +// Android: get native activity handle +[inline] +pub fn android_get_native_activity() voidptr { + return voidptr(C.sapp_android_get_native_activity()) +} + +// Toggle full screen +[inline] +pub fn toggle_fullscreen() { + C.sapp_toggle_fullscreen() +} + +// Check if full screen rendering +[inline] +pub fn is_fullscreen() bool { + return C.sapp_is_fullscreen() +} diff --git a/v_windows/v/vlib/sokol/sapp/sapp_funcs.c.v b/v_windows/v/vlib/sokol/sapp/sapp_funcs.c.v new file mode 100644 index 0000000..3d177bd --- /dev/null +++ b/v_windows/v/vlib/sokol/sapp/sapp_funcs.c.v @@ -0,0 +1,111 @@ +module sapp + +// returns true after sokol-app has been initialized +fn C.sapp_isvalid() bool + +// returns the current framebuffer width in pixels +fn C.sapp_width() int +fn C.sapp_widthf() f32 + +// returns the current framebuffer height in pixels +fn C.sapp_height() int +fn C.sapp_heightf() f32 + +// returns true when high_dpi was requested and actually running in a high-dpi scenario +fn C.sapp_high_dpi() bool + +// returns the dpi scaling factor (window pixels to framebuffer pixels) +fn C.sapp_dpi_scale() f32 + +// show or hide the mobile device onscreen keyboard +fn C.sapp_show_keyboard(visible bool) + +// return true if the mobile device onscreen keyboard is currently shown +fn C.sapp_keyboard_shown() bool + +// show or hide the mouse cursor +fn C.sapp_show_mouse(visible bool) + +// return true if the mouse cursor is shown +fn C.sapp_mouse_shown() bool + +// lock or unlock the mouse cursor +fn C.sapp_lock_mouse(locked bool) + +// return true if the mouse cursor is locked +fn C.sapp_mouse_locked() bool + +// return the userdata pointer optionally provided in sapp_desc +fn C.sapp_userdata() voidptr + +// return a copy of the sapp_desc structure +fn C.sapp_query_desc() C.sapp_desc + +// initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) +fn C.sapp_request_quit() + +// cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) +fn C.sapp_cancel_quit() + +// intiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) +fn C.sapp_quit() + +// call from inside event callback to consume the current event (don't forward to platform) +fn C.sapp_consume_event() + +// get the current frame counter (for comparison with sapp_event.frame_count) +fn C.sapp_frame_count() u64 + +// write string into clipboard +fn C.sapp_set_clipboard_string(str &byte) + +// read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) +fn C.sapp_get_clipboard_string() &byte + +// special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) +fn C.sapp_run(desc &C.sapp_desc) int + +// GL: return true when GLES2 fallback is active (to detect fallback from GLES3) +fn C.sapp_gles2() bool + +// HTML5: enable or disable the hardwired "Leave Site?" dialog box +fn C.sapp_html5_ask_leave_site(ask bool) + +// Metal: get ARC-bridged pointer to Metal device object +fn C.sapp_metal_get_device() voidptr + +// Metal: get ARC-bridged pointer to this frame's renderpass descriptor +fn C.sapp_metal_get_renderpass_descriptor() voidptr + +// Metal: get ARC-bridged pointer to current drawable +fn C.sapp_metal_get_drawable() voidptr + +// macOS: get ARC-bridged pointer to macOS NSWindow +fn C.sapp_macos_get_window() voidptr + +// iOS: get ARC-bridged pointer to iOS UIWindow +fn C.sapp_ios_get_window() voidptr + +// D3D11: get pointer to ID3D11Device object +fn C.sapp_d3d11_get_device() voidptr + +// D3D11: get pointer to ID3D11DeviceContext object +fn C.sapp_d3d11_get_device_context() voidptr + +// D3D11: get pointer to ID3D11RenderTargetView object +fn C.sapp_d3d11_get_render_target_view() voidptr + +// D3D11: get pointer to ID3D11DepthStencilView +fn C.sapp_d3d11_get_depth_stencil_view() voidptr + +// Win32: get the HWND window handle +fn C.sapp_win32_get_hwnd() voidptr + +// Android: get native activity handle +fn C.sapp_android_get_native_activity() voidptr + +// Toggle full screen +fn C.sapp_toggle_fullscreen() + +// Check if full screen rendering +fn C.sapp_is_fullscreen() bool diff --git a/v_windows/v/vlib/sokol/sapp/sapp_structs.c.v b/v_windows/v/vlib/sokol/sapp/sapp_structs.c.v new file mode 100644 index 0000000..a8ff8b1 --- /dev/null +++ b/v_windows/v/vlib/sokol/sapp/sapp_structs.c.v @@ -0,0 +1,104 @@ +module sapp + +pub struct C.sapp_desc { +pub: + init_cb fn () // these are the user-provided callbacks without user data + frame_cb fn () + cleanup_cb fn () + event_cb fn (&C.sapp_event) //&sapp_event) + fail_cb fn (&byte) + + user_data voidptr // these are the user-provided callbacks with user data + init_userdata_cb fn (voidptr) + frame_userdata_cb fn (voidptr) + cleanup_userdata_cb fn (voidptr) + event_userdata_cb fn (&C.sapp_event, voidptr) + fail_userdata_cb fn (&char, voidptr) + + width int // the preferred width of the window / canvas + height int // the preferred height of the window / canvas + sample_count int // MSAA sample count + swap_interval int // the preferred swap interval (ignored on some platforms) + high_dpi bool // whether the rendering canvas is full-resolution on HighDPI displays + fullscreen bool // whether the window should be created in fullscreen mode + alpha bool // whether the framebuffer should have an alpha channel (ignored on some platforms) + window_title &char // the window title as UTF-8 encoded string + user_cursor bool // if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR + enable_clipboard bool // enable clipboard access, default is false + clipboard_size int // max size of clipboard content in bytes + enable_dragndrop bool // enable file dropping (drag'n'drop), default is false + max_dropped_files int // max number of dropped files to process (default: 1) + max_dropped_file_path_length int // max length in bytes of a dropped UTF-8 file path (default: 2048) + // backend-specific options + gl_force_gles2 bool // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available + win32_console_utf8 bool // if true, set the output console codepage to UTF-8 + win32_console_create bool // if true, attach stdout/stderr to a new console window + win32_console_attach bool // if true, attach stdout/stderr to parent process + html5_canvas_name &char // the name (id) of the HTML5 canvas element, default is "canvas" + html5_canvas_resize bool // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked + html5_preserve_drawing_buffer bool // HTML5 only: whether to preserve default framebuffer content between frames + html5_premultiplied_alpha bool // HTML5 only: whether the rendered pixels use premultiplied alpha convention + html5_ask_leave_site bool // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) + ios_keyboard_resizes_canvas bool // if true, showing the iOS keyboard shrinks the canvas + // V patches + __v_native_render bool // V patch to allow for native rendering +} + +pub struct Event { +pub: + frame_count u64 + typ EventType + key_code KeyCode + char_code u32 + key_repeat bool + modifiers u32 + mouse_button MouseButton + mouse_x f32 + mouse_y f32 + mouse_dx f32 + mouse_dy f32 + scroll_x f32 + scroll_y f32 + num_touches int + touches [8]C.sapp_touchpoint + window_width int + window_height int + framebuffer_width int + framebuffer_height int +} + +pub struct C.sapp_event { +pub: + frame_count u64 + @type EventType + key_code KeyCode + char_code u32 + key_repeat bool + modifiers u32 + mouse_button MouseButton + mouse_x f32 + mouse_y f32 + mouse_dx f32 + mouse_dy f32 + scroll_x f32 + scroll_y f32 + num_touches int + touches [8]C.sapp_touchpoint + window_width int + window_height int + framebuffer_width int + framebuffer_height int +} + +pub fn (e &C.sapp_event) str() string { + t := e.@type + return 'evt: frame_count=$e.frame_count, type=$t' +} + +pub struct C.sapp_touchpoint { +pub: + identifier u64 + pos_x f32 + pos_y f32 + changed bool +} diff --git a/v_windows/v/vlib/sokol/sfons/sfons.c.v b/v_windows/v/vlib/sokol/sfons/sfons.c.v new file mode 100644 index 0000000..003c112 --- /dev/null +++ b/v_windows/v/vlib/sokol/sfons/sfons.c.v @@ -0,0 +1,26 @@ +module sfons + +import fontstash + +// keep v from warning about unused imports +const used_import = fontstash.used_import + 1 + +[inline] +pub fn create(width int, height int, flags int) &C.FONScontext { + return C.sfons_create(width, height, flags) +} + +[inline] +pub fn destroy(ctx &C.FONScontext) { + C.sfons_destroy(ctx) +} + +[inline] +pub fn rgba(r byte, g byte, b byte, a byte) u32 { + return C.sfons_rgba(r, g, b, a) +} + +[inline] +pub fn flush(ctx &C.FONScontext) { + C.sfons_flush(ctx) +} diff --git a/v_windows/v/vlib/sokol/sfons/sfons_funcs.c.v b/v_windows/v/vlib/sokol/sfons/sfons_funcs.c.v new file mode 100644 index 0000000..cec30d6 --- /dev/null +++ b/v_windows/v/vlib/sokol/sfons/sfons_funcs.c.v @@ -0,0 +1,6 @@ +module sfons + +fn C.sfons_create(width int, height int, flags int) &C.FONScontext +fn C.sfons_destroy(ctx &C.FONScontext) +fn C.sfons_rgba(r byte, g byte, b byte, a byte) u32 +fn C.sfons_flush(ctx &C.FONScontext) diff --git a/v_windows/v/vlib/sokol/sgl/sgl.c.v b/v_windows/v/vlib/sokol/sgl/sgl.c.v new file mode 100644 index 0000000..0129cbf --- /dev/null +++ b/v_windows/v/vlib/sokol/sgl/sgl.c.v @@ -0,0 +1,375 @@ +module sgl + +import sokol.gfx + +pub const ( + version = gfx.version + 1 +) + +// setup/shutdown/misc +[inline] +pub fn setup(desc &C.sgl_desc_t) { + C.sgl_setup(desc) +} + +[inline] +pub fn shutdown() { + C.sgl_shutdown() +} + +[inline] +pub fn error() C.sgl_error_t { + return C.sgl_error() +} + +[inline] +pub fn defaults() { + C.sgl_defaults() +} + +[inline] +pub fn rad(deg f32) f32 { + return C.sgl_rad(deg) +} + +[inline] +pub fn deg(rad f32) f32 { + return C.sgl_deg(rad) +} + +// create and destroy pipeline objects +[inline] +pub fn make_pipeline(desc &C.sg_pipeline_desc) C.sgl_pipeline { + return C.sgl_make_pipeline(desc) +} + +[inline] +pub fn destroy_pipeline(pip C.sgl_pipeline) { + C.sgl_destroy_pipeline(pip) +} + +// render state functions +[inline] +pub fn viewport(x int, y int, w int, h int, origin_top_left bool) { + C.sgl_viewport(x, y, w, h, origin_top_left) +} + +[inline] +pub fn scissor_rect(x int, y int, w int, h int, origin_top_left bool) { + C.sgl_scissor_rect(x, y, w, h, origin_top_left) +} + +[inline] +pub fn enable_texture() { + C.sgl_enable_texture() +} + +[inline] +pub fn disable_texture() { + C.sgl_disable_texture() +} + +[inline] +pub fn texture(img C.sg_image) { + C.sgl_texture(img) +} + +// pipeline stack functions +[inline] +pub fn default_pipeline() { + C.sgl_default_pipeline() +} + +[inline] +pub fn load_pipeline(pip C.sgl_pipeline) { + C.sgl_load_pipeline(pip) +} + +[inline] +pub fn push_pipeline() { + C.sgl_push_pipeline() +} + +[inline] +pub fn pop_pipeline() { + C.sgl_pop_pipeline() +} + +// matrix stack functions +[inline] +pub fn matrix_mode_modelview() { + C.sgl_matrix_mode_modelview() +} + +[inline] +pub fn matrix_mode_projection() { + C.sgl_matrix_mode_projection() +} + +[inline] +pub fn matrix_mode_texture() { + C.sgl_matrix_mode_texture() +} + +[inline] +pub fn load_identity() { + C.sgl_load_identity() +} + +[inline] +pub fn load_matrix(m []f32) { + C.sgl_load_matrix(m.data) +} + +[inline] +pub fn load_transpose_matrix(m []f32) { + C.sgl_load_transpose_matrix(m.data) +} + +[inline] +pub fn mult_matrix(m []f32) { + C.sgl_mult_matrix(m.data) +} + +[inline] +pub fn mult_transpose_matrix(m []f32) { + C.sgl_mult_transpose_matrix(m.data) +} + +[inline] +pub fn rotate(angle_rad f32, x f32, y f32, z f32) { + C.sgl_rotate(angle_rad, x, y, z) +} + +[inline] +pub fn scale(x f32, y f32, z f32) { + C.sgl_scale(x, y, z) +} + +[inline] +pub fn translate(x f32, y f32, z f32) { + C.sgl_translate(x, y, z) +} + +[inline] +pub fn frustum(l f32, r f32, b f32, t f32, n f32, f f32) { + C.sgl_frustum(l, r, b, t, n, f) +} + +[inline] +pub fn ortho(l f32, r f32, b f32, t f32, n f32, f f32) { + C.sgl_ortho(l, r, b, t, n, f) +} + +[inline] +pub fn perspective(fov_y f32, aspect f32, z_near f32, z_far f32) { + C.sgl_perspective(fov_y, aspect, z_near, z_far) +} + +[inline] +pub fn lookat(eye_x f32, eye_y f32, eye_z f32, center_x f32, center_y f32, center_z f32, up_x f32, up_y f32, up_z f32) { + C.sgl_lookat(eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z) +} + +[inline] +pub fn push_matrix() { + C.sgl_push_matrix() +} + +[inline] +pub fn pop_matrix() { + C.sgl_pop_matrix() +} + +// these functions only set the internal 'current texcoord / color' (valid inside or outside begin/end) +[inline] +pub fn t2f(u f32, v f32) { + C.sgl_t2f(u, v) +} + +[inline] +pub fn c3f(r f32, g f32, b f32) { + C.sgl_c3f(r, g, b) +} + +[inline] +pub fn c4f(r f32, g f32, b f32, a f32) { + C.sgl_c4f(r, g, b, a) +} + +[inline] +pub fn c3b(r byte, g byte, b byte) { + C.sgl_c3b(r, g, b) +} + +[inline] +pub fn c4b(r byte, g byte, b byte, a byte) { + C.sgl_c4b(r, g, b, a) +} + +[inline] +pub fn c1i(rgba u32) { + C.sgl_c1i(rgba) +} + +// define primitives, each begin/end is one draw command +[inline] +pub fn begin_points() { + C.sgl_begin_points() +} + +[inline] +pub fn begin_lines() { + C.sgl_begin_lines() +} + +[inline] +pub fn begin_line_strip() { + C.sgl_begin_line_strip() +} + +[inline] +pub fn begin_triangles() { + C.sgl_begin_triangles() +} + +[inline] +pub fn begin_triangle_strip() { + C.sgl_begin_triangle_strip() +} + +[inline] +pub fn begin_quads() { + C.sgl_begin_quads() +} + +[inline] +pub fn v2f(x f32, y f32) { + C.sgl_v2f(x, y) +} + +[inline] +pub fn v3f(x f32, y f32, z f32) { + C.sgl_v3f(x, y, z) +} + +[inline] +pub fn v2f_t2f(x f32, y f32, u f32, v f32) { + C.sgl_v2f_t2f(x, y, u, v) +} + +[inline] +pub fn v3f_t2f(x f32, y f32, z f32, u f32, v f32) { + C.sgl_v3f_t2f(x, y, z, u, v) +} + +[inline] +pub fn v2f_c3f(x f32, y f32, r f32, g f32, b f32) { + C.sgl_v2f_c3f(x, y, r, g, b) +} + +[inline] +pub fn v2f_c3b(x f32, y f32, r byte, g byte, b byte) { + C.sgl_v2f_c3b(x, y, r, g, b) +} + +[inline] +pub fn v2f_c4f(x f32, y f32, r f32, g f32, b f32, a f32) { + C.sgl_v2f_c4f(x, y, r, g, b, a) +} + +[inline] +pub fn v2f_c4b(x f32, y f32, r byte, g byte, b byte, a byte) { + C.sgl_v2f_c4b(x, y, r, g, b, a) +} + +[inline] +pub fn v2f_c1i(x f32, y f32, rgba u32) { + C.sgl_v2f_c1i(x, y, rgba) +} + +[inline] +pub fn v3f_c3f(x f32, y f32, z f32, r f32, g f32, b f32) { + C.sgl_v3f_c3f(x, y, z, r, g, b) +} + +[inline] +pub fn v3f_c3b(x f32, y f32, z f32, r byte, g byte, b byte) { + C.sgl_v3f_c3b(x, y, z, r, g, b) +} + +[inline] +pub fn v3f_c4f(x f32, y f32, z f32, r f32, g f32, b f32, a f32) { + C.sgl_v3f_c4f(x, y, z, r, g, b, a) +} + +[inline] +pub fn v3f_c4b(x f32, y f32, z f32, r byte, g byte, b byte, a byte) { + C.sgl_v3f_c4b(x, y, z, r, g, b, a) +} + +[inline] +pub fn v3f_c1i(x f32, y f32, z f32, rgba u32) { + C.sgl_v3f_c1i(x, y, z, rgba) +} + +[inline] +pub fn v2f_t2f_c3f(x f32, y f32, u f32, v f32, r f32, g f32, b f32) { + C.sgl_v2f_t2f_c3f(x, y, u, v, r, g, b) +} + +[inline] +pub fn v2f_t2f_c3b(x f32, y f32, u f32, v f32, r byte, g byte, b byte) { + C.sgl_v2f_t2f_c3b(x, y, u, v, r, g, b) +} + +[inline] +pub fn v2f_t2f_c4f(x f32, y f32, u f32, v f32, r f32, g f32, b f32, a f32) { + C.sgl_v2f_t2f_c4f(x, y, u, v, r, g, b, a) +} + +[inline] +pub fn v2f_t2f_c4b(x f32, y f32, u f32, v f32, r byte, g byte, b byte, a byte) { + C.sgl_v2f_t2f_c4b(x, y, u, v, r, g, b, a) +} + +[inline] +pub fn v2f_t2f_c1i(x f32, y f32, u f32, v f32, rgba u32) { + C.sgl_v2f_t2f_c1i(x, y, u, v, rgba) +} + +[inline] +pub fn v3f_t2f_c3f(x f32, y f32, z f32, u f32, v f32, r f32, g f32, b f32) { + C.sgl_v3f_t2f_c3f(x, y, z, u, v, r, g, b) +} + +[inline] +pub fn v3f_t2f_c3b(x f32, y f32, z f32, u f32, v f32, r byte, g byte, b byte) { + C.sgl_v3f_t2f_c3b(x, y, z, u, v, r, g, b) +} + +[inline] +pub fn v3f_t2f_c4f(x f32, y f32, z f32, u f32, v f32, r f32, g f32, b f32, a f32) { + C.sgl_v3f_t2f_c4f(x, y, z, u, v, r, g, b, a) +} + +[inline] +pub fn v3f_t2f_c4b(x f32, y f32, z f32, u f32, v f32, r byte, g byte, b byte, a byte) { + C.sgl_v3f_t2f_c4b(x, y, z, u, v, r, g, b, a) +} + +[inline] +pub fn v3f_t2f_c1i(x f32, y f32, z f32, u f32, v f32, rgba u32) { + C.sgl_v3f_t2f_c1i(x, y, z, u, v, rgba) +} + +[inline] +pub fn end() { + C.sgl_end() +} + +// render everything +[inline] +pub fn draw() { + C.sgl_draw() +} diff --git a/v_windows/v/vlib/sokol/sgl/sgl_funcs.c.v b/v_windows/v/vlib/sokol/sgl/sgl_funcs.c.v new file mode 100644 index 0000000..16e980d --- /dev/null +++ b/v_windows/v/vlib/sokol/sgl/sgl_funcs.c.v @@ -0,0 +1,91 @@ +module sgl + +// setup/shutdown/misc +fn C.sgl_setup(desc &C.sgl_desc_t) +fn C.sgl_shutdown() +fn C.sgl_error() C.sgl_error_t +fn C.sgl_defaults() +fn C.sgl_rad(deg f32) f32 +fn C.sgl_deg(rad f32) f32 + +// create and destroy pipeline objects +fn C.sgl_make_pipeline(desc &C.sg_pipeline_desc) C.sgl_pipeline +fn C.sgl_destroy_pipeline(pip C.sgl_pipeline) + +// render state functions +fn C.sgl_viewport(x int, y int, w int, h int, origin_top_left bool) +fn C.sgl_viewportf(x f32, y f32, w f32, h f32, origin_top_left bool) +fn C.sgl_scissor_rect(x int, y int, w int, h int, origin_top_left bool) +fn C.sgl_scissor_rectf(x f32, y f32, w f32, h f32, origin_top_left bool) +fn C.sgl_enable_texture() +fn C.sgl_disable_texture() +fn C.sgl_texture(img C.sg_image) + +// pipeline stack functions +fn C.sgl_default_pipeline() +fn C.sgl_load_pipeline(pip C.sgl_pipeline) +fn C.sgl_push_pipeline() +fn C.sgl_pop_pipeline() + +// matrix stack functions +fn C.sgl_matrix_mode_modelview() +fn C.sgl_matrix_mode_projection() +fn C.sgl_matrix_mode_texture() +fn C.sgl_load_identity() +fn C.sgl_load_matrix(m [16]f32) +fn C.sgl_load_transpose_matrix(m [16]f32) +fn C.sgl_mult_matrix(m [16]f32) +fn C.sgl_mult_transpose_matrix(m [16]f32) +fn C.sgl_rotate(angle_rad f32, x f32, y f32, z f32) +fn C.sgl_scale(x f32, y f32, z f32) +fn C.sgl_translate(x f32, y f32, z f32) +fn C.sgl_frustum(l f32, r f32, b f32, t f32, n f32, f f32) +fn C.sgl_ortho(l f32, r f32, b f32, t f32, n f32, f f32) +fn C.sgl_perspective(fov_y f32, aspect f32, z_near f32, z_far f32) +fn C.sgl_lookat(eye_x f32, eye_y f32, eye_z f32, center_x f32, center_y f32, center_z f32, up_x f32, up_y f32, up_z f32) +fn C.sgl_push_matrix() +fn C.sgl_pop_matrix() + +// these functions only set the internal 'current texcoord / color' (valid inside or outside begin/end) +fn C.sgl_t2f(u f32, v f32) +fn C.sgl_c3f(r f32, g f32, b f32) +fn C.sgl_c4f(r f32, g f32, b f32, a f32) +fn C.sgl_c3b(r byte, g byte, b byte) +fn C.sgl_c4b(r byte, g byte, b byte, a byte) +fn C.sgl_c1i(rgba u32) + +// define primitives, each begin/end is one draw command +fn C.sgl_begin_points() +fn C.sgl_begin_lines() +fn C.sgl_begin_line_strip() +fn C.sgl_begin_triangles() +fn C.sgl_begin_triangle_strip() +fn C.sgl_begin_quads() +fn C.sgl_v2f(x f32, y f32) +fn C.sgl_v3f(x f32, y f32, z f32) +fn C.sgl_v2f_t2f(x f32, y f32, u f32, v f32) +fn C.sgl_v3f_t2f(x f32, y f32, z f32, u f32, v f32) +fn C.sgl_v2f_c3f(x f32, y f32, r f32, g f32, b f32) +fn C.sgl_v2f_c3b(x f32, y f32, r byte, g byte, b byte) +fn C.sgl_v2f_c4f(x f32, y f32, r f32, g f32, b f32, a f32) +fn C.sgl_v2f_c4b(x f32, y f32, r byte, g byte, b byte, a byte) +fn C.sgl_v2f_c1i(x f32, y f32, rgba u32) +fn C.sgl_v3f_c3f(x f32, y f32, z f32, r f32, g f32, b f32) +fn C.sgl_v3f_c3b(x f32, y f32, z f32, r byte, g byte, b byte) +fn C.sgl_v3f_c4f(x f32, y f32, z f32, r f32, g f32, b f32, a f32) +fn C.sgl_v3f_c4b(x f32, y f32, z f32, r byte, g byte, b byte, a byte) +fn C.sgl_v3f_c1i(x f32, y f32, z f32, rgba u32) +fn C.sgl_v2f_t2f_c3f(x f32, y f32, u f32, v f32, r f32, g f32, b f32) +fn C.sgl_v2f_t2f_c3b(x f32, y f32, u f32, v f32, r byte, g byte, b byte) +fn C.sgl_v2f_t2f_c4f(x f32, y f32, u f32, v f32, r f32, g f32, b f32, a f32) +fn C.sgl_v2f_t2f_c4b(x f32, y f32, u f32, v f32, r byte, g byte, b byte, a byte) +fn C.sgl_v2f_t2f_c1i(x f32, y f32, u f32, v f32, rgba u32) +fn C.sgl_v3f_t2f_c3f(x f32, y f32, z f32, u f32, v f32, r f32, g f32, b f32) +fn C.sgl_v3f_t2f_c3b(x f32, y f32, z f32, u f32, v f32, r byte, g byte, b byte) +fn C.sgl_v3f_t2f_c4f(x f32, y f32, z f32, u f32, v f32, r f32, g f32, b f32, a f32) +fn C.sgl_v3f_t2f_c4b(x f32, y f32, z f32, u f32, v f32, r byte, g byte, b byte, a byte) +fn C.sgl_v3f_t2f_c1i(x f32, y f32, z f32, u f32, v f32, rgba u32) +fn C.sgl_end() + +// render everything +fn C.sgl_draw() diff --git a/v_windows/v/vlib/sokol/sgl/sgl_structs.c.v b/v_windows/v/vlib/sokol/sgl/sgl_structs.c.v new file mode 100644 index 0000000..25753c4 --- /dev/null +++ b/v_windows/v/vlib/sokol/sgl/sgl_structs.c.v @@ -0,0 +1,24 @@ +module sgl + +// should be in a proper module +pub enum SglError { + no_error + vertices_full + commands_full + stack_overflow + stack_underfloat +} + +pub struct C.sgl_pipeline { + id u32 +} + +pub struct C.sgl_desc_t { + max_vertices int // size for vertex buffer + max_commands int // size of uniform- and command-buffers + pipeline_pool_size int // size of the internal pipeline pool, default is 64 + color_format C.sg_pixel_format + depth_format C.sg_pixel_format + sample_count int + face_winding C.sg_face_winding // default front face winding is CCW +} diff --git a/v_windows/v/vlib/sokol/sokol.v b/v_windows/v/vlib/sokol/sokol.v new file mode 100644 index 0000000..8c9a632 --- /dev/null +++ b/v_windows/v/vlib/sokol/sokol.v @@ -0,0 +1,19 @@ +module sokol + +import sokol.c +import sokol.f + +pub const ( + used_import = c.used_import + f.used_import +) + +/* +pub enum Key { + up=C.SAPP_KEYCODE_UP + left = C.SAPP_KEYCODE_LEFT + right =C.SAPP_KEYCODE_RIGHT + down = C.SAPP_KEYCODE_DOWN + escape = C.SAPP_KEYCODE_ESCAPE + space = C.SAPP_KEYCODE_SPACE +} +*/ diff --git a/v_windows/v/vlib/sqlite/README.md b/v_windows/v/vlib/sqlite/README.md new file mode 100644 index 0000000..d7462c6 --- /dev/null +++ b/v_windows/v/vlib/sqlite/README.md @@ -0,0 +1,16 @@ +# Install SQLite Dependency + +**Fedora 31**: + +`sudo dnf -y install sqlite-devel` + + +**Ubuntu 20.04**: + +`sudo apt install -y libsqlite3-dev` + + +**Windows**: +- Download the source zip from [SQLite Downloads](https://sqlite.org/download.html) +- Create a new `sqlite` subfolder inside `v/thirdparty` +- Extract the zip into that folder diff --git a/v_windows/v/vlib/sqlite/orm.v b/v_windows/v/vlib/sqlite/orm.v new file mode 100644 index 0000000..f2e9310 --- /dev/null +++ b/v_windows/v/vlib/sqlite/orm.v @@ -0,0 +1,162 @@ +module sqlite + +import orm +import time + +// sql expr + +pub fn (db DB) @select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ?[][]orm.Primitive { + query := orm.orm_select_gen(config, '`', true, '?', 1, where) + stmt := db.new_init_stmt(query) ? + mut c := 1 + sqlite_stmt_binder(stmt, where, query, mut c) ? + sqlite_stmt_binder(stmt, data, query, mut c) ? + + defer { + stmt.finalize() + } + + mut ret := [][]orm.Primitive{} + + if config.is_count { + step := stmt.step() + if step !in [sqlite_row, sqlite_ok, sqlite_done] { + return db.error_message(step, query) + } + count := stmt.sqlite_select_column(0, 8) ? + ret << [count] + return ret + } + for { + step := stmt.step() + if step == sqlite_done { + break + } + if step != sqlite_ok && step != sqlite_row { + break + } + mut row := []orm.Primitive{} + for i, typ in config.types { + primitive := stmt.sqlite_select_column(i, typ) ? + row << primitive + } + ret << row + } + return ret +} + +// sql stmt + +pub fn (db DB) insert(table string, data orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .insert, true, '?', 1, data, orm.QueryData{}) + sqlite_stmt_worker(db, query, data, orm.QueryData{}) ? +} + +pub fn (db DB) update(table string, data orm.QueryData, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .update, true, '?', 1, data, where) + sqlite_stmt_worker(db, query, data, where) ? +} + +pub fn (db DB) delete(table string, where orm.QueryData) ? { + query := orm.orm_stmt_gen(table, '`', .delete, true, '?', 1, orm.QueryData{}, where) + sqlite_stmt_worker(db, query, orm.QueryData{}, where) ? +} + +pub fn (db DB) last_id() orm.Primitive { + query := 'SELECT last_insert_rowid();' + id := db.q_int(query) + return orm.Primitive(id) +} + +// table +pub fn (db DB) create(table string, fields []orm.TableField) ? { + query := orm.orm_table_gen(table, '`', true, 0, fields, sqlite_type_from_v, false) or { + return err + } + sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +pub fn (db DB) drop(table string) ? { + query := 'DROP TABLE `$table`;' + sqlite_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{}) ? +} + +// helper + +fn sqlite_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ? { + stmt := db.new_init_stmt(query) ? + mut c := 1 + sqlite_stmt_binder(stmt, data, query, mut c) ? + sqlite_stmt_binder(stmt, where, query, mut c) ? + stmt.orm_step(query) ? + stmt.finalize() +} + +fn sqlite_stmt_binder(stmt Stmt, d orm.QueryData, query string, mut c &int) ? { + for data in d.data { + err := bind(stmt, c, data) + + if err != 0 { + return stmt.db.error_message(err, query) + } + c++ + } +} + +fn bind(stmt Stmt, c &int, data orm.Primitive) int { + mut err := 0 + match data { + i8, i16, int, byte, u16, u32, bool { + err = stmt.bind_int(c, int(data)) + } + i64, u64 { + err = stmt.bind_i64(c, i64(data)) + } + f32, f64 { + err = stmt.bind_f64(c, unsafe { *(&f64(&data)) }) + } + string { + err = stmt.bind_text(c, data) + } + time.Time { + err = stmt.bind_int(c, int(data.unix)) + } + orm.InfixType { + err = bind(stmt, c, data.right) + } + } + return err +} + +fn (stmt Stmt) sqlite_select_column(idx int, typ int) ?orm.Primitive { + mut primitive := orm.Primitive(0) + + if typ in orm.nums || typ == -1 { + primitive = stmt.get_int(idx) + } else if typ in orm.num64 { + primitive = stmt.get_i64(idx) + } else if typ in orm.float { + primitive = stmt.get_f64(idx) + } else if typ == orm.string { + primitive = stmt.get_text(idx).clone() + } else if typ == orm.time { + d := stmt.get_int(idx) + primitive = time.unix(d) + } else { + return error('Unknown type $typ') + } + + return primitive +} + +fn sqlite_type_from_v(typ int) ?string { + return if typ in orm.nums || typ < 0 || typ in orm.num64 || typ == orm.time { + 'INTEGER' + } else if typ in orm.float { + 'REAL' + } else if typ == orm.string { + 'TEXT' + } else { + error('Unknown type $typ') + } +} diff --git a/v_windows/v/vlib/sqlite/sqlite.v b/v_windows/v/vlib/sqlite/sqlite.v new file mode 100644 index 0000000..4bbe40e --- /dev/null +++ b/v_windows/v/vlib/sqlite/sqlite.v @@ -0,0 +1,236 @@ +module sqlite + +$if freebsd || openbsd { + #flag -I/usr/local/include + #flag -L/usr/local/lib +} +$if windows { + #flag windows -I@VEXEROOT/thirdparty/sqlite + #flag windows -L@VEXEROOT/thirdparty/sqlite + #flag windows @VEXEROOT/thirdparty/sqlite/sqlite3.o +} $else { + #flag -lsqlite3 +} + +#include "sqlite3.h" + +const ( + sqlite_ok = 0 + sqlite_error = 1 + sqlite_row = 100 + sqlite_done = 101 +) + +struct C.sqlite3 { +} + +struct C.sqlite3_stmt { +} + +struct Stmt { + stmt &C.sqlite3_stmt + db &DB +} + +struct SQLError { + msg string + code int +} + +// +pub struct DB { +pub mut: + is_open bool +mut: + conn &C.sqlite3 +} + +pub fn (db DB) str() string { + return 'sqlite.DB{ conn: ' + ptr_str(db.conn) + ' }' +} + +pub struct Row { +pub mut: + vals []string +} + +// +fn C.sqlite3_open(&char, &&C.sqlite3) int + +fn C.sqlite3_close(&C.sqlite3) int + +fn C.sqlite3_last_insert_rowid(&C.sqlite3) i64 + +// +fn C.sqlite3_prepare_v2(&C.sqlite3, &char, int, &&C.sqlite3_stmt, &&char) int + +fn C.sqlite3_step(&C.sqlite3_stmt) int + +fn C.sqlite3_finalize(&C.sqlite3_stmt) int + +// +fn C.sqlite3_column_name(&C.sqlite3_stmt, int) &char + +fn C.sqlite3_column_text(&C.sqlite3_stmt, int) &byte + +fn C.sqlite3_column_int(&C.sqlite3_stmt, int) int + +fn C.sqlite3_column_int64(&C.sqlite3_stmt, int) i64 + +fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64 + +fn C.sqlite3_column_count(&C.sqlite3_stmt) int + +// +fn C.sqlite3_errstr(int) &char + +fn C.sqlite3_errmsg(&C.sqlite3) &char + +fn C.sqlite3_free(voidptr) + +// connect Opens the connection with a database. +pub fn connect(path string) ?DB { + db := &C.sqlite3(0) + code := C.sqlite3_open(&char(path.str), &db) + if code != 0 { + return IError(&SQLError{ + msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) } + code: code + }) + } + return DB{ + conn: db + is_open: true + } +} + +// close Closes the DB. +// TODO: For all functions, determine whether the connection is +// closed first, and determine what to do if it is +pub fn (mut db DB) close() ?bool { + code := C.sqlite3_close(db.conn) + if code == 0 { + db.is_open = false + } else { + return IError(&SQLError{ + msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) } + code: code + }) + } + return true // successfully closed +} + +// Only for V ORM +fn get_int_from_stmt(stmt &C.sqlite3_stmt) int { + x := C.sqlite3_step(stmt) + if x != C.SQLITE_OK && x != C.SQLITE_DONE { + C.puts(C.sqlite3_errstr(x)) + } + + res := C.sqlite3_column_int(stmt, 0) + C.sqlite3_finalize(stmt) + return res +} + +// Returns last insert rowid +// https://www.sqlite.org/c3ref/last_insert_rowid.html +pub fn (db DB) last_insert_rowid() i64 { + return C.sqlite3_last_insert_rowid(db.conn) +} + +// Returns a single cell with value int. +pub fn (db DB) q_int(query string) int { + stmt := &C.sqlite3_stmt(0) + C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) + C.sqlite3_step(stmt) + + res := C.sqlite3_column_int(stmt, 0) + C.sqlite3_finalize(stmt) + return res +} + +// Returns a single cell with value string. +pub fn (db DB) q_string(query string) string { + stmt := &C.sqlite3_stmt(0) + defer { + C.sqlite3_finalize(stmt) + } + C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) + C.sqlite3_step(stmt) + + val := unsafe { &byte(C.sqlite3_column_text(stmt, 0)) } + return if val != &byte(0) { unsafe { tos_clone(val) } } else { '' } +} + +// Execute the query on db, return an array of all the results, alongside any result code. +// Result codes: https://www.sqlite.org/rescode.html +pub fn (db DB) exec(query string) ([]Row, int) { + stmt := &C.sqlite3_stmt(0) + C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) + nr_cols := C.sqlite3_column_count(stmt) + mut res := 0 + mut rows := []Row{} + for { + res = C.sqlite3_step(stmt) + // Result Code SQLITE_ROW; Another row is available + if res != 100 { + // C.puts(C.sqlite3_errstr(res)) + break + } + mut row := Row{} + for i in 0 .. nr_cols { + val := unsafe { &byte(C.sqlite3_column_text(stmt, i)) } + if val == &byte(0) { + row.vals << '' + } else { + row.vals << unsafe { tos_clone(val) } + } + } + rows << row + } + C.sqlite3_finalize(stmt) + return rows, res +} + +// Execute a query, handle error code +// Return the first row from the resulting table +pub fn (db DB) exec_one(query string) ?Row { + rows, code := db.exec(query) + if rows.len == 0 { + return IError(&SQLError{ + msg: 'No rows' + code: code + }) + } else if code != 101 { + return IError(&SQLError{ + msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) } + code: code + }) + } + return rows[0] +} + +pub fn (db DB) error_message(code int, query string) IError { + msg := unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) } + return IError(&SQLError{ + msg: '$msg ($code) ($query)' + code: code + }) +} + +// In case you don't expect any result, but still want an error code +// e.g. INSERT INTO ... VALUES (...) +pub fn (db DB) exec_none(query string) int { + _, code := db.exec(query) + return code +} + +/* +TODO +pub fn (db DB) exec_param(query string, param string) []Row { +} +*/ + +pub fn (db DB) create_table(table_name string, columns []string) { + db.exec('create table if not exists $table_name (' + columns.join(',\n') + ')') +} diff --git a/v_windows/v/vlib/sqlite/sqlite_orm_test.v b/v_windows/v/vlib/sqlite/sqlite_orm_test.v new file mode 100644 index 0000000..efbd9fe --- /dev/null +++ b/v_windows/v/vlib/sqlite/sqlite_orm_test.v @@ -0,0 +1,70 @@ +import orm +import sqlite + +fn test_sqlite_orm() { + sdb := sqlite.connect(':memory:') or { panic(err) } + db := orm.Connection(sdb) + db.create('Test', [ + orm.TableField{ + name: 'id' + typ: 7 + attrs: [ + StructAttribute{ + name: 'primary' + }, + StructAttribute{ + name: 'sql' + has_arg: true + kind: .plain + arg: 'serial' + }, + ] + }, + orm.TableField{ + name: 'name' + typ: 18 + attrs: [] + }, + orm.TableField{ + name: 'age' + typ: 8 + }, + ]) or { panic(err) } + + db.insert('Test', orm.QueryData{ + fields: ['name', 'age'] + data: [orm.string_to_primitive('Louis'), orm.i64_to_primitive(100)] + }) or { panic(err) } + + res := db.@select(orm.SelectConfig{ + table: 'Test' + has_where: true + fields: ['id', 'name', 'age'] + types: [7, 18, 8] + }, orm.QueryData{}, orm.QueryData{ + fields: ['name', 'age'] + data: [orm.Primitive('Louis'), i64(100)] + types: [18, 8] + is_and: [true, true] + kinds: [.eq, .eq] + }) or { panic(err) } + + id := res[0][0] + name := res[0][1] + age := res[0][2] + + assert id is int + if id is int { + assert id == 1 + } + + assert name is string + if name is string { + assert name == 'Louis' + } + + assert age is i64 + if age is i64 { + assert age == 100 + } +} diff --git a/v_windows/v/vlib/sqlite/sqlite_test.v b/v_windows/v/vlib/sqlite/sqlite_test.v new file mode 100644 index 0000000..f1e9db3 --- /dev/null +++ b/v_windows/v/vlib/sqlite/sqlite_test.v @@ -0,0 +1,31 @@ +import sqlite + +fn test_sqlite() { + $if !linux { + return + } + mut db := sqlite.connect(':memory:') or { panic(err) } + assert db.is_open + db.exec('drop table if exists users') + db.exec("create table users (id integer primary key, name text default '');") + db.exec("insert into users (name) values ('Sam')") + assert db.last_insert_rowid() == 1 + db.exec("insert into users (name) values ('Peter')") + assert db.last_insert_rowid() == 2 + db.exec("insert into users (name) values ('Kate')") + assert db.last_insert_rowid() == 3 + nr_users := db.q_int('select count(*) from users') + assert nr_users == 3 + name := db.q_string('select name from users where id = 1') + assert name == 'Sam' + users, mut code := db.exec('select * from users') + assert users.len == 3 + assert code == 101 + code = db.exec_none('vacuum') + assert code == 101 + user := db.exec_one('select * from users where id = 3') or { panic(err) } + println(user) + assert user.vals.len == 2 + db.close() or { panic(err) } + assert !db.is_open +} diff --git a/v_windows/v/vlib/sqlite/stmt.v b/v_windows/v/vlib/sqlite/stmt.v new file mode 100644 index 0000000..da07e82 --- /dev/null +++ b/v_windows/v/vlib/sqlite/stmt.v @@ -0,0 +1,74 @@ +module sqlite + +fn C.sqlite3_bind_double(&C.sqlite3_stmt, int, f64) int +fn C.sqlite3_bind_int(&C.sqlite3_stmt, int, int) int +fn C.sqlite3_bind_int64(&C.sqlite3_stmt, int, i64) int +fn C.sqlite3_bind_text(&C.sqlite3_stmt, int, &char, int, voidptr) int + +// Only for V ORM +fn (db DB) init_stmt(query string) (&C.sqlite3_stmt, int) { + // println('init_stmt("$query")') + stmt := &C.sqlite3_stmt(0) + err := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0) + return stmt, err +} + +fn (db DB) new_init_stmt(query string) ?Stmt { + stmt, err := db.init_stmt(query) + if err != sqlite_ok { + return db.error_message(err, query) + } + return Stmt{stmt, unsafe { &db }} +} + +fn (stmt Stmt) bind_int(idx int, v int) int { + return C.sqlite3_bind_int(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_i64(idx int, v i64) int { + return C.sqlite3_bind_int64(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_f64(idx int, v f64) int { + return C.sqlite3_bind_double(stmt.stmt, idx, v) +} + +fn (stmt Stmt) bind_text(idx int, s string) int { + return C.sqlite3_bind_text(stmt.stmt, idx, voidptr(s.str), s.len, 0) +} + +fn (stmt Stmt) get_int(idx int) int { + return C.sqlite3_column_int(stmt.stmt, idx) +} + +fn (stmt Stmt) get_i64(idx int) i64 { + return C.sqlite3_column_int64(stmt.stmt, idx) +} + +fn (stmt Stmt) get_f64(idx int) f64 { + return C.sqlite3_column_double(stmt.stmt, idx) +} + +fn (stmt Stmt) get_text(idx int) string { + b := &char(C.sqlite3_column_text(stmt.stmt, idx)) + return unsafe { b.vstring() } +} + +fn (stmt Stmt) get_count() int { + return C.sqlite3_column_count(stmt.stmt) +} + +fn (stmt Stmt) step() int { + return C.sqlite3_step(stmt.stmt) +} + +fn (stmt Stmt) orm_step(query string) ? { + res := stmt.step() + if res != sqlite_ok && res != sqlite_done && res != sqlite_row { + return stmt.db.error_message(res, query) + } +} + +fn (stmt Stmt) finalize() { + C.sqlite3_finalize(stmt.stmt) +} diff --git a/v_windows/v/vlib/stbi/stbi.c.v b/v_windows/v/vlib/stbi/stbi.c.v new file mode 100644 index 0000000..da72dca --- /dev/null +++ b/v_windows/v/vlib/stbi/stbi.c.v @@ -0,0 +1,87 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module stbi + +#flag -I @VEXEROOT/thirdparty/stb_image +#include "stb_image.h" +#flag @VEXEROOT/thirdparty/stb_image/stbi.o + +pub struct Image { +pub mut: + width int + height int + nr_channels int + ok bool + data voidptr + ext string +} + +fn C.stbi_load(filename &char, x &int, y &int, channels_in_file &int, desired_channels int) &byte + +fn C.stbi_load_from_file(f voidptr, x &int, y &int, channels_in_file &int, desired_channels int) &byte + +fn C.stbi_load_from_memory(buffer &byte, len int, x &int, y &int, channels_in_file &int, desired_channels int) &byte + +fn C.stbi_image_free(retval_from_stbi_load &byte) + +fn C.stbi_set_flip_vertically_on_load(should_flip int) + +fn init() { + set_flip_vertically_on_load(false) +} + +pub fn load(path string) ?Image { + ext := path.all_after_last('.') + mut res := Image{ + ok: true + ext: ext + data: 0 + } + // flag := if ext == 'png' { C.STBI_rgb_alpha } else { 0 } + desired_channels := if ext == 'png' { 4 } else { 0 } + res.data = C.stbi_load(&char(path.str), &res.width, &res.height, &res.nr_channels, + desired_channels) + if desired_channels == 4 && res.nr_channels == 3 { + // Fix an alpha png bug + res.nr_channels = 4 + } + if isnil(res.data) { + return error('stbi image failed to load from "$path"') + } + return res +} + +pub fn load_from_memory(buf &byte, bufsize int) ?Image { + mut res := Image{ + ok: true + data: 0 + } + flag := C.STBI_rgb_alpha + res.data = C.stbi_load_from_memory(buf, bufsize, &res.width, &res.height, &res.nr_channels, + flag) + if isnil(res.data) { + return error('stbi image failed to load from memory') + } + return res +} + +pub fn (img &Image) free() { + C.stbi_image_free(img.data) +} + +/* +pub fn (img Image) tex_image_2d() { + mut rgb_flag := C.GL_RGB + if img.ext == 'png' { + rgb_flag = C.GL_RGBA + } + C.glTexImage2D(C.GL_TEXTURE_2D, 0, rgb_flag, img.width, img.height, 0, + rgb_flag, C.GL_UNSIGNED_BYTE, img.data) +} +*/ + +pub fn set_flip_vertically_on_load(val bool) { + C.stbi_set_flip_vertically_on_load(val) +} diff --git a/v_windows/v/vlib/strconv/atof.v b/v_windows/v/vlib/strconv/atof.v new file mode 100644 index 0000000..dd994bd --- /dev/null +++ b/v_windows/v/vlib/strconv/atof.v @@ -0,0 +1,441 @@ +module strconv + +/* +atof util + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains utilities for convert a string in a f64 variable +IEEE 754 standard is used + +Know limitation: +- limited to 18 significant digits + +The code is inspired by: +Grzegorz Kraszewski krashan@teleinfo.pb.edu.pl +URL: http://krashan.ppa.pl/articles/stringtofloat/ +Original license: MIT + +96 bit operation utilities +Note: when u128 will be available these function can be refactored +*/ + +// right logical shift 96 bit +fn lsr96(s2 u32, s1 u32, s0 u32) (u32, u32, u32) { + mut r0 := u32(0) + mut r1 := u32(0) + mut r2 := u32(0) + r0 = (s0 >> 1) | ((s1 & u32(1)) << 31) + r1 = (s1 >> 1) | ((s2 & u32(1)) << 31) + r2 = s2 >> 1 + return r2, r1, r0 +} + +// left logical shift 96 bit +fn lsl96(s2 u32, s1 u32, s0 u32) (u32, u32, u32) { + mut r0 := u32(0) + mut r1 := u32(0) + mut r2 := u32(0) + r2 = (s2 << 1) | ((s1 & (u32(1) << 31)) >> 31) + r1 = (s1 << 1) | ((s0 & (u32(1) << 31)) >> 31) + r0 = s0 << 1 + return r2, r1, r0 +} + +// sum on 96 bit +fn add96(s2 u32, s1 u32, s0 u32, d2 u32, d1 u32, d0 u32) (u32, u32, u32) { + mut w := u64(0) + mut r0 := u32(0) + mut r1 := u32(0) + mut r2 := u32(0) + w = u64(s0) + u64(d0) + r0 = u32(w) + w >>= 32 + w += u64(s1) + u64(d1) + r1 = u32(w) + w >>= 32 + w += u64(s2) + u64(d2) + r2 = u32(w) + return r2, r1, r0 +} + +// subtraction on 96 bit +fn sub96(s2 u32, s1 u32, s0 u32, d2 u32, d1 u32, d0 u32) (u32, u32, u32) { + mut w := u64(0) + mut r0 := u32(0) + mut r1 := u32(0) + mut r2 := u32(0) + w = u64(s0) - u64(d0) + r0 = u32(w) + w >>= 32 + w += u64(s1) - u64(d1) + r1 = u32(w) + w >>= 32 + w += u64(s2) - u64(d2) + r2 = u32(w) + return r2, r1, r0 +} + +/* +Constants +*/ + +pub const ( + // + // f32 constants + // + single_plus_zero = u32(0x0000_0000) + single_minus_zero = u32(0x8000_0000) + single_plus_infinity = u32(0x7F80_0000) + single_minus_infinity = u32(0xFF80_0000) + // + // f64 constants + // + digits = 18 + double_plus_zero = u64(0x0000000000000000) + double_minus_zero = u64(0x8000000000000000) + double_plus_infinity = u64(0x7FF0000000000000) + double_minus_infinity = u64(0xFFF0000000000000) + // + // Possible parser return values. + // + parser_ok = 0 // parser finished OK + parser_pzero = 1 // no digits or number is smaller than +-2^-1022 + parser_mzero = 2 // number is negative, module smaller + parser_pinf = 3 // number is higher than +HUGE_VAL + parser_minf = 4 // number is lower than -HUGE_VAL + // + // char constants + // Note: Modify these if working with non-ASCII encoding + // + c_dpoint = `.` + c_plus = `+` + c_minus = `-` + c_zero = `0` + c_nine = `9` + c_ten = u32(10) +) + +/* +Utility +*/ + +// NOTE: Modify these if working with non-ASCII encoding +fn is_digit(x byte) bool { + return (x >= strconv.c_zero && x <= strconv.c_nine) == true +} + +fn is_space(x byte) bool { + return (x == `\t` || x == `\n` || x == `\v` || x == `\f` || x == `\r` || x == ` `) +} + +fn is_exp(x byte) bool { + return (x == `E` || x == `e`) == true +} + +/* +Support struct +*/ + +/* +String parser +NOTE: #TOFIX need one char after the last char of the number +*/ + +fn parser(s string) (int, PrepNumber) { + mut digx := 0 + mut result := strconv.parser_ok + mut expneg := false + mut expexp := 0 + mut i := 0 + mut pn := PrepNumber{} + + // skip spaces + for i < s.len && s[i].is_space() { + i++ + } + + // check negatives + if s[i] == `-` { + pn.negative = true + i++ + } + + // positive sign ignore it + if s[i] == `+` { + i++ + } + + // read mantissa + for i < s.len && s[i].is_digit() { + // println("$i => ${s[i]}") + if digx < strconv.digits { + pn.mantissa *= 10 + pn.mantissa += u64(s[i] - strconv.c_zero) + digx++ + } else if pn.exponent < 2147483647 { + pn.exponent++ + } + i++ + } + + // read mantissa decimals + if (i < s.len) && (s[i] == `.`) { + i++ + for i < s.len && s[i].is_digit() { + if digx < strconv.digits { + pn.mantissa *= 10 + pn.mantissa += u64(s[i] - strconv.c_zero) + pn.exponent-- + digx++ + } + i++ + } + } + + // read exponent + if (i < s.len) && ((s[i] == `e`) || (s[i] == `E`)) { + i++ + if i < s.len { + // esponent sign + if s[i] == strconv.c_plus { + i++ + } else if s[i] == strconv.c_minus { + expneg = true + i++ + } + + for i < s.len && s[i].is_digit() { + if expexp < 214748364 { + expexp *= 10 + expexp += int(s[i] - strconv.c_zero) + } + i++ + } + } + } + + if expneg { + expexp = -expexp + } + pn.exponent += expexp + if pn.mantissa == 0 { + if pn.negative { + result = strconv.parser_mzero + } else { + result = strconv.parser_pzero + } + } else if pn.exponent > 309 { + if pn.negative { + result = strconv.parser_minf + } else { + result = strconv.parser_pinf + } + } else if pn.exponent < -328 { + if pn.negative { + result = strconv.parser_mzero + } else { + result = strconv.parser_pzero + } + } + return result, pn +} + +/* +Converter to the bit form of the f64 number +*/ + +// converter return a u64 with the bit image of the f64 number +fn converter(mut pn PrepNumber) u64 { + mut binexp := 92 + mut s2 := u32(0) // 96-bit precision integer + mut s1 := u32(0) + mut s0 := u32(0) + mut q2 := u32(0) // 96-bit precision integer + mut q1 := u32(0) + mut q0 := u32(0) + mut r2 := u32(0) // 96-bit precision integer + mut r1 := u32(0) + mut r0 := u32(0) + mask28 := u32(u64(0xF) << 28) + mut result := u64(0) + // working on 3 u32 to have 96 bit precision + s0 = u32(pn.mantissa & u64(0x00000000FFFFFFFF)) + s1 = u32(pn.mantissa >> 32) + s2 = u32(0) + // so we take the decimal exponent off + for pn.exponent > 0 { + q2, q1, q0 = lsl96(s2, s1, s0) // q = s * 2 + r2, r1, r0 = lsl96(q2, q1, q0) // r = s * 4 <=> q * 2 + s2, s1, s0 = lsl96(r2, r1, r0) // s = s * 8 <=> r * 2 + s2, s1, s0 = add96(s2, s1, s0, q2, q1, q0) // s = (s * 8) + (s * 2) <=> s*10 + pn.exponent-- + for (s2 & mask28) != 0 { + q2, q1, q0 = lsr96(s2, s1, s0) + binexp++ + s2 = q2 + s1 = q1 + s0 = q0 + } + } + for pn.exponent < 0 { + for !((s2 & (u32(1) << 31)) != 0) { + q2, q1, q0 = lsl96(s2, s1, s0) + binexp-- + s2 = q2 + s1 = q1 + s0 = q0 + } + q2 = s2 / strconv.c_ten + r1 = s2 % strconv.c_ten + r2 = (s1 >> 8) | (r1 << 24) + q1 = r2 / strconv.c_ten + r1 = r2 % strconv.c_ten + r2 = ((s1 & u32(0xFF)) << 16) | (s0 >> 16) | (r1 << 24) + r0 = r2 / strconv.c_ten + r1 = r2 % strconv.c_ten + q1 = (q1 << 8) | ((r0 & u32(0x00FF0000)) >> 16) + q0 = r0 << 16 + r2 = (s0 & u32(0xFFFF)) | (r1 << 16) + q0 |= r2 / strconv.c_ten + s2 = q2 + s1 = q1 + s0 = q0 + pn.exponent++ + } + // C.printf("mantissa before normalization: %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp) + // normalization, the 28 bit in s2 must the leftest one in the variable + if s2 != 0 || s1 != 0 || s0 != 0 { + for (s2 & mask28) == 0 { + q2, q1, q0 = lsl96(s2, s1, s0) + binexp-- + s2 = q2 + s1 = q1 + s0 = q0 + } + } + // rounding if needed + /* + * "round half to even" algorithm + * Example for f32, just a reminder + * + * If bit 54 is 0, round down + * If bit 54 is 1 + * If any bit beyond bit 54 is 1, round up + * If all bits beyond bit 54 are 0 (meaning the number is halfway between two floating-point numbers) + * If bit 53 is 0, round down + * If bit 53 is 1, round up + */ + /* + test case 1 complete + s2=0x1FFFFFFF + s1=0xFFFFFF80 + s0=0x0 + */ + + /* + test case 1 check_round_bit + s2=0x18888888 + s1=0x88888880 + s0=0x0 + */ + + /* + test case check_round_bit + normalization + s2=0x18888888 + s1=0x88888F80 + s0=0x0 + */ + + // C.printf("mantissa before rounding: %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp) + // s1 => 0xFFFFFFxx only F are rapresented + nbit := 7 + check_round_bit := u32(1) << u32(nbit) + check_round_mask := u32(0xFFFFFFFF) << u32(nbit) + if (s1 & check_round_bit) != 0 { + // C.printf("need round!! cehck mask: %08x\n", s1 & ~check_round_mask ) + if (s1 & ~check_round_mask) != 0 { + // C.printf("Add 1!\n") + s2, s1, s0 = add96(s2, s1, s0, 0, check_round_bit, 0) + } else { + // C.printf("All 0!\n") + if (s1 & (check_round_bit << u32(1))) != 0 { + // C.printf("Add 1 form -1 bit control!\n") + s2, s1, s0 = add96(s2, s1, s0, 0, check_round_bit, 0) + } + } + s1 = s1 & check_round_mask + s0 = u32(0) + // recheck normalization + if s2 & (mask28 << u32(1)) != 0 { + // C.printf("Renormalize!!") + q2, q1, q0 = lsr96(s2, s1, s0) + binexp-- + s2 = q2 + s1 = q1 + s0 = q0 + } + } + // tmp := ( u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8) + // C.printf("mantissa after rounding : %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp) + // C.printf("Tmp result: %016x\n",tmp) + // end rounding + // offset the binary exponent IEEE 754 + binexp += 1023 + if binexp > 2046 { + if pn.negative { + result = strconv.double_minus_infinity + } else { + result = strconv.double_plus_infinity + } + } else if binexp < 1 { + if pn.negative { + result = strconv.double_minus_zero + } else { + result = strconv.double_plus_zero + } + } else if s2 != 0 { + mut q := u64(0) + binexs2 := u64(binexp) << 52 + q = (u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8) | binexs2 + if pn.negative { + q |= (u64(1) << 63) + } + result = q + } + return result +} + +/* +Public functions +*/ + +// atof64 return a f64 from a string doing a parsing operation +pub fn atof64(s string) f64 { + mut pn := PrepNumber{} + mut res_parsing := 0 + mut res := Float64u{} + + res_parsing, pn = parser(s) + match res_parsing { + strconv.parser_ok { + res.u = converter(mut pn) + } + strconv.parser_pzero { + res.u = strconv.double_plus_zero + } + strconv.parser_mzero { + res.u = strconv.double_minus_zero + } + strconv.parser_pinf { + res.u = strconv.double_plus_infinity + } + strconv.parser_minf { + res.u = strconv.double_minus_infinity + } + else {} + } + return unsafe { res.f } +} diff --git a/v_windows/v/vlib/strconv/atof_test.v b/v_windows/v/vlib/strconv/atof_test.v new file mode 100644 index 0000000..ca286c8 --- /dev/null +++ b/v_windows/v/vlib/strconv/atof_test.v @@ -0,0 +1,75 @@ +import strconv + +/********************************************************************** +* +* String to float Test +* +**********************************************************************/ + +fn test_atof() { + // + // test set + // + + // float64 + src_num := [ + f64(0.3), + -0.3, + 0.004, + -0.004, + 0.0, + -0.0, + 31234567890123, + ] + + // strings + src_num_str := [ + '0.3', + '-0.3', + '0.004', + '-0.004', + '0.0', + '-0.0', + '31234567890123', + ] + + // check conversion case 1 string <=> string + for c, x in src_num { + // slow atof + assert strconv.atof64(src_num_str[c]).strlong() == x.strlong() + + // quick atof + mut s1 := (strconv.atof_quick(src_num_str[c]).str()) + mut s2 := (x.str()) + delta := s1.f64() - s2.f64() + // println("$s1 $s2 $delta") + assert delta < f64(1e-16) + + // test C.atof + n1 := x.strsci(18) + n2 := f64(C.atof(&char(src_num_str[c].str))).strsci(18) + // println("$n1 $n2") + assert n1 == n2 + } + + // check conversion case 2 string <==> f64 + // we don't test atof_quick beacuse we already know the rounding error + for c, x in src_num_str { + b := src_num[c].strlong() + a1 := strconv.atof64(x).strlong() + assert a1 == b + } + + // special cases + mut f1 := f64(0.0) + mut ptr := unsafe { &u64(&f1) } + ptr = unsafe { &u64(&f1) } + + // double_plus_zero + f1 = 0.0 + assert *ptr == u64(0x0000000000000000) + // double_minus_zero + f1 = -0.0 + assert *ptr == u64(0x8000000000000000) + println('DONE!') +} diff --git a/v_windows/v/vlib/strconv/atofq.v b/v_windows/v/vlib/strconv/atofq.v new file mode 100644 index 0000000..07bb7b3 --- /dev/null +++ b/v_windows/v/vlib/strconv/atofq.v @@ -0,0 +1,348 @@ +module strconv + +/* +atof util + +Copyright (c) 2019 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains utilities for convert a string in a f64 variable in a very quick way +IEEE 754 standard is used + +Know limitation: +- round to 0 approximation +- loos of precision with big exponents +*/ + +// atof_quick return a f64 number from a string in a quick way +pub fn atof_quick(s string) f64 { + mut f := Float64u{} // result + mut sign := f64(1.0) // result sign + mut i := 0 // index + // skip white spaces + for i < s.len && s[i] == ` ` { + i++ + } + // check sign + if i < s.len { + if s[i] == `-` { + sign = -1.0 + i++ + } else if s[i] == `+` { + i++ + } + } + // infinite + if s[i] == `i` && i + 2 < s.len && s[i + 1] == `n` && s[i + 2] == `f` { + if sign > 0.0 { + f.u = double_plus_infinity + } else { + f.u = double_minus_infinity + } + return unsafe { f.f } + } + // skip zeros + for i < s.len && s[i] == `0` { + i++ + // we have a zero, manage it + if i >= s.len { + if sign > 0.0 { + f.u = double_plus_zero + } else { + f.u = double_minus_zero + } + return unsafe { f.f } + } + } + // integer part + for i < s.len && (s[i] >= `0` && s[i] <= `9`) { + f.f *= f64(10.0) + f.f += f64(s[i] - `0`) + i++ + } + // decimal point + if i < s.len && s[i] == `.` { + i++ + mut frac_mul := f64(0.1) + for i < s.len && (s[i] >= `0` && s[i] <= `9`) { + f.f += f64(s[i] - `0`) * frac_mul + frac_mul *= f64(0.1) + i++ + } + } + // exponent management + if i < s.len && (s[i] == `e` || s[i] == `E`) { + i++ + mut exp := 0 + mut exp_sign := 1 + // negative exponent + if i < s.len { + if s[i] == `-` { + exp_sign = -1 + i++ + } else if s[i] == `+` { + i++ + } + } + // skip zeros + for i < s.len && s[i] == `0` { + i++ + } + for i < s.len && (s[i] >= `0` && s[i] <= `9`) { + exp *= 10 + exp += int(s[i] - `0`) + i++ + } + if exp_sign == 1 { + if exp > strconv.pos_exp.len { + if sign > 0 { + f.u = double_plus_infinity + } else { + f.u = double_minus_infinity + } + return unsafe { f.f } + } + tmp_mul := Float64u{ + u: strconv.pos_exp[exp] + } + // C.printf("exp: %d [0x%016llx] %f,",exp,pos_exp[exp],tmp_mul) + f.f = unsafe { f.f * tmp_mul.f } + } else { + if exp > strconv.neg_exp.len { + if sign > 0 { + f.u = double_plus_zero + } else { + f.u = double_minus_zero + } + return unsafe { f.f } + } + tmp_mul := Float64u{ + u: strconv.neg_exp[exp] + } + + // C.printf("exp: %d [0x%016llx] %f,",exp,pos_exp[exp],tmp_mul) + f.f = unsafe { f.f * tmp_mul.f } + } + } + unsafe { + f.f = f.f * sign + return f.f + } +} + +const ( + // positive exp of 10 binary form + pos_exp = [u64(0x3ff0000000000000), u64(0x4024000000000000), u64(0x4059000000000000), + u64(0x408f400000000000), u64(0x40c3880000000000), u64(0x40f86a0000000000), + u64(0x412e848000000000), u64(0x416312d000000000), u64(0x4197d78400000000), + u64(0x41cdcd6500000000), u64(0x4202a05f20000000), u64(0x42374876e8000000), + u64(0x426d1a94a2000000), u64(0x42a2309ce5400000), u64(0x42d6bcc41e900000), + u64(0x430c6bf526340000), u64(0x4341c37937e08000), u64(0x4376345785d8a000), + u64(0x43abc16d674ec800), u64(0x43e158e460913d00), u64(0x4415af1d78b58c40), + u64(0x444b1ae4d6e2ef50), u64(0x4480f0cf064dd592), u64(0x44b52d02c7e14af6), + u64(0x44ea784379d99db4), u64(0x45208b2a2c280291), u64(0x4554adf4b7320335), + u64(0x4589d971e4fe8402), u64(0x45c027e72f1f1281), u64(0x45f431e0fae6d721), + u64(0x46293e5939a08cea), u64(0x465f8def8808b024), u64(0x4693b8b5b5056e17), + u64(0x46c8a6e32246c99c), u64(0x46fed09bead87c03), u64(0x4733426172c74d82), + u64(0x476812f9cf7920e3), u64(0x479e17b84357691b), u64(0x47d2ced32a16a1b1), + u64(0x48078287f49c4a1d), u64(0x483d6329f1c35ca5), u64(0x48725dfa371a19e7), + u64(0x48a6f578c4e0a061), u64(0x48dcb2d6f618c879), u64(0x4911efc659cf7d4c), + u64(0x49466bb7f0435c9e), u64(0x497c06a5ec5433c6), u64(0x49b18427b3b4a05c), + u64(0x49e5e531a0a1c873), u64(0x4a1b5e7e08ca3a8f), u64(0x4a511b0ec57e649a), + u64(0x4a8561d276ddfdc0), u64(0x4ababa4714957d30), u64(0x4af0b46c6cdd6e3e), + u64(0x4b24e1878814c9ce), u64(0x4b5a19e96a19fc41), u64(0x4b905031e2503da9), + u64(0x4bc4643e5ae44d13), u64(0x4bf97d4df19d6057), u64(0x4c2fdca16e04b86d), + u64(0x4c63e9e4e4c2f344), u64(0x4c98e45e1df3b015), u64(0x4ccf1d75a5709c1b), + u64(0x4d03726987666191), u64(0x4d384f03e93ff9f5), u64(0x4d6e62c4e38ff872), + u64(0x4da2fdbb0e39fb47), u64(0x4dd7bd29d1c87a19), u64(0x4e0dac74463a989f), + u64(0x4e428bc8abe49f64), u64(0x4e772ebad6ddc73d), u64(0x4eacfa698c95390c), + u64(0x4ee21c81f7dd43a7), u64(0x4f16a3a275d49491), u64(0x4f4c4c8b1349b9b5), + u64(0x4f81afd6ec0e1411), u64(0x4fb61bcca7119916), u64(0x4feba2bfd0d5ff5b), + u64(0x502145b7e285bf99), u64(0x50559725db272f7f), u64(0x508afcef51f0fb5f), + u64(0x50c0de1593369d1b), u64(0x50f5159af8044462), u64(0x512a5b01b605557b), + u64(0x516078e111c3556d), u64(0x5194971956342ac8), u64(0x51c9bcdfabc1357a), + u64(0x5200160bcb58c16c), u64(0x52341b8ebe2ef1c7), u64(0x526922726dbaae39), + u64(0x529f6b0f092959c7), u64(0x52d3a2e965b9d81d), u64(0x53088ba3bf284e24), + u64(0x533eae8caef261ad), u64(0x53732d17ed577d0c), u64(0x53a7f85de8ad5c4f), + u64(0x53ddf67562d8b363), u64(0x5412ba095dc7701e), u64(0x5447688bb5394c25), + u64(0x547d42aea2879f2e), u64(0x54b249ad2594c37d), u64(0x54e6dc186ef9f45c), + u64(0x551c931e8ab87173), u64(0x5551dbf316b346e8), u64(0x558652efdc6018a2), + u64(0x55bbe7abd3781eca), u64(0x55f170cb642b133f), u64(0x5625ccfe3d35d80e), + u64(0x565b403dcc834e12), u64(0x569108269fd210cb), u64(0x56c54a3047c694fe), + u64(0x56fa9cbc59b83a3d), u64(0x5730a1f5b8132466), u64(0x5764ca732617ed80), + u64(0x5799fd0fef9de8e0), u64(0x57d03e29f5c2b18c), u64(0x58044db473335def), + u64(0x583961219000356b), u64(0x586fb969f40042c5), u64(0x58a3d3e2388029bb), + u64(0x58d8c8dac6a0342a), u64(0x590efb1178484135), u64(0x59435ceaeb2d28c1), + u64(0x59783425a5f872f1), u64(0x59ae412f0f768fad), u64(0x59e2e8bd69aa19cc), + u64(0x5a17a2ecc414a03f), u64(0x5a4d8ba7f519c84f), u64(0x5a827748f9301d32), + u64(0x5ab7151b377c247e), u64(0x5aecda62055b2d9e), u64(0x5b22087d4358fc82), + u64(0x5b568a9c942f3ba3), u64(0x5b8c2d43b93b0a8c), u64(0x5bc19c4a53c4e697), + u64(0x5bf6035ce8b6203d), u64(0x5c2b843422e3a84d), u64(0x5c6132a095ce4930), + u64(0x5c957f48bb41db7c), u64(0x5ccadf1aea12525b), u64(0x5d00cb70d24b7379), + u64(0x5d34fe4d06de5057), u64(0x5d6a3de04895e46d), u64(0x5da066ac2d5daec4), + u64(0x5dd4805738b51a75), u64(0x5e09a06d06e26112), u64(0x5e400444244d7cab), + u64(0x5e7405552d60dbd6), u64(0x5ea906aa78b912cc), u64(0x5edf485516e7577f), + u64(0x5f138d352e5096af), u64(0x5f48708279e4bc5b), u64(0x5f7e8ca3185deb72), + u64(0x5fb317e5ef3ab327), u64(0x5fe7dddf6b095ff1), u64(0x601dd55745cbb7ed), + u64(0x6052a5568b9f52f4), u64(0x60874eac2e8727b1), u64(0x60bd22573a28f19d), + u64(0x60f2357684599702), u64(0x6126c2d4256ffcc3), u64(0x615c73892ecbfbf4), + u64(0x6191c835bd3f7d78), u64(0x61c63a432c8f5cd6), u64(0x61fbc8d3f7b3340c), + u64(0x62315d847ad00087), u64(0x6265b4e5998400a9), u64(0x629b221effe500d4), + u64(0x62d0f5535fef2084), u64(0x630532a837eae8a5), u64(0x633a7f5245e5a2cf), + u64(0x63708f936baf85c1), u64(0x63a4b378469b6732), u64(0x63d9e056584240fe), + u64(0x64102c35f729689f), u64(0x6444374374f3c2c6), u64(0x647945145230b378), + u64(0x64af965966bce056), u64(0x64e3bdf7e0360c36), u64(0x6518ad75d8438f43), + u64(0x654ed8d34e547314), u64(0x6583478410f4c7ec), u64(0x65b819651531f9e8), + u64(0x65ee1fbe5a7e7861), u64(0x6622d3d6f88f0b3d), u64(0x665788ccb6b2ce0c), + u64(0x668d6affe45f818f), u64(0x66c262dfeebbb0f9), u64(0x66f6fb97ea6a9d38), + u64(0x672cba7de5054486), u64(0x6761f48eaf234ad4), u64(0x679671b25aec1d89), + u64(0x67cc0e1ef1a724eb), u64(0x680188d357087713), u64(0x6835eb082cca94d7), + u64(0x686b65ca37fd3a0d), u64(0x68a11f9e62fe4448), u64(0x68d56785fbbdd55a), + u64(0x690ac1677aad4ab1), u64(0x6940b8e0acac4eaf), u64(0x6974e718d7d7625a), + u64(0x69aa20df0dcd3af1), u64(0x69e0548b68a044d6), u64(0x6a1469ae42c8560c), + u64(0x6a498419d37a6b8f), u64(0x6a7fe52048590673), u64(0x6ab3ef342d37a408), + u64(0x6ae8eb0138858d0a), u64(0x6b1f25c186a6f04c), u64(0x6b537798f4285630), + u64(0x6b88557f31326bbb), u64(0x6bbe6adefd7f06aa), u64(0x6bf302cb5e6f642a), + u64(0x6c27c37e360b3d35), u64(0x6c5db45dc38e0c82), u64(0x6c9290ba9a38c7d1), + u64(0x6cc734e940c6f9c6), u64(0x6cfd022390f8b837), u64(0x6d3221563a9b7323), + u64(0x6d66a9abc9424feb), u64(0x6d9c5416bb92e3e6), u64(0x6dd1b48e353bce70), + u64(0x6e0621b1c28ac20c), u64(0x6e3baa1e332d728f), u64(0x6e714a52dffc6799), + u64(0x6ea59ce797fb817f), u64(0x6edb04217dfa61df), u64(0x6f10e294eebc7d2c), + u64(0x6f451b3a2a6b9c76), u64(0x6f7a6208b5068394), u64(0x6fb07d457124123d), + u64(0x6fe49c96cd6d16cc), u64(0x7019c3bc80c85c7f), u64(0x70501a55d07d39cf), + u64(0x708420eb449c8843), u64(0x70b9292615c3aa54), u64(0x70ef736f9b3494e9), + u64(0x7123a825c100dd11), u64(0x7158922f31411456), u64(0x718eb6bafd91596b), + u64(0x71c33234de7ad7e3), u64(0x71f7fec216198ddc), u64(0x722dfe729b9ff153), + u64(0x7262bf07a143f6d4), u64(0x72976ec98994f489), u64(0x72cd4a7bebfa31ab), + u64(0x73024e8d737c5f0b), u64(0x7336e230d05b76cd), u64(0x736c9abd04725481), + u64(0x73a1e0b622c774d0), u64(0x73d658e3ab795204), u64(0x740bef1c9657a686), + u64(0x74417571ddf6c814), u64(0x7475d2ce55747a18), u64(0x74ab4781ead1989e), + u64(0x74e10cb132c2ff63), u64(0x75154fdd7f73bf3c), u64(0x754aa3d4df50af0b), + u64(0x7580a6650b926d67), u64(0x75b4cffe4e7708c0), u64(0x75ea03fde214caf1), + u64(0x7620427ead4cfed6), u64(0x7654531e58a03e8c), u64(0x768967e5eec84e2f), + u64(0x76bfc1df6a7a61bb), u64(0x76f3d92ba28c7d15), u64(0x7728cf768b2f9c5a), + u64(0x775f03542dfb8370), u64(0x779362149cbd3226), u64(0x77c83a99c3ec7eb0), + u64(0x77fe494034e79e5c), u64(0x7832edc82110c2f9), u64(0x7867a93a2954f3b8), + u64(0x789d9388b3aa30a5), u64(0x78d27c35704a5e67), u64(0x79071b42cc5cf601), + u64(0x793ce2137f743382), u64(0x79720d4c2fa8a031), u64(0x79a6909f3b92c83d), + u64(0x79dc34c70a777a4d), u64(0x7a11a0fc668aac70), u64(0x7a46093b802d578c), + u64(0x7a7b8b8a6038ad6f), u64(0x7ab137367c236c65), u64(0x7ae585041b2c477f), + u64(0x7b1ae64521f7595e), u64(0x7b50cfeb353a97db), u64(0x7b8503e602893dd2), + u64(0x7bba44df832b8d46), u64(0x7bf06b0bb1fb384c), u64(0x7c2485ce9e7a065f), + u64(0x7c59a742461887f6), u64(0x7c9008896bcf54fa), u64(0x7cc40aabc6c32a38), + u64(0x7cf90d56b873f4c7), u64(0x7d2f50ac6690f1f8), u64(0x7d63926bc01a973b), + u64(0x7d987706b0213d0a), u64(0x7dce94c85c298c4c), u64(0x7e031cfd3999f7b0), + u64(0x7e37e43c8800759c), u64(0x7e6ddd4baa009303), u64(0x7ea2aa4f4a405be2), + u64(0x7ed754e31cd072da), u64(0x7f0d2a1be4048f90), u64(0x7f423a516e82d9ba), + u64(0x7f76c8e5ca239029), u64(0x7fac7b1f3cac7433), u64(0x7fe1ccf385ebc8a0)] + // negative exp of 10 binary form + neg_exp = [u64(0x3ff0000000000000), u64(0x3fb999999999999a), u64(0x3f847ae147ae147b), + u64(0x3f50624dd2f1a9fc), u64(0x3f1a36e2eb1c432d), u64(0x3ee4f8b588e368f1), + u64(0x3eb0c6f7a0b5ed8d), u64(0x3e7ad7f29abcaf48), u64(0x3e45798ee2308c3a), + u64(0x3e112e0be826d695), u64(0x3ddb7cdfd9d7bdbb), u64(0x3da5fd7fe1796495), + u64(0x3d719799812dea11), u64(0x3d3c25c268497682), u64(0x3d06849b86a12b9b), + u64(0x3cd203af9ee75616), u64(0x3c9cd2b297d889bc), u64(0x3c670ef54646d497), + u64(0x3c32725dd1d243ac), u64(0x3bfd83c94fb6d2ac), u64(0x3bc79ca10c924223), + u64(0x3b92e3b40a0e9b4f), u64(0x3b5e392010175ee6), u64(0x3b282db34012b251), + u64(0x3af357c299a88ea7), u64(0x3abef2d0f5da7dd9), u64(0x3a88c240c4aecb14), + u64(0x3a53ce9a36f23c10), u64(0x3a1fb0f6be506019), u64(0x39e95a5efea6b347), + u64(0x39b4484bfeebc2a0), u64(0x398039d665896880), u64(0x3949f623d5a8a733), + u64(0x3914c4e977ba1f5c), u64(0x38e09d8792fb4c49), u64(0x38aa95a5b7f87a0f), + u64(0x38754484932d2e72), u64(0x3841039d428a8b8f), u64(0x380b38fb9daa78e4), + u64(0x37d5c72fb1552d83), u64(0x37a16c262777579c), u64(0x376be03d0bf225c7), + u64(0x37364cfda3281e39), u64(0x3701d7314f534b61), u64(0x36cc8b8218854567), + u64(0x3696d601ad376ab9), u64(0x366244ce242c5561), u64(0x362d3ae36d13bbce), + u64(0x35f7624f8a762fd8), u64(0x35c2b50c6ec4f313), u64(0x358dee7a4ad4b81f), + u64(0x3557f1fb6f10934c), u64(0x352327fc58da0f70), u64(0x34eea6608e29b24d), + u64(0x34b8851a0b548ea4), u64(0x34839dae6f76d883), u64(0x344f62b0b257c0d2), + u64(0x34191bc08eac9a41), u64(0x33e41633a556e1ce), u64(0x33b011c2eaabe7d8), + u64(0x3379b604aaaca626), u64(0x3344919d5556eb52), u64(0x3310747ddddf22a8), + u64(0x32da53fc9631d10d), u64(0x32a50ffd44f4a73d), u64(0x3270d9976a5d5297), + u64(0x323af5bf109550f2), u64(0x32059165a6ddda5b), u64(0x31d1411e1f17e1e3), + u64(0x319b9b6364f30304), u64(0x316615e91d8f359d), u64(0x3131ab20e472914a), + u64(0x30fc45016d841baa), u64(0x30c69d9abe034955), u64(0x309217aefe690777), + u64(0x305cf2b1970e7258), u64(0x3027288e1271f513), u64(0x2ff286d80ec190dc), + u64(0x2fbda48ce468e7c7), u64(0x2f87b6d71d20b96c), u64(0x2f52f8ac174d6123), + u64(0x2f1e5aacf2156838), u64(0x2ee8488a5b445360), u64(0x2eb36d3b7c36a91a), + u64(0x2e7f152bf9f10e90), u64(0x2e48ddbcc7f40ba6), u64(0x2e13e497065cd61f), + u64(0x2ddfd424d6faf031), u64(0x2da97683df2f268d), u64(0x2d745ecfe5bf520b), + u64(0x2d404bd984990e6f), u64(0x2d0a12f5a0f4e3e5), u64(0x2cd4dbf7b3f71cb7), + u64(0x2ca0aff95cc5b092), u64(0x2c6ab328946f80ea), u64(0x2c355c2076bf9a55), + u64(0x2c0116805effaeaa), u64(0x2bcb5733cb32b111), u64(0x2b95df5ca28ef40d), + u64(0x2b617f7d4ed8c33e), u64(0x2b2bff2ee48e0530), u64(0x2af665bf1d3e6a8d), + u64(0x2ac1eaff4a98553d), u64(0x2a8cab3210f3bb95), u64(0x2a56ef5b40c2fc77), + u64(0x2a225915cd68c9f9), u64(0x29ed5b561574765b), u64(0x29b77c44ddf6c516), + u64(0x2982c9d0b1923745), u64(0x294e0fb44f50586e), u64(0x29180c903f7379f2), + u64(0x28e33d4032c2c7f5), u64(0x28aec866b79e0cba), u64(0x2878a0522c7e7095), + u64(0x2843b374f06526de), u64(0x280f8587e7083e30), u64(0x27d9379fec069826), + u64(0x27a42c7ff0054685), u64(0x277023998cd10537), u64(0x2739d28f47b4d525), + u64(0x2704a8729fc3ddb7), u64(0x26d086c219697e2c), u64(0x269a71368f0f3047), + u64(0x2665275ed8d8f36c), u64(0x2630ec4be0ad8f89), u64(0x25fb13ac9aaf4c0f), + u64(0x25c5a956e225d672), u64(0x2591544581b7dec2), u64(0x255bba08cf8c979d), + u64(0x25262e6d72d6dfb0), u64(0x24f1bebdf578b2f4), u64(0x24bc6463225ab7ec), + u64(0x2486b6b5b5155ff0), u64(0x24522bc490dde65a), u64(0x241d12d41afca3c3), + u64(0x23e7424348ca1c9c), u64(0x23b29b69070816e3), u64(0x237dc574d80cf16b), + u64(0x2347d12a4670c123), u64(0x23130dbb6b8d674f), u64(0x22de7c5f127bd87e), + u64(0x22a8637f41fcad32), u64(0x227382cc34ca2428), u64(0x223f37ad21436d0c), + u64(0x2208f9574dcf8a70), u64(0x21d3faac3e3fa1f3), u64(0x219ff779fd329cb9), + u64(0x216992c7fdc216fa), u64(0x2134756ccb01abfb), u64(0x21005df0a267bcc9), + u64(0x20ca2fe76a3f9475), u64(0x2094f31f8832dd2a), u64(0x2060c27fa028b0ef), + u64(0x202ad0cc33744e4b), u64(0x1ff573d68f903ea2), u64(0x1fc1297872d9cbb5), + u64(0x1f8b758d848fac55), u64(0x1f55f7a46a0c89dd), u64(0x1f2192e9ee706e4b), + u64(0x1eec1e43171a4a11), u64(0x1eb67e9c127b6e74), u64(0x1e81fee341fc585d), + u64(0x1e4ccb0536608d61), u64(0x1e1708d0f84d3de7), u64(0x1de26d73f9d764b9), + u64(0x1dad7becc2f23ac2), u64(0x1d779657025b6235), u64(0x1d42deac01e2b4f7), + u64(0x1d0e3113363787f2), u64(0x1cd8274291c6065b), u64(0x1ca3529ba7d19eaf), + u64(0x1c6eea92a61c3118), u64(0x1c38bba884e35a7a), u64(0x1c03c9539d82aec8), + u64(0x1bcfa885c8d117a6), u64(0x1b99539e3a40dfb8), u64(0x1b6442e4fb671960), + u64(0x1b303583fc527ab3), u64(0x1af9ef3993b72ab8), u64(0x1ac4bf6142f8eefa), + u64(0x1a90991a9bfa58c8), u64(0x1a5a8e90f9908e0d), u64(0x1a253eda614071a4), + u64(0x19f0ff151a99f483), u64(0x19bb31bb5dc320d2), u64(0x1985c162b168e70e), + u64(0x1951678227871f3e), u64(0x191bd8d03f3e9864), u64(0x18e6470cff6546b6), + u64(0x18b1d270cc51055f), u64(0x187c83e7ad4e6efe), u64(0x1846cfec8aa52598), + u64(0x18123ff06eea847a), u64(0x17dd331a4b10d3f6), u64(0x17a75c1508da432b), + u64(0x1772b010d3e1cf56), u64(0x173de6815302e556), u64(0x1707eb9aa8cf1dde), + u64(0x16d322e220a5b17e), u64(0x169e9e369aa2b597), u64(0x16687e92154ef7ac), + u64(0x16339874ddd8c623), u64(0x15ff5a549627a36c), u64(0x15c91510781fb5f0), + u64(0x159410d9f9b2f7f3), u64(0x15600d7b2e28c65c), u64(0x1529af2b7d0e0a2d), + u64(0x14f48c22ca71a1bd), u64(0x14c0701bd527b498), u64(0x148a4cf9550c5426), + u64(0x14550a6110d6a9b8), u64(0x1420d51a73deee2d), u64(0x13eaee90b964b047), + u64(0x13b58ba6fab6f36c), u64(0x13813c85955f2923), u64(0x134b9408eefea839), + u64(0x1316100725988694), u64(0x12e1a66c1e139edd), u64(0x12ac3d79c9b8fe2e), + u64(0x12769794a160cb58), u64(0x124212dd4de70913), u64(0x120ceafbafd80e85), + u64(0x11d72262f3133ed1), u64(0x11a281e8c275cbda), u64(0x116d9ca79d89462a), + u64(0x1137b08617a104ee), u64(0x1102f39e794d9d8b), u64(0x10ce5297287c2f45), + u64(0x1098421286c9bf6b), u64(0x1063680ed23aff89), u64(0x102f0ce4839198db), + u64(0x0ff8d71d360e13e2), u64(0x0fc3df4a91a4dcb5), u64(0x0f8fcbaa82a16121), + u64(0x0f596fbb9bb44db4), u64(0x0f245962e2f6a490), u64(0x0ef047824f2bb6da), + u64(0x0eba0c03b1df8af6), u64(0x0e84d6695b193bf8), u64(0x0e50ab877c142ffa), + u64(0x0e1aac0bf9b9e65c), u64(0x0de5566ffafb1eb0), u64(0x0db111f32f2f4bc0), + u64(0x0d7b4feb7eb212cd), u64(0x0d45d98932280f0a), u64(0x0d117ad428200c08), + u64(0x0cdbf7b9d9cce00d), u64(0x0ca65fc7e170b33e), u64(0x0c71e6398126f5cb), + u64(0x0c3ca38f350b22df), u64(0x0c06e93f5da2824c), u64(0x0bd25432b14ecea3), + u64(0x0b9d53844ee47dd1), u64(0x0b677603725064a8), u64(0x0b32c4cf8ea6b6ec), + u64(0x0afe07b27dd78b14), u64(0x0ac8062864ac6f43), u64(0x0a9338205089f29c), + u64(0x0a5ec033b40fea93), u64(0x0a2899c2f6732210), u64(0x09f3ae3591f5b4d9), + u64(0x09bf7d228322baf5), u64(0x098930e868e89591), u64(0x0954272053ed4474), + u64(0x09201f4d0ff10390), u64(0x08e9cbae7fe805b3), u64(0x08b4a2f1ffecd15c), + u64(0x0880825b3323dab0), u64(0x084a6a2b85062ab3), u64(0x081521bc6a6b555c), + u64(0x07e0e7c9eebc444a), u64(0x07ab0c764ac6d3a9), u64(0x0775a391d56bdc87), + u64(0x07414fa7ddefe3a0), u64(0x070bb2a62fe638ff), u64(0x06d62884f31e93ff), + u64(0x06a1ba03f5b21000), u64(0x066c5cd322b67fff), u64(0x0636b0a8e891ffff), + u64(0x060226ed86db3333), u64(0x05cd0b15a491eb84), u64(0x05973c115074bc6a), + u64(0x05629674405d6388), u64(0x052dbd86cd6238d9), u64(0x04f7cad23de82d7b), + u64(0x04c308a831868ac9), u64(0x048e74404f3daadb), u64(0x04585d003f6488af), + u64(0x04237d99cc506d59), u64(0x03ef2f5c7a1a488e), u64(0x03b8f2b061aea072), + u64(0x0383f559e7bee6c1), u64(0x034feef63f97d79c), u64(0x03198bf832dfdfb0), + u64(0x02e46ff9c24cb2f3), u64(0x02b059949b708f29), u64(0x027a28edc580e50e), + u64(0x0244ed8b04671da5), u64(0x0210be08d0527e1d), u64(0x01dac9a7b3b7302f), + u64(0x01a56e1fc2f8f359), u64(0x017124e63593f5e1), u64(0x013b6e3d22865634), + u64(0x0105f1ca820511c3), u64(0x00d18e3b9b374169), u64(0x009c16c5c5253575), + u64(0x0066789e3750f791), u64(0x0031fa182c40c60d), u64(0x000730d67819e8d2), + u64(0x0000b8157268fdaf), u64(0x000012688b70e62b), u64(0x000001d74124e3d1), + u64(0x0000002f201d49fb), u64(0x00000004b6695433), u64(0x0000000078a42205), + u64(0x000000000c1069cd), u64(0x000000000134d761), u64(0x00000000001ee257), + u64(0x00000000000316a2), u64(0x0000000000004f10), u64(0x00000000000007e8), + u64(0x00000000000000ca), u64(0x0000000000000014), u64(0x0000000000000002)] +) diff --git a/v_windows/v/vlib/strconv/atoi.v b/v_windows/v/vlib/strconv/atoi.v new file mode 100644 index 0000000..3334919 --- /dev/null +++ b/v_windows/v/vlib/strconv/atoi.v @@ -0,0 +1,261 @@ +module strconv + +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// TODO: use optionals, or some way to return default with error. +const ( + // int_size is the size in bits of an int or uint value. + // int_size = 32 << (~u32(0) >> 63) + // max_u64 = u64(u64(1 << 63) - 1) + int_size = 32 + max_u64 = u64(18446744073709551615) // as u64 // use this until we add support +) + +pub fn byte_to_lower(c byte) byte { + return c | (`x` - `X`) +} + +// common_parse_uint is called by parse_uint and allows the parsing +// to stop on non or invalid digit characters and return with an error +pub fn common_parse_uint(s string, _base int, _bit_size int, error_on_non_digit bool, error_on_high_digit bool) ?u64 { + result, err := common_parse_uint2(s, _base, _bit_size) + // TODO: error_on_non_digit and error_on_high_digit have no difference + if err != 0 && (error_on_non_digit || error_on_high_digit) { + match err { + -1 { return error('common_parse_uint: wrong base $_base for $s') } + -2 { return error('common_parse_uint: wrong bit size $_bit_size for $s') } + -3 { return error('common_parse_uint: integer overflow $s') } + else { return error('common_parse_uint: syntax error $s') } + } + } + return result +} + +// the first returned value contains the parsed value, +// the second returned value contains the error code (0 = OK, >1 = index of first non-parseable character + 1, -1 = wrong base, -2 = wrong bit size, -3 = overflow) +pub fn common_parse_uint2(s string, _base int, _bit_size int) (u64, int) { + mut bit_size := _bit_size + mut base := _base + if s.len < 1 || !underscore_ok(s) { + // return error('parse_uint: syntax error $s') + return u64(0), 1 + } + base0 := base == 0 + mut start_index := 0 + if 2 <= base && base <= 36 { + // valid base; nothing to do + } else if base == 0 { + // Look for octal, hex prefix. + base = 10 + if s[0] == `0` { + if s.len >= 3 && byte_to_lower(s[1]) == `b` { + base = 2 + start_index += 2 + } else if s.len >= 3 && byte_to_lower(s[1]) == `o` { + base = 8 + start_index += 2 + } else if s.len >= 3 && byte_to_lower(s[1]) == `x` { + base = 16 + start_index += 2 + } + // manage leading zeros in decimal base's numbers + else if s.len >= 2 && (s[1] >= `0` && s[1] <= `9`) { + base = 10 + start_index++ + } else { + base = 8 + start_index++ + } + } + } else { + // return error('parse_uint: base error $s - $base') + return u64(0), -1 + } + if bit_size == 0 { + bit_size = strconv.int_size + } else if bit_size < 0 || bit_size > 64 { + // return error('parse_uint: bitsize error $s - $bit_size') + return u64(0), -2 + } + // Cutoff is the smallest number such that cutoff*base > maxUint64. + // Use compile-time constants for common cases. + cutoff := strconv.max_u64 / u64(base) + u64(1) + max_val := if bit_size == 64 { strconv.max_u64 } else { (u64(1) << u64(bit_size)) - u64(1) } + mut n := u64(0) + for i in start_index .. s.len { + c := s[i] + cl := byte_to_lower(c) + mut d := byte(0) + if c == `_` && base0 { + // underscore_ok already called + continue + } else if `0` <= c && c <= `9` { + d = c - `0` + } else if `a` <= cl && cl <= `z` { + d = cl - `a` + 10 + } else { + return n, i + 1 + } + if d >= byte(base) { + return n, i + 1 + } + if n >= cutoff { + // n*base overflows + // return error('parse_uint: range error $s') + return max_val, -3 + } + n *= u64(base) + n1 := n + u64(d) + if n1 < n || n1 > max_val { + // n+v overflows + // return error('parse_uint: range error $s') + return max_val, -3 + } + n = n1 + } + return n, 0 +} + +// parse_uint is like parse_int but for unsigned numbers. +pub fn parse_uint(s string, _base int, _bit_size int) ?u64 { + return common_parse_uint(s, _base, _bit_size, true, true) +} + +// common_parse_int is called by parse int and allows the parsing +// to stop on non or invalid digit characters and return with an error +pub fn common_parse_int(_s string, base int, _bit_size int, error_on_non_digit bool, error_on_high_digit bool) ?i64 { + mut s := _s + mut bit_size := _bit_size + if s.len < 1 { + // return error('parse_int: syntax error $s') + return i64(0) + } + // Pick off leading sign. + mut neg := false + if s[0] == `+` { + s = s[1..] + } else if s[0] == `-` { + neg = true + s = s[1..] + } + // Convert unsigned and check range. + // un := parse_uint(s, base, bit_size) or { + // return i64(0) + // } + un := common_parse_uint(s, base, bit_size, error_on_non_digit, error_on_high_digit) ? + if un == 0 { + return i64(0) + } + if bit_size == 0 { + bit_size = strconv.int_size + } + // TODO: check should u64(bit_size-1) be size of int (32)? + cutoff := u64(1) << u64(bit_size - 1) + if !neg && un >= cutoff { + // return error('parse_int: range error $s0') + return i64(cutoff - u64(1)) + } + if neg && un > cutoff { + // return error('parse_int: range error $s0') + return -i64(cutoff) + } + return if neg { -i64(un) } else { i64(un) } +} + +// parse_int interprets a string s in the given base (0, 2 to 36) and +// bit size (0 to 64) and returns the corresponding value i. +// +// If the base argument is 0, the true base is implied by the string's +// prefix: 2 for "0b", 8 for "0" or "0o", 16 for "0x", and 10 otherwise. +// Also, for argument base 0 only, underscore characters are permitted +// as defined by the Go syntax for integer literals. +// +// The bitSize argument specifies the integer type +// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64 +// correspond to int, int8, int16, int32, and int64. +// If bitSize is below 0 or above 64, an error is returned. +pub fn parse_int(_s string, base int, _bit_size int) ?i64 { + return common_parse_int(_s, base, _bit_size, true, true) +} + +// atoi is equivalent to parse_int(s, 10, 0), converted to type int. +pub fn atoi(s string) ?int { + if s == '' { + return error('strconv.atoi: parsing "$s": invalid syntax ') + } + if (strconv.int_size == 32 && (0 < s.len && s.len < 10)) + || (strconv.int_size == 64 && (0 < s.len && s.len < 19)) { + // Fast path for small integers that fit int type. + mut start_idx := 0 + if s[0] == `-` || s[0] == `+` { + start_idx++ + if s.len - start_idx < 1 { + // return 0, &NumError{fnAtoi, s0, ErrSyntax} + return error('strconv.atoi: parsing "$s": invalid syntax ') + } + } + mut n := 0 + for i in start_idx .. s.len { + ch := s[i] - `0` + if ch > 9 { + // return 0, &NumError{fnAtoi, s0, ErrSyntax} + return error('strconv.atoi: parsing "$s": invalid syntax ') + } + n = n * 10 + int(ch) + } + return if s[0] == `-` { -n } else { n } + } + // Slow path for invalid, big, or underscored integers. + int64 := parse_int(s, 10, 0) ? + return int(int64) +} + +// underscore_ok reports whether the underscores in s are allowed. +// Checking them in this one function lets all the parsers skip over them simply. +// Underscore must appear only between digits or between a base prefix and a digit. +fn underscore_ok(s string) bool { + // saw tracks the last character (class) we saw: + // ^ for beginning of number, + // 0 for a digit or base prefix, + // _ for an underscore, + // ! for none of the above. + mut saw := `^` + mut i := 0 + // Optional sign. + if s.len >= 1 && (s[0] == `-` || s[0] == `+`) { + i++ + } + // Optional base prefix. + mut hex := false + if s.len - i >= 2 && s[i] == `0` && (byte_to_lower(s[i + 1]) == `b` + || byte_to_lower(s[i + 1]) == `o` || byte_to_lower(s[i + 1]) == `x`) { + saw = `0` // base prefix counts as a digit for "underscore as digit separator" + hex = byte_to_lower(s[i + 1]) == `x` + i += 2 + } + // Number proper. + for ; i < s.len; i++ { + // Digits are always okay. + if (`0` <= s[i] && s[i] <= `9`) || (hex && `a` <= byte_to_lower(s[i]) + && byte_to_lower(s[i]) <= `f`) { + saw = `0` + continue + } + // Underscore must follow digit. + if s[i] == `_` { + if saw != `0` { + return false + } + saw = `_` + continue + } + // Underscore must also be followed by digit. + if saw == `_` { + return false + } + // Saw non-digit, non-underscore. + saw = `!` + } + return saw != `_` +} diff --git a/v_windows/v/vlib/strconv/atoi_test.v b/v_windows/v/vlib/strconv/atoi_test.v new file mode 100644 index 0000000..b356db6 --- /dev/null +++ b/v_windows/v/vlib/strconv/atoi_test.v @@ -0,0 +1,84 @@ +import strconv + +fn test_atoi() ? { + assert strconv.atoi('16') ? == 16 + assert strconv.atoi('+16') ? == 16 + assert strconv.atoi('-16') ? == -16 + + // invalid strings + if x := strconv.atoi('str') { + println(x) + assert false + } else { + assert true + } + if x := strconv.atoi('string_longer_than_10_chars') { + println(x) + assert false + } else { + assert true + } + if x := strconv.atoi('') { + println(x) + assert false + } else { + assert true + } +} + +fn test_parse_int() ? { + // Different bases + assert strconv.parse_int('16', 16, 0) ? == 0x16 + assert strconv.parse_int('16', 8, 0) ? == 0o16 + assert strconv.parse_int('11', 2, 0) ? == 3 + // Different bit sizes + assert strconv.parse_int('127', 10, 8) ? == 127 + assert strconv.parse_int('128', 10, 8) ? == 127 + assert strconv.parse_int('32767', 10, 16) ? == 32767 + assert strconv.parse_int('32768', 10, 16) ? == 32767 + assert strconv.parse_int('2147483647', 10, 32) ? == 2147483647 + assert strconv.parse_int('2147483648', 10, 32) ? == 2147483647 + assert strconv.parse_int('9223372036854775807', 10, 64) ? == 9223372036854775807 + assert strconv.parse_int('9223372036854775808', 10, 64) ? == 9223372036854775807 + assert strconv.parse_int('baobab', 36, 64) ? == 683058467 + // Invalid bit sizes + if x := strconv.parse_int('123', 10, -1) { + println(x) + assert false + } else { + assert true + } + if x := strconv.parse_int('123', 10, 65) { + println(x) + assert false + } else { + assert true + } +} + +fn test_common_parse_uint2() { + mut result, mut error := strconv.common_parse_uint2('1', 10, 8) + assert result == 1 + assert error == 0 + result, error = strconv.common_parse_uint2('123', 10, 8) + assert result == 123 + assert error == 0 + result, error = strconv.common_parse_uint2('123', 10, 65) + assert result == 0 + assert error == -2 + result, error = strconv.common_parse_uint2('123', 10, -1) + assert result == 0 + assert error == -2 + result, error = strconv.common_parse_uint2('', 10, 8) + assert result == 0 + assert error == 1 + result, error = strconv.common_parse_uint2('1a', 10, 8) + assert result == 1 + assert error == 2 + result, error = strconv.common_parse_uint2('12a', 10, 8) + assert result == 12 + assert error == 3 + result, error = strconv.common_parse_uint2('123a', 10, 8) + assert result == 123 + assert error == 4 +} diff --git a/v_windows/v/vlib/strconv/f32_f64_to_string_test.v b/v_windows/v/vlib/strconv/f32_f64_to_string_test.v new file mode 100644 index 0000000..0b24aa2 --- /dev/null +++ b/v_windows/v/vlib/strconv/f32_f64_to_string_test.v @@ -0,0 +1,171 @@ +/********************************************************************** +* +* Float to string Test +* +**********************************************************************/ +import strconv +import math + +union Ufloat32 { +mut: + f f32 = f32(0) + b u32 +} + +union Ufloat64 { +mut: + f f64 = f64(0) + b u64 +} + +fn f64_from_bits1(b u64) f64 { + mut x := Ufloat64{} + x.b = b + // C.printf("bin: %016llx\n",x.f) + return unsafe { x.f } +} + +fn f32_from_bits1(b u32) f32 { + mut x := Ufloat32{} + x.b = b + // C.printf("bin: %08x\n",x.f) + return unsafe { x.f } +} + +fn test_float_to_str() { + test_cases_f32 := [ + f32_from_bits1(0x0000_0000), // +0 + f32_from_bits1(0x8000_0000), // -0 + f32_from_bits1(0xFFC0_0001), // sNan + f32_from_bits1(0xFF80_0001), // qNan + f32_from_bits1(0x7F80_0000), // +inf + f32_from_bits1(0xFF80_0000), // -inf + 1, + -1, + 10, + -10, + 0.3, + -0.3, + 1000000, + 123456.7, + 123e35, + -123.45, + 1e23, + f32_from_bits1(0x0080_0000), // smallest float32 + math.max_f32, + 383260575764816448.0, + ] + + exp_result_f32 := [ + '0e+00', + '-0e+00', + 'nan', + 'nan', + '+inf', + '-inf', + '1.e+00', + '-1.e+00', + '1.e+01', + '-1.e+01', + '3.e-01', + '-3.e-01', + '1.e+06', + '1.234567e+05', + '1.23e+37', + '-1.2345e+02', + '1.e+23', + '1.1754944e-38', // aprox from 1.1754943508 × 10−38, + '3.4028235e+38', + '3.8326058e+17', + ] + + test_cases_f64 := [ + f64_from_bits1(0x0000_0000_0000_0000), // +0 + f64_from_bits1(0x8000_0000_0000_0000), // -0 + f64_from_bits1(0x7FF0_0000_0000_0001), // sNan + f64_from_bits1(0x7FF8_0000_0000_0001), // qNan + f64_from_bits1(0x7FF0_0000_0000_0000), // +inf + f64_from_bits1(0xFFF0_0000_0000_0000), // -inf + 1, + -1, + 10, + -10, + 0.3, + -0.3, + 1000000, + 123456.7, + 123e45, + -123.45, + 1e23, + f64_from_bits1(0x0010_0000_0000_0000), // smallest float64 + math.max_f32, + 383260575764816448, + 383260575764816448, + // C failing cases + 123e300, + 123e-300, + 5.e-324, + -5.e-324, + ] + + exp_result_f64 := [ + '0e+00', + '-0e+00', + 'nan', + 'nan', + '+inf', + '-inf', + '1.e+00', + '-1.e+00', + '1.e+01', + '-1.e+01', + '3.e-01', + '-3.e-01', + '1.e+06', + '1.234567e+05', + '1.23e+47', + '-1.2345e+02', + '1.e+23', + '2.2250738585072014e-308', + '3.4028234663852886e+38', + '3.8326057576481645e+17', + '3.8326057576481645e+17', + '1.23e+302', // this test is failed from C sprintf!! + '1.23e-298', + '5.e-324', + '-5.e-324', + ] + + // test f32 + for c, x in test_cases_f32 { + println(x) + s := strconv.f32_to_str(x, 8) + s1 := exp_result_f32[c] + // println("$s1 $s") + assert s == s1 + } + + // test f64 + for c, x in test_cases_f64 { + s := strconv.f64_to_str(x, 17) + s1 := exp_result_f64[c] + // println("$s1 $s") + assert s == s1 + } + + // test long format + for exp := 1; exp < 120; exp++ { + a := strconv.f64_to_str_l(('1e' + exp.str()).f64()) + // println(a) + assert a.len == exp + 1 + + b := strconv.f64_to_str_l(('1e-' + exp.str()).f64()) + // println(b) + assert b.len == exp + 2 + } + + // test rounding str conversion + // println( ftoa.f64_to_str(0.3456789123456, 4) ) + // assert ftoa.f64_to_str(0.3456789123456, 4)=="3.4568e-01" + // assert ftoa.f32_to_str(0.345678, 3)=="3.457e-01" +} diff --git a/v_windows/v/vlib/strconv/f32_str.v b/v_windows/v/vlib/strconv/f32_str.v new file mode 100644 index 0000000..8e17c89 --- /dev/null +++ b/v_windows/v/vlib/strconv/f32_str.v @@ -0,0 +1,377 @@ +module strconv + +/*============================================================================= + +f32 to string + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains the f32 to string functions + +These functions are based on the work of: +Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN +Conference on Programming Language Design and ImplementationJune 2018 +Pages 270–282 https://doi.org/10.1145/3192366.3192369 + +inspired by the Go version here: +https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea + +=============================================================================*/ + +// pow of ten table used by n_digit reduction +const ( + ten_pow_table_32 = [ + u32(1), + u32(10), + u32(100), + u32(1000), + u32(10000), + u32(100000), + u32(1000000), + u32(10000000), + u32(100000000), + u32(1000000000), + u32(10000000000), + u32(100000000000), + ] +) + +//============================================================================= +// Conversion Functions +//============================================================================= +const ( + mantbits32 = u32(23) + expbits32 = u32(8) + bias32 = 127 // f32 exponent bias + maxexp32 = 255 +) + +// max 46 char +// -3.40282346638528859811704183484516925440e+38 +[direct_array_access] +pub fn (d Dec32) get_string_32(neg bool, i_n_digit int, i_pad_digit int) string { + n_digit := i_n_digit + 1 + pad_digit := i_pad_digit + 1 + mut out := d.m + // mut out_len := decimal_len_32(out) + mut out_len := dec_digits(out) + out_len_original := out_len + + mut fw_zeros := 0 + if pad_digit > out_len { + fw_zeros = pad_digit - out_len + } + + mut buf := []byte{len: int(out_len + 5 + 1 + 1)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0} + mut i := 0 + + if neg { + if buf.data != 0 { + // The buf.data != 0 check here, is needed for clean compilation + // with `-cc gcc -cstrict -prod`. Without it, gcc produces: + // error: potential null pointer dereference + buf[i] = `-` + } + i++ + } + + mut disp := 0 + if out_len <= 1 { + disp = 1 + } + + if n_digit < out_len { + // println("orig: ${out_len_original}") + out += strconv.ten_pow_table_32[out_len - n_digit - 1] * 5 // round to up + out /= strconv.ten_pow_table_32[out_len - n_digit] + out_len = n_digit + } + + y := i + out_len + mut x := 0 + for x < (out_len - disp - 1) { + buf[y - x] = `0` + byte(out % 10) + out /= 10 + i++ + x++ + } + + // no decimal digits needed, end here + if i_n_digit == 0 { + unsafe { + buf[i] = 0 + return tos(&byte(&buf[0]), i) + } + } + + if out_len >= 1 { + buf[y - x] = `.` + x++ + i++ + } + + if y - x >= 0 { + buf[y - x] = `0` + byte(out % 10) + i++ + } + + for fw_zeros > 0 { + buf[i] = `0` + i++ + fw_zeros-- + } + + buf[i] = `e` + i++ + + mut exp := d.e + out_len_original - 1 + if exp < 0 { + buf[i] = `-` + i++ + exp = -exp + } else { + buf[i] = `+` + i++ + } + + // Always print two digits to match strconv's formatting. + d1 := exp % 10 + d0 := exp / 10 + buf[i] = `0` + byte(d0) + i++ + buf[i] = `0` + byte(d1) + i++ + buf[i] = 0 + + return unsafe { + tos(&byte(&buf[0]), i) + } +} + +fn f32_to_decimal_exact_int(i_mant u32, exp u32) (Dec32, bool) { + mut d := Dec32{} + e := exp - strconv.bias32 + if e > strconv.mantbits32 { + return d, false + } + shift := strconv.mantbits32 - e + mant := i_mant | 0x0080_0000 // implicit 1 + // mant := i_mant | (1 << mantbits32) // implicit 1 + d.m = mant >> shift + if (d.m << shift) != mant { + return d, false + } + for (d.m % 10) == 0 { + d.m /= 10 + d.e++ + } + return d, true +} + +fn f32_to_decimal(mant u32, exp u32) Dec32 { + mut e2 := 0 + mut m2 := u32(0) + if exp == 0 { + // We subtract 2 so that the bounds computation has + // 2 additional bits. + e2 = 1 - strconv.bias32 - int(strconv.mantbits32) - 2 + m2 = mant + } else { + e2 = int(exp) - strconv.bias32 - int(strconv.mantbits32) - 2 + m2 = (u32(1) << strconv.mantbits32) | mant + } + even := (m2 & 1) == 0 + accept_bounds := even + + // Step 2: Determine the interval of valid decimal representations. + mv := u32(4 * m2) + mp := u32(4 * m2 + 2) + mm_shift := bool_to_u32(mant != 0 || exp <= 1) + mm := u32(4 * m2 - 1 - mm_shift) + + mut vr := u32(0) + mut vp := u32(0) + mut vm := u32(0) + mut e10 := 0 + mut vm_is_trailing_zeros := false + mut vr_is_trailing_zeros := false + mut last_removed_digit := byte(0) + + if e2 >= 0 { + q := log10_pow2(e2) + e10 = int(q) + k := pow5_inv_num_bits_32 + pow5_bits(int(q)) - 1 + i := -e2 + int(q) + k + + vr = mul_pow5_invdiv_pow2(mv, q, i) + vp = mul_pow5_invdiv_pow2(mp, q, i) + vm = mul_pow5_invdiv_pow2(mm, q, i) + if q != 0 && (vp - 1) / 10 <= vm / 10 { + // We need to know one removed digit even if we are not + // going to loop below. We could use q = X - 1 above, + // except that would require 33 bits for the result, and + // we've found that 32-bit arithmetic is faster even on + // 64-bit machines. + l := pow5_inv_num_bits_32 + pow5_bits(int(q - 1)) - 1 + last_removed_digit = byte(mul_pow5_invdiv_pow2(mv, q - 1, -e2 + int(q - 1) + l) % 10) + } + if q <= 9 { + // The largest power of 5 that fits in 24 bits is 5^10, + // but q <= 9 seems to be safe as well. Only one of mp, + // mv, and mm can be a multiple of 5, if any. + if mv % 5 == 0 { + vr_is_trailing_zeros = multiple_of_power_of_five_32(mv, q) + } else if accept_bounds { + vm_is_trailing_zeros = multiple_of_power_of_five_32(mm, q) + } else if multiple_of_power_of_five_32(mp, q) { + vp-- + } + } + } else { + q := log10_pow5(-e2) + e10 = int(q) + e2 + i := -e2 - int(q) + k := pow5_bits(i) - pow5_num_bits_32 + mut j := int(q) - k + vr = mul_pow5_div_pow2(mv, u32(i), j) + vp = mul_pow5_div_pow2(mp, u32(i), j) + vm = mul_pow5_div_pow2(mm, u32(i), j) + if q != 0 && ((vp - 1) / 10) <= vm / 10 { + j = int(q) - 1 - (pow5_bits(i + 1) - pow5_num_bits_32) + last_removed_digit = byte(mul_pow5_div_pow2(mv, u32(i + 1), j) % 10) + } + if q <= 1 { + // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at + // least q trailing 0 bits. mv = 4 * m2, so it always + // has at least two trailing 0 bits. + vr_is_trailing_zeros = true + if accept_bounds { + // mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit + // if mm_shift == 1. + vm_is_trailing_zeros = mm_shift == 1 + } else { + // mp = mv + 2, so it always has at least one + // trailing 0 bit. + vp-- + } + } else if q < 31 { + vr_is_trailing_zeros = multiple_of_power_of_two_32(mv, q - 1) + } + } + + // Step 4: Find the shortest decimal representation + // in the interval of valid representations. + mut removed := 0 + mut out := u32(0) + if vm_is_trailing_zeros || vr_is_trailing_zeros { + // General case, which happens rarely (~4.0%). + for vp / 10 > vm / 10 { + vm_is_trailing_zeros = vm_is_trailing_zeros && (vm % 10) == 0 + vr_is_trailing_zeros = vr_is_trailing_zeros && (last_removed_digit == 0) + last_removed_digit = byte(vr % 10) + vr /= 10 + vp /= 10 + vm /= 10 + removed++ + } + if vm_is_trailing_zeros { + for vm % 10 == 0 { + vr_is_trailing_zeros = vr_is_trailing_zeros && (last_removed_digit == 0) + last_removed_digit = byte(vr % 10) + vr /= 10 + vp /= 10 + vm /= 10 + removed++ + } + } + if vr_is_trailing_zeros && (last_removed_digit == 5) && (vr % 2) == 0 { + // Round even if the exact number is .....50..0. + last_removed_digit = 4 + } + out = vr + // We need to take vr + 1 if vr is outside bounds + // or we need to round up. + if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 { + out++ + } + } else { + // Specialized for the common case (~96.0%). Percentages below + // are relative to this. Loop iterations below (approximately): + // 0: 13.6%, 1: 70.7%, 2: 14.1%, 3: 1.39%, 4: 0.14%, 5+: 0.01% + for vp / 10 > vm / 10 { + last_removed_digit = byte(vr % 10) + vr /= 10 + vp /= 10 + vm /= 10 + removed++ + } + // We need to take vr + 1 if vr is outside bounds + // or we need to round up. + out = vr + bool_to_u32(vr == vm || last_removed_digit >= 5) + } + + return Dec32{ + m: out + e: e10 + removed + } +} + +//============================================================================= +// String Functions +//============================================================================= + +// f32_to_str return a string in scientific notation with max n_digit after the dot +pub fn f32_to_str(f f32, n_digit int) string { + mut u1 := Uf32{} + u1.f = f + u := unsafe { u1.u } + + neg := (u >> (strconv.mantbits32 + strconv.expbits32)) != 0 + mant := u & ((u32(1) << strconv.mantbits32) - u32(1)) + exp := (u >> strconv.mantbits32) & ((u32(1) << strconv.expbits32) - u32(1)) + + // println("${neg} ${mant} e ${exp-bias32}") + + // Exit early for easy cases. + if (exp == strconv.maxexp32) || (exp == 0 && mant == 0) { + return get_string_special(neg, exp == 0, mant == 0) + } + + mut d, ok := f32_to_decimal_exact_int(mant, exp) + if !ok { + // println("with exp form") + d = f32_to_decimal(mant, exp) + } + + // println("${d.m} ${d.e}") + return d.get_string_32(neg, n_digit, 0) +} + +// f32_to_str return a string in scientific notation with max n_digit after the dot +pub fn f32_to_str_pad(f f32, n_digit int) string { + mut u1 := Uf32{} + u1.f = f + u := unsafe { u1.u } + + neg := (u >> (strconv.mantbits32 + strconv.expbits32)) != 0 + mant := u & ((u32(1) << strconv.mantbits32) - u32(1)) + exp := (u >> strconv.mantbits32) & ((u32(1) << strconv.expbits32) - u32(1)) + + // println("${neg} ${mant} e ${exp-bias32}") + + // Exit early for easy cases. + if (exp == strconv.maxexp32) || (exp == 0 && mant == 0) { + return get_string_special(neg, exp == 0, mant == 0) + } + + mut d, ok := f32_to_decimal_exact_int(mant, exp) + if !ok { + // println("with exp form") + d = f32_to_decimal(mant, exp) + } + + // println("${d.m} ${d.e}") + return d.get_string_32(neg, n_digit, n_digit) +} diff --git a/v_windows/v/vlib/strconv/f64_str.v b/v_windows/v/vlib/strconv/f64_str.v new file mode 100644 index 0000000..6be4354 --- /dev/null +++ b/v_windows/v/vlib/strconv/f64_str.v @@ -0,0 +1,418 @@ +module strconv + +/*============================================================================= + +f64 to string + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains the f64 to string functions + +These functions are based on the work of: +Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN +Conference on Programming Language Design and ImplementationJune 2018 +Pages 270–282 https://doi.org/10.1145/3192366.3192369 + +inspired by the Go version here: +https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea + +=============================================================================*/ + +// pow of ten table used by n_digit reduction +const ( + ten_pow_table_64 = [ + u64(1), + u64(10), + u64(100), + u64(1000), + u64(10000), + u64(100000), + u64(1000000), + u64(10000000), + u64(100000000), + u64(1000000000), + u64(10000000000), + u64(100000000000), + u64(1000000000000), + u64(10000000000000), + u64(100000000000000), + u64(1000000000000000), + u64(10000000000000000), + u64(100000000000000000), + u64(1000000000000000000), + u64(10000000000000000000), + ] +) + +//============================================================================= +// Conversion Functions +//============================================================================= +const ( + mantbits64 = u32(52) + expbits64 = u32(11) + bias64 = 1023 // f64 exponent bias + maxexp64 = 2047 +) + +[direct_array_access] +fn (d Dec64) get_string_64(neg bool, i_n_digit int, i_pad_digit int) string { + mut n_digit := i_n_digit + 1 + pad_digit := i_pad_digit + 1 + mut out := d.m + mut d_exp := d.e + // mut out_len := decimal_len_64(out) + mut out_len := dec_digits(out) + out_len_original := out_len + + mut fw_zeros := 0 + if pad_digit > out_len { + fw_zeros = pad_digit - out_len + } + + mut buf := []byte{len: (out_len + 6 + 1 + 1 + fw_zeros)} // sign + mant_len + . + e + e_sign + exp_len(2) + \0} + mut i := 0 + + if neg { + buf[i] = `-` + i++ + } + + mut disp := 0 + if out_len <= 1 { + disp = 1 + } + + // rounding last used digit + if n_digit < out_len { + // println("out:[$out]") + out += strconv.ten_pow_table_64[out_len - n_digit - 1] * 5 // round to up + out /= strconv.ten_pow_table_64[out_len - n_digit] + // println("out1:[$out] ${d.m / ten_pow_table_64[out_len - n_digit ]}") + if d.m / strconv.ten_pow_table_64[out_len - n_digit] < out { + d_exp++ + n_digit++ + } + + // println("cmp: ${d.m/ten_pow_table_64[out_len - n_digit ]} ${out/ten_pow_table_64[out_len - n_digit ]}") + + out_len = n_digit + // println("orig: ${out_len_original} new len: ${out_len} out:[$out]") + } + + y := i + out_len + mut x := 0 + for x < (out_len - disp - 1) { + buf[y - x] = `0` + byte(out % 10) + out /= 10 + i++ + x++ + } + + // no decimal digits needed, end here + if i_n_digit == 0 { + unsafe { + buf[i] = 0 + return tos(&byte(&buf[0]), i) + } + } + + if out_len >= 1 { + buf[y - x] = `.` + x++ + i++ + } + + if y - x >= 0 { + buf[y - x] = `0` + byte(out % 10) + i++ + } + + for fw_zeros > 0 { + buf[i] = `0` + i++ + fw_zeros-- + } + + buf[i] = `e` + i++ + + mut exp := d_exp + out_len_original - 1 + if exp < 0 { + buf[i] = `-` + i++ + exp = -exp + } else { + buf[i] = `+` + i++ + } + + // Always print at least two digits to match strconv's formatting. + d2 := exp % 10 + exp /= 10 + d1 := exp % 10 + d0 := exp / 10 + if d0 > 0 { + buf[i] = `0` + byte(d0) + i++ + } + buf[i] = `0` + byte(d1) + i++ + buf[i] = `0` + byte(d2) + i++ + buf[i] = 0 + + return unsafe { + tos(&byte(&buf[0]), i) + } +} + +fn f64_to_decimal_exact_int(i_mant u64, exp u64) (Dec64, bool) { + mut d := Dec64{} + e := exp - strconv.bias64 + if e > strconv.mantbits64 { + return d, false + } + shift := strconv.mantbits64 - e + mant := i_mant | u64(0x0010_0000_0000_0000) // implicit 1 + // mant := i_mant | (1 << mantbits64) // implicit 1 + d.m = mant >> shift + if (d.m << shift) != mant { + return d, false + } + + for (d.m % 10) == 0 { + d.m /= 10 + d.e++ + } + return d, true +} + +fn f64_to_decimal(mant u64, exp u64) Dec64 { + mut e2 := 0 + mut m2 := u64(0) + if exp == 0 { + // We subtract 2 so that the bounds computation has + // 2 additional bits. + e2 = 1 - strconv.bias64 - int(strconv.mantbits64) - 2 + m2 = mant + } else { + e2 = int(exp) - strconv.bias64 - int(strconv.mantbits64) - 2 + m2 = (u64(1) << strconv.mantbits64) | mant + } + even := (m2 & 1) == 0 + accept_bounds := even + + // Step 2: Determine the interval of valid decimal representations. + mv := u64(4 * m2) + mm_shift := bool_to_u64(mant != 0 || exp <= 1) + + // Step 3: Convert to a decimal power base uing 128-bit arithmetic. + mut vr := u64(0) + mut vp := u64(0) + mut vm := u64(0) + mut e10 := 0 + mut vm_is_trailing_zeros := false + mut vr_is_trailing_zeros := false + + if e2 >= 0 { + // This expression is slightly faster than max(0, log10Pow2(e2) - 1). + q := log10_pow2(e2) - bool_to_u32(e2 > 3) + e10 = int(q) + k := pow5_inv_num_bits_64 + pow5_bits(int(q)) - 1 + i := -e2 + int(q) + k + + mul := pow5_inv_split_64[q] + vr = mul_shift_64(u64(4) * m2, mul, i) + vp = mul_shift_64(u64(4) * m2 + u64(2), mul, i) + vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, i) + if q <= 21 { + // This should use q <= 22, but I think 21 is also safe. + // Smaller values may still be safe, but it's more + // difficult to reason about them. Only one of mp, mv, + // and mm can be a multiple of 5, if any. + if mv % 5 == 0 { + vr_is_trailing_zeros = multiple_of_power_of_five_64(mv, q) + } else if accept_bounds { + // Same as min(e2 + (^mm & 1), pow5Factor64(mm)) >= q + // <=> e2 + (^mm & 1) >= q && pow5Factor64(mm) >= q + // <=> true && pow5Factor64(mm) >= q, since e2 >= q. + vm_is_trailing_zeros = multiple_of_power_of_five_64(mv - 1 - mm_shift, + q) + } else if multiple_of_power_of_five_64(mv + 2, q) { + vp-- + } + } + } else { + // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). + q := log10_pow5(-e2) - bool_to_u32(-e2 > 1) + e10 = int(q) + e2 + i := -e2 - int(q) + k := pow5_bits(i) - pow5_num_bits_64 + j := int(q) - k + mul := pow5_split_64[i] + vr = mul_shift_64(u64(4) * m2, mul, j) + vp = mul_shift_64(u64(4) * m2 + u64(2), mul, j) + vm = mul_shift_64(u64(4) * m2 - u64(1) - mm_shift, mul, j) + if q <= 1 { + // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. + // mv = 4 * m2, so it always has at least two trailing 0 bits. + vr_is_trailing_zeros = true + if accept_bounds { + // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. + vm_is_trailing_zeros = (mm_shift == 1) + } else { + // mp = mv + 2, so it always has at least one trailing 0 bit. + vp-- + } + } else if q < 63 { // TODO(ulfjack/cespare): Use a tighter bound here. + // We need to compute min(ntz(mv), pow5Factor64(mv) - e2) >= q - 1 + // <=> ntz(mv) >= q - 1 && pow5Factor64(mv) - e2 >= q - 1 + // <=> ntz(mv) >= q - 1 (e2 is negative and -e2 >= q) + // <=> (mv & ((1 << (q - 1)) - 1)) == 0 + // We also need to make sure that the left shift does not overflow. + vr_is_trailing_zeros = multiple_of_power_of_two_64(mv, q - 1) + } + } + + // Step 4: Find the shortest decimal representation + // in the interval of valid representations. + mut removed := 0 + mut last_removed_digit := byte(0) + mut out := u64(0) + // On average, we remove ~2 digits. + if vm_is_trailing_zeros || vr_is_trailing_zeros { + // General case, which happens rarely (~0.7%). + for { + vp_div_10 := vp / 10 + vm_div_10 := vm / 10 + if vp_div_10 <= vm_div_10 { + break + } + vm_mod_10 := vm % 10 + vr_div_10 := vr / 10 + vr_mod_10 := vr % 10 + vm_is_trailing_zeros = vm_is_trailing_zeros && vm_mod_10 == 0 + vr_is_trailing_zeros = vr_is_trailing_zeros && (last_removed_digit == 0) + last_removed_digit = byte(vr_mod_10) + vr = vr_div_10 + vp = vp_div_10 + vm = vm_div_10 + removed++ + } + if vm_is_trailing_zeros { + for { + vm_div_10 := vm / 10 + vm_mod_10 := vm % 10 + if vm_mod_10 != 0 { + break + } + vp_div_10 := vp / 10 + vr_div_10 := vr / 10 + vr_mod_10 := vr % 10 + vr_is_trailing_zeros = vr_is_trailing_zeros && (last_removed_digit == 0) + last_removed_digit = byte(vr_mod_10) + vr = vr_div_10 + vp = vp_div_10 + vm = vm_div_10 + removed++ + } + } + if vr_is_trailing_zeros && (last_removed_digit == 5) && (vr % 2) == 0 { + // Round even if the exact number is .....50..0. + last_removed_digit = 4 + } + out = vr + // We need to take vr + 1 if vr is outside bounds + // or we need to round up. + if (vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5 { + out++ + } + } else { + // Specialized for the common case (~99.3%). + // Percentages below are relative to this. + mut round_up := false + for vp / 100 > vm / 100 { + // Optimization: remove two digits at a time (~86.2%). + round_up = (vr % 100) >= 50 + vr /= 100 + vp /= 100 + vm /= 100 + removed += 2 + } + // Loop iterations below (approximately), without optimization above: + // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% + // Loop iterations below (approximately), with optimization above: + // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% + for vp / 10 > vm / 10 { + round_up = (vr % 10) >= 5 + vr /= 10 + vp /= 10 + vm /= 10 + removed++ + } + // We need to take vr + 1 if vr is outside bounds + // or we need to round up. + out = vr + bool_to_u64(vr == vm || round_up) + } + + return Dec64{ + m: out + e: e10 + removed + } +} + +//============================================================================= +// String Functions +//============================================================================= + +// f64_to_str return a string in scientific notation with max n_digit after the dot +pub fn f64_to_str(f f64, n_digit int) string { + mut u1 := Uf64{} + u1.f = f + u := unsafe { u1.u } + + neg := (u >> (strconv.mantbits64 + strconv.expbits64)) != 0 + mant := u & ((u64(1) << strconv.mantbits64) - u64(1)) + exp := (u >> strconv.mantbits64) & ((u64(1) << strconv.expbits64) - u64(1)) + // println("s:${neg} mant:${mant} exp:${exp} float:${f} byte:${u1.u:016lx}") + + // Exit early for easy cases. + if (exp == strconv.maxexp64) || (exp == 0 && mant == 0) { + return get_string_special(neg, exp == 0, mant == 0) + } + + mut d, ok := f64_to_decimal_exact_int(mant, exp) + if !ok { + // println("to_decimal") + d = f64_to_decimal(mant, exp) + } + // println("${d.m} ${d.e}") + return d.get_string_64(neg, n_digit, 0) +} + +// f64_to_str return a string in scientific notation with max n_digit after the dot +pub fn f64_to_str_pad(f f64, n_digit int) string { + mut u1 := Uf64{} + u1.f = f + u := unsafe { u1.u } + + neg := (u >> (strconv.mantbits64 + strconv.expbits64)) != 0 + mant := u & ((u64(1) << strconv.mantbits64) - u64(1)) + exp := (u >> strconv.mantbits64) & ((u64(1) << strconv.expbits64) - u64(1)) + // println("s:${neg} mant:${mant} exp:${exp} float:${f} byte:${u1.u:016lx}") + + // Exit early for easy cases. + if (exp == strconv.maxexp64) || (exp == 0 && mant == 0) { + return get_string_special(neg, exp == 0, mant == 0) + } + + mut d, ok := f64_to_decimal_exact_int(mant, exp) + if !ok { + // println("to_decimal") + d = f64_to_decimal(mant, exp) + } + // println("DEBUG: ${d.m} ${d.e}") + return d.get_string_64(neg, n_digit, n_digit) +} diff --git a/v_windows/v/vlib/strconv/format.md b/v_windows/v/vlib/strconv/format.md new file mode 100644 index 0000000..1563ce9 --- /dev/null +++ b/v_windows/v/vlib/strconv/format.md @@ -0,0 +1,250 @@ +# v_printf/v_sprintf + +These are v implementations of the C language `printf` and `sprintf` functions. + +***Note: These functions are platform dependent in C, but in V they are platform independent.*** + +### v_sprintf + +`v_sprintf` has a variable number of parameters. +The first is a format string to control the appearance of the final string. +Each format specifier (%s, %d, etc.) in the format string +is replaced by the textual version of the following parameters. + +```v +import strconv + +fn main() { + a := 'World' + s := strconv.v_sprintf('Hello %s!', a) + println(s) +} +``` + +``` +Hello World! +``` + +### v_printf + +`v_printf` creates the same modified string as `v_sprintf`, using the same format specifiers, +but it will immediately print the modified string to stdout instead of returning a string. + +### Syntax + +The syntax for a format specifier is: + +``` +%[parameter][flags][width][.precision][length]type +``` + +#### Flags field + +The Flags field may be zero or more (in any order) of: + +| Character | Description | +| ----------- | ------------------------------------------------------------ | +| `-` (minus) | Left-align the output of this specifier. (The default is to right-align the output.) | +| `+` (plus) | Prepends a plus for positive signed-numeric types. positive = `+`, negative = `-`. (The default doesn't prepend anything to positive numbers.) | +| `0` (zero) | When the 'width' option is specified, prepends zeros for numeric types. (The default prepends spaces.) For example, `printf("%4X",3)` produces ` 3`, while `printf("%04X",3)` produces `0003`. | + +#### Width field + +The Width field specifies a *maximum* number of characters to output, +and is typically used to pad fixed-width fields in tabulated output, +it causes truncation of oversized fields. + +The width field may be omitted, or it may be a numeric integer value, +or may also be specified by a parameter when indicated by an asterisk `*`. +For example, `v_printf("%*.s", 5, my_string)` will result in ` mystring` being printed, +with a total width of 5 characters. + +#### Length field + +The Length field can be omitted or be any of: + +| Character | Description | +| --------- | ------------------------------------------------------------ | +| `hh` | For integer types, causes `printf` to expect an `byte` or `i8` argument. | +| `h` | For integer types, causes `printf` to expect an `int16` or `u16` argument. | +| `l` | For integer types, causes `printf` to expect an `i64` or `u64` argument. | +| `ll` | For integer types, causes `printf` to expect an `i64` or `u64` argument. | +| | | +| | | + +#### Type field + +The Type field can be any of: + +| Character | Description | +| --------- | ------------------------------------------------------------ | +| `%` | Prints a literal `%` character (this type doesn't accept any flags, width, precision, length fields). | +| `d`, `i` | `int` as a signed `int` `%d` and `%i` are synonymous for output. The size of the argument is specified by the length field. | +| `u` | `unsigned int`. The size of the argument is specified by the length field. | +| `f`, `F` | `double` in normal notation. `f` and `F` only differs in how the strings are printed: lowercase or uppercase. | +| `e`, `E` | `double` in scientific notation.`e` and `E` only differs in how the strings are printed: lowercase or uppercase. | +| `g`, `G` | `double` in automatic notation.`g` and `G` only differs in how the strings are printed: lowercase or uppercase. | +| `x`, `X` | `unsigned int` as a hexadecimal number. `x` uses lower-case letters and `X` uses upper-case. | +| `s` | string | +| `p` | `void *` (pointer to void) in an implementation-defined format. | +| `c` | `char` (character). | + +## Examples + +various types + +```v oksyntax +a0 := u32(10) +b0 := 200 +c0 := byte(12) +s0 := 'ciAo' +ch0 := `B` +f0 := 0.312345 +f1 := 200000.0 +sc0 := 'ciao: [%-08u] %d %hhd [%8s] [%08X] [%-20.4f] [%-20.4f] [%c]' +temp_s = strconv.v_sprintf(sc0, a0, b0, c0, s0, b0, f0, f1, ch0) +println(temp_s) +``` + +``` +ciao: [10 ] 200 12 [ ciAo] [000000C8] [0.3123 ] [200000.0000 ] [B] +``` + +integer + +```v oksyntax +a := byte(12) +b := i16(13) +c := 14 +d := i64(15) +sc1 := '==>%hhd %hd %d %ld' +temp_s = strconv.v_sprintf(sc1, a, b, c, d) +println(temp_s) +``` + +``` +==>12 13 14 15 +``` + +unsigned integer + +```v oksyntax +a1 := byte(0xff) +b1 := u16(0xffff) +c1 := u32(0xffffffff) +d1 := u64(-1) +sc2 := '%hhu %hu %u %lu' +temp_s = strconv.v_sprintf(sc2, a1, b1, c1, d1) +println(temp_s) +``` + +``` +255 65535 4294967295 18446744073709551615 +``` + +hexadecimal + +```v oksyntax +a1 := byte(0xff) +b1 := i16(0xffff) +c1 := u32(0xffffffff) +d1 := u64(-1) +sc3 := '%hhx %hx %x %lx' +temp_s = strconv.v_sprintf(sc3, a1, b1, c1, d1) +println(temp_s) +``` + +``` +ff ffff ffffffff ffffffffffffffff +``` + +hexadecimal + +```v oksyntax +a2 := 125 +sc7 := '[%9x] [%9X] [%-9x] [%-9X] [%09x] [%09X]' +temp_s = strconv.v_sprintf(sc7, a2, a2, a2, a2, a2, a2) +println(temp_s) +``` + +``` +[ 7d] [ 7D] [7d ] [7D ] [00000007d] [00000007D] +``` + +floating points + +```v oksyntax +f0 := 0.312345 +f1 := 200000.0 +f2 := -1234.300e6 +f3 := 1234.300e-6 +sc4 := '[%-20.3e] [%20.3e] [%-020.3e] [%-020.3E] [%-020.3e] [%-020.3e]' +temp_s = strconv.v_sprintf(sc4, f0, f1, f1, f1, f2, f3) +println(temp_s) +``` + +``` +[3.123e-01 ] [ 2.000e+05] [2.000e+05 ] [2.000E+05 ] [-1.234e+09 ] [1.234e-03 ] +``` + +float automatic notations + +```v oksyntax +mut ft := -1e-7 +mut x := 0 +sc8 := '[%20g][%20G]|' +for x < 12 { + temp_s = strconv.v_sprintf(sc8, ft, ft) + println('$temp_s\n') + ft = ft * 10.0 + x++ +} +``` + +``` +[ -1e-07][ -1E-07]| +[ -1e-06][ -1E-06]| +[ -1e-05][ -1E-05]| +[ -0.0001][ -0.0001]| +[ -0.001][ -0.001]| +[ -0.01][ -0.01]| +[ -0.1][ -0.1]| +[ -1][ -1]| +[ -10][ -10]| +[ -100][ -100]| +[ -1000][ -1000]| +[ -10000][ -10000]| + +``` + +## Utility functions + +The format module also has some utility functions: + +```v oksyntax nofmt +// calling struct +struct BF_param { + pad_ch byte = ` ` // padding char + len0 int = -1 // default len for whole the number or string + len1 int = 6 // number of decimal digits, if needed + positive bool = true // mandatory: the sign of the number passed + sign_flag bool = false // flag for print sign as prefix in padding + allign Align_text = .right // alignment of the string + rm_tail_zero bool = false // remove the tail zeros from floats +} + +// utilities +fn format_dec(d u64, p BF_param) string +fn format_fl(f f64, p BF_param) string +fn format_es(f f64, p BF_param) string +fn remove_tail_zeros(s string) string +``` + +`format_dec` format the integer number using the parameters in the `BF_param` struct. + +`format_fl` format a float number in normal notation using the parameters in the `BF_param` struct. + +`format_es format a float number in scientific notation using the parameters in the BF_param` +struct. + +`remove_tail_zeros` removes the tailing zeros from a floating point number as string. diff --git a/v_windows/v/vlib/strconv/format.v b/v_windows/v/vlib/strconv/format.v new file mode 100644 index 0000000..e11a604 --- /dev/null +++ b/v_windows/v/vlib/strconv/format.v @@ -0,0 +1,113 @@ +module strconv + +/* +printf/sprintf V implementation + +Copyright (c) 2020 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains the printf/sprintf functions +*/ +import strings + +pub enum Align_text { + right = 0 + left + center +} + +/* +Float conversion utility +*/ +const ( + // rounding value + dec_round = [ + f64(0.5), + 0.05, + 0.005, + 0.0005, + 0.00005, + 0.000005, + 0.0000005, + 0.00000005, + 0.000000005, + 0.0000000005, + 0.00000000005, + 0.000000000005, + 0.0000000000005, + 0.00000000000005, + 0.000000000000005, + 0.0000000000000005, + 0.00000000000000005, + 0.000000000000000005, + 0.0000000000000000005, + 0.00000000000000000005, + ] +) + +/* +const( + // rounding value + dec_round = [ + f64(0.44), + 0.044, + 0.0044, + 0.00044, + 0.000044, + 0.0000044, + 0.00000044, + 0.000000044, + 0.0000000044, + 0.00000000044, + 0.000000000044, + 0.0000000000044, + 0.00000000000044, + 0.000000000000044, + 0.0000000000000044, + 0.00000000000000044, + 0.000000000000000044, + 0.0000000000000000044, + 0.00000000000000000044, + 0.000000000000000000044, + ] +) +*/ +// max float 1.797693134862315708145274237317043567981e+308 + +/* +Single format functions +*/ +pub struct BF_param { +pub mut: + pad_ch byte = byte(` `) // padding char + len0 int = -1 // default len for whole the number or string + len1 int = 6 // number of decimal digits, if needed + positive bool = true // mandatory: the sign of the number passed + sign_flag bool // flag for print sign as prefix in padding + allign Align_text = .right // alignment of the string + rm_tail_zero bool // remove the tail zeros from floats +} + +pub fn format_str(s string, p BF_param) string { + if p.len0 <= 0 { + return s.clone() + } + dif := p.len0 - utf8_str_visible_length(s) + if dif <= 0 { + return s.clone() + } + mut res := strings.new_builder(s.len + dif) + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + return res.str() +} diff --git a/v_windows/v/vlib/strconv/format_mem.v b/v_windows/v/vlib/strconv/format_mem.v new file mode 100644 index 0000000..f3a11cb --- /dev/null +++ b/v_windows/v/vlib/strconv/format_mem.v @@ -0,0 +1,498 @@ +/*============================================================================= +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains string interpolation V functions +=============================================================================*/ +module strconv + +import strings + +// strings.Builder version of format_str +pub fn format_str_sb(s string, p BF_param, mut sb strings.Builder) { + if p.len0 <= 0 { + sb.write_string(s) + return + } + dif := p.len0 - utf8_str_visible_length(s) + if dif <= 0 { + sb.write_string(s) + return + } + + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + sb.write_b(p.pad_ch) + } + } + sb.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + sb.write_b(p.pad_ch) + } + } +} + +const ( + // digit pairs in reverse order + digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999' +) + +// format_dec_sb format a u64 +[direct_array_access] +pub fn format_dec_sb(d u64, p BF_param, mut res strings.Builder) { + mut n_char := dec_digits(d) + sign_len := if !p.positive || p.sign_flag { 1 } else { 0 } + number_len := sign_len + n_char + dif := p.len0 - number_len + mut sign_written := false + + if p.allign == .right { + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_written = true + } + } else { + res.write_b(`-`) + sign_written = true + } + } + // write the pad chars + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + + if !sign_written { + // no pad char, write the sign before the number + if p.positive { + if p.sign_flag { + res.write_b(`+`) + } + } else { + res.write_b(`-`) + } + } + + /* + // Legacy version + // max u64 18446744073709551615 => 20 byte + mut buf := [32]byte{} + mut i := 20 + mut d1 := d + for i >= (21 - n_char) { + buf[i] = byte(d1 % 10) + `0` + d1 = d1 / 10 + i-- + } + i++ + */ + + //=========================================== + // Speed version + // max u64 18446744073709551615 => 20 byte + mut buf := [32]byte{} + mut i := 20 + mut n := d + mut d_i := u64(0) + if n > 0 { + for n > 0 { + n1 := n / 100 + // calculate the digit_pairs start index + d_i = (n - (n1 * 100)) << 1 + n = n1 + unsafe { + buf[i] = strconv.digit_pairs.str[d_i] + } + i-- + d_i++ + unsafe { + buf[i] = strconv.digit_pairs.str[d_i] + } + i-- + } + i++ + // remove head zero + if d_i < 20 { + i++ + } + unsafe { res.write_ptr(&buf[i], n_char) } + } else { + // we have a zero no need of more code! + res.write_b(`0`) + } + //=========================================== + + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + return +} + +[direct_array_access; manualfree] +pub fn f64_to_str_lnd1(f f64, dec_digit int) string { + unsafe { + // we add the rounding value + s := f64_to_str(f + dec_round[dec_digit], 18) + // check for +inf -inf Nan + if s.len > 2 && (s[0] == `n` || s[1] == `i`) { + return s + } + + m_sgn_flag := false + mut sgn := 1 + mut b := [26]byte{} + mut d_pos := 1 + mut i := 0 + mut i1 := 0 + mut exp := 0 + mut exp_sgn := 1 + + mut dot_res_sp := -1 + + // get sign and deciaml parts + for c in s { + if c == `-` { + sgn = -1 + i++ + } else if c == `+` { + sgn = 1 + i++ + } else if c >= `0` && c <= `9` { + b[i1] = c + i1++ + i++ + } else if c == `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i - 1 + } + i++ + } else if c == `e` { + i++ + break + } else { + s.free() + return '[Float conversion error!!]' + } + } + b[i1] = 0 + + // get exponent + if s[i] == `-` { + exp_sgn = -1 + i++ + } else if s[i] == `+` { + exp_sgn = 1 + i++ + } + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c] - `0`) + c++ + } + + // allocate exp+32 chars for the return string + // mut res := []byte{len:exp+32,init:`0`} + mut res := []byte{len: exp + 32, init: 0} + mut r_i := 0 // result string buffer index + + // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") + + // s no more needed + s.free() + + if sgn == 1 { + if m_sgn_flag { + res[r_i] = `+` + r_i++ + } + } else { + res[r_i] = `-` + r_i++ + } + + i = 0 + if exp_sgn >= 0 { + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + if i >= d_pos && exp >= 0 { + if exp == 0 { + dot_res_sp = r_i + res[r_i] = `.` + r_i++ + } + exp-- + } + } + for exp >= 0 { + res[r_i] = `0` + r_i++ + exp-- + } + // println("exp: $exp $r_i $dot_res_sp") + } else { + mut dot_p := true + for exp > 0 { + res[r_i] = `0` + r_i++ + exp-- + if dot_p { + dot_res_sp = r_i + res[r_i] = `.` + r_i++ + dot_p = false + } + } + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + } + } + + // no more digits needed, stop here + if dec_digit <= 0 { + tmp_res := tos(res.data, dot_res_sp).clone() + res.free() + return tmp_res + } + + // println("r_i-d_pos: ${r_i - d_pos}") + if dot_res_sp >= 0 { + if (r_i - dot_res_sp) > dec_digit { + r_i = dot_res_sp + dec_digit + 1 + } + res[r_i] = 0 + // println("result: [${tos(&res[0],r_i)}]") + tmp_res := tos(res.data, r_i).clone() + res.free() + return tmp_res + } else { + if dec_digit > 0 { + mut c1 := 0 + res[r_i] = `.` + r_i++ + for c1 < dec_digit { + res[r_i] = `0` + r_i++ + c1++ + } + res[r_i] = 0 + } + tmp_res := tos(res.data, r_i).clone() + res.free() + return tmp_res + } + } +} + +// strings.Builder version of format_fl +[manualfree] +pub fn format_fl(f f64, p BF_param) string { + unsafe { + mut s := '' + // mut fs := "1.2343" + mut fs := f64_to_str_lnd1(if f >= 0.0 { f } else { -f }, p.len1) + // println("Dario") + // println(fs) + + // error!! + if fs[0] == `[` { + s.free() + return fs + } + + if p.rm_tail_zero { + tmp := fs + fs = remove_tail_zeros(fs) + tmp.free() + } + mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = '+' + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = '-' + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +[manualfree] +pub fn format_es(f f64, p BF_param) string { + unsafe { + mut s := '' + mut fs := f64_to_str_pad(if f > 0 { f } else { -f }, p.len1) + if p.rm_tail_zero { + fs = remove_tail_zeros(fs) + } + mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = '+' + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = '-' + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +[direct_array_access] +pub fn remove_tail_zeros(s string) string { + unsafe { + mut buf := malloc_noscan(s.len + 1) + mut i_d := 0 + mut i_s := 0 + + // skip spaces + for i_s < s.len && s[i_s] !in [`-`, `+`] && (s[i_s] > `9` || s[i_s] < `0`) { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + // sign + if i_s < s.len && s[i_s] in [`-`, `+`] { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + + // integer part + for i_s < s.len && s[i_s] >= `0` && s[i_s] <= `9` { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + + // check decimals + if i_s < s.len && s[i_s] == `.` { + mut i_s1 := i_s + 1 + mut sum := 0 + for i_s1 < s.len && s[i_s1] >= `0` && s[i_s1] <= `9` { + sum += s[i_s1] - byte(`0`) + i_s1++ + } + // decimal part must be copied + if sum > 0 { + for c_i in i_s .. i_s1 { + buf[i_d] = s[c_i] + i_d++ + } + } + i_s = i_s1 + } + + if i_s < s.len && s[i_s] != `.` { + // check exponent + for { + buf[i_d] = s[i_s] + i_s++ + i_d++ + if i_s >= s.len { + break + } + } + } + + buf[i_d] = 0 + return tos(buf, i_d) + } +} diff --git a/v_windows/v/vlib/strconv/format_test.v b/v_windows/v/vlib/strconv/format_test.v new file mode 100644 index 0000000..745318b --- /dev/null +++ b/v_windows/v/vlib/strconv/format_test.v @@ -0,0 +1,110 @@ +import strconv + +fn test_format() { + mut temp_s := '' + mut tmp_str := '' + a0 := u32(10) + b0 := 200 + c0 := byte(12) + s0 := 'ciAo' + ch0 := `B` + f0 := 0.312345 + f1 := 200000.0 + f2 := -1234.300e6 + f3 := 1234.300e-6 + + sc0 := 'ciao: [%-08u] %d %hhd [%8s] [%08X] [%-20.4f] [%-20.4f] [%c]' + temp_s = strconv.v_sprintf(sc0, a0, b0, c0, s0, b0, f0, f1, ch0) + tmp_str = 'ciao: [10 ] 200 12 [ ciAo] [000000C8] [0.3123 ] [200000.0000 ] [B]' + // C.printf(sc0.str,a0 ,b0 ,c0 ,s0.str ,b0 ,f0, f1, ch0) + // println("\n$temp_s") + assert tmp_str == temp_s + + a := byte(12) + b := i16(13) + c := 14 + d := i64(15) + sc1 := '==>%hhd %hd %d %ld' + temp_s = strconv.v_sprintf(sc1, a, b, c, d) + tmp_str = '==>12 13 14 15' + // C.printf(sc1.str, a ,b ,c, d) + // println("\n$temp_s") + assert tmp_str == temp_s + + a1 := byte(0xff) + b1 := i16(0xffff) + c1 := u32(0xffff_ffff) + d1 := u64(-1) + sc2 := '%hhu %hu %u %lu' + temp_s = strconv.v_sprintf(sc2, a1, b1, c1, d1) + tmp_str = '255 65535 4294967295 18446744073709551615' + // C.printf(sc2.str, a1 ,b1 ,c1, d1) + // println("\n$temp_s") + assert tmp_str == temp_s + + sc3 := '%hhx %hx %x %lx' + temp_s = strconv.v_sprintf(sc3, a1, b1, c1, d1) + tmp_str = 'ff ffff ffffffff ffffffffffffffff' + // C.printf(sc3.str, a1 ,b1 ,c1, d1) + // println("\n$temp_s") + assert tmp_str == temp_s + + sc4 := '[%-20.3e] [%20.3e] [%-020.3e] [%-020.3E] [%-020.3e] [%-020.3e]' + temp_s = strconv.v_sprintf(sc4, f0, f1, f1, f1, f2, f3) + tmp_str = '[3.123e-01 ] [ 2.000e+05] [2.000e+05 ] [2.000E+05 ] [-1.234e+09 ] [1.234e-03 ]' + // C.printf(sc4.str, f0, f1, f1, f1, f2, f3) + // println("\n$temp_s") + assert tmp_str == temp_s + + sc5 := '[%.3f] [%0.3f] [%0.3F] [%0.3f] [%0.3F]' + temp_s = strconv.v_sprintf(sc5, f0, f1, f1, f2, f3) + tmp_str = '[0.312] [200000.000] [200000.000] [-1234300000.000] [0.001]' + // C.printf(sc5.str, f0, f1, f1, f2, f3, f3) + // println("\n$temp_s") + assert tmp_str == temp_s + + ml := 3 + sc6 := '%.*s [%05hhX]' + temp_s = strconv.v_sprintf(sc6, ml, s0, a) + tmp_str = 'ciA [0000C]' + // C.printf(sc6.str, ml, s0.str, a) + // println("\n$temp_s") + assert tmp_str == temp_s + + a2 := 125 + sc7 := '[%9x] [%9X] [%-9x] [%-9X] [%09x] [%09X]' + temp_s = strconv.v_sprintf(sc7, a2, a2, a2, a2, a2, a2) + tmp_str = '[ 7d] [ 7D] [7d ] [7D ] [00000007d] [00000007D]' + // C.printf(sc7.str, a2, a2, a2, a2, a2, a2) + // println("\n$temp_s") + assert tmp_str == temp_s + + g_test := [ + '[ -1e-07][ -1E-07]|', + '[ -1e-06][ -1E-06]|', + '[ -1e-05][ -1E-05]|', + '[ -0.0001][ -0.0001]|', + '[ -0.001][ -0.001]|', + '[ -0.01][ -0.01]|', + '[ -0.1][ -0.1]|', + '[ -1][ -1]|', + '[ -10][ -10]|', + '[ -100][ -100]|', + '[ -1000][ -1000]|', + '[ -10000][ -10000]|', + ] + + mut ft := -1e-7 + mut x := 0 + mut cnt := 0 + sc8 := '[%20g][%20G]|' + for x < 12 { + temp_s = strconv.v_sprintf(sc8, ft, ft) + // C.printf(sc8.str, ft, ft) + // println("\n$temp_s") + assert temp_s == g_test[cnt] + ft = ft * 10.0 + x++ + cnt++ + } +} diff --git a/v_windows/v/vlib/strconv/ftoa.v b/v_windows/v/vlib/strconv/ftoa.v new file mode 100644 index 0000000..700ecc7 --- /dev/null +++ b/v_windows/v/vlib/strconv/ftoa.v @@ -0,0 +1,39 @@ +module strconv + +/* +f32/f64 ftoa functions + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains the f32/f64 ftoa functions + +These functions are based on the work of: +Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN +Conference on Programming Language Design and ImplementationJune 2018 +Pages 270–282 https://doi.org/10.1145/3192366.3192369 + +inspired by the Go version here: +https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea +*/ + +[inline] +pub fn ftoa_64(f f64) string { + return f64_to_str(f, 17) +} + +[inline] +pub fn ftoa_long_64(f f64) string { + return f64_to_str_l(f) +} + +[inline] +pub fn ftoa_32(f f32) string { + return f32_to_str(f, 8) +} + +[inline] +pub fn ftoa_long_32(f f32) string { + return f32_to_str_l(f) +} diff --git a/v_windows/v/vlib/strconv/number_to_base.v b/v_windows/v/vlib/strconv/number_to_base.v new file mode 100644 index 0000000..0ca7e9c --- /dev/null +++ b/v_windows/v/vlib/strconv/number_to_base.v @@ -0,0 +1,61 @@ +module strconv + +const base_digits = '0123456789abcdefghijklmnopqrstuvwxyz' + +// format_int returns the string representation of the number n in base `radix` +// for digit values > 10, this function uses the small latin leters a-z. +[manualfree] +pub fn format_int(n i64, radix int) string { + unsafe { + if radix < 2 || radix > 36 { + panic('invalid radix: $radix . It should be => 2 and <= 36') + } + if n == 0 { + return '0' + } + mut n_copy := n + mut sign := '' + if n < 0 { + sign = '-' + n_copy = -n_copy + } + mut res := '' + for n_copy != 0 { + tmp_0 := res + tmp_1 := strconv.base_digits[n_copy % radix].ascii_str() + res = tmp_1 + res + tmp_0.free() + tmp_1.free() + // res = base_digits[n_copy % radix].ascii_str() + res + n_copy /= radix + } + return '$sign$res' + } +} + +// format_uint returns the string representation of the number n in base `radix` +// for digit values > 10, this function uses the small latin leters a-z. +[manualfree] +pub fn format_uint(n u64, radix int) string { + unsafe { + if radix < 2 || radix > 36 { + panic('invalid radix: $radix . It should be => 2 and <= 36') + } + if n == 0 { + return '0' + } + mut n_copy := n + mut res := '' + uradix := u64(radix) + for n_copy != 0 { + tmp_0 := res + tmp_1 := strconv.base_digits[n_copy % uradix].ascii_str() + res = tmp_1 + res + tmp_0.free() + tmp_1.free() + // res = base_digits[n_copy % uradix].ascii_str() + res + n_copy /= uradix + } + return res + } +} diff --git a/v_windows/v/vlib/strconv/number_to_base_test.v b/v_windows/v/vlib/strconv/number_to_base_test.v new file mode 100644 index 0000000..b313b0f --- /dev/null +++ b/v_windows/v/vlib/strconv/number_to_base_test.v @@ -0,0 +1,38 @@ +import strconv + +fn test_format_int() { + assert strconv.format_int(0, 2) == '0' + assert strconv.format_int(0, 10) == '0' + assert strconv.format_int(0, 16) == '0' + assert strconv.format_int(0, 36) == '0' + assert strconv.format_int(1, 2) == '1' + assert strconv.format_int(1, 10) == '1' + assert strconv.format_int(1, 16) == '1' + assert strconv.format_int(1, 36) == '1' + assert strconv.format_int(-1, 2) == '-1' + assert strconv.format_int(-1, 10) == '-1' + assert strconv.format_int(-1, 16) == '-1' + assert strconv.format_int(-1, 36) == '-1' + assert strconv.format_int(255, 2) == '11111111' + assert strconv.format_int(255, 8) == '377' + assert strconv.format_int(255, 10) == '255' + assert strconv.format_int(255, 16) == 'ff' + assert strconv.format_int(-255, 2) == '-11111111' + assert strconv.format_int(-255, 8) == '-377' + assert strconv.format_int(-255, 10) == '-255' + assert strconv.format_int(-255, 16) == '-ff' + for i in -256 .. 256 { + assert strconv.format_int(i, 10) == i.str() + } +} + +fn test_format_uint() { + assert strconv.format_uint(0, 2) == '0' + assert strconv.format_int(255, 2) == '11111111' + assert strconv.format_int(255, 8) == '377' + assert strconv.format_int(255, 10) == '255' + assert strconv.format_int(255, 16) == 'ff' + assert strconv.format_uint(18446744073709551615, 2) == '1111111111111111111111111111111111111111111111111111111111111111' + assert strconv.format_uint(18446744073709551615, 16) == 'ffffffffffffffff' + assert strconv.format_uint(683058467, 36) == 'baobab' +} diff --git a/v_windows/v/vlib/strconv/structs.v b/v_windows/v/vlib/strconv/structs.v new file mode 100644 index 0000000..35ade80 --- /dev/null +++ b/v_windows/v/vlib/strconv/structs.v @@ -0,0 +1,55 @@ +module strconv + +// The structure is filled by parser, then given to converter. +pub struct PrepNumber { +pub mut: + negative bool // 0 if positive number, 1 if negative + exponent int // power of 10 exponent + mantissa u64 // integer mantissa +} + +// dec32 is a floating decimal type representing m * 10^e. +struct Dec32 { +mut: + m u32 + e int +} + +// dec64 is a floating decimal type representing m * 10^e. +struct Dec64 { +mut: + m u64 + e int +} + +struct Uint128 { +mut: + lo u64 + hi u64 +} + +// support union for convert f32 to u32 +union Uf32 { +mut: + f f32 + u u32 +} + +// support union for convert f64 to u64 +union Uf64 { +mut: + f f64 + u u64 +} + +pub union Float64u { +pub mut: + f f64 + u u64 +} + +pub union Float32u { +pub mut: + f f32 + u u32 +} diff --git a/v_windows/v/vlib/strconv/tables.v b/v_windows/v/vlib/strconv/tables.v new file mode 100644 index 0000000..4f6d3ac --- /dev/null +++ b/v_windows/v/vlib/strconv/tables.v @@ -0,0 +1,738 @@ +module strconv + +const ( + pow5_num_bits_32 = 61 + pow5_inv_num_bits_32 = 59 + pow5_num_bits_64 = 121 + pow5_inv_num_bits_64 = 122 + + powers_of_10 = [ + u64(1e0), + u64(1e1), + u64(1e2), + u64(1e3), + u64(1e4), + u64(1e5), + u64(1e6), + u64(1e7), + u64(1e8), + u64(1e9), + u64(1e10), + u64(1e11), + u64(1e12), + u64(1e13), + u64(1e14), + u64(1e15), + u64(1e16), + u64(1e17) + // We only need to find the length of at most 17 digit numbers. + ] + + pow5_split_32 = [ + u64(1152921504606846976), + u64(1441151880758558720), + u64(1801439850948198400), + u64(2251799813685248000), + u64(1407374883553280000), + u64(1759218604441600000), + u64(2199023255552000000), + u64(1374389534720000000), + u64(1717986918400000000), + u64(2147483648000000000), + u64(1342177280000000000), + u64(1677721600000000000), + u64(2097152000000000000), + u64(1310720000000000000), + u64(1638400000000000000), + u64(2048000000000000000), + u64(1280000000000000000), + u64(1600000000000000000), + u64(2000000000000000000), + u64(1250000000000000000), + u64(1562500000000000000), + u64(1953125000000000000), + u64(1220703125000000000), + u64(1525878906250000000), + u64(1907348632812500000), + u64(1192092895507812500), + u64(1490116119384765625), + u64(1862645149230957031), + u64(1164153218269348144), + u64(1455191522836685180), + u64(1818989403545856475), + u64(2273736754432320594), + u64(1421085471520200371), + u64(1776356839400250464), + u64(2220446049250313080), + u64(1387778780781445675), + u64(1734723475976807094), + u64(2168404344971008868), + u64(1355252715606880542), + u64(1694065894508600678), + u64(2117582368135750847), + u64(1323488980084844279), + u64(1654361225106055349), + u64(2067951531382569187), + u64(1292469707114105741), + u64(1615587133892632177), + u64(2019483917365790221), + ] + + pow5_inv_split_32 = [ + u64(576460752303423489), + u64(461168601842738791), + u64(368934881474191033), + u64(295147905179352826), + u64(472236648286964522), + u64(377789318629571618), + u64(302231454903657294), + u64(483570327845851670), + u64(386856262276681336), + u64(309485009821345069), + u64(495176015714152110), + u64(396140812571321688), + u64(316912650057057351), + u64(507060240091291761), + u64(405648192073033409), + u64(324518553658426727), + u64(519229685853482763), + u64(415383748682786211), + u64(332306998946228969), + u64(531691198313966350), + u64(425352958651173080), + u64(340282366920938464), + u64(544451787073501542), + u64(435561429658801234), + u64(348449143727040987), + u64(557518629963265579), + u64(446014903970612463), + u64(356811923176489971), + u64(570899077082383953), + u64(456719261665907162), + u64(365375409332725730), + ] + + pow5_split_64 = [ + Uint128{u64(0x0000000000000000), u64(0x0100000000000000)}, + Uint128{u64(0x0000000000000000), u64(0x0140000000000000)}, + Uint128{u64(0x0000000000000000), u64(0x0190000000000000)}, + Uint128{u64(0x0000000000000000), u64(0x01f4000000000000)}, + Uint128{u64(0x0000000000000000), u64(0x0138800000000000)}, + Uint128{u64(0x0000000000000000), u64(0x0186a00000000000)}, + Uint128{u64(0x0000000000000000), u64(0x01e8480000000000)}, + Uint128{u64(0x0000000000000000), u64(0x01312d0000000000)}, + Uint128{u64(0x0000000000000000), u64(0x017d784000000000)}, + Uint128{u64(0x0000000000000000), u64(0x01dcd65000000000)}, + Uint128{u64(0x0000000000000000), u64(0x012a05f200000000)}, + Uint128{u64(0x0000000000000000), u64(0x0174876e80000000)}, + Uint128{u64(0x0000000000000000), u64(0x01d1a94a20000000)}, + Uint128{u64(0x0000000000000000), u64(0x012309ce54000000)}, + Uint128{u64(0x0000000000000000), u64(0x016bcc41e9000000)}, + Uint128{u64(0x0000000000000000), u64(0x01c6bf5263400000)}, + Uint128{u64(0x0000000000000000), u64(0x011c37937e080000)}, + Uint128{u64(0x0000000000000000), u64(0x016345785d8a0000)}, + Uint128{u64(0x0000000000000000), u64(0x01bc16d674ec8000)}, + Uint128{u64(0x0000000000000000), u64(0x01158e460913d000)}, + Uint128{u64(0x0000000000000000), u64(0x015af1d78b58c400)}, + Uint128{u64(0x0000000000000000), u64(0x01b1ae4d6e2ef500)}, + Uint128{u64(0x0000000000000000), u64(0x010f0cf064dd5920)}, + Uint128{u64(0x0000000000000000), u64(0x0152d02c7e14af68)}, + Uint128{u64(0x0000000000000000), u64(0x01a784379d99db42)}, + Uint128{u64(0x4000000000000000), u64(0x0108b2a2c2802909)}, + Uint128{u64(0x9000000000000000), u64(0x014adf4b7320334b)}, + Uint128{u64(0x7400000000000000), u64(0x019d971e4fe8401e)}, + Uint128{u64(0x0880000000000000), u64(0x01027e72f1f12813)}, + Uint128{u64(0xcaa0000000000000), u64(0x01431e0fae6d7217)}, + Uint128{u64(0xbd48000000000000), u64(0x0193e5939a08ce9d)}, + Uint128{u64(0x2c9a000000000000), u64(0x01f8def8808b0245)}, + Uint128{u64(0x3be0400000000000), u64(0x013b8b5b5056e16b)}, + Uint128{u64(0x0ad8500000000000), u64(0x018a6e32246c99c6)}, + Uint128{u64(0x8d8e640000000000), u64(0x01ed09bead87c037)}, + Uint128{u64(0xb878fe8000000000), u64(0x013426172c74d822)}, + Uint128{u64(0x66973e2000000000), u64(0x01812f9cf7920e2b)}, + Uint128{u64(0x403d0da800000000), u64(0x01e17b84357691b6)}, + Uint128{u64(0xe826288900000000), u64(0x012ced32a16a1b11)}, + Uint128{u64(0x622fb2ab40000000), u64(0x0178287f49c4a1d6)}, + Uint128{u64(0xfabb9f5610000000), u64(0x01d6329f1c35ca4b)}, + Uint128{u64(0x7cb54395ca000000), u64(0x0125dfa371a19e6f)}, + Uint128{u64(0x5be2947b3c800000), u64(0x016f578c4e0a060b)}, + Uint128{u64(0x32db399a0ba00000), u64(0x01cb2d6f618c878e)}, + Uint128{u64(0xdfc9040047440000), u64(0x011efc659cf7d4b8)}, + Uint128{u64(0x17bb450059150000), u64(0x0166bb7f0435c9e7)}, + Uint128{u64(0xddaa16406f5a4000), u64(0x01c06a5ec5433c60)}, + Uint128{u64(0x8a8a4de845986800), u64(0x0118427b3b4a05bc)}, + Uint128{u64(0xad2ce16256fe8200), u64(0x015e531a0a1c872b)}, + Uint128{u64(0x987819baecbe2280), u64(0x01b5e7e08ca3a8f6)}, + Uint128{u64(0x1f4b1014d3f6d590), u64(0x0111b0ec57e6499a)}, + Uint128{u64(0xa71dd41a08f48af4), u64(0x01561d276ddfdc00)}, + Uint128{u64(0xd0e549208b31adb1), u64(0x01aba4714957d300)}, + Uint128{u64(0x828f4db456ff0c8e), u64(0x010b46c6cdd6e3e0)}, + Uint128{u64(0xa33321216cbecfb2), u64(0x014e1878814c9cd8)}, + Uint128{u64(0xcbffe969c7ee839e), u64(0x01a19e96a19fc40e)}, + Uint128{u64(0x3f7ff1e21cf51243), u64(0x0105031e2503da89)}, + Uint128{u64(0x8f5fee5aa43256d4), u64(0x014643e5ae44d12b)}, + Uint128{u64(0x7337e9f14d3eec89), u64(0x0197d4df19d60576)}, + Uint128{u64(0x1005e46da08ea7ab), u64(0x01fdca16e04b86d4)}, + Uint128{u64(0x8a03aec4845928cb), u64(0x013e9e4e4c2f3444)}, + Uint128{u64(0xac849a75a56f72fd), u64(0x018e45e1df3b0155)}, + Uint128{u64(0x17a5c1130ecb4fbd), u64(0x01f1d75a5709c1ab)}, + Uint128{u64(0xeec798abe93f11d6), u64(0x013726987666190a)}, + Uint128{u64(0xaa797ed6e38ed64b), u64(0x0184f03e93ff9f4d)}, + Uint128{u64(0x1517de8c9c728bde), u64(0x01e62c4e38ff8721)}, + Uint128{u64(0xad2eeb17e1c7976b), u64(0x012fdbb0e39fb474)}, + Uint128{u64(0xd87aa5ddda397d46), u64(0x017bd29d1c87a191)}, + Uint128{u64(0x4e994f5550c7dc97), u64(0x01dac74463a989f6)}, + Uint128{u64(0xf11fd195527ce9de), u64(0x0128bc8abe49f639)}, + Uint128{u64(0x6d67c5faa71c2456), u64(0x0172ebad6ddc73c8)}, + Uint128{u64(0x88c1b77950e32d6c), u64(0x01cfa698c95390ba)}, + Uint128{u64(0x957912abd28dfc63), u64(0x0121c81f7dd43a74)}, + Uint128{u64(0xbad75756c7317b7c), u64(0x016a3a275d494911)}, + Uint128{u64(0x298d2d2c78fdda5b), u64(0x01c4c8b1349b9b56)}, + Uint128{u64(0xd9f83c3bcb9ea879), u64(0x011afd6ec0e14115)}, + Uint128{u64(0x50764b4abe865297), u64(0x0161bcca7119915b)}, + Uint128{u64(0x2493de1d6e27e73d), u64(0x01ba2bfd0d5ff5b2)}, + Uint128{u64(0x56dc6ad264d8f086), u64(0x01145b7e285bf98f)}, + Uint128{u64(0x2c938586fe0f2ca8), u64(0x0159725db272f7f3)}, + Uint128{u64(0xf7b866e8bd92f7d2), u64(0x01afcef51f0fb5ef)}, + Uint128{u64(0xfad34051767bdae3), u64(0x010de1593369d1b5)}, + Uint128{u64(0x79881065d41ad19c), u64(0x015159af80444623)}, + Uint128{u64(0x57ea147f49218603), u64(0x01a5b01b605557ac)}, + Uint128{u64(0xb6f24ccf8db4f3c1), u64(0x01078e111c3556cb)}, + Uint128{u64(0xa4aee003712230b2), u64(0x014971956342ac7e)}, + Uint128{u64(0x4dda98044d6abcdf), u64(0x019bcdfabc13579e)}, + Uint128{u64(0xf0a89f02b062b60b), u64(0x010160bcb58c16c2)}, + Uint128{u64(0xacd2c6c35c7b638e), u64(0x0141b8ebe2ef1c73)}, + Uint128{u64(0x98077874339a3c71), u64(0x01922726dbaae390)}, + Uint128{u64(0xbe0956914080cb8e), u64(0x01f6b0f092959c74)}, + Uint128{u64(0xf6c5d61ac8507f38), u64(0x013a2e965b9d81c8)}, + Uint128{u64(0x34774ba17a649f07), u64(0x0188ba3bf284e23b)}, + Uint128{u64(0x01951e89d8fdc6c8), u64(0x01eae8caef261aca)}, + Uint128{u64(0x40fd3316279e9c3d), u64(0x0132d17ed577d0be)}, + Uint128{u64(0xd13c7fdbb186434c), u64(0x017f85de8ad5c4ed)}, + Uint128{u64(0x458b9fd29de7d420), u64(0x01df67562d8b3629)}, + Uint128{u64(0xcb7743e3a2b0e494), u64(0x012ba095dc7701d9)}, + Uint128{u64(0x3e5514dc8b5d1db9), u64(0x017688bb5394c250)}, + Uint128{u64(0x4dea5a13ae346527), u64(0x01d42aea2879f2e4)}, + Uint128{u64(0xb0b2784c4ce0bf38), u64(0x01249ad2594c37ce)}, + Uint128{u64(0x5cdf165f6018ef06), u64(0x016dc186ef9f45c2)}, + Uint128{u64(0xf416dbf7381f2ac8), u64(0x01c931e8ab871732)}, + Uint128{u64(0xd88e497a83137abd), u64(0x011dbf316b346e7f)}, + Uint128{u64(0xceb1dbd923d8596c), u64(0x01652efdc6018a1f)}, + Uint128{u64(0xc25e52cf6cce6fc7), u64(0x01be7abd3781eca7)}, + Uint128{u64(0xd97af3c1a40105dc), u64(0x01170cb642b133e8)}, + Uint128{u64(0x0fd9b0b20d014754), u64(0x015ccfe3d35d80e3)}, + Uint128{u64(0xd3d01cde90419929), u64(0x01b403dcc834e11b)}, + Uint128{u64(0x6462120b1a28ffb9), u64(0x01108269fd210cb1)}, + Uint128{u64(0xbd7a968de0b33fa8), u64(0x0154a3047c694fdd)}, + Uint128{u64(0x2cd93c3158e00f92), u64(0x01a9cbc59b83a3d5)}, + Uint128{u64(0x3c07c59ed78c09bb), u64(0x010a1f5b81324665)}, + Uint128{u64(0x8b09b7068d6f0c2a), u64(0x014ca732617ed7fe)}, + Uint128{u64(0x2dcc24c830cacf34), u64(0x019fd0fef9de8dfe)}, + Uint128{u64(0xdc9f96fd1e7ec180), u64(0x0103e29f5c2b18be)}, + Uint128{u64(0x93c77cbc661e71e1), u64(0x0144db473335deee)}, + Uint128{u64(0x38b95beb7fa60e59), u64(0x01961219000356aa)}, + Uint128{u64(0xc6e7b2e65f8f91ef), u64(0x01fb969f40042c54)}, + Uint128{u64(0xfc50cfcffbb9bb35), u64(0x013d3e2388029bb4)}, + Uint128{u64(0x3b6503c3faa82a03), u64(0x018c8dac6a0342a2)}, + Uint128{u64(0xca3e44b4f9523484), u64(0x01efb1178484134a)}, + Uint128{u64(0xbe66eaf11bd360d2), u64(0x0135ceaeb2d28c0e)}, + Uint128{u64(0x6e00a5ad62c83907), u64(0x0183425a5f872f12)}, + Uint128{u64(0x0980cf18bb7a4749), u64(0x01e412f0f768fad7)}, + Uint128{u64(0x65f0816f752c6c8d), u64(0x012e8bd69aa19cc6)}, + Uint128{u64(0xff6ca1cb527787b1), u64(0x017a2ecc414a03f7)}, + Uint128{u64(0xff47ca3e2715699d), u64(0x01d8ba7f519c84f5)}, + Uint128{u64(0xbf8cde66d86d6202), u64(0x0127748f9301d319)}, + Uint128{u64(0x2f7016008e88ba83), u64(0x017151b377c247e0)}, + Uint128{u64(0x3b4c1b80b22ae923), u64(0x01cda62055b2d9d8)}, + Uint128{u64(0x250f91306f5ad1b6), u64(0x012087d4358fc827)}, + Uint128{u64(0xee53757c8b318623), u64(0x0168a9c942f3ba30)}, + Uint128{u64(0x29e852dbadfde7ac), u64(0x01c2d43b93b0a8bd)}, + Uint128{u64(0x3a3133c94cbeb0cc), u64(0x0119c4a53c4e6976)}, + Uint128{u64(0xc8bd80bb9fee5cff), u64(0x016035ce8b6203d3)}, + Uint128{u64(0xbaece0ea87e9f43e), u64(0x01b843422e3a84c8)}, + Uint128{u64(0x74d40c9294f238a7), u64(0x01132a095ce492fd)}, + Uint128{u64(0xd2090fb73a2ec6d1), u64(0x0157f48bb41db7bc)}, + Uint128{u64(0x068b53a508ba7885), u64(0x01adf1aea12525ac)}, + Uint128{u64(0x8417144725748b53), u64(0x010cb70d24b7378b)}, + Uint128{u64(0x651cd958eed1ae28), u64(0x014fe4d06de5056e)}, + Uint128{u64(0xfe640faf2a8619b2), u64(0x01a3de04895e46c9)}, + Uint128{u64(0x3efe89cd7a93d00f), u64(0x01066ac2d5daec3e)}, + Uint128{u64(0xcebe2c40d938c413), u64(0x014805738b51a74d)}, + Uint128{u64(0x426db7510f86f518), u64(0x019a06d06e261121)}, + Uint128{u64(0xc9849292a9b4592f), u64(0x0100444244d7cab4)}, + Uint128{u64(0xfbe5b73754216f7a), u64(0x01405552d60dbd61)}, + Uint128{u64(0x7adf25052929cb59), u64(0x01906aa78b912cba)}, + Uint128{u64(0x1996ee4673743e2f), u64(0x01f485516e7577e9)}, + Uint128{u64(0xaffe54ec0828a6dd), u64(0x0138d352e5096af1)}, + Uint128{u64(0x1bfdea270a32d095), u64(0x018708279e4bc5ae)}, + Uint128{u64(0xa2fd64b0ccbf84ba), u64(0x01e8ca3185deb719)}, + Uint128{u64(0x05de5eee7ff7b2f4), u64(0x01317e5ef3ab3270)}, + Uint128{u64(0x0755f6aa1ff59fb1), u64(0x017dddf6b095ff0c)}, + Uint128{u64(0x092b7454a7f3079e), u64(0x01dd55745cbb7ecf)}, + Uint128{u64(0x65bb28b4e8f7e4c3), u64(0x012a5568b9f52f41)}, + Uint128{u64(0xbf29f2e22335ddf3), u64(0x0174eac2e8727b11)}, + Uint128{u64(0x2ef46f9aac035570), u64(0x01d22573a28f19d6)}, + Uint128{u64(0xdd58c5c0ab821566), u64(0x0123576845997025)}, + Uint128{u64(0x54aef730d6629ac0), u64(0x016c2d4256ffcc2f)}, + Uint128{u64(0x29dab4fd0bfb4170), u64(0x01c73892ecbfbf3b)}, + Uint128{u64(0xfa28b11e277d08e6), u64(0x011c835bd3f7d784)}, + Uint128{u64(0x38b2dd65b15c4b1f), u64(0x0163a432c8f5cd66)}, + Uint128{u64(0xc6df94bf1db35de7), u64(0x01bc8d3f7b3340bf)}, + Uint128{u64(0xdc4bbcf772901ab0), u64(0x0115d847ad000877)}, + Uint128{u64(0xd35eac354f34215c), u64(0x015b4e5998400a95)}, + Uint128{u64(0x48365742a30129b4), u64(0x01b221effe500d3b)}, + Uint128{u64(0x0d21f689a5e0ba10), u64(0x010f5535fef20845)}, + Uint128{u64(0x506a742c0f58e894), u64(0x01532a837eae8a56)}, + Uint128{u64(0xe4851137132f22b9), u64(0x01a7f5245e5a2ceb)}, + Uint128{u64(0x6ed32ac26bfd75b4), u64(0x0108f936baf85c13)}, + Uint128{u64(0x4a87f57306fcd321), u64(0x014b378469b67318)}, + Uint128{u64(0x5d29f2cfc8bc07e9), u64(0x019e056584240fde)}, + Uint128{u64(0xfa3a37c1dd7584f1), u64(0x0102c35f729689ea)}, + Uint128{u64(0xb8c8c5b254d2e62e), u64(0x014374374f3c2c65)}, + Uint128{u64(0x26faf71eea079fb9), u64(0x01945145230b377f)}, + Uint128{u64(0xf0b9b4e6a48987a8), u64(0x01f965966bce055e)}, + Uint128{u64(0x5674111026d5f4c9), u64(0x013bdf7e0360c35b)}, + Uint128{u64(0x2c111554308b71fb), u64(0x018ad75d8438f432)}, + Uint128{u64(0xb7155aa93cae4e7a), u64(0x01ed8d34e547313e)}, + Uint128{u64(0x326d58a9c5ecf10c), u64(0x013478410f4c7ec7)}, + Uint128{u64(0xff08aed437682d4f), u64(0x01819651531f9e78)}, + Uint128{u64(0x3ecada89454238a3), u64(0x01e1fbe5a7e78617)}, + Uint128{u64(0x873ec895cb496366), u64(0x012d3d6f88f0b3ce)}, + Uint128{u64(0x290e7abb3e1bbc3f), u64(0x01788ccb6b2ce0c2)}, + Uint128{u64(0xb352196a0da2ab4f), u64(0x01d6affe45f818f2)}, + Uint128{u64(0xb0134fe24885ab11), u64(0x01262dfeebbb0f97)}, + Uint128{u64(0x9c1823dadaa715d6), u64(0x016fb97ea6a9d37d)}, + Uint128{u64(0x031e2cd19150db4b), u64(0x01cba7de5054485d)}, + Uint128{u64(0x21f2dc02fad2890f), u64(0x011f48eaf234ad3a)}, + Uint128{u64(0xaa6f9303b9872b53), u64(0x01671b25aec1d888)}, + Uint128{u64(0xd50b77c4a7e8f628), u64(0x01c0e1ef1a724eaa)}, + Uint128{u64(0xc5272adae8f199d9), u64(0x01188d357087712a)}, + Uint128{u64(0x7670f591a32e004f), u64(0x015eb082cca94d75)}, + Uint128{u64(0xd40d32f60bf98063), u64(0x01b65ca37fd3a0d2)}, + Uint128{u64(0xc4883fd9c77bf03e), u64(0x0111f9e62fe44483)}, + Uint128{u64(0xb5aa4fd0395aec4d), u64(0x0156785fbbdd55a4)}, + Uint128{u64(0xe314e3c447b1a760), u64(0x01ac1677aad4ab0d)}, + Uint128{u64(0xaded0e5aaccf089c), u64(0x010b8e0acac4eae8)}, + Uint128{u64(0xd96851f15802cac3), u64(0x014e718d7d7625a2)}, + Uint128{u64(0x8fc2666dae037d74), u64(0x01a20df0dcd3af0b)}, + Uint128{u64(0x39d980048cc22e68), u64(0x010548b68a044d67)}, + Uint128{u64(0x084fe005aff2ba03), u64(0x01469ae42c8560c1)}, + Uint128{u64(0x4a63d8071bef6883), u64(0x0198419d37a6b8f1)}, + Uint128{u64(0x9cfcce08e2eb42a4), u64(0x01fe52048590672d)}, + Uint128{u64(0x821e00c58dd309a7), u64(0x013ef342d37a407c)}, + Uint128{u64(0xa2a580f6f147cc10), u64(0x018eb0138858d09b)}, + Uint128{u64(0x8b4ee134ad99bf15), u64(0x01f25c186a6f04c2)}, + Uint128{u64(0x97114cc0ec80176d), u64(0x0137798f428562f9)}, + Uint128{u64(0xfcd59ff127a01d48), u64(0x018557f31326bbb7)}, + Uint128{u64(0xfc0b07ed7188249a), u64(0x01e6adefd7f06aa5)}, + Uint128{u64(0xbd86e4f466f516e0), u64(0x01302cb5e6f642a7)}, + Uint128{u64(0xace89e3180b25c98), u64(0x017c37e360b3d351)}, + Uint128{u64(0x1822c5bde0def3be), u64(0x01db45dc38e0c826)}, + Uint128{u64(0xcf15bb96ac8b5857), u64(0x01290ba9a38c7d17)}, + Uint128{u64(0xc2db2a7c57ae2e6d), u64(0x01734e940c6f9c5d)}, + Uint128{u64(0x3391f51b6d99ba08), u64(0x01d022390f8b8375)}, + Uint128{u64(0x403b393124801445), u64(0x01221563a9b73229)}, + Uint128{u64(0x904a077d6da01956), u64(0x016a9abc9424feb3)}, + Uint128{u64(0x745c895cc9081fac), u64(0x01c5416bb92e3e60)}, + Uint128{u64(0x48b9d5d9fda513cb), u64(0x011b48e353bce6fc)}, + Uint128{u64(0x5ae84b507d0e58be), u64(0x01621b1c28ac20bb)}, + Uint128{u64(0x31a25e249c51eeee), u64(0x01baa1e332d728ea)}, + Uint128{u64(0x5f057ad6e1b33554), u64(0x0114a52dffc67992)}, + Uint128{u64(0xf6c6d98c9a2002aa), u64(0x0159ce797fb817f6)}, + Uint128{u64(0xb4788fefc0a80354), u64(0x01b04217dfa61df4)}, + Uint128{u64(0xf0cb59f5d8690214), u64(0x010e294eebc7d2b8)}, + Uint128{u64(0x2cfe30734e83429a), u64(0x0151b3a2a6b9c767)}, + Uint128{u64(0xf83dbc9022241340), u64(0x01a6208b50683940)}, + Uint128{u64(0x9b2695da15568c08), u64(0x0107d457124123c8)}, + Uint128{u64(0xc1f03b509aac2f0a), u64(0x0149c96cd6d16cba)}, + Uint128{u64(0x726c4a24c1573acd), u64(0x019c3bc80c85c7e9)}, + Uint128{u64(0xe783ae56f8d684c0), u64(0x0101a55d07d39cf1)}, + Uint128{u64(0x616499ecb70c25f0), u64(0x01420eb449c8842e)}, + Uint128{u64(0xf9bdc067e4cf2f6c), u64(0x019292615c3aa539)}, + Uint128{u64(0x782d3081de02fb47), u64(0x01f736f9b3494e88)}, + Uint128{u64(0x4b1c3e512ac1dd0c), u64(0x013a825c100dd115)}, + Uint128{u64(0x9de34de57572544f), u64(0x018922f31411455a)}, + Uint128{u64(0x455c215ed2cee963), u64(0x01eb6bafd91596b1)}, + Uint128{u64(0xcb5994db43c151de), u64(0x0133234de7ad7e2e)}, + Uint128{u64(0x7e2ffa1214b1a655), u64(0x017fec216198ddba)}, + Uint128{u64(0x1dbbf89699de0feb), u64(0x01dfe729b9ff1529)}, + Uint128{u64(0xb2957b5e202ac9f3), u64(0x012bf07a143f6d39)}, + Uint128{u64(0x1f3ada35a8357c6f), u64(0x0176ec98994f4888)}, + Uint128{u64(0x270990c31242db8b), u64(0x01d4a7bebfa31aaa)}, + Uint128{u64(0x5865fa79eb69c937), u64(0x0124e8d737c5f0aa)}, + Uint128{u64(0xee7f791866443b85), u64(0x016e230d05b76cd4)}, + Uint128{u64(0x2a1f575e7fd54a66), u64(0x01c9abd04725480a)}, + Uint128{u64(0x5a53969b0fe54e80), u64(0x011e0b622c774d06)}, + Uint128{u64(0xf0e87c41d3dea220), u64(0x01658e3ab7952047)}, + Uint128{u64(0xed229b5248d64aa8), u64(0x01bef1c9657a6859)}, + Uint128{u64(0x3435a1136d85eea9), u64(0x0117571ddf6c8138)}, + Uint128{u64(0x4143095848e76a53), u64(0x015d2ce55747a186)}, + Uint128{u64(0xd193cbae5b2144e8), u64(0x01b4781ead1989e7)}, + Uint128{u64(0xe2fc5f4cf8f4cb11), u64(0x0110cb132c2ff630)}, + Uint128{u64(0x1bbb77203731fdd5), u64(0x0154fdd7f73bf3bd)}, + Uint128{u64(0x62aa54e844fe7d4a), u64(0x01aa3d4df50af0ac)}, + Uint128{u64(0xbdaa75112b1f0e4e), u64(0x010a6650b926d66b)}, + Uint128{u64(0xad15125575e6d1e2), u64(0x014cffe4e7708c06)}, + Uint128{u64(0x585a56ead360865b), u64(0x01a03fde214caf08)}, + Uint128{u64(0x37387652c41c53f8), u64(0x010427ead4cfed65)}, + Uint128{u64(0x850693e7752368f7), u64(0x014531e58a03e8be)}, + Uint128{u64(0x264838e1526c4334), u64(0x01967e5eec84e2ee)}, + Uint128{u64(0xafda4719a7075402), u64(0x01fc1df6a7a61ba9)}, + Uint128{u64(0x0de86c7008649481), u64(0x013d92ba28c7d14a)}, + Uint128{u64(0x9162878c0a7db9a1), u64(0x018cf768b2f9c59c)}, + Uint128{u64(0xb5bb296f0d1d280a), u64(0x01f03542dfb83703)}, + Uint128{u64(0x5194f9e568323906), u64(0x01362149cbd32262)}, + Uint128{u64(0xe5fa385ec23ec747), u64(0x0183a99c3ec7eafa)}, + Uint128{u64(0x9f78c67672ce7919), u64(0x01e494034e79e5b9)}, + Uint128{u64(0x03ab7c0a07c10bb0), u64(0x012edc82110c2f94)}, + Uint128{u64(0x04965b0c89b14e9c), u64(0x017a93a2954f3b79)}, + Uint128{u64(0x45bbf1cfac1da243), u64(0x01d9388b3aa30a57)}, + Uint128{u64(0x8b957721cb92856a), u64(0x0127c35704a5e676)}, + Uint128{u64(0x2e7ad4ea3e7726c4), u64(0x0171b42cc5cf6014)}, + Uint128{u64(0x3a198a24ce14f075), u64(0x01ce2137f7433819)}, + Uint128{u64(0xc44ff65700cd1649), u64(0x0120d4c2fa8a030f)}, + Uint128{u64(0xb563f3ecc1005bdb), u64(0x016909f3b92c83d3)}, + Uint128{u64(0xa2bcf0e7f14072d2), u64(0x01c34c70a777a4c8)}, + Uint128{u64(0x65b61690f6c847c3), u64(0x011a0fc668aac6fd)}, + Uint128{u64(0xbf239c35347a59b4), u64(0x016093b802d578bc)}, + Uint128{u64(0xeeec83428198f021), u64(0x01b8b8a6038ad6eb)}, + Uint128{u64(0x7553d20990ff9615), u64(0x01137367c236c653)}, + Uint128{u64(0x52a8c68bf53f7b9a), u64(0x01585041b2c477e8)}, + Uint128{u64(0x6752f82ef28f5a81), u64(0x01ae64521f7595e2)}, + Uint128{u64(0x8093db1d57999890), u64(0x010cfeb353a97dad)}, + Uint128{u64(0xe0b8d1e4ad7ffeb4), u64(0x01503e602893dd18)}, + Uint128{u64(0x18e7065dd8dffe62), u64(0x01a44df832b8d45f)}, + Uint128{u64(0x6f9063faa78bfefd), u64(0x0106b0bb1fb384bb)}, + Uint128{u64(0x4b747cf9516efebc), u64(0x01485ce9e7a065ea)}, + Uint128{u64(0xde519c37a5cabe6b), u64(0x019a742461887f64)}, + Uint128{u64(0x0af301a2c79eb703), u64(0x01008896bcf54f9f)}, + Uint128{u64(0xcdafc20b798664c4), u64(0x0140aabc6c32a386)}, + Uint128{u64(0x811bb28e57e7fdf5), u64(0x0190d56b873f4c68)}, + Uint128{u64(0xa1629f31ede1fd72), u64(0x01f50ac6690f1f82)}, + Uint128{u64(0xa4dda37f34ad3e67), u64(0x013926bc01a973b1)}, + Uint128{u64(0x0e150c5f01d88e01), u64(0x0187706b0213d09e)}, + Uint128{u64(0x919a4f76c24eb181), u64(0x01e94c85c298c4c5)}, + Uint128{u64(0x7b0071aa39712ef1), u64(0x0131cfd3999f7afb)}, + Uint128{u64(0x59c08e14c7cd7aad), u64(0x017e43c8800759ba)}, + Uint128{u64(0xf030b199f9c0d958), u64(0x01ddd4baa0093028)}, + Uint128{u64(0x961e6f003c1887d7), u64(0x012aa4f4a405be19)}, + Uint128{u64(0xfba60ac04b1ea9cd), u64(0x01754e31cd072d9f)}, + Uint128{u64(0xfa8f8d705de65440), u64(0x01d2a1be4048f907)}, + Uint128{u64(0xfc99b8663aaff4a8), u64(0x0123a516e82d9ba4)}, + Uint128{u64(0x3bc0267fc95bf1d2), u64(0x016c8e5ca239028e)}, + Uint128{u64(0xcab0301fbbb2ee47), u64(0x01c7b1f3cac74331)}, + Uint128{u64(0x1eae1e13d54fd4ec), u64(0x011ccf385ebc89ff)}, + Uint128{u64(0xe659a598caa3ca27), u64(0x01640306766bac7e)}, + Uint128{u64(0x9ff00efefd4cbcb1), u64(0x01bd03c81406979e)}, + Uint128{u64(0x23f6095f5e4ff5ef), u64(0x0116225d0c841ec3)}, + Uint128{u64(0xecf38bb735e3f36a), u64(0x015baaf44fa52673)}, + Uint128{u64(0xe8306ea5035cf045), u64(0x01b295b1638e7010)}, + Uint128{u64(0x911e4527221a162b), u64(0x010f9d8ede39060a)}, + Uint128{u64(0x3565d670eaa09bb6), u64(0x015384f295c7478d)}, + Uint128{u64(0x82bf4c0d2548c2a3), u64(0x01a8662f3b391970)}, + Uint128{u64(0x51b78f88374d79a6), u64(0x01093fdd8503afe6)}, + Uint128{u64(0xe625736a4520d810), u64(0x014b8fd4e6449bdf)}, + Uint128{u64(0xdfaed044d6690e14), u64(0x019e73ca1fd5c2d7)}, + Uint128{u64(0xebcd422b0601a8cc), u64(0x0103085e53e599c6)}, + Uint128{u64(0xa6c092b5c78212ff), u64(0x0143ca75e8df0038)}, + Uint128{u64(0xd070b763396297bf), u64(0x0194bd136316c046)}, + Uint128{u64(0x848ce53c07bb3daf), u64(0x01f9ec583bdc7058)}, + Uint128{u64(0x52d80f4584d5068d), u64(0x013c33b72569c637)}, + Uint128{u64(0x278e1316e60a4831), u64(0x018b40a4eec437c5)}, + ] + + pow5_inv_split_64 = [ + Uint128{u64(0x0000000000000001), u64(0x0400000000000000)}, + Uint128{u64(0x3333333333333334), u64(0x0333333333333333)}, + Uint128{u64(0x28f5c28f5c28f5c3), u64(0x028f5c28f5c28f5c)}, + Uint128{u64(0xed916872b020c49c), u64(0x020c49ba5e353f7c)}, + Uint128{u64(0xaf4f0d844d013a93), u64(0x0346dc5d63886594)}, + Uint128{u64(0x8c3f3e0370cdc876), u64(0x029f16b11c6d1e10)}, + Uint128{u64(0xd698fe69270b06c5), u64(0x0218def416bdb1a6)}, + Uint128{u64(0xf0f4ca41d811a46e), u64(0x035afe535795e90a)}, + Uint128{u64(0xf3f70834acdae9f1), u64(0x02af31dc4611873b)}, + Uint128{u64(0x5cc5a02a23e254c1), u64(0x0225c17d04dad296)}, + Uint128{u64(0xfad5cd10396a2135), u64(0x036f9bfb3af7b756)}, + Uint128{u64(0xfbde3da69454e75e), u64(0x02bfaffc2f2c92ab)}, + Uint128{u64(0x2fe4fe1edd10b918), u64(0x0232f33025bd4223)}, + Uint128{u64(0x4ca19697c81ac1bf), u64(0x0384b84d092ed038)}, + Uint128{u64(0x3d4e1213067bce33), u64(0x02d09370d4257360)}, + Uint128{u64(0x643e74dc052fd829), u64(0x024075f3dceac2b3)}, + Uint128{u64(0x6d30baf9a1e626a7), u64(0x039a5652fb113785)}, + Uint128{u64(0x2426fbfae7eb5220), u64(0x02e1dea8c8da92d1)}, + Uint128{u64(0x1cebfcc8b9890e80), u64(0x024e4bba3a487574)}, + Uint128{u64(0x94acc7a78f41b0cc), u64(0x03b07929f6da5586)}, + Uint128{u64(0xaa23d2ec729af3d7), u64(0x02f394219248446b)}, + Uint128{u64(0xbb4fdbf05baf2979), u64(0x025c768141d369ef)}, + Uint128{u64(0xc54c931a2c4b758d), u64(0x03c7240202ebdcb2)}, + Uint128{u64(0x9dd6dc14f03c5e0b), u64(0x0305b66802564a28)}, + Uint128{u64(0x4b1249aa59c9e4d6), u64(0x026af8533511d4ed)}, + Uint128{u64(0x44ea0f76f60fd489), u64(0x03de5a1ebb4fbb15)}, + Uint128{u64(0x6a54d92bf80caa07), u64(0x0318481895d96277)}, + Uint128{u64(0x21dd7a89933d54d2), u64(0x0279d346de4781f9)}, + Uint128{u64(0x362f2a75b8622150), u64(0x03f61ed7ca0c0328)}, + Uint128{u64(0xf825bb91604e810d), u64(0x032b4bdfd4d668ec)}, + Uint128{u64(0xc684960de6a5340b), u64(0x0289097fdd7853f0)}, + Uint128{u64(0xd203ab3e521dc33c), u64(0x02073accb12d0ff3)}, + Uint128{u64(0xe99f7863b696052c), u64(0x033ec47ab514e652)}, + Uint128{u64(0x87b2c6b62bab3757), u64(0x02989d2ef743eb75)}, + Uint128{u64(0xd2f56bc4efbc2c45), u64(0x0213b0f25f69892a)}, + Uint128{u64(0x1e55793b192d13a2), u64(0x0352b4b6ff0f41de)}, + Uint128{u64(0x4b77942f475742e8), u64(0x02a8909265a5ce4b)}, + Uint128{u64(0xd5f9435905df68ba), u64(0x022073a8515171d5)}, + Uint128{u64(0x565b9ef4d6324129), u64(0x03671f73b54f1c89)}, + Uint128{u64(0xdeafb25d78283421), u64(0x02b8e5f62aa5b06d)}, + Uint128{u64(0x188c8eb12cecf681), u64(0x022d84c4eeeaf38b)}, + Uint128{u64(0x8dadb11b7b14bd9b), u64(0x037c07a17e44b8de)}, + Uint128{u64(0x7157c0e2c8dd647c), u64(0x02c99fb46503c718)}, + Uint128{u64(0x8ddfcd823a4ab6ca), u64(0x023ae629ea696c13)}, + Uint128{u64(0x1632e269f6ddf142), u64(0x0391704310a8acec)}, + Uint128{u64(0x44f581ee5f17f435), u64(0x02dac035a6ed5723)}, + Uint128{u64(0x372ace584c1329c4), u64(0x024899c4858aac1c)}, + Uint128{u64(0xbeaae3c079b842d3), u64(0x03a75c6da27779c6)}, + Uint128{u64(0x6555830061603576), u64(0x02ec49f14ec5fb05)}, + Uint128{u64(0xb7779c004de6912b), u64(0x0256a18dd89e626a)}, + Uint128{u64(0xf258f99a163db512), u64(0x03bdcf495a9703dd)}, + Uint128{u64(0x5b7a614811caf741), u64(0x02fe3f6de212697e)}, + Uint128{u64(0xaf951aa00e3bf901), u64(0x0264ff8b1b41edfe)}, + Uint128{u64(0x7f54f7667d2cc19b), u64(0x03d4cc11c5364997)}, + Uint128{u64(0x32aa5f8530f09ae3), u64(0x0310a3416a91d479)}, + Uint128{u64(0xf55519375a5a1582), u64(0x0273b5cdeedb1060)}, + Uint128{u64(0xbbbb5b8bc3c3559d), u64(0x03ec56164af81a34)}, + Uint128{u64(0x2fc916096969114a), u64(0x03237811d593482a)}, + Uint128{u64(0x596dab3ababa743c), u64(0x0282c674aadc39bb)}, + Uint128{u64(0x478aef622efb9030), u64(0x0202385d557cfafc)}, + Uint128{u64(0xd8de4bd04b2c19e6), u64(0x0336c0955594c4c6)}, + Uint128{u64(0xad7ea30d08f014b8), u64(0x029233aaaadd6a38)}, + Uint128{u64(0x24654f3da0c01093), u64(0x020e8fbbbbe454fa)}, + Uint128{u64(0x3a3bb1fc346680eb), u64(0x034a7f92c63a2190)}, + Uint128{u64(0x94fc8e635d1ecd89), u64(0x02a1ffa89e94e7a6)}, + Uint128{u64(0xaa63a51c4a7f0ad4), u64(0x021b32ed4baa52eb)}, + Uint128{u64(0xdd6c3b607731aaed), u64(0x035eb7e212aa1e45)}, + Uint128{u64(0x1789c919f8f488bd), u64(0x02b22cb4dbbb4b6b)}, + Uint128{u64(0xac6e3a7b2d906d64), u64(0x022823c3e2fc3c55)}, + Uint128{u64(0x13e390c515b3e23a), u64(0x03736c6c9e606089)}, + Uint128{u64(0xdcb60d6a77c31b62), u64(0x02c2bd23b1e6b3a0)}, + Uint128{u64(0x7d5e7121f968e2b5), u64(0x0235641c8e52294d)}, + Uint128{u64(0xc8971b698f0e3787), u64(0x0388a02db0837548)}, + Uint128{u64(0xa078e2bad8d82c6c), u64(0x02d3b357c0692aa0)}, + Uint128{u64(0xe6c71bc8ad79bd24), u64(0x0242f5dfcd20eee6)}, + Uint128{u64(0x0ad82c7448c2c839), u64(0x039e5632e1ce4b0b)}, + Uint128{u64(0x3be023903a356cfa), u64(0x02e511c24e3ea26f)}, + Uint128{u64(0x2fe682d9c82abd95), u64(0x0250db01d8321b8c)}, + Uint128{u64(0x4ca4048fa6aac8ee), u64(0x03b4919c8d1cf8e0)}, + Uint128{u64(0x3d5003a61eef0725), u64(0x02f6dae3a4172d80)}, + Uint128{u64(0x9773361e7f259f51), u64(0x025f1582e9ac2466)}, + Uint128{u64(0x8beb89ca6508fee8), u64(0x03cb559e42ad070a)}, + Uint128{u64(0x6fefa16eb73a6586), u64(0x0309114b688a6c08)}, + Uint128{u64(0xf3261abef8fb846b), u64(0x026da76f86d52339)}, + Uint128{u64(0x51d691318e5f3a45), u64(0x03e2a57f3e21d1f6)}, + Uint128{u64(0x0e4540f471e5c837), u64(0x031bb798fe8174c5)}, + Uint128{u64(0xd8376729f4b7d360), u64(0x027c92e0cb9ac3d0)}, + Uint128{u64(0xf38bd84321261eff), u64(0x03fa849adf5e061a)}, + Uint128{u64(0x293cad0280eb4bff), u64(0x032ed07be5e4d1af)}, + Uint128{u64(0xedca240200bc3ccc), u64(0x028bd9fcb7ea4158)}, + Uint128{u64(0xbe3b50019a3030a4), u64(0x02097b309321cde0)}, + Uint128{u64(0xc9f88002904d1a9f), u64(0x03425eb41e9c7c9a)}, + Uint128{u64(0x3b2d3335403daee6), u64(0x029b7ef67ee396e2)}, + Uint128{u64(0x95bdc291003158b8), u64(0x0215ff2b98b6124e)}, + Uint128{u64(0x892f9db4cd1bc126), u64(0x035665128df01d4a)}, + Uint128{u64(0x07594af70a7c9a85), u64(0x02ab840ed7f34aa2)}, + Uint128{u64(0x6c476f2c0863aed1), u64(0x0222d00bdff5d54e)}, + Uint128{u64(0x13a57eacda3917b4), u64(0x036ae67966562217)}, + Uint128{u64(0x0fb7988a482dac90), u64(0x02bbeb9451de81ac)}, + Uint128{u64(0xd95fad3b6cf156da), u64(0x022fefa9db1867bc)}, + Uint128{u64(0xf565e1f8ae4ef15c), u64(0x037fe5dc91c0a5fa)}, + Uint128{u64(0x911e4e608b725ab0), u64(0x02ccb7e3a7cd5195)}, + Uint128{u64(0xda7ea51a0928488d), u64(0x023d5fe9530aa7aa)}, + Uint128{u64(0xf7310829a8407415), u64(0x039566421e7772aa)}, + Uint128{u64(0x2c2739baed005cde), u64(0x02ddeb68185f8eef)}, + Uint128{u64(0xbcec2e2f24004a4b), u64(0x024b22b9ad193f25)}, + Uint128{u64(0x94ad16b1d333aa11), u64(0x03ab6ac2ae8ecb6f)}, + Uint128{u64(0xaa241227dc2954db), u64(0x02ef889bbed8a2bf)}, + Uint128{u64(0x54e9a81fe35443e2), u64(0x02593a163246e899)}, + Uint128{u64(0x2175d9cc9eed396a), u64(0x03c1f689ea0b0dc2)}, + Uint128{u64(0xe7917b0a18bdc788), u64(0x03019207ee6f3e34)}, + Uint128{u64(0xb9412f3b46fe393a), u64(0x0267a8065858fe90)}, + Uint128{u64(0xf535185ed7fd285c), u64(0x03d90cd6f3c1974d)}, + Uint128{u64(0xc42a79e57997537d), u64(0x03140a458fce12a4)}, + Uint128{u64(0x03552e512e12a931), u64(0x02766e9e0ca4dbb7)}, + Uint128{u64(0x9eeeb081e3510eb4), u64(0x03f0b0fce107c5f1)}, + Uint128{u64(0x4bf226ce4f740bc3), u64(0x0326f3fd80d304c1)}, + Uint128{u64(0xa3281f0b72c33c9c), u64(0x02858ffe00a8d09a)}, + Uint128{u64(0x1c2018d5f568fd4a), u64(0x020473319a20a6e2)}, + Uint128{u64(0xf9ccf48988a7fba9), u64(0x033a51e8f69aa49c)}, + Uint128{u64(0xfb0a5d3ad3b99621), u64(0x02950e53f87bb6e3)}, + Uint128{u64(0x2f3b7dc8a96144e7), u64(0x0210d8432d2fc583)}, + Uint128{u64(0xe52bfc7442353b0c), u64(0x034e26d1e1e608d1)}, + Uint128{u64(0xb756639034f76270), u64(0x02a4ebdb1b1e6d74)}, + Uint128{u64(0x2c451c735d92b526), u64(0x021d897c15b1f12a)}, + Uint128{u64(0x13a1c71efc1deea3), u64(0x0362759355e981dd)}, + Uint128{u64(0x761b05b2634b2550), u64(0x02b52adc44bace4a)}, + Uint128{u64(0x91af37c1e908eaa6), u64(0x022a88b036fbd83b)}, + Uint128{u64(0x82b1f2cfdb417770), u64(0x03774119f192f392)}, + Uint128{u64(0xcef4c23fe29ac5f3), u64(0x02c5cdae5adbf60e)}, + Uint128{u64(0x3f2a34ffe87bd190), u64(0x0237d7beaf165e72)}, + Uint128{u64(0x984387ffda5fb5b2), u64(0x038c8c644b56fd83)}, + Uint128{u64(0xe0360666484c915b), u64(0x02d6d6b6a2abfe02)}, + Uint128{u64(0x802b3851d3707449), u64(0x024578921bbccb35)}, + Uint128{u64(0x99dec082ebe72075), u64(0x03a25a835f947855)}, + Uint128{u64(0xae4bcd358985b391), u64(0x02e8486919439377)}, + Uint128{u64(0xbea30a913ad15c74), u64(0x02536d20e102dc5f)}, + Uint128{u64(0xfdd1aa81f7b560b9), u64(0x03b8ae9b019e2d65)}, + Uint128{u64(0x97daeece5fc44d61), u64(0x02fa2548ce182451)}, + Uint128{u64(0xdfe258a51969d781), u64(0x0261b76d71ace9da)}, + Uint128{u64(0x996a276e8f0fbf34), u64(0x03cf8be24f7b0fc4)}, + Uint128{u64(0xe121b9253f3fcc2a), u64(0x030c6fe83f95a636)}, + Uint128{u64(0xb41afa8432997022), u64(0x02705986994484f8)}, + Uint128{u64(0xecf7f739ea8f19cf), u64(0x03e6f5a4286da18d)}, + Uint128{u64(0x23f99294bba5ae40), u64(0x031f2ae9b9f14e0b)}, + Uint128{u64(0x4ffadbaa2fb7be99), u64(0x027f5587c7f43e6f)}, + Uint128{u64(0x7ff7c5dd1925fdc2), u64(0x03feef3fa6539718)}, + Uint128{u64(0xccc637e4141e649b), u64(0x033258ffb842df46)}, + Uint128{u64(0xd704f983434b83af), u64(0x028ead9960357f6b)}, + Uint128{u64(0x126a6135cf6f9c8c), u64(0x020bbe144cf79923)}, + Uint128{u64(0x83dd685618b29414), u64(0x0345fced47f28e9e)}, + Uint128{u64(0x9cb12044e08edcdd), u64(0x029e63f1065ba54b)}, + Uint128{u64(0x16f419d0b3a57d7d), u64(0x02184ff405161dd6)}, + Uint128{u64(0x8b20294dec3bfbfb), u64(0x035a19866e89c956)}, + Uint128{u64(0x3c19baa4bcfcc996), u64(0x02ae7ad1f207d445)}, + Uint128{u64(0xc9ae2eea30ca3adf), u64(0x02252f0e5b39769d)}, + Uint128{u64(0x0f7d17dd1add2afd), u64(0x036eb1b091f58a96)}, + Uint128{u64(0x3f97464a7be42264), u64(0x02bef48d41913bab)}, + Uint128{u64(0xcc790508631ce850), u64(0x02325d3dce0dc955)}, + Uint128{u64(0xe0c1a1a704fb0d4d), u64(0x0383c862e3494222)}, + Uint128{u64(0x4d67b4859d95a43e), u64(0x02cfd3824f6dce82)}, + Uint128{u64(0x711fc39e17aae9cb), u64(0x023fdc683f8b0b9b)}, + Uint128{u64(0xe832d2968c44a945), u64(0x039960a6cc11ac2b)}, + Uint128{u64(0xecf575453d03ba9e), u64(0x02e11a1f09a7bcef)}, + Uint128{u64(0x572ac4376402fbb1), u64(0x024dae7f3aec9726)}, + Uint128{u64(0x58446d256cd192b5), u64(0x03af7d985e47583d)}, + Uint128{u64(0x79d0575123dadbc4), u64(0x02f2cae04b6c4697)}, + Uint128{u64(0x94a6ac40e97be303), u64(0x025bd5803c569edf)}, + Uint128{u64(0x8771139b0f2c9e6c), u64(0x03c62266c6f0fe32)}, + Uint128{u64(0x9f8da948d8f07ebd), u64(0x0304e85238c0cb5b)}, + Uint128{u64(0xe60aedd3e0c06564), u64(0x026a5374fa33d5e2)}, + Uint128{u64(0xa344afb9679a3bd2), u64(0x03dd5254c3862304)}, + Uint128{u64(0xe903bfc78614fca8), u64(0x031775109c6b4f36)}, + Uint128{u64(0xba6966393810ca20), u64(0x02792a73b055d8f8)}, + Uint128{u64(0x2a423d2859b4769a), u64(0x03f510b91a22f4c1)}, + Uint128{u64(0xee9b642047c39215), u64(0x032a73c7481bf700)}, + Uint128{u64(0xbee2b680396941aa), u64(0x02885c9f6ce32c00)}, + Uint128{u64(0xff1bc53361210155), u64(0x0206b07f8a4f5666)}, + Uint128{u64(0x31c6085235019bbb), u64(0x033de73276e5570b)}, + Uint128{u64(0x27d1a041c4014963), u64(0x0297ec285f1ddf3c)}, + Uint128{u64(0xeca7b367d0010782), u64(0x021323537f4b18fc)}, + Uint128{u64(0xadd91f0c8001a59d), u64(0x0351d21f3211c194)}, + Uint128{u64(0xf17a7f3d3334847e), u64(0x02a7db4c280e3476)}, + Uint128{u64(0x279532975c2a0398), u64(0x021fe2a3533e905f)}, + Uint128{u64(0xd8eeb75893766c26), u64(0x0366376bb8641a31)}, + Uint128{u64(0x7a5892ad42c52352), u64(0x02b82c562d1ce1c1)}, + Uint128{u64(0xfb7a0ef102374f75), u64(0x022cf044f0e3e7cd)}, + Uint128{u64(0xc59017e8038bb254), u64(0x037b1a07e7d30c7c)}, + Uint128{u64(0x37a67986693c8eaa), u64(0x02c8e19feca8d6ca)}, + Uint128{u64(0xf951fad1edca0bbb), u64(0x023a4e198a20abd4)}, + Uint128{u64(0x28832ae97c76792b), u64(0x03907cf5a9cddfbb)}, + Uint128{u64(0x2068ef21305ec756), u64(0x02d9fd9154a4b2fc)}, + Uint128{u64(0x19ed8c1a8d189f78), u64(0x0247fe0ddd508f30)}, + Uint128{u64(0x5caf4690e1c0ff26), u64(0x03a66349621a7eb3)}, + Uint128{u64(0x4a25d20d81673285), u64(0x02eb82a11b48655c)}, + Uint128{u64(0x3b5174d79ab8f537), u64(0x0256021a7c39eab0)}, + Uint128{u64(0x921bee25c45b21f1), u64(0x03bcd02a605caab3)}, + Uint128{u64(0xdb498b5169e2818e), u64(0x02fd735519e3bbc2)}, + Uint128{u64(0x15d46f7454b53472), u64(0x02645c4414b62fcf)}, + Uint128{u64(0xefba4bed545520b6), u64(0x03d3c6d35456b2e4)}, + Uint128{u64(0xf2fb6ff110441a2b), u64(0x030fd242a9def583)}, + Uint128{u64(0x8f2f8cc0d9d014ef), u64(0x02730e9bbb18c469)}, + Uint128{u64(0xb1e5ae015c80217f), u64(0x03eb4a92c4f46d75)}, + Uint128{u64(0xc1848b344a001acc), u64(0x0322a20f03f6bdf7)}, + Uint128{u64(0xce03a2903b3348a3), u64(0x02821b3f365efe5f)}, + Uint128{u64(0xd802e873628f6d4f), u64(0x0201af65c518cb7f)}, + Uint128{u64(0x599e40b89db2487f), u64(0x0335e56fa1c14599)}, + Uint128{u64(0xe14b66fa17c1d399), u64(0x029184594e3437ad)}, + Uint128{u64(0x81091f2e7967dc7a), u64(0x020e037aa4f692f1)}, + Uint128{u64(0x9b41cb7d8f0c93f6), u64(0x03499f2aa18a84b5)}, + Uint128{u64(0xaf67d5fe0c0a0ff8), u64(0x02a14c221ad536f7)}, + Uint128{u64(0xf2b977fe70080cc7), u64(0x021aa34e7bddc592)}, + Uint128{u64(0x1df58cca4cd9ae0b), u64(0x035dd2172c9608eb)}, + Uint128{u64(0xe4c470a1d7148b3c), u64(0x02b174df56de6d88)}, + Uint128{u64(0x83d05a1b1276d5ca), u64(0x022790b2abe5246d)}, + Uint128{u64(0x9fb3c35e83f1560f), u64(0x0372811ddfd50715)}, + Uint128{u64(0xb2f635e5365aab3f), u64(0x02c200e4b310d277)}, + Uint128{u64(0xf591c4b75eaeef66), u64(0x0234cd83c273db92)}, + Uint128{u64(0xef4fa125644b18a3), u64(0x0387af39371fc5b7)}, + Uint128{u64(0x8c3fb41de9d5ad4f), u64(0x02d2f2942c196af9)}, + Uint128{u64(0x3cffc34b2177bdd9), u64(0x02425ba9bce12261)}, + Uint128{u64(0x94cc6bab68bf9628), u64(0x039d5f75fb01d09b)}, + Uint128{u64(0x10a38955ed6611b9), u64(0x02e44c5e6267da16)}, + Uint128{u64(0xda1c6dde5784dafb), u64(0x02503d184eb97b44)}, + Uint128{u64(0xf693e2fd58d49191), u64(0x03b394f3b128c53a)}, + Uint128{u64(0xc5431bfde0aa0e0e), u64(0x02f610c2f4209dc8)}, + Uint128{u64(0x6a9c1664b3bb3e72), u64(0x025e73cf29b3b16d)}, + Uint128{u64(0x10f9bd6dec5eca4f), u64(0x03ca52e50f85e8af)}, + Uint128{u64(0xda616457f04bd50c), u64(0x03084250d937ed58)}, + Uint128{u64(0xe1e783798d09773d), u64(0x026d01da475ff113)}, + Uint128{u64(0x030c058f480f252e), u64(0x03e19c9072331b53)}, + Uint128{u64(0x68d66ad906728425), u64(0x031ae3a6c1c27c42)}, + Uint128{u64(0x8711ef14052869b7), u64(0x027be952349b969b)}, + Uint128{u64(0x0b4fe4ecd50d75f2), u64(0x03f97550542c242c)}, + Uint128{u64(0xa2a650bd773df7f5), u64(0x032df7737689b689)}, + Uint128{u64(0xb551da312c31932a), u64(0x028b2c5c5ed49207)}, + Uint128{u64(0x5ddb14f4235adc22), u64(0x0208f049e576db39)}, + Uint128{u64(0x2fc4ee536bc49369), u64(0x034180763bf15ec2)}, + Uint128{u64(0xbfd0bea92303a921), u64(0x029acd2b63277f01)}, + Uint128{u64(0x9973cbba8269541a), u64(0x021570ef8285ff34)}, + Uint128{u64(0x5bec792a6a42202a), u64(0x0355817f373ccb87)}, + Uint128{u64(0xe3239421ee9b4cef), u64(0x02aacdff5f63d605)}, + Uint128{u64(0xb5b6101b25490a59), u64(0x02223e65e5e97804)}, + Uint128{u64(0x22bce691d541aa27), u64(0x0369fd6fd64259a1)}, + Uint128{u64(0xb563eba7ddce21b9), u64(0x02bb31264501e14d)}, + Uint128{u64(0xf78322ecb171b494), u64(0x022f5a850401810a)}, + Uint128{u64(0x259e9e47824f8753), u64(0x037ef73b399c01ab)}, + Uint128{u64(0x1e187e9f9b72d2a9), u64(0x02cbf8fc2e1667bc)}, + Uint128{u64(0x4b46cbb2e2c24221), u64(0x023cc73024deb963)}, + Uint128{u64(0x120adf849e039d01), u64(0x039471e6a1645bd2)}, + Uint128{u64(0xdb3be603b19c7d9a), u64(0x02dd27ebb4504974)}, + Uint128{u64(0x7c2feb3627b0647c), u64(0x024a865629d9d45d)}, + Uint128{u64(0x2d197856a5e7072c), u64(0x03aa7089dc8fba2f)}, + Uint128{u64(0x8a7ac6abb7ec05bd), u64(0x02eec06e4a0c94f2)}, + Uint128{u64(0xd52f05562cbcd164), u64(0x025899f1d4d6dd8e)}, + Uint128{u64(0x21e4d556adfae8a0), u64(0x03c0f64fbaf1627e)}, + Uint128{u64(0xe7ea444557fbed4d), u64(0x0300c50c958de864)}, + Uint128{u64(0xecbb69d1132ff10a), u64(0x0267040a113e5383)}, + Uint128{u64(0xadf8a94e851981aa), u64(0x03d8067681fd526c)}, + Uint128{u64(0x8b2d543ed0e13488), u64(0x0313385ece6441f0)}, + Uint128{u64(0xd5bddcff0d80f6d3), u64(0x0275c6b23eb69b26)}, + Uint128{u64(0x892fc7fe7c018aeb), u64(0x03efa45064575ea4)}, + Uint128{u64(0x3a8c9ffec99ad589), u64(0x03261d0d1d12b21d)}, + Uint128{u64(0xc8707fff07af113b), u64(0x0284e40a7da88e7d)}, + Uint128{u64(0x39f39998d2f2742f), u64(0x0203e9a1fe2071fe)}, + Uint128{u64(0x8fec28f484b7204b), u64(0x033975cffd00b663)}, + Uint128{u64(0xd989ba5d36f8e6a2), u64(0x02945e3ffd9a2b82)}, + Uint128{u64(0x47a161e42bfa521c), u64(0x02104b66647b5602)}, + Uint128{u64(0x0c35696d132a1cf9), u64(0x034d4570a0c5566a)}, + Uint128{u64(0x09c454574288172d), u64(0x02a4378d4d6aab88)}, + Uint128{u64(0xa169dd129ba0128b), u64(0x021cf93dd7888939)}, + Uint128{u64(0x0242fb50f9001dab), u64(0x03618ec958da7529)}, + Uint128{u64(0x9b68c90d940017bc), u64(0x02b4723aad7b90ed)}, + Uint128{u64(0x4920a0d7a999ac96), u64(0x0229f4fbbdfc73f1)}, + Uint128{u64(0x750101590f5c4757), u64(0x037654c5fcc71fe8)}, + Uint128{u64(0x2a6734473f7d05df), u64(0x02c5109e63d27fed)}, + Uint128{u64(0xeeb8f69f65fd9e4c), u64(0x0237407eb641fff0)}, + Uint128{u64(0xe45b24323cc8fd46), u64(0x038b9a6456cfffe7)}, + Uint128{u64(0xb6af502830a0ca9f), u64(0x02d6151d123fffec)}, + Uint128{u64(0xf88c402026e7087f), u64(0x0244ddb0db666656)}, + Uint128{u64(0x2746cd003e3e73fe), u64(0x03a162b4923d708b)}, + Uint128{u64(0x1f6bd73364fec332), u64(0x02e7822a0e978d3c)}, + Uint128{u64(0xe5efdf5c50cbcf5b), u64(0x0252ce880bac70fc)}, + Uint128{u64(0x3cb2fefa1adfb22b), u64(0x03b7b0d9ac471b2e)}, + Uint128{u64(0x308f3261af195b56), u64(0x02f95a47bd05af58)}, + Uint128{u64(0x5a0c284e25ade2ab), u64(0x0261150630d15913)}, + Uint128{u64(0x29ad0d49d5e30445), u64(0x03ce8809e7b55b52)}, + Uint128{u64(0x548a7107de4f369d), u64(0x030ba007ec9115db)}, + Uint128{u64(0xdd3b8d9fe50c2bb1), u64(0x026fb3398a0dab15)}, + Uint128{u64(0x952c15cca1ad12b5), u64(0x03e5eb8f434911bc)}, + Uint128{u64(0x775677d6e7bda891), u64(0x031e560c35d40e30)}, + Uint128{u64(0xc5dec645863153a7), u64(0x027eab3cf7dcd826)}, + ] +) diff --git a/v_windows/v/vlib/strconv/utilities.v b/v_windows/v/vlib/strconv/utilities.v new file mode 100644 index 0000000..2098a73 --- /dev/null +++ b/v_windows/v/vlib/strconv/utilities.v @@ -0,0 +1,556 @@ +module strconv + +import math.bits +// import math + +/* +f32/f64 to string utilities + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains the f32/f64 to string utilities functions + +These functions are based on the work of: +Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN +Conference on Programming Language Design and ImplementationJune 2018 +Pages 270–282 https://doi.org/10.1145/3192366.3192369 + +inspired by the Go version here: +https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea +*/ + +// General Utilities +[if debug_strconv ?] +fn assert1(t bool, msg string) { + if !t { + panic(msg) + } +} + +[inline] +fn bool_to_int(b bool) int { + if b { + return 1 + } + return 0 +} + +[inline] +fn bool_to_u32(b bool) u32 { + if b { + return u32(1) + } + return u32(0) +} + +[inline] +fn bool_to_u64(b bool) u64 { + if b { + return u64(1) + } + return u64(0) +} + +fn get_string_special(neg bool, expZero bool, mantZero bool) string { + if !mantZero { + return 'nan' + } + if !expZero { + if neg { + return '-inf' + } else { + return '+inf' + } + } + if neg { + return '-0e+00' + } + return '0e+00' +} + +/* +32 bit functions +*/ + +fn mul_shift_32(m u32, mul u64, ishift int) u32 { + // QTODO + // assert ishift > 32 + + hi, lo := bits.mul_64(u64(m), mul) + shifted_sum := (lo >> u64(ishift)) + (hi << u64(64 - ishift)) + assert1(shifted_sum <= 2147483647, 'shiftedSum <= math.max_u32') + return u32(shifted_sum) +} + +fn mul_pow5_invdiv_pow2(m u32, q u32, j int) u32 { + return mul_shift_32(m, pow5_inv_split_32[q], j) +} + +fn mul_pow5_div_pow2(m u32, i u32, j int) u32 { + return mul_shift_32(m, pow5_split_32[i], j) +} + +fn pow5_factor_32(i_v u32) u32 { + mut v := i_v + for n := u32(0); true; n++ { + q := v / 5 + r := v % 5 + if r != 0 { + return n + } + v = q + } + return v +} + +// multiple_of_power_of_five_32 reports whether v is divisible by 5^p. +fn multiple_of_power_of_five_32(v u32, p u32) bool { + return pow5_factor_32(v) >= p +} + +// multiple_of_power_of_two_32 reports whether v is divisible by 2^p. +fn multiple_of_power_of_two_32(v u32, p u32) bool { + return u32(bits.trailing_zeros_32(v)) >= p +} + +// log10_pow2 returns floor(log_10(2^e)). +fn log10_pow2(e int) u32 { + // The first value this approximation fails for is 2^1651 + // which is just greater than 10^297. + assert1(e >= 0, 'e >= 0') + assert1(e <= 1650, 'e <= 1650') + return (u32(e) * 78913) >> 18 +} + +// log10_pow5 returns floor(log_10(5^e)). +fn log10_pow5(e int) u32 { + // The first value this approximation fails for is 5^2621 + // which is just greater than 10^1832. + assert1(e >= 0, 'e >= 0') + assert1(e <= 2620, 'e <= 2620') + return (u32(e) * 732923) >> 20 +} + +// pow5_bits returns ceil(log_2(5^e)), or else 1 if e==0. +fn pow5_bits(e int) int { + // This approximation works up to the point that the multiplication + // overflows at e = 3529. If the multiplication were done in 64 bits, + // it would fail at 5^4004 which is just greater than 2^9297. + assert1(e >= 0, 'e >= 0') + assert1(e <= 3528, 'e <= 3528') + return int(((u32(e) * 1217359) >> 19) + 1) +} + +/* +64 bit functions +*/ + +fn shift_right_128(v Uint128, shift int) u64 { + // The shift value is always modulo 64. + // In the current implementation of the 64-bit version + // of Ryu, the shift value is always < 64. + // (It is in the range [2, 59].) + // Check this here in case a future change requires larger shift + // values. In this case this function needs to be adjusted. + assert1(shift < 64, 'shift < 64') + return (v.hi << u64(64 - shift)) | (v.lo >> u32(shift)) +} + +fn mul_shift_64(m u64, mul Uint128, shift int) u64 { + hihi, hilo := bits.mul_64(m, mul.hi) + lohi, _ := bits.mul_64(m, mul.lo) + mut sum := Uint128{ + lo: lohi + hilo + hi: hihi + } + if sum.lo < lohi { + sum.hi++ // overflow + } + return shift_right_128(sum, shift - 64) +} + +fn pow5_factor_64(v_i u64) u32 { + mut v := v_i + for n := u32(0); true; n++ { + q := v / 5 + r := v % 5 + if r != 0 { + return n + } + v = q + } + return u32(0) +} + +fn multiple_of_power_of_five_64(v u64, p u32) bool { + return pow5_factor_64(v) >= p +} + +fn multiple_of_power_of_two_64(v u64, p u32) bool { + return u32(bits.trailing_zeros_64(v)) >= p +} + +/* +f64 to string with string format +*/ + +// TODO: Investigate precision issues +// f32_to_str_l return a string with the f32 converted in a string in decimal notation +[manualfree] +pub fn f32_to_str_l(f f32) string { + s := f32_to_str(f, 6) + res := fxx_to_str_l_parse(s) + unsafe { s.free() } + return res +} + +[manualfree] +pub fn f32_to_str_l_no_dot(f f32) string { + s := f32_to_str(f, 6) + res := fxx_to_str_l_parse_no_dot(s) + unsafe { s.free() } + return res +} + +[manualfree] +pub fn f64_to_str_l(f f64) string { + s := f64_to_str(f, 18) + res := fxx_to_str_l_parse(s) + unsafe { s.free() } + return res +} + +[manualfree] +pub fn f64_to_str_l_no_dot(f f64) string { + s := f64_to_str(f, 18) + res := fxx_to_str_l_parse_no_dot(s) + unsafe { s.free() } + return res +} + +// f64_to_str_l return a string with the f64 converted in a string in decimal notation +[manualfree] +pub fn fxx_to_str_l_parse(s string) string { + // check for +inf -inf Nan + if s.len > 2 && (s[0] == `n` || s[1] == `i`) { + return s.clone() + } + + m_sgn_flag := false + mut sgn := 1 + mut b := [26]byte{} + mut d_pos := 1 + mut i := 0 + mut i1 := 0 + mut exp := 0 + mut exp_sgn := 1 + + // get sign and decimal parts + for c in s { + if c == `-` { + sgn = -1 + i++ + } else if c == `+` { + sgn = 1 + i++ + } else if c >= `0` && c <= `9` { + b[i1] = c + i1++ + i++ + } else if c == `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i - 1 + } + i++ + } else if c == `e` { + i++ + break + } else { + return 'Float conversion error!!' + } + } + b[i1] = 0 + + // get exponent + if s[i] == `-` { + exp_sgn = -1 + i++ + } else if s[i] == `+` { + exp_sgn = 1 + i++ + } + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c] - `0`) + c++ + } + + // allocate exp+32 chars for the return string + mut res := []byte{len: exp + 32, init: 0} + mut r_i := 0 // result string buffer index + + // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") + + if sgn == 1 { + if m_sgn_flag { + res[r_i] = `+` + r_i++ + } + } else { + res[r_i] = `-` + r_i++ + } + + i = 0 + if exp_sgn >= 0 { + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + if i >= d_pos && exp >= 0 { + if exp == 0 { + res[r_i] = `.` + r_i++ + } + exp-- + } + } + for exp >= 0 { + res[r_i] = `0` + r_i++ + exp-- + } + } else { + mut dot_p := true + for exp > 0 { + res[r_i] = `0` + r_i++ + exp-- + if dot_p { + res[r_i] = `.` + r_i++ + dot_p = false + } + } + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + } + } + /* + // remove the dot form the numbers like 2. + if r_i > 1 && res[r_i-1] == `.` { + r_i-- + } + */ + res[r_i] = 0 + return unsafe { tos(res.data, r_i) } +} + +// f64_to_str_l return a string with the f64 converted in a string in decimal notation +[manualfree] +pub fn fxx_to_str_l_parse_no_dot(s string) string { + // check for +inf -inf Nan + if s.len > 2 && (s[0] == `n` || s[1] == `i`) { + return s.clone() + } + + m_sgn_flag := false + mut sgn := 1 + mut b := [26]byte{} + mut d_pos := 1 + mut i := 0 + mut i1 := 0 + mut exp := 0 + mut exp_sgn := 1 + + // get sign and decimal parts + for c in s { + if c == `-` { + sgn = -1 + i++ + } else if c == `+` { + sgn = 1 + i++ + } else if c >= `0` && c <= `9` { + b[i1] = c + i1++ + i++ + } else if c == `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i - 1 + } + i++ + } else if c == `e` { + i++ + break + } else { + return 'Float conversion error!!' + } + } + b[i1] = 0 + + // get exponent + if s[i] == `-` { + exp_sgn = -1 + i++ + } else if s[i] == `+` { + exp_sgn = 1 + i++ + } + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c] - `0`) + c++ + } + + // allocate exp+32 chars for the return string + mut res := []byte{len: exp + 32, init: 0} + mut r_i := 0 // result string buffer index + + // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") + + if sgn == 1 { + if m_sgn_flag { + res[r_i] = `+` + r_i++ + } + } else { + res[r_i] = `-` + r_i++ + } + + i = 0 + if exp_sgn >= 0 { + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + if i >= d_pos && exp >= 0 { + if exp == 0 { + res[r_i] = `.` + r_i++ + } + exp-- + } + } + for exp >= 0 { + res[r_i] = `0` + r_i++ + exp-- + } + } else { + mut dot_p := true + for exp > 0 { + res[r_i] = `0` + r_i++ + exp-- + if dot_p { + res[r_i] = `.` + r_i++ + dot_p = false + } + } + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + } + } + + // remove the dot form the numbers like 2. + if r_i > 1 && res[r_i - 1] == `.` { + r_i-- + } + + res[r_i] = 0 + return unsafe { tos(res.data, r_i) } +} + +// dec_digits return the number of decimal digit of an u64 +pub fn dec_digits(n u64) int { + if n <= 9_999_999_999 { // 1-10 + if n <= 99_999 { // 5 + if n <= 99 { // 2 + if n <= 9 { // 1 + return 1 + } else { + return 2 + } + } else { + if n <= 999 { // 3 + return 3 + } else { + if n <= 9999 { // 4 + return 4 + } else { + return 5 + } + } + } + } else { + if n <= 9_999_999 { // 7 + if n <= 999_999 { // 6 + return 6 + } else { + return 7 + } + } else { + if n <= 99_999_999 { // 8 + return 8 + } else { + if n <= 999_999_999 { // 9 + return 9 + } + return 10 + } + } + } + } else { + if n <= 999_999_999_999_999 { // 5 + if n <= 999_999_999_999 { // 2 + if n <= 99_999_999_999 { // 1 + return 11 + } else { + return 12 + } + } else { + if n <= 9_999_999_999_999 { // 3 + return 13 + } else { + if n <= 99_999_999_999_999 { // 4 + return 14 + } else { + return 15 + } + } + } + } else { + if n <= 99_999_999_999_999_999 { // 7 + if n <= 9_999_999_999_999_999 { // 6 + return 16 + } else { + return 17 + } + } else { + if n <= 999_999_999_999_999_999 { // 8 + return 18 + } else { + if n <= 9_999_999_999_999_999_999 { // 9 + return 19 + } + return 20 + } + } + } + } +} diff --git a/v_windows/v/vlib/strconv/vprintf.v b/v_windows/v/vlib/strconv/vprintf.v new file mode 100644 index 0000000..18e37b6 --- /dev/null +++ b/v_windows/v/vlib/strconv/vprintf.v @@ -0,0 +1,726 @@ +/*============================================================================= +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains string interpolation V functions +=============================================================================*/ +module strconv + +import strings + +enum Char_parse_state { + start + norm_char + field_char + pad_ch + len_set_start + len_set_in + check_type + check_float + check_float_in + reset_params +} + +pub fn v_printf(str string, pt ...voidptr) { + print(v_sprintf(str, pt)) +} + +pub fn v_sprintf(str string, pt ...voidptr) string { + mut res := strings.new_builder(pt.len * 16) + + mut i := 0 // main string index + mut p_index := 0 // parameter index + mut sign := false // sign flag + mut allign := Align_text.right + mut len0 := -1 // forced length, if -1 free length + mut len1 := -1 // decimal part for floats + def_len1 := 6 // default value for len1 + mut pad_ch := byte(` `) // pad char + + // prefix chars for Length field + mut ch1 := `0` // +1 char if present else `0` + mut ch2 := `0` // +2 char if present else `0` + + mut status := Char_parse_state.norm_char + for i < str.len { + if status == .reset_params { + sign = false + allign = .right + len0 = -1 + len1 = -1 + pad_ch = ` ` + status = .norm_char + ch1 = `0` + ch2 = `0` + continue + } + + ch := str[i] + if ch != `%` && status == .norm_char { + res.write_b(ch) + i++ + continue + } + if ch == `%` && status == .norm_char { + status = .field_char + i++ + continue + } + + // single char, manage it here + if ch == `c` && status == .field_char { + v_sprintf_panic(p_index, pt.len) + d1 := unsafe { *(&byte(pt[p_index])) } + res.write_b(d1) + status = .reset_params + p_index++ + i++ + continue + } + + // pointer, manage it here + if ch == `p` && status == .field_char { + v_sprintf_panic(p_index, pt.len) + res.write_string('0x') + res.write_string(ptr_str(unsafe { pt[p_index] })) + status = .reset_params + p_index++ + i++ + continue + } + + if status == .field_char { + mut fc_ch1 := `0` + mut fc_ch2 := `0` + if (i + 1) < str.len { + fc_ch1 = str[i + 1] + if (i + 2) < str.len { + fc_ch2 = str[i + 2] + } + } + if ch == `+` { + sign = true + i++ + continue + } else if ch == `-` { + allign = .left + i++ + continue + } else if ch in [`0`, ` `] { + if allign == .right { + pad_ch = ch + } + i++ + continue + } else if ch == `'` { + i++ + continue + } else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` { + status = .check_float + i++ + continue + } + // manage "%.*s" precision field + else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` { + v_sprintf_panic(p_index, pt.len) + len := unsafe { *(&int(pt[p_index])) } + p_index++ + v_sprintf_panic(p_index, pt.len) + mut s := unsafe { *(&string(pt[p_index])) } + s = s[..len] + p_index++ + res.write_string(s) + status = .reset_params + i += 3 + continue + } + status = .len_set_start + continue + } + + if status == .len_set_start { + if ch >= `1` && ch <= `9` { + len0 = int(ch - `0`) + status = .len_set_in + i++ + continue + } + if ch == `.` { + status = .check_float + i++ + continue + } + status = .check_type + continue + } + + if status == .len_set_in { + if ch >= `0` && ch <= `9` { + len0 *= 10 + len0 += int(ch - `0`) + i++ + continue + } + if ch == `.` { + status = .check_float + i++ + continue + } + status = .check_type + continue + } + + if status == .check_float { + if ch >= `0` && ch <= `9` { + len1 = int(ch - `0`) + status = .check_float_in + i++ + continue + } + status = .check_type + continue + } + + if status == .check_float_in { + if ch >= `0` && ch <= `9` { + len1 *= 10 + len1 += int(ch - `0`) + i++ + continue + } + status = .check_type + continue + } + + if status == .check_type { + if ch == `l` { + if ch1 == `0` { + ch1 = `l` + i++ + continue + } else { + ch2 = `l` + i++ + continue + } + } else if ch == `h` { + if ch1 == `0` { + ch1 = `h` + i++ + continue + } else { + ch2 = `h` + i++ + continue + } + } + // signed integer + else if ch in [`d`, `i`] { + mut d1 := u64(0) + mut positive := true + + // println("$ch1 $ch2") + match ch1 { + // h for 16 bit int + // hh fot 8 bit int + `h` { + if ch2 == `h` { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&i8(pt[p_index])) } + positive = if x >= 0 { true } else { false } + d1 = if positive { u64(x) } else { u64(-x) } + } else { + x := unsafe { *(&i16(pt[p_index])) } + positive = if x >= 0 { true } else { false } + d1 = if positive { u64(x) } else { u64(-x) } + } + } + // l i64 + // ll i64 for now + `l` { + // placeholder for future 128bit integer code + /* + if ch2 == `l` { + v_sprintf_panic(p_index, pt.len) + x := *(&i128(pt[p_index])) + positive = if x >= 0 { true } else { false } + d1 = if positive { u128(x) } else { u128(-x) } + } else { + v_sprintf_panic(p_index, pt.len) + x := *(&i64(pt[p_index])) + positive = if x >= 0 { true } else { false } + d1 = if positive { u64(x) } else { u64(-x) } + } + */ + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&i64(pt[p_index])) } + positive = if x >= 0 { true } else { false } + d1 = if positive { u64(x) } else { u64(-x) } + } + // default int + else { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&int(pt[p_index])) } + positive = if x >= 0 { true } else { false } + d1 = if positive { u64(x) } else { u64(-x) } + } + } + res.write_string(format_dec_old(d1, + pad_ch: pad_ch + len0: len0 + len1: 0 + positive: positive + sign_flag: sign + allign: allign + )) + status = .reset_params + p_index++ + i++ + ch1 = `0` + ch2 = `0` + continue + } + // unsigned integer + else if ch == `u` { + mut d1 := u64(0) + positive := true + v_sprintf_panic(p_index, pt.len) + match ch1 { + // h for 16 bit unsigned int + // hh fot 8 bit unsigned int + `h` { + if ch2 == `h` { + d1 = u64(unsafe { *(&byte(pt[p_index])) }) + } else { + d1 = u64(unsafe { *(&u16(pt[p_index])) }) + } + } + // l u64 + // ll u64 for now + `l` { + // placeholder for future 128bit integer code + /* + if ch2 == `l` { + d1 = u128(*(&u128(pt[p_index]))) + } else { + d1 = u64(*(&u64(pt[p_index]))) + } + */ + d1 = u64(unsafe { *(&u64(pt[p_index])) }) + } + // default int + else { + d1 = u64(unsafe { *(&u32(pt[p_index])) }) + } + } + + res.write_string(format_dec_old(d1, + pad_ch: pad_ch + len0: len0 + len1: 0 + positive: positive + sign_flag: sign + allign: allign + )) + status = .reset_params + p_index++ + i++ + continue + } + // hex + else if ch in [`x`, `X`] { + v_sprintf_panic(p_index, pt.len) + mut s := '' + match ch1 { + // h for 16 bit int + // hh fot 8 bit int + `h` { + if ch2 == `h` { + x := unsafe { *(&i8(pt[p_index])) } + s = x.hex() + } else { + x := unsafe { *(&i16(pt[p_index])) } + s = x.hex() + } + } + // l i64 + // ll i64 for now + `l` { + // placeholder for future 128bit integer code + /* + if ch2 == `l` { + x := *(&i128(pt[p_index])) + s = x.hex() + } else { + x := *(&i64(pt[p_index])) + s = x.hex() + } + */ + x := unsafe { *(&i64(pt[p_index])) } + s = x.hex() + } + else { + x := unsafe { *(&int(pt[p_index])) } + s = x.hex() + } + } + + if ch == `X` { + s = s.to_upper() + } + + res.write_string(format_str(s, + pad_ch: pad_ch + len0: len0 + len1: 0 + positive: true + sign_flag: false + allign: allign + )) + status = .reset_params + p_index++ + i++ + continue + } + + // float and double + if ch in [`f`, `F`] { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&f64(pt[p_index])) } + positive := x >= f64(0.0) + len1 = if len1 >= 0 { len1 } else { def_len1 } + s := format_fl_old(f64(x), + pad_ch: pad_ch + len0: len0 + len1: len1 + positive: positive + sign_flag: sign + allign: allign + ) + res.write_string(if ch == `F` { s.to_upper() } else { s }) + status = .reset_params + p_index++ + i++ + continue + } else if ch in [`e`, `E`] { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&f64(pt[p_index])) } + positive := x >= f64(0.0) + len1 = if len1 >= 0 { len1 } else { def_len1 } + s := format_es_old(f64(x), + pad_ch: pad_ch + len0: len0 + len1: len1 + positive: positive + sign_flag: sign + allign: allign + ) + res.write_string(if ch == `E` { s.to_upper() } else { s }) + status = .reset_params + p_index++ + i++ + continue + } else if ch in [`g`, `G`] { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&f64(pt[p_index])) } + positive := x >= f64(0.0) + mut s := '' + tx := fabs(x) + if tx < 999_999.0 && tx >= 0.00001 { + // println("Here g format_fl [$tx]") + len1 = if len1 >= 0 { len1 + 1 } else { def_len1 } + s = format_fl_old(x, + pad_ch: pad_ch + len0: len0 + len1: len1 + positive: positive + sign_flag: sign + allign: allign + rm_tail_zero: true + ) + } else { + len1 = if len1 >= 0 { len1 + 1 } else { def_len1 } + s = format_es_old(x, + pad_ch: pad_ch + len0: len0 + len1: len1 + positive: positive + sign_flag: sign + allign: allign + rm_tail_zero: true + ) + } + res.write_string(if ch == `G` { s.to_upper() } else { s }) + status = .reset_params + p_index++ + i++ + continue + } + // string + else if ch == `s` { + v_sprintf_panic(p_index, pt.len) + s1 := unsafe { *(&string(pt[p_index])) } + pad_ch = ` ` + res.write_string(format_str(s1, + pad_ch: pad_ch + len0: len0 + len1: 0 + positive: true + sign_flag: false + allign: allign + )) + status = .reset_params + p_index++ + i++ + continue + } + } + + status = .reset_params + p_index++ + i++ + } + + if p_index != pt.len { + panic('$p_index % conversion specifiers, but given $pt.len args') + } + + return res.str() +} + +[inline] +fn v_sprintf_panic(idx int, len int) { + if idx >= len { + panic('${idx + 1} % conversion specifiers, but given only $len args') + } +} + +fn fabs(x f64) f64 { + if x < 0.0 { + return -x + } + return x +} + +// strings.Builder version of format_fl +[manualfree] +pub fn format_fl_old(f f64, p BF_param) string { + unsafe { + mut s := '' + // mut fs := "1.2343" + mut fs := f64_to_str_lnd1(if f >= 0.0 { f } else { -f }, p.len1) + // println("Dario") + // println(fs) + + // error!! + if fs[0] == `[` { + s.free() + return fs + } + + if p.rm_tail_zero { + tmp := fs + fs = remove_tail_zeros_old(fs) + tmp.free() + } + mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = '+' + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = '-' + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +[manualfree] +pub fn format_es_old(f f64, p BF_param) string { + unsafe { + mut s := '' + mut fs := f64_to_str_pad(if f > 0 { f } else { -f }, p.len1) + if p.rm_tail_zero { + fs = remove_tail_zeros_old(fs) + } + mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = '+' + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = '-' + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +pub fn remove_tail_zeros_old(s string) string { + mut i := 0 + mut last_zero_start := -1 + mut dot_pos := -1 + mut in_decimal := false + mut prev_ch := byte(0) + for i < s.len { + ch := unsafe { s.str[i] } + if ch == `.` { + in_decimal = true + dot_pos = i + } else if in_decimal { + if ch == `0` && prev_ch != `0` { + last_zero_start = i + } else if ch >= `1` && ch <= `9` { + last_zero_start = -1 + } else if ch == `e` { + break + } + } + prev_ch = ch + i++ + } + + mut tmp := '' + if last_zero_start > 0 { + if last_zero_start == dot_pos + 1 { + tmp = s[..dot_pos] + s[i..] + } else { + tmp = s[..last_zero_start] + s[i..] + } + } else { + tmp = s + } + if unsafe { tmp.str[tmp.len - 1] } == `.` { + return tmp[..tmp.len - 1] + } + return tmp +} + +// max int64 9223372036854775807 +pub fn format_dec_old(d u64, p BF_param) string { + mut s := '' + mut res := strings.new_builder(20) + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + s = d.str() + } else { + if p.positive { + if p.sign_flag { + s = '+' + d.str() + } else { + s = d.str() + } + } else { + s = '-' + d.str() + } + } + dif := p.len0 - s.len + sign_len_diff + + if p.allign == .right { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 := 0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + return res.str() +} diff --git a/v_windows/v/vlib/strings/builder.js.v b/v_windows/v/vlib/strings/builder.js.v new file mode 100644 index 0000000..e445804 --- /dev/null +++ b/v_windows/v/vlib/strings/builder.js.v @@ -0,0 +1,51 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module strings + +pub struct Builder { +mut: + buf []byte +pub mut: + len int + initial_size int = 1 +} + +pub fn new_builder(initial_size int) Builder { + return Builder{ + buf: make(0, initial_size, sizeof(byte)) + initial_size: initial_size + } +} + +pub fn (mut b Builder) write_b(data byte) { + b.buf << data + b.len++ +} + +pub fn (mut b Builder) write_string(s string) { + b.buf.push_many(s.str, s.len) + // b.buf << []byte(s) // TODO + b.len += s.len +} + +pub fn (mut b Builder) writeln(s string) { + b.buf.push_many(s.str, s.len) + // b.buf << []byte(s) // TODO + b.buf << `\n` + b.len += s.len + 1 +} + +pub fn (b Builder) str() string { + x := &byte(b.buf.data) + return unsafe { x.vstring_with_len(b.len) } +} + +pub fn (mut b Builder) cut(n int) { + b.len -= n +} + +pub fn (mut b Builder) free() { + b.buf = make(0, b.initial_size, 1) + b.len = 0 +} diff --git a/v_windows/v/vlib/strings/builder.v b/v_windows/v/vlib/strings/builder.v new file mode 100644 index 0000000..4616a19 --- /dev/null +++ b/v_windows/v/vlib/strings/builder.v @@ -0,0 +1,163 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module strings + +// strings.Builder is used to efficiently append many strings to a large +// dynamically growing buffer, then use the resulting large string. Using +// a string builder is much better for performance/memory usage than doing +// constantly string concatenation. +pub type Builder = []byte + +// new_builder returns a new string builder, with an initial capacity of `initial_size` +pub fn new_builder(initial_size int) Builder { + return Builder([]byte{cap: initial_size}) +} + +// write_ptr writes `len` bytes provided byteptr to the accumulated buffer +[unsafe] +pub fn (mut b Builder) write_ptr(ptr &byte, len int) { + if len == 0 { + return + } + unsafe { b.push_many(ptr, len) } +} + +// write_rune appends a single rune to the accumulated buffer +[manualfree] +pub fn (mut b Builder) write_rune(r rune) { + mut buffer := [5]byte{} + res := unsafe { utf32_to_str_no_malloc(u32(r), &buffer[0]) } + if res.len == 0 { + return + } + unsafe { b.push_many(res.str, res.len) } +} + +// write_runes appends all the given runes to the accumulated buffer +pub fn (mut b Builder) write_runes(runes []rune) { + mut buffer := [5]byte{} + for r in runes { + res := unsafe { utf32_to_str_no_malloc(u32(r), &buffer[0]) } + if res.len == 0 { + continue + } + unsafe { b.push_many(res.str, res.len) } + } +} + +// write_b appends a single `data` byte to the accumulated buffer +pub fn (mut b Builder) write_b(data byte) { + b << data +} + +// write implements the Writer interface +pub fn (mut b Builder) write(data []byte) ?int { + if data.len == 0 { + return 0 + } + b << data + return data.len +} + +[inline] +pub fn (b &Builder) byte_at(n int) byte { + return unsafe { (&[]byte(b))[n] } +} + +// write appends the string `s` to the buffer +[inline] +pub fn (mut b Builder) write_string(s string) { + if s.len == 0 { + return + } + unsafe { b.push_many(s.str, s.len) } + // for c in s { + // b.buf << c + // } + // b.buf << []byte(s) // TODO +} + +// go_back discards the last `n` bytes from the buffer +pub fn (mut b Builder) go_back(n int) { + b.trim(b.len - n) +} + +// cut_last cuts the last `n` bytes from the buffer and returns them +pub fn (mut b Builder) cut_last(n int) string { + cut_pos := b.len - n + x := unsafe { (&[]byte(b))[cut_pos..] } + res := x.bytestr() + b.trim(cut_pos) + return res +} + +// cut_to cuts the string after `pos` and returns it. +// if `pos` is superior to builder length, returns an empty string +// and cancel further operations +pub fn (mut b Builder) cut_to(pos int) string { + if pos > b.len { + return '' + } + return b.cut_last(b.len - pos) +} + +// go_back_to resets the buffer to the given position `pos` +// NB: pos should be < than the existing buffer length. +pub fn (mut b Builder) go_back_to(pos int) { + b.trim(pos) +} + +// writeln appends the string `s`, and then a newline character. +[inline] +pub fn (mut b Builder) writeln(s string) { + // for c in s { + // b.buf << c + // } + if s.len > 0 { + unsafe { b.push_many(s.str, s.len) } + } + // b.buf << []byte(s) // TODO + b << byte(`\n`) +} + +// last_n(5) returns 'world' +// buf == 'hello world' +pub fn (b &Builder) last_n(n int) string { + if n > b.len { + return '' + } + x := unsafe { (&[]byte(b))[b.len - n..] } + return x.bytestr() +} + +// after(6) returns 'world' +// buf == 'hello world' +pub fn (b &Builder) after(n int) string { + if n >= b.len { + return '' + } + x := unsafe { (&[]byte(b))[n..] } + return x.bytestr() +} + +// str returns a copy of all of the accumulated buffer content. +// NB: after a call to b.str(), the builder b should not be +// used again, you need to call b.free() first, or just leave +// it to be freed by -autofree when it goes out of scope. +// The returned string *owns* its own separate copy of the +// accumulated data that was in the string builder, before the +// .str() call. +pub fn (mut b Builder) str() string { + b << byte(0) + bcopy := unsafe { &byte(memdup(b.data, b.len)) } + s := unsafe { bcopy.vstring_with_len(b.len - 1) } + b.trim(0) + return s +} + +// free is for manually freeing the contents of the buffer +[unsafe] +pub fn (mut b Builder) free() { + unsafe { free(b.data) } +} diff --git a/v_windows/v/vlib/strings/builder_test.v b/v_windows/v/vlib/strings/builder_test.v new file mode 100644 index 0000000..9180c09 --- /dev/null +++ b/v_windows/v/vlib/strings/builder_test.v @@ -0,0 +1,114 @@ +import strings + +type MyInt = int + +const maxn = 100000 + +fn test_sb() { + mut sb := strings.new_builder(100) + sb.write_string('hi') + sb.write_string('!') + sb.write_string('hello') + assert sb.len == 8 + sb_end := sb.str() + assert sb_end == 'hi!hello' + assert sb.len == 0 + /// + sb = strings.new_builder(10) + sb.write_string('a') + sb.write_string('b') + assert sb.len == 2 + assert sb.str() == 'ab' + // Test interpolation optimization + sb = strings.new_builder(10) + x := 10 + y := MyInt(20) + sb.writeln('x = $x y = $y') + res := sb.str() + assert res[res.len - 1] == `\n` + println('"$res"') + assert res.trim_space() == 'x = 10 y = 20' + // + sb = strings.new_builder(10) + sb.write_string('x = $x y = $y') + assert sb.str() == 'x = 10 y = 20' + //$if !windows { + sb = strings.new_builder(10) + sb.write_string('123456') + last_2 := sb.cut_last(2) + assert last_2 == '56' + final_sb := sb.str() + assert final_sb == '1234' + //} +} + +fn test_big_sb() { + mut sb := strings.new_builder(100) + mut sb2 := strings.new_builder(10000) + for i in 0 .. maxn { + sb.writeln(i.str()) + sb2.write_string('+') + } + s := sb.str() + lines := s.split_into_lines() + assert lines.len == maxn + assert lines[0] == '0' + assert lines[1] == '1' + assert lines[777] == '777' + assert lines[98765] == '98765' + println(sb2.len) + assert sb2.len == maxn +} + +fn test_byte_write() { + mut sb := strings.new_builder(100) + temp_str := 'byte testing' + mut count := 0 + for word in temp_str { + sb.write_b(word) + count++ + assert count == sb.len + } + sb_final := sb.str() + assert sb_final == temp_str +} + +fn test_strings_builder_reuse() { + mut sb := strings.new_builder(256) + sb.write_string('world') + assert sb.str() == 'world' + sb.write_string('hello') + assert sb.str() == 'hello' +} + +fn test_cut_to() { + mut sb := strings.new_builder(16) + sb.write_string('hello') + assert sb.cut_to(3) == 'lo' + assert sb.len == 3 + assert sb.cut_to(3) == '' + assert sb.len == 3 + assert sb.cut_to(0) == 'hel' + assert sb.cut_to(32) == '' + assert sb.len == 0 +} + +fn test_write_rune() { + mut sb := strings.new_builder(10) + sb.write_rune(`h`) + sb.write_rune(`e`) + sb.write_rune(`l`) + sb.write_rune(`l`) + sb.write_rune(`o`) + x := sb.str() + assert x == 'hello' +} + +fn test_write_runes() { + mut sb := strings.new_builder(20) + sb.write_runes([`h`, `e`, `l`, `l`, `o`]) + sb.write_rune(` `) + sb.write_runes([`w`, `o`, `r`, `l`, `d`]) + x := sb.str() + assert x == 'hello world' +} diff --git a/v_windows/v/vlib/strings/similarity.v b/v_windows/v/vlib/strings/similarity.v new file mode 100644 index 0000000..8d8de95 --- /dev/null +++ b/v_windows/v/vlib/strings/similarity.v @@ -0,0 +1,69 @@ +module strings + +// #-js +// use levenshtein distance algorithm to calculate +// the distance between between two strings (lower is closer) +pub fn levenshtein_distance(a string, b string) int { + mut f := [0].repeat(b.len + 1) + for j in 0 .. f.len { + f[j] = j + } + for ca in a { + mut j := 1 + mut fj1 := f[0] + f[0]++ + for cb in b { + mut mn := if f[j] + 1 <= f[j - 1] + 1 { f[j] + 1 } else { f[j - 1] + 1 } + if cb != ca { + mn = if mn <= fj1 + 1 { mn } else { fj1 + 1 } + } else { + mn = if mn <= fj1 { mn } else { fj1 } + } + fj1 = f[j] + f[j] = mn + j++ + } + } + return f[f.len - 1] +} + +// use levenshtein distance algorithm to calculate +// how similar two strings are as a percentage (higher is closer) +pub fn levenshtein_distance_percentage(a string, b string) f32 { + d := levenshtein_distance(a, b) + l := if a.len >= b.len { a.len } else { b.len } + return (1.00 - f32(d) / f32(l)) * 100.00 +} + +// implementation of Sørensen–Dice coefficient. +// find the similarity between two strings. +// returns coefficient between 0.0 (not similar) and 1.0 (exact match). +pub fn dice_coefficient(s1 string, s2 string) f32 { + if s1.len == 0 || s2.len == 0 { + return 0.0 + } + if s1 == s2 { + return 1.0 + } + if s1.len < 2 || s2.len < 2 { + return 0.0 + } + a := if s1.len > s2.len { s1 } else { s2 } + b := if a == s1 { s2 } else { s1 } + mut first_bigrams := map[string]int{} + for i in 0 .. a.len - 1 { + bigram := a[i..i + 2] + q := if bigram in first_bigrams { first_bigrams[bigram] + 1 } else { 1 } + first_bigrams[bigram] = q + } + mut intersection_size := 0 + for i in 0 .. b.len - 1 { + bigram := b[i..i + 2] + count := if bigram in first_bigrams { first_bigrams[bigram] } else { 0 } + if count > 0 { + first_bigrams[bigram] = count - 1 + intersection_size++ + } + } + return (2.0 * f32(intersection_size)) / (f32(a.len) + f32(b.len) - 2) +} diff --git a/v_windows/v/vlib/strings/similarity_test.v b/v_windows/v/vlib/strings/similarity_test.v new file mode 100644 index 0000000..965da45 --- /dev/null +++ b/v_windows/v/vlib/strings/similarity_test.v @@ -0,0 +1,13 @@ +import strings + +fn test_levenshtein_distance() { + assert strings.levenshtein_distance('', '') == 0 + assert strings.levenshtein_distance('one', 'one') == 0 + assert strings.levenshtein_distance('', 'two') == 3 + assert strings.levenshtein_distance('three', '') == 5 + assert strings.levenshtein_distance('bananna', '') == 7 + assert strings.levenshtein_distance('cats', 'hats') == 1 + assert strings.levenshtein_distance('hugs', 'shrugs') == 2 + assert strings.levenshtein_distance('broom', 'shroom') == 2 + assert strings.levenshtein_distance('flomax', 'volmax') == 3 +} diff --git a/v_windows/v/vlib/strings/strings.c.v b/v_windows/v/vlib/strings/strings.c.v new file mode 100644 index 0000000..c020f5b --- /dev/null +++ b/v_windows/v/vlib/strings/strings.c.v @@ -0,0 +1,38 @@ +module strings + +// strings.repeat - fill a string with `n` repetitions of the character `c` +pub fn repeat(c byte, n int) string { + if n <= 0 { + return '' + } + mut bytes := unsafe { malloc_noscan(n + 1) } + unsafe { + C.memset(bytes, c, n) + bytes[n] = `0` + } + return unsafe { bytes.vstring_with_len(n) } +} + +// strings.repeat_string - gives you `n` repetitions of the substring `s` +// NB: strings.repeat, that repeats a single byte, is between 2x +// and 24x faster than strings.repeat_string called for a 1 char string. +pub fn repeat_string(s string, n int) string { + if n <= 0 || s.len == 0 { + return '' + } + slen := s.len + blen := slen * n + mut bytes := unsafe { malloc_noscan(blen + 1) } + for bi in 0 .. n { + bislen := bi * slen + for si in 0 .. slen { + unsafe { + bytes[bislen + si] = s[si] + } + } + } + unsafe { + bytes[blen] = `0` + } + return unsafe { bytes.vstring_with_len(blen) } +} diff --git a/v_windows/v/vlib/strings/strings.js.v b/v_windows/v/vlib/strings/strings.js.v new file mode 100644 index 0000000..2681b94 --- /dev/null +++ b/v_windows/v/vlib/strings/strings.js.v @@ -0,0 +1,17 @@ +module strings + +pub fn repeat(c byte, n int) string { + if n <= 0 { + return '' + } + arr := [c].repeat(n) + return arr.bytestr() +} + +pub fn repeat_string(s string, n int) string { + /* + // TODO: uncomment this. It is commented for now, so that `v doc strings` works + res := # s.repeat(n) + return res + */ +} diff --git a/v_windows/v/vlib/strings/strings.v b/v_windows/v/vlib/strings/strings.v new file mode 100644 index 0000000..c01dd90 --- /dev/null +++ b/v_windows/v/vlib/strings/strings.v @@ -0,0 +1,13 @@ +module strings + +// import rand +// random returns a random string with `n` characters +/* +pub fn random(n int) string { + buf := vmalloc(n) + for i in 0..n { + buf[i] = rand.next() + } + return tos(buf) +} +*/ diff --git a/v_windows/v/vlib/strings/strings_test.v b/v_windows/v/vlib/strings/strings_test.v new file mode 100644 index 0000000..ff5ddf5 --- /dev/null +++ b/v_windows/v/vlib/strings/strings_test.v @@ -0,0 +1,14 @@ +import strings + +fn test_repeat() { + assert strings.repeat(`x`, 10) == 'xxxxxxxxxx' + assert strings.repeat(`a`, 1) == 'a' + assert strings.repeat(`a`, 0) == '' +} + +fn test_repeat_string() { + assert strings.repeat_string('abc', 3) == 'abcabcabc' + assert strings.repeat_string('abc', 1) == 'abc' + assert strings.repeat_string('abc', 0) == '' + assert strings.repeat_string('', 200) == '' +} diff --git a/v_windows/v/vlib/strings/textscanner/textscanner.v b/v_windows/v/vlib/strings/textscanner/textscanner.v new file mode 100644 index 0000000..5525137 --- /dev/null +++ b/v_windows/v/vlib/strings/textscanner/textscanner.v @@ -0,0 +1,154 @@ +module textscanner + +// TextScanner simplifies writing small scanners/parsers +// by providing safe methods to scan texts character by +// character, peek for the next characters, go back, etc. +pub struct TextScanner { +pub: + input string + ilen int +mut: + pos int // current position; pos is *always* kept in [0,ilen] +} + +// new returns a stack allocated instance of TextScanner. +pub fn new(input string) TextScanner { + return TextScanner{ + input: input + ilen: input.len + } +} + +// free frees all allocated resources. +[unsafe] +pub fn (mut ss TextScanner) free() { + unsafe { + ss.input.free() + } +} + +// remaining returns how many characters remain from current position. +[inline] +pub fn (ss &TextScanner) remaining() int { + return ss.ilen - ss.pos +} + +// next returns the next character code from the input text. +// next returns `-1` if it can't reach the next character. +// next advances the scanner position. +[direct_array_access; inline] +pub fn (mut ss TextScanner) next() int { + if ss.pos < ss.ilen { + opos := ss.pos + ss.pos++ + return ss.input[opos] + } + return -1 +} + +// skip skips one character ahead; `skip()` is slightly faster than `.next()`. +// `skip()` does not return a result. +[inline] +pub fn (mut ss TextScanner) skip() { + if ss.pos + 1 < ss.ilen { + ss.pos++ + } +} + +// skip_n skips ahead `n` characters, stopping at the end of the input. +[inline] +pub fn (mut ss TextScanner) skip_n(n int) { + ss.pos += n + if ss.pos > ss.ilen { + ss.pos = ss.ilen + } +} + +// peek returns the *next* character code from the input text. +// peek returns `-1` if it can't peek the next character. +// unlike `next()`, `peek()` does not change the state of the scanner. +[direct_array_access; inline] +pub fn (ss &TextScanner) peek() int { + if ss.pos < ss.ilen { + return ss.input[ss.pos] + } + return -1 +} + +// peek_n returns the character code from the input text at position + `n`. +// peek_n returns `-1` if it can't peek `n` characters ahead. +// ts.peek_n(0) == ts.current() . +// ts.peek_n(1) == ts.peek() . +[direct_array_access; inline] +pub fn (ss &TextScanner) peek_n(n int) int { + if ss.pos + n < ss.ilen { + return ss.input[ss.pos + n] + } + return -1 +} + +// back goes back one character from the current scanner position. +[inline] +pub fn (mut ss TextScanner) back() { + if ss.pos > 0 { + ss.pos-- + } +} + +// back_n goes back `n` characters from the current scanner position. +pub fn (mut ss TextScanner) back_n(n int) { + ss.pos -= n + if ss.pos < 0 { + ss.pos = 0 + } + if ss.pos > ss.ilen { + ss.pos = ss.ilen + } +} + +// peek_back returns the *previous* character code from the input text. +// peek_back returns `-1` if it can't peek the previous character. +// unlike `back()`, `peek_back()` does not change the state of the scanner. +[direct_array_access; inline] +pub fn (ss &TextScanner) peek_back() int { + return ss.peek_back_n(1) +} + +// peek_back_n returns the character code from the input text at position - `n`. +// peek_back_n returns `-1` if it can't peek `n` characters back. +// ts.peek_back_n(0) == ts.current() +// ts.peek_back_n(1) == ts.peek_back() +[direct_array_access; inline] +pub fn (ss &TextScanner) peek_back_n(n int) int { + offset := n + 1 + if ss.pos >= offset { + return ss.input[ss.pos - offset] + } + return -1 +} + +// current returns the current character code from the input text. +// current returns `-1` at the start of the input text. +// NB: after `c := ts.next()`, `ts.current()` will also return `c`. +[direct_array_access; inline] +pub fn (mut ss TextScanner) current() int { + if ss.pos > 0 { + return ss.input[ss.pos - 1] + } + return -1 +} + +// reset resets the internal state of the scanner +// After calling .reset(), .next() will start reading +// again from the start of the input text. +pub fn (mut ss TextScanner) reset() { + ss.pos = 0 +} + +// goto_end has the same effect as `for ts.next() != -1 {}` +// i.e. after calling .goto_end(), the scanner will be at +// the end of the input text. Further .next() calls will +// return -1, unless you go back. +pub fn (mut ss TextScanner) goto_end() { + ss.pos = ss.ilen +} diff --git a/v_windows/v/vlib/strings/textscanner/textscanner_test.v b/v_windows/v/vlib/strings/textscanner/textscanner_test.v new file mode 100644 index 0000000..e9d2487 --- /dev/null +++ b/v_windows/v/vlib/strings/textscanner/textscanner_test.v @@ -0,0 +1,159 @@ +import strings.textscanner + +fn test_remaining() { + mut s := textscanner.new('abc') + assert s.remaining() == 3 + s.next() + s.next() + assert s.remaining() == 1 + s.next() + assert s.remaining() == 0 + s.next() + s.next() + assert s.remaining() == 0 + s.reset() + assert s.remaining() == 3 +} + +fn test_next() { + mut s := textscanner.new('abc') + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 + assert s.next() == -1 + assert s.next() == -1 +} + +fn test_skip() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.skip() + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_skip_n() { + mut s := textscanner.new('abc') + s.skip_n(2) + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_peek() { + mut s := textscanner.new('abc') + assert s.peek() == `a` + assert s.peek() == `a` + assert s.peek() == `a` + // + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_peek_n() { + mut s := textscanner.new('abc') + assert s.peek_n(0) == `a` + assert s.peek_n(1) == `b` + assert s.peek_n(2) == `c` + assert s.peek_n(3) == -1 + assert s.peek_n(4) == -1 + // + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_back() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.back() + assert s.next() == `a` + assert s.next() == `b` + s.back() + assert s.next() == `b` + assert s.next() == `c` + assert s.next() == -1 +} + +fn test_back_n() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.back_n(10) + assert s.next() == `a` + assert s.next() == `b` + assert s.next() == `c` + s.back_n(2) + assert s.next() == `b` +} + +fn test_peek_back() { + mut s := textscanner.new('abc') + assert s.next() == `a` + assert s.next() == `b` + // check that calling .peek_back() multiple times + // does not change the state: + assert s.peek_back() == `a` + assert s.peek_back() == `a` + assert s.peek_back() == `a` + // advance, then peek_back again: + assert s.next() == `c` + assert s.peek_back() == `b` + // peeking before the start: + s.reset() + assert s.peek_back() == -1 + // peeking right at the end: + s.goto_end() + assert s.peek_back() == `b` +} + +fn test_peek_back_n() { + mut s := textscanner.new('abc') + s.goto_end() + assert s.peek_back_n(0) == `c` + assert s.peek_back_n(1) == `b` + assert s.peek_back_n(2) == `a` + assert s.peek_back_n(3) == -1 + assert s.peek_back_n(4) == -1 +} + +fn test_reset() { + mut s := textscanner.new('abc') + assert s.next() == `a` + s.next() + s.next() + assert s.next() == -1 + s.reset() + assert s.next() == `a` +} + +fn test_current() { + mut s := textscanner.new('abc') + assert s.current() == -1 + assert s.next() == `a` + assert s.current() == `a` + assert s.current() == `a` + assert s.peek_back() == -1 + assert s.next() == `b` + assert s.current() == `b` + assert s.current() == `b` + assert s.peek_back() == `a` + assert s.next() == `c` + assert s.current() == `c` + assert s.next() == -1 + assert s.current() == `c` + assert s.next() == -1 + assert s.current() == `c` + s.reset() + assert s.current() == -1 + assert s.next() == `a` + assert s.current() == `a` +} + +fn test_goto_end() { + mut s := textscanner.new('abc') + s.goto_end() + assert s.current() == `c` +} diff --git a/v_windows/v/vlib/sync/array_rlock_test.v b/v_windows/v/vlib/sync/array_rlock_test.v new file mode 100644 index 0000000..ad4f778 --- /dev/null +++ b/v_windows/v/vlib/sync/array_rlock_test.v @@ -0,0 +1,38 @@ +fn test_shared_modification() { + shared foo := &[2, 0, 5] + lock foo { + unsafe { + foo[1] = 3 + foo[0] *= 7 + foo[1]-- + foo[2] -= 2 + } + } + rlock foo { + unsafe { + assert foo[0] == 14 + assert foo[1] == 2 + assert foo[2] == 3 + } + } +} + +[direct_array_access] +fn test_shared_direct_modification() { + shared foo := &[2, 0, 5] + lock foo { + unsafe { + foo[1] = 3 + foo[0] *= 7 + foo[1]-- + foo[2] -= 2 + } + } + rlock foo { + unsafe { + assert foo[0] == 14 + assert foo[1] == 2 + assert foo[2] == 3 + } + } +} diff --git a/v_windows/v/vlib/sync/atomic2/atomic.v b/v_windows/v/vlib/sync/atomic2/atomic.v new file mode 100644 index 0000000..2ff64f2 --- /dev/null +++ b/v_windows/v/vlib/sync/atomic2/atomic.v @@ -0,0 +1,88 @@ +module atomic2 + +/* +Implements the atomic operations. For now TCC does not support +the atomic versions on nix so it uses locks to simulate the same behavor. +On windows tcc can simulate with other atomic operations. + +The @VEXEROOT/thirdparty/stdatomic contains compability header files +for stdatomic that supports both nix, windows and c++. + +This implementations should be regarded as alpha stage and be +further tested. +*/ + +#flag windows -I @VEXEROOT/thirdparty/stdatomic/win +#flag linux -I @VEXEROOT/thirdparty/stdatomic/nix +#flag darwin -I @VEXEROOT/thirdparty/stdatomic/nix +#flag freebsd -I @VEXEROOT/thirdparty/stdatomic/nix +#flag solaris -I @VEXEROOT/thirdparty/stdatomic/nix + +$if linux { + $if tinyc { + $if amd64 { + // most Linux distributions have /usr/lib/libatomic.so, but Ubuntu uses gcc version specific dir + #flag -L/usr/lib/gcc/x86_64-linux-gnu/6 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/7 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/8 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/9 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/10 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/11 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/12 + } $else $if arm64 { + #flag -L/usr/lib/gcc/aarch64-linux-gnu/6 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/7 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/8 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/9 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/10 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/11 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/12 + } + #flag -latomic + } +} +#include +// add_u64 adds provided delta as an atomic operation +pub fn add_u64(ptr &u64, delta int) bool { + res := C.atomic_fetch_add_u64(voidptr(ptr), delta) + return res == 0 +} + +// sub_u64 subtracts provided delta as an atomic operation +pub fn sub_u64(ptr &u64, delta int) bool { + res := C.atomic_fetch_sub_u64(voidptr(ptr), delta) + return res == 0 +} + +// add_i64 adds provided delta as an atomic operation +pub fn add_i64(ptr &i64, delta int) bool { + res := C.atomic_fetch_add_u64(voidptr(ptr), delta) + return res == 0 +} + +// add_i64 subtracts provided delta as an atomic operation +pub fn sub_i64(ptr &i64, delta int) bool { + res := C.atomic_fetch_sub_u64(voidptr(ptr), delta) + return res == 0 +} + +// atomic store/load operations have to be used when there might be another concurrent access +// atomicall set a value +pub fn store_u64(ptr &u64, val u64) { + C.atomic_store_u64(voidptr(ptr), val) +} + +// atomicall get a value +pub fn load_u64(ptr &u64) u64 { + return C.atomic_load_u64(voidptr(ptr)) +} + +// atomicall set a value +pub fn store_i64(ptr &i64, val i64) { + C.atomic_store_u64(voidptr(ptr), val) +} + +// atomicall get a value +pub fn load_i64(ptr &i64) i64 { + return i64(C.atomic_load_u64(voidptr(ptr))) +} diff --git a/v_windows/v/vlib/sync/atomic2/atomic_test.v b/v_windows/v/vlib/sync/atomic2/atomic_test.v new file mode 100644 index 0000000..7a5ffd8 --- /dev/null +++ b/v_windows/v/vlib/sync/atomic2/atomic_test.v @@ -0,0 +1,105 @@ +import sync.atomic2 +import sync + +const ( + iterations_per_cycle = 100_000 +) + +struct Counter { +mut: + counter u64 +} + +// without proper syncronization this would fail +fn test_count_10_times_1_cycle_should_result_10_cycles_with_sync() { + desired_iterations := 10 * iterations_per_cycle + mut wg := sync.new_waitgroup() + mut counter := &Counter{} + wg.add(10) + for i := 0; i < 10; i++ { + go count_one_cycle(mut counter, mut wg) + } + wg.wait() + assert counter.counter == desired_iterations + eprintln(' with synchronization the counter is: ${counter.counter:10} , expectedly == ${desired_iterations:10}') +} + +// This test just to make sure that we have an anti-test to prove it works +fn test_count_10_times_1_cycle_should_not_be_10_cycles_without_sync() { + desired_iterations := 10 * iterations_per_cycle + mut wg := sync.new_waitgroup() + mut counter := &Counter{} + wg.add(10) + for i := 0; i < 10; i++ { + go count_one_cycle_without_sync(mut counter, mut wg) + } + wg.wait() + // NB: we do not assert here, just print, because sometimes by chance counter.counter may be == desired_iterations + eprintln('without synchronization the counter is: ${counter.counter:10} , expectedly != ${desired_iterations:10}') +} + +fn test_count_plus_one_u64() { + mut c := u64(0) + atomic2.add_u64(&c, 1) + assert atomic2.load_u64(&c) == 1 +} + +fn test_count_plus_one_i64() { + mut c := i64(0) + atomic2.add_i64(&c, 1) + assert atomic2.load_i64(&c) == 1 +} + +fn test_count_plus_greater_than_one_u64() { + mut c := u64(0) + atomic2.add_u64(&c, 10) + assert atomic2.load_u64(&c) == 10 +} + +fn test_count_plus_greater_than_one_i64() { + mut c := i64(0) + atomic2.add_i64(&c, 10) + assert atomic2.load_i64(&c) == 10 +} + +fn test_count_minus_one_u64() { + mut c := u64(1) + atomic2.sub_u64(&c, 1) + assert atomic2.load_u64(&c) == 0 +} + +fn test_count_minus_one_i64() { + mut c := i64(0) + atomic2.sub_i64(&c, 1) + assert atomic2.load_i64(&c) == -1 +} + +fn test_count_minus_greater_than_one_u64() { + mut c := u64(0) + atomic2.store_u64(&c, 10) + atomic2.sub_u64(&c, 10) + assert atomic2.load_u64(&c) == 0 +} + +fn test_count_minus_greater_than_one_i64() { + mut c := i64(0) + atomic2.store_i64(&c, 10) + atomic2.sub_i64(&c, 20) + assert atomic2.load_i64(&c) == -10 +} + +// count_one_cycle counts the common counter iterations_per_cycle times in thread-safe way +fn count_one_cycle(mut counter Counter, mut group sync.WaitGroup) { + for i := 0; i < iterations_per_cycle; i++ { + atomic2.add_u64(&counter.counter, 1) + } + group.done() +} + +// count_one_cycle_without_sync counts the common counter iterations_per_cycle times in none thread-safe way +fn count_one_cycle_without_sync(mut counter Counter, mut group sync.WaitGroup) { + for i := 0; i < iterations_per_cycle; i++ { + counter.counter++ + } + group.done() +} diff --git a/v_windows/v/vlib/sync/bench/channel_bench_go.go b/v_windows/v/vlib/sync/bench/channel_bench_go.go new file mode 100644 index 0000000..a0afbbc --- /dev/null +++ b/v_windows/v/vlib/sync/bench/channel_bench_go.go @@ -0,0 +1,68 @@ +package main + +import "fmt" +import "log" +import "os" +import "time" +import "strconv" + +func assert_eq(a, b int64) { + if a != b { + log.Fatalf("assertion failed\nleft: %d, right: %d\n", a, b) + } +} + +func do_rec(ch chan int32, resch chan int64, n int32) { + var sum int64 + var i int32 + for i = 0; i < n; i++ { + sum += int64(<- ch) + } + fmt.Println(sum) + resch <- sum +} + +func do_send(ch chan int32, start, end int32) { + for i := start; i < end; i++ { + ch <- i + } +} + +func main() { + if len(os.Args) != 5 { + log.Fatalf("usage:\n\t%s \n", os.Args[0]) + } + nsend, _ := strconv.Atoi(os.Args[1]) + nrec, _ := strconv.Atoi(os.Args[2]) + buflen, _ := strconv.Atoi(os.Args[3]) + nobj, _ := strconv.Atoi(os.Args[4]) + stopwatch := time.Now() + ch := make(chan int32, buflen) + resch := make(chan int64, 0) + no := nobj + for i := 0; i < nrec; i++ { + n := no / (nrec - i) + go do_rec(ch, resch, int32(n)) + no -= n + } + assert_eq(int64(no), 0) + no = nobj + for i := 0; i < nsend; i++ { + n := no / (nsend - i) + end := no + no -= n + go do_send(ch, int32(no), int32(end)) + } + assert_eq(int64(no), 0) + var sum int64 + for i := 0; i < nrec; i++ { + sum += <-resch + } + elapsed := time.Now().Sub(stopwatch) + rate := float64(nobj)/float64(elapsed.Nanoseconds())*1000.0 + duration := 1.0e-09 * float64(elapsed.Nanoseconds()) + fmt.Printf("%d objects in %g s (%.2f objs/µs)\n", nobj, duration, rate) + expected_sum := int64(nobj)*int64(nobj-1)/2 + fmt.Printf("got: %d, expected: %d\n", sum, expected_sum) + assert_eq(sum, expected_sum) +} diff --git a/v_windows/v/vlib/sync/bench/channel_bench_v.v b/v_windows/v/vlib/sync/bench/channel_bench_v.v new file mode 100644 index 0000000..54dcfe9 --- /dev/null +++ b/v_windows/v/vlib/sync/bench/channel_bench_v.v @@ -0,0 +1,64 @@ +// Channel Benchmark +// +// `nobj` integers are sent thru a channel with queue length`buflen` +// using `nsend` sender threads and `nrec` receiver threads. +// +// The receive threads add all received numbers and send them to the +// main thread where the total sum is compare to the expected value. +import time +import os + +fn do_rec(ch chan int, resch chan i64, n int) { + mut sum := i64(0) + for _ in 0 .. n { + sum += <-ch + } + println(sum) + resch <- sum +} + +fn do_send(ch chan int, start int, end int) { + for i in start .. end { + ch <- i + } +} + +fn main() { + if os.args.len != 5 { + eprintln('usage:\n\t${os.args[0]} ') + exit(1) + } + nsend := os.args[1].int() + nrec := os.args[2].int() + buflen := os.args[3].int() + nobj := os.args[4].int() + stopwatch := time.new_stopwatch() + ch := chan int{cap: buflen} + resch := chan i64{} + mut no := nobj + for i in 0 .. nrec { + n := no / (nrec - i) + go do_rec(ch, resch, n) + no -= n + } + assert no == 0 + no = nobj + for i in 0 .. nsend { + n := no / (nsend - i) + end := no + no -= n + go do_send(ch, no, end) + } + assert no == 0 + mut sum := i64(0) + for _ in 0 .. nrec { + sum += <-resch + } + elapsed := stopwatch.elapsed() + rate := f64(nobj) / elapsed * time.microsecond + println('$nobj objects in ${f64(elapsed) / time.second} s (${rate:.2f} objs/µs)') + // use sum formula by Gauß to calculate the expected result + expected_sum := i64(nobj) * (nobj - 1) / 2 + println('got: $sum, expected: $expected_sum') + assert sum == expected_sum +} diff --git a/v_windows/v/vlib/sync/bench/many_writers_and_receivers_on_1_channel.v b/v_windows/v/vlib/sync/bench/many_writers_and_receivers_on_1_channel.v new file mode 100644 index 0000000..999bb1d --- /dev/null +++ b/v_windows/v/vlib/sync/bench/many_writers_and_receivers_on_1_channel.v @@ -0,0 +1,150 @@ +import os +import os.cmdline +import time +import sync + +// Usage: +// many_writers_and_receivers_on_1_channel [-readers 1] [-writers 4] [-chan_cap 100] [-iterations 25000] > results.csv +// +// You can then open results.csv in Excel/Calc and for example plot the first vs the second column. +enum EventKind { + push + pop +} + +struct Event { + is_set bool + id int + gtime u64 // nanoseconds + i int + kind EventKind + elapsed i64 // nanoseconds, elapsed after the previous event of the same kind +} + +struct Context { +mut: + n_iters int + n_readers int + n_writers int + // + pops_wg &sync.WaitGroup + pops []Event + // + pushes_wg &sync.WaitGroup + pushes []Event +} + +fn do_rec(ch chan int, id int, mut ctx Context) { + eprintln('start of do_rec id: $id') + mut timer_sw_x := time.new_stopwatch() + mut tmp := int(0) + mut i := int(0) + // NB: a single receiver thread can get slightly more + // than its fair share of sends, that is why + // the receiver's Event array is much larger, + // enough so a single receiver can potentially process all + // writers pushes, and it is partitioned over all of + // id, ctx.n_writers and n_iters: + n_iters := ctx.n_iters + base := id * n_iters * ctx.n_writers + for { + for ch.try_pop(tmp) == .success { + ctx.pops[base + i] = Event{ + is_set: true + id: id + gtime: time.sys_mono_now() + i: i + kind: .pop + elapsed: timer_sw_x.elapsed().nanoseconds() + } + timer_sw_x.restart() + i++ + if tmp == 1 { + ctx.pops_wg.done() + return + } + } + } +} + +fn do_send(ch chan int, id int, mut ctx Context) { + eprintln('start of do_send id: $id') + mut timer_sw_x := time.new_stopwatch() + n_iters := ctx.n_iters + base := n_iters * id // sender events can not overlap + for i := 0; i < n_iters; i++ { + idx := base + i + ctx.pushes[idx] = Event{ + is_set: true + id: id + gtime: time.sys_mono_now() + i: i + kind: .push + elapsed: timer_sw_x.elapsed().nanoseconds() + } + timer_sw_x.restart() + tmp := int(0) + ch <- tmp + } + ctx.pushes_wg.done() +} + +fn main() { + // + args := os.args[1..] + if '-h' in args || '--help' in args { + eprintln('Usage:\n many_writers_and_receivers_on_1_channel [-readers 1] [-writers 4] [-chan_cap 100] [-iterations 25000]') + exit(0) + } + n_iters := cmdline.option(args, '-iterations', '25000').int() + n_readers := cmdline.option(args, '-readers', '1').int() + n_writers := cmdline.option(args, '-writers', '4').int() + chan_cap := cmdline.option(args, '-chan_cap', '100').int() + eprintln('> n_iters, $n_iters, n_writers, $n_writers, n_readers, $n_readers, chan_cap, $chan_cap') + // + ch := chan int{cap: chan_cap} + max_number_of_pushes := n_writers * (n_iters + 2) + max_number_of_pops := max_number_of_pushes * n_readers + eprintln('> max_number_of_pushes, $max_number_of_pushes, max_number_of_pops (per receiver), $max_number_of_pops') + mut ctx := &Context{ + n_iters: n_iters + n_readers: n_readers + n_writers: n_writers + pushes_wg: sync.new_waitgroup() + pops_wg: sync.new_waitgroup() + pushes: []Event{len: max_number_of_pushes} + pops: []Event{len: max_number_of_pops} + } + ctx.pops_wg.add(n_readers) + for i := 0; i < n_readers; i++ { + go do_rec(ch, i, mut ctx) + } + ctx.pushes_wg.add(n_writers) + for i := 0; i < n_writers; i++ { + go do_send(ch, i, mut ctx) + } + ctx.pushes_wg.wait() + eprintln('>> all pushes done') + for i := 0; i < n_readers; i++ { + ch <- 1 + } + ctx.pops_wg.wait() + eprintln('>> all pops done') + mut all_events := []Event{} + all_events << ctx.pops + all_events << ctx.pushes + all_events.sort(a.elapsed < b.elapsed) + mut i := 0 + for e in all_events { + if !e.is_set { + continue + } + i++ + if e.kind == .pop { + println('${i:8} , ${e.elapsed:10}, ns , do_rec id:, ${e.id:3} , i=, ${e.i:5} , ${e.gtime:20}') + } + if e.kind == .push { + println('${i:8} , ${e.elapsed:10}, ns , do_send id:, ${e.id:3} , i=, ${e.i:5} , ${e.gtime:20}') + } + } +} diff --git a/v_windows/v/vlib/sync/bench/results.md b/v_windows/v/vlib/sync/bench/results.md new file mode 100644 index 0000000..882471c --- /dev/null +++ b/v_windows/v/vlib/sync/bench/results.md @@ -0,0 +1,48 @@ +# Channel Benchmark Results + +This documents lists several benchmark results for different platforms in order to +identify performance regressions and improvements. + +The are measured using the command + +``` +> channel_bench_* + +nsend ... number of threads that push objects into the channel +nrec .... number of threads that pop objects from the channel +buflen .. length of channel buffer queue - `0` means unbuffered channel +nobj .... number of objects to pass thru the channel +``` + +## AMD Ryzen 7 3800X, Ubuntu-20.04 x86_64 + +10000000 Objects transfered, results in Objects/µs + +| nsend | nrec | buflen | **V (gcc -O2)** | **V (clang)** | **V (tcc)** | **Go (golang)** | **Go (gccgo -O2)** | +| :---: | :---:| :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | 1 | 0 | 1.97 | 1.63 | 2.08 | 4.65 | 0.56 | +| 1 | 1 | 100 | 3.05 | 2.29 | 1.93 | 18.90 | 6.08 | +| 4 | 4 | 0 | 0.87 | 0.90 | 0.99 | 1.84 | 0.84 | +| 4 | 4 | 100 | 3.35 | 3.07 | 2.92 | 7.43 | 3.71 | + +## AMD Ryzen 7 3800X, Windows 10 2004 x64 + +| nsend | nrec | buflen | **V (gcc -O2)** | **V (msvc /O2)** | **V (tcc)** | **Go (golang)** | +| :---: | :---:| :---: | :---: | :---: | :---: | :---: | +| 1 | 1 | 0 | 2.30 | 3.76 | 2.02 | 4.67 | +| 1 | 1 | 100 | 2.96 | 3.12 | 2.26 | 23.31 | +| 4 | 4 | 0 | 0.90 | 1.05 | 0.83 | 1.38 | +| 4 | 4 | 100 | 2.28 | 2.16 | 2.43 | 4.63 | + +## Raspberry Pi 3B+, Void Linux musl 32 bit + +10000000 Objects transfered, results in Objects/µs + +| nsend | nrec | buflen | **V (gcc -O2)** | **Go (golang)** | +| :---: | :---:| :---: | :---: | :---: | +| 1 | 1 | 0 | 0.37 | 0.21 | +| 1 | 1 | 100 | 1.03 | 0.74 | +| 4 | 4 | 0 | 0.04 | 0.38 | +| 4 | 4 | 100 | 2.78 | 2.63 | +| 2 | 2 | 0 | 0.05 | 0.38 | +| 2 | 2 | 100 | 1.26 | 0.75 | diff --git a/v_windows/v/vlib/sync/channel_1_test.v b/v_windows/v/vlib/sync/channel_1_test.v new file mode 100644 index 0000000..17588fd --- /dev/null +++ b/v_windows/v/vlib/sync/channel_1_test.v @@ -0,0 +1,25 @@ +const ( + num_iterations = 10000 +) + +fn do_send(ch chan int) { + for i in 0 .. num_iterations { + ch <- i + } +} + +fn test_channel_buffered() { + ch := chan int{cap: 1000} + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. num_iterations { + sum += <-ch + } + assert sum == u64(num_iterations) * (num_iterations - 1) / 2 +} + +fn test_builtin_enum() { + x := ChanState.closed + assert x == .closed + println(x) +} diff --git a/v_windows/v/vlib/sync/channel_2_test.v b/v_windows/v/vlib/sync/channel_2_test.v new file mode 100644 index 0000000..5e8251d --- /dev/null +++ b/v_windows/v/vlib/sync/channel_2_test.v @@ -0,0 +1,19 @@ +const ( + num_iterations = 10000 +) + +fn do_send(ch chan int) { + for i in 0 .. num_iterations { + ch <- i + } +} + +fn test_channel_unbuffered() { + ch := chan int{} + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. num_iterations { + sum += <-ch + } + assert sum == u64(num_iterations) * (num_iterations - 1) / 2 +} diff --git a/v_windows/v/vlib/sync/channel_3_test.v b/v_windows/v/vlib/sync/channel_3_test.v new file mode 100644 index 0000000..d07276b --- /dev/null +++ b/v_windows/v/vlib/sync/channel_3_test.v @@ -0,0 +1,32 @@ +fn do_rec(ch chan int, resch chan i64) { + mut sum := i64(0) + for _ in 0 .. 2000 { + sum += <-ch + } + println(sum) + resch <- sum +} + +fn do_send(ch chan int) { + for i in 0 .. 2000 { + ch <- i + } +} + +fn test_channel_multi_unbuffered() { + ch := chan int{} + resch := chan i64{} + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_send(ch) + go do_send(ch) + go do_send(ch) + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. 4 { + sum += <-resch + } + assert sum == i64(4) * 2000 * (2000 - 1) / 2 +} diff --git a/v_windows/v/vlib/sync/channel_4_test.v b/v_windows/v/vlib/sync/channel_4_test.v new file mode 100644 index 0000000..3792668 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_4_test.v @@ -0,0 +1,32 @@ +fn do_rec(ch chan int, resch chan i64) { + mut sum := i64(0) + for _ in 0 .. 2000 { + sum += <-ch + } + println(sum) + resch <- sum +} + +fn do_send(ch chan int) { + for i in 0 .. 2000 { + ch <- i + } +} + +fn test_channel_multi_buffered() { + ch := chan int{cap: 100} + resch := chan i64{} + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_send(ch) + go do_send(ch) + go do_send(ch) + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. 4 { + sum += <-resch + } + assert sum == i64(4) * 2000 * (2000 - 1) / 2 +} diff --git a/v_windows/v/vlib/sync/channel_array_mut_test.v b/v_windows/v/vlib/sync/channel_array_mut_test.v new file mode 100644 index 0000000..bfd53a1 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_array_mut_test.v @@ -0,0 +1,35 @@ +const ( + num_iterations = 10000 +) + +struct St { +mut: + dummy i64 + dummy2 u32 + dummy3 i64 + n int + dummy4 int +} + +// this function gets an array of channels for `St` references +fn do_rec_calc_send(chs []chan mut St) { + for { + mut s := <-chs[0] or { break } + s.n++ + chs[1] <- s + } +} + +fn test_channel_array_mut() { + mut chs := [chan mut St{cap: 1}, chan mut St{}] + go do_rec_calc_send(chs) + mut t := &St{ + n: 100 + } + for _ in 0 .. num_iterations { + chs[0] <- t + t = <-chs[1] + } + chs[0].close() + assert t.n == 100 + num_iterations +} diff --git a/v_windows/v/vlib/sync/channel_close_test.v b/v_windows/v/vlib/sync/channel_close_test.v new file mode 100644 index 0000000..a31bfa9 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_close_test.v @@ -0,0 +1,104 @@ +import time + +fn do_rec(ch chan int, resch chan i64) { + mut sum := i64(0) + for { + a := <-ch or { break } + sum += a + } + assert ch.closed == true + println(sum) + resch <- sum +} + +fn do_send(ch chan int) { + for i in 0 .. 8000 { + ch <- i + } + assert ch.closed == false + ch.close() + assert ch.closed == true +} + +fn test_channel_close_buffered_multi() { + ch := chan int{cap: 10} + resch := chan i64{} + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. 4 { + sum += <-resch + } + assert sum == i64(8000) * (8000 - 1) / 2 +} + +fn test_channel_close_unbuffered_multi() { + ch := chan int{} + resch := chan i64{} + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_rec(ch, resch) + go do_send(ch) + mut sum := i64(0) + for _ in 0 .. 4 { + sum += <-resch + } + assert sum == i64(8000) * (8000 - 1) / 2 +} + +fn test_channel_close_buffered() { + ch := chan int{cap: 100} + resch := chan i64{} + go do_rec(ch, resch) + go do_send(ch) + mut sum := i64(0) + sum += <-resch + assert sum == i64(8000) * (8000 - 1) / 2 +} + +fn test_channel_close_unbuffered() { + ch := chan int{} + resch := chan i64{cap: 100} + go do_rec(ch, resch) + go do_send(ch) + mut sum := i64(0) + sum += <-resch + assert sum == i64(8000) * (8000 - 1) / 2 +} + +fn test_channel_send_close_buffered() { + ch := chan int{cap: 1} + t := go fn (ch chan int) { + ch <- 31 + mut x := 45 + ch <- 17 or { x = -133 } + + assert x == -133 + }(ch) + time.sleep(100 * time.millisecond) + ch.close() + mut r := <-ch + r = <-ch or { 23 } + assert r == 23 + t.wait() +} + +fn test_channel_send_close_unbuffered() { + time.sleep(1 * time.second) + ch := chan int{} + t := go fn (ch chan int) { + mut x := 31 + ch <- 177 or { x = -71 } + + assert x == -71 + }(ch) + time.sleep(100 * time.millisecond) + ch.close() + r := <-ch or { 238 } + assert r == 238 + t.wait() +} diff --git a/v_windows/v/vlib/sync/channel_fill_test.v b/v_windows/v/vlib/sync/channel_fill_test.v new file mode 100644 index 0000000..b4eabc0 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_fill_test.v @@ -0,0 +1,22 @@ +import sync + +const ( + queue_len = 1000 + queue_fill = 763 +) + +fn do_send(ch chan int, mut fin sync.Semaphore) { + for i in 0 .. queue_fill { + ch <- i + } + fin.post() +} + +fn test_channel_len_cap() { + ch := chan int{cap: queue_len} + mut sem := sync.new_semaphore() + go do_send(ch, mut sem) + sem.wait() + assert ch.cap == queue_len + assert ch.len == queue_fill +} diff --git a/v_windows/v/vlib/sync/channel_opt_propagate_test.v b/v_windows/v/vlib/sync/channel_opt_propagate_test.v new file mode 100644 index 0000000..a7e26fe --- /dev/null +++ b/v_windows/v/vlib/sync/channel_opt_propagate_test.v @@ -0,0 +1,39 @@ +import sync + +const ( + num_iterations = 10000 +) + +fn get_val_from_chan(ch chan i64) ?i64 { + r := <-ch ? + return r +} + +// this function gets an array of channels for `i64` +fn do_rec_calc_send(chs []chan i64, mut sem sync.Semaphore) { + mut msg := '' + for { + mut s := get_val_from_chan(chs[0]) or { + msg = err.msg + break + } + s++ + chs[1] <- s + } + assert msg == 'channel closed' + sem.post() +} + +fn test_channel_array_mut() { + mut chs := [chan i64{}, chan i64{cap: 10}] + mut sem := sync.new_semaphore() + go do_rec_calc_send(chs, mut sem) + mut t := i64(100) + for _ in 0 .. num_iterations { + chs[0] <- t + t = <-chs[1] + } + chs[0].close() + sem.wait() + assert t == 100 + num_iterations +} diff --git a/v_windows/v/vlib/sync/channel_polling_test.v b/v_windows/v/vlib/sync/channel_polling_test.v new file mode 100644 index 0000000..846dcfd --- /dev/null +++ b/v_windows/v/vlib/sync/channel_polling_test.v @@ -0,0 +1,56 @@ +// Channel Benchmark +// +// `nobj` integers are sent thru a channel with queue length`buflen` +// using `nsend` sender threads and `nrec` receiver threads. +// +// The receive threads add all received numbers and send them to the +// main thread where the total sum is compare to the expected value. +const ( + nsend = 2 + nrec = 2 + buflen = 100 + nobj = 10000 + objs_per_thread = 5000 +) + +fn do_rec(ch chan int, resch chan i64, n int) { + mut sum := i64(0) + for _ in 0 .. n { + mut r := 0 + for ch.try_pop(mut r) != .success { + } + sum += r + } + println(sum) + resch <- sum +} + +fn do_send(ch chan int, start int, end int) { + for i in start .. end { + for ch.try_push(i) != .success { + } + } +} + +fn test_channel_polling() { + ch := chan int{cap: buflen} + resch := chan i64{} + for _ in 0 .. nrec { + go do_rec(ch, resch, objs_per_thread) + } + mut n := nobj + for _ in 0 .. nsend { + end := n + n -= objs_per_thread + go do_send(ch, n, end) + } + mut sum := i64(0) + for _ in 0 .. nrec { + sum += <-resch + println('> running sum: $sum') + } + // use sum formula by Gauß to calculate the expected result + expected_sum := i64(nobj) * (nobj - 1) / 2 + println('expected sum: $expected_sum | sum: $sum') + assert sum == expected_sum +} diff --git a/v_windows/v/vlib/sync/channel_push_or_1_test.v b/v_windows/v/vlib/sync/channel_push_or_1_test.v new file mode 100644 index 0000000..1551d83 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_push_or_1_test.v @@ -0,0 +1,65 @@ +const n = 1000 + +const c = 100 + +fn f(ch chan int) { + for _ in 0 .. n { + _ := <-ch + } + ch.close() +} + +fn test_push_or_unbuffered() { + ch := chan int{} + go f(ch) + mut j := 0 + for { + ch <- j or { break } + + j++ + } + assert j == n +} + +fn test_push_or_buffered() { + ch := chan int{cap: c} + go f(ch) + mut j := 0 + for { + ch <- j or { break } + + j++ + } + // we don't know how many elements are in the buffer when the channel + // is closed, so check j against an interval + assert j >= n + assert j <= n + c +} + +fn g(ch chan int, res chan int) { + mut j := 0 + for { + ch <- j or { break } + + j++ + } + println('done $j') + res <- j +} + +fn test_many_senders() { + ch := chan int{} + res := chan int{} + go g(ch, res) + go g(ch, res) + go g(ch, res) + mut k := 0 + for _ in 0 .. 3 * n { + k = <-ch + } + ch.close() + mut sum := <-res + sum += <-res + sum += <-res + assert sum == 3 * n +} diff --git a/v_windows/v/vlib/sync/channel_push_or_2_test.v b/v_windows/v/vlib/sync/channel_push_or_2_test.v new file mode 100644 index 0000000..451d9e7 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_push_or_2_test.v @@ -0,0 +1,25 @@ +const n = 1000 + +fn f(ch chan f64) { + mut s := 0.0 + for _ in 0 .. n { + s += <-ch + } + assert s == f64(n * (n + 1) / 2) + ch.close() +} + +fn do_send(ch chan f64, val f64) ?f64 { + ch <- val ? + return val + 1.0 +} + +fn test_push_propargate() { + ch := chan f64{} + go f(ch) + mut s := 1.0 + for { + s = do_send(ch, s) or { break } + } + assert s == f64(n + 1) +} diff --git a/v_windows/v/vlib/sync/channel_select_2_test.v b/v_windows/v/vlib/sync/channel_select_2_test.v new file mode 100644 index 0000000..189aaf6 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_2_test.v @@ -0,0 +1,62 @@ +import time + +fn do_rec_i64(ch chan i64) { + mut sum := i64(0) + for _ in 0 .. 300 { + sum += <-ch + } + assert sum == 300 * (300 - 1) / 2 +} + +fn do_send_int(ch chan int) { + for i in 0 .. 300 { + ch <- i + } +} + +fn do_send_byte(ch chan byte) { + for i in 0 .. 300 { + ch <- byte(i) + } +} + +fn do_send_i64(ch chan i64) { + for i in 0 .. 300 { + ch <- i + } +} + +fn test_select() { + chi := chan int{} + chl := chan i64{cap: 1} + chb := chan byte{cap: 10} + recch := chan i64{cap: 0} + go do_rec_i64(recch) + go do_send_int(chi) + go do_send_byte(chb) + go do_send_i64(chl) + mut sum := i64(0) + mut rl := i64(0) + mut sl := i64(0) + for _ in 0 .. 1200 { + select { + ri := <-chi { + sum += ri + } + recch <- sl { + sl++ + } + rl = <-chl { + sum += rl + } + rb := <-chb { + sum += rb + } + } + } + // Use Gauß' formula for the first 2 contributions + // the 3rd contribution is `byte` and must be seen modulo 256 + expected_sum := 2 * (300 * (300 - 1) / 2) + 256 * (256 - 1) / 2 + 44 * (44 - 1) / 2 + assert sum == expected_sum + time.sleep(20 * time.millisecond) // to give assert in coroutine enough time +} diff --git a/v_windows/v/vlib/sync/channel_select_3_test.v b/v_windows/v/vlib/sync/channel_select_3_test.v new file mode 100644 index 0000000..fdf6096 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_3_test.v @@ -0,0 +1,122 @@ +import time +import sync + +struct St { + a int +} + +fn getint() int { + return 8 +} + +fn f1(ch1 chan int, ch2 chan St, ch3 chan int, ch4 chan int, ch5 chan int, mut sem sync.Semaphore) { + mut a := 5 + select { + a = <-ch3 { + a = 0 + } + b := <-ch2 { + a = b.a + } + ch3 <- 5 { + a = 1 + } + ch2 <- St{ + a: 37 + } { + a = 2 + } + ch4 <- (6 + 7 * 9) { + a = 8 + } + ch5 <- getint() { + a = 9 + } + 300 * time.millisecond { + a = 3 + } + } + assert a == 3 + sem.post() +} + +fn f2(ch1 chan St, ch2 chan int, mut sem sync.Semaphore) { + mut r := 23 + for i in 0 .. 2 { + select { + b := <-ch1 { + r = b.a + } + ch2 <- r { + r = 17 + } + } + if i == 0 { + assert r == 17 + } else { + assert r == 13 + } + } + sem.post() +} + +fn test_select_blocks() { + ch1 := chan int{cap: 1} + ch2 := chan St{} + ch3 := chan int{} + ch4 := chan int{} + ch5 := chan int{} + mut sem := sync.new_semaphore() + mut r := false + t := select { + b := <-ch1 { + println(b) + } + else { + // no channel ready + r = true + } + } + assert r == true + assert t == true + go f2(ch2, ch3, mut sem) + n := <-ch3 + assert n == 23 + ch2 <- St{ + a: 13 + } + sem.wait() + stopwatch := time.new_stopwatch() + go f1(ch1, ch2, ch3, ch4, ch5, mut sem) + sem.wait() + elapsed_ms := f64(stopwatch.elapsed()) / time.millisecond + // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/high-resolution-timers + // > For example, for Windows running on an x86 processor, the default interval between + // > system clock ticks is typically about 15 milliseconds, and the minimum interval + // > between system clock ticks is about 1 millisecond. + assert elapsed_ms >= 280.0 // 300 - (15ms + 5ms just in case) + + ch1.close() + ch2.close() + mut h := 7 + mut is_open := true + if select { + _ := <-ch2 { + h = 0 + } + ch1 <- h { + h = 1 + } + else { + h = 2 + } + } { + panic('channel is still open') + } else { + is_open = false + } + // no branch should have run + assert h == 7 + // since all channels are closed `select` should return `false` + assert is_open == false +} diff --git a/v_windows/v/vlib/sync/channel_select_4_test.v b/v_windows/v/vlib/sync/channel_select_4_test.v new file mode 100644 index 0000000..77cd557 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_4_test.v @@ -0,0 +1,43 @@ +fn do_rec_i64(ch chan i64, sumch chan i64) { + mut sum := i64(0) + for _ in 0 .. 30000 { + sum += <-ch + } + sumch <- sum +} + +fn do_send_int(ch chan int) { + for i in 0 .. 30000 { + ch <- i + } +} + +fn test_select() { + chi := chan int{cap: 10} + recch := chan i64{cap: 10} + chsum := chan i64{} + go do_rec_i64(recch, chsum) + go do_send_int(chi) + mut sum := i64(0) + mut sl := i64(0) + for _ in 0 .. 60000 + recch.cap { + select { + ri := <-chi { + sum += ri + } + recch <- sl { + sl++ + } + } + } + // Use Gauß' formula + expected_sum := i64(30000) * (30000 - 1) / 2 + assert sum == expected_sum + + mut sumrec := <-chsum + // Empty receive buffer + for _ in 0 .. recch.cap { + sumrec += <-recch + } + assert sumrec == i64(30000 + recch.cap) * (30000 + recch.cap - 1) / 2 +} diff --git a/v_windows/v/vlib/sync/channel_select_5_test.v b/v_windows/v/vlib/sync/channel_select_5_test.v new file mode 100644 index 0000000..97228fe --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_5_test.v @@ -0,0 +1,61 @@ +fn do_rec_i64(ch chan i64, sumch chan i64) { + mut sum := i64(0) + for _ in 0 .. 10000 { + sum += <-ch + } + sumch <- sum +} + +fn do_send_int(ch chan int) { + for i in 0 .. 10000 { + ch <- i + } +} + +fn do_send_int2(ch chan int) { + for i in 10000 .. 20000 { + ch <- i + } +} + +fn do_send_int3(ch chan int) { + for i in 20000 .. 30000 { + ch <- i + } +} + +fn test_select() { + chi := chan int{cap: 10} + recch := chan i64{cap: 10} + chsum := chan i64{} + go do_rec_i64(recch, chsum) + go do_rec_i64(recch, chsum) + go do_rec_i64(recch, chsum) + go do_send_int(chi) + go do_send_int2(chi) + go do_send_int3(chi) + mut sum := i64(0) + mut sl := i64(0) + for _ in 0 .. 60000 + recch.cap { + select { + ri := <-chi { + sum += ri + } + recch <- sl { + sl++ + } + } + } + // Use Gauß' formula + expected_sum := i64(30000) * (30000 - 1) / 2 + assert sum == expected_sum + + mut sumrec := <-chsum + sumrec += <-chsum + sumrec += <-chsum + // Empty receive buffer + for _ in 0 .. recch.cap { + sumrec += <-recch + } + assert sumrec == i64(30000 + recch.cap) * (30000 + recch.cap - 1) / 2 +} diff --git a/v_windows/v/vlib/sync/channel_select_6_test.v b/v_windows/v/vlib/sync/channel_select_6_test.v new file mode 100644 index 0000000..9b5f2d4 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_6_test.v @@ -0,0 +1,75 @@ +// This test case runs concurrent 3 instances of `do_select` that +// communicate with 6 other threads doing send and receive operations. +// There are buffered and unbuffered channels - handled by one or two +// concurrend threads on the other side + +fn do_select(ch1 chan int, ch2 chan int, chf1 chan f64, chf2 chan f64, sumch1 chan i64, sumch2 chan i64) { + mut sum1 := i64(0) + mut sum2 := i64(0) + f1 := 17.0 + f2 := 7.0 + for _ in 0 .. 20000 + chf1.cap / 3 { + select { + chf1 <- f1 {} + i := <-ch1 { + sum1 += i + } + j := <-ch2 { + sum2 += j + } + chf2 <- f2 {} + } + } + sumch1 <- sum1 + sumch2 <- sum2 +} + +fn do_send_int(ch chan int, factor int) { + for i in 0 .. 10000 { + ch <- (i * factor) + } +} + +fn do_rec_f64(ch chan f64, sumch chan f64) { + mut sum := 0.0 + for _ in 0 .. 10000 { + sum += <-ch + } + sumch <- sum +} + +fn test_select() { + ch1 := chan int{cap: 3} + ch2 := chan int{} + // buffer length of chf1 mus be mutiple of 3 (# select threads) + chf1 := chan f64{cap: 30} + chf2 := chan f64{} + chsum1 := chan i64{} + chsum2 := chan i64{} + chsumf1 := chan f64{} + chsumf2 := chan f64{} + go do_send_int(ch1, 3) + go do_select(ch1, ch2, chf1, chf2, chsum1, chsum2) + go do_rec_f64(chf1, chsumf1) + go do_rec_f64(chf2, chsumf2) + go do_rec_f64(chf2, chsumf2) + go do_select(ch1, ch2, chf1, chf2, chsum1, chsum2) + go do_send_int(ch2, 7) + go do_send_int(ch2, 17) + go do_select(ch1, ch2, chf1, chf2, chsum1, chsum2) + + sum1 := <-chsum1 + <-chsum1 + <-chsum1 + sum2 := <-chsum2 + <-chsum2 + <-chsum2 + mut sumf1 := <-chsumf1 + // empty channel buffer + for _ in 0 .. chf1.cap { + sumf1 += <-chf1 + } + sumf2 := <-chsumf2 + <-chsumf2 + // Use Gauß' formula + expected_sum := i64(10000) * (10000 - 1) / 2 + assert sum1 == 3 * expected_sum + assert sum2 == (7 + 17) * expected_sum + assert sumf1 == 17.0 * f64(10000 + chf1.cap) + assert sumf2 == 7.0 * 20000 +} diff --git a/v_windows/v/vlib/sync/channel_select_test.v b/v_windows/v/vlib/sync/channel_select_test.v new file mode 100644 index 0000000..26ed641 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_select_test.v @@ -0,0 +1,84 @@ +/* +* ATTENTION! Do not use this file as an example! + * For that, please look at `channel_select_2_test.v` or `channel_select_3_test.v` + * + * This test case uses the implementation in `sync/channels.v` directly + * in order to test it independently from the support in the core language +*/ + +module sync + +import time + +fn do_rec_i64(mut ch Channel) { + mut sum := i64(0) + for _ in 0 .. 300 { + mut a := i64(0) + ch.pop(&a) + sum += a + } + assert sum == 300 * (300 - 1) / 2 +} + +fn do_send_int(mut ch Channel) { + for i in 0 .. 300 { + ch.push(&i) + } +} + +fn do_send_byte(mut ch Channel) { + for i in 0 .. 300 { + ii := byte(i) + ch.push(&ii) + } +} + +fn do_send_i64(mut ch Channel) { + for i in 0 .. 300 { + ii := i64(i) + ch.push(&ii) + } +} + +fn test_select() { + mut chi := new_channel(0) + mut chl := new_channel(1) + mut chb := new_channel(10) + mut recch := new_channel(0) + go do_rec_i64(mut recch) + go do_send_int(mut chi) + go do_send_byte(mut chb) + go do_send_i64(mut chl) + mut channels := [chi, recch, chl, chb] + directions := [Direction.pop, .push, .pop, .pop] + mut sum := i64(0) + mut rl := i64(0) + mut ri := int(0) + mut rb := byte(0) + mut sl := i64(0) + mut objs := [voidptr(&ri), &sl, &rl, &rb] + for _ in 0 .. 1200 { + idx := channel_select(mut channels, directions, mut objs, time.infinite) + match idx { + 0 { + sum += ri + } + 1 { + sl++ + } + 2 { + sum += rl + } + 3 { + sum += rb + } + else { + println('got $idx (timeout)') + } + } + } + // Use Gauß' formula for the first 2 contributions + // the 3rd contribution is `byte` and must be seen modulo 256 + expected_sum := 2 * (300 * (300 - 1) / 2) + 256 * (256 - 1) / 2 + 44 * (44 - 1) / 2 + assert sum == expected_sum +} diff --git a/v_windows/v/vlib/sync/channel_try_buf_test.v b/v_windows/v/vlib/sync/channel_try_buf_test.v new file mode 100644 index 0000000..e521314 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_try_buf_test.v @@ -0,0 +1,17 @@ +fn test_channel_try_buffered() { + ch := chan int{cap: 5} + for z in 2 .. 13 { + if ch.try_push(z) == .not_ready { + assert z == 7 + break + } + } + mut obj := int(0) + for ch.try_pop(mut obj) == .success { + println(obj) + } + assert obj == 6 + ch <- 17 + obj = <-ch + assert obj == 17 +} diff --git a/v_windows/v/vlib/sync/channel_try_unbuf_test.v b/v_windows/v/vlib/sync/channel_try_unbuf_test.v new file mode 100644 index 0000000..ee11468 --- /dev/null +++ b/v_windows/v/vlib/sync/channel_try_unbuf_test.v @@ -0,0 +1,13 @@ +fn test_channel_try_unbuffered() { + ch := chan int{} + for z in 5 .. 8 { + if ch.try_push(z) == .not_ready { + assert z == 5 + break + } + panic('push on non-ready channel not detected') + } + mut obj := -17 + for ch.try_pop(mut obj) == .success {} + assert obj == -17 +} diff --git a/v_windows/v/vlib/sync/channels.v b/v_windows/v/vlib/sync/channels.v new file mode 100644 index 0000000..49f1396 --- /dev/null +++ b/v_windows/v/vlib/sync/channels.v @@ -0,0 +1,730 @@ +module sync + +import time +import rand + +$if windows { + #flag -I @VEXEROOT/thirdparty/stdatomic/win +} $else { + #flag -I @VEXEROOT/thirdparty/stdatomic/nix +} + +$if linux { + $if tinyc { + $if amd64 { + // most Linux distributions have /usr/lib/libatomic.so, but Ubuntu uses gcc version specific dir + #flag -L/usr/lib/gcc/x86_64-linux-gnu/6 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/7 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/8 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/9 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/10 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/11 + #flag -L/usr/lib/gcc/x86_64-linux-gnu/12 + } $else $if arm64 { + #flag -L/usr/lib/gcc/aarch64-linux-gnu/6 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/7 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/8 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/9 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/10 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/11 + #flag -L/usr/lib/gcc/aarch64-linux-gnu/12 + } + #flag -latomic + } +} + +#include +// The following functions are actually generic in C +fn C.atomic_load_ptr(voidptr) voidptr +fn C.atomic_store_ptr(voidptr, voidptr) +fn C.atomic_compare_exchange_weak_ptr(voidptr, voidptr, voidptr) bool +fn C.atomic_compare_exchange_strong_ptr(voidptr, voidptr, voidptr) bool +fn C.atomic_exchange_ptr(voidptr, voidptr) voidptr +fn C.atomic_fetch_add_ptr(voidptr, voidptr) voidptr +fn C.atomic_fetch_sub_ptr(voidptr, voidptr) voidptr + +fn C.atomic_load_u16(voidptr) u16 +fn C.atomic_store_u16(voidptr, u16) +fn C.atomic_compare_exchange_weak_u16(voidptr, voidptr, u16) bool +fn C.atomic_compare_exchange_strong_u16(voidptr, voidptr, u16) bool +fn C.atomic_exchange_u16(voidptr, u16) u16 +fn C.atomic_fetch_add_u16(voidptr, u16) u16 +fn C.atomic_fetch_sub_u16(voidptr, u16) u16 + +fn C.atomic_load_u32(voidptr) u32 +fn C.atomic_store_u32(voidptr, u32) +fn C.atomic_compare_exchange_weak_u32(voidptr, voidptr, u32) bool +fn C.atomic_compare_exchange_strong_u32(voidptr, voidptr, u32) bool +fn C.atomic_exchange_u32(voidptr, u32) u32 +fn C.atomic_fetch_add_u32(voidptr, u32) u32 +fn C.atomic_fetch_sub_u32(voidptr, u32) u32 + +fn C.atomic_load_u64(voidptr) u64 +fn C.atomic_store_u64(voidptr, u64) +fn C.atomic_compare_exchange_weak_u64(voidptr, voidptr, u64) bool +fn C.atomic_compare_exchange_strong_u64(voidptr, voidptr, u64) bool +fn C.atomic_exchange_u64(voidptr, u64) u64 +fn C.atomic_fetch_add_u64(voidptr, u64) u64 +fn C.atomic_fetch_sub_u64(voidptr, u64) u64 + +const ( + // how often to try to get data without blocking before to wait for semaphore + spinloops = 750 + spinloops_sem = 4000 +) + +enum BufferElemStat { + unused = 0 + writing + written + reading +} + +struct Subscription { +mut: + sem &Semaphore + prev &&Subscription + nxt &Subscription +} + +enum Direction { + pop + push +} + +struct Channel { + ringbuf &byte // queue for buffered channels + statusbuf &byte // flags to synchronize write/read in ringbuf + objsize u32 +mut: // atomic + writesem Semaphore // to wake thread that wanted to write, but buffer was full + readsem Semaphore // to wake thread that wanted to read, but buffer was empty + writesem_im Semaphore + readsem_im Semaphore + write_adr C.atomic_uintptr_t // if != NULL the next obj can be written here without wait + read_adr C.atomic_uintptr_t // if != NULL an obj can be read from here without wait + adr_read C.atomic_uintptr_t // used to identify origin of writesem + adr_written C.atomic_uintptr_t // used to identify origin of readsem + write_free u32 // for queue state + read_avail u32 + buf_elem_write_idx u32 + buf_elem_read_idx u32 + // for select + write_subscriber &Subscription + read_subscriber &Subscription + write_sub_mtx u16 + read_sub_mtx u16 + closed u16 +pub: + cap u32 // queue length in #objects +} + +pub fn new_channel(n u32) &Channel { + st := sizeof(T) + if isreftype(T) { + return new_channel_st(n, st) + } else { + return new_channel_st_noscan(n, st) + } +} + +fn new_channel_st(n u32, st u32) &Channel { + wsem := if n > 0 { n } else { 1 } + rsem := if n > 0 { u32(0) } else { 1 } + rbuf := if n > 0 { unsafe { malloc(int(n * st)) } } else { &byte(0) } + sbuf := if n > 0 { vcalloc_noscan(int(n * 2)) } else { &byte(0) } + mut ch := Channel{ + objsize: st + cap: n + write_free: n + read_avail: 0 + ringbuf: rbuf + statusbuf: sbuf + write_subscriber: 0 + read_subscriber: 0 + } + ch.writesem.init(wsem) + ch.readsem.init(rsem) + ch.writesem_im.init(0) + ch.readsem_im.init(0) + return &ch +} + +fn new_channel_st_noscan(n u32, st u32) &Channel { + $if gcboehm_opt ? { + wsem := if n > 0 { n } else { 1 } + rsem := if n > 0 { u32(0) } else { 1 } + rbuf := if n > 0 { unsafe { malloc_noscan(int(n * st)) } } else { &byte(0) } + sbuf := if n > 0 { vcalloc_noscan(int(n * 2)) } else { &byte(0) } + mut ch := Channel{ + objsize: st + cap: n + write_free: n + read_avail: 0 + ringbuf: rbuf + statusbuf: sbuf + write_subscriber: 0 + read_subscriber: 0 + } + ch.writesem.init(wsem) + ch.readsem.init(rsem) + ch.writesem_im.init(0) + ch.readsem_im.init(0) + return &ch + } $else { + return new_channel_st(n, st) + } +} + +pub fn (ch &Channel) auto_str(typename string) string { + return 'chan $typename{cap: $ch.cap, closed: $ch.closed}' +} + +pub fn (mut ch Channel) close() { + open_val := u16(0) + if !C.atomic_compare_exchange_strong_u16(&ch.closed, &open_val, 1) { + return + } + mut nulladr := voidptr(0) + for !C.atomic_compare_exchange_weak_ptr(unsafe { &voidptr(&ch.adr_written) }, &nulladr, + voidptr(-1)) { + nulladr = voidptr(0) + } + ch.readsem_im.post() + ch.readsem.post() + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.read_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + if ch.read_subscriber != voidptr(0) { + ch.read_subscriber.sem.post() + } + C.atomic_store_u16(&ch.read_sub_mtx, u16(0)) + null16 = u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.write_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + if ch.write_subscriber != voidptr(0) { + ch.write_subscriber.sem.post() + } + C.atomic_store_u16(&ch.write_sub_mtx, u16(0)) + ch.writesem.post() + if ch.cap == 0 { + C.atomic_store_ptr(unsafe { &voidptr(&ch.read_adr) }, voidptr(0)) + } + ch.writesem_im.post() +} + +[inline] +pub fn (mut ch Channel) len() int { + return int(C.atomic_load_u32(&ch.read_avail)) +} + +[inline] +pub fn (mut ch Channel) closed() bool { + return C.atomic_load_u16(&ch.closed) != 0 +} + +[inline] +pub fn (mut ch Channel) push(src voidptr) { + if ch.try_push_priv(src, false) == .closed { + panic('push on closed channel') + } +} + +[inline] +pub fn (mut ch Channel) try_push(src voidptr) ChanState { + return ch.try_push_priv(src, true) +} + +fn (mut ch Channel) try_push_priv(src voidptr, no_block bool) ChanState { + if C.atomic_load_u16(&ch.closed) != 0 { + return .closed + } + spinloops_sem_, spinloops_ := if no_block { 1, 1 } else { sync.spinloops, sync.spinloops_sem } + mut have_swapped := false + for { + mut got_sem := false + mut wradr := C.atomic_load_ptr(unsafe { &voidptr(&ch.write_adr) }) + for wradr != C.NULL { + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.write_adr) }, + &wradr, voidptr(0)) + { + // there is a reader waiting for us + unsafe { C.memcpy(wradr, src, ch.objsize) } + mut nulladr := voidptr(0) + for !C.atomic_compare_exchange_weak_ptr(unsafe { &voidptr(&ch.adr_written) }, + &nulladr, wradr) { + nulladr = voidptr(0) + } + ch.readsem_im.post() + return .success + } + } + if no_block && ch.cap == 0 { + return .not_ready + } + // get token to read + for _ in 0 .. spinloops_sem_ { + if got_sem { + break + } + got_sem = ch.writesem.try_wait() + } + if !got_sem { + if no_block { + return .not_ready + } + ch.writesem.wait() + } + if C.atomic_load_u16(&ch.closed) != 0 { + ch.writesem.post() + return .closed + } + if ch.cap == 0 { + // try to advertise current object as readable + mut read_in_progress := false + C.atomic_store_ptr(unsafe { &voidptr(&ch.read_adr) }, src) + wradr = C.atomic_load_ptr(unsafe { &voidptr(&ch.write_adr) }) + if wradr != C.NULL { + mut src2 := src + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.read_adr) }, + &src2, voidptr(0)) + { + ch.writesem.post() + continue + } else { + read_in_progress = true + } + } + if !read_in_progress { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(voidptr(&ch.read_sub_mtx), &null16, + u16(1)) { + null16 = u16(0) + } + if ch.read_subscriber != voidptr(0) { + ch.read_subscriber.sem.post() + } + C.atomic_store_u16(&ch.read_sub_mtx, u16(0)) + } + mut src2 := src + for sp := u32(0); sp < spinloops_ || read_in_progress; sp++ { + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.adr_read) }, + &src2, voidptr(0)) + { + have_swapped = true + read_in_progress = true + break + } + src2 = src + } + mut got_im_sem := false + for sp := u32(0); sp < spinloops_sem_ || read_in_progress; sp++ { + got_im_sem = ch.writesem_im.try_wait() + if got_im_sem { + break + } + } + for { + if got_im_sem { + got_im_sem = false + } else { + ch.writesem_im.wait() + } + if C.atomic_load_u16(&ch.closed) != 0 { + if have_swapped + || C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.adr_read) }, &src2, voidptr(0)) { + ch.writesem.post() + return .success + } else { + return .closed + } + } + if have_swapped + || C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.adr_read) }, &src2, voidptr(0)) { + ch.writesem.post() + break + } else { + // this semaphore was not for us - repost in + ch.writesem_im.post() + if src2 == voidptr(-1) { + ch.readsem.post() + return .closed + } + src2 = src + } + } + return .success + } else { + // buffered channel + mut space_in_queue := false + mut wr_free := C.atomic_load_u32(&ch.write_free) + for wr_free > 0 { + space_in_queue = C.atomic_compare_exchange_weak_u32(&ch.write_free, &wr_free, + wr_free - 1) + if space_in_queue { + break + } + } + if space_in_queue { + mut wr_idx := C.atomic_load_u32(&ch.buf_elem_write_idx) + for { + mut new_wr_idx := wr_idx + 1 + for new_wr_idx >= ch.cap { + new_wr_idx -= ch.cap + } + if C.atomic_compare_exchange_strong_u32(&ch.buf_elem_write_idx, &wr_idx, + new_wr_idx) + { + break + } + } + mut wr_ptr := ch.ringbuf + mut status_adr := ch.statusbuf + unsafe { + wr_ptr += wr_idx * ch.objsize + status_adr += wr_idx * sizeof(u16) + } + mut expected_status := u16(BufferElemStat.unused) + for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status, + u16(BufferElemStat.writing)) { + expected_status = u16(BufferElemStat.unused) + } + unsafe { + C.memcpy(wr_ptr, src, ch.objsize) + } + C.atomic_store_u16(unsafe { &u16(status_adr) }, u16(BufferElemStat.written)) + C.atomic_fetch_add_u32(&ch.read_avail, 1) + ch.readsem.post() + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.read_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + if ch.read_subscriber != voidptr(0) { + ch.read_subscriber.sem.post() + } + C.atomic_store_u16(&ch.read_sub_mtx, u16(0)) + return .success + } else { + if no_block { + return .not_ready + } + ch.writesem.post() + } + } + } + // we should not get here but the V compiler want's to see a return statement + assert false + return .success +} + +[inline] +pub fn (mut ch Channel) pop(dest voidptr) bool { + return ch.try_pop_priv(dest, false) == .success +} + +[inline] +pub fn (mut ch Channel) try_pop(dest voidptr) ChanState { + return ch.try_pop_priv(dest, true) +} + +fn (mut ch Channel) try_pop_priv(dest voidptr, no_block bool) ChanState { + spinloops_sem_, spinloops_ := if no_block { 1, 1 } else { sync.spinloops, sync.spinloops_sem } + mut have_swapped := false + mut write_in_progress := false + for { + mut got_sem := false + if ch.cap == 0 { + // unbuffered channel - first see if a `push()` has adversized + mut rdadr := C.atomic_load_ptr(unsafe { &voidptr(&ch.read_adr) }) + for rdadr != C.NULL { + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.read_adr) }, + &rdadr, voidptr(0)) + { + // there is a writer waiting for us + unsafe { C.memcpy(dest, rdadr, ch.objsize) } + mut nulladr := voidptr(0) + for !C.atomic_compare_exchange_weak_ptr(unsafe { &voidptr(&ch.adr_read) }, + &nulladr, rdadr) { + nulladr = voidptr(0) + } + ch.writesem_im.post() + return .success + } + } + if no_block { + if C.atomic_load_u16(&ch.closed) == 0 { + return .not_ready + } else { + return .closed + } + } + } + // get token to read + for _ in 0 .. spinloops_sem_ { + if got_sem { + break + } + got_sem = ch.readsem.try_wait() + } + if !got_sem { + if no_block { + if C.atomic_load_u16(&ch.closed) == 0 { + return .not_ready + } else { + return .closed + } + } + ch.readsem.wait() + } + if ch.cap > 0 { + // try to get buffer token + mut obj_in_queue := false + mut rd_avail := C.atomic_load_u32(&ch.read_avail) + for rd_avail > 0 { + obj_in_queue = C.atomic_compare_exchange_weak_u32(&ch.read_avail, &rd_avail, + rd_avail - 1) + if obj_in_queue { + break + } + } + if obj_in_queue { + mut rd_idx := C.atomic_load_u32(&ch.buf_elem_read_idx) + for { + mut new_rd_idx := rd_idx + 1 + for new_rd_idx >= ch.cap { + new_rd_idx -= ch.cap + } + if C.atomic_compare_exchange_weak_u32(&ch.buf_elem_read_idx, &rd_idx, + new_rd_idx) + { + break + } + } + mut rd_ptr := ch.ringbuf + mut status_adr := ch.statusbuf + unsafe { + rd_ptr += rd_idx * ch.objsize + status_adr += rd_idx * sizeof(u16) + } + mut expected_status := u16(BufferElemStat.written) + for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status, + u16(BufferElemStat.reading)) { + expected_status = u16(BufferElemStat.written) + } + unsafe { + C.memcpy(dest, rd_ptr, ch.objsize) + } + C.atomic_store_u16(unsafe { &u16(status_adr) }, u16(BufferElemStat.unused)) + C.atomic_fetch_add_u32(&ch.write_free, 1) + ch.writesem.post() + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.write_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + if ch.write_subscriber != voidptr(0) { + ch.write_subscriber.sem.post() + } + C.atomic_store_u16(&ch.write_sub_mtx, u16(0)) + return .success + } + } + // try to advertise `dest` as writable + C.atomic_store_ptr(unsafe { &voidptr(&ch.write_adr) }, dest) + if ch.cap == 0 { + mut rdadr := C.atomic_load_ptr(unsafe { &voidptr(&ch.read_adr) }) + if rdadr != C.NULL { + mut dest2 := dest + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.write_adr) }, + &dest2, voidptr(0)) + { + ch.readsem.post() + continue + } else { + write_in_progress = true + } + } + } + if ch.cap == 0 && !write_in_progress { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.write_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + if ch.write_subscriber != voidptr(0) { + ch.write_subscriber.sem.post() + } + C.atomic_store_u16(&ch.write_sub_mtx, u16(0)) + } + mut dest2 := dest + for sp := u32(0); sp < spinloops_ || write_in_progress; sp++ { + if C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.adr_written) }, + &dest2, voidptr(0)) + { + have_swapped = true + break + } else if dest2 == voidptr(-1) { + ch.readsem.post() + return .closed + } + dest2 = dest + } + mut got_im_sem := false + for sp := u32(0); sp < spinloops_sem_ || write_in_progress; sp++ { + got_im_sem = ch.readsem_im.try_wait() + if got_im_sem { + break + } + } + for { + if got_im_sem { + got_im_sem = false + } else { + ch.readsem_im.wait() + } + if have_swapped + || C.atomic_compare_exchange_strong_ptr(unsafe { &voidptr(&ch.adr_written) }, &dest2, voidptr(0)) { + ch.readsem.post() + break + } else { + // this semaphore was not for us - repost in + ch.readsem_im.post() + if dest2 == voidptr(-1) { + ch.readsem.post() + return .closed + } + dest2 = dest + } + } + break + } + return .success +} + +// Wait `timeout` on any of `channels[i]` until one of them can push (`is_push[i] = true`) or pop (`is_push[i] = false`) +// object referenced by `objrefs[i]`. `timeout = time.infinite` means wait unlimited time. `timeout <= 0` means return +// immediately if no transaction can be performed without waiting. +// return value: the index of the channel on which a transaction has taken place +// -1 if waiting for a transaction has exceeded timeout +// -2 if all channels are closed + +pub fn channel_select(mut channels []&Channel, dir []Direction, mut objrefs []voidptr, timeout time.Duration) int { + assert channels.len == dir.len + assert dir.len == objrefs.len + mut subscr := []Subscription{len: channels.len} + mut sem := unsafe { Semaphore{} } + sem.init(0) + for i, ch in channels { + subscr[i].sem = unsafe { &sem } + if dir[i] == .push { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.write_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + subscr[i].prev = unsafe { &ch.write_subscriber } + unsafe { + subscr[i].nxt = C.atomic_exchange_ptr(&voidptr(&ch.write_subscriber), + &subscr[i]) + } + if voidptr(subscr[i].nxt) != voidptr(0) { + subscr[i].nxt.prev = unsafe { &subscr[i].nxt } + } + C.atomic_store_u16(&ch.write_sub_mtx, u16(0)) + } else { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.read_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + subscr[i].prev = unsafe { &ch.read_subscriber } + unsafe { + subscr[i].nxt = C.atomic_exchange_ptr(&voidptr(&ch.read_subscriber), &subscr[i]) + } + if voidptr(subscr[i].nxt) != voidptr(0) { + subscr[i].nxt.prev = unsafe { &subscr[i].nxt } + } + C.atomic_store_u16(&ch.read_sub_mtx, u16(0)) + } + } + stopwatch := if timeout == time.infinite || timeout <= 0 { + time.StopWatch{} + } else { + time.new_stopwatch() + } + mut event_idx := -1 // negative index means `timed out` + + outer: for { + rnd := rand.u32_in_range(0, u32(channels.len)) + mut num_closed := 0 + for j, _ in channels { + mut i := j + int(rnd) + if i >= channels.len { + i -= channels.len + } + if dir[i] == .push { + stat := channels[i].try_push_priv(objrefs[i], true) + if stat == .success { + event_idx = i + break outer + } else if stat == .closed { + num_closed++ + } + } else { + stat := channels[i].try_pop_priv(objrefs[i], true) + if stat == .success { + event_idx = i + break outer + } else if stat == .closed { + num_closed++ + } + } + } + if num_closed == channels.len { + event_idx = -2 + break outer + } + if timeout <= 0 { + break outer + } + if timeout != time.infinite { + remaining := timeout - stopwatch.elapsed() + if !sem.timed_wait(remaining) { + break outer + } + } else { + sem.wait() + } + } + // reset subscribers + for i, ch in channels { + if dir[i] == .push { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.write_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + unsafe { + *subscr[i].prev = subscr[i].nxt + } + if subscr[i].nxt != 0 { + subscr[i].nxt.prev = subscr[i].prev + // just in case we have missed a semaphore during restore + subscr[i].nxt.sem.post() + } + C.atomic_store_u16(&ch.write_sub_mtx, u16(0)) + } else { + mut null16 := u16(0) + for !C.atomic_compare_exchange_weak_u16(&ch.read_sub_mtx, &null16, u16(1)) { + null16 = u16(0) + } + unsafe { + *subscr[i].prev = subscr[i].nxt + } + if subscr[i].nxt != 0 { + subscr[i].nxt.prev = subscr[i].prev + subscr[i].nxt.sem.post() + } + C.atomic_store_u16(&ch.read_sub_mtx, u16(0)) + } + } + sem.destroy() + return event_idx +} diff --git a/v_windows/v/vlib/sync/pool/README.md b/v_windows/v/vlib/sync/pool/README.md new file mode 100644 index 0000000..bdea5b3 --- /dev/null +++ b/v_windows/v/vlib/sync/pool/README.md @@ -0,0 +1,36 @@ + +The `sync.pool` module provides a convenient way to run identical tasks over +an array of items *in parallel*, without worrying about thread synchronization, +waitgroups, mutexes etc.., you just need to supply a callback function, that +will be called once per each item in your input array. + +After all the work is done in parallel by the worker threads in the pool, +pool.work_on_items will return. You can then call pool.get_results() +to retrieve a list of all the results, that the worker callbacks returned +for each input item. Example: + +```v +import sync.pool + +struct SResult { + s string +} + +fn sprocess(pp &pool.PoolProcessor, idx int, wid int) &SResult { + item := pp.get_item(idx) + println('idx: $idx, wid: $wid, item: ' + item) + return &SResult{item.reverse()} +} + +fn main() { + mut pp := pool.new_pool_processor(callback: sprocess) + pp.work_on_items(['1abc', '2abc', '3abc', '4abc', '5abc', '6abc', '7abc']) + // optionally, you can iterate over the results too: + for x in pp.get_results() { + println('result: $x.s') + } +} +``` + +See https://github.com/vlang/v/blob/master/vlib/sync/pool/pool_test.v for a +more detailed usage example. diff --git a/v_windows/v/vlib/sync/pool/pool.v b/v_windows/v/vlib/sync/pool/pool.v new file mode 100644 index 0000000..b2c5340 --- /dev/null +++ b/v_windows/v/vlib/sync/pool/pool.v @@ -0,0 +1,165 @@ +module pool + +import sync +import runtime + +[trusted] +fn C.atomic_fetch_add_u32(voidptr, u32) u32 + +pub const ( + no_result = voidptr(0) +) + +pub struct PoolProcessor { + thread_cb voidptr +mut: + njobs int + items []voidptr + results []voidptr + ntask u32 // reading/writing to this should be atomic + waitgroup sync.WaitGroup + shared_context voidptr + thread_contexts []voidptr +} + +pub type ThreadCB = fn (p &PoolProcessor, idx int, task_id int) voidptr + +pub struct PoolProcessorConfig { + maxjobs int + callback ThreadCB +} + +// new_pool_processor returns a new PoolProcessor instance. +// The parameters of new_pool_processor are: +// context.maxjobs: when 0 (the default), the PoolProcessor will use a +// number of threads, that is optimal for your system to process your items. +// context.callback: this should be a callback function, that each worker +// thread in the pool will run for each item. +// The callback function will receive as parameters: +// 1) the PoolProcessor instance, so it can call +// p.get_item(idx) to get the actual item at index idx +// 2) idx - the index of the currently processed item +// 3) task_id - the index of the worker thread in which the callback +// function is running. +pub fn new_pool_processor(context PoolProcessorConfig) &PoolProcessor { + if isnil(context.callback) { + panic('You need to pass a valid callback to new_pool_processor.') + } + mut pool := PoolProcessor{ + items: [] + results: [] + shared_context: voidptr(0) + thread_contexts: [] + njobs: context.maxjobs + ntask: 0 + thread_cb: voidptr(context.callback) + } + pool.waitgroup.init() + return &pool +} + +// set_max_jobs gives you the ability to override the number +// of jobs *after* the PoolProcessor had been created already. +pub fn (mut pool PoolProcessor) set_max_jobs(njobs int) { + pool.njobs = njobs +} + +// work_on_items receives a list of items of type T, +// then starts a work pool of pool.njobs threads, each running +// pool.thread_cb in a loop, untill all items in the list, +// are processed. +// When pool.njobs is 0, the number of jobs is determined +// by the number of available cores on the system. +// work_on_items returns *after* all threads finish. +// You can optionally call get_results after that. +pub fn (mut pool PoolProcessor) work_on_items(items []T) { + pool.work_on_pointers(unsafe { items.pointers() }) +} + +pub fn (mut pool PoolProcessor) work_on_pointers(items []voidptr) { + mut njobs := runtime.nr_jobs() + if pool.njobs > 0 { + njobs = pool.njobs + } + pool.items = [] + pool.results = [] + pool.thread_contexts = [] + pool.items << items + pool.results = []voidptr{len: (pool.items.len)} + pool.thread_contexts << []voidptr{len: (pool.items.len)} + pool.waitgroup.add(njobs) + for i := 0; i < njobs; i++ { + if njobs > 1 { + go process_in_thread(mut pool, i) + } else { + // do not run concurrently, just use the same thread: + process_in_thread(mut pool, i) + } + } + pool.waitgroup.wait() +} + +// process_in_thread does the actual work of worker thread. +// It is a workaround for the current inability to pass a +// method in a callback. +fn process_in_thread(mut pool PoolProcessor, task_id int) { + cb := ThreadCB(pool.thread_cb) + ilen := pool.items.len + for { + idx := int(C.atomic_fetch_add_u32(&pool.ntask, 1)) + if idx >= ilen { + break + } + pool.results[idx] = cb(pool, idx, task_id) + } + pool.waitgroup.done() +} + +// get_item - called by the worker callback. +// Retrieves a type safe instance of the currently processed item +pub fn (pool &PoolProcessor) get_item(idx int) T { + return *(&T(pool.items[idx])) +} + +// get_result - called by the main thread to get a specific result. +// Retrieves a type safe instance of the produced result. +pub fn (pool &PoolProcessor) get_result(idx int) T { + return *(&T(pool.results[idx])) +} + +// get_results - get a list of type safe results in the main thread. +pub fn (pool &PoolProcessor) get_results() []T { + mut res := []T{} + for i in 0 .. pool.results.len { + res << *(&T(pool.results[i])) + } + return res +} + +// set_shared_context - can be called during the setup so that you can +// provide a context that is shared between all worker threads, like +// common options/settings. +pub fn (mut pool PoolProcessor) set_shared_context(context voidptr) { + pool.shared_context = context +} + +// get_shared_context - can be called in each worker callback, to get +// the context set by pool.set_shared_context +pub fn (pool &PoolProcessor) get_shared_context() voidptr { + return pool.shared_context +} + +// set_thread_context - can be called during the setup at the start of +// each worker callback, so that the worker callback can have some thread +// local storage area where it can write/read information that is private +// to the given thread, without worrying that it will get overwritten by +// another thread +pub fn (mut pool PoolProcessor) set_thread_context(idx int, context voidptr) { + pool.thread_contexts[idx] = context +} + +// get_thread_context - returns a pointer, that was set with +// pool.set_thread_context . This pointer is private to each thread. +pub fn (pool &PoolProcessor) get_thread_context(idx int) voidptr { + return pool.thread_contexts[idx] +} diff --git a/v_windows/v/vlib/sync/pool/pool_test.v b/v_windows/v/vlib/sync/pool/pool_test.v new file mode 100644 index 0000000..629b524 --- /dev/null +++ b/v_windows/v/vlib/sync/pool/pool_test.v @@ -0,0 +1,52 @@ +import time +import sync.pool + +pub struct SResult { + s string +} + +pub struct IResult { + i int +} + +fn worker_s(p &pool.PoolProcessor, idx int, worker_id int) &SResult { + item := p.get_item(idx) + println('worker_s worker_id: $worker_id | idx: $idx | item: $item') + time.sleep(3 * time.millisecond) + return &SResult{'$item $item'} +} + +fn worker_i(p &pool.PoolProcessor, idx int, worker_id int) &IResult { + item := p.get_item(idx) + println('worker_i worker_id: $worker_id | idx: $idx | item: $item') + time.sleep(5 * time.millisecond) + return &IResult{item * 1000} +} + +fn test_work_on_strings() { + mut pool_s := pool.new_pool_processor( + callback: worker_s + maxjobs: 8 + ) + + pool_s.work_on_items(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']) + for x in pool_s.get_results() { + println(x.s) + assert x.s.len > 1 + } +} + +fn test_work_on_ints() { + // NB: since maxjobs is left empty here, + // the pool processor will use njobs = runtime.nr_jobs so that + // it will work optimally without overloading the system + mut pool_i := pool.new_pool_processor( + callback: worker_i + ) + + pool_i.work_on_items([1, 2, 3, 4, 5, 6, 7, 8]) + for x in pool_i.get_results() { + println(x.i) + assert x.i > 100 + } +} diff --git a/v_windows/v/vlib/sync/select_close_test.v b/v_windows/v/vlib/sync/select_close_test.v new file mode 100644 index 0000000..3a40e2d --- /dev/null +++ b/v_windows/v/vlib/sync/select_close_test.v @@ -0,0 +1,92 @@ +module sync + +import time + +fn do_rec_i64(mut ch Channel) { + mut sum := i64(0) + for i in 0 .. 300 { + if i == 200 { + ch.close() + } + mut a := i64(0) + if ch.pop(&a) { + sum += a + } + } + assert sum == 200 * (200 - 1) / 2 +} + +fn do_send_int(mut ch Channel) { + for i in 0 .. 300 { + ch.push(&i) + } + ch.close() +} + +fn do_send_byte(mut ch Channel) { + for i in 0 .. 300 { + ii := byte(i) + ch.push(&ii) + } + ch.close() +} + +fn do_send_i64(mut ch Channel) { + for i in 0 .. 300 { + ii := i64(i) + ch.push(&ii) + } + ch.close() +} + +fn test_select() { + mut chi := new_channel(0) + mut chl := new_channel(1) + mut chb := new_channel(10) + mut recch := new_channel(0) + go do_rec_i64(mut recch) + go do_send_int(mut chi) + go do_send_byte(mut chb) + go do_send_i64(mut chl) + mut channels := [chi, recch, chl, chb] + directions := [Direction.pop, .push, .pop, .pop] + mut sum := i64(0) + mut rl := i64(0) + mut ri := int(0) + mut rb := byte(0) + mut sl := i64(0) + mut objs := [voidptr(&ri), &sl, &rl, &rb] + for j in 0 .. 1101 { + idx := channel_select(mut channels, directions, mut objs, time.infinite) + match idx { + 0 { + sum += ri + } + 1 { + sl++ + } + 2 { + sum += rl + } + 3 { + sum += rb + } + -2 { + // channel was closed - last item + assert j == 1100 + } + else { + println('got $idx (timeout)') + assert false + } + } + if j == 1100 { + // check also in other direction + assert idx == -2 + } + } + // Use Gauß' formula for the first 2 contributions + // the 3rd contribution is `byte` and must be seen modulo 256 + expected_sum := 2 * (300 * (300 - 1) / 2) + 256 * (256 - 1) / 2 + 44 * (44 - 1) / 2 + assert sum == expected_sum +} diff --git a/v_windows/v/vlib/sync/struct_chan_init_test.v b/v_windows/v/vlib/sync/struct_chan_init_test.v new file mode 100644 index 0000000..a51ea4b --- /dev/null +++ b/v_windows/v/vlib/sync/struct_chan_init_test.v @@ -0,0 +1,14 @@ +struct Abc { + ch chan int +} + +fn f(st Abc) { + st.ch <- 47 +} + +fn test_chan_init() { + st := Abc{} + go f(st) + i := <-st.ch + assert i == 47 +} diff --git a/v_windows/v/vlib/sync/sync_default.c.v b/v_windows/v/vlib/sync/sync_default.c.v new file mode 100644 index 0000000..76f0b44 --- /dev/null +++ b/v_windows/v/vlib/sync/sync_default.c.v @@ -0,0 +1,193 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sync + +import time + +// There's no additional linking (-lpthread) needed for Android. +// See https://stackoverflow.com/a/31277163/1904615 +$if !android { + #flag -lpthread +} + +#include + +[trusted] +fn C.pthread_mutex_init(voidptr, voidptr) int +fn C.pthread_mutex_lock(voidptr) int +fn C.pthread_mutex_unlock(voidptr) int +fn C.pthread_mutex_destroy(voidptr) int +fn C.pthread_rwlockattr_init(voidptr) int +fn C.pthread_rwlockattr_setkind_np(voidptr, int) int +fn C.pthread_rwlockattr_setpshared(voidptr, int) int +fn C.pthread_rwlock_init(voidptr, voidptr) int +fn C.pthread_rwlock_rdlock(voidptr) int +fn C.pthread_rwlock_wrlock(voidptr) int +fn C.pthread_rwlock_unlock(voidptr) int +fn C.sem_init(voidptr, int, u32) int +fn C.sem_post(voidptr) int +fn C.sem_wait(voidptr) int +fn C.sem_trywait(voidptr) int +fn C.sem_timedwait(voidptr, voidptr) int +fn C.sem_destroy(voidptr) int + +// [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function. +[heap] +pub struct Mutex { + mutex C.pthread_mutex_t +} + +[heap] +pub struct RwMutex { + mutex C.pthread_rwlock_t +} + +struct RwMutexAttr { + attr C.pthread_rwlockattr_t +} + +[heap] +struct Semaphore { + sem C.sem_t +} + +pub fn new_mutex() &Mutex { + mut m := &Mutex{} + m.init() + return m +} + +pub fn (mut m Mutex) init() { + C.pthread_mutex_init(&m.mutex, C.NULL) +} + +pub fn new_rwmutex() &RwMutex { + mut m := &RwMutex{} + m.init() + return m +} + +pub fn (mut m RwMutex) init() { + a := RwMutexAttr{} + C.pthread_rwlockattr_init(&a.attr) + // Give writer priority over readers + C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) + C.pthread_rwlockattr_setpshared(&a.attr, C.PTHREAD_PROCESS_PRIVATE) + C.pthread_rwlock_init(&m.mutex, &a.attr) +} + +// @lock(), for *manual* mutex handling, since `lock` is a keyword +pub fn (mut m Mutex) @lock() { + C.pthread_mutex_lock(&m.mutex) +} + +pub fn (mut m Mutex) unlock() { + C.pthread_mutex_unlock(&m.mutex) +} + +// RwMutex has separate read- and write locks +pub fn (mut m RwMutex) @rlock() { + C.pthread_rwlock_rdlock(&m.mutex) +} + +pub fn (mut m RwMutex) @lock() { + C.pthread_rwlock_wrlock(&m.mutex) +} + +// Windows SRWLocks have different function to unlock +// So provide two functions here, too, to have a common interface +pub fn (mut m RwMutex) runlock() { + C.pthread_rwlock_unlock(&m.mutex) +} + +pub fn (mut m RwMutex) unlock() { + C.pthread_rwlock_unlock(&m.mutex) +} + +[inline] +pub fn new_semaphore() &Semaphore { + return new_semaphore_init(0) +} + +pub fn new_semaphore_init(n u32) &Semaphore { + mut sem := &Semaphore{} + sem.init(n) + return sem +} + +pub fn (mut sem Semaphore) init(n u32) { + C.sem_init(&sem.sem, 0, n) +} + +pub fn (mut sem Semaphore) post() { + C.sem_post(&sem.sem) +} + +pub fn (mut sem Semaphore) wait() { + for { + if C.sem_wait(&sem.sem) == 0 { + return + } + e := C.errno + match e { + C.EINTR { + continue // interrupted by signal + } + else { + panic(unsafe { tos_clone(&byte(C.strerror(C.errno))) }) + } + } + } +} + +// `try_wait()` should return as fast as possible so error handling is only +// done when debugging +pub fn (mut sem Semaphore) try_wait() bool { + $if !debug { + return C.sem_trywait(&sem.sem) == 0 + } $else { + if C.sem_trywait(&sem.sem) != 0 { + e := C.errno + match e { + C.EAGAIN { + return false + } + else { + panic(unsafe { tos_clone(&byte(C.strerror(C.errno))) }) + } + } + } + return true + } +} + +pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool { + t_spec := timeout.timespec() + for { + if C.sem_timedwait(&sem.sem, &t_spec) == 0 { + return true + } + e := C.errno + match e { + C.EINTR { + continue // interrupted by signal + } + C.ETIMEDOUT { + break + } + else { + panic(unsafe { tos_clone(&byte(C.strerror(e))) }) + } + } + } + return false +} + +pub fn (sem Semaphore) destroy() { + res := C.sem_destroy(&sem.sem) + if res == 0 { + return + } + panic(unsafe { tos_clone(&byte(C.strerror(res))) }) +} diff --git a/v_windows/v/vlib/sync/sync_macos.c.v b/v_windows/v/vlib/sync/sync_macos.c.v new file mode 100644 index 0000000..3f83198 --- /dev/null +++ b/v_windows/v/vlib/sync/sync_macos.c.v @@ -0,0 +1,232 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sync + +import time + +#flag -lpthread +#include +#include + +[trusted] +fn C.pthread_mutex_init(voidptr, voidptr) int +fn C.pthread_mutex_lock(voidptr) int +fn C.pthread_mutex_unlock(voidptr) int +fn C.pthread_mutex_destroy(voidptr) int +fn C.pthread_rwlockattr_init(voidptr) int +fn C.pthread_rwlockattr_setkind_np(voidptr, int) int +fn C.pthread_rwlockattr_setpshared(voidptr, int) int +fn C.pthread_rwlock_init(voidptr, voidptr) int +fn C.pthread_rwlock_rdlock(voidptr) int +fn C.pthread_rwlock_wrlock(voidptr) int +fn C.pthread_rwlock_unlock(voidptr) int +fn C.pthread_condattr_init(voidptr) int +fn C.pthread_condattr_setpshared(voidptr, int) int +fn C.pthread_condattr_destroy(voidptr) int +fn C.pthread_cond_init(voidptr, voidptr) int +fn C.pthread_cond_signal(voidptr) int +fn C.pthread_cond_wait(voidptr, voidptr) int +fn C.pthread_cond_timedwait(voidptr, voidptr, voidptr) int +fn C.pthread_cond_destroy(voidptr) int + +// [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function. +[heap] +pub struct Mutex { + mutex C.pthread_mutex_t +} + +[heap] +pub struct RwMutex { + mutex C.pthread_rwlock_t +} + +struct RwMutexAttr { + attr C.pthread_rwlockattr_t +} + +struct CondAttr { + attr C.pthread_condattr_t +} + +/* +MacOSX has no unnamed semaphores and no `timed_wait()` at all + so we emulate the behaviour with other devices +*/ +[heap] +struct Semaphore { + mtx C.pthread_mutex_t + cond C.pthread_cond_t +mut: + count u32 +} + +pub fn new_mutex() &Mutex { + mut m := &Mutex{} + m.init() + return m +} + +pub fn (mut m Mutex) init() { + C.pthread_mutex_init(&m.mutex, C.NULL) +} + +pub fn new_rwmutex() &RwMutex { + mut m := &RwMutex{} + m.init() + return m +} + +pub fn (mut m RwMutex) init() { + a := RwMutexAttr{} + C.pthread_rwlockattr_init(&a.attr) + // Give writer priority over readers + C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) + C.pthread_rwlockattr_setpshared(&a.attr, C.PTHREAD_PROCESS_PRIVATE) + C.pthread_rwlock_init(&m.mutex, &a.attr) +} + +// @lock(), for *manual* mutex handling, since `lock` is a keyword +pub fn (mut m Mutex) @lock() { + C.pthread_mutex_lock(&m.mutex) +} + +pub fn (mut m Mutex) unlock() { + C.pthread_mutex_unlock(&m.mutex) +} + +// RwMutex has separate read- and write locks +pub fn (mut m RwMutex) @rlock() { + C.pthread_rwlock_rdlock(&m.mutex) +} + +pub fn (mut m RwMutex) @lock() { + C.pthread_rwlock_wrlock(&m.mutex) +} + +// Windows SRWLocks have different function to unlock +// So provide two functions here, too, to have a common interface +pub fn (mut m RwMutex) runlock() { + C.pthread_rwlock_unlock(&m.mutex) +} + +pub fn (mut m RwMutex) unlock() { + C.pthread_rwlock_unlock(&m.mutex) +} + +[inline] +pub fn new_semaphore() &Semaphore { + return new_semaphore_init(0) +} + +pub fn new_semaphore_init(n u32) &Semaphore { + mut sem := &Semaphore{} + sem.init(n) + return sem +} + +pub fn (mut sem Semaphore) init(n u32) { + C.atomic_store_u32(&sem.count, n) + C.pthread_mutex_init(&sem.mtx, C.NULL) + attr := CondAttr{} + C.pthread_condattr_init(&attr.attr) + C.pthread_condattr_setpshared(&attr.attr, C.PTHREAD_PROCESS_PRIVATE) + C.pthread_cond_init(&sem.cond, &attr.attr) + C.pthread_condattr_destroy(&attr.attr) +} + +pub fn (mut sem Semaphore) post() { + mut c := C.atomic_load_u32(&sem.count) + for c > 1 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c + 1) { + return + } + } + C.pthread_mutex_lock(&sem.mtx) + c = C.atomic_fetch_add_u32(&sem.count, 1) + if c == 0 { + C.pthread_cond_signal(&sem.cond) + } + C.pthread_mutex_unlock(&sem.mtx) +} + +pub fn (mut sem Semaphore) wait() { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return + } + } + C.pthread_mutex_lock(&sem.mtx) + c = C.atomic_load_u32(&sem.count) + + outer: for { + if c == 0 { + C.pthread_cond_wait(&sem.cond, &sem.mtx) + c = C.atomic_load_u32(&sem.count) + } + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + if c > 1 { + C.pthread_cond_signal(&sem.cond) + } + break outer + } + } + } + C.pthread_mutex_unlock(&sem.mtx) +} + +pub fn (mut sem Semaphore) try_wait() bool { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return true + } + } + return false +} + +pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return true + } + } + C.pthread_mutex_lock(&sem.mtx) + t_spec := timeout.timespec() + mut res := 0 + c = C.atomic_load_u32(&sem.count) + + outer: for { + if c == 0 { + res = C.pthread_cond_timedwait(&sem.cond, &sem.mtx, &t_spec) + if res == C.ETIMEDOUT { + break outer + } + c = C.atomic_load_u32(&sem.count) + } + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + if c > 1 { + C.pthread_cond_signal(&sem.cond) + } + break outer + } + } + } + C.pthread_mutex_unlock(&sem.mtx) + return res == 0 +} + +pub fn (mut sem Semaphore) destroy() { + mut res := C.pthread_cond_destroy(&sem.cond) + if res == 0 { + res = C.pthread_mutex_destroy(&sem.mtx) + if res == 0 { + return + } + } + panic(unsafe { tos_clone(&byte(C.strerror(res))) }) +} diff --git a/v_windows/v/vlib/sync/sync_windows.c.v b/v_windows/v/vlib/sync/sync_windows.c.v new file mode 100644 index 0000000..d0942b7 --- /dev/null +++ b/v_windows/v/vlib/sync/sync_windows.c.v @@ -0,0 +1,212 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sync + +import time + +#include +#include + +fn C.GetSystemTimeAsFileTime(lpSystemTimeAsFileTime &C._FILETIME) +fn C.InitializeConditionVariable(voidptr) +fn C.WakeConditionVariable(voidptr) +fn C.SleepConditionVariableSRW(voidptr, voidptr, u32, u32) int + +// TODO: The suggestion of using CriticalSection instead of mutex +// was discussed. Needs consideration. + +// Mutex HANDLE +type MHANDLE = voidptr + +// Semaphore HANDLE +type SHANDLE = voidptr + +//[init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function. + +// `SRWLOCK` is much more performant that `Mutex` on Windows, so use that in both cases since we don't want to share with other processes +[heap] +pub struct Mutex { +mut: + mx C.SRWLOCK // mutex handle +} + +[heap] +pub struct RwMutex { +mut: + mx C.SRWLOCK // mutex handle +} + +[heap] +struct Semaphore { + mtx C.SRWLOCK + cond C.CONDITION_VARIABLE +mut: + count u32 +} + +pub fn new_mutex() &Mutex { + mut m := &Mutex{} + m.init() + return m +} + +pub fn new_rwmutex() &RwMutex { + mut m := &RwMutex{} + m.init() + return m +} + +pub fn (mut m Mutex) init() { + C.InitializeSRWLock(&m.mx) +} + +pub fn (mut m RwMutex) init() { + C.InitializeSRWLock(&m.mx) +} + +pub fn (mut m Mutex) @lock() { + C.AcquireSRWLockExclusive(&m.mx) +} + +pub fn (mut m Mutex) unlock() { + C.ReleaseSRWLockExclusive(&m.mx) +} + +// RwMutex has separate read- and write locks +pub fn (mut m RwMutex) @rlock() { + C.AcquireSRWLockShared(&m.mx) +} + +pub fn (mut m RwMutex) @lock() { + C.AcquireSRWLockExclusive(&m.mx) +} + +// Windows SRWLocks have different function to unlock +// So provide two functions here, too, to have a common interface +pub fn (mut m RwMutex) runlock() { + C.ReleaseSRWLockShared(&m.mx) +} + +pub fn (mut m RwMutex) unlock() { + C.ReleaseSRWLockExclusive(&m.mx) +} + +pub fn (mut m Mutex) destroy() { + // nothing to do +} + +[inline] +pub fn new_semaphore() &Semaphore { + return new_semaphore_init(0) +} + +pub fn new_semaphore_init(n u32) &Semaphore { + mut sem := &Semaphore{} + sem.init(n) + return sem +} + +pub fn (mut sem Semaphore) init(n u32) { + C.atomic_store_u32(&sem.count, n) + C.InitializeSRWLock(&sem.mtx) + C.InitializeConditionVariable(&sem.cond) +} + +pub fn (mut sem Semaphore) post() { + mut c := C.atomic_load_u32(&sem.count) + for c > 1 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c + 1) { + return + } + } + C.AcquireSRWLockExclusive(&sem.mtx) + c = C.atomic_fetch_add_u32(&sem.count, 1) + if c == 0 { + C.WakeConditionVariable(&sem.cond) + } + C.ReleaseSRWLockExclusive(&sem.mtx) +} + +pub fn (mut sem Semaphore) wait() { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return + } + } + C.AcquireSRWLockExclusive(&sem.mtx) + c = C.atomic_load_u32(&sem.count) + + outer: for { + if c == 0 { + C.SleepConditionVariableSRW(&sem.cond, &sem.mtx, C.INFINITE, 0) + c = C.atomic_load_u32(&sem.count) + } + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + if c > 1 { + C.WakeConditionVariable(&sem.cond) + } + break outer + } + } + } + C.ReleaseSRWLockExclusive(&sem.mtx) +} + +pub fn (mut sem Semaphore) try_wait() bool { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return true + } + } + return false +} + +pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool { + mut c := C.atomic_load_u32(&sem.count) + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + return true + } + } + mut ft_start := C._FILETIME{} + C.GetSystemTimeAsFileTime(&ft_start) + time_end := ((u64(ft_start.dwHighDateTime) << 32) | ft_start.dwLowDateTime) + + u64(timeout / (100 * time.nanosecond)) + mut t_ms := timeout.sys_milliseconds() + C.AcquireSRWLockExclusive(&sem.mtx) + mut res := 0 + c = C.atomic_load_u32(&sem.count) + + outer: for { + if c == 0 { + res = C.SleepConditionVariableSRW(&sem.cond, &sem.mtx, t_ms, 0) + if res == 0 { + break outer + } + c = C.atomic_load_u32(&sem.count) + } + for c > 0 { + if C.atomic_compare_exchange_weak_u32(&sem.count, &c, c - 1) { + if c > 1 { + C.WakeConditionVariable(&sem.cond) + } + break outer + } + } + C.GetSystemTimeAsFileTime(&ft_start) + time_now := ((u64(ft_start.dwHighDateTime) << 32) | ft_start.dwLowDateTime) // in 100ns + if time_now > time_end { + break outer // timeout exceeded + } + t_ms = u32((time_end - time_now) / 10000) + } + C.ReleaseSRWLockExclusive(&sem.mtx) + return res != 0 +} + +pub fn (s Semaphore) destroy() { +} diff --git a/v_windows/v/vlib/sync/threads/threads.c.v b/v_windows/v/vlib/sync/threads/threads.c.v new file mode 100644 index 0000000..02506c2 --- /dev/null +++ b/v_windows/v/vlib/sync/threads/threads.c.v @@ -0,0 +1,13 @@ +module threads + +// This module adds the necessary compiler flags for using threads. +// It is automatically imported by code that does `go func()` . +// See vlib/v/parser/pratt.v, search for ast.GoExpr . +// The goal is that programs, that do not use threads at all will not need +// to link to -lpthread etc. +// NB: on some platforms like Android, linking -lpthread is not needed too. +// See https://stackoverflow.com/a/31277163/1904615 + +$if !windows && !android { + #flag -lpthread +} diff --git a/v_windows/v/vlib/sync/threads/threads.v b/v_windows/v/vlib/sync/threads/threads.v new file mode 100644 index 0000000..f20fc0e --- /dev/null +++ b/v_windows/v/vlib/sync/threads/threads.v @@ -0,0 +1,4 @@ +module threads + +// This file is just a placeholder. +// The actual implementation is backend/platform specific, so see threads.c.v, threads.js.v etc. diff --git a/v_windows/v/vlib/sync/waitgroup.v b/v_windows/v/vlib/sync/waitgroup.v new file mode 100644 index 0000000..3e9ac39 --- /dev/null +++ b/v_windows/v/vlib/sync/waitgroup.v @@ -0,0 +1,84 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module sync + +[trusted] +fn C.atomic_fetch_add_u32(voidptr, u32) u32 + +[trusted] +fn C.atomic_load_u32(voidptr) u32 + +[trusted] +fn C.atomic_compare_exchange_weak_u32(voidptr, voidptr, u32) bool + +// WaitGroup +// Do not copy an instance of WaitGroup, use a ref instead. +// +// usage: in main thread: +// `wg := sync.new_waitgroup() +// `wg.add(nr_jobs)` before starting jobs with `go ...` +// `wg.wait()` to wait for all jobs to have finished +// +// in each parallel job: +// `wg.done()` when finished +// +// [init_with=new_waitgroup] // TODO: implement support for init_with struct attribute, and disallow WaitGroup{} from outside the sync.new_waitgroup() function. +[heap] +struct WaitGroup { +mut: + task_count u32 // current task count - reading/writing should be atomic + wait_count u32 // current wait count - reading/writing should be atomic + sem Semaphore // This blocks wait() until tast_countreleased by add() +} + +pub fn new_waitgroup() &WaitGroup { + mut wg := WaitGroup{} + wg.init() + return &wg +} + +pub fn (mut wg WaitGroup) init() { + wg.sem.init(0) +} + +// add increments (+ve delta) or decrements (-ve delta) task count by delta +// and unblocks any wait() calls if task count becomes zero. +// add panics if task count drops below zero. +pub fn (mut wg WaitGroup) add(delta int) { + old_nrjobs := int(C.atomic_fetch_add_u32(&wg.task_count, u32(delta))) + new_nrjobs := old_nrjobs + delta + mut num_waiters := C.atomic_load_u32(&wg.wait_count) + if new_nrjobs < 0 { + panic('Negative number of jobs in waitgroup') + } + + if new_nrjobs == 0 && num_waiters > 0 { + // clear waiters + for !C.atomic_compare_exchange_weak_u32(&wg.wait_count, &num_waiters, 0) { + if num_waiters == 0 { + return + } + } + for (num_waiters > 0) { + wg.sem.post() + num_waiters-- + } + } +} + +// done is a convenience fn for add(-1) +pub fn (mut wg WaitGroup) done() { + wg.add(-1) +} + +// wait blocks until all tasks are done (task count becomes zero) +pub fn (mut wg WaitGroup) wait() { + nrjobs := int(C.atomic_load_u32(&wg.task_count)) + if nrjobs == 0 { + // no need to wait + return + } + C.atomic_fetch_add_u32(&wg.wait_count, 1) + wg.sem.wait() // blocks until task_count becomes 0 +} diff --git a/v_windows/v/vlib/sync/waitgroup_test.v b/v_windows/v/vlib/sync/waitgroup_test.v new file mode 100644 index 0000000..493665f --- /dev/null +++ b/v_windows/v/vlib/sync/waitgroup_test.v @@ -0,0 +1,41 @@ +module sync + +import time + +fn test_waitgroup_reuse() { + mut wg := new_waitgroup() + + wg.add(1) + wg.done() + + wg.add(1) + mut executed := false + go fn (mut wg WaitGroup, executed voidptr) { + defer { + wg.done() + } + unsafe { + *(&bool(executed)) = true + } + time.sleep(100 * time.millisecond) + assert wg.wait_count == 1 + }(mut wg, voidptr(&executed)) + + wg.wait() + assert executed + assert wg.wait_count == 0 +} + +fn test_waitgroup_no_use() { + mut done := false + go fn (done voidptr) { + time.sleep(1 * time.second) + if *(&bool(done)) == false { + panic('test_waitgroup_no_use did not complete in time') + } + }(voidptr(&done)) + + mut wg := new_waitgroup() + wg.wait() + done = true +} diff --git a/v_windows/v/vlib/szip/szip.v b/v_windows/v/vlib/szip/szip.v new file mode 100644 index 0000000..9cb157d --- /dev/null +++ b/v_windows/v/vlib/szip/szip.v @@ -0,0 +1,296 @@ +module szip + +import os + +#flag -I @VEXEROOT/thirdparty/zip +#include "zip.c" +#include "zip.h" + +struct C.zip_t { +} + +type Zip = C.zip_t + +fn C.zip_open(&char, int, char) &Zip + +fn C.zip_close(&Zip) + +fn C.zip_entry_open(&Zip, &byte) int + +fn C.zip_entry_openbyindex(&Zip, int) int + +fn C.zip_entry_close(&Zip) int + +fn C.zip_entry_name(&Zip) &byte + +fn C.zip_entry_index(&Zip) int + +fn C.zip_entry_isdir(&Zip) int + +fn C.zip_entry_size(&Zip) u64 + +fn C.zip_entry_crc32(&Zip) u32 + +fn C.zip_entry_write(&Zip, voidptr, size_t) int + +fn C.zip_entry_fwrite(&Zip, &char) int + +fn C.zip_entry_read(&Zip, &voidptr, &size_t) int + +fn C.zip_entry_noallocread(&Zip, voidptr, size_t) int + +fn C.zip_entry_fread(&Zip, &char) int + +fn C.zip_total_entries(&Zip) int + +fn C.zip_extract_without_callback(&char, &char) int + +// CompressionLevel lists compression levels, see in "thirdparty/zip/miniz.h" +pub enum CompressionLevel { + no_compression = 0 + best_speed = 1 + best_compression = 9 + uber_compression = 10 + default_level = 6 + default_compression = -1 +} + +// OpenMode lists the opening modes +// .write: opens a file for reading/extracting (the file must exists). +// .read_only: creates an empty file for writing. +// .append: appends to an existing archive. +pub enum OpenMode { + write + read_only + append +} + +[inline] +fn (om OpenMode) to_byte() byte { + return match om { + .write { + `w` + } + .read_only { + `r` + } + .append { + `a` + } + } +} + +// open opens zip archive with compression level using the given mode. +// name: the name of the zip file to open. +// level: can be any value of the CompressionLevel enum. +// mode: can be any value of the OpenMode enum. +pub fn open(name string, level CompressionLevel, mode OpenMode) ?&Zip { + if name.len == 0 { + return error('szip: name of file empty') + } + p_zip := &Zip(C.zip_open(&char(name.str), int(level), char(mode.to_byte()))) + if isnil(p_zip) { + return error('szip: cannot open/create/append new zip archive') + } + return p_zip +} + +// close closes the zip archive, releases resources - always finalize. +[inline] +pub fn (mut z Zip) close() { + C.zip_close(z) +} + +// open_entry opens an entry by name in the zip archive. +// For zip archive opened in 'w' or 'a' mode the function will append +// a new entry. In readonly mode the function tries to locate the entry +// in global dictionary. +pub fn (mut zentry Zip) open_entry(name string) ? { + res := C.zip_entry_open(zentry, &char(name.str)) + if res == -1 { + return error('szip: cannot open archive entry') + } +} + +// open_entry_by_index opens an entry by index in the archive. +pub fn (mut z Zip) open_entry_by_index(index int) ? { + res := C.zip_entry_openbyindex(z, index) + if res == -1 { + return error('szip: cannot open archive entry at index $index') + } +} + +// close_entry closes a zip entry, flushes buffer and releases resources. +[inline] +pub fn (mut zentry Zip) close_entry() { + C.zip_entry_close(zentry) +} + +// name returns a local name of the current zip entry. +// The main difference between user's entry name and local entry name +// is optional relative path. +// Following .ZIP File Format Specification - the path stored MUST not contain +// a drive or device letter, or a leading slash. +// All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' +// for compatibility with Amiga and UNIX file systems etc. +pub fn (mut zentry Zip) name() string { + name := unsafe { &byte(C.zip_entry_name(zentry)) } + if name == 0 { + return '' + } + return unsafe { name.vstring() } +} + +// index returns an index of the current zip entry. +pub fn (mut zentry Zip) index() ?int { + index := int(C.zip_entry_index(zentry)) + if index == -1 { + return error('szip: cannot get current index of zip entry') + } + return index // must be check for INVALID_VALUE +} + +// is_dir determines if the current zip entry is a directory entry. +pub fn (mut zentry Zip) is_dir() ?bool { + isdir := C.zip_entry_isdir(zentry) + if isdir < 0 { + return error('szip: cannot check entry type') + } + return isdir == 1 +} + +// size returns an uncompressed size of the current zip entry. +[inline] +pub fn (mut zentry Zip) size() u64 { + return C.zip_entry_size(zentry) +} + +// crc32 returns CRC-32 checksum of the current zip entry. +[inline] +pub fn (mut zentry Zip) crc32() u32 { + return C.zip_entry_crc32(zentry) +} + +// write_entry compresses an input buffer for the current zip entry. +pub fn (mut zentry Zip) write_entry(data []byte) ? { + if (data[0] & 0xff) == -1 { + return error('szip: cannot write entry') + } + res := C.zip_entry_write(zentry, data.data, data.len) + if res != 0 { + return error('szip: failed to write entry') + } +} + +// create_entry compresses a file for the current zip entry. +pub fn (mut zentry Zip) create_entry(name string) ? { + res := C.zip_entry_fwrite(zentry, &char(name.str)) + if res != 0 { + return error('szip: failed to create entry') + } +} + +// read_entry extracts the current zip entry into output buffer. +// The function allocates sufficient memory for an output buffer. +// NOTE: remember to release the memory allocated for an output buffer. +// for large entries, please take a look at zip_entry_extract function. +pub fn (mut zentry Zip) read_entry() ?voidptr { + mut buf := &byte(0) + mut bsize := size_t(0) + res := C.zip_entry_read(zentry, unsafe { &voidptr(&buf) }, &bsize) + if res == -1 { + return error('szip: cannot read properly data from entry') + } + return buf +} + +// read_entry_buf extracts the current zip entry into user specified buffer +pub fn (mut zentry Zip) read_entry_buf(buf voidptr, in_bsize int) ?int { + bsize := size_t(in_bsize) + res := C.zip_entry_noallocread(zentry, buf, bsize) + if res == -1 { + return error('szip: cannot read properly data from entry') + } + return res +} + +// extract_entry extracts the current zip entry into output file. +pub fn (mut zentry Zip) extract_entry(path string) ? { + if !os.is_file(path) { + return error('szip: cannot open file for extracting, "$path" not exists') + } + res := C.zip_entry_fread(zentry, &char(path.str)) + if res != 0 { + return error('szip: failed to extract entry') + } +} + +// extract zip file to directory +pub fn extract_zip_to_dir(file string, dir string) ?bool { + if C.access(&char(dir.str), 0) == -1 { + return error('szip: cannot open directory for extracting, directory not exists') + } + res := C.zip_extract_without_callback(&char(file.str), &char(dir.str)) + return res == 0 +} + +// zip files (full path) to zip file +pub fn zip_files(path_to_file []string, path_to_export_zip string) ? { + // open or create new zip + mut zip := open(path_to_export_zip, .no_compression, .write) or { panic(err) } + + // add all files from the directory to the archive + for file in path_to_file { + // add file to zip + zip.open_entry(os.base(file)) or { panic(err) } + file_as_byte := os.read_bytes(file) or { panic(err) } + zip.write_entry(file_as_byte) or { panic(err) } + + zip.close_entry() + } + + // close zip + defer { + zip.close() + } +} + +/* +TODO add +// zip all files in directory to zip file +pub fn zip_folder(path_to_dir string, path_to_export_zip string) { + + // get list files from directory + files := os.ls(path_to_dir) or { panic(err) } + + // open or create new zip + mut zip := szip.open(path_to_export_zip, .no_compression, .write) or { panic(err) } + + // add all files from the directory to the archive + for file in files { + eprintln('Zipping $file to ${path_to_export_zip}...') + println(path_to_dir + file) + + // add file to zip + zip.open_entry(file) or { panic(err) } + file_as_byte := os.read_bytes(path_to_dir + '/'+ file) or { panic(err) } + zip.write_entry(file_as_byte) or { panic(err) } + + zip.close_entry() + } + + // close zip + zip.close() + + eprintln('Successfully') +} +*/ + +// total returns the number of all entries (files and directories) in the zip archive. +pub fn (mut zentry Zip) total() ?int { + tentry := int(C.zip_total_entries(zentry)) + if tentry == -1 { + return error('szip: cannot count total entries') + } + return tentry +} diff --git a/v_windows/v/vlib/szip/szip_test.v b/v_windows/v/vlib/szip/szip_test.v new file mode 100644 index 0000000..e9d6412 --- /dev/null +++ b/v_windows/v/vlib/szip/szip_test.v @@ -0,0 +1,88 @@ +import szip +import os + +const ( + test_out_zip = 'v_test_zip.zip' + test_path = 'zip files' + fname1 = 'file_1.txt' + fpath1 = os.join_path(test_path, fname1) + fname2 = 'file_2.txt' + fpath2 = os.join_path(test_path, fname2) +) + +fn test_szip_create_temp_files() ? { + os.chdir(os.temp_dir()) or {} + os.rmdir_all(test_path) or {} + os.mkdir(test_path) ? + os.write_file(fpath1, 'file one') ? + os.write_file(fpath2, 'file two') ? + assert os.exists(fpath1) + assert os.exists(fpath2) +} + +fn test_zipping_files() ? { + files := (os.ls(test_path) ?).map(os.join_path(test_path, it)) + szip.zip_files(files, test_out_zip) ? + assert os.exists(test_out_zip) +} + +fn test_extract_zipped_files() ? { + os.rm(fpath1) ? + os.rm(fpath2) ? + szip.extract_zip_to_dir(test_out_zip, test_path) ? + assert os.exists(fpath1) + assert os.exists(fpath2) + assert (os.read_file(fpath1) ?) == 'file one' + assert (os.read_file(fpath2) ?) == 'file two' + os.rmdir_all(test_path) ? + os.rm(test_out_zip) or {} +} + +fn test_reading_zipping_files() ? { + n_files := 2 + mut file_name_list := []string{} + for i in 0 .. n_files { + file_name_list << 'file_${i:02}.txt' + } + + os.chdir(os.temp_dir()) or {} + os.rmdir_all(test_path) or {} + os.mkdir(test_path) ? + for c, f_name in file_name_list { + tmp_path := os.join_path(test_path, f_name) + os.write_file(tmp_path, 'file ${c:02}') ? + assert os.exists(tmp_path) + } + files := (os.ls(test_path) ?).map(os.join_path(test_path, it)) + + szip.zip_files(files, test_out_zip) ? + assert os.exists(test_out_zip) + + mut zp := szip.open(test_out_zip, szip.CompressionLevel.no_compression, szip.OpenMode.read_only) ? + n_entries := zp.total() ? + assert n_entries == n_files + + unsafe { + data_len := 'file XX'.len + buf_size := 32 + buf := malloc(data_len * 2) + + for _ in 0 .. n_files { + zp.open_entry_by_index(0) ? + name := zp.name() + assert name in file_name_list + + zp.read_entry_buf(buf, buf_size) ? + buf[data_len] = 0 + tmp_str := tos(buf, data_len) + + assert tmp_str[0..4] == 'file' + assert tmp_str[5..7] == name[5..7] + + zp.close_entry() + } + + free(buf) + } + zp.close() +} diff --git a/v_windows/v/vlib/term/README.md b/v_windows/v/vlib/term/README.md new file mode 100644 index 0000000..f9fcb9e --- /dev/null +++ b/v_windows/v/vlib/term/README.md @@ -0,0 +1,85 @@ +# Quickstart + +The V `term` module is a module designed to provide the building blocks +for building very simple TUI apps. +For more complex apps, you should really look at the `term.input` module, +as it includes terminal events, is easier to use and is much more performant for large draws. + +# Use + +You can use the `term` module to either color the output on a terminal +or to decide on where to put the output in your terminal. + +For example let's make a simple program which prints colored text in the middle of the terminal. + +```v +import term +import os + +fn main() { + term.clear() // clears the content in the terminal + width, height := term.get_terminal_size() // get the size of the terminal + term.set_cursor_position(x: width / 2, y: height / 2) // now we point the cursor to the middle of the terminal + println(term.strikethrough(term.bright_green('hello world'))) // Print green text + term.set_cursor_position(x: 0, y: height) // Sets the position of the cursor to the bottom of the terminal + // Keep prompting until the user presses the q key + for { + if var := os.input_opt('press q to quit: ') { + if var != 'q' { + continue + } + break + } + println('') + break + } + println('Goodbye.') +} +``` + +This simple program covers many of the principal aspects of the `term ` module. + +# API + +Here are some functions you should be aware of in the `term `module: + +```v oksyntax +import term + +// returns the height and the width of the terminal +width, height := term.get_terminal_size() +// returns the string as green text to be printed on stdout +term.ok_message('cool') +// returns the string as red text to be printed on stdout +term.fail_message('oh, no') +// returns the string as yellow text to be printed on stdout +term.warning_message('be warned') +// clears the entire terminal and leaves a blank one +term.clear() +// colors the output of the output, the available colors are: +// black,blue,yellow,green,cyan,gray,bright_blue,bright_green,bright_red,bright_black,bright_cyan +term.yellow('submarine') +// transforms the given string into bold text +term.bold('and beautiful') +// puts a strikethrough into the given string +term.strikethrough('the core of the problem') +// underlines the given string +term.underline('important') +// colors the background of the output following the given color +// the available colors are: black, blue, yellow, green, cyan, gray +term.bg_green('field') +// sets the position of the cursor at a given place in the terminal +term.set_cursor_position(x: 5, y: 10) +// moves the cursor up +term.cursor_up() +// moves the cursor down +term.cursor_down() +// moves the cursor to the right +term.cursor_forward() +// moves the cursor to the left +term.cursor_back() +// shows the cursor +term.show_cursor() +// hides the cursor +term.hide_cursor() +``` diff --git a/v_windows/v/vlib/term/colors.v b/v_windows/v/vlib/term/colors.v new file mode 100644 index 0000000..f89d56b --- /dev/null +++ b/v_windows/v/vlib/term/colors.v @@ -0,0 +1,198 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module term + +pub fn format(msg string, open string, close string) string { + return '\x1b[${open}m$msg\x1b[${close}m' +} + +pub fn format_rgb(r int, g int, b int, msg string, open string, close string) string { + return '\x1b[$open;2;$r;$g;${b}m$msg\x1b[${close}m' +} + +pub fn rgb(r int, g int, b int, msg string) string { + return format_rgb(r, g, b, msg, '38', '39') +} + +pub fn bg_rgb(r int, g int, b int, msg string) string { + return format_rgb(r, g, b, msg, '48', '49') +} + +pub fn hex(hex int, msg string) string { + return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '38', '39') +} + +pub fn bg_hex(hex int, msg string) string { + return format_rgb(hex >> 16, hex >> 8 & 0xFF, hex & 0xFF, msg, '48', '49') +} + +pub fn bg_black(msg string) string { + return format(msg, '40', '49') +} + +pub fn bright_bg_black(msg string) string { + return format(msg, '100', '49') +} + +pub fn bg_blue(msg string) string { + return format(msg, '44', '49') +} + +pub fn bright_bg_blue(msg string) string { + return format(msg, '104', '49') +} + +pub fn bg_cyan(msg string) string { + return format(msg, '46', '49') +} + +pub fn bright_bg_cyan(msg string) string { + return format(msg, '106', '49') +} + +pub fn bg_green(msg string) string { + return format(msg, '42', '49') +} + +pub fn bright_bg_green(msg string) string { + return format(msg, '102', '49') +} + +pub fn bg_magenta(msg string) string { + return format(msg, '45', '49') +} + +pub fn bright_bg_magenta(msg string) string { + return format(msg, '105', '49') +} + +pub fn bg_red(msg string) string { + return format(msg, '41', '49') +} + +pub fn bright_bg_red(msg string) string { + return format(msg, '101', '49') +} + +pub fn bg_white(msg string) string { + return format(msg, '47', '49') +} + +pub fn bright_bg_white(msg string) string { + return format(msg, '107', '49') +} + +pub fn bg_yellow(msg string) string { + return format(msg, '43', '49') +} + +pub fn bright_bg_yellow(msg string) string { + return format(msg, '103', '49') +} + +pub fn black(msg string) string { + return format(msg, '30', '39') +} + +pub fn bright_black(msg string) string { + return format(msg, '90', '39') +} + +pub fn blue(msg string) string { + return format(msg, '34', '39') +} + +pub fn bright_blue(msg string) string { + return format(msg, '94', '39') +} + +pub fn bold(msg string) string { + return format(msg, '1', '22') +} + +pub fn cyan(msg string) string { + return format(msg, '36', '39') +} + +pub fn bright_cyan(msg string) string { + return format(msg, '96', '39') +} + +pub fn dim(msg string) string { + return format(msg, '2', '22') +} + +pub fn green(msg string) string { + return format(msg, '32', '39') +} + +pub fn bright_green(msg string) string { + return format(msg, '92', '39') +} + +pub fn gray(msg string) string { + return bright_black(msg) +} + +pub fn hidden(msg string) string { + return format(msg, '8', '28') +} + +pub fn italic(msg string) string { + return format(msg, '3', '23') +} + +pub fn inverse(msg string) string { + return format(msg, '7', '27') +} + +pub fn magenta(msg string) string { + return format(msg, '35', '39') +} + +pub fn bright_magenta(msg string) string { + return format(msg, '95', '39') +} + +pub fn reset(msg string) string { + return format(msg, '0', '0') +} + +pub fn red(msg string) string { + return format(msg, '31', '39') +} + +pub fn bright_red(msg string) string { + return format(msg, '91', '39') +} + +pub fn strikethrough(msg string) string { + return format(msg, '9', '29') +} + +pub fn underline(msg string) string { + return format(msg, '4', '24') +} + +pub fn white(msg string) string { + return format(msg, '37', '39') +} + +pub fn bright_white(msg string) string { + return format(msg, '97', '39') +} + +pub fn yellow(msg string) string { + return format(msg, '33', '39') +} + +pub fn bright_yellow(msg string) string { + return format(msg, '93', '39') +} + +// highlight_command highlights the command with an on-brand background +// to make CLI commands immediately recognizable. +pub fn highlight_command(command string) string { + return bright_white(bg_cyan(' $command ')) +} diff --git a/v_windows/v/vlib/term/control.v b/v_windows/v/vlib/term/control.v new file mode 100644 index 0000000..377cc42 --- /dev/null +++ b/v_windows/v/vlib/term/control.v @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module term + +// Sources for ANSI Control Sequences +// https://github.com/RajeshPatkarInstitute/Panim +// https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html +// https://en.wikipedia.org/wiki/ANSI_escape_code +// Support for Windows +// https://en.wikipedia.org/wiki/ANSI.SYS +// #include +// C.SetConsoleMode(C.ENABLE_VIRTUAL_TERMINAL_INPUT) +// Setting cursor to the given position +// x is the x coordinate +// y is the y coordinate +pub fn set_cursor_position(c Coord) { + print('\x1b[$c.y;$c.x' + 'H') +} + +// n is number of cells +// direction: A is up / North +// direction: B is down / South +// direction: C is forward / East +// direction: D is backward / West +pub fn move(n int, direction string) { + print('\x1b[$n$direction') +} + +pub fn cursor_up(n int) { + move(n, 'A') +} + +pub fn cursor_down(n int) { + move(n, 'B') +} + +pub fn cursor_forward(n int) { + move(n, 'C') +} + +pub fn cursor_back(n int) { + move(n, 'D') +} + +// type: 0 -> current cursor position to end of the screen +// type: 1 -> current cursor position to beginning of the screen +// type: 2 -> clears entire screen +// type: 3 -> clears entire screen and also delete scrollback buffer + +pub fn erase_display(t string) { + print('\x1b[' + t + 'J') +} + +pub fn erase_toend() { + erase_display('0') +} + +pub fn erase_tobeg() { + erase_display('1') +} + +// clears entire screen and returns cursor to top left-corner +pub fn erase_clear() { + print('\033[H\033[J') +} + +pub fn erase_del_clear() { + erase_display('3') +} + +// type: 0 -> current cursor position to end of the line +// type: 1 -> current cursor position to beginning of the line +// type: 2 -> clears entire line +// Note: Cursor position does not change +pub fn erase_line(t string) { + print('\x1b[' + t + 'K') +} + +pub fn erase_line_toend() { + erase_line('0') +} + +pub fn erase_line_tobeg() { + erase_line('1') +} + +pub fn erase_line_clear() { + erase_line('2') +} + +// Will make cursor appear if not visible +pub fn show_cursor() { + print('\x1b[?25h') +} + +// Will make cursor invisible +pub fn hide_cursor() { + print('\x1b[?25l') +} + +// clear_previous_line - useful for progressbars. +// It moves the cursor to start of line, then 1 line above, +// then erases the line. In effect the next println will overwrite +// the previous content. +pub fn clear_previous_line() { + print('\r\x1b[1A\x1b[2K') +} diff --git a/v_windows/v/vlib/term/term.js.v b/v_windows/v/vlib/term/term.js.v new file mode 100644 index 0000000..be85eca --- /dev/null +++ b/v_windows/v/vlib/term/term.js.v @@ -0,0 +1,8 @@ +module term + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { + // TODO Find a way to get proper width & height of the terminal + // on a Javascript environment + return default_columns_size, default_rows_size +} diff --git a/v_windows/v/vlib/term/term.v b/v_windows/v/vlib/term/term.v new file mode 100644 index 0000000..460c0b1 --- /dev/null +++ b/v_windows/v/vlib/term/term.v @@ -0,0 +1,198 @@ +module term + +import os +import strings.textscanner + +const ( + default_columns_size = 80 + default_rows_size = 25 +) + +// Coord - used by term.get_cursor_position and term.set_cursor_position +pub struct Coord { +pub mut: + x int + y int +} + +// can_show_color_on_stdout returns true if colors are allowed in stdout; +// returns false otherwise. +pub fn can_show_color_on_stdout() bool { + return supports_escape_sequences(1) +} + +// can_show_color_on_stderr returns true if colors are allowed in stderr; +// returns false otherwise. +pub fn can_show_color_on_stderr() bool { + return supports_escape_sequences(2) +} + +// failed returns a bold white on red version of the string `s` +// If colors are not allowed, returns the string `s` +pub fn failed(s string) string { + if can_show_color_on_stdout() { + return bg_red(bold(white(s))) + } + return s +} + +// ok_message returns a colored string with green color. +// If colors are not allowed, returns a given string. +pub fn ok_message(s string) string { + if can_show_color_on_stdout() { + return green(' $s ') + } + return s +} + +// fail_message returns a colored string with red color. +// If colors are not allowed, returns a given string. +pub fn fail_message(s string) string { + return failed(' $s ') +} + +// warn_message returns a colored string with yellow color. +// If colors are not allowed, returns a given string. +pub fn warn_message(s string) string { + if can_show_color_on_stdout() { + return bright_yellow(' $s ') + } + return s +} + +// colorize returns a colored string by running the specified `cfn` over +// the message `s`, only if colored output is supported by the terminal. +// Example: term.colorize(term.yellow, 'the message') +pub fn colorize(cfn fn (string) string, s string) string { + if can_show_color_on_stdout() { + return cfn(s) + } + return s +} + +// strip_ansi removes any ANSI sequences in the `text` +pub fn strip_ansi(text string) string { + // This is a port of https://github.com/kilobyte/colorized-logs/blob/master/ansi2txt.c + // \e, [, 1, m, a, b, c, \e, [, 2, 2, m => abc + mut input := textscanner.new(text) + mut output := []byte{cap: text.len} + mut ch := 0 + for ch != -1 { + ch = input.next() + if ch == 27 { + ch = input.next() + if ch == `[` { + for { + ch = input.next() + if ch in [`;`, `?`] || (ch >= `0` && ch <= `9`) { + continue + } + break + } + } else if ch == `]` { + ch = input.next() + if ch >= `0` && ch <= `9` { + for { + ch = input.next() + if ch == -1 || ch == 7 { + break + } + if ch == 27 { + ch = input.next() + break + } + } + } + } else if ch == `%` { + ch = input.next() + } + } else if ch != -1 { + output << byte(ch) + } + } + return output.bytestr() +} + +// h_divider returns a horizontal divider line with a dynamic width, +// that depends on the current terminal settings. +// If an empty string is passed in, print enough spaces to make a new line +pub fn h_divider(divider string) string { + cols, _ := get_terminal_size() + mut result := '' + if divider.len > 0 { + result = divider.repeat(1 + (cols / divider.len)) + } else { + result = ' '.repeat(1 + cols) + } + return result[0..cols] +} + +// header_left returns a horizontal divider line with a title text on the left. +// e.g: term.header_left('TITLE', '=') +// ==== TITLE ========================= +pub fn header_left(text string, divider string) string { + plain_text := strip_ansi(text) + xcols, _ := get_terminal_size() + cols := imax(1, xcols) + relement := if divider.len > 0 { divider } else { ' ' } + hstart := relement.repeat(4)[0..4] + remaining_cols := (cols - (hstart.len + 1 + plain_text.len + 1)) + hend := relement.repeat((remaining_cols + 1) / relement.len)[0..remaining_cols] + return '$hstart $text $hend' +} + +// header returns a horizontal divider line with a centered text in the middle. +// e.g: term.header('TEXT', '=') +// =============== TEXT =============== +pub fn header(text string, divider string) string { + if text.len == 0 { + return h_divider(divider) + } + xcols, _ := get_terminal_size() + cols := imax(1, xcols) + tlimit := imax(1, if cols > text.len + 2 + 2 * divider.len { + text.len + } else { + cols - 3 - 2 * divider.len + }) + tlimit_alligned := if (tlimit % 2) != (cols % 2) { tlimit + 1 } else { tlimit } + tstart := imax(0, (cols - tlimit_alligned) / 2) + mut ln := '' + if divider.len > 0 { + ln = divider.repeat(1 + cols / divider.len)[0..cols] + } else { + ln = ' '.repeat(1 + cols) + } + if ln.len == 1 { + return ln + ' ' + text[0..tlimit] + ' ' + ln + } + return ln[0..tstart] + ' ' + text[0..tlimit] + ' ' + ln[tstart + tlimit + 2..cols] +} + +fn imax(x int, y int) int { + return if x > y { x } else { y } +} + +fn supports_escape_sequences(fd int) bool { + vcolors_override := os.getenv('VCOLORS') + if vcolors_override == 'always' { + return true + } + if vcolors_override == 'never' { + return false + } + env_term := os.getenv('TERM') + if env_term == 'dumb' { + return false + } + $if windows { + env_conemu := os.getenv('ConEmuANSI') + if env_conemu == 'ON' { + return true + } + // 4 is enable_virtual_terminal_processing + return (os.is_atty(fd) & 0x0004) > 0 + } $else { + return os.is_atty(fd) > 0 + } +} diff --git a/v_windows/v/vlib/term/term_nix.c.v b/v_windows/v/vlib/term/term_nix.c.v new file mode 100644 index 0000000..45a0a9b --- /dev/null +++ b/v_windows/v/vlib/term/term_nix.c.v @@ -0,0 +1,105 @@ +module term + +import os + +#include +#include // TIOCGWINSZ + +pub struct C.winsize { +pub: + ws_row u16 + ws_col u16 + ws_xpixel u16 + ws_ypixel u16 +} + +fn C.ioctl(fd int, request u64, arg voidptr) int + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { + if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { + return default_columns_size, default_rows_size + } + w := C.winsize{} + C.ioctl(1, u64(C.TIOCGWINSZ), &w) + return int(w.ws_col), int(w.ws_row) +} + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { + if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { + return Coord{ + x: 0 + y: 0 + } + } + // TODO: use termios.h, C.tcgetattr & C.tcsetattr directly, + // instead of using `stty` + mut oldsettings := os.execute('stty -g') + if oldsettings.exit_code < 0 { + oldsettings = os.Result{} + } + os.system('stty -echo -icanon time 0') + print('\033[6n') + mut ch := int(0) + mut i := 0 + // ESC [ YYY `;` XXX `R` + mut reading_x := false + mut reading_y := false + mut x := 0 + mut y := 0 + for { + ch = C.getchar() + b := byte(ch) + i++ + if i >= 15 { + panic('C.getchar() called too many times') + } + // state management: + if b == `R` { + break + } + if b == `[` { + reading_y = true + reading_x = false + continue + } + if b == `;` { + reading_y = false + reading_x = true + continue + } + // converting string vals to ints: + if reading_x { + x *= 10 + x += (b - byte(`0`)) + } + if reading_y { + y *= 10 + y += (b - byte(`0`)) + } + } + // restore the old terminal settings: + os.system('stty $oldsettings.output') + return Coord{ + x: x + y: y + } +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { + if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' { + return true + } + print('\033]0') + print(title) + print('\007') + return true +} + +// clear clears current terminal screen. +pub fn clear() { + print('\x1b[2J') + print('\x1b[H') +} diff --git a/v_windows/v/vlib/term/term_test.v b/v_windows/v/vlib/term/term_test.v new file mode 100644 index 0000000..00f9293 --- /dev/null +++ b/v_windows/v/vlib/term/term_test.v @@ -0,0 +1,115 @@ +import os +import term + +fn test_get_terminal_size() { + cols, _ := term.get_terminal_size() + assert cols > 0 +} + +fn test_h_divider() { + divider := term.h_divider('-') + assert divider.len > 0 + assert divider[0] == `-` + assert divider[divider.len - 1] == `-` +} + +fn test_h_divider_multiple_characters() { + xdivider := term.h_divider('abc') + assert xdivider.len > 0 + assert xdivider.contains('abcabc') +} + +fn test_header() { + divider := term.h_divider('-') + term_width := divider.len + assert term_width > 0 + empty_header := term.header('', '-') + short_header := term.header('reasonable header', '-') + very_long_header := term.header(['abc'].repeat(500).join(' '), '-') + very_long_header_2 := term.header(['abcd'].repeat(500).join(' '), '-') + /* + eprintln(divider) + eprintln(empty_header) + eprintln(short_header) + eprintln(term.header('another longer header', '_-/\\')) + eprintln(term.header('another longer header', '-')) + eprintln(term.header('short', '-')) + eprintln(term.header('12345', '-')) + eprintln(term.header('1234', '-')) + eprintln(term.header('123', '-')) + eprintln(term.header('12', '-')) + eprintln(term.header('1', '-')) + eprintln(very_long_header) + eprintln(divider) + eprintln(very_long_header_2) + eprintln(term.header(['abcd'].repeat(500).join(' '), '_-/\\')) + eprintln(term.header(['abcd'].repeat(500).join(' '), '_-//')) + eprintln(term.header('1', '_-/\\\/')) + eprintln(term.header('12', '_-/\\\/')) + eprintln(term.header('123', '_-/\\\/')) + eprintln(term.header('1234', '_-/\\/\\')) + eprintln(term.header('', '-')) + */ + assert term_width == empty_header.len + assert term_width == short_header.len + assert term_width == very_long_header.len + assert term_width == very_long_header_2.len + assert term_width == term.header('1234', '_-/\\/\\').len +} + +fn test_get_cursor_position() { + original_position := term.get_cursor_position() + cursor_position_1 := term.get_cursor_position() + assert original_position.x == cursor_position_1.x + assert original_position.y == cursor_position_1.y + // + term.set_cursor_position( + x: 10 + y: 11 + ) + cursor_position_2 := term.get_cursor_position() + // + term.set_cursor_position( + x: 5 + y: 6 + ) + cursor_position_3 := term.get_cursor_position() + // + term.set_cursor_position(original_position) + eprintln('original_position: $original_position') + eprintln('cursor_position_2: $cursor_position_2') + eprintln('cursor_position_3: $cursor_position_3') + // 0,0 is returned on dumb terminals + if cursor_position_2.x == 0 && cursor_position_2.y == 0 { + return + } + if cursor_position_3.x == 0 && cursor_position_3.y == 0 { + return + } + assert cursor_position_2.x == 10 + assert cursor_position_2.y == 11 + assert cursor_position_3.x == 5 + assert cursor_position_3.y == 6 +} + +fn test_set_terminal_title() { + // do not change the current terminal title outside of CI: + if os.getenv('CI') != 'true' { + return + } + title_change := term.set_terminal_title('v is awesome!') + assert title_change == true +} + +fn test_strip_ansi() { + strings := [ + 'abc', + term.bold('abc'), + term.yellow('abc'), + term.bold(term.red('abc')), + term.strikethrough(term.inverse(term.dim(term.bold(term.bright_bg_blue('abc'))))), + ] + for s in strings { + assert term.strip_ansi(s) == 'abc' + } +} diff --git a/v_windows/v/vlib/term/term_windows.c.v b/v_windows/v/vlib/term/term_windows.c.v new file mode 100644 index 0000000..1d2f032 --- /dev/null +++ b/v_windows/v/vlib/term/term_windows.c.v @@ -0,0 +1,125 @@ +module term + +import os + +[typedef] +struct C.COORD { +mut: + X i16 + Y i16 +} + +[typedef] +struct C.SMALL_RECT { +mut: + Left u16 + Top u16 + Right u16 + Bottom u16 +} + +// win: CONSOLE_SCREEN_BUFFER_INFO +// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str +[typedef] +struct C.CONSOLE_SCREEN_BUFFER_INFO { +mut: + dwSize C.COORD + dwCursorPosition C.COORD + wAttributes u16 + srWindow C.SMALL_RECT + dwMaximumWindowSize C.COORD +} + +union C.uChar { +mut: + UnicodeChar rune + AsciiChar byte +} + +[typedef] +struct C.CHAR_INFO { +mut: + Char C.uChar + Attributes u16 +} + +// ref - https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo +fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/setconsoletitle +fn C.SetConsoleTitle(title &u16) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/setconsolecursorposition +fn C.SetConsoleCursorPosition(handle C.HANDLE, coord C.COORD) bool + +// ref - https://docs.microsoft.com/en-us/windows/console/scrollconsolescreenbuffer +fn C.ScrollConsoleScreenBuffer(output C.HANDLE, scroll_rect &C.SMALL_RECT, clip_rect &C.SMALL_RECT, des C.COORD, fill &C.CHAR_INFO) bool + +// get_terminal_size returns a number of colums and rows of terminal window. +pub fn get_terminal_size() (int, int) { + if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { + info := C.CONSOLE_SCREEN_BUFFER_INFO{} + if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { + columns := int(info.srWindow.Right - info.srWindow.Left + 1) + rows := int(info.srWindow.Bottom - info.srWindow.Top + 1) + return columns, rows + } + } + return default_columns_size, default_rows_size +} + +// get_cursor_position returns a Coord containing the current cursor position +pub fn get_cursor_position() Coord { + mut res := Coord{} + if os.is_atty(1) > 0 && os.getenv('TERM') != 'dumb' { + info := C.CONSOLE_SCREEN_BUFFER_INFO{} + if C.GetConsoleScreenBufferInfo(C.GetStdHandle(C.STD_OUTPUT_HANDLE), &info) { + res.x = info.dwCursorPosition.X + res.y = info.dwCursorPosition.Y + } + } + return res +} + +// set_terminal_title change the terminal title +pub fn set_terminal_title(title string) bool { + title_change := C.SetConsoleTitle(title.to_wide()) + return title_change +} + +// clear clears current terminal screen. +// Implementation taken from https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2. +pub fn clear() { + hconsole := C.GetStdHandle(C.STD_OUTPUT_HANDLE) + mut csbi := C.CONSOLE_SCREEN_BUFFER_INFO{} + mut scrollrect := C.SMALL_RECT{} + mut scrolltarget := C.COORD{} + mut fill := C.CHAR_INFO{} + + // Get the number of character cells in the current buffer. + if !C.GetConsoleScreenBufferInfo(hconsole, &csbi) { + return + } + // Scroll the rectangle of the entire buffer. + scrollrect.Left = 0 + scrollrect.Top = 0 + scrollrect.Right = u16(csbi.dwSize.X) + scrollrect.Bottom = u16(csbi.dwSize.Y) + + // Scroll it upwards off the top of the buffer with a magnitude of the entire height. + scrolltarget.X = 0 + scrolltarget.Y = (0 - csbi.dwSize.Y) + + // Fill with empty spaces with the buffer's default text attribute. + fill.Char.UnicodeChar = rune(` `) + fill.Attributes = csbi.wAttributes + + // Do the scroll + C.ScrollConsoleScreenBuffer(hconsole, &scrollrect, C.NULL, scrolltarget, &fill) + + // Move the cursor to the top left corner too. + csbi.dwCursorPosition.X = 0 + csbi.dwCursorPosition.Y = 0 + + C.SetConsoleCursorPosition(hconsole, csbi.dwCursorPosition) +} diff --git a/v_windows/v/vlib/term/ui/README.md b/v_windows/v/vlib/term/ui/README.md new file mode 100644 index 0000000..6bce054 --- /dev/null +++ b/v_windows/v/vlib/term/ui/README.md @@ -0,0 +1,99 @@ +## `term.ui` + +A V module for designing terminal UI apps + +#### Quickstart + +```v +import term.ui as tui + +struct App { +mut: + tui &tui.Context = 0 +} + +fn event(e &tui.Event, x voidptr) { + mut app := &App(x) + println(e) + if e.typ == .key_down && e.code == .escape { + exit(0) + } +} + +fn frame(x voidptr) { + mut app := &App(x) + + app.tui.clear() + app.tui.set_bg_color(r: 63, g: 81, b: 181) + app.tui.draw_rect(20, 6, 41, 10) + app.tui.draw_text(24, 8, 'Hello from V!') + app.tui.set_cursor_position(0, 0) + + app.tui.reset() + app.tui.flush() +} + +mut app := &App{} +app.tui = tui.init( + user_data: app + event_fn: event + frame_fn: frame + hide_cursor: true +) +app.tui.run() ? +``` + +See the `/examples/term.ui/` folder for more usage examples. + +#### Configuration + +- `user_data voidptr` - a pointer to any `user_data`, it will be passed as the last argument to + each callback. Used for accessing your app context from the different callbacks. +- `init_fn fn(voidptr)` - a callback that will be called after initialization + and before the first event / frame. Useful for initializing any user data. +- `frame_fn fn(voidptr)` - a callback that will be fired on each frame, + at a rate of `frame_rate` frames per second. +`event_fn fn(&Event, voidptr)` - a callback that will be fired for every event received. +- `cleanup_fn fn(voidptr)` - a callback that will be fired once, before the application exits. +- `fail_fn fn(string)` - a callback that will be fired + if a fatal error occurs during app initialization. +- `buffer_size int = 256` - the internal size of the read buffer. + Increasing it may help in case you're missing events, but you probably shouldn't lower + this value unless you make sure you're still receiving all events. In general, + higher frame rates work better with lower buffer sizes, and vice versa. +- `frame_rate int = 30` - the number of times per second that the `frame` callback will be fired. + 30fps is a nice balance between smoothness and performance, + but you can increase or lower it as you wish. +- `hide_cursor bool` - whether to hide the mouse cursor. Useful if you want to use your own. +- `capture_events bool` - sets the terminal into raw mode, which makes it intercept some + escape codes such as `ctrl + c` and `ctrl + z`. + Useful if you want to use those key combinations in your app. +- `window_title string` - sets the title of the terminal window. + This may be changed later, by calling the `set_window_title()` method. +- `reset []int = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 19]` - a list of reset signals, + to setup handlers to cleanup the terminal state when they're received. + You should not need to change this, unless you know what you're doing. + +All of these fields may be omitted, in which case, the default value will be used. +In the case of the various callbacks, they will not be fired if a handler has not been specified. + + +#### FAQ + +Q: My terminal (doesn't receive events / doesn't print anything / prints gibberish characters), +what's up with that? +A: Please check if your terminal. The module has been tested with `xterm`-based terminals on Linux +(like `gnome-terminal` and `konsole`), and `Terminal.app` and `iterm2` on macOS. +If your terminal does not work, open an issue with the output of `echo $TERM`. + +Q: There are screen tearing issues when doing large prints +A: This is an issue with how terminals render frames, +as they may decide to do so in the middle of receiving a frame, +and cannot be fully fixed unless your console implements the [synchronized updates spec](https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec). +It can be reduced *drastically*, though, by using the rendering methods built in to the module, +and by only painting frames when your app's content has actually changed. + +Q: Why does the module only emit `keydown` events, and not `keyup` like `sokol`/`gg`? +A: It's because of the way terminals emit events. Every key event is received as a keypress, +and there isn't a way of telling terminals to send keyboard events differently, +nor a reliable way of converting these into `keydown` / `keyup` events. diff --git a/v_windows/v/vlib/term/ui/color.v b/v_windows/v/vlib/term/ui/color.v new file mode 100644 index 0000000..3e0a0bb --- /dev/null +++ b/v_windows/v/vlib/term/ui/color.v @@ -0,0 +1,88 @@ +// radare - LGPL - Copyright 2013-2020 - pancake, xarkes +// ansi 256 color extension for r_cons +// https://en.wikipedia.org/wiki/ANSI_color + +module ui + +const ( + value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]! + color_table = init_color_table() +) + +[direct_array_access] +fn init_color_table() []int { + mut color_table_ := []int{len: 256} + // ansi colors + color_table_[0] = 0x000000 + color_table_[1] = 0x800000 + color_table_[2] = 0x008000 + color_table_[3] = 0x808000 + color_table_[4] = 0x000080 + color_table_[5] = 0x800080 + color_table_[6] = 0x008080 + color_table_[7] = 0xc0c0c0 + color_table_[8] = 0x808080 + color_table_[9] = 0xff0000 + color_table_[10] = 0x00ff00 + color_table_[11] = 0xffff00 + color_table_[12] = 0x0000ff + color_table_[13] = 0xff00ff + color_table_[14] = 0x00ffff + color_table_[15] = 0xffffff + // color palette + for i in 0 .. 216 { + r := ui.value_range[(i / 36) % 6] + g := ui.value_range[(i / 6) % 6] + b := ui.value_range[i % 6] + color_table_[i + 16] = ((r << 16) & 0xffffff) + ((g << 8) & 0xffff) + (b & 0xff) + } + // grayscale + for i in 0 .. 24 { + r := 8 + (i * 10) + color_table_[i + 232] = ((r << 16) & 0xffffff) + ((r << 8) & 0xffff) + (r & 0xff) + } + return color_table_ +} + +fn clamp(x int, y int, z int) int { + if x < y { + return y + } + if x > z { + return z + } + return x +} + +fn approximate_rgb(r int, g int, b int) int { + grey := r > 0 && r < 255 && r == g && r == b + if grey { + return 232 + int(f64(r) / (255 / 24.1)) + } + k := int(256.0 / 6) + r2 := clamp(r / k, 0, 5) + g2 := clamp(g / k, 0, 5) + b2 := clamp(b / k, 0, 5) + return 16 + (r2 * 36) + (g2 * 6) + b2 +} + +fn lookup_rgb(r int, g int, b int) int { + color := (r << 16) + (g << 8) + b + // lookup extended colors only, coz non-extended can be changed by users. + for i in 16 .. 256 { + if ui.color_table[i] == color { + return i + } + } + return -1 +} + +// converts an RGB color to an ANSI 256-color, approximating it to the nearest available color +// if an exact match is not found +fn rgb2ansi(r int, g int, b int) int { + c := lookup_rgb(r, g, b) + if c == -1 { + return approximate_rgb(r, g, b) + } + return c +} diff --git a/v_windows/v/vlib/term/ui/consoleapi_windows.c.v b/v_windows/v/vlib/term/ui/consoleapi_windows.c.v new file mode 100644 index 0000000..a6002a6 --- /dev/null +++ b/v_windows/v/vlib/term/ui/consoleapi_windows.c.v @@ -0,0 +1,82 @@ +module ui + +union C.Event { + KeyEvent C.KEY_EVENT_RECORD + MouseEvent C.MOUSE_EVENT_RECORD + WindowBufferSizeEvent C.WINDOW_BUFFER_SIZE_RECORD + MenuEvent C.MENU_EVENT_RECORD + FocusEvent C.FOCUS_EVENT_RECORD +} + +[typedef] +struct C.INPUT_RECORD { + EventType u16 + Event C.Event +} + +union C.uChar { + UnicodeChar rune + AsciiChar byte +} + +[typedef] +struct C.KEY_EVENT_RECORD { + bKeyDown int + wRepeatCount u16 + wVirtualKeyCode u16 + wVirtualScanCode u16 + uChar C.uChar + dwControlKeyState u32 +} + +[typedef] +struct C.MOUSE_EVENT_RECORD { + dwMousePosition C.COORD + dwButtonState u32 + dwControlKeyState u32 + dwEventFlags u32 +} + +[typedef] +struct C.WINDOW_BUFFER_SIZE_RECORD { + dwSize C.COORD +} + +[typedef] +struct C.MENU_EVENT_RECORD { + dwCommandId u32 +} + +[typedef] +struct C.FOCUS_EVENT_RECORD { + bSetFocus int +} + +[typedef] +struct C.COORD { + X i16 + Y i16 +} + +[typedef] +struct C.SMALL_RECT { + Left u16 + Top u16 + Right u16 + Bottom u16 +} + +[typedef] +struct C.CONSOLE_SCREEN_BUFFER_INFO { + dwSize C.COORD + dwCursorPosition C.COORD + wAttributes u16 + srWindow C.SMALL_RECT + dwMaximumWindowSize C.COORD +} + +fn C.ReadConsoleInput(hConsoleInput C.HANDLE, lpBuffer &C.INPUT_RECORD, nLength u32, lpNumberOfEventsRead &u32) bool + +fn C.GetNumberOfConsoleInputEvents(hConsoleInput C.HANDLE, lpcNumberOfEvents &u32) bool + +fn C.GetConsoleScreenBufferInfo(handle C.HANDLE, info &C.CONSOLE_SCREEN_BUFFER_INFO) bool diff --git a/v_windows/v/vlib/term/ui/input.v b/v_windows/v/vlib/term/ui/input.v new file mode 100644 index 0000000..0532b39 --- /dev/null +++ b/v_windows/v/vlib/term/ui/input.v @@ -0,0 +1,241 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os + +pub enum KeyCode { + null = 0 + tab = 9 + enter = 10 + escape = 27 + space = 32 + backspace = 127 + exclamation = 33 + double_quote = 34 + hashtag = 35 + dollar = 36 + percent = 37 + ampersand = 38 + single_quote = 39 + left_paren = 40 + right_paren = 41 + asterisk = 42 + plus = 43 + comma = 44 + minus = 45 + period = 46 + slash = 47 + _0 = 48 + _1 = 49 + _2 = 50 + _3 = 51 + _4 = 52 + _5 = 53 + _6 = 54 + _7 = 55 + _8 = 56 + _9 = 57 + colon = 58 + semicolon = 59 + less_than = 60 + equal = 61 + greater_than = 62 + question_mark = 63 + at = 64 + a = 97 + b = 98 + c = 99 + d = 100 + e = 101 + f = 102 + g = 103 + h = 104 + i = 105 + j = 106 + k = 107 + l = 108 + m = 109 + n = 110 + o = 111 + p = 112 + q = 113 + r = 114 + s = 115 + t = 116 + u = 117 + v = 118 + w = 119 + x = 120 + y = 121 + z = 122 + left_square_bracket = 91 + backslash = 92 + right_square_bracket = 93 + caret = 94 + underscore = 95 + backtick = 96 + left_curly_bracket = 123 + vertical_bar = 124 + right_curly_bracket = 125 + tilde = 126 + insert = 260 + delete = 261 + up = 262 + down = 263 + right = 264 + left = 265 + page_up = 266 + page_down = 267 + home = 268 + end = 269 + f1 = 290 + f2 = 291 + f3 = 292 + f4 = 293 + f5 = 294 + f6 = 295 + f7 = 296 + f8 = 297 + f9 = 298 + f10 = 299 + f11 = 300 + f12 = 301 + f13 = 302 + f14 = 303 + f15 = 304 + f16 = 305 + f17 = 306 + f18 = 307 + f19 = 308 + f20 = 309 + f21 = 310 + f22 = 311 + f23 = 312 + f24 = 313 +} + +pub enum Direction { + unknown + up + down + left + right +} + +pub enum MouseButton { + unknown + left + middle + right +} + +pub enum EventType { + unknown + mouse_down + mouse_up + mouse_move + mouse_drag + mouse_scroll + key_down + resized +} + +[flag] +pub enum Modifiers { + ctrl + shift + alt +} + +pub struct Event { +pub: + typ EventType + // Mouse event info + x int + y int + button MouseButton + direction Direction + // Keyboard event info + code KeyCode + modifiers Modifiers + ascii byte + utf8 string + // Resized event info + width int + height int +} + +pub struct Context { + ExtraContext // contains fields specific to an implementation +pub: + cfg Config // adsasdas +mut: + print_buf []byte + paused bool + enable_su bool + enable_rgb bool +pub mut: + frame_count u64 + window_width int + window_height int +} + +pub struct Config { + user_data voidptr + init_fn fn (voidptr) + frame_fn fn (voidptr) + cleanup_fn fn (voidptr) + event_fn fn (&Event, voidptr) + fail_fn fn (string) + + buffer_size int = 256 + frame_rate int = 30 + use_x11 bool + + window_title string + hide_cursor bool + capture_events bool + use_alternate_buffer bool = true + skip_init_checks bool + // All kill signals to set up exit listeners on: + reset []os.Signal = [.hup, .int, .quit, .ill, .abrt, .bus, .fpe, .kill, .segv, .pipe, .alrm, .term, + .stop, +] +} + +[inline] +fn (ctx &Context) init() { + if ctx.cfg.init_fn != voidptr(0) { + ctx.cfg.init_fn(ctx.cfg.user_data) + } +} + +[inline] +fn (ctx &Context) frame() { + if ctx.cfg.frame_fn != voidptr(0) { + ctx.cfg.frame_fn(ctx.cfg.user_data) + } +} + +[inline] +fn (ctx &Context) cleanup() { + if ctx.cfg.cleanup_fn != voidptr(0) { + ctx.cfg.cleanup_fn(ctx.cfg.user_data) + } +} + +[inline] +fn (ctx &Context) fail(error string) { + if ctx.cfg.fail_fn != voidptr(0) { + ctx.cfg.fail_fn(error) + } +} + +[inline] +fn (ctx &Context) event(event &Event) { + if ctx.cfg.event_fn != voidptr(0) { + ctx.cfg.event_fn(event, ctx.cfg.user_data) + } +} diff --git a/v_windows/v/vlib/term/ui/input_nix.c.v b/v_windows/v/vlib/term/ui/input_nix.c.v new file mode 100644 index 0000000..e806fb8 --- /dev/null +++ b/v_windows/v/vlib/term/ui/input_nix.c.v @@ -0,0 +1,70 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +struct ExtraContext { +mut: + read_buf []byte +} + +const ( + ctx_ptr = &Context(0) +) + +pub fn init(cfg Config) &Context { + mut ctx := &Context{ + cfg: cfg + } + ctx.read_buf = []byte{cap: cfg.buffer_size} + + // lmao + unsafe { + x := &ui.ctx_ptr + *x = ctx + _ = x + } + return ctx +} + +[inline] +fn save_title() { + // restore the previously saved terminal title + print('\x1b[22;0t') +} + +[inline] +fn load_title() { + // restore the previously saved terminal title + print('\x1b[23;0t') +} + +pub fn (mut ctx Context) run() ? { + if ctx.cfg.use_x11 { + ctx.fail('error: x11 backend not implemented yet') + exit(1) + } else { + ctx.termios_setup() ? + ctx.termios_loop() + } +} + +// shifts the array left, to remove any data that was just read, and updates its len +// TODO: remove +[inline] +fn (mut ctx Context) shift(len int) { + unsafe { + C.memmove(ctx.read_buf.data, &byte(ctx.read_buf.data) + len, ctx.read_buf.cap - len) + ctx.resize_arr(ctx.read_buf.len - len) + } +} + +// TODO: don't actually do this, lmao +[inline] +fn (mut ctx Context) resize_arr(size int) { + mut l := unsafe { &ctx.read_buf.len } + unsafe { + *l = size + _ = l + } +} diff --git a/v_windows/v/vlib/term/ui/input_windows.c.v b/v_windows/v/vlib/term/ui/input_windows.c.v new file mode 100644 index 0000000..bd9782d --- /dev/null +++ b/v_windows/v/vlib/term/ui/input_windows.c.v @@ -0,0 +1,326 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os +import time + +const ( + buf_size = 64 + ctx_ptr = &Context(0) + stdin_at_startup = u32(0) +) + +struct ExtraContext { +mut: + stdin_handle C.HANDLE + stdout_handle C.HANDLE + read_buf [buf_size]C.INPUT_RECORD + mouse_down MouseButton +} + +fn restore_terminal_state() { + if ui.ctx_ptr != 0 { + if ui.ctx_ptr.cfg.use_alternate_buffer { + // clear the terminal and set the cursor to the origin + print('\x1b[2J\x1b[3J') + print('\x1b[?1049l') + } + C.SetConsoleMode(ui.ctx_ptr.stdin_handle, ui.stdin_at_startup) + } + load_title() + os.flush() +} + +pub fn init(cfg Config) &Context { + mut ctx := &Context{ + cfg: cfg + } + // get the standard input handle + stdin_handle := C.GetStdHandle(C.STD_INPUT_HANDLE) + stdout_handle := C.GetStdHandle(C.STD_OUTPUT_HANDLE) + if stdin_handle == C.INVALID_HANDLE_VALUE { + panic('could not get stdin handle') + } + // save the current input mode, to be restored on exit + if C.GetConsoleMode(stdin_handle, &ui.stdin_at_startup) == 0 { + panic('could not get stdin console mode') + } + + // enable extended input flags (see https://stackoverflow.com/a/46802726) + // 0x80 == C.ENABLE_EXTENDED_FLAGS + if C.SetConsoleMode(stdin_handle, 0x80) == 0 { + panic('could not set raw input mode') + } + // enable window and mouse input events. + if C.SetConsoleMode(stdin_handle, C.ENABLE_WINDOW_INPUT | C.ENABLE_MOUSE_INPUT) == 0 { + panic('could not set raw input mode') + } + // store the current title, so restore_terminal_state can get it back + save_title() + + if ctx.cfg.use_alternate_buffer { + // switch to the alternate buffer + print('\x1b[?1049h') + // clear the terminal and set the cursor to the origin + print('\x1b[2J\x1b[3J\x1b[1;1H') + } + + if ctx.cfg.hide_cursor { + ctx.hide_cursor() + ctx.flush() + } + + if ctx.cfg.window_title != '' { + print('\x1b]0;$ctx.cfg.window_title\x07') + } + + unsafe { + x := &ui.ctx_ptr + *x = ctx + } + C.atexit(restore_terminal_state) + for code in ctx.cfg.reset { + os.signal_opt(code, fn (_ os.Signal) { + mut c := ui.ctx_ptr + if c != 0 { + c.cleanup() + } + exit(0) + }) or {} + } + + ctx.stdin_handle = stdin_handle + ctx.stdout_handle = stdout_handle + return ctx +} + +pub fn (mut ctx Context) run() ? { + frame_time := 1_000_000 / ctx.cfg.frame_rate + mut init_called := false + mut sw := time.new_stopwatch(auto_start: false) + mut sleep_len := 0 + for { + if !init_called { + ctx.init() + init_called = true + } + if sleep_len > 0 { + time.sleep(sleep_len * time.microsecond) + } + if !ctx.paused { + sw.restart() + if ctx.cfg.event_fn != voidptr(0) { + ctx.parse_events() + } + ctx.frame() + sw.pause() + e := sw.elapsed().microseconds() + sleep_len = frame_time - int(e) + ctx.frame_count++ + } + } +} + +fn (mut ctx Context) parse_events() { + nr_events := u32(0) + if !C.GetNumberOfConsoleInputEvents(ctx.stdin_handle, &nr_events) { + panic('could not get number of events in stdin') + } + if nr_events < 1 { + return + } + + // print('$nr_events | ') + if !C.ReadConsoleInput(ctx.stdin_handle, &ctx.read_buf[0], ui.buf_size, &nr_events) { + panic('could not read from stdin') + } + for i in 0 .. nr_events { + // print('E ') + match int(ctx.read_buf[i].EventType) { + C.KEY_EVENT { + e := unsafe { ctx.read_buf[i].Event.KeyEvent } + ch := e.wVirtualKeyCode + ascii := unsafe { e.uChar.AsciiChar } + if e.bKeyDown == 0 { + continue + } + // we don't handle key_up events because they don't exist on linux... + // see: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + code := match int(ch) { + C.VK_BACK { KeyCode.backspace } + C.VK_RETURN { KeyCode.enter } + C.VK_PRIOR { KeyCode.page_up } + 14...20 { KeyCode.null } + C.VK_NEXT { KeyCode.page_down } + C.VK_END { KeyCode.end } + C.VK_HOME { KeyCode.home } + C.VK_LEFT { KeyCode.left } + C.VK_UP { KeyCode.up } + C.VK_RIGHT { KeyCode.right } + C.VK_DOWN { KeyCode.down } + C.VK_INSERT { KeyCode.insert } + C.VK_DELETE { KeyCode.delete } + 65...90 { KeyCode(ch + 32) } // letters + 91...93 { KeyCode.null } // special keys + 96...105 { KeyCode(ch - 48) } // numpad numbers + 112...135 { KeyCode(ch + 178) } // f1 - f24 + else { KeyCode(ascii) } + } + + mut modifiers := Modifiers{} + if e.dwControlKeyState & (0x1 | 0x2) != 0 { + modifiers.set(.alt) + } + if e.dwControlKeyState & (0x4 | 0x8) != 0 { + modifiers.set(.ctrl) + } + if e.dwControlKeyState & 0x10 != 0 { + modifiers.set(.shift) + } + + mut event := &Event{ + typ: .key_down + modifiers: modifiers + code: code + ascii: ascii + width: int(e.dwControlKeyState) + height: int(e.wVirtualKeyCode) + utf8: unsafe { e.uChar.UnicodeChar.str() } + } + ctx.event(event) + } + C.MOUSE_EVENT { + e := unsafe { ctx.read_buf[i].Event.MouseEvent } + sb_info := C.CONSOLE_SCREEN_BUFFER_INFO{} + if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb_info) { + panic('could not get screenbuffer info') + } + x := e.dwMousePosition.X + 1 + y := int(e.dwMousePosition.Y) - sb_info.srWindow.Top + 1 + mut modifiers := Modifiers{} + if e.dwControlKeyState & (0x1 | 0x2) != 0 { + modifiers.set(.alt) + } + if e.dwControlKeyState & (0x4 | 0x8) != 0 { + modifiers.set(.ctrl) + } + if e.dwControlKeyState & 0x10 != 0 { + modifiers.set(.shift) + } + // TODO: handle capslock/numlock/etc?? events exist for those keys + match int(e.dwEventFlags) { + C.MOUSE_MOVED { + mut button := match int(e.dwButtonState) { + 0 { MouseButton.unknown } + 1 { MouseButton.left } + 2 { MouseButton.right } + else { MouseButton.middle } + } + typ := if e.dwButtonState == 0 { + if ctx.mouse_down != .unknown { + button = ctx.mouse_down + ctx.mouse_down = .unknown + EventType.mouse_up + } else { + EventType.mouse_move + } + } else { + EventType.mouse_drag + } + ctx.event(&Event{ + typ: typ + x: x + y: y + button: button + modifiers: modifiers + }) + } + C.MOUSE_WHEELED { + ctx.event(&Event{ + typ: .mouse_scroll + direction: if i16(e.dwButtonState >> 16) < 0 { + Direction.up + } else { + Direction.down + } + x: x + y: y + modifiers: modifiers + }) + } + 0x0008 /* C.MOUSE_HWHEELED */ { + ctx.event(&Event{ + typ: .mouse_scroll + direction: if i16(e.dwButtonState >> 16) < 0 { + Direction.right + } else { + Direction.left + } + x: x + y: y + modifiers: modifiers + }) + } + 0 /* CLICK */, C.DOUBLE_CLICK { + button := match int(e.dwButtonState) { + 0 { ctx.mouse_down } + 1 { MouseButton.left } + 2 { MouseButton.right } + else { MouseButton.middle } + } + ctx.mouse_down = button + ctx.event(&Event{ + typ: .mouse_down + x: x + y: y + button: button + modifiers: modifiers + }) + } + else {} + } + } + C.WINDOW_BUFFER_SIZE_EVENT { + // e := unsafe { ctx.read_buf[i].Event.WindowBufferSizeEvent } + sb := C.CONSOLE_SCREEN_BUFFER_INFO{} + if !C.GetConsoleScreenBufferInfo(ctx.stdout_handle, &sb) { + panic('could not get screenbuffer info') + } + w := sb.srWindow.Right - sb.srWindow.Left + 1 + h := sb.srWindow.Bottom - sb.srWindow.Top + 1 + utf8 := '($ctx.window_width, $ctx.window_height) -> ($w, $h)' + if w != ctx.window_width || h != ctx.window_height { + ctx.window_width, ctx.window_height = w, h + mut event := &Event{ + typ: .resized + width: ctx.window_width + height: ctx.window_height + utf8: utf8 + } + ctx.event(event) + } + } + // C.MENU_EVENT { + // e := unsafe { ctx.read_buf[i].Event.MenuEvent } + // } + // C.FOCUS_EVENT { + // e := unsafe { ctx.read_buf[i].Event.FocusEvent } + // } + else {} + } + } +} + +[inline] +fn save_title() { + // restore the previously saved terminal title + print('\x1b[22;0t') +} + +[inline] +fn load_title() { + // restore the previously saved terminal title + print('\x1b[23;0t') +} diff --git a/v_windows/v/vlib/term/ui/termios_nix.c.v b/v_windows/v/vlib/term/ui/termios_nix.c.v new file mode 100644 index 0000000..fb5ff76 --- /dev/null +++ b/v_windows/v/vlib/term/ui/termios_nix.c.v @@ -0,0 +1,530 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import os +import time + +#include +#include +#include + +fn C.tcgetattr(fd int, termios_p &C.termios) int + +fn C.tcsetattr(fd int, optional_actions int, termios_p &C.termios) int + +fn C.ioctl(fd int, request u64, arg voidptr) int + +struct C.termios { +mut: + c_iflag u32 + c_lflag u32 + c_cc [32]byte +} + +struct C.winsize { + ws_row u16 + ws_col u16 +} + +const ( + termios_at_startup = get_termios() +) + +[inline] +fn get_termios() C.termios { + mut t := C.termios{} + C.tcgetattr(C.STDIN_FILENO, &t) + return t +} + +[inline] +fn get_terminal_size() (u16, u16) { + winsz := C.winsize{} + C.ioctl(0, C.TIOCGWINSZ, &winsz) + return winsz.ws_row, winsz.ws_col +} + +fn restore_terminal_state_signal(_ os.Signal) { + restore_terminal_state() +} + +fn restore_terminal_state() { + termios_reset() + mut c := ctx_ptr + if c != 0 { + c.paused = true + load_title() + } + os.flush() +} + +fn (mut ctx Context) termios_setup() ? { + // store the current title, so restore_terminal_state can get it back + save_title() + + if !ctx.cfg.skip_init_checks && !(os.is_atty(C.STDIN_FILENO) != 0 + && os.is_atty(C.STDOUT_FILENO) != 0) { + return error('not running under a TTY') + } + + mut termios := get_termios() + + if ctx.cfg.capture_events { + // Set raw input mode by unsetting ICANON and ECHO, + // as well as disable e.g. ctrl+c and ctrl.z + termios.c_iflag &= ~u32(C.IGNBRK | C.BRKINT | C.PARMRK | C.IXON) + termios.c_lflag &= ~u32(C.ICANON | C.ISIG | C.ECHO | C.IEXTEN | C.TOSTOP) + } else { + // Set raw input mode by unsetting ICANON and ECHO + termios.c_lflag &= ~u32(C.ICANON | C.ECHO) + } + + if ctx.cfg.hide_cursor { + ctx.hide_cursor() + ctx.flush() + } + + if ctx.cfg.window_title != '' { + print('\x1b]0;$ctx.cfg.window_title\x07') + } + + if !ctx.cfg.skip_init_checks { + // prevent blocking during the feature detections, but allow enough time for the terminal + // to send back the relevant input data + termios.c_cc[C.VTIME] = 1 + termios.c_cc[C.VMIN] = 0 + C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) + // feature-test the SU spec + sx, sy := get_cursor_position() + print('$bsu$esu') + ex, ey := get_cursor_position() + if sx == ex && sy == ey { + // the terminal either ignored or handled the sequence properly, enable SU + ctx.enable_su = true + } else { + ctx.draw_line(sx, sy, ex, ey) + ctx.set_cursor_position(sx, sy) + ctx.flush() + } + // feature-test rgb (truecolor) support + ctx.enable_rgb = supports_truecolor() + } + // Prevent stdin from blocking by making its read time 0 + termios.c_cc[C.VTIME] = 0 + termios.c_cc[C.VMIN] = 0 + C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) + // enable mouse input + print('\x1b[?1003h\x1b[?1006h') + if ctx.cfg.use_alternate_buffer { + // switch to the alternate buffer + print('\x1b[?1049h') + // clear the terminal and set the cursor to the origin + print('\x1b[2J\x1b[3J\x1b[1;1H') + } + ctx.window_height, ctx.window_width = get_terminal_size() + + // Reset console on exit + C.atexit(restore_terminal_state) + os.signal_opt(.tstp, restore_terminal_state_signal) or {} + os.signal_opt(.cont, fn (_ os.Signal) { + mut c := ctx_ptr + if c != 0 { + c.termios_setup() or { panic(err) } + c.window_height, c.window_width = get_terminal_size() + mut event := &Event{ + typ: .resized + width: c.window_width + height: c.window_height + } + c.paused = false + c.event(event) + } + }) or {} + for code in ctx.cfg.reset { + os.signal_opt(code, fn (_ os.Signal) { + mut c := ctx_ptr + if c != 0 { + c.cleanup() + } + exit(0) + }) or {} + } + + os.signal_opt(.winch, fn (_ os.Signal) { + mut c := ctx_ptr + if c != 0 { + c.window_height, c.window_width = get_terminal_size() + + mut event := &Event{ + typ: .resized + width: c.window_width + height: c.window_height + } + c.event(event) + } + }) or {} + + os.flush() +} + +fn get_cursor_position() (int, int) { + print('\033[6n') + mut s := '' + unsafe { + buf := malloc_noscan(25) + len := C.read(C.STDIN_FILENO, buf, 24) + buf[len] = 0 + s = tos(buf, len) + } + a := s[2..].split(';') + if a.len != 2 { + return -1, -1 + } + return a[0].int(), a[1].int() +} + +fn supports_truecolor() bool { + // faster/simpler, but less reliable, check + if os.getenv('COLORTERM') in ['truecolor', '24bit'] { + return true + } + // set the bg color to some arbirtrary value (#010203), assumed not to be the default + print('\x1b[48:2:1:2:3m') + // andquery the current color + print('\x1bP\$qm\x1b\\') + mut s := '' + unsafe { + buf := malloc_noscan(25) + len := C.read(C.STDIN_FILENO, buf, 24) + buf[len] = 0 + s = tos(buf, len) + } + return s.contains('1:2:3') +} + +fn termios_reset() { + // C.TCSANOW ?? + C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &ui.termios_at_startup) + print('\x1b[?1003l\x1b[?1006l\x1b[?25h') + c := ctx_ptr + if c != 0 && c.cfg.use_alternate_buffer { + print('\x1b[?1049l') + } + os.flush() +} + +/////////////////////////////////////////// +// TODO: do multiple sleep/read cycles, rather than one big one +fn (mut ctx Context) termios_loop() { + frame_time := 1_000_000 / ctx.cfg.frame_rate + mut init_called := false + mut sw := time.new_stopwatch(auto_start: false) + mut sleep_len := 0 + for { + if !init_called { + ctx.init() + init_called = true + } + // println('SLEEPING: $sleep_len') + if sleep_len > 0 { + time.sleep(sleep_len * time.microsecond) + } + if !ctx.paused { + sw.restart() + if ctx.cfg.event_fn != voidptr(0) { + unsafe { + len := C.read(C.STDIN_FILENO, &byte(ctx.read_buf.data) + ctx.read_buf.len, + ctx.read_buf.cap - ctx.read_buf.len) + ctx.resize_arr(ctx.read_buf.len + len) + } + if ctx.read_buf.len > 0 { + ctx.parse_events() + } + } + ctx.frame() + sw.pause() + e := sw.elapsed().microseconds() + sleep_len = frame_time - int(e) + + ctx.frame_count++ + } + } +} + +fn (mut ctx Context) parse_events() { + // Stop this from getting stuck in rare cases where something isn't parsed correctly + mut nr_iters := 0 + for ctx.read_buf.len > 0 { + nr_iters++ + if nr_iters > 100 { + ctx.shift(1) + } + mut event := &Event(0) + if ctx.read_buf[0] == 0x1b { + e, len := escape_sequence(ctx.read_buf.bytestr()) + event = e + ctx.shift(len) + } else { + event = single_char(ctx.read_buf.bytestr()) + ctx.shift(1) + } + if event != 0 { + ctx.event(event) + nr_iters = 0 + } + } +} + +fn single_char(buf string) &Event { + ch := buf[0] + + mut event := &Event{ + typ: .key_down + ascii: ch + code: KeyCode(ch) + utf8: buf + } + + match ch { + // special handling for `ctrl + letter` + // TODO: Fix assoc in V and remove this workaround :/ + // 1 ... 26 { event = Event{ ...event, code: KeyCode(96 | ch), modifiers: .ctrl } } + // 65 ... 90 { event = Event{ ...event, code: KeyCode(32 | ch), modifiers: .shift } } + // The bit `or`s here are really just `+`'s, just written in this way for a tiny performance improvement + // don't treat tab, enter as ctrl+i, ctrl+j + 1...8, 11...26 { + event = &Event{ + typ: event.typ + ascii: event.ascii + utf8: event.utf8 + code: KeyCode(96 | ch) + modifiers: .ctrl + } + } + 65...90 { + event = &Event{ + typ: event.typ + ascii: event.ascii + utf8: event.utf8 + code: KeyCode(32 | ch) + modifiers: .shift + } + } + else {} + } + + return event +} + +// Gets an entire, independent escape sequence from the buffer +// Normally, this just means reading until the first letter, but there are some exceptions... +fn escape_end(buf string) int { + mut i := 0 + for { + if i + 1 == buf.len { + return buf.len + } + + if buf[i].is_letter() || buf[i] == `~` { + if buf[i] == `O` && i + 2 <= buf.len { + n := buf[i + 1] + if (n >= `A` && n <= `D`) || (n >= `P` && n <= `S`) || n == `F` || n == `H` { + return i + 2 + } + } + return i + 1 + // escape hatch to avoid potential issues/crashes, although ideally this should never eval to true + } else if buf[i + 1] == 0x1b { + return i + 1 + } + i++ + } + // this point should be unreachable + assert false + return 0 +} + +fn escape_sequence(buf_ string) (&Event, int) { + end := escape_end(buf_) + single := buf_[..end] // read until the end of the sequence + buf := single[1..] // skip the escape character + + if buf.len == 0 { + return &Event{ + typ: .key_down + ascii: 27 + code: .escape + utf8: single + }, 1 + } + + if buf.len == 1 { + c := single_char(buf) + mut modifiers := c.modifiers + modifiers.set(.alt) + return &Event{ + typ: c.typ + ascii: c.ascii + code: c.code + utf8: single + modifiers: modifiers + }, 2 + } + // ---------------- + // Mouse events + // ---------------- + // Documentation: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking + if buf.len > 2 && buf[1] == `<` { + split := buf[2..].split(';') + if split.len < 3 { + return &Event(0), 0 + } + + typ, x, y := split[0].int(), split[1].int(), split[2].int() + lo := typ & 0b00011 + hi := typ & 0b11100 + + mut modifiers := Modifiers{} + if hi & 4 != 0 { + modifiers.set(.shift) + } + if hi & 8 != 0 { + modifiers.set(.alt) + } + if hi & 16 != 0 { + modifiers.set(.ctrl) + } + + match typ { + 0...31 { + last := buf[buf.len - 1] + button := if lo < 3 { MouseButton(lo + 1) } else { MouseButton.unknown } + event := if last == `m` || lo == 3 { + EventType.mouse_up + } else { + EventType.mouse_down + } + + return &Event{ + typ: event + x: x + y: y + button: button + modifiers: modifiers + utf8: single + }, end + } + 32...63 { + button, event := if lo < 3 { + MouseButton(lo + 1), EventType.mouse_drag + } else { + MouseButton.unknown, EventType.mouse_move + } + + return &Event{ + typ: event + x: x + y: y + button: button + modifiers: modifiers + utf8: single + }, end + } + 64...95 { + direction := if typ & 1 == 0 { Direction.down } else { Direction.up } + return &Event{ + typ: .mouse_scroll + x: x + y: y + direction: direction + modifiers: modifiers + utf8: single + }, end + } + else { + return &Event{ + typ: .unknown + utf8: single + }, end + } + } + } + // ---------------------------- + // Special key combinations + // ---------------------------- + + mut code := KeyCode.null + mut modifiers := Modifiers{} + match buf { + '[A', 'OA' { code = .up } + '[B', 'OB' { code = .down } + '[C', 'OC' { code = .right } + '[D', 'OD' { code = .left } + '[5~', '[[5~' { code = .page_up } + '[6~', '[[6~' { code = .page_down } + '[F', 'OF', '[4~', '[[8~' { code = .end } + '[H', 'OH', '[1~', '[[7~' { code = .home } + '[2~' { code = .insert } + '[3~' { code = .delete } + 'OP', '[11~' { code = .f1 } + 'OQ', '[12~' { code = .f2 } + 'OR', '[13~' { code = .f3 } + 'OS', '[14~' { code = .f4 } + '[15~' { code = .f5 } + '[17~' { code = .f6 } + '[18~' { code = .f7 } + '[19~' { code = .f8 } + '[20~' { code = .f9 } + '[21~' { code = .f10 } + '[23~' { code = .f11 } + '[24~' { code = .f12 } + else {} + } + + if buf == '[Z' { + code = .tab + modifiers.set(.shift) + } + + if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` { + match buf[3] { + `2` { modifiers = .shift } + `3` { modifiers = .alt } + `4` { modifiers = .shift | .alt } + `5` { modifiers = .ctrl } + `6` { modifiers = .ctrl | .shift } + `7` { modifiers = .ctrl | .alt } + `8` { modifiers = .ctrl | .alt | .shift } + else {} + } + + if buf[1] == `1` { + match buf[4] { + `A` { code = KeyCode.up } + `B` { code = KeyCode.down } + `C` { code = KeyCode.right } + `D` { code = KeyCode.left } + `F` { code = KeyCode.end } + `H` { code = KeyCode.home } + `P` { code = KeyCode.f1 } + `Q` { code = KeyCode.f2 } + `R` { code = KeyCode.f3 } + `S` { code = KeyCode.f4 } + else {} + } + } else if buf[1] == `5` { + code = KeyCode.page_up + } else if buf[1] == `6` { + code = KeyCode.page_down + } + } + + return &Event{ + typ: .key_down + code: code + utf8: single + modifiers: modifiers + }, end +} diff --git a/v_windows/v/vlib/term/ui/ui.v b/v_windows/v/vlib/term/ui/ui.v new file mode 100644 index 0000000..6ba3d7c --- /dev/null +++ b/v_windows/v/vlib/term/ui/ui.v @@ -0,0 +1,256 @@ +// Copyright (c) 2020-2021 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ui + +import strings + +pub struct Color { +pub: + r byte + g byte + b byte +} + +pub fn (c Color) hex() string { + return '#$c.r.hex()$c.g.hex()$c.b.hex()' +} + +// Synchronized Updates spec, designed to avoid tearing during renders +// https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec +const ( + bsu = '\x1bP=1s\x1b\\' + esu = '\x1bP=2s\x1b\\' +) + +// write puts the string `s` into the print buffer. +[inline] +pub fn (mut ctx Context) write(s string) { + if s == '' { + return + } + unsafe { ctx.print_buf.push_many(s.str, s.len) } +} + +// flush displays the accumulated print buffer to the screen. +[inline] +pub fn (mut ctx Context) flush() { + // TODO: Diff the previous frame against this one, and only render things that changed? + if !ctx.enable_su { + C.write(1, ctx.print_buf.data, ctx.print_buf.len) + } else { + C.write(1, ui.bsu.str, ui.bsu.len) + C.write(1, ctx.print_buf.data, ctx.print_buf.len) + C.write(1, ui.esu.str, ui.esu.len) + } + ctx.print_buf.clear() +} + +// bold sets the character state to bold. +[inline] +pub fn (mut ctx Context) bold() { + ctx.write('\x1b[1m') +} + +// set_cursor_position positions the cusor at the given coordinates `x`,`y`. +[inline] +pub fn (mut ctx Context) set_cursor_position(x int, y int) { + ctx.write('\x1b[$y;${x}H') +} + +// show_cursor will make the cursor appear if it is not already visible +[inline] +pub fn (mut ctx Context) show_cursor() { + ctx.write('\x1b[?25h') +} + +// hide_cursor will make the cursor invisible +[inline] +pub fn (mut ctx Context) hide_cursor() { + ctx.write('\x1b[?25l') +} + +// set_color sets the current foreground color used by any succeeding `draw_*` calls. +[inline] +pub fn (mut ctx Context) set_color(c Color) { + if ctx.enable_rgb { + ctx.write('\x1b[38;2;${int(c.r)};${int(c.g)};${int(c.b)}m') + } else { + ctx.write('\x1b[38;5;${rgb2ansi(c.r, c.g, c.b)}m') + } +} + +// set_color sets the current background color used by any succeeding `draw_*` calls. +[inline] +pub fn (mut ctx Context) set_bg_color(c Color) { + if ctx.enable_rgb { + ctx.write('\x1b[48;2;${int(c.r)};${int(c.g)};${int(c.b)}m') + } else { + ctx.write('\x1b[48;5;${rgb2ansi(c.r, c.g, c.b)}m') + } +} + +// reset_color sets the current foreground color back to it's default value. +[inline] +pub fn (mut ctx Context) reset_color() { + ctx.write('\x1b[39m') +} + +// reset_bg_color sets the current background color back to it's default value. +[inline] +pub fn (mut ctx Context) reset_bg_color() { + ctx.write('\x1b[49m') +} + +// reset restores the state of all colors and text formats back to their default values. +[inline] +pub fn (mut ctx Context) reset() { + ctx.write('\x1b[0m') +} + +[inline] +pub fn (mut ctx Context) clear() { + ctx.write('\x1b[2J\x1b[3J') +} + +// set_window_title sets the string `s` as the window title. +[inline] +pub fn (mut ctx Context) set_window_title(s string) { + print('\x1b]0;$s\x07') +} + +// draw_point draws a point at position `x`,`y`. +[inline] +pub fn (mut ctx Context) draw_point(x int, y int) { + ctx.set_cursor_position(x, y) + ctx.write(' ') +} + +// draw_text draws the string `s`, starting from position `x`,`y`. +[inline] +pub fn (mut ctx Context) draw_text(x int, y int, s string) { + ctx.set_cursor_position(x, y) + ctx.write(s) +} + +// draw_line draws a line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`. +pub fn (mut ctx Context) draw_line(x int, y int, x2 int, y2 int) { + min_x, min_y := if x < x2 { x } else { x2 }, if y < y2 { y } else { y2 } + max_x, _ := if x > x2 { x } else { x2 }, if y > y2 { y } else { y2 } + if y == y2 { + // Horizontal line, performance improvement + ctx.set_cursor_position(min_x, min_y) + ctx.write(strings.repeat(` `, max_x + 1 - min_x)) + return + } + // Draw the various points with Bresenham's line algorithm: + mut x0, x1 := x, x2 + mut y0, y1 := y, y2 + sx := if x0 < x1 { 1 } else { -1 } + sy := if y0 < y1 { 1 } else { -1 } + dx := if x0 < x1 { x1 - x0 } else { x0 - x1 } + dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed + mut err := dx + dy + for { + // res << Segment{ x0, y0 } + ctx.draw_point(x0, y0) + if x0 == x1 && y0 == y1 { + break + } + e2 := 2 * err + if e2 >= dy { + err += dy + x0 += sx + } + if e2 <= dx { + err += dx + y0 += sy + } + } +} + +// draw_dashed_line draws a dashed line segment, starting at point `x`,`y`, and ending at point `x2`,`y2`. +pub fn (mut ctx Context) draw_dashed_line(x int, y int, x2 int, y2 int) { + // Draw the various points with Bresenham's line algorithm: + mut x0, x1 := x, x2 + mut y0, y1 := y, y2 + sx := if x0 < x1 { 1 } else { -1 } + sy := if y0 < y1 { 1 } else { -1 } + dx := if x0 < x1 { x1 - x0 } else { x0 - x1 } + dy := if y0 < y1 { y0 - y1 } else { y1 - y0 } // reversed + mut err := dx + dy + mut i := 0 + for { + if i % 2 == 0 { + ctx.draw_point(x0, y0) + } + if x0 == x1 && y0 == y1 { + break + } + e2 := 2 * err + if e2 >= dy { + err += dy + x0 += sx + } + if e2 <= dx { + err += dx + y0 += sy + } + i++ + } +} + +// draw_rect draws a rectangle, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_rect(x int, y int, x2 int, y2 int) { + if y == y2 || x == x2 { + ctx.draw_line(x, y, x2, y2) + return + } + min_y, max_y := if y < y2 { y, y2 } else { y2, y } + for y_pos in min_y .. max_y + 1 { + ctx.draw_line(x, y_pos, x2, y_pos) + } +} + +// draw_empty_dashed_rect draws a rectangle with dashed lines, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_empty_dashed_rect(x int, y int, x2 int, y2 int) { + if y == y2 || x == x2 { + ctx.draw_dashed_line(x, y, x2, y2) + return + } + + min_x, max_x := if x < x2 { x, x2 } else { x2, x } + min_y, max_y := if y < y2 { y, y2 } else { y2, y } + + ctx.draw_dashed_line(min_x, min_y, max_x, min_y) + ctx.draw_dashed_line(min_x, min_y, min_x, max_y) + if (max_y - min_y) & 1 == 0 { + ctx.draw_dashed_line(min_x, max_y, max_x, max_y) + } else { + ctx.draw_dashed_line(min_x + 1, max_y, max_x, max_y) + } + if (max_x - min_x) & 1 == 0 { + ctx.draw_dashed_line(max_x, min_y, max_x, max_y) + } else { + ctx.draw_dashed_line(max_x, min_y + 1, max_x, max_y) + } +} + +// draw_empty_rect draws a rectangle with no fill, starting at top left `x`,`y`, and ending at bottom right `x2`,`y2`. +pub fn (mut ctx Context) draw_empty_rect(x int, y int, x2 int, y2 int) { + if y == y2 || x == x2 { + ctx.draw_line(x, y, x2, y2) + return + } + ctx.draw_line(x, y, x2, y) + ctx.draw_line(x, y2, x2, y2) + ctx.draw_line(x, y, x, y2) + ctx.draw_line(x2, y, x2, y2) +} + +// horizontal_separator draws a horizontal separator, spanning the width of the screen. +[inline] +pub fn (mut ctx Context) horizontal_separator(y int) { + ctx.set_cursor_position(0, y) + ctx.write(strings.repeat(`-`, ctx.window_width)) // /* `⎽` */ +} diff --git a/v_windows/v/vlib/time/Y2K38_test.v b/v_windows/v/vlib/time/Y2K38_test.v new file mode 100644 index 0000000..0ebc0ef --- /dev/null +++ b/v_windows/v/vlib/time/Y2K38_test.v @@ -0,0 +1,13 @@ +import time + +fn test_time_after_2038_works() { + after_time := time.parse_iso8601('2037-07-23') or { time.now() } + dump(after_time) + error_time := after_time.add_days(180) + dump(error_time) + assert error_time.str() == '2038-01-19 00:00:00' + // NB: the next date is after Y2K38, it should NOT wrap: + error_time2 := after_time.add_days(181) + dump(error_time2) + assert error_time2.str() == '2038-01-20 00:00:00' +} diff --git a/v_windows/v/vlib/time/chrono.c.v b/v_windows/v/vlib/time/chrono.c.v new file mode 100644 index 0000000..4d6de7d --- /dev/null +++ b/v_windows/v/vlib/time/chrono.c.v @@ -0,0 +1,18 @@ +module time + +// portable_timegm does the same as C._mkgmtime, but unlike it, +// can work with dates before the Unix epoch of 1970-01-01 . +pub fn portable_timegm(t &C.tm) i64 { + mut year := t.tm_year + 1900 + mut month := t.tm_mon // 0-11 + if month > 11 { + year += month / 12 + month %= 12 + } else if month < 0 { + years_diff := (11 - month) / 12 + year -= years_diff + month += 12 * years_diff + } + days_since_1970 := i64(days_from_civil(year, month + 1, t.tm_mday)) + return 60 * (60 * (24 * days_since_1970 + t.tm_hour) + t.tm_min) + t.tm_sec +} diff --git a/v_windows/v/vlib/time/chrono.v b/v_windows/v/vlib/time/chrono.v new file mode 100644 index 0000000..58f1ef8 --- /dev/null +++ b/v_windows/v/vlib/time/chrono.v @@ -0,0 +1,14 @@ +module time + +// days_from_civil - return the number of days since the +// Unix epoch 1970-01-01. A detailed description of the algorithm here +// is in: http://howardhinnant.github.io/date_algorithms.html +// Note that it will return negative values for days before 1970-01-01. +pub fn days_from_civil(oy int, m int, d int) int { + y := if m <= 2 { oy - 1 } else { oy } + era := y / 400 + yoe := y - era * 400 // [0, 399] + doy := (153 * (m + (if m > 2 { -3 } else { 9 })) + 2) / 5 + d - 1 // [0, 365] + doe := yoe * 365 + yoe / 4 - yoe / 100 + doy // [0, 146096] + return era * 146097 + doe - 719468 +} diff --git a/v_windows/v/vlib/time/format.v b/v_windows/v/vlib/time/format.v new file mode 100644 index 0000000..6a77bb3 --- /dev/null +++ b/v_windows/v/vlib/time/format.v @@ -0,0 +1,174 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// format returns a date string in "YYYY-MM-DD HH:MM" format (24h). +pub fn (t Time) format() string { + return t.get_fmt_str(.hyphen, .hhmm24, .yyyymmdd) +} + +// format_ss returns a date string in "YYYY-MM-DD HH:MM:SS" format (24h). +pub fn (t Time) format_ss() string { + return t.get_fmt_str(.hyphen, .hhmmss24, .yyyymmdd) +} + +// format_ss_milli returns a date string in "YYYY-MM-DD HH:MM:SS.123" format (24h). +pub fn (t Time) format_ss_milli() string { + return t.get_fmt_str(.hyphen, .hhmmss24_milli, .yyyymmdd) +} + +// format_ss_micro returns a date string in "YYYY-MM-DD HH:MM:SS.123456" format (24h). +pub fn (t Time) format_ss_micro() string { + return t.get_fmt_str(.hyphen, .hhmmss24_micro, .yyyymmdd) +} + +// hhmm returns a date string in "HH:MM" format (24h). +pub fn (t Time) hhmm() string { + return t.get_fmt_time_str(.hhmm24) +} + +// hhmmss returns a date string in "HH:MM:SS" format (24h). +pub fn (t Time) hhmmss() string { + return t.get_fmt_time_str(.hhmmss24) +} + +// hhmm12 returns a date string in "HH:MM" format (12h). +pub fn (t Time) hhmm12() string { + return t.get_fmt_time_str(.hhmm12) +} + +// ymmdd returns a date string in "YYYY-MM-DD" format. +pub fn (t Time) ymmdd() string { + return t.get_fmt_date_str(.hyphen, .yyyymmdd) +} + +// ddmmy returns a date string in "DD.MM.YYYY" format. +pub fn (t Time) ddmmy() string { + return t.get_fmt_date_str(.dot, .ddmmyyyy) +} + +// md returns a date string in "MMM D" format. +pub fn (t Time) md() string { + return t.get_fmt_date_str(.space, .mmmd) +} + +// clean returns a date string in a following format: +// - a date string in "HH:MM" format (24h) for current day +// - a date string in "MMM D HH:MM" format (24h) for date of current year +// - a date string formatted with format function for other dates +pub fn (t Time) clean() string { + znow := now() + // Today + if t.month == znow.month && t.year == znow.year && t.day == znow.day { + return t.get_fmt_time_str(.hhmm24) + } + // This year + if t.year == znow.year { + return t.get_fmt_str(.space, .hhmm24, .mmmd) + } + return t.format() +} + +// clean12 returns a date string in a following format: +// - a date string in "HH:MM" format (12h) for current day +// - a date string in "MMM D HH:MM" format (12h) for date of current year +// - a date string formatted with format function for other dates +pub fn (t Time) clean12() string { + znow := now() + // Today + if t.month == znow.month && t.year == znow.year && t.day == znow.day { + return t.get_fmt_time_str(.hhmm12) + } + // This year + if t.year == znow.year { + return t.get_fmt_str(.space, .hhmm12, .mmmd) + } + return t.format() +} + +// get_fmt_time_str returns a date string with specified FormatTime type. +pub fn (t Time) get_fmt_time_str(fmt_time FormatTime) string { + if fmt_time == .no_time { + return '' + } + tp := if t.hour > 11 { 'p.m.' } else { 'a.m.' } + hour_ := if t.hour > 12 { + t.hour - 12 + } else if t.hour == 0 { + 12 + } else { + t.hour + } + return match fmt_time { + .hhmm12 { '$hour_:${t.minute:02d} $tp' } + .hhmm24 { '${t.hour:02d}:${t.minute:02d}' } + .hhmmss12 { '$hour_:${t.minute:02d}:${t.second:02d} $tp' } + .hhmmss24 { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' } + .hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' } + .hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' } + else { 'unknown enumeration $fmt_time' } + } +} + +// get_fmt_time_str returns a date string with specified +// FormatDelimiter and FormatDate type. +pub fn (t Time) get_fmt_date_str(fmt_dlmtr FormatDelimiter, fmt_date FormatDate) string { + if fmt_date == .no_date { + return '' + } + month := t.smonth() + year := '${(t.year % 100):02d}' + mut res := match fmt_date { + .ddmmyy { '${t.day:02d}|${t.month:02d}|$year' } + .ddmmyyyy { '${t.day:02d}|${t.month:02d}|${t.year:04d}' } + .mmddyy { '${t.month:02d}|${t.day:02d}|$year' } + .mmddyyyy { '${t.month:02d}|${t.day:02d}|${t.year:04d}' } + .mmmd { '$month|$t.day' } + .mmmdd { '$month|${t.day:02d}' } + .mmmddyy { '$month|${t.day:02d}|$year' } + .mmmddyyyy { '$month|${t.day:02d}|${t.year:04d}' } + .yyyymmdd { '${t.year:04d}|${t.month:02d}|${t.day:02d}' } + .yymmdd { '$year|${t.month:02d}|${t.day:02d}' } + else { 'unknown enumeration $fmt_date' } + } + del := match fmt_dlmtr { + .dot { '.' } + .hyphen { '-' } + .slash { '/' } + .space { ' ' } + .no_delimiter { '' } + } + res = res.replace('|', del) + return res +} + +// get_fmt_str returns a date string with specified FormatDelimiter, +// FormatTime type, and FormatDate type. +pub fn (t Time) get_fmt_str(fmt_dlmtr FormatDelimiter, fmt_time FormatTime, fmt_date FormatDate) string { + if fmt_date == .no_date { + if fmt_time == .no_time { + // saving one function call although it's checked in + // t.get_fmt_time_str(fmt_time) in the beginning + return '' + } else { + return t.get_fmt_time_str(fmt_time) + } + } else { + if fmt_time != .no_time { + dstr := t.get_fmt_date_str(fmt_dlmtr, fmt_date) + tstr := t.get_fmt_time_str(fmt_time) + return '$dstr $tstr' + } else { + return t.get_fmt_date_str(fmt_dlmtr, fmt_date) + } + } +} + +// This is just a TEMPORARY function for cookies and their expire dates +pub fn (t Time) utc_string() string { + day_str := t.weekday_str() + month_str := t.smonth() + utc_string := '$day_str, $t.day $month_str $t.year ${t.hour:02d}:${t.minute:02d}:${t.second:02d} UTC' + return utc_string +} diff --git a/v_windows/v/vlib/time/misc/misc.v b/v_windows/v/vlib/time/misc/misc.v new file mode 100644 index 0000000..b0be126 --- /dev/null +++ b/v_windows/v/vlib/time/misc/misc.v @@ -0,0 +1,13 @@ +module misc + +import rand +import time + +const ( + start_time_unix = time.now().unix // start_time_unix is set when the program is started. +) + +// random returns a random time struct in *the past*. +pub fn random() time.Time { + return time.unix(int(rand.i64n(misc.start_time_unix))) +} diff --git a/v_windows/v/vlib/time/misc/misc_test.v b/v_windows/v/vlib/time/misc/misc_test.v new file mode 100644 index 0000000..9bcc8ad --- /dev/null +++ b/v_windows/v/vlib/time/misc/misc_test.v @@ -0,0 +1,17 @@ +import time.misc as tmisc +import rand + +fn test_random() { + // guarantee CI test stability, by seeding the random number generator with a known seed + rand.seed([u32(0), 0]) + t1 := tmisc.random() + t2 := tmisc.random() + t3 := tmisc.random() + t4 := tmisc.random() + assert t1.unix != t2.unix + assert t1.unix != t3.unix + assert t1.unix != t4.unix + assert t2.unix != t3.unix + assert t2.unix != t4.unix + assert t3.unix != t4.unix +} diff --git a/v_windows/v/vlib/time/operator.v b/v_windows/v/vlib/time/operator.v new file mode 100644 index 0000000..f628549 --- /dev/null +++ b/v_windows/v/vlib/time/operator.v @@ -0,0 +1,21 @@ +module time + +// operator `==` returns true if provided time is equal to time +[inline] +pub fn (t1 Time) == (t2 Time) bool { + return t1.unix == t2.unix && t1.microsecond == t2.microsecond +} + +// operator `<` returns true if provided time is less than time +[inline] +pub fn (t1 Time) < (t2 Time) bool { + return t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond) +} + +// Time subtract using operator overloading. +[inline] +pub fn (lhs Time) - (rhs Time) Duration { + lhs_micro := lhs.unix * 1_000_000 + lhs.microsecond + rhs_micro := rhs.unix * 1_000_000 + rhs.microsecond + return (lhs_micro - rhs_micro) * microsecond +} diff --git a/v_windows/v/vlib/time/operator_test.v b/v_windows/v/vlib/time/operator_test.v new file mode 100644 index 0000000..5f3e1b7 --- /dev/null +++ b/v_windows/v/vlib/time/operator_test.v @@ -0,0 +1,391 @@ +module time + +fn assert_greater_time(ms int, t1 Time) { + sleep(ms * millisecond) + t2 := now() + assert t2 > t1 +} + +// Tests the now in all platform and the gt operator function with at least ms resolution +fn test_now_always_results_in_greater_time() { + t1 := now() + $if macos { + assert_greater_time(1, t1) + return + } + $if windows { + // Lower resolution of time for windows + assert_greater_time(15, t1) + return + } + $if linux { + assert_greater_time(1, t1) + return + } + $if solaris { + assert_greater_time(1, t1) + return + } + // other platforms may have more accurate resolution, + // but we do not know that ... so wait at least 1s: + assert_greater_time(1001, t1) +} + +fn test_time1_should_be_same_as_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + assert t1 == t2 +} + +fn test_time1_should_not_be_same_as_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 != t2 + assert t3 != t4 +} + +fn test_time1_should_be_greater_than_time2() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 5 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 > t2 + assert t3 > t4 +} + +fn test_time2_should_be_less_than_time1() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 2 + microsecond: 0 + }) + assert t2 < t1 + assert t4 < t3 +} + +fn test_time1_should_be_greater_or_equal_to_time2_when_gt() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 102 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 5 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 >= t2 + assert t3 >= t4 +} + +fn test_time1_should_be_greater_or_equal_to_time2_when_eq() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + assert t1 >= t2 + assert t3 >= t4 +} + +fn test_time1_should_be_less_or_equal_to_time2_when_lt() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 101 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 4 + microsecond: 0 + }) + assert t1 <= t2 + assert t3 <= t4 +} + +fn test_time1_should_be_less_or_equal_to_time2_when_eq() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + // Difference is one microsecond + t2 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t3 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + // Difference is one second + t4 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 0 + }) + assert t1 <= t2 + assert t3 <= t4 +} + +fn test_time2_copied_from_time1_should_be_equal() { + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := new_time(t1) + assert t2 == t1 +} + +fn test_subtract() { + d_seconds := 3 + d_microseconds := 13 + duration := d_seconds * second + d_microseconds * microsecond + t1 := new_time(Time{ + year: 2000 + month: 5 + day: 10 + hour: 22 + minute: 11 + second: 3 + microsecond: 100 + }) + t2 := unix2(i64(t1.unix) + d_seconds, t1.microsecond + d_microseconds) + d1 := t2 - t1 + d2 := t1 - t2 + assert d1 > 0 + assert d1 == duration + assert d2 < 0 + assert d2 == -duration +} diff --git a/v_windows/v/vlib/time/parse.c.v b/v_windows/v/vlib/time/parse.c.v new file mode 100644 index 0000000..b74cd41 --- /dev/null +++ b/v_windows/v/vlib/time/parse.c.v @@ -0,0 +1,140 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// parse returns time from a date string in "YYYY-MM-DD HH:MM:SS" format. +pub fn parse(s string) ?Time { + pos := s.index(' ') or { return error('Invalid time format: $s') } + symd := s[..pos] + ymd := symd.split('-') + if ymd.len != 3 { + return error('Invalid time format: $s') + } + shms := s[pos..] + hms := shms.split(':') + hour_ := hms[0][1..] + minute_ := hms[1] + second_ := hms[2] + res := new_time(Time{ + year: ymd[0].int() + month: ymd[1].int() + day: ymd[2].int() + hour: hour_.int() + minute: minute_.int() + second: second_.int() + }) + return res +} + +// parse_rfc2822 returns time from a date string in RFC 2822 datetime format. +pub fn parse_rfc2822(s string) ?Time { + fields := s.split(' ') + if fields.len < 5 { + return error('Invalid time format: $s') + } + pos := months_string.index(fields[2]) or { return error('Invalid time format: $s') } + mm := pos / 3 + 1 + unsafe { + tmstr := malloc_noscan(s.len * 2) + count := C.snprintf(&char(tmstr), (s.len * 2), c'%s-%02d-%s %s', fields[3].str, + mm, fields[1].str, fields[4].str) + return parse(tos(tmstr, count)) + } +} + +// ----- iso8601 ----- +const ( + err_invalid_8601 = 'Invalid 8601 Format' +) + +fn parse_iso8601_date(s string) ?(int, int, int) { + year, month, day, dummy := 0, 0, 0, byte(0) + count := unsafe { C.sscanf(&char(s.str), c'%4d-%2d-%2d%c', &year, &month, &day, &dummy) } + if count != 3 { + return error(time.err_invalid_8601) + } + return year, month, day +} + +fn parse_iso8601_time(s string) ?(int, int, int, int, i64, bool) { + hour_ := 0 + minute_ := 0 + second_ := 0 + microsecond_ := 0 + plus_min_z := `a` + offset_hour := 0 + offset_minute := 0 + mut count := unsafe { + C.sscanf(&char(s.str), c'%2d:%2d:%2d.%6d%c%2d:%2d', &hour_, &minute_, &second_, + µsecond_, &char(&plus_min_z), &offset_hour, &offset_minute) + } + // Missread microsecond ([Sec Hour Minute].len == 3 < 4) + if count < 4 { + count = unsafe { + C.sscanf(&char(s.str), c'%2d:%2d:%2d%c%2d:%2d', &hour_, &minute_, &second_, + &char(&plus_min_z), &offset_hour, &offset_minute) + } + count++ // Increment count because skipped microsecond + } + if count < 4 { + return error(time.err_invalid_8601) + } + is_local_time := plus_min_z == `a` && count == 4 + is_utc := plus_min_z == `Z` && count == 5 + if !(count == 7 || is_local_time || is_utc) { + return error(time.err_invalid_8601) + } + if plus_min_z != `+` && plus_min_z != `-` && !is_utc && !is_local_time { + return error('Invalid 8601 format, expected `Z` or `+` or `-` as time separator') + } + mut unix_offset := 0 + if offset_hour > 0 { + unix_offset += 3600 * offset_hour + } + if offset_minute > 0 { + unix_offset += 60 * offset_minute + } + if plus_min_z == `+` { + unix_offset *= -1 + } + return hour_, minute_, second_, microsecond_, unix_offset, is_local_time +} + +// parse_iso8601 parses rfc8601 time format yyyy-MM-ddTHH:mm:ss.dddddd+dd:dd as local time +// the fraction part is difference in milli seconds and the last part is offset +// from UTC time and can be both +/- HH:mm +// remarks: not all iso8601 is supported +// also checks and support for leapseconds should be added in future PR +pub fn parse_iso8601(s string) ?Time { + t_i := s.index('T') or { -1 } + parts := if t_i != -1 { [s[..t_i], s[t_i + 1..]] } else { s.split(' ') } + if !(parts.len == 1 || parts.len == 2) { + return error(time.err_invalid_8601) + } + year, month, day := parse_iso8601_date(parts[0]) ? + mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true + if parts.len == 2 { + hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1]) ? + } + mut t := new_time(Time{ + year: year + month: month + day: day + hour: hour_ + minute: minute_ + second: second_ + microsecond: microsecond_ + }) + if is_local_time { + return t // Time already local time + } + mut unix_time := t.unix + if unix_offset < 0 { + unix_time -= (-unix_offset) + } else if unix_offset > 0 { + unix_time += unix_offset + } + t = unix2(i64(unix_time), t.microsecond) + return t +} diff --git a/v_windows/v/vlib/time/parse.js.v b/v_windows/v/vlib/time/parse.js.v new file mode 100644 index 0000000..1303561 --- /dev/null +++ b/v_windows/v/vlib/time/parse.js.v @@ -0,0 +1,24 @@ +module time + +// parse returns time from a date string. +// +// TODO(playX): JS Date expects iso8061 format of strings and other formats +// are implementation dependant, we probably want to implement parsing in JS. +pub fn parse(s string) Time { + mut res := Time{} + #let date = new Date(s.str) + #res.year.val = date.getFullYear() + #res.month.val = date.getMonth() + #res.day.val = date.getDay() + #res.hour.val = date.getHours() + #res.minute.val = date.getMinutes() + #res.second.val = date.getSeconds() + #res.microsecond.val = date.getMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +pub fn parse_iso8601(s string) ?Time { + return parse(s) +} diff --git a/v_windows/v/vlib/time/parse_test.v b/v_windows/v/vlib/time/parse_test.v new file mode 100644 index 0000000..de7df37 --- /dev/null +++ b/v_windows/v/vlib/time/parse_test.v @@ -0,0 +1,138 @@ +import time + +fn test_parse() { + s := '2018-01-27 12:48:34' + t := time.parse(s) or { + assert false + return + } + assert t.year == 2018 && t.month == 1 && t.day == 27 && t.hour == 12 && t.minute == 48 + && t.second == 34 + assert t.unix == 1517057314 +} + +fn test_parse_invalid() { + s := 'Invalid time string' + time.parse(s) or { + assert true + return + } + assert false +} + +fn test_parse_rfc2822() { + s1 := 'Thu, 12 Dec 2019 06:07:45 GMT' + t1 := time.parse_rfc2822(s1) or { + assert false + return + } + assert t1.year == 2019 && t1.month == 12 && t1.day == 12 && t1.hour == 6 && t1.minute == 7 + && t1.second == 45 + assert t1.unix == 1576130865 + s2 := 'Thu 12 Dec 2019 06:07:45 +0800' + t2 := time.parse_rfc2822(s2) or { + assert false + return + } + assert t2.year == 2019 && t2.month == 12 && t2.day == 12 && t2.hour == 6 && t2.minute == 7 + && t2.second == 45 + assert t2.unix == 1576130865 +} + +fn test_parse_rfc2822_invalid() { + s3 := 'Thu 12 Foo 2019 06:07:45 +0800' + time.parse_rfc2822(s3) or { + assert true + return + } + assert false +} + +fn test_parse_iso8601() { + formats := [ + '2020-06-05T15:38:06Z', + '2020-06-05T15:38:06.015959Z', + '2020-06-05T15:38:06.015959+00:00', + '2020-06-05T15:38:06.015959+02:00', + '2020-06-05T15:38:06.015959-02:00', + '2020-11-05T15:38:06.015959Z', + ] + times := [ + [2020, 6, 5, 15, 38, 6, 0], + [2020, 6, 5, 15, 38, 6, 15959], + [2020, 6, 5, 15, 38, 6, 15959], + [2020, 6, 5, 13, 38, 6, 15959], + [2020, 6, 5, 17, 38, 6, 15959], + [2020, 11, 5, 15, 38, 6, 15959], + ] + for i, format in formats { + t := time.parse_iso8601(format) or { + assert false + continue + } + year := times[i][0] + assert t.year == year + month := times[i][1] + assert t.month == month + day := times[i][2] + assert t.day == day + hour := times[i][3] + assert t.hour == hour + minute := times[i][4] + assert t.minute == minute + second := times[i][5] + assert t.second == second + microsecond := times[i][6] + assert t.microsecond == microsecond + } +} + +fn test_parse_iso8601_local() { + format := '2020-06-05T15:38:06.015959' + t := time.parse_iso8601(format) or { + assert false + return + } + assert t.year == 2020 + assert t.month == 6 + assert t.day == 5 + assert t.hour == 15 + assert t.minute == 38 + assert t.second == 6 + assert t.microsecond == 15959 +} + +fn test_parse_iso8601_invalid() { + formats := [ + '', + '2020-06-05X15:38:06.015959Z', + '2020-06-05T15:38:06.015959X', + '2020-06-05T15:38:06.015959+0000', + '2020-06-05T', + '2020-06-05Z', + '2020-06-05+00:00', + '15:38:06', + ] + for format in formats { + time.parse_iso8601(format) or { + assert true + continue + } + assert false + } +} + +fn test_parse_iso8601_date_only() { + format := '2020-06-05' + t := time.parse_iso8601(format) or { + assert false + return + } + assert t.year == 2020 + assert t.month == 6 + assert t.day == 5 + assert t.hour == 0 + assert t.minute == 0 + assert t.second == 0 + assert t.microsecond == 0 +} diff --git a/v_windows/v/vlib/time/private_test.v b/v_windows/v/vlib/time/private_test.v new file mode 100644 index 0000000..8dde561 --- /dev/null +++ b/v_windows/v/vlib/time/private_test.v @@ -0,0 +1,13 @@ +// tests that use and test private functions +module time + +// test the old behavor is same as new, the unix time should always be local time +fn test_new_is_same_as_old_for_all_platforms() { + t := C.time(0) + tm := C.localtime(&t) + old_time := convert_ctime(tm, 0) + new_time := now() + diff := new_time.unix - old_time.unix + // could in very rare cases be that the second changed between calls + assert (diff >= 0 && diff <= 1) == true +} diff --git a/v_windows/v/vlib/time/stopwatch.v b/v_windows/v/vlib/time/stopwatch.v new file mode 100644 index 0000000..569e10c --- /dev/null +++ b/v_windows/v/vlib/time/stopwatch.v @@ -0,0 +1,72 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +pub struct StopWatchOptions { + auto_start bool = true +} + +// StopWatch is used to measure elapsed time. +pub struct StopWatch { +mut: + elapsed u64 +pub mut: + start u64 + end u64 +} + +// new_stopwatch initializes a new StopWatch with the current time as start. +pub fn new_stopwatch(opts StopWatchOptions) StopWatch { + mut initial := u64(0) + if opts.auto_start { + initial = sys_mono_now() + } + return StopWatch{ + elapsed: 0 + start: initial + end: 0 + } +} + +// start starts the stopwatch. If the timer was paused, restarts counting. +pub fn (mut t StopWatch) start() { + t.start = sys_mono_now() + t.end = 0 +} + +// restart restarts the stopwatch. If the timer was paused, restarts counting. +pub fn (mut t StopWatch) restart() { + t.start = sys_mono_now() + t.end = 0 + t.elapsed = 0 +} + +// stop stops the timer, by setting the end time to the current time. +pub fn (mut t StopWatch) stop() { + t.end = sys_mono_now() +} + +// pause resets the `start` time and adds the current elapsed time to `elapsed`. +pub fn (mut t StopWatch) pause() { + if t.start > 0 { + if t.end == 0 { + t.elapsed += sys_mono_now() - t.start + } else { + t.elapsed += t.end - t.start + } + } + t.start = 0 +} + +// elapsed returns the Duration since the last start call +pub fn (t StopWatch) elapsed() Duration { + if t.start > 0 { + if t.end == 0 { + return Duration(i64(sys_mono_now() - t.start + t.elapsed)) + } else { + return Duration(i64(t.end - t.start + t.elapsed)) + } + } + return Duration(i64(t.elapsed)) +} diff --git a/v_windows/v/vlib/time/stopwatch_test.v b/v_windows/v/vlib/time/stopwatch_test.v new file mode 100644 index 0000000..49d005a --- /dev/null +++ b/v_windows/v/vlib/time/stopwatch_test.v @@ -0,0 +1,36 @@ +import time + +// NB: on CI jobs, especially msvc ones, sleep_ms may sleep for much more +// time than you have specified. To avoid false positives from CI test +// failures, some of the asserts will be run only if you pass `-d stopwatch` +fn test_stopwatch_works_as_intended() { + mut sw := time.new_stopwatch() + // sample code that you want to measure: + println('Hello world') + time.sleep(1 * time.millisecond) + // + println('Greeting the world took: ${sw.elapsed().nanoseconds()}ns') + assert sw.elapsed().nanoseconds() > 0 +} + +fn test_stopwatch_time_between_pause_and_start_should_be_skipped_in_elapsed() { + println('Testing pause function') + mut sw := time.new_stopwatch() + time.sleep(10 * time.millisecond) // A + eprintln('Elapsed after 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 8 // sometimes it sleeps for 9ms on windows.. + sw.pause() + time.sleep(10 * time.millisecond) + eprintln('Elapsed after pause and another 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 8 + $if stopwatch ? { + assert sw.elapsed().milliseconds() < 20 + } + sw.start() + time.sleep(10 * time.millisecond) // B + eprintln('Elapsed after resume and another 10ms nap: ${sw.elapsed().milliseconds()}ms') + assert sw.elapsed().milliseconds() >= 18 + $if stopwatch ? { + assert sw.elapsed().milliseconds() < 30 + } +} diff --git a/v_windows/v/vlib/time/time.c.v b/v_windows/v/vlib/time/time.c.v new file mode 100644 index 0000000..1bee150 --- /dev/null +++ b/v_windows/v/vlib/time/time.c.v @@ -0,0 +1,125 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include + +// C.timeval represents a C time value. +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +fn C.localtime(t &C.time_t) &C.tm + +fn C.time(t &C.time_t) C.time_t + +// now returns current local time. +pub fn now() Time { + $if macos { + return darwin_now() + } + $if windows { + return win_now() + } + $if solaris { + return solaris_now() + } + $if linux || android { + return linux_now() + } + // defaults to most common feature, the microsecond precision is not available + // in this API call + t := C.time(0) + now := C.localtime(&t) + return convert_ctime(*now, 0) +} + +// utc returns the current UTC time. +pub fn utc() Time { + $if macos { + return darwin_utc() + } + $if windows { + return win_utc() + } + $if solaris { + return solaris_utc() + } + $if linux || android { + return linux_utc() + } + // defaults to most common feature, the microsecond precision is not available + // in this API call + t := C.time(0) + _ = C.time(&t) + return unix2(i64(t), 0) +} + +// new_time returns a time struct with calculated Unix time. +pub fn new_time(t Time) Time { + if t.unix != 0 { + return t + } + tt := C.tm{ + tm_sec: t.second + tm_min: t.minute + tm_hour: t.hour + tm_mday: t.day + tm_mon: t.month - 1 + tm_year: t.year - 1900 + } + utime := make_unix_time(tt) + return Time{ + ...t + unix: utime + } +} + +// ticks returns a number of milliseconds elapsed since system start. +pub fn ticks() i64 { + $if windows { + return C.GetTickCount() + } $else { + ts := C.timeval{} + C.gettimeofday(&ts, 0) + return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1000))) + } + // t := i64(C.mach_absolute_time()) + // # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t ); + // # return (double)(* (uint64_t *) &elapsedNano) / 1000000; +} + +/* +// sleep makes the calling thread sleep for a given number of seconds. +[deprecated: 'call time.sleep(n * time.second)'] +pub fn sleep(seconds int) { + wait(seconds * time.second) +} +*/ + +// str returns time in the same format as `parse` expects ("YYYY-MM-DD HH:MM:SS"). +pub fn (t Time) str() string { + // TODO Define common default format for + // `str` and `parse` and use it in both ways + return t.format_ss() +} + +// convert_ctime converts a C time to V time. +fn convert_ctime(t C.tm, microsecond int) Time { + return Time{ + year: t.tm_year + 1900 + month: t.tm_mon + 1 + day: t.tm_mday + hour: t.tm_hour + minute: t.tm_min + second: t.tm_sec + microsecond: microsecond + unix: make_unix_time(t) + } +} + +pub const ( + infinite = Duration(C.INT64_MAX) +) diff --git a/v_windows/v/vlib/time/time.js.v b/v_windows/v/vlib/time/time.js.v new file mode 100644 index 0000000..9d08e08 --- /dev/null +++ b/v_windows/v/vlib/time/time.js.v @@ -0,0 +1,38 @@ +module time + +pub fn now() Time { + mut res := Time{} + #let date = new Date() + #res.year.val = date.getFullYear() + #res.month.val = date.getMonth() + #res.day.val = date.getDay() + #res.hour.val = date.getHours() + #res.minute.val = date.getMinutes() + #res.second.val = date.getSeconds() + #res.microsecond.val = date.getMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +pub fn utc() Time { + mut res := Time{} + #let date = new Date() + #res.year.val = date.getUTCFullYear() + #res.month.val = date.getUTCMonth() + #res.day.val = date.getUTCDay() + #res.hour.val = date.getUTCHours() + #res.minute.val = date.getUTCMinutes() + #res.second.val = date.getUTCSeconds() + #res.microsecond.val = date.getUTCMilliseconds() * 1000 + #res.unix.val = (date.getTime() / 1000).toFixed(0) + + return res +} + +/// Returns local time +pub fn (t Time) local() Time { + // TODO: Does this actually correct? JS clock is always set to timezone or no? + // if it is not we should try to use Intl for getting local time. + return t +} diff --git a/v_windows/v/vlib/time/time.v b/v_windows/v/vlib/time/time.v new file mode 100644 index 0000000..fa50918 --- /dev/null +++ b/v_windows/v/vlib/time/time.v @@ -0,0 +1,315 @@ +module time + +pub const ( + days_string = 'MonTueWedThuFriSatSun' + month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + months_string = 'JanFebMarAprMayJunJulAugSepOctNovDec' + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absolute_zero_year = i64(-292277022399) // as i64 + seconds_per_minute = 60 + seconds_per_hour = 60 * seconds_per_minute + seconds_per_day = 24 * seconds_per_hour + seconds_per_week = 7 * seconds_per_day + days_per_400_years = 365 * 400 + 97 + days_per_100_years = 365 * 100 + 24 + days_per_4_years = 365 * 4 + 1 + days_before = [ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, + ] + long_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', + 'Sunday', + ] +) + +// Time contains various time units for a point in time. +pub struct Time { +pub: + year int + month int + day int + hour int + minute int + second int + microsecond int + unix i64 +} + +// FormatDelimiter contains different time formats. +pub enum FormatTime { + hhmm12 + hhmm24 + hhmmss12 + hhmmss24 + hhmmss24_milli + hhmmss24_micro + no_time +} + +// FormatDelimiter contains different date formats. +pub enum FormatDate { + ddmmyy + ddmmyyyy + mmddyy + mmddyyyy + mmmd + mmmdd + mmmddyy + mmmddyyyy + no_date + yyyymmdd + yymmdd +} + +// FormatDelimiter contains different time/date delimiters. +pub enum FormatDelimiter { + dot + hyphen + slash + space + no_delimiter +} + +// smonth returns month name. +pub fn (t Time) smonth() string { + if t.month <= 0 || t.month > 12 { + return '---' + } + i := t.month - 1 + return time.months_string[i * 3..(i + 1) * 3] +} + +// unix_time returns Unix time. +[inline] +pub fn (t Time) unix_time() i64 { + return t.unix +} + +// unix_time_milli returns Unix time with millisecond resolution. +[inline] +pub fn (t Time) unix_time_milli() i64 { + return t.unix * 1000 + (t.microsecond / 1000) +} + +// add returns a new time that duration is added +pub fn (t Time) add(d Duration) Time { + microseconds := i64(t.unix) * 1_000_000 + t.microsecond + d.microseconds() + unix := microseconds / 1_000_000 + micro := microseconds % 1_000_000 + return unix2(unix, int(micro)) +} + +// add_seconds returns a new time struct with an added number of seconds. +pub fn (t Time) add_seconds(seconds int) Time { + return t.add(seconds * time.second) +} + +// add_days returns a new time struct with an added number of days. +pub fn (t Time) add_days(days int) Time { + return t.add(days * 24 * time.hour) +} + +// since returns a number of seconds elapsed since a given time. +fn since(t Time) int { + // TODO Use time.Duration instead of seconds + return 0 +} + +// relative returns a string representation of the difference between t +// and the current time. +pub fn (t Time) relative() string { + znow := now() + secs := znow.unix - t.unix + if secs <= 30 { + // right now or in the future + // TODO handle time in the future + return 'now' + } + if secs < 60 { + return '1m' + } + if secs < 3600 { + m := secs / 60 + if m == 1 { + return '1 minute ago' + } + return '$m minutes ago' + } + if secs < 3600 * 24 { + h := secs / 3600 + if h == 1 { + return '1 hour ago' + } + return '$h hours ago' + } + if secs < 3600 * 24 * 5 { + d := secs / 3600 / 24 + if d == 1 { + return '1 day ago' + } + return '$d days ago' + } + if secs > 3600 * 24 * 10000 { + return '' + } + return t.md() +} + +// relative_short returns a string saying how long ago a time occured as follows: +// 0-30 seconds: `"now"`; 30-60 seconds: `"1m"`; anything else is rounded to the +// nearest minute, hour or day; anything higher than 10000 days (about 27 years) +// years returns an empty string. +// Some Examples: +// `0s -> 'now'`; +// `20s -> 'now'`; +// `47s -> '1m'`; +// `456s -> '7m'`; +// `1234s -> '20m'`; +// `16834s -> '4h'`; +// `1687440s -> '33d'`; +// `15842354871s -> ''` +pub fn (t Time) relative_short() string { + znow := now() + secs := znow.unix - t.unix + if secs <= 30 { + // right now or in the future + // TODO handle time in the future + return 'now' + } + if secs < 60 { + return '1m' + } + if secs < 3600 { + return '${secs / 60}m' + } + if secs < 3600 * 24 { + return '${secs / 3600}h' + } + if secs < 3600 * 24 * 5 { + return '${secs / 3600 / 24}d' + } + if secs > 3600 * 24 * 10000 { + return '' + } + return t.md() +} + +// day_of_week returns the current day of a given year, month, and day, +// as an integer. +pub fn day_of_week(y int, m int, d int) int { + // Sakomotho's algorithm is explained here: + // https://stackoverflow.com/a/6385934 + t := [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4] + mut sy := y + if m < 3 { + sy = sy - 1 + } + return (sy + sy / 4 - sy / 100 + sy / 400 + t[m - 1] + d - 1) % 7 + 1 +} + +// day_of_week returns the current day as an integer. +pub fn (t Time) day_of_week() int { + return day_of_week(t.year, t.month, t.day) +} + +// weekday_str returns the current day as a string. +pub fn (t Time) weekday_str() string { + i := t.day_of_week() - 1 + return time.days_string[i * 3..(i + 1) * 3] +} + +// weekday_str returns the current day as a string. +pub fn (t Time) long_weekday_str() string { + i := t.day_of_week() - 1 + return time.long_days[i] +} + +// is_leap_year checks if a given a year is a leap year. +pub fn is_leap_year(year int) bool { + return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0) +} + +// days_in_month returns a number of days in a given month. +pub fn days_in_month(month int, year int) ?int { + if month > 12 || month < 1 { + return error('Invalid month: $month') + } + extra := if month == 2 && is_leap_year(year) { 1 } else { 0 } + res := time.month_days[month - 1] + extra + return res +} + +// str returns time in the same format as `parse` expects ("YYYY-MM-DD HH:MM:SS"). +pub fn (t Time) debug() string { + return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} microsecond: ${t.microsecond:06} unix: ${t.unix:07} }' +} + +// A lot of these are taken from the Go library. +pub type Duration = i64 + +pub const ( + nanosecond = Duration(1) + microsecond = Duration(1000 * nanosecond) + millisecond = Duration(1000 * microsecond) + second = Duration(1000 * millisecond) + minute = Duration(60 * second) + hour = Duration(60 * minute) +) + +// nanoseconds returns the duration as an integer number of nanoseconds. +pub fn (d Duration) nanoseconds() i64 { + return i64(d) +} + +// microseconds returns the duration as an integer number of microseconds. +pub fn (d Duration) microseconds() i64 { + return i64(d) / 1000 +} + +// milliseconds returns the duration as an integer number of milliseconds. +pub fn (d Duration) milliseconds() i64 { + return i64(d) / 1000000 +} + +// The following functions return floating point numbers because it's common to +// consider all of them in sub-one intervals +// seconds returns the duration as a floating point number of seconds. +pub fn (d Duration) seconds() f64 { + sec := d / time.second + nsec := d % time.second + return f64(sec) + f64(nsec) / 1e9 +} + +// minutes returns the duration as a floating point number of minutes. +pub fn (d Duration) minutes() f64 { + min := d / time.minute + nsec := d % time.minute + return f64(min) + f64(nsec) / (60 * 1e9) +} + +// hours returns the duration as a floating point number of hours. +pub fn (d Duration) hours() f64 { + hr := d / time.hour + nsec := d % time.hour + return f64(hr) + f64(nsec) / (60 * 60 * 1e9) +} + +// offset returns time zone UTC offset in seconds. +pub fn offset() int { + t := now() + local := t.local() + return int(local.unix - t.unix) +} diff --git a/v_windows/v/vlib/time/time_addition_test.v b/v_windows/v/vlib/time/time_addition_test.v new file mode 100644 index 0000000..8fde874 --- /dev/null +++ b/v_windows/v/vlib/time/time_addition_test.v @@ -0,0 +1,33 @@ +import time + +fn test_add_to_day_in_the_previous_century() ? { + a := time.parse_iso8601('1900-01-01') ? + aa := a.add_days(180) + dump(a.debug()) + dump(aa.debug()) + assert aa.ymmdd() == '1900-06-29' +} + +fn test_add_to_day_in_the_past() ? { + a := time.parse_iso8601('1990-03-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '1990-08-27' +} + +fn test_add_to_day_in_the_recent_past() ? { + a := time.parse_iso8601('2021-03-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '2021-08-28' +} + +fn test_add_to_day_in_the_future_1() ? { + a := time.parse_iso8601('3000-11-01') ? + aa := a.add_days(180) + assert aa.ymmdd() == '3001-04-30' +} + +fn test_add_to_day_in_the_future_2() ? { + a := time.parse_iso8601('3000-12-30') ? + aa := a.add_days(180) + assert aa.ymmdd() == '3001-06-28' +} diff --git a/v_windows/v/vlib/time/time_darwin.c.v b/v_windows/v/vlib/time/time_darwin.c.v new file mode 100644 index 0000000..d16c66b --- /dev/null +++ b/v_windows/v/vlib/time/time_darwin.c.v @@ -0,0 +1,84 @@ +module time + +#include + +const ( + // start_time is needed on Darwin and Windows because of potential overflows + start_time = C.mach_absolute_time() + time_base = init_time_base() +) + +[typedef] +struct C.mach_timebase_info_data_t { + numer u32 + denom u32 +} + +fn C.mach_absolute_time() u64 + +fn C.mach_timebase_info(&C.mach_timebase_info_data_t) + +fn C.clock_gettime_nsec_np(int) u64 + +struct InternalTimeBase { + numer u32 = 1 + denom u32 = 1 +} + +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +fn init_time_base() C.mach_timebase_info_data_t { + tb := C.mach_timebase_info_data_t{} + C.mach_timebase_info(&tb) + return C.mach_timebase_info_data_t{ + numer: tb.numer + denom: tb.denom + } +} + +fn sys_mono_now_darwin() u64 { + tm := C.mach_absolute_time() + if time.time_base.denom == 0 { + C.mach_timebase_info(&time.time_base) + } + return (tm - time.start_time) * time.time_base.numer / time.time_base.denom +} + +// NB: vpc_now_darwin is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now_darwin() u64 { + tm := C.mach_absolute_time() + if time.time_base.denom == 0 { + C.mach_timebase_info(&time.time_base) + } + return (tm - time.start_time) * time.time_base.numer / time.time_base.denom +} + +// darwin_now returns a better precision current time for Darwin based operating system +// this should be implemented with native system calls eventually +// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get +// the microseconds seconds part and converts to local time +fn darwin_now() Time { + // get the high precision time as UTC clock + tv := C.timeval{} + C.gettimeofday(&tv, 0) + loc_tm := C.tm{} + asec := voidptr(&tv.tv_sec) + C.localtime_r(asec, &loc_tm) + return convert_ctime(loc_tm, int(tv.tv_usec)) +} + +// darwin_utc returns a better precision current time for Darwin based operating system +// this should be implemented with native system calls eventually +// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get +// the microseconds seconds part and normal local time to get correct local time +fn darwin_utc() Time { + // get the high precision time as UTC clock + tv := C.timeval{} + C.gettimeofday(&tv, 0) + return unix2(i64(tv.tv_sec), int(tv.tv_usec)) +} diff --git a/v_windows/v/vlib/time/time_format_test.v b/v_windows/v/vlib/time/time_format_test.v new file mode 100644 index 0000000..6042cf9 --- /dev/null +++ b/v_windows/v/vlib/time/time_format_test.v @@ -0,0 +1,90 @@ +import time + +const ( + time_to_test = time.Time{ + year: 1980 + month: 7 + day: 11 + hour: 21 + minute: 23 + second: 42 + unix: 332198622 + } +) + +fn test_now_format() { + t := time.now() + u := t.unix + assert t.format() == time.unix(int(u)).format() +} + +fn test_format() { + assert '11.07.1980 21:23' == time_to_test.get_fmt_str(.dot, .hhmm24, .ddmmyyyy) +} + +fn test_hhmm() { + assert '21:23' == time_to_test.hhmm() +} + +fn test_hhmm12() { + assert '9:23 p.m.' == time_to_test.hhmm12() +} + +fn test_hhmmss() { + assert '21:23:42' == time_to_test.hhmmss() +} + +fn test_ymmdd() { + assert '1980-07-11' == time_to_test.ymmdd() +} + +fn test_ddmmy() { + assert '11.07.1980' == time_to_test.ddmmy() +} + +fn test_md() { + assert 'Jul 11' == time_to_test.md() +} + +fn test_get_fmt_time_str() { + assert '21:23:42' == time_to_test.get_fmt_time_str(.hhmmss24) + assert '21:23' == time_to_test.get_fmt_time_str(.hhmm24) + assert '9:23:42 p.m.' == time_to_test.get_fmt_time_str(.hhmmss12) + assert '9:23 p.m.' == time_to_test.get_fmt_time_str(.hhmm12) +} + +fn test_get_fmt_date_str() { + assert '11.07.1980' == time_to_test.get_fmt_date_str(.dot, .ddmmyyyy) + assert '11/07/1980' == time_to_test.get_fmt_date_str(.slash, .ddmmyyyy) + assert '11-07-1980' == time_to_test.get_fmt_date_str(.hyphen, .ddmmyyyy) + assert '11 07 1980' == time_to_test.get_fmt_date_str(.space, .ddmmyyyy) + assert '07.11.1980' == time_to_test.get_fmt_date_str(.dot, .mmddyyyy) + assert '07/11/1980' == time_to_test.get_fmt_date_str(.slash, .mmddyyyy) + assert '07-11-1980' == time_to_test.get_fmt_date_str(.hyphen, .mmddyyyy) + assert '07 11 1980' == time_to_test.get_fmt_date_str(.space, .mmddyyyy) + assert '11.07.80' == time_to_test.get_fmt_date_str(.dot, .ddmmyy) + assert '11/07/80' == time_to_test.get_fmt_date_str(.slash, .ddmmyy) + assert '11-07-80' == time_to_test.get_fmt_date_str(.hyphen, .ddmmyy) + assert '11 07 80' == time_to_test.get_fmt_date_str(.space, .ddmmyy) + assert '07.11.80' == time_to_test.get_fmt_date_str(.dot, .mmddyy) + assert '07/11/80' == time_to_test.get_fmt_date_str(.slash, .mmddyy) + assert '07-11-80' == time_to_test.get_fmt_date_str(.hyphen, .mmddyy) + assert '07 11 80' == time_to_test.get_fmt_date_str(.space, .mmddyy) + assert 'Jul 11' == time_to_test.get_fmt_date_str(.space, .mmmd) + assert 'Jul 11' == time_to_test.get_fmt_date_str(.space, .mmmdd) + assert 'Jul 11 80' == time_to_test.get_fmt_date_str(.space, .mmmddyy) + assert 'Jul 11 1980' == time_to_test.get_fmt_date_str(.space, .mmmddyyyy) + assert '1980-07-11' == time_to_test.get_fmt_date_str(.hyphen, .yyyymmdd) + assert '80.07.11' == time_to_test.get_fmt_date_str(.dot, .yymmdd) +} + +fn test_get_fmt_str() { + // Since get_fmt_time_str and get_fmt_date_str do have comprehensive + // tests I don't want to exaggerate here with all possible + // combinations. + assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) +} + +fn test_utc_string() { + assert 'Fri, 11 Jul 1980 21:23:42 UTC' == time_to_test.utc_string() +} diff --git a/v_windows/v/vlib/time/time_js.js.v b/v_windows/v/vlib/time/time_js.js.v new file mode 100644 index 0000000..55abac3 --- /dev/null +++ b/v_windows/v/vlib/time/time_js.js.v @@ -0,0 +1,31 @@ +module time + +#var $timeOff = 0; +#var $seen = 0 +#function $sys_mono_new_Date() { +#var t = Date.now() +#if (t < seen) +#timeOff += (seen - t) +# +#seen = t +#return t + timeOff +#} + +pub fn sys_mono_now() u64 { + $if js_browser { + mut res := u64(0) + #res = new u64(window.performance.now() * 1000000) + + return res + } $else $if js_node { + mut res := u64(0) + #res.val = Number($process.hrtime.bigint()) + + return res + } $else { + mut res := u64(0) + #res = new u64($sys_mono_new_Date() * 1000000) + + return res + } +} diff --git a/v_windows/v/vlib/time/time_linux.c.v b/v_windows/v/vlib/time/time_linux.c.v new file mode 100644 index 0000000..b0ab7ad --- /dev/null +++ b/v_windows/v/vlib/time/time_linux.c.v @@ -0,0 +1,26 @@ +module time + +// sys_mono_now_darwin - dummy fn to compile on all platforms/compilers +fn sys_mono_now_darwin() u64 { + return 0 +} + +// darwin_now - dummy fn to compile on all platforms/compilers +pub fn darwin_now() Time { + return Time{} +} + +// solaris_now - dummy fn to compile on all platforms/compilers +pub fn solaris_now() Time { + return Time{} +} + +// darwin_utc - dummy fn to compile on all platforms/compilers +pub fn darwin_utc() Time { + return Time{} +} + +// solaris_utc - dummy fn to compile on all platforms/compilers +pub fn solaris_utc() Time { + return Time{} +} diff --git a/v_windows/v/vlib/time/time_nix.c.v b/v_windows/v/vlib/time/time_nix.c.v new file mode 100644 index 0000000..f4ad2c8 --- /dev/null +++ b/v_windows/v/vlib/time/time_nix.c.v @@ -0,0 +1,156 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include +#include + +struct C.tm { + tm_sec int + tm_min int + tm_hour int + tm_mday int + tm_mon int + tm_year int + tm_wday int + tm_yday int + tm_isdst int +} + +fn C.timegm(&C.tm) C.time_t + +// fn C.gmtime_r(&tm, &gbuf) +fn C.localtime_r(t &C.time_t, tm &C.tm) + +fn make_unix_time(t C.tm) i64 { + return i64(C.timegm(&t)) +} + +// local returns t with the location set to local time. +pub fn (t Time) local() Time { + loc_tm := C.tm{} + C.localtime_r(voidptr(&t.unix), &loc_tm) + return convert_ctime(loc_tm, t.microsecond) +} + +// in most systems, these are __quad_t, which is an i64 +struct C.timespec { +mut: + tv_sec i64 + tv_nsec i64 +} + +// the first arg is defined in include/bits/types.h as `__S32_TYPE`, which is `int` +fn C.clock_gettime(int, &C.timespec) + +fn C.nanosleep(req &C.timespec, rem &C.timespec) int + +// sys_mono_now returns a *monotonically increasing time*, NOT a time adjusted for daylight savings, location etc. +pub fn sys_mono_now() u64 { + $if macos { + return sys_mono_now_darwin() + } $else { + ts := C.timespec{} + C.clock_gettime(C.CLOCK_MONOTONIC, &ts) + return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) + } +} + +// NB: vpc_now is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now() u64 { + ts := C.timespec{} + C.clock_gettime(C.CLOCK_MONOTONIC, &ts) + return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) +} + +// The linux_* functions are placed here, since they're used on Android as well +// TODO: should `$if linux {}` be parsed on Android as well? (Android runs under the Linux kernel) +// linux_now returns the local time with high precision for most os:es +// this should be implemented properly with support for leap seconds. +// It uses the realtime clock to get and converts it to local time +fn linux_now() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + loc_tm := C.tm{} + C.localtime_r(voidptr(&ts.tv_sec), &loc_tm) + return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) +} + +fn linux_utc() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) +} + +// dummy to compile with all compilers +pub fn win_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn win_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +// return absolute timespec for now()+d +pub fn (d Duration) timespec() C.timespec { + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + d_sec := d / second + d_nsec := d % second + ts.tv_sec += d_sec + ts.tv_nsec += d_nsec + if ts.tv_nsec > i64(second) { + ts.tv_nsec -= i64(second) + ts.tv_sec++ + } + return ts +} + +// return timespec of 1970/1/1 +pub fn zero_timespec() C.timespec { + ts := C.timespec{ + tv_sec: 0 + tv_nsec: 0 + } + return ts +} + +// sleep makes the calling thread sleep for a given duration (in nanoseconds). +pub fn sleep(duration Duration) { + mut req := C.timespec{duration / second, duration % second} + rem := C.timespec{} + for C.nanosleep(&req, &rem) < 0 { + if C.errno == C.EINTR { + // Interrupted by a signal handler + req = rem + } else { + break + } + } +} + +// some *nix system functions (e.g. `C.poll()`, C.epoll_wait()) accept an `int` +// value as *timeout in milliseconds* with the special value `-1` meaning "infinite" +pub fn (d Duration) sys_milliseconds() int { + if d > C.INT32_MAX * millisecond { // treat 2147483647000001 .. C.INT64_MAX as "infinite" + return -1 + } else if d <= 0 { + return 0 // treat negative timeouts as 0 - consistent with Unix behaviour + } else { + return int(d / millisecond) + } +} diff --git a/v_windows/v/vlib/time/time_solaris.c.v b/v_windows/v/vlib/time/time_solaris.c.v new file mode 100644 index 0000000..b87f1c8 --- /dev/null +++ b/v_windows/v/vlib/time/time_solaris.c.v @@ -0,0 +1,32 @@ +module time + +// solaris_now returns the local time with high precision for most os:es +// this should be implemented properly with support for leap seconds. +// It uses the realtime clock to get and converts it to local time +fn solaris_now() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + loc_tm := C.tm{} + C.localtime_r(voidptr(&ts.tv_sec), &loc_tm) + return convert_ctime(loc_tm, int(ts.tv_nsec / 1000)) +} + +fn solaris_utc() Time { + // get the high precision time as UTC realtime clock + // and use the nanoseconds part + mut ts := C.timespec{} + C.clock_gettime(C.CLOCK_REALTIME, &ts) + return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000)) +} + +// dummy to compile with all compilers +pub fn darwin_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn darwin_utc() Time { + return Time{} +} diff --git a/v_windows/v/vlib/time/time_test.v b/v_windows/v/vlib/time/time_test.v new file mode 100644 index 0000000..dbd3c48 --- /dev/null +++ b/v_windows/v/vlib/time/time_test.v @@ -0,0 +1,247 @@ +import time +import math + +const ( + time_to_test = time.Time{ + year: 1980 + month: 7 + day: 11 + hour: 21 + minute: 23 + second: 42 + microsecond: 123456 + unix: 332198622 + } +) + +fn test_is_leap_year() { + // 1996 % 4 = 0 and 1996 % 100 > 0 + assert time.is_leap_year(1996) == true + // 2000 % 4 = 0 and 2000 % 400 = 0 + assert time.is_leap_year(2000) == true + // 1996 % 4 > 0 + assert time.is_leap_year(1997) == false + // 2000 % 4 = 0 and 2000 % 100 = 0 + assert time.is_leap_year(2100) == false +} + +fn check_days_in_month(month int, year int, expected int) bool { + res := time.days_in_month(month, year) or { return false } + return res == expected +} + +fn test_days_in_month() { + days_in_month := [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + for i, days in days_in_month { + month := i + 1 + assert check_days_in_month(month, 2001, days) + } +} + +fn test_unix() { + t := time.unix(1564366499) + assert t.year == 2019 + assert t.month == 7 + assert t.day == 29 + assert t.hour == 2 + assert t.minute == 14 + assert t.second == 59 + t2 := time.unix(1078058096) + assert t2.year == 2004 + assert t2.month == 2 + assert t2.day == 29 + assert t2.hour == 12 + assert t2.minute == 34 + assert t2.second == 56 + t3 := time.unix(1070236799) + assert t3.year == 2003 + assert t3.month == 11 + assert t3.day == 30 + assert t3.hour == 23 + assert t3.minute == 59 + assert t3.second == 59 + t4 := time.unix(1577783439) + assert t4.year == 2019 + assert t4.month == 12 + assert t4.day == 31 + assert t4.hour == 9 + assert t4.minute == 10 + assert t4.second == 39 + t5 := time.unix(-1824922433) + assert t5.year == 1912 + assert t5.month == 3 + assert t5.day == 4 + assert t5.hour == 5 + assert t5.minute == 6 + assert t5.second == 7 + t6 := time.unix(1577858969) + assert t6.year == 2020 + assert t6.month == 1 + assert t6.day == 1 + assert t6.hour == 6 + assert t6.minute == 9 + assert t6.second == 29 +} + +fn test_format_ss() { + assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) +} + +fn test_format_ss_milli() { + assert '11.07.1980 21:23:42.123' == time_to_test.get_fmt_str(.dot, .hhmmss24_milli, + .ddmmyyyy) + assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli() +} + +fn test_format_ss_micro() { + assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro, + .ddmmyyyy) + assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro() +} + +fn test_smonth() { + month_names := ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', + 'Dec', + ] + for i, name in month_names { + month_num := i + 1 + t := time.Time{ + year: 1980 + month: month_num + day: 1 + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert t.smonth() == name + } +} + +fn test_day_of_week() { + for i in 0 .. 7 { + day_of_week := i + 1 + // 2 Dec 2019 is Monday + t := time.Time{ + year: 2019 + month: 12 + day: 2 + i + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert day_of_week == t.day_of_week() + } +} + +fn test_weekday_str() { + day_names := ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + for i, name in day_names { + // 2 Dec 2019 is Monday + t := time.Time{ + year: 2019 + month: 12 + day: 2 + i + hour: 0 + minute: 0 + second: 0 + unix: 0 + } + assert t.weekday_str() == name + } +} + +fn test_add() { + d_seconds := 3 + d_microseconds := 13 + duration := time.Duration(d_seconds * time.second + d_microseconds * time.microsecond) + t1 := time_to_test + t2 := time_to_test.add(duration) + assert t2.second == t1.second + d_seconds + assert t2.microsecond == t1.microsecond + d_microseconds + assert t2.unix == t1.unix + d_seconds + t3 := time_to_test.add(-duration) + assert t3.second == t1.second - d_seconds + assert t3.microsecond == t1.microsecond - d_microseconds + assert t3.unix == t1.unix - d_seconds +} + +fn test_add_days() { + num_of_days := 3 + t := time_to_test.add_days(num_of_days) + assert t.day == time_to_test.day + num_of_days + assert t.unix == time_to_test.unix + 86400 * num_of_days +} + +fn test_str() { + assert '1980-07-11 21:23:42' == time_to_test.str() +} + +// not optimal test but will find obvious bugs +fn test_now() { + now := time.now() + // The year the test was built + assert now.year >= 2020 + assert now.month > 0 + assert now.month <= 12 + assert now.minute >= 0 + assert now.minute < 60 + assert now.second >= 0 + assert now.second <= 60 // <= 60 cause of leap seconds + assert now.microsecond >= 0 + assert now.microsecond < 1000000 +} + +fn test_utc() { + now := time.utc() + // The year the test was built + assert now.year >= 2020 + assert now.month > 0 + assert now.month <= 12 + assert now.minute >= 0 + assert now.minute < 60 + assert now.second >= 0 + assert now.second <= 60 // <= 60 cause of leap seconds + assert now.microsecond >= 0 + assert now.microsecond < 1000000 +} + +fn test_unix_time() { + t1 := time.utc() + time.sleep(50 * time.millisecond) + t2 := time.utc() + ut1 := t1.unix_time() + ut2 := t2.unix_time() + assert ut2 - ut1 < 2 + // + utm1 := t1.unix_time_milli() + utm2 := t2.unix_time_milli() + assert (utm1 - ut1 * 1000) < 1000 + assert (utm2 - ut2 * 1000) < 1000 + // + // println('utm1: $utm1 | utm2: $utm2') + assert utm2 - utm1 > 2 + assert utm2 - utm1 < 999 +} + +fn test_offset() { + u := time.utc() + n := time.now() + // + mut diff_seconds := 0 + if u.day != n.day { + if u.day > n.day { + diff_seconds = int(math.abs(((u.hour * 60 + u.minute) - (n.hour * 60 + n.minute)) * 60)) - 86400 + } else { + diff_seconds = 86400 - int(math.abs(((u.hour * 60 + u.minute) - (n.hour * 60 + n.minute)) * 60)) + } + if math.abs(u.day - n.day) > 1 { // different month + diff_seconds = diff_seconds * -1 + } + } else { // same day + diff_seconds = ((n.hour * 60 + n.minute) - (u.hour * 60 + u.minute)) * 60 + } + + assert diff_seconds == time.offset() +} diff --git a/v_windows/v/vlib/time/time_windows.c.v b/v_windows/v/vlib/time/time_windows.c.v new file mode 100644 index 0000000..a810cb9 --- /dev/null +++ b/v_windows/v/vlib/time/time_windows.c.v @@ -0,0 +1,231 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +#include +// #include + +struct C.tm { + tm_year int + tm_mon int + tm_mday int + tm_hour int + tm_min int + tm_sec int +} + +struct C._FILETIME { + dwLowDateTime u32 + dwHighDateTime u32 +} + +struct SystemTime { + year u16 + month u16 + day_of_week u16 + day u16 + hour u16 + minute u16 + second u16 + millisecond u16 +} + +fn C.GetSystemTimeAsFileTime(lpSystemTimeAsFileTime &C._FILETIME) + +fn C.FileTimeToSystemTime(lpFileTime &C._FILETIME, lpSystemTime &SystemTime) + +fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMATION, lpUniversalTime &SystemTime, lpLocalTime &SystemTime) + +fn C.localtime_s(t &C.time_t, tm &C.tm) + +const ( + // start_time is needed on Darwin and Windows because of potential overflows + start_time = init_win_time_start() + freq_time = init_win_time_freq() + start_local_time = local_as_unix_time() +) + +// in most systems, these are __quad_t, which is an i64 +struct C.timespec { + tv_sec i64 + tv_nsec i64 +} + +fn C.QueryPerformanceCounter(&u64) C.BOOL + +fn C.QueryPerformanceFrequency(&u64) C.BOOL + +fn make_unix_time(t C.tm) i64 { + return portable_timegm(&t) +} + +fn init_win_time_freq() u64 { + f := u64(0) + C.QueryPerformanceFrequency(&f) + return f +} + +fn init_win_time_start() u64 { + s := u64(0) + C.QueryPerformanceCounter(&s) + return s +} + +// sys_mono_now returns a *monotonically increasing time*, NOT a time adjusted for daylight savings, location etc. +pub fn sys_mono_now() u64 { + tm := u64(0) + C.QueryPerformanceCounter(&tm) // XP or later never fail + return (tm - time.start_time) * 1000000000 / time.freq_time +} + +// NB: vpc_now is used by `v -profile` . +// It should NOT call *any other v function*, just C functions and casts. +[inline] +fn vpc_now() u64 { + tm := u64(0) + C.QueryPerformanceCounter(&tm) + return tm +} + +// local_as_unix_time returns the current local time as unix time +fn local_as_unix_time() i64 { + t := C.time(0) + tm := C.localtime(&t) + return make_unix_time(tm) +} + +// local - return the time `t`, converted to the currently active local timezone +pub fn (t Time) local() Time { + st_utc := SystemTime{ + year: u16(t.year) + month: u16(t.month) + day: u16(t.day) + hour: u16(t.hour) + minute: u16(t.minute) + second: u16(t.second) + } + st_local := SystemTime{} + C.SystemTimeToTzSpecificLocalTime(voidptr(0), &st_utc, &st_local) + t_local := Time{ + year: st_local.year + month: st_local.month + day: st_local.day + hour: st_local.hour + minute: st_local.minute + second: st_local.second // These are the same + microsecond: st_local.millisecond * 1000 + unix: st_local.unix_time() + } + return t_local +} + +// win_now calculates current time using winapi to get higher resolution on windows +// GetSystemTimeAsFileTime is used and converted to local time. It can resolve time +// down to millisecond. Other more precice methods can be implemented in the future +fn win_now() Time { + ft_utc := C._FILETIME{} + C.GetSystemTimeAsFileTime(&ft_utc) + st_utc := SystemTime{} + C.FileTimeToSystemTime(&ft_utc, &st_utc) + st_local := SystemTime{} + C.SystemTimeToTzSpecificLocalTime(voidptr(0), &st_utc, &st_local) + t := Time{ + year: st_local.year + month: st_local.month + day: st_local.day + hour: st_local.hour + minute: st_local.minute + second: st_local.second + microsecond: st_local.millisecond * 1000 + unix: st_local.unix_time() + } + return t +} + +// win_utc calculates current time using winapi to get higher resolution on windows +// GetSystemTimeAsFileTime is used. It can resolve time down to millisecond +// other more precice methods can be implemented in the future +fn win_utc() Time { + ft_utc := C._FILETIME{} + C.GetSystemTimeAsFileTime(&ft_utc) + st_utc := SystemTime{} + C.FileTimeToSystemTime(&ft_utc, &st_utc) + t := Time{ + year: st_utc.year + month: st_utc.month + day: st_utc.day + hour: st_utc.hour + minute: st_utc.minute + second: st_utc.second + microsecond: st_utc.millisecond * 1000 + unix: st_utc.unix_time() + } + return t +} + +// unix_time returns Unix time. +pub fn (st SystemTime) unix_time() i64 { + tt := C.tm{ + tm_sec: st.second + tm_min: st.minute + tm_hour: st.hour + tm_mday: st.day + tm_mon: st.month - 1 + tm_year: st.year - 1900 + } + return make_unix_time(tt) +} + +// dummy to compile with all compilers +pub fn darwin_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn linux_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn solaris_now() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn darwin_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn linux_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub fn solaris_utc() Time { + return Time{} +} + +// dummy to compile with all compilers +pub struct C.timeval { + tv_sec u64 + tv_usec u64 +} + +// sleep makes the calling thread sleep for a given duration (in nanoseconds). +pub fn sleep(duration Duration) { + C.Sleep(int(duration / millisecond)) +} + +// some Windows system functions (e.g. `C.WaitForSingleObject()`) accept an `u32` +// value as *timeout in milliseconds* with the special value `u32(-1)` meaning "infinite" +pub fn (d Duration) sys_milliseconds() u32 { + if d >= u32(-1) * millisecond { // treat 4294967295000000 .. C.INT64_MAX as "infinite" + return u32(-1) + } else if d <= 0 { + return 0 // treat negative timeouts as 0 - consistent with Unix behaviour + } else { + return u32(d / millisecond) + } +} diff --git a/v_windows/v/vlib/time/unix.v b/v_windows/v/vlib/time/unix.v new file mode 100644 index 0000000..30cae42 --- /dev/null +++ b/v_windows/v/vlib/time/unix.v @@ -0,0 +1,124 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module time + +// unix returns a time struct from Unix time. +pub fn unix(abs i64) Time { + // Split into day and time + mut day_offset := abs / seconds_per_day + if abs % seconds_per_day < 0 { + // Compensate for round towards zero on integers as we want floored instead + day_offset-- + } + year, month, day := calculate_date_from_offset(day_offset) + hr, min, sec := calculate_time_from_offset(abs % seconds_per_day) + return Time{ + year: year + month: month + day: day + hour: hr + minute: min + second: sec + unix: abs + } +} + +// unix2 returns a time struct from Unix time and microsecond value +pub fn unix2(abs i64, microsecond int) Time { + // Split into day and time + mut day_offset := abs / seconds_per_day + if abs % seconds_per_day < 0 { + // Compensate for round towards zero on integers as we want floored instead + day_offset-- + } + year, month, day := calculate_date_from_offset(day_offset) + hr, min, sec := calculate_time_from_offset(abs % seconds_per_day) + return Time{ + year: year + month: month + day: day + hour: hr + minute: min + second: sec + microsecond: microsecond + unix: abs + } +} + +fn calculate_date_from_offset(day_offset_ i64) (int, int, int) { + mut day_offset := day_offset_ + // Move offset to year 2001 as it's the start of a new 400-year cycle + // Code below this rely on the fact that the day_offset is lined up with the 400-year cycle + // 1970-2000 (inclusive) has 31 years (8 of which are leap years) + mut year := 2001 + day_offset -= 31 * 365 + 8 + // Account for 400 year cycle + year += int(day_offset / days_per_400_years) * 400 + day_offset %= days_per_400_years + // Account for 100 year cycle + if day_offset == days_per_100_years * 4 { + year += 300 + day_offset -= days_per_100_years * 3 + } else { + year += int(day_offset / days_per_100_years) * 100 + day_offset %= days_per_100_years + } + // Account for 4 year cycle + if day_offset == days_per_4_years * 25 { + year += 96 + day_offset -= days_per_4_years * 24 + } else { + year += int(day_offset / days_per_4_years) * 4 + day_offset %= days_per_4_years + } + // Account for every year + if day_offset == 365 * 4 { + year += 3 + day_offset -= 365 * 3 + } else { + year += int(day_offset / 365) + day_offset %= 365 + } + if day_offset < 0 { + year-- + if is_leap_year(year) { + day_offset += 366 + } else { + day_offset += 365 + } + } + if is_leap_year(year) { + if day_offset > 31 + 29 - 1 { + // After leap day; pretend it wasn't there. + day_offset-- + } else if day_offset == 31 + 29 - 1 { + // Leap day. + return year, 2, 29 + } + } + mut estimated_month := day_offset / 31 + for day_offset >= days_before[estimated_month + 1] { + estimated_month++ + } + for day_offset < days_before[estimated_month] { + if estimated_month == 0 { + break + } + estimated_month-- + } + day_offset -= days_before[estimated_month] + return year, int(estimated_month + 1), int(day_offset + 1) +} + +fn calculate_time_from_offset(second_offset_ i64) (int, int, int) { + mut second_offset := second_offset_ + if second_offset < 0 { + second_offset += seconds_per_day + } + hour_ := second_offset / seconds_per_hour + second_offset %= seconds_per_hour + min := second_offset / seconds_per_minute + second_offset %= seconds_per_minute + return int(hour_), int(min), int(second_offset) +} diff --git a/v_windows/v/vlib/v/README.md b/v_windows/v/vlib/v/README.md new file mode 100644 index 0000000..c5360f9 --- /dev/null +++ b/v_windows/v/vlib/v/README.md @@ -0,0 +1,119 @@ +# Compiler pipeline +A simple high level explanation +how the compiler pipeline (`parser` -> `checker` -> `generator`) works. + +## Reading files +### Getting builtin files +To load all builtin files, +a preference `Preferences.lookup_path` for the path where to look for exists. +See `Builder.get_builtin_files` as example. +If the file is a `.vsh` file and the backend is C, `vlib/os` will also be loaded as builtin. + +### Getting project files +Either there is a specific file: `my_file.v` or a directory containing V files. +In the last case it scans that directory for all files. +See `Builder.v_files_from_dir` as the helper method. +This list of files needs to be filtered so that only `*.v` files exist. + +Skips the following file types: +- `*_test.v` +- either `*.c.v` or `*.c.js` depending on the backend +- all files that doesn't end with `.v` +- Files that are not defined in `Preferences.compile_defines` +or `Preferences.compile_defines_all` **if any file is defined**. + +## Parsing files +To parse something a new template is created as the first step: +```v +import v.ast + +table := ast.new_table() +``` + +a new preference is created: +```v +import v.pref + +pref := &pref.Preferences{} +``` + +and a new scope is created: +```v +import v.ast + +scope := ast.Scope{ + parent: 0 +} +``` +after that, you can parse your files. + +## Parse text +If you want to parse only text which isn't saved on the disk you can use this function. +```v oksyntax +import v.parser + +code := '' +// table, pref and scope needs to be passed as reference +parsed_file := parser.parse_text(code, table, .parse_comments, &pref, &scope) +``` + +## Parse a single file +For parsing files on disk, a path needs to be provided. +The paths are collected one step earlier. +```v oksyntax +import v.parser + +path := '' +// table, pref and scope needs to be passed as reference +parsed_file := parser.parse_file(path, table, .parse_comments, &pref, &scope) +``` + +## Parse a set of files +If you have a batch of paths available which should be parsed, +there is also a function which does all the work. +```v oksyntax +import v.parser + +paths := [''] +// table, pref and scope needs to be passed as reference +parsed_files := parser.parse_files(paths, table, &pref, &scope) +``` + +## Parse imports +A file often contains imports. These imports might need to be parsed as well. +The builder contains a method which does this: `Builder.parse_imports`. + +If the module which is imported isn't parsed already, +you have to collect it relatively from the main file. +For this the `ast.File` contains a list of imports. +Those imports needs to be found on disk. +`.` is just replaced with seperators in the relative location of the main file. +Then all files from that directory are collected and parsed again like the previous steps explained. + +## Checking AST +A new checker is created: +```v oksyntax +import v.checker + +mut checker := checker.new_checker(table, &pref) +``` + +After checking your files in `checker.errors` and `checker.warnings` you can see the results. + +### Check `ast.File` +```v oksyntax +checker.check(parsed_file) +``` + +### Check a list of `ast.File` +```v oksyntax +checker.check_files(parsed_files) +``` + +## Generate target from AST +Generating C code works just as this: +```v oksyntax +import v.gen.c + +res := c.gen(parsed_files, table, &pref) +``` diff --git a/v_windows/v/vlib/v/TEMPLATES.md b/v_windows/v/vlib/v/TEMPLATES.md new file mode 100644 index 0000000..25806d2 --- /dev/null +++ b/v_windows/v/vlib/v/TEMPLATES.md @@ -0,0 +1,129 @@ +V allows for easily using text templates, expanded at compile time to +V functions, that efficiently produce text output. This is especially +usefull for templated HTML views, but the mechanism is general enough +to be used for other kinds of text output also. + +# Template directives +Each template directive begins with an `@` sign. +Some directives contain a `{}` block, others only have `''` (string) parameters. + +Newlines on the beginning and end are ignored in `{}` blocks, +otherwise this (see [if](#if) for this syntax): +```html +@if bool_val { + This is shown if bool_val is true +} +``` +... would output: +```html + + This is shown if bool_val is true + +``` +... which is less readable. + +## if +The if directive, consists of three parts, the `@if` tag, the condition (same syntax like in V) +and the `{}` block, where you can write html, which will be rendered if the condition is true: +``` +@if {} +``` + +### Example +```html +@if bool_val { + This is shown if bool_val is true +} +``` +One-liner: +```html +@if bool_val { This is shown if bool_val is true } +``` + +The first example would result in: +```html + This is shown if bool_val is true +``` +... while the one-liner results in: +```html +This is shown if bool_val is true +``` + +## for +The for directive consists of three parts, the `@for` tag, +the condition (same syntax like in V) and the `{}` block, +where you can write text, rendered for each iteration of the loop: +``` +@for {} +``` + +### Example for @for +```html +@for i, val in my_vals { + $i - $val +} +``` +One-liner: +```html +@for i, val in my_vals { $i - $val } +``` + +The first example would result in: +```html + 0 - "First" + 1 - "Second" + 2 - "Third" + ... +``` +... while the one-liner results in: +```html +0 - "First" +1 - "Second" +2 - "Third" +... +``` + +You can also write (and all other for condition syntaxes that are allowed in V): +```html +@for i = 0; i < 5; i++ { + $i +} +``` + +## include +The include directive is for including other html files (which will be processed as well) +and consists of two parts, the `@include` tag and a following `''` string. +The path parameter is relative to the `/templates` directory in the corresponding project. + +### Example for the folder structure of a project using templates: +``` +Project root +/templates + - index.html + /headers + - base.html +``` + +`index.html` +```html +
@include 'header/base'
+``` +> Note that there shouldn't be a file suffix, + it is automatically appended and only allows `html` files. + + +## js +The js directive consists of two parts, the `@js` tag and `''` string, +where you can insert your src +``` +@js '' +``` + +### Example for the @js directive: +```html +@js 'myscripts.js' +``` + +# Variables +All variables, which are declared before the $tmpl can be used through the `@{my_var}` syntax. +It's also possible to use properties of structs here like `@{my_struct.prop}`. diff --git a/v_windows/v/vlib/v/ast/ast.v b/v_windows/v/vlib/v/ast/ast.v new file mode 100644 index 0000000..45f1c1c --- /dev/null +++ b/v_windows/v/vlib/v/ast/ast.v @@ -0,0 +1,2026 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +import v.token +import v.errors +import v.pref + +pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl + +pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | + CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | + ComptimeSelector | ConcatExpr | DumpExpr | EmptyExpr | EnumVal | FloatLiteral | GoExpr | + Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | IsRefType | + Likely | LockExpr | MapInit | MatchExpr | NodeError | None | OffsetOf | OrExpr | ParExpr | + PostfixExpr | PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | + StringInterLiteral | StringLiteral | StructInit | TypeNode | TypeOf | UnsafeExpr + +pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | + DeferStmt | EmptyStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | + GlobalDecl | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | NodeError | + Return | SqlStmt | StructDecl | TypeDecl + +pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var + +// TODO: replace Param +pub type Node = CallArg | ConstField | EmptyNode | EnumField | Expr | File | GlobalField | + IfBranch | MatchBranch | NodeError | Param | ScopeObject | SelectBranch | Stmt | StructField | + StructInitField + +pub struct TypeNode { +pub: + typ Type + pos token.Position +} + +pub struct EmptyExpr { + x int +} + +pub fn empty_expr() Expr { + return EmptyExpr{} +} + +pub struct EmptyStmt { +pub: + pos token.Position +} + +pub fn empty_stmt() Stmt { + return EmptyStmt{} +} + +pub struct EmptyNode { + x int +} + +pub fn empty_node() Node { + return EmptyNode{} +} + +// `{stmts}` or `unsafe {stmts}` +pub struct Block { +pub: + stmts []Stmt + is_unsafe bool + pos token.Position +} + +// | IncDecStmt k +// Stand-alone expression in a statement list. +pub struct ExprStmt { +pub: + expr Expr + pos token.Position + comments []Comment +pub mut: + is_expr bool + typ Type +} + +pub struct IntegerLiteral { +pub: + val string + pos token.Position +} + +pub struct FloatLiteral { +pub: + val string + pos token.Position +} + +pub struct StringLiteral { +pub: + val string + is_raw bool + language Language + pos token.Position +} + +// 'name: $name' +pub struct StringInterLiteral { +pub: + vals []string + exprs []Expr + fwidths []int + precisions []int + pluss []bool + fills []bool + fmt_poss []token.Position + pos token.Position +pub mut: + expr_types []Type + fmts []byte + need_fmts []bool // an explicit non-default fmt required, e.g. `x` +} + +pub struct CharLiteral { +pub: + val string + pos token.Position +} + +pub struct BoolLiteral { +pub: + val bool + pos token.Position +} + +pub enum GenericKindField { + unknown + name + typ +} + +// `foo.bar` +pub struct SelectorExpr { +pub: + pos token.Position + field_name string + is_mut bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable + mut_pos token.Position + next_token token.Kind +pub mut: + expr Expr // expr.field_name + expr_type Type // type of `Foo` in `Foo.bar` + typ Type // type of the entire thing (`Foo.bar`) + name_type Type // T in `T.name` or typeof in `typeof(expr).name` + gkind_field GenericKindField // `T.name` => ast.GenericKindField.name, `T.typ` => ast.GenericKindField.typ, or .unknown + scope &Scope + from_embed_type Type // holds the type of the embed that the method is called from +} + +// root_ident returns the origin ident where the selector started. +pub fn (e &SelectorExpr) root_ident() ?Ident { + mut root := e.expr + for root is SelectorExpr { + // TODO: remove this line + selector_expr := root as SelectorExpr + root = selector_expr.expr + } + if root is Ident { + return root as Ident + } + + return none +} + +// module declaration +pub struct Module { +pub: + name string // encoding.base64 + short_name string // base64 + attrs []Attr + pos token.Position + name_pos token.Position // `name` in import name + is_skipped bool // module main can be skipped in single file programs +} + +pub struct StructField { +pub: + pos token.Position + type_pos token.Position + comments []Comment + has_default_expr bool + attrs []Attr + is_pub bool + default_val string + is_mut bool + is_global bool +pub mut: + default_expr Expr + default_expr_typ Type + name string + typ Type +} + +/* +pub struct Field { +pub: + name string + pos token.Position +pub mut: + typ Type +} +*/ + +// const field in const declaration group +pub struct ConstField { +pub: + mod string + name string + expr Expr // the value expr of field; everything after `=` + is_pub bool + pos token.Position +pub mut: + typ Type // the type of the const field, it can be any type in V + comments []Comment // comments before current const field + // the comptime_expr_value field is filled by the checker, when it has enough + // info to evaluate the constant at compile time + comptime_expr_value ComptTimeConstValue = empty_comptime_const_expr() +} + +// const declaration +pub struct ConstDecl { +pub: + is_pub bool + pos token.Position +pub mut: + fields []ConstField // all the const fields in the `const (...)` block + end_comments []Comment // comments that after last const field + is_block bool // const() block +} + +pub struct StructDecl { +pub: + pos token.Position + name string + generic_types []Type + is_pub bool + // _pos fields for vfmt + mut_pos int // mut: + pub_pos int // pub: + pub_mut_pos int // pub mut: + global_pos int // __global: + module_pos int // module: + language Language + is_union bool + attrs []Attr + end_comments []Comment + embeds []Embed +pub mut: + fields []StructField +} + +pub struct Embed { +pub: + typ Type + pos token.Position + comments []Comment +} + +pub struct InterfaceEmbedding { +pub: + name string + typ Type + pos token.Position + comments []Comment +} + +pub struct InterfaceDecl { +pub: + name string + typ Type + name_pos token.Position + language Language + field_names []string + is_pub bool + mut_pos int // mut: + pos token.Position + pre_comments []Comment + generic_types []Type +pub mut: + methods []FnDecl + fields []StructField + // + ifaces []InterfaceEmbedding + are_ifaces_expanded bool +} + +pub struct StructInitField { +pub: + pos token.Position + name_pos token.Position + comments []Comment + next_comments []Comment +pub mut: + expr Expr + name string + typ Type + expected_type Type + parent_type Type +} + +pub struct StructInitEmbed { +pub: + expr Expr + pos token.Position + comments []Comment + next_comments []Comment +pub mut: + name string + typ Type + expected_type Type +} + +pub struct StructInit { +pub: + pos token.Position + name_pos token.Position + is_short bool +pub mut: + unresolved bool + pre_comments []Comment + typ Type + update_expr Expr + update_expr_type Type + update_expr_comments []Comment + has_update_expr bool + fields []StructInitField + embeds []StructInitEmbed +} + +// import statement +pub struct Import { +pub: + mod string // the module name of the import + alias string // the `x` in `import xxx as x` + pos token.Position + mod_pos token.Position + alias_pos token.Position + syms_pos token.Position +pub mut: + syms []ImportSymbol // the list of symbols in `import {symbol1, symbol2}` + comments []Comment + next_comments []Comment +} + +// import symbol,for import {symbol} syntax +pub struct ImportSymbol { +pub: + pos token.Position + name string +} + +// anonymous function +pub struct AnonFn { +pub mut: + decl FnDecl + inherited_vars []Param + typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated + has_gen bool // has been generated +} + +// function or method declaration +pub struct FnDecl { +pub: + name string + mod string + is_deprecated bool + is_pub bool + is_variadic bool + is_anon bool + is_noreturn bool // true, when [noreturn] is used on a fn + is_manualfree bool // true, when [manualfree] is used on a fn + is_main bool // true for `fn main()` + is_test bool // true for `fn test_abcde` + is_conditional bool // true for `[if abc] fn abc(){}` + is_exported bool // true for `[export: 'exact_C_name']` + is_keep_alive bool // passed memory must not be freed (by GC) before function returns + is_unsafe bool // true, when [unsafe] is used on a fn + receiver StructField // TODO this is not a struct field + receiver_pos token.Position // `(u User)` in `fn (u User) name()` position + is_method bool + method_type_pos token.Position // `User` in ` fn (u User)` position + method_idx int + rec_mut bool // is receiver mutable + rec_share ShareType + language Language // V, C, JS + file_mode Language // whether *the file*, where a function was a '.c.v', '.js.v' etc. + no_body bool // just a definition `fn C.malloc()` + is_builtin bool // this function is defined in builtin/strconv + body_pos token.Position // function bodys position + file string + generic_names []string + is_direct_arr bool // direct array access + attrs []Attr + ctdefine_idx int = -1 // the index in fn.attrs of `[if xyz]`, when such attribute exists +pub mut: + params []Param + stmts []Stmt + defer_stmts []DeferStmt + return_type Type + return_type_pos token.Position // `string` in `fn (u User) name() string` position + has_return bool + should_be_skipped bool + // + comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl + next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl + // + source_file &File = 0 + scope &Scope + label_names []string + pos token.Position // function declaration position +} + +// break, continue +pub struct BranchStmt { +pub: + kind token.Kind + label string + pos token.Position +} + +// function or method call expr +pub struct CallExpr { +pub: + pos token.Position + name_pos token.Position + mod string +pub mut: + name string // left.name() + is_method bool + is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe) + is_keep_alive bool // GC must not free arguments before fn returns + is_noreturn bool // whether the function/method is marked as [noreturn] + args []CallArg + expected_arg_types []Type + language Language + or_block OrExpr + left Expr // `user` in `user.register()` + left_type Type // type of `user` + receiver_type Type // User + return_type Type + should_be_skipped bool + concrete_types []Type // concrete types, e.g. + concrete_list_pos token.Position + free_receiver bool // true if the receiver expression needs to be freed + scope &Scope + from_embed_type Type // holds the type of the embed that the method is called from + comments []Comment +} + +/* +pub struct AutofreeArgVar { + name string + idx int +} +*/ +// function call argument: `f(callarg)` +pub struct CallArg { +pub: + is_mut bool + share ShareType + comments []Comment +pub mut: + expr Expr + typ Type + is_tmp_autofree bool // this tells cgen that a tmp variable has to be used for the arg expression in order to free it after the call + pos token.Position + // tmp_name string // for autofree +} + +// function return statement +pub struct Return { +pub: + pos token.Position + comments []Comment +pub mut: + exprs []Expr + types []Type +} + +/* +pub enum Expr { + Binary(InfixExpr) + If(IfExpr) + Integer(IntegerExpr) +} +*/ +/* +pub struct Stmt { + pos int + //end int +} +*/ +pub struct Var { +pub: + name string + expr Expr + share ShareType + is_mut bool + is_autofree_tmp bool + is_arg bool // fn args should not be autofreed + is_auto_deref bool + is_inherited bool +pub mut: + typ Type + orig_type Type // original sumtype type; 0 if it's not a sumtype + smartcasts []Type // nested sum types require nested smart casting, for that a list of types is needed + // TODO: move this to a real docs site later + // 10 <- original type (orig_type) + // [11, 12, 13] <- cast order (smartcasts) + // 12 <- the current casted type (typ) + pos token.Position + is_used bool + is_changed bool // to detect mutable vars that are never changed + // + // (for setting the position after the or block for autofree) + is_or bool // `x := foo() or { ... }` + is_tmp bool // for tmp for loop vars, so that autofree can skip them + is_auto_heap bool // value whoes address goes out of scope + is_stack_obj bool // may be pointer to stack value (`mut` or `&` arg and not [heap] struct) +} + +// used for smartcasting only +// struct fields change type in scopes +pub struct ScopeStructField { +pub: + struct_type Type // type of struct + name string + pos token.Position + typ Type + smartcasts []Type // nested sum types require nested smart casting, for that a list of types is needed + orig_type Type // original sumtype type; 0 if it's not a sumtype + // TODO: move this to a real docs site later + // 10 <- original type (orig_type) + // [11, 12, 13] <- cast order (smartcasts) + // 12 <- the current casted type (typ) +} + +pub struct GlobalField { +pub: + name string + has_expr bool + pos token.Position + typ_pos token.Position +pub mut: + expr Expr + typ Type + comments []Comment +} + +pub struct GlobalDecl { +pub: + mod string + pos token.Position + is_block bool // __global() block +pub mut: + fields []GlobalField + end_comments []Comment +} + +pub struct EmbeddedFile { +pub: + rpath string // used in the source code, as an ID/key to the embed + apath string // absolute path during compilation to the resource +} + +// Each V source file is represented by one File structure. +// When the V compiler runs, the parser will fill an []File. +// That array is then passed to V's checker. +[heap] +pub struct File { +pub: + nr_lines int // number of source code lines in the file (including newlines and comments) + nr_bytes int // number of processed source code bytes + mod Module // the module of the source file (from `module xyz` at the top) + global_scope &Scope + is_test bool // true for _test.v files +pub mut: + path string // absolute path of the source file - '/projects/v/file.v' + path_base string // file name - 'file.v' (useful for tracing) + scope &Scope + stmts []Stmt // all the statements in the source file + imports []Import // all the imports + auto_imports []string // imports that were implicitely added + embedded_files []EmbeddedFile // list of files to embed in the binary + imported_symbols map[string]string // used for `import {symbol}`, it maps symbol => module.symbol + errors []errors.Error // all the checker errors in the file + warnings []errors.Warning // all the checker warnings in the file + notices []errors.Notice // all the checker notices in the file + generic_fns []&FnDecl + global_labels []string // from `asm { .globl labelname }` +} + +[unsafe] +pub fn (f &File) free() { + unsafe { + f.path.free() + f.path_base.free() + f.scope.free() + f.stmts.free() + f.imports.free() + f.auto_imports.free() + f.embedded_files.free() + f.imported_symbols.free() + f.errors.free() + f.warnings.free() + f.notices.free() + f.global_labels.free() + } +} + +pub struct IdentFn { +pub mut: + typ Type +} + +// TODO: (joe) remove completely, use ident.obj +// instead which points to the scope object +pub struct IdentVar { +pub mut: + typ Type + is_mut bool + is_static bool + is_optional bool + share ShareType +} + +pub type IdentInfo = IdentFn | IdentVar + +pub enum IdentKind { + unresolved + blank_ident + variable + constant + global + function +} + +// A single identifier +pub struct Ident { +pub: + language Language + tok_kind token.Kind + pos token.Position + mut_pos token.Position + comptime bool +pub mut: + scope &Scope + obj ScopeObject + mod string + name string + kind IdentKind + info IdentInfo + is_mut bool +} + +pub fn (i &Ident) var_info() IdentVar { + match mut i.info { + IdentVar { + return i.info + } + else { + // return IdentVar{} + panic('Ident.var_info(): info is not IdentVar variant') + } + } +} + +// left op right +// See: token.Kind.is_infix +pub struct InfixExpr { +pub: + op token.Kind + pos token.Position + is_stmt bool +pub mut: + left Expr + right Expr + left_type Type + right_type Type + auto_locked string + or_block OrExpr +} + +// ++, -- +pub struct PostfixExpr { +pub: + op token.Kind + expr Expr + pos token.Position +pub mut: + auto_locked string +} + +// See: token.Kind.is_prefix +pub struct PrefixExpr { +pub: + op token.Kind + pos token.Position +pub mut: + right_type Type + right Expr + or_block OrExpr + is_option bool // IfGuard +} + +pub struct IndexExpr { +pub: + pos token.Position + index Expr // [0], RangeExpr [start..end] or map[key] + or_expr OrExpr +pub mut: + left Expr + left_type Type // array, map, fixed array + is_setter bool + is_map bool + is_array bool + is_farray bool + is_option bool // IfGuard +} + +pub struct IfExpr { +pub: + is_comptime bool + tok_kind token.Kind + left Expr // `a` in `a := if ...` + pos token.Position + post_comments []Comment +pub mut: + branches []IfBranch // includes all `else if` branches + is_expr bool + typ Type + has_else bool + // implements bool // comptime $if implements interface +} + +pub struct IfBranch { +pub: + cond Expr + pos token.Position + body_pos token.Position + comments []Comment +pub mut: + pkg_exist bool + stmts []Stmt + scope &Scope +} + +pub struct UnsafeExpr { +pub: + expr Expr + pos token.Position +} + +pub struct LockExpr { +pub: + stmts []Stmt + is_rlock []bool + pos token.Position +pub mut: + lockeds []Expr // `x`, `y.z` in `lock x, y.z {` + comments []Comment + is_expr bool + typ Type + scope &Scope +} + +pub struct MatchExpr { +pub: + tok_kind token.Kind + cond Expr + branches []MatchBranch + pos token.Position + comments []Comment // comments before the first branch +pub mut: + is_expr bool // returns a value + return_type Type + cond_type Type // type of `x` in `match x {` + expected_type Type // for debugging only + is_sum_type bool +} + +pub struct MatchBranch { +pub: + ecmnts [][]Comment // inline comments for each left side expr + stmts []Stmt // right side + pos token.Position + is_else bool + post_comments []Comment // comments below ´... }´ + branch_pos token.Position // for checker errors about invalid branches +pub mut: + exprs []Expr // left side + scope &Scope +} + +pub struct SelectExpr { +pub: + branches []SelectBranch + pos token.Position + has_exception bool +pub mut: + is_expr bool // returns a value + expected_type Type // for debugging only +} + +pub struct SelectBranch { +pub: + stmt Stmt // `a := <-ch` or `ch <- a` + stmts []Stmt // right side + pos token.Position + comment Comment // comment above `select {` + is_else bool + is_timeout bool + post_comments []Comment +} + +pub enum CompForKind { + methods + fields + attributes +} + +pub struct CompFor { +pub: + val_var string + stmts []Stmt + kind CompForKind + pos token.Position + typ_pos token.Position +pub mut: + // expr Expr + typ Type +} + +pub struct ForStmt { +pub: + cond Expr + stmts []Stmt + is_inf bool // `for {}` + pos token.Position +pub mut: + label string // `label: for {` + scope &Scope +} + +pub struct ForInStmt { +pub: + key_var string + val_var string + cond Expr + is_range bool + high Expr // `10` in `for i in 0..10 {` + stmts []Stmt + pos token.Position + val_is_mut bool // `for mut val in vals {` means that modifying `val` will modify the array + // and the array cannot be indexed inside the loop +pub mut: + key_type Type + val_type Type + cond_type Type + kind Kind // array/map/string + label string // `label: for {` + scope &Scope +} + +pub struct ForCStmt { +pub: + init Stmt // i := 0; + has_init bool + cond Expr // i < 10; + has_cond bool + inc Stmt // i++; i += 2 + has_inc bool + is_multi bool // for a,b := 0,1; a < 10; a,b = a+b, a {...} + stmts []Stmt + pos token.Position +pub mut: + label string // `label: for {` + scope &Scope +} + +// #include, #define etc +pub struct HashStmt { +pub: + mod string + pos token.Position + source_file string +pub mut: + val string // example: 'include # please install openssl // comment' + kind string // : 'include' + main string // : '' + msg string // : 'please install openssl' + ct_conds []Expr // *all* comptime conditions, that must be true, for the hash to be processed + // ct_conds is filled by the checker, based on the current nesting of `$if cond1 {}` blocks +} + +/* +// filter(), map(), sort() +pub struct Lambda { +pub: + name string +} +*/ +// variable assign statement +pub struct AssignStmt { +pub: + op token.Kind // include: =,:=,+=,-=,*=,/= and so on; for a list of all the assign operators, see vlib/token/token.v + pos token.Position + comments []Comment + end_comments []Comment +pub mut: + right []Expr + left []Expr + left_types []Type + right_types []Type + is_static bool // for translated code only + is_simple bool // `x+=2` in `for x:=1; ; x+=2` + has_cross_var bool +} + +// `expr as Ident` +pub struct AsCast { +pub: + expr Expr // from expr: `expr` in `expr as Ident` + typ Type // to type + pos token.Position +pub mut: + expr_type Type // from type +} + +// an enum value, like OS.macos or .macos +pub struct EnumVal { +pub: + enum_name string + val string + mod string // for full path `mod_Enum_val` + pos token.Position +pub mut: + typ Type +} + +// enum field in enum declaration +pub struct EnumField { +pub: + name string + pos token.Position + comments []Comment // comment after Enumfield in the same line + next_comments []Comment // comments between current EnumField and next EnumField + expr Expr // the value of current EnumField; 123 in `ename = 123` + has_expr bool // true, when .expr has a value +} + +// enum declaration +pub struct EnumDecl { +pub: + name string + is_pub bool + is_flag bool // true when the enum has [flag] tag,for bit field enum + is_multi_allowed bool // true when the enum has [_allow_multiple_values] tag + comments []Comment // comments before the first EnumField + fields []EnumField // all the enum fields + attrs []Attr // attributes of enum declaration + pos token.Position +} + +pub struct AliasTypeDecl { +pub: + name string + is_pub bool + parent_type Type + pos token.Position + type_pos token.Position + comments []Comment +} + +// New implementation of sum types +pub struct SumTypeDecl { +pub: + name string + is_pub bool + pos token.Position + comments []Comment + typ Type + generic_types []Type +pub mut: + variants []TypeNode +} + +pub struct FnTypeDecl { +pub: + name string + is_pub bool + typ Type + pos token.Position + type_pos token.Position + comments []Comment +} + +// TODO: handle this differently +// v1 excludes non current os ifdefs so +// the defer's never get added in the first place +pub struct DeferStmt { +pub: + stmts []Stmt + pos token.Position +pub mut: + defer_vars []Ident + ifdef string + idx_in_fn int = -1 // index in FnDecl.defer_stmts +} + +// `(3+4)` +pub struct ParExpr { +pub: + expr Expr + pos token.Position +} + +pub struct GoExpr { +pub: + pos token.Position +pub mut: + call_expr CallExpr + is_expr bool +} + +pub struct GotoLabel { +pub: + name string + pos token.Position +} + +pub struct GotoStmt { +pub: + name string + pos token.Position +} + +pub struct ArrayInit { +pub: + pos token.Position // `[]` in []Type{} position + elem_type_pos token.Position // `Type` in []Type{} position + exprs []Expr // `[expr, expr]` or `[expr]Type{}` for fixed array + ecmnts [][]Comment // optional iembed comments after each expr + pre_cmnts []Comment + is_fixed bool + has_val bool // fixed size literal `[expr, expr]!` + mod string + len_expr Expr // len: expr + cap_expr Expr // cap: expr + default_expr Expr // init: expr + has_len bool + has_cap bool + has_default bool +pub mut: + expr_types []Type // [Dog, Cat] // also used for interface_types + elem_type Type // element type + typ Type // array type +} + +pub struct ArrayDecompose { +pub: + expr Expr + pos token.Position +pub mut: + expr_type Type + arg_type Type +} + +pub struct ChanInit { +pub: + pos token.Position + cap_expr Expr + has_cap bool +pub mut: + typ Type + elem_type Type +} + +pub struct MapInit { +pub: + pos token.Position + keys []Expr + vals []Expr + comments [][]Comment // comments after key-value pairs + pre_cmnts []Comment // comments before the first key-value pair +pub mut: + typ Type + key_type Type + value_type Type +} + +// s[10..20] +pub struct RangeExpr { +pub: + low Expr + high Expr + has_high bool + has_low bool + pos token.Position +} + +// NB: &string(x) gets parsed as PrefixExpr{ right: CastExpr{...} } +// TODO: that is very likely a parsing bug. It should get parsed as just +// CastExpr{...}, where .typname is '&string' instead. +// The current situation leads to special cases in vfmt and cgen +// (see prefix_expr_cast_expr in fmt.v, and .is_amp in cgen.v) +// .in_prexpr is also needed because of that, because the checker needs to +// show warnings about the deprecated C->V conversions `string(x)` and +// `string(x,y)`, while skipping the real pointer casts like `&string(x)`. +// 2021/07/17: TODO: since 6edfb2c, the above is fixed at the parser level, +// we need to remove the hacks/special cases in vfmt and the checker too. +pub struct CastExpr { +pub: + arg Expr // `n` in `string(buf, n)` +pub mut: + typ Type // `string` + expr Expr // `buf` in `string(buf, n)` and `&Type(buf)` + typname string // `&Type` in `&Type(buf)` + expr_type Type // `byteptr`, the type of the `buf` expression + has_arg bool // true for `string(buf, n)`, false for `&Type(buf)` + pos token.Position +} + +pub struct AsmStmt { +pub: + arch pref.Arch + is_basic bool + is_volatile bool + is_goto bool + clobbered []AsmClobbered + pos token.Position +pub mut: + templates []AsmTemplate + scope &Scope + output []AsmIO + input []AsmIO + global_labels []string // labels defined in assembly block, exported with `.globl` + local_labels []string // local to the assembly block +} + +pub struct AsmTemplate { +pub mut: + name string + is_label bool // `example_label:` + is_directive bool // .globl assembly_function + args []AsmArg + comments []Comment + pos token.Position +} + +// [eax+5] | j | displacement literal (e.g. 123 in [rax + 123] ) | eax | true | `a` | 0.594 | 123 | label_name +pub type AsmArg = AsmAddressing | AsmAlias | AsmDisp | AsmRegister | BoolLiteral | CharLiteral | + FloatLiteral | IntegerLiteral | string + +pub struct AsmRegister { +pub mut: + name string // eax or r12d etc. + typ Type + size int +} + +pub struct AsmDisp { +pub: + val string + pos token.Position +} + +pub struct AsmAlias { +pub: + pos token.Position +pub mut: + name string // a +} + +pub struct AsmAddressing { +pub: + scale int = -1 // 1, 2, 4, or 8 literal + mode AddressingMode + pos token.Position +pub mut: + displacement AsmArg // 8, 16 or 32 bit literal value + base AsmArg // gpr + index AsmArg // gpr +} + +// adressing modes: +pub enum AddressingMode { + invalid + displacement // displacement + base // base + base_plus_displacement // base + displacement + index_times_scale_plus_displacement // (index ∗ scale) + displacement + base_plus_index_plus_displacement // base + (index ∗ scale) + displacement + base_plus_index_times_scale_plus_displacement // base + index + displacement + rip_plus_displacement // rip + displacement +} + +pub struct AsmClobbered { +pub mut: + reg AsmRegister + comments []Comment +} + +// : [alias_a] '=r' (a) // this is a comment +pub struct AsmIO { +pub: + alias string // [alias_a] + constraint string // '=r' TODO: allow all backends to easily use this with a struct + expr Expr // (a) + comments []Comment // // this is a comment + typ Type + pos token.Position +} + +pub const ( + // reference: https://en.wikipedia.org/wiki/X86#/media/File:Table_of_x86_Registers_svg.svg + // map register size -> register name + x86_no_number_register_list = { + 8: ['al', 'ah', 'bl', 'bh', 'cl', 'ch', 'dl', 'dh', 'bpl', 'sil', 'dil', 'spl'] + 16: ['ax', 'bx', 'cx', 'dx', 'bp', 'si', 'di', 'sp', /* segment registers */ 'cs', 'ss', + 'ds', 'es', 'fs', 'gs', 'flags', 'ip', /* task registers */ 'gdtr', 'idtr', 'tr', 'ldtr', + // CSR register 'msw', /* FP core registers */ 'cw', 'sw', 'tw', 'fp_ip', 'fp_dp', + 'fp_cs', 'fp_ds', 'fp_opc'] + 32: [ + 'eax', + 'ebx', + 'ecx', + 'edx', + 'ebp', + 'esi', + 'edi', + 'esp', + 'eflags', + 'eip', /* CSR register */ + 'mxcsr' /* 32-bit FP core registers 'fp_dp', 'fp_ip' (TODO: why are there duplicates?) */, + ] + 64: ['rax', 'rbx', 'rcx', 'rdx', 'rbp', 'rsi', 'rdi', 'rsp', 'rflags', 'rip'] + } + // no comments because maps do not support comments + // r#*: gp registers added in 64-bit extensions, can only be from 8-15 actually + // *mm#: vector/simd registors + // st#: floating point numbers + // cr#: control/status registers + // dr#: debug registers + x86_with_number_register_list = { + 8: { + 'r#b': 16 + } + 16: { + 'r#w': 16 + } + 32: { + 'r#d': 16 + } + 64: { + 'r#': 16 + 'mm#': 16 + 'cr#': 16 + 'dr#': 16 + } + 80: { + 'st#': 16 + } + 128: { + 'xmm#': 32 + } + 256: { + 'ymm#': 32 + } + 512: { + 'zmm#': 32 + } + } +) + +// TODO: saved priviled registers for arm +pub const ( + arm_no_number_register_list = ['fp' /* aka r11 */, /* not instruction pointer: */ 'ip' /* aka r12 */, + 'sp' /* aka r13 */, 'lr' /* aka r14 */, /* this is instruction pointer ('program counter'): */ + 'pc' /* aka r15 */, + ] // 'cpsr' and 'apsr' are special flags registers, but cannot be referred to directly + arm_with_number_register_list = { + 'r#': 16 + } +) + +pub const ( + riscv_no_number_register_list = ['zero', 'ra', 'sp', 'gp', 'tp'] + riscv_with_number_register_list = { + 'x#': 32 + 't#': 3 + 's#': 12 + 'a#': 8 + } +) + +pub struct AssertStmt { +pub: + pos token.Position +pub mut: + expr Expr + is_used bool // asserts are used in _test.v files, as well as in non -prod builds of all files +} + +// `if [x := opt()] {` +pub struct IfGuardExpr { +pub: + var_name string + pos token.Position +pub mut: + expr Expr + expr_type Type +} + +pub enum OrKind { + absent + block + propagate +} + +// `or { ... }` +pub struct OrExpr { +pub: + stmts []Stmt + kind OrKind + pos token.Position +} + +/* +// `or { ... }` +pub struct OrExpr2 { +pub: + call_expr CallExpr + stmts []Stmt // inside `or { }` + kind OrKind + pos token.Position +} +*/ + +// deprecated +pub struct Assoc { +pub: + var_name string + fields []string + exprs []Expr + pos token.Position +pub mut: + typ Type + scope &Scope +} + +pub struct SizeOf { +pub: + is_type bool + expr Expr // checker uses this to set typ + pos token.Position +pub mut: + typ Type +} + +pub struct IsRefType { +pub: + is_type bool + expr Expr // checker uses this to set typ + pos token.Position +pub mut: + typ Type +} + +pub struct OffsetOf { +pub: + struct_type Type + field string + pos token.Position +} + +pub struct Likely { +pub: + expr Expr + pos token.Position + is_likely bool // false for _unlikely_ +} + +pub struct TypeOf { +pub: + expr Expr + pos token.Position +pub mut: + expr_type Type +} + +pub struct DumpExpr { +pub: + expr Expr + pos token.Position +pub mut: + expr_type Type + cname string // filled in the checker +} + +pub struct Comment { +pub: + text string + is_multi bool // true only for /* comment */, that use many lines + is_inline bool // true for all /* comment */ comments + pos token.Position +} + +pub struct ConcatExpr { +pub: + vals []Expr + pos token.Position +pub mut: + return_type Type +} + +// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens +pub struct AtExpr { +pub: + name string + pos token.Position + kind token.AtKind +pub mut: + val string +} + +pub struct ComptimeSelector { +pub: + has_parens bool // if $() is used, for vfmt + left Expr + field_expr Expr + pos token.Position +pub mut: + left_type Type + typ Type +} + +pub struct ComptimeCall { +pub: + pos token.Position + has_parens bool // if $() is used, for vfmt + method_name string + method_pos token.Position + scope &Scope + left Expr + args_var string + // + is_vweb bool + vweb_tmpl File + // + is_embed bool + embed_file EmbeddedFile + // + is_env bool + env_pos token.Position + // + is_pkgconfig bool +pub mut: + sym TypeSymbol + result_type Type + env_value string + args []CallArg +} + +pub struct None { +pub: + pos token.Position +} + +pub enum SqlStmtKind { + insert + update + delete + create + drop +} + +pub struct SqlStmt { +pub: + pos token.Position + db_expr Expr // `db` in `sql db {` +pub mut: + lines []SqlStmtLine +} + +pub struct SqlStmtLine { +pub: + kind SqlStmtKind + pos token.Position + where_expr Expr + update_exprs []Expr // for `update` +pub mut: + object_var_name string // `user` + updated_columns []string // for `update set x=y` + table_expr TypeNode + fields []StructField + sub_structs map[int]SqlStmtLine +} + +pub struct SqlExpr { +pub: + typ Type + is_count bool + db_expr Expr // `db` in `sql db {` + has_where bool + has_offset bool + offset_expr Expr + has_order bool + order_expr Expr + has_desc bool + is_array bool + pos token.Position + has_limit bool + limit_expr Expr +pub mut: + where_expr Expr + table_expr TypeNode + fields []StructField + sub_structs map[int]SqlExpr +} + +pub struct NodeError { +pub: + idx int // index for referencing the related File error + pos token.Position +} + +[inline] +pub fn (expr Expr) is_blank_ident() bool { + match expr { + Ident { return expr.kind == .blank_ident } + else { return false } + } +} + +pub fn (expr Expr) position() token.Position { + // all uncommented have to be implemented + // NB: please do not print here. the language server will hang + // as it uses STDIO primarly to communicate ~Ned + match expr { + AnonFn { + return expr.decl.pos + } + CTempVar, EmptyExpr { + // println('compiler bug, unhandled EmptyExpr position()') + return token.Position{} + } + NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, + CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, + EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely, + LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, + RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, + StructInit, TypeNode, TypeOf, UnsafeExpr { + return expr.pos + } + IndexExpr { + if expr.or_expr.kind != .absent { + return expr.or_expr.pos + } + return expr.pos + } + IfGuardExpr { + return expr.expr.position() + } + InfixExpr { + left_pos := expr.left.position() + right_pos := expr.right.position() + return token.Position{ + line_nr: expr.pos.line_nr + pos: left_pos.pos + len: right_pos.pos - left_pos.pos + right_pos.len + col: left_pos.col + last_line: right_pos.last_line + } + } + // Please, do NOT use else{} here. + // This match is exhaustive *on purpose*, to help force + // maintaining/implementing proper .pos fields. + } +} + +pub fn (expr Expr) is_lvalue() bool { + match expr { + Ident { return true } + CTempVar { return true } + IndexExpr { return expr.left.is_lvalue() } + SelectorExpr { return expr.expr.is_lvalue() } + ParExpr { return expr.expr.is_lvalue() } // for var := &{...(*pointer_var)} + PrefixExpr { return expr.right.is_lvalue() } + else {} + } + return false +} + +pub fn (expr Expr) is_expr() bool { + match expr { + IfExpr { return expr.is_expr } + LockExpr { return expr.is_expr } + MatchExpr { return expr.is_expr } + SelectExpr { return expr.is_expr } + else {} + } + return true +} + +pub fn (expr Expr) is_lit() bool { + return match expr { + BoolLiteral, CharLiteral, StringLiteral, IntegerLiteral { true } + else { false } + } +} + +pub fn (expr Expr) is_auto_deref_var() bool { + match expr { + Ident { + if expr.obj is Var { + if expr.obj.is_auto_deref { + return true + } + } + } + PrefixExpr { + if expr.op == .amp && expr.right.is_auto_deref_var() { + return true + } + } + else {} + } + return false +} + +// returns if an expression can be used in `lock x, y.z {` +pub fn (e &Expr) is_lockable() bool { + match e { + Ident { + return true + } + SelectorExpr { + return e.expr.is_lockable() + } + else { + return false + } + } +} + +// check if stmt can be an expression in C +pub fn (stmt Stmt) check_c_expr() ? { + match stmt { + AssignStmt { + return + } + ExprStmt { + if stmt.expr.is_expr() { + return + } + return error('unsupported statement (`$stmt.expr.type_name()`)') + } + else {} + } + return error('unsupported statement (`$stmt.type_name()`)') +} + +// CTempVar is used in cgen only, to hold nodes for temporary variables +pub struct CTempVar { +pub: + name string // the name of the C temporary variable; used by g.expr(x) + orig Expr // the original expression, which produced the C temp variable; used by x.str() + typ Type // the type of the original expression + is_ptr bool // whether the type is a pointer +} + +pub fn (node Node) position() token.Position { + match node { + NodeError { + return token.Position{} + } + EmptyNode { + return token.Position{} + } + Stmt { + mut pos := node.pos + if node is Import { + for sym in node.syms { + pos = pos.extend(sym.pos) + } + } else if node is TypeDecl { + match node { + FnTypeDecl, AliasTypeDecl { + pos = pos.extend(node.type_pos) + } + SumTypeDecl { + for variant in node.variants { + pos = pos.extend(variant.pos) + } + } + } + } + if node is AssignStmt { + return pos.extend(node.right.last().position()) + } + if node is AssertStmt { + return pos.extend(node.expr.position()) + } + return pos + } + Expr { + return node.position() + } + StructField { + return node.pos.extend(node.type_pos) + } + MatchBranch, SelectBranch, EnumField, ConstField, StructInitField, GlobalField, CallArg { + return node.pos + } + Param { + return node.pos.extend(node.type_pos) + } + IfBranch { + return node.pos.extend(node.body_pos) + } + ScopeObject { + match node { + ConstField, GlobalField, Var { + return node.pos + } + AsmRegister { + return token.Position{ + len: -1 + line_nr: -1 + pos: -1 + last_line: -1 + col: -1 + } + } + } + } + File { + mut pos := token.Position{} + if node.stmts.len > 0 { + first_pos := node.stmts.first().pos + last_pos := node.stmts.last().pos + pos = first_pos.extend_with_last_line(last_pos, last_pos.line_nr) + } + return pos + } + } +} + +pub fn (node Node) children() []Node { + mut children := []Node{} + if node is Expr { + match node { + StringInterLiteral, Assoc, ArrayInit { + return node.exprs.map(Node(it)) + } + SelectorExpr, PostfixExpr, UnsafeExpr, AsCast, ParExpr, IfGuardExpr, SizeOf, Likely, + TypeOf, ArrayDecompose { + children << node.expr + } + LockExpr, OrExpr { + return node.stmts.map(Node(it)) + } + StructInit { + return node.fields.map(Node(it)) + } + AnonFn { + children << Stmt(node.decl) + } + CallExpr { + children << node.left + children << node.args.map(Node(it)) + children << Expr(node.or_block) + } + InfixExpr { + children << node.left + children << node.right + } + PrefixExpr { + children << node.right + } + IndexExpr { + children << node.left + children << node.index + } + IfExpr { + children << node.left + children << node.branches.map(Node(it)) + } + MatchExpr { + children << node.cond + children << node.branches.map(Node(it)) + } + SelectExpr { + return node.branches.map(Node(it)) + } + ChanInit { + children << node.cap_expr + } + MapInit { + children << node.keys.map(Node(it)) + children << node.vals.map(Node(it)) + } + RangeExpr { + children << node.low + children << node.high + } + CastExpr { + children << node.expr + children << node.arg + } + ConcatExpr { + return node.vals.map(Node(it)) + } + ComptimeCall, ComptimeSelector { + children << node.left + } + else {} + } + } else if node is Stmt { + match node { + Block, DeferStmt, ForCStmt, ForInStmt, ForStmt, CompFor { + return node.stmts.map(Node(it)) + } + ExprStmt, AssertStmt { + children << node.expr + } + InterfaceDecl { + children << node.methods.map(Node(Stmt(it))) + children << node.fields.map(Node(it)) + } + AssignStmt { + children << node.left.map(Node(it)) + children << node.right.map(Node(it)) + } + Return { + return node.exprs.map(Node(it)) + } + // NB: these four decl nodes cannot be merged as one branch + StructDecl { + return node.fields.map(Node(it)) + } + GlobalDecl { + return node.fields.map(Node(it)) + } + ConstDecl { + return node.fields.map(Node(it)) + } + EnumDecl { + return node.fields.map(Node(it)) + } + FnDecl { + if node.is_method { + children << Node(node.receiver) + } + children << node.params.map(Node(it)) + children << node.stmts.map(Node(it)) + } + TypeDecl { + if node is SumTypeDecl { + children << node.variants.map(Node(Expr(it))) + } + } + else {} + } + } else if node is ScopeObject { + match node { + GlobalField, ConstField, Var { children << node.expr } + AsmRegister {} + } + } else { + match node { + GlobalField, ConstField, EnumField, StructInitField, CallArg { + children << node.expr + } + SelectBranch { + children << node.stmt + children << node.stmts.map(Node(it)) + } + IfBranch, File { + return node.stmts.map(Node(it)) + } + MatchBranch { + children << node.stmts.map(Node(it)) + children << node.exprs.map(Node(it)) + } + else {} + } + } + return children +} + +// helper for dealing with `m[k1][k2][k3][k3] = value` +pub fn (mut lx IndexExpr) recursive_mapset_is_setter(val bool) { + lx.is_setter = val + if mut lx.left is IndexExpr { + if lx.left.is_map { + lx.left.recursive_mapset_is_setter(val) + } + } +} + +// return all the registers for the given architecture +pub fn all_registers(mut t Table, arch pref.Arch) map[string]ScopeObject { + mut res := map[string]ScopeObject{} + match arch { + .amd64, .i386 { + for bit_size, array in ast.x86_no_number_register_list { + for name in array { + res[name] = AsmRegister{ + name: name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + for bit_size, array in ast.x86_with_number_register_list { + for name, max_num in array { + for i in 0 .. max_num { + hash_index := name.index('#') or { + panic('all_registers: no hashtag found') + } + assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}' + res[assembled_name] = AsmRegister{ + name: assembled_name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + } + } + .arm32 { + arm32 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list, + 32) + for k, v in arm32 { + res[k] = v + } + } + .arm64 { + arm64 := gen_all_registers(mut t, ast.arm_no_number_register_list, ast.arm_with_number_register_list, + 64) + for k, v in arm64 { + res[k] = v + } + } + .rv32 { + rv32 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list, + 32) + for k, v in rv32 { + res[k] = v + } + } + .rv64 { + rv64 := gen_all_registers(mut t, ast.riscv_no_number_register_list, ast.riscv_with_number_register_list, + 64) + for k, v in rv64 { + res[k] = v + } + } + else { // TODO + panic('all_registers: unhandled arch') + } + } + + return res +} + +// only for arm and riscv because x86 has different sized registers +fn gen_all_registers(mut t Table, without_numbers []string, with_numbers map[string]int, bit_size int) map[string]ScopeObject { + mut res := map[string]ScopeObject{} + for name in without_numbers { + res[name] = AsmRegister{ + name: name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + for name, max_num in with_numbers { + for i in 0 .. max_num { + hash_index := name.index('#') or { panic('all_registers: no hashtag found') } + assembled_name := '${name[..hash_index]}$i${name[hash_index + 1..]}' + res[assembled_name] = AsmRegister{ + name: assembled_name + typ: t.bitsize_to_type(bit_size) + size: bit_size + } + } + } + return res +} + +// is `expr` a literal, i.e. it does not depend on any other declarations (C compile time constant) +pub fn (expr Expr) is_literal() bool { + match expr { + BoolLiteral, CharLiteral, FloatLiteral, IntegerLiteral { + return true + } + PrefixExpr { + return expr.right.is_literal() + } + InfixExpr { + return expr.left.is_literal() && expr.right.is_literal() + } + ParExpr { + return expr.expr.is_literal() + } + CastExpr { + return !expr.has_arg && expr.expr.is_literal() + && (expr.typ.is_ptr() || expr.typ.is_pointer() + || expr.typ in [i8_type, i16_type, int_type, i64_type, byte_type, u8_type, u16_type, u32_type, u64_type, f32_type, f64_type, char_type, bool_type, rune_type]) + } + SizeOf, IsRefType { + return expr.is_type || expr.expr.is_literal() + } + else { + return false + } + } +} diff --git a/v_windows/v/vlib/v/ast/attr.v b/v_windows/v/vlib/v/ast/attr.v new file mode 100644 index 0000000..f91cdb8 --- /dev/null +++ b/v_windows/v/vlib/v/ast/attr.v @@ -0,0 +1,62 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +import v.token + +pub enum AttrKind { + plain // [name] + string // ['name'] + number // [123] + comptime_define // [if name] +} + +// e.g. `[unsafe]` +pub struct Attr { +pub: + name string // [name] + has_arg bool + arg string // [name: arg] + kind AttrKind + ct_expr Expr // .kind == comptime_define, for [if !name] + ct_opt bool // true for [if user_defined_name?] + pos token.Position +pub mut: + ct_evaled bool // whether ct_skip has been evaluated already + ct_skip bool // is the comptime expr *false*, filled by checker +} + +pub fn (a Attr) debug() string { + return 'Attr{ name: "$a.name", has_arg: $a.has_arg, arg: "$a.arg", kind: $a.kind, ct_expr: $a.ct_expr, ct_opt: $a.ct_opt, ct_skip: $a.ct_skip}' +} + +// str returns the string representation without square brackets +pub fn (a Attr) str() string { + mut s := '' + mut arg := if a.has_arg { + s += '$a.name: ' + a.arg + } else { + a.name + } + s += match a.kind { + .plain, .number { arg } + .string { "'$arg'" } + .comptime_define { 'if $arg' } + } + return s +} + +pub fn (attrs []Attr) contains(str string) bool { + return attrs.any(it.name == str) +} + +pub fn (attrs []Attr) find_comptime_define() ?int { + for idx in 0 .. attrs.len { + if attrs[idx].kind == .comptime_define { + return idx + } + } + return none +} diff --git a/v_windows/v/vlib/v/ast/cflags.v b/v_windows/v/vlib/v/ast/cflags.v new file mode 100644 index 0000000..77b1555 --- /dev/null +++ b/v_windows/v/vlib/v/ast/cflags.v @@ -0,0 +1,89 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +import v.cflag + +// check if cflag is in table +fn (t &Table) has_cflag(flag cflag.CFlag) bool { + for cf in t.cflags { + if cf.os == flag.os && cf.name == flag.name && cf.value == flag.value { + return true + } + } + return false +} + +// parse the flags to (ast.cflags) []CFlag +// Note: clean up big time (joe-c) +pub fn (mut t Table) parse_cflag(cflg string, mod string, ctimedefines []string) ?bool { + allowed_flags := ['framework', 'library', 'Wa', 'Wl', 'Wp', 'I', 'l', 'L'] + flag_orig := cflg.trim_space() + mut flag := flag_orig + if flag == '' { + return none + } + mut fos := '' + mut allowed_os_overrides := ['linux', 'darwin', 'freebsd', 'windows', 'mingw', 'solaris'] + allowed_os_overrides << ctimedefines + for os_override in allowed_os_overrides { + if !flag.starts_with(os_override) { + continue + } + pos := flag.index(' ') or { return none } + fos = flag[..pos].trim_space() + flag = flag[pos..].trim_space() + } + for { + mut name := '' + mut value := '' + if flag[0] == `-` { + for f in allowed_flags { + i := 1 + f.len + if i <= flag.len && f == flag[1..i] { + name = flag[..i].trim_space() + flag = flag[i..].trim_space() + break + } + } + } + mut index := flag.index(' -') or { -1 } + for index > -1 { + mut has_next := false + for f in allowed_flags { + i := index + 2 + f.len + if i <= flag.len && f == flag[index + 2..i] { + value = flag[..index + 1].trim_space() + flag = flag[index + 1..].trim_space() + has_next = true + break + } + } + if has_next { + break + } + index = flag.index_after(' -', index + 1) + } + if index == -1 { + value = flag.trim_space() + } + if (name in ['-I', '-l', '-L']) && value == '' { + hint := if name == '-l' { 'library name' } else { 'path' } + return error('bad #flag `$flag_orig`: missing $hint after `$name`') + } + cf := cflag.CFlag{ + mod: mod + os: fos + name: name + value: value + } + if !t.has_cflag(cf) { + t.cflags << cf + } + if index == -1 { + break + } + } + return true +} diff --git a/v_windows/v/vlib/v/ast/cflags_test.v b/v_windows/v/vlib/v/ast/cflags_test.v new file mode 100644 index 0000000..a64fe8c --- /dev/null +++ b/v_windows/v/vlib/v/ast/cflags_test.v @@ -0,0 +1,72 @@ +import v.ast +import v.cflag + +const ( + module_name = 'main' + cdefines = []string{} + no_name = '' + no_flag = '' + no_os = '' +) + +fn test_parse_valid_cflags() { + mut t := ast.new_table() + expected_flags := [ + make_flag('freebsd', '-I', '/usr/local/include/freetype2'), + make_flag('linux', '-l', 'glfw'), + make_flag('mingw', no_name, '-mwindows'), + make_flag('solaris', '-L', '/opt/local/lib'), + make_flag('darwin', '-framework', 'Cocoa'), + make_flag('windows', '-l', 'gdi32'), + make_flag(no_os, '-l', 'mysqlclient'), + make_flag(no_os, no_name, '-test'), + ] + parse_valid_flag(mut t, '-lmysqlclient') + parse_valid_flag(mut t, '-test') + parse_valid_flag(mut t, 'darwin -framework Cocoa') + parse_valid_flag(mut t, 'freebsd -I/usr/local/include/freetype2') + parse_valid_flag(mut t, 'linux -lglfw') + parse_valid_flag(mut t, 'mingw -mwindows') + parse_valid_flag(mut t, 'solaris -L/opt/local/lib') + parse_valid_flag(mut t, 'windows -lgdi32') + assert t.cflags.len == expected_flags.len + for f in expected_flags { + assert t.has_cflag(f) + } +} + +fn test_parse_invalid_cflags() { + mut t := ast.new_table() + // -I, -L, -l must have values + assert_parse_invalid_flag(mut t, 'windows -l') + assert_parse_invalid_flag(mut t, '-I') + assert_parse_invalid_flag(mut t, '-L') + // OS/compiler name only is not allowed + assert_parse_invalid_flag(mut t, 'darwin') + assert_parse_invalid_flag(mut t, 'freebsd') + assert_parse_invalid_flag(mut t, 'linux') + assert_parse_invalid_flag(mut t, 'mingw') + assert_parse_invalid_flag(mut t, 'solaris') + assert_parse_invalid_flag(mut t, 'windows') + // Empty flag is not allowed + assert_parse_invalid_flag(mut t, no_flag) + assert t.cflags.len == 0 +} + +fn parse_valid_flag(mut t ast.Table, flag string) { + t.parse_cflag(flag, module_name, cdefines) or {} +} + +fn assert_parse_invalid_flag(mut t ast.Table, flag string) { + t.parse_cflag(flag, module_name, cdefines) or { return } + assert false +} + +fn make_flag(os string, name string, value string) cflag.CFlag { + return cflag.CFlag{ + mod: module_name + os: os + name: name + value: value + } +} diff --git a/v_windows/v/vlib/v/ast/comptime_const_values.v b/v_windows/v/vlib/v/ast/comptime_const_values.v new file mode 100644 index 0000000..6f9ef6a --- /dev/null +++ b/v_windows/v/vlib/v/ast/comptime_const_values.v @@ -0,0 +1,271 @@ +module ast + +pub type ComptTimeConstValue = EmptyExpr | byte | f32 | f64 | i16 | i64 | i8 | int | rune | + string | u16 | u32 | u64 + +pub fn empty_comptime_const_expr() ComptTimeConstValue { + return EmptyExpr{} +} + +pub fn (val ComptTimeConstValue) i8() ?i8 { + x := val.i64() ? + if x > -129 && x < 128 { + return i8(x) + } + return none +} + +pub fn (val ComptTimeConstValue) i16() ?i16 { + x := val.i64() ? + if x > -32769 && x < 32768 { + return i16(x) + } + return none +} + +pub fn (val ComptTimeConstValue) int() ?int { + x := val.i64() ? + if x > -2147483649 && x < 2147483648 { + return int(x) + } + return none +} + +pub fn (val ComptTimeConstValue) i64() ?i64 { + match val { + i8 { + return i64(val) + } + i16 { + return i64(val) + } + int { + return i64(val) + } + i64 { + return i64(val) + } + // + byte { + return i64(val) + } + u16 { + return i64(val) + } + u32 { + return i64(val) + } + u64 { + if val <= 9223372036854775807 { + return i64(val) + } + } + // + f32 { + if -9223372036854775808.0 <= val && val <= 9223372036854775807.0 { + return i64(val) + } + } + f64 { + if -9223372036854775808.0 <= val && val <= 9223372036854775807.0 { + return i64(val) + } + } + // + string { + return val.i64() + } + rune { + return int(val) + } + EmptyExpr {} + } + return none +} + +pub fn (val ComptTimeConstValue) byte() ?byte { + x := val.u64() ? + if x < 256 { + return byte(x) + } + return none +} + +pub fn (val ComptTimeConstValue) u16() ?u16 { + x := val.u64() ? + if x < 65536 { + return u16(x) + } + return none +} + +pub fn (val ComptTimeConstValue) u32() ?u32 { + x := val.u64() ? + if x < 4294967296 { + return u32(x) + } + return none +} + +pub fn (val ComptTimeConstValue) u64() ?u64 { + match val { + i8 { + if val >= 0 { + return u64(val) + } + } + i16 { + if val >= 0 { + return u64(val) + } + } + int { + if val >= 0 { + return u64(val) + } + } + i64 { + if val >= 0 { + return u64(val) + } + } + byte { + return u64(val) + } + u16 { + return u64(val) + } + u32 { + return u64(val) + } + u64 { + return val + } + f32 { + if val <= 18446744073709551615.0 { + return u64(val) + } + } + f64 { + if val <= 18446744073709551615.0 { + return u64(val) + } + } + string { + return val.u64() + } + rune {} + EmptyExpr {} + } + return none +} + +pub fn (val ComptTimeConstValue) f32() ?f32 { + x := val.f64() ? + return f32(x) +} + +pub fn (val ComptTimeConstValue) f64() ?f64 { + match val { + i8 { + return f64(val) + } + i16 { + return f64(val) + } + int { + return f64(val) + } + i64 { + return f64(val) + } + byte { + return f64(val) + } + u16 { + return f64(val) + } + u32 { + return f64(val) + } + u64 { + return f64(val) + } + f32 { + return f64(val) + } + f64 { + return val + } + string { + return val.f64() + } + rune {} + EmptyExpr {} + } + return none +} + +pub fn (val ComptTimeConstValue) string() ?string { + match val { + i8 { + return val.str() + } + i16 { + return val.str() + } + int { + return val.str() + } + i64 { + return val.str() + } + byte { + return val.str() + } + u16 { + return val.str() + } + u32 { + return val.str() + } + u64 { + return val.str() + } + f32 { + return val.str() + } + f64 { + return val.str() + } + rune { + return val.str() + } + string { + return val + } + EmptyExpr {} + } + return none +} + +pub fn (obj ConstField) comptime_expr_value() ?ComptTimeConstValue { + if obj.comptime_expr_value !is EmptyExpr { + return obj.comptime_expr_value + } + return none +} + +pub fn (obj ConstField) is_simple_define_const() bool { + return match obj.expr { + CharLiteral, FloatLiteral, IntegerLiteral { true } + else { false } + } +} + +pub fn (obj ScopeObject) is_simple_define_const() bool { + if obj is ConstField { + return obj.is_simple_define_const() + } + return false +} diff --git a/v_windows/v/vlib/v/ast/init.v b/v_windows/v/vlib/v/ast/init.v new file mode 100644 index 0000000..4bda1ac --- /dev/null +++ b/v_windows/v/vlib/v/ast/init.v @@ -0,0 +1,70 @@ +module ast + +pub fn resolve_init(node StructInit, typ Type, t &Table) Expr { + type_sym := t.get_type_symbol(typ) + if type_sym.kind == .array { + array_info := type_sym.info as Array + mut has_len := false + mut has_cap := false + mut has_default := false + mut len_expr := empty_expr() + mut cap_expr := empty_expr() + mut default_expr := empty_expr() + mut exprs := []Expr{} + for field in node.fields { + match field.name { + 'len' { + has_len = true + len_expr = field.expr + } + 'cap' { + has_cap = true + len_expr = field.expr + } + 'default' { + has_default = true + len_expr = field.expr + } + else { + exprs << field.expr + } + } + } + return ArrayInit{ + // TODO: mod is not being set for now, we could need this in future + // mod: mod + pos: node.pos + typ: typ + elem_type: array_info.elem_type + has_len: has_len + has_cap: has_cap + has_default: has_default + len_expr: len_expr + cap_expr: cap_expr + default_expr: default_expr + exprs: exprs + } + } else if type_sym.kind == .map { + map_info := type_sym.info as Map + mut keys := []Expr{} + mut vals := []Expr{} + for field in node.fields { + keys << StringLiteral{ + val: field.name + } + vals << field.expr + } + return MapInit{ + typ: typ + key_type: map_info.key_type + value_type: map_info.value_type + keys: keys + vals: vals + } + } + // struct / other (sumtype?) + return StructInit{ + ...node + unresolved: false + } +} diff --git a/v_windows/v/vlib/v/ast/scope.v b/v_windows/v/vlib/v/ast/scope.v new file mode 100644 index 0000000..cc8dd2d --- /dev/null +++ b/v_windows/v/vlib/v/ast/scope.v @@ -0,0 +1,204 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +[heap] +pub struct Scope { +pub mut: + // mut: + objects map[string]ScopeObject + struct_fields map[string]ScopeStructField + parent &Scope + detached_from_parent bool + children []&Scope + start_pos int + end_pos int +} + +[unsafe] +pub fn (s &Scope) free() { + unsafe { + s.objects.free() + s.struct_fields.free() + for child in s.children { + child.free() + } + s.children.free() + } +} + +/* +pub fn new_scope(parent &Scope, start_pos int) &Scope { + return &Scope{ + parent: parent + start_pos: start_pos + } +} +*/ + +fn (s &Scope) dont_lookup_parent() bool { + return isnil(s.parent) || s.detached_from_parent +} + +pub fn (s &Scope) find(name string) ?ScopeObject { + for sc := s; true; sc = sc.parent { + if name in sc.objects { + return sc.objects[name] + } + if sc.dont_lookup_parent() { + break + } + } + return none +} + +// selector_expr: name.field_name +pub fn (s &Scope) find_struct_field(name string, struct_type Type, field_name string) ?ScopeStructField { + for sc := s; true; sc = sc.parent { + if field := sc.struct_fields[name] { + if field.struct_type == struct_type && field.name == field_name { + return field + } + } + if sc.dont_lookup_parent() { + break + } + } + return none +} + +pub fn (s &Scope) find_var(name string) ?&Var { + if obj := s.find(name) { + match obj { + Var { return &obj } + else {} + } + } + return none +} + +pub fn (s &Scope) find_global(name string) ?&GlobalField { + if obj := s.find(name) { + match obj { + GlobalField { return &obj } + else {} + } + } + return none +} + +pub fn (s &Scope) find_const(name string) ?&ConstField { + if obj := s.find(name) { + match obj { + ConstField { return &obj } + else {} + } + } + return none +} + +pub fn (s &Scope) known_var(name string) bool { + s.find_var(name) or { return false } + return true +} + +pub fn (mut s Scope) update_var_type(name string, typ Type) { + mut obj := s.objects[name] + if mut obj is Var { + if obj.typ != typ { + obj.typ = typ + } + } +} + +// selector_expr: name.field_name +pub fn (mut s Scope) register_struct_field(name string, field ScopeStructField) { + if f := s.struct_fields[name] { + if f.struct_type == field.struct_type && f.name == field.name { + return + } + } + s.struct_fields[name] = field +} + +pub fn (mut s Scope) register(obj ScopeObject) { + if obj.name == '_' || obj.name in s.objects { + return + } + s.objects[obj.name] = obj +} + +// returns the innermost scope containing pos +// pub fn (s &Scope) innermost(pos int) ?&Scope { +pub fn (s &Scope) innermost(pos int) &Scope { + if s.contains(pos) { + // binary search + mut first := 0 + mut last := s.children.len - 1 + mut middle := last / 2 + for first <= last { + // println('FIRST: $first, LAST: $last, LEN: $s.children.len-1') + s1 := s.children[middle] + if s1.end_pos < pos { + first = middle + 1 + } else if s1.contains(pos) { + return s1.innermost(pos) + } else { + last = middle - 1 + } + middle = (first + last) / 2 + if first > last { + break + } + } + return s + } + // return none + return s +} + +[inline] +pub fn (s &Scope) contains(pos int) bool { + return pos >= s.start_pos && pos <= s.end_pos +} + +pub fn (s &Scope) has_inherited_vars() bool { + for _, obj in s.objects { + if obj is Var { + if obj.is_inherited { + return true + } + } + } + return false +} + +pub fn (sc Scope) show(depth int, max_depth int) string { + mut out := '' + mut indent := '' + for _ in 0 .. depth * 4 { + indent += ' ' + } + out += '$indent# $sc.start_pos - $sc.end_pos\n' + for _, obj in sc.objects { + match obj { + ConstField { out += '$indent * const: $obj.name - $obj.typ\n' } + Var { out += '$indent * var: $obj.name - $obj.typ\n' } + else {} + } + } + for _, field in sc.struct_fields { + out += '$indent * struct_field: $field.struct_type $field.name - $field.typ\n' + } + if max_depth == 0 || depth < max_depth - 1 { + for i, _ in sc.children { + out += sc.children[i].show(depth + 1, max_depth) + } + } + return out +} + +pub fn (sc Scope) str() string { + return sc.show(0, 0) +} diff --git a/v_windows/v/vlib/v/ast/str.v b/v_windows/v/vlib/v/ast/str.v new file mode 100644 index 0000000..b0a1664 --- /dev/null +++ b/v_windows/v/vlib/v/ast/str.v @@ -0,0 +1,588 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +import v.util +import strings + +pub fn (node &FnDecl) modname() string { + if node.mod != '' { + return node.mod + } + mut pamod := node.name.all_before_last('.') + if pamod == node.name.after('.') { + pamod = if node.is_builtin { 'builtin' } else { 'main' } + } + return pamod +} + +// fkey returns a unique name of the function/method. +// it is used in table.used_fns and v.markused. +pub fn (node &FnDecl) fkey() string { + if node.is_method { + return '${int(node.receiver.typ)}.$node.name' + } + return node.name +} + +pub fn (node &CallExpr) fkey() string { + if node.is_method { + return '${int(node.receiver_type)}.$node.name' + } + return node.name +} + +// These methods are used only by vfmt, vdoc, and for debugging. +pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) string { + mut f := strings.new_builder(30) + f.write_string('fn ') + if node.inherited_vars.len > 0 { + f.write_string('[') + for i, var in node.inherited_vars { + if i > 0 { + f.write_string(', ') + } + if var.is_mut { + f.write_string('mut ') + } + f.write_string(var.name) + } + f.write_string('] ') + } + stringify_fn_after_name(node.decl, mut f, t, cur_mod, m2a) + return f.str() +} + +pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string { + mut f := strings.new_builder(30) + if node.is_pub { + f.write_string('pub ') + } + f.write_string('fn ') + if node.is_method { + f.write_string('(') + mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)), + cur_mod) + if node.rec_mut { + f.write_string(node.receiver.typ.share().str() + ' ') + styp = styp[1..] // remove & + } + f.write_string(node.receiver.name + ' ') + styp = util.no_cur_mod(styp, cur_mod) + if node.params[0].is_auto_rec { + styp = styp.trim('&') + } + f.write_string(styp + ') ') + } + name := if !node.is_method && node.language == .v { + node.name.all_after_last('.') + } else { + node.name + } + f.write_string(name) + if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { + f.write_string(' ') + } + stringify_fn_after_name(node, mut f, t, cur_mod, m2a) + return f.str() +} + +fn stringify_fn_after_name(node &FnDecl, mut f strings.Builder, t &Table, cur_mod string, m2a map[string]string) { + mut add_para_types := true + if node.generic_names.len > 0 { + if node.is_method { + sym := t.get_type_symbol(node.params[0].typ) + if sym.info is Struct { + generic_names := sym.info.generic_types.map(t.get_type_symbol(it).name) + if generic_names == node.generic_names { + add_para_types = false + } + } + } + if add_para_types { + f.write_string('<') + for i, gname in node.generic_names { + is_last := i == node.generic_names.len - 1 + f.write_string(gname) + if !is_last { + f.write_string(', ') + } + } + f.write_string('>') + } + } + f.write_string('(') + for i, arg in node.params { + // skip receiver + // if (node.is_method || node.is_interface) && i == 0 { + if node.is_method && i == 0 { + continue + } + if arg.is_hidden { + continue + } + is_last_arg := i == node.params.len - 1 + is_type_only := arg.name == '' + should_add_type := true // is_last_arg || is_type_only || node.params[i + 1].typ != arg.typ || + // (node.is_variadic && i == node.params.len - 2) + if arg.is_mut { + f.write_string(arg.typ.share().str() + ' ') + } + f.write_string(arg.name) + mut s := t.type_to_str(arg.typ.clear_flag(.shared_f)) + if arg.is_mut { + // f.write_string(' mut') + if s.starts_with('&') { + s = s[1..] + } + } + s = util.no_cur_mod(s, cur_mod) + for mod, alias in m2a { + s = s.replace(mod, alias) + } + if should_add_type { + if !is_type_only { + f.write_string(' ') + } + if node.is_variadic && is_last_arg { + f.write_string('...') + } + f.write_string(s) + } + if !is_last_arg { + f.write_string(', ') + } + } + f.write_string(')') + if node.return_type != void_type { + mut rs := util.no_cur_mod(t.type_to_str(node.return_type), cur_mod) + for mod, alias in m2a { + rs = rs.replace(mod, alias) + } + f.write_string(' ' + rs) + } +} + +// Expressions in string interpolations may have to be put in braces if they +// are non-trivial, if they would interfere with the next character or if a +// format specification is given. In the latter case +// the format specifier must be appended, separated by a colon: +// '$z $z.b $z.c.x ${x[4]} ${z:8.3f} ${a:-20} ${a>b+2}' +// This method creates the format specifier (including the colon) or an empty +// string if none is needed and also returns (as bool) if the expression +// must be enclosed in braces. +pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) { + mut res := []string{} + needs_fspec := lit.need_fmts[i] || lit.pluss[i] + || (lit.fills[i] && lit.fwidths[i] >= 0) || lit.fwidths[i] != 0 + || lit.precisions[i] != 987698 + mut needs_braces := needs_fspec + sx := lit.exprs[i].str() + if sx.contains(r'"') || sx.contains(r"'") { + needs_braces = true + } + if !needs_braces { + if i + 1 < lit.vals.len && lit.vals[i + 1].len > 0 { + next_char := lit.vals[i + 1][0] + if util.is_func_char(next_char) || next_char == `.` || next_char == `(` { + needs_braces = true + } + } + } + if !needs_braces { + mut sub_expr := lit.exprs[i] + for { + match mut sub_expr { + Ident { + if sub_expr.name[0] == `@` { + needs_braces = true + } + break + } + CallExpr { + if sub_expr.args.len != 0 { + needs_braces = true + } else if sub_expr.left is CallExpr { + sub_expr = sub_expr.left + continue + } else if sub_expr.left is CastExpr || sub_expr.left is IndexExpr { + needs_braces = true + } + break + } + SelectorExpr { + if sub_expr.field_name[0] == `@` { + needs_braces = true + break + } + sub_expr = sub_expr.expr + continue + } + else { + needs_braces = true + break + } + } + } + } + if needs_fspec { + res << ':' + if lit.pluss[i] { + res << '+' + } + if lit.fills[i] && lit.fwidths[i] >= 0 { + res << '0' + } + if lit.fwidths[i] != 0 { + res << '${lit.fwidths[i]}' + } + if lit.precisions[i] != 987698 { + res << '.${lit.precisions[i]}' + } + if lit.need_fmts[i] { + res << '${lit.fmts[i]:c}' + } + } + return res.join(''), needs_braces +} + +// string representation of expr +pub fn (x Expr) str() string { + match x { + AnonFn { + return 'anon_fn' + } + DumpExpr { + return 'dump($x.expr.str())' + } + ArrayInit { + mut fields := []string{} + if x.has_len { + fields << 'len: $x.len_expr.str()' + } + if x.has_cap { + fields << 'cap: $x.cap_expr.str()' + } + if x.has_default { + fields << 'init: $x.default_expr.str()' + } + if fields.len > 0 { + return '[]T{${fields.join(', ')}}' + } else { + return x.exprs.str() + } + } + AsCast { + return '$x.expr.str() as ${global_table.type_to_str(x.typ)}' + } + AtExpr { + return '$x.val' + } + CTempVar { + return x.orig.str() + } + BoolLiteral { + return x.val.str() + } + CastExpr { + return '${x.typname}($x.expr.str())' + } + CallExpr { + sargs := args2str(x.args) + propagate_suffix := if x.or_block.kind == .propagate { ' ?' } else { '' } + if x.is_method { + return '${x.left.str()}.${x.name}($sargs)$propagate_suffix' + } + if x.name.starts_with('${x.mod}.') { + return util.strip_main_name('${x.name}($sargs)$propagate_suffix') + } + if x.mod == '' && x.name == '' { + return x.left.str() + '($sargs)$propagate_suffix' + } + return '${x.mod}.${x.name}($sargs)$propagate_suffix' + } + CharLiteral { + return '`$x.val`' + } + Comment { + if x.is_multi { + lines := x.text.split_into_lines() + return '/* $lines.len lines comment */' + } else { + text := x.text.trim('\x01').trim_space() + return '´// $text´' + } + } + ComptimeSelector { + return '${x.left}.$$x.field_expr' + } + ConcatExpr { + return x.vals.map(it.str()).join(',') + } + EnumVal { + return '.$x.val' + } + FloatLiteral, IntegerLiteral { + return x.val + } + GoExpr { + return 'go $x.call_expr' + } + Ident { + return x.name + } + IfExpr { + mut parts := []string{} + dollar := if x.is_comptime { '$' } else { '' } + for i, branch in x.branches { + if i != 0 { + parts << ' } ${dollar}else ' + } + if i < x.branches.len - 1 || !x.has_else { + parts << ' ${dollar}if ' + branch.cond.str() + ' { ' + } + for stmt in branch.stmts { + parts << stmt.str() + } + } + parts << ' }' + return parts.join('') + } + IndexExpr { + return '$x.left.str()[$x.index.str()]' + } + InfixExpr { + return '$x.left.str() $x.op.str() $x.right.str()' + } + MapInit { + mut pairs := []string{} + for ik, kv in x.keys { + mv := x.vals[ik].str() + pairs << '$kv: $mv' + } + return 'map{ ${pairs.join(' ')} }' + } + ParExpr { + return '($x.expr)' + } + PostfixExpr { + if x.op == .question { + return '$x.expr ?' + } + return '$x.expr$x.op' + } + PrefixExpr { + return x.op.str() + x.right.str() + } + RangeExpr { + mut s := '..' + if x.has_low { + s = '$x.low ' + s + } + if x.has_high { + s = s + ' $x.high' + } + return s + } + SelectExpr { + return 'ast.SelectExpr' + } + SelectorExpr { + return '${x.expr.str()}.$x.field_name' + } + SizeOf { + if x.is_type { + return 'sizeof(${global_table.type_to_str(x.typ)})' + } + return 'sizeof($x.expr)' + } + OffsetOf { + return '__offsetof(${global_table.type_to_str(x.struct_type)}, $x.field)' + } + StringInterLiteral { + mut res := strings.new_builder(50) + res.write_string("'") + for i, val in x.vals { + res.write_string(val) + if i >= x.exprs.len { + break + } + res.write_string('$') + fspec_str, needs_braces := x.get_fspec_braces(i) + if needs_braces { + res.write_string('{') + res.write_string(x.exprs[i].str()) + res.write_string(fspec_str) + res.write_string('}') + } else { + res.write_string(x.exprs[i].str()) + } + } + res.write_string("'") + return res.str() + } + StringLiteral { + return "'$x.val'" + } + TypeNode { + return 'TypeNode($x.typ)' + } + TypeOf { + return 'typeof($x.expr.str())' + } + Likely { + return '_likely_($x.expr.str())' + } + UnsafeExpr { + return 'unsafe { $x.expr }' + } + None { + return 'none' + } + IsRefType { + return 'isreftype(' + if x.is_type { + global_table.type_to_str(x.typ) + } else { + x.expr.str() + } + ')' + } + IfGuardExpr { + return x.var_name + ' := ' + x.expr.str() + } + StructInit { + sname := global_table.get_type_symbol(x.typ).name + return '$sname{....}' + } + ArrayDecompose { + return 'ast.ArrayDecompose' + } + Assoc { + return 'ast.Assoc' + } + ChanInit { + return 'ast.ChanInit' + } + ComptimeCall { + return 'ast.ComptimeCall' + } + EmptyExpr { + return 'ast.EmptyExpr' + } + LockExpr { + return 'ast.LockExpr' + } + MatchExpr { + return 'ast.MatchExpr' + } + NodeError { + return 'ast.NodeError' + } + OrExpr { + return 'ast.OrExpr' + } + SqlExpr { + return 'ast.SqlExpr' + } + } + return '[unhandled expr type $x.type_name()]' +} + +pub fn (a CallArg) str() string { + if a.is_mut { + return 'mut $a.expr.str()' + } + return '$a.expr.str()' +} + +pub fn args2str(args []CallArg) string { + mut res := []string{} + for a in args { + res << a.str() + } + return res.join(', ') +} + +pub fn (node &BranchStmt) str() string { + mut s := '$node.kind' + if node.label.len > 0 { + s += ' $node.label' + } + return s +} + +pub fn (node Stmt) str() string { + match node { + AssertStmt { + return 'assert $node.expr' + } + AssignStmt { + mut out := '' + for i, left in node.left { + if left is Ident { + var_info := left.var_info() + if var_info.is_mut { + out += 'mut ' + } + } + out += left.str() + if i < node.left.len - 1 { + out += ',' + } + } + out += ' $node.op.str() ' + for i, val in node.right { + out += val.str() + if i < node.right.len - 1 { + out += ',' + } + } + return out + } + BranchStmt { + return node.str() + } + ConstDecl { + fields := node.fields.map(field_to_string) + return 'const (${fields.join(' ')})' + } + ExprStmt { + return node.expr.str() + } + FnDecl { + return 'fn ${node.name}( $node.params.len params ) { $node.stmts.len stmts }' + } + EnumDecl { + return 'enum $node.name { $node.fields.len fields }' + } + Module { + return 'module $node.name' + } + Import { + mut out := 'import $node.mod' + if node.alias.len > 0 { + out += ' as $node.alias' + } + return out + } + StructDecl { + return 'struct $node.name { $node.fields.len fields }' + } + else { + return '[unhandled stmt str type: $node.type_name() ]' + } + } +} + +fn field_to_string(f ConstField) string { + x := f.name.trim_prefix(f.mod + '.') + return '$x = $f.expr' +} + +pub fn (e CompForKind) str() string { + match e { + .methods { return 'methods' } + .fields { return 'fields' } + .attributes { return 'attributes' } + } +} diff --git a/v_windows/v/vlib/v/ast/table.v b/v_windows/v/vlib/v/ast/table.v new file mode 100644 index 0000000..51226a0 --- /dev/null +++ b/v_windows/v/vlib/v/ast/table.v @@ -0,0 +1,1463 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ast + +import v.cflag +import v.token +import v.util + +[heap] +pub struct Table { +pub mut: + type_symbols []TypeSymbol + type_idxs map[string]int + fns map[string]Fn + iface_types map[string][]Type + dumps map[int]string // needed for efficiently generating all _v_dump_expr_TNAME() functions + imports []string // List of all imports + modules []string // Topologically sorted list of all modules registered by the application + global_scope &Scope + cflags []cflag.CFlag + redefined_fns []string + fn_generic_types map[string][][]Type // for generic functions + interfaces map[int]InterfaceDecl + cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.` + is_fmt bool + used_fns map[string]bool // filled in by the checker, when pref.skip_unused = true; + used_consts map[string]bool // filled in by the checker, when pref.skip_unused = true; + used_globals map[string]bool // filled in by the checker, when pref.skip_unused = true; + used_vweb_types []Type // vweb context types, filled in by checker, when pref.skip_unused = true; + used_maps int // how many times maps were used, filled in by checker, when pref.skip_unused = true; + panic_handler FnPanicHandler = default_table_panic_handler + panic_userdata voidptr = voidptr(0) // can be used to pass arbitrary data to panic_handler; + panic_npanics int + cur_fn &FnDecl = 0 // previously stored in Checker.cur_fn and Gen.cur_fn + cur_concrete_types []Type // current concrete types, e.g. + gostmts int // how many `go` statements there were in the parsed files. + enum_decls map[string]EnumDecl + // When table.gostmts > 0, __VTHREADS__ is defined, which can be checked with `$if threads {` +} + +// used by vls to avoid leaks +// TODO remove manual memory management +[unsafe] +pub fn (t &Table) free() { + unsafe { + t.type_symbols.free() + t.type_idxs.free() + t.fns.free() + t.dumps.free() + t.imports.free() + t.modules.free() + t.cflags.free() + t.redefined_fns.free() + t.fn_generic_types.free() + t.cmod_prefix.free() + t.used_fns.free() + t.used_consts.free() + t.used_globals.free() + t.used_vweb_types.free() + } +} + +pub type FnPanicHandler = fn (&Table, string) + +fn default_table_panic_handler(t &Table, message string) { + panic(message) +} + +pub fn (t &Table) panic(message string) { + mut mt := unsafe { &Table(t) } + mt.panic_npanics++ + t.panic_handler(t, message) +} + +pub struct Fn { +pub: + is_variadic bool + language Language + is_pub bool + is_deprecated bool // `[deprecated] fn abc(){}` + is_noreturn bool // `[noreturn] fn abc(){}` + is_unsafe bool // `[unsafe] fn abc(){}` + is_placeholder bool + is_main bool // `fn main(){}` + is_test bool // `fn test_abc(){}` + is_keep_alive bool // passed memory must not be freed (by GC) before function returns + no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns. + mod string + file_mode Language + pos token.Position + return_type_pos token.Position +pub mut: + return_type Type + name string + params []Param + source_fn voidptr // set in the checker, while processing fn declarations + usages int + // + generic_names []string + attrs []Attr // all fn attributes + is_conditional bool // true for `[if abc]fn(){}` + ctdefine_idx int // the index of the attribute, containing the compile time define [if mytag] +} + +fn (f &Fn) method_equals(o &Fn) bool { + return f.params[1..].equals(o.params[1..]) && f.return_type == o.return_type + && f.is_variadic == o.is_variadic && f.language == o.language + && f.generic_names == o.generic_names && f.is_pub == o.is_pub && f.mod == o.mod + && f.name == o.name +} + +pub struct Param { +pub: + pos token.Position + name string + is_mut bool + is_auto_rec bool + type_pos token.Position + is_hidden bool // interface first arg +pub mut: + typ Type +} + +pub fn (f Fn) new_method_with_receiver_type(new_type Type) Fn { + mut new_method := f + new_method.params = f.params.clone() + for i in 1 .. new_method.params.len { + if new_method.params[i].typ == new_method.params[0].typ { + new_method.params[i].typ = new_type + } + } + new_method.params[0].typ = new_type + return new_method +} + +pub fn (f FnDecl) new_method_with_receiver_type(new_type Type) FnDecl { + mut new_method := f + new_method.params = f.params.clone() + for i in 1 .. new_method.params.len { + if new_method.params[i].typ == new_method.params[0].typ { + new_method.params[i].typ = new_type + } + } + new_method.params[0].typ = new_type + return new_method +} + +fn (p &Param) equals(o &Param) bool { + return p.name == o.name && p.is_mut == o.is_mut && p.typ == o.typ && p.is_hidden == o.is_hidden +} + +fn (p []Param) equals(o []Param) bool { + if p.len != o.len { + return false + } + for i in 0 .. p.len { + if !p[i].equals(o[i]) { + return false + } + } + return true +} + +/* +pub struct Var { +pub: + name string + is_mut bool +mut: + typ Type +} +*/ + +pub fn new_table() &Table { + mut t := &Table{ + type_symbols: []TypeSymbol{cap: 64000} + global_scope: &Scope{ + parent: 0 + } + cur_fn: 0 + } + t.register_builtin_type_symbols() + t.is_fmt = true + set_global_table(t) + return t +} + +const global_table = &Table(0) + +pub fn set_global_table(t &Table) { + unsafe { + mut pg := &ast.global_table + *pg = t + } +} + +// used to compare fn's & for naming anon fn's +pub fn (t &Table) fn_type_signature(f &Fn) string { + mut sig := '' + for i, arg in f.params { + // TODO: for now ignore mut/pts in sig for now + typ := arg.typ.set_nr_muls(0) + arg_type_sym := t.get_type_symbol(typ) + sig += arg_type_sym.str().to_lower().replace_each(['.', '__', '&', '', '[]', 'arr_', 'chan ', + 'chan_', 'map[', 'map_of_', ']', '_to_', '<', '_T_', ',', '_', ' ', '', '>', '']) + if i < f.params.len - 1 { + sig += '_' + } + } + if f.return_type != 0 && f.return_type != void_type { + sym := t.get_type_symbol(f.return_type) + opt := if f.return_type.has_flag(.optional) { 'option_' } else { '' } + sig += '__$opt$sym.kind' + } + return sig +} + +// source_signature generates the signature of a function which looks like in the V source +pub fn (t &Table) fn_type_source_signature(f &Fn) string { + mut sig := '(' + for i, arg in f.params { + if arg.is_mut { + sig += 'mut ' + } + // NB: arg name is only added for fmt, else it would causes errors with generics + if t.is_fmt && arg.name.len > 0 { + sig += '$arg.name ' + } + arg_type_sym := t.get_type_symbol(arg.typ) + sig += arg_type_sym.name + if i < f.params.len - 1 { + sig += ', ' + } + } + sig += ')' + if f.return_type == ovoid_type { + sig += ' ?' + } else if f.return_type != void_type { + return_type_sym := t.get_type_symbol(f.return_type) + if f.return_type.has_flag(.optional) { + sig += ' ?$return_type_sym.name' + } else { + sig += ' $return_type_sym.name' + } + } + return sig +} + +pub fn (t &Table) is_same_method(f &Fn, func &Fn) string { + if f.return_type != func.return_type { + s := t.type_to_str(f.return_type) + return 'expected return type `$s`' + } + if f.params.len != func.params.len { + return 'expected $f.params.len parameter(s), not $func.params.len' + } + for i in 1 .. f.params.len { + if f.params[i].typ != func.params[i].typ { + exps := t.type_to_str(f.params[i].typ) + gots := t.type_to_str(func.params[i].typ) + return 'expected `$exps`, not `$gots` for parameter $i' + } + } + return '' +} + +pub fn (t &Table) find_fn(name string) ?Fn { + if f := t.fns[name] { + return f + } + return none +} + +pub fn (t &Table) known_fn(name string) bool { + t.find_fn(name) or { return false } + return true +} + +pub fn (mut t Table) register_fn(new_fn Fn) { + // println('reg fn $new_fn.name nr_args=$new_fn.args.len') + t.fns[new_fn.name] = new_fn +} + +pub fn (mut t Table) register_interface(idecl InterfaceDecl) { + t.interfaces[idecl.typ] = idecl +} + +pub fn (mut t TypeSymbol) register_method(new_fn Fn) int { + // returns a method index, stored in the ast.FnDecl + // for faster lookup in the checker's fn_decl method + // println('reg me $new_fn.name nr_args=$new_fn.args.len') + t.methods << new_fn + return t.methods.len - 1 +} + +pub fn (t &Table) register_aggregate_method(mut sym TypeSymbol, name string) ?Fn { + if sym.kind != .aggregate { + t.panic('Unexpected type symbol: $sym.kind') + } + agg_info := sym.info as Aggregate + // an aggregate always has at least 2 types + mut found_once := false + mut new_fn := Fn{} + for typ in agg_info.types { + ts := t.get_type_symbol(typ) + if type_method := ts.find_method(name) { + if !found_once { + found_once = true + new_fn = type_method + } else if !new_fn.method_equals(type_method) { + return error('method `${t.type_to_str(typ)}.$name` signature is different') + } + } else { + return error('unknown method: `${t.type_to_str(typ)}.$name`') + } + } + // register the method in the aggregate, so lookup is faster next time + sym.register_method(new_fn) + return new_fn +} + +pub fn (t &Table) type_has_method(s &TypeSymbol, name string) bool { + // println('type_has_method($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx') + if _ := t.type_find_method(s, name) { + return true + } + return false +} + +// type_find_method searches from current type up through each parent looking for method +pub fn (t &Table) type_find_method(s &TypeSymbol, name string) ?Fn { + // println('type_find_method($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx') + mut ts := unsafe { s } + for { + if method := ts.find_method(name) { + return method + } + if ts.kind == .aggregate { + return t.register_aggregate_method(mut ts, name) + } + if ts.parent_idx == 0 { + break + } + ts = unsafe { &t.type_symbols[ts.parent_idx] } + } + return none +} + +pub struct GetEmbedsOptions { + preceding []Type +} + +// get_embeds returns all nested embedded structs +// the hierarchy of embeds is returned as a list +pub fn (t &Table) get_embeds(sym &TypeSymbol, options GetEmbedsOptions) [][]Type { + mut embeds := [][]Type{} + if sym.info is Struct { + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + mut preceding := options.preceding + preceding << embed + embeds << t.get_embeds(embed_sym, preceding: preceding) + } + if sym.info.embeds.len == 0 && options.preceding.len > 0 { + embeds << options.preceding + } + } + return embeds +} + +pub fn (t &Table) type_find_method_from_embeds(sym &TypeSymbol, method_name string) ?(Fn, Type) { + if sym.info is Struct { + mut found_methods := []Fn{} + mut embed_of_found_methods := []Type{} + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + if m := t.type_find_method(embed_sym, method_name) { + found_methods << m + embed_of_found_methods << embed + } + } + if found_methods.len == 1 { + return found_methods[0], embed_of_found_methods[0] + } else if found_methods.len > 1 { + return error('ambiguous method `$method_name`') + } + } else if sym.info is Aggregate { + for typ in sym.info.types { + agg_sym := t.get_type_symbol(typ) + method, embed_type := t.type_find_method_from_embeds(agg_sym, method_name) or { + return err + } + if embed_type != 0 { + return method, embed_type + } + } + } + return none +} + +fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?StructField { + if sym.kind != .aggregate { + t.panic('Unexpected type symbol: $sym.kind') + } + mut agg_info := sym.info as Aggregate + // an aggregate always has at least 2 types + mut found_once := false + mut new_field := StructField{} + for typ in agg_info.types { + ts := t.get_type_symbol(typ) + if type_field := t.find_field(ts, name) { + if !found_once { + found_once = true + new_field = type_field + } else if new_field.typ != type_field.typ { + return error('field `${t.type_to_str(typ)}.$name` type is different') + } + new_field = StructField{ + ...new_field + is_mut: new_field.is_mut && type_field.is_mut + is_pub: new_field.is_pub && type_field.is_pub + } + } else { + return error('type `${t.type_to_str(typ)}` has no field or method `$name`') + } + } + agg_info.fields << new_field + return new_field +} + +pub fn (t &Table) struct_has_field(struct_ &TypeSymbol, name string) bool { + // println('struct_has_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx') + if _ := t.find_field(struct_, name) { + return true + } + return false +} + +// struct_fields returns all fields including fields from embeds +// use this instead symbol.info.fields to get all fields +pub fn (t &Table) struct_fields(sym &TypeSymbol) []StructField { + mut fields := []StructField{} + if sym.info is Struct { + fields << sym.info.fields + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + fields << t.struct_fields(embed_sym) + } + } + return fields +} + +// search from current type up through each parent looking for field +pub fn (t &Table) find_field(s &TypeSymbol, name string) ?StructField { + // println('find_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx') + mut ts := unsafe { s } + for { + match mut ts.info { + Struct { + if field := ts.info.find_field(name) { + return field + } + } + Aggregate { + if field := ts.info.find_field(name) { + return field + } + field := t.register_aggregate_field(mut ts, name) or { return err } + return field + } + Interface { + if field := ts.info.find_field(name) { + return field + } + } + SumType { + t.resolve_common_sumtype_fields(ts) + if field := ts.info.find_field(name) { + return field + } + return error('field `$name` does not exist or have the same type in all sumtype variants') + } + else {} + } + if ts.parent_idx == 0 { + break + } + ts = unsafe { &t.type_symbols[ts.parent_idx] } + } + return none +} + +// find_field_from_embeds is the same as find_field_from_embeds but also looks into nested embeds +pub fn (t &Table) find_field_from_embeds_recursive(sym &TypeSymbol, field_name string) ?(StructField, []Type) { + if sym.info is Struct { + mut found_fields := []StructField{} + mut embeds_of_found_fields := [][]Type{} + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + if field := t.find_field(embed_sym, field_name) { + found_fields << field + embeds_of_found_fields << [embed] + } else { + field, types := t.find_field_from_embeds_recursive(embed_sym, field_name) or { + StructField{}, []Type{} + } + found_fields << field + embeds_of_found_fields << types + } + } + if found_fields.len == 1 { + return found_fields[0], embeds_of_found_fields[0] + } else if found_fields.len > 1 { + return error('ambiguous field `$field_name`') + } + } else if sym.info is Aggregate { + for typ in sym.info.types { + agg_sym := t.get_type_symbol(typ) + field, embed_types := t.find_field_from_embeds_recursive(agg_sym, field_name) or { + return err + } + if embed_types.len > 0 { + return field, embed_types + } + } + } + return none +} + +// find_field_from_embeds finds and returns a field in the embeddings of a struct and the embedding type +pub fn (t &Table) find_field_from_embeds(sym &TypeSymbol, field_name string) ?(StructField, Type) { + if sym.info is Struct { + mut found_fields := []StructField{} + mut embed_of_found_fields := []Type{} + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + if field := t.find_field(embed_sym, field_name) { + found_fields << field + embed_of_found_fields << embed + } + } + if found_fields.len == 1 { + return found_fields[0], embed_of_found_fields[0] + } else if found_fields.len > 1 { + return error('ambiguous field `$field_name`') + } + } else if sym.info is Aggregate { + for typ in sym.info.types { + agg_sym := t.get_type_symbol(typ) + field, embed_type := t.find_field_from_embeds(agg_sym, field_name) or { return err } + if embed_type != 0 { + return field, embed_type + } + } + } + return none +} + +// find_field_with_embeds searches for a given field, also looking through embedded fields +pub fn (t &Table) find_field_with_embeds(sym &TypeSymbol, field_name string) ?StructField { + if field := t.find_field(sym, field_name) { + return field + } else { + // look for embedded field + first_err := err + field, _ := t.find_field_from_embeds(sym, field_name) or { return first_err } + return field + } +} + +pub fn (t &Table) resolve_common_sumtype_fields(sym_ &TypeSymbol) { + mut sym := unsafe { sym_ } + mut info := sym.info as SumType + if info.found_fields { + return + } + mut field_map := map[string]StructField{} + mut field_usages := map[string]int{} + for variant in info.variants { + mut v_sym := t.get_type_symbol(variant) + fields := match mut v_sym.info { + Struct { + t.struct_fields(v_sym) + } + SumType { + t.resolve_common_sumtype_fields(v_sym) + v_sym.info.fields + } + else { + []StructField{} + } + } + for field in fields { + if field.name !in field_map { + field_map[field.name] = field + field_usages[field.name]++ + } else if field.equals(field_map[field.name]) { + field_usages[field.name]++ + } + } + } + for field, nr_definitions in field_usages { + if nr_definitions == info.variants.len { + info.fields << field_map[field] + } + } + info.found_fields = true + sym.info = info +} + +[inline] +pub fn (t &Table) find_type_idx(name string) int { + return t.type_idxs[name] +} + +[inline] +pub fn (t &Table) find_type(name string) ?TypeSymbol { + idx := t.type_idxs[name] + if idx > 0 { + return t.type_symbols[idx] + } + return none +} + +pub const invalid_type_symbol = &TypeSymbol{ + parent_idx: -1 + language: .v + mod: 'builtin' + kind: .placeholder + name: 'InvalidType' + cname: 'InvalidType' +} + +[inline] +pub fn (t &Table) get_type_symbol(typ Type) &TypeSymbol { + idx := typ.idx() + if idx > 0 { + return unsafe { &t.type_symbols[idx] } + } + // this should never happen + t.panic('get_type_symbol: invalid type (typ=$typ idx=$idx). Compiler bug. This should never happen. Please report the bug using `v bug file.v`. +') + return ast.invalid_type_symbol +} + +// get_final_type_symbol follows aliases until it gets to a "real" Type +[inline] +pub fn (t &Table) get_final_type_symbol(typ Type) &TypeSymbol { + mut idx := typ.idx() + if idx > 0 { + current_type := t.type_symbols[idx] + if current_type.kind == .alias { + idx = (current_type.info as Alias).parent_type.idx() + } + return unsafe { &t.type_symbols[idx] } + } + // this should never happen + t.panic('get_final_type_symbol: invalid type (typ=$typ idx=$idx). Compiler bug. This should never happen. Please report the bug using `v bug file.v`.') + return ast.invalid_type_symbol +} + +[inline] +pub fn (t &Table) get_type_name(typ Type) string { + typ_sym := t.get_type_symbol(typ) + return typ_sym.name +} + +[inline] +pub fn (t &Table) unalias_num_type(typ Type) Type { + sym := t.get_type_symbol(typ) + if sym.kind == .alias { + pt := (sym.info as Alias).parent_type + if pt <= f64_type && pt >= void_type { + return pt + } + } + return typ +} + +fn (mut t Table) check_for_already_registered_symbol(typ TypeSymbol, existing_idx int) int { + ex_type := t.type_symbols[existing_idx] + match ex_type.kind { + .placeholder { + // override placeholder + // println('overriding type placeholder `$typ.name`') + t.type_symbols[existing_idx] = TypeSymbol{ + ...typ + methods: ex_type.methods + } + return existing_idx + } + else { + // builtin + // this will override the already registered builtin types + // with the actual v struct declaration in the source + if (existing_idx >= string_type_idx && existing_idx <= map_type_idx) + || existing_idx == error_type_idx { + if existing_idx == string_type_idx { + // existing_type := t.type_symbols[existing_idx] + t.type_symbols[existing_idx] = TypeSymbol{ + ...typ + kind: ex_type.kind + } + } else { + t.type_symbols[existing_idx] = typ + } + return existing_idx + } + return -1 + } + } + return -2 +} + +[inline] +pub fn (mut t Table) register_type_symbol(typ TypeSymbol) int { + mut typ_idx := -2 + mut existing_idx := t.type_idxs[typ.name] + if existing_idx > 0 { + typ_idx = t.check_for_already_registered_symbol(typ, existing_idx) + if typ_idx != -2 { + return typ_idx + } + } + if typ.mod == 'main' { + existing_idx = t.type_idxs[typ.name.trim_prefix('main.')] + if existing_idx > 0 { + typ_idx = t.check_for_already_registered_symbol(typ, existing_idx) + if typ_idx != -2 { + return typ_idx + } + } + } + typ_idx = t.type_symbols.len + t.type_symbols << typ + t.type_symbols[typ_idx].idx = typ_idx + t.type_idxs[typ.name] = typ_idx + return typ_idx +} + +[inline] +pub fn (mut t Table) register_enum_decl(enum_decl EnumDecl) { + t.enum_decls[enum_decl.name] = enum_decl +} + +pub fn (t &Table) known_type(name string) bool { + return t.find_type_idx(name) != 0 +} + +pub fn (t &Table) known_type_idx(typ Type) bool { + if typ == 0 { + return false + } + sym := t.get_type_symbol(typ) + match sym.kind { + .placeholder { + return sym.language != .v || sym.name.starts_with('C.') + } + .array { + return t.known_type_idx((sym.info as Array).elem_type) + } + .array_fixed { + return t.known_type_idx((sym.info as ArrayFixed).elem_type) + } + .map { + info := sym.info as Map + return t.known_type_idx(info.key_type) && t.known_type_idx(info.value_type) + } + else {} + } + return true +} + +// array_source_name generates the original name for the v source. +// e. g. []int +[inline] +pub fn (t &Table) array_name(elem_type Type) string { + elem_type_sym := t.get_type_symbol(elem_type) + ptr := if elem_type.is_ptr() { '&'.repeat(elem_type.nr_muls()) } else { '' } + return '[]$ptr$elem_type_sym.name' +} + +[inline] +pub fn (t &Table) array_cname(elem_type Type) string { + elem_type_sym := t.get_type_symbol(elem_type) + mut res := '' + if elem_type.is_ptr() { + res = '_ptr'.repeat(elem_type.nr_muls()) + } + return 'Array_$elem_type_sym.cname' + res +} + +// array_fixed_source_name generates the original name for the v source. +// e. g. [16][8]int +[inline] +pub fn (t &Table) array_fixed_name(elem_type Type, size int, size_expr Expr) string { + elem_type_sym := t.get_type_symbol(elem_type) + ptr := if elem_type.is_ptr() { '&'.repeat(elem_type.nr_muls()) } else { '' } + size_str := if size_expr is EmptyExpr || size != 987654321 { + size.str() + } else { + size_expr.str() + } + return '[$size_str]$ptr$elem_type_sym.name' +} + +[inline] +pub fn (t &Table) array_fixed_cname(elem_type Type, size int) string { + elem_type_sym := t.get_type_symbol(elem_type) + mut res := '' + if elem_type.is_ptr() { + res = '_ptr$elem_type.nr_muls()' + } + return 'Array_fixed_${elem_type_sym.cname}_$size' + res +} + +[inline] +pub fn (t &Table) chan_name(elem_type Type, is_mut bool) string { + elem_type_sym := t.get_type_symbol(elem_type) + mut ptr := '' + if is_mut { + ptr = 'mut ' + } else if elem_type.is_ptr() { + ptr = '&' + } + return 'chan $ptr$elem_type_sym.name' +} + +[inline] +pub fn (t &Table) chan_cname(elem_type Type, is_mut bool) string { + elem_type_sym := t.get_type_symbol(elem_type) + mut suffix := '' + if is_mut { + suffix = '_mut' + } else if elem_type.is_ptr() { + suffix = '_ptr' + } + return 'chan_$elem_type_sym.cname' + suffix +} + +[inline] +pub fn (t &Table) thread_name(return_type Type) string { + if return_type.idx() == void_type_idx { + if return_type.has_flag(.optional) { + return 'thread ?' + } else { + return 'thread' + } + } + return_type_sym := t.get_type_symbol(return_type) + ptr := if return_type.is_ptr() { '&' } else { '' } + opt := if return_type.has_flag(.optional) { '?' } else { '' } + return 'thread $opt$ptr$return_type_sym.name' +} + +[inline] +pub fn (t &Table) thread_cname(return_type Type) string { + if return_type == void_type { + if return_type.has_flag(.optional) { + return '__v_thread_Option_void' + } else { + return '__v_thread' + } + } + return_type_sym := t.get_type_symbol(return_type) + suffix := if return_type.is_ptr() { '_ptr' } else { '' } + prefix := if return_type.has_flag(.optional) { 'Option_' } else { '' } + return '__v_thread_$prefix$return_type_sym.cname$suffix' +} + +// map_source_name generates the original name for the v source. +// e. g. map[string]int +[inline] +pub fn (t &Table) map_name(key_type Type, value_type Type) string { + key_type_sym := t.get_type_symbol(key_type) + value_type_sym := t.get_type_symbol(value_type) + ptr := if value_type.is_ptr() { '&' } else { '' } + return 'map[$key_type_sym.name]$ptr$value_type_sym.name' +} + +[inline] +pub fn (t &Table) map_cname(key_type Type, value_type Type) string { + key_type_sym := t.get_type_symbol(key_type) + value_type_sym := t.get_type_symbol(value_type) + suffix := if value_type.is_ptr() { '_ptr' } else { '' } + return 'Map_${key_type_sym.cname}_$value_type_sym.cname' + suffix + // return 'map_${value_type_sym.name}' + suffix +} + +pub fn (mut t Table) find_or_register_chan(elem_type Type, is_mut bool) int { + name := t.chan_name(elem_type, is_mut) + cname := t.chan_cname(elem_type, is_mut) + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + // register + chan_typ := TypeSymbol{ + parent_idx: chan_type_idx + kind: .chan + name: name + cname: cname + info: Chan{ + elem_type: elem_type + is_mut: is_mut + } + } + return t.register_type_symbol(chan_typ) +} + +pub fn (mut t Table) find_or_register_map(key_type Type, value_type Type) int { + name := t.map_name(key_type, value_type) + cname := t.map_cname(key_type, value_type) + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + // register + map_typ := TypeSymbol{ + parent_idx: map_type_idx + kind: .map + name: name + cname: cname + info: Map{ + key_type: key_type + value_type: value_type + } + } + return t.register_type_symbol(map_typ) +} + +pub fn (mut t Table) find_or_register_thread(return_type Type) int { + name := t.thread_name(return_type) + cname := t.thread_cname(return_type) + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + // register + thread_typ := TypeSymbol{ + parent_idx: thread_type_idx + kind: .thread + name: name + cname: cname + info: Thread{ + return_type: return_type + } + } + return t.register_type_symbol(thread_typ) +} + +pub fn (mut t Table) find_or_register_array(elem_type Type) int { + name := t.array_name(elem_type) + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + cname := t.array_cname(elem_type) + // register + array_type_ := TypeSymbol{ + parent_idx: array_type_idx + kind: .array + name: name + cname: cname + info: Array{ + nr_dims: 1 + elem_type: elem_type + } + } + return t.register_type_symbol(array_type_) +} + +pub fn (mut t Table) find_or_register_array_with_dims(elem_type Type, nr_dims int) int { + if nr_dims == 1 { + return t.find_or_register_array(elem_type) + } + return t.find_or_register_array(t.find_or_register_array_with_dims(elem_type, nr_dims - 1)) +} + +pub fn (mut t Table) find_or_register_array_fixed(elem_type Type, size int, size_expr Expr) int { + name := t.array_fixed_name(elem_type, size, size_expr) + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + cname := t.array_fixed_cname(elem_type, size) + // register + array_fixed_type := TypeSymbol{ + kind: .array_fixed + name: name + cname: cname + info: ArrayFixed{ + elem_type: elem_type + size: size + size_expr: size_expr + } + } + return t.register_type_symbol(array_fixed_type) +} + +pub fn (mut t Table) find_or_register_multi_return(mr_typs []Type) int { + mut name := '(' + mut cname := 'multi_return' + for i, mr_typ in mr_typs { + mr_type_sym := t.get_type_symbol(mr_typ) + name += mr_type_sym.name + cname += '_$mr_type_sym.cname' + if i < mr_typs.len - 1 { + name += ', ' + } + } + name += ')' + // existing + existing_idx := t.type_idxs[name] + if existing_idx > 0 { + return existing_idx + } + // register + mr_type := TypeSymbol{ + kind: .multi_return + name: name + cname: cname + info: MultiReturn{ + types: mr_typs + } + } + return t.register_type_symbol(mr_type) +} + +pub fn (mut t Table) find_or_register_fn_type(mod string, f Fn, is_anon bool, has_decl bool) int { + name := if f.name.len == 0 { 'fn ${t.fn_type_source_signature(f)}' } else { f.name.clone() } + cname := if f.name.len == 0 { + 'anon_fn_${t.fn_type_signature(f)}' + } else { + util.no_dots(f.name.clone()) + } + anon := f.name.len == 0 || is_anon + existing_idx := t.type_idxs[name] + if existing_idx > 0 && t.type_symbols[existing_idx].kind != .placeholder { + return existing_idx + } + return t.register_type_symbol( + kind: .function + name: name + cname: cname + mod: mod + info: FnType{ + is_anon: anon + has_decl: has_decl + func: f + } + ) +} + +pub fn (mut t Table) add_placeholder_type(name string, language Language) int { + mut modname := '' + if name.contains('.') { + modname = name.all_before_last('.') + } + ph_type := TypeSymbol{ + kind: .placeholder + name: name + cname: util.no_dots(name) + language: language + mod: modname + } + // println('added placeholder: $name - $ph_type.idx') + return t.register_type_symbol(ph_type) +} + +[inline] +pub fn (t &Table) value_type(typ Type) Type { + typ_sym := t.get_final_type_symbol(typ) + if typ.has_flag(.variadic) { + // ...string => string + // return typ.clear_flag(.variadic) + array_info := typ_sym.info as Array + return array_info.elem_type + } + if typ_sym.kind == .array { + // Check index type + info := typ_sym.info as Array + return info.elem_type + } + if typ_sym.kind == .array_fixed { + info := typ_sym.info as ArrayFixed + return info.elem_type + } + if typ_sym.kind == .map { + info := typ_sym.info as Map + return info.value_type + } + if typ_sym.kind == .string && typ.is_ptr() { + // (&string)[i] => string + return string_type + } + if typ_sym.kind in [.byteptr, .string] { + return byte_type + } + if typ.is_ptr() { + // byte* => byte + // bytes[0] is a byte, not byte* + return typ.deref() + } + // TODO: remove when map_string is removed + if typ_sym.name == 'map_string' { + return string_type + } + return void_type +} + +[inline] +pub fn (t &Table) mktyp(typ Type) Type { + match typ { + float_literal_type { return f64_type } + int_literal_type { return int_type } + else { return typ } + } +} + +pub fn (mut t Table) register_fn_concrete_types(fn_name string, types []Type) bool { + mut a := t.fn_generic_types[fn_name] + if types in a { + return false + } + a << types + t.fn_generic_types[fn_name] = a + return true +} + +// TODO: there is a bug when casting sumtype the other way if its pointer +// so until fixed at least show v (not C) error `x(variant) = y(SumType*)` +pub fn (t &Table) sumtype_has_variant(parent Type, variant Type) bool { + parent_sym := t.get_type_symbol(parent) + if parent_sym.kind == .sum_type { + parent_info := parent_sym.info as SumType + var_sym := t.get_type_symbol(variant) + if var_sym.kind == .aggregate { + var_info := var_sym.info as Aggregate + for var_type in var_info.types { + if !t.sumtype_has_variant(parent, var_type) { + return false + } + } + return true + } else { + for v in parent_info.variants { + if v.idx() == variant.idx() { + return true + } + } + } + } + return false +} + +// only used for debugging V compiler type bugs +pub fn (t &Table) known_type_names() []string { + mut res := []string{cap: t.type_idxs.len} + for _, idx in t.type_idxs { + // Skip `int_literal_type_idx` and `float_literal_type_idx` because they shouldn't be visible to the User. + if idx !in [0, int_literal_type_idx, float_literal_type_idx] && t.known_type_idx(idx) + && t.get_type_symbol(idx).kind != .function { + res << t.type_to_str(idx) + } + } + return res +} + +// has_deep_child_no_ref returns true if type is struct and has any child or nested child with the type of the given name +// the given name consists of module and name (`mod.Name`) +// it doesn't care about childs that are references +pub fn (t &Table) has_deep_child_no_ref(ts &TypeSymbol, name string) bool { + if ts.info is Struct { + for field in ts.info.fields { + sym := t.get_type_symbol(field.typ) + if !field.typ.is_ptr() && (sym.name == name || t.has_deep_child_no_ref(sym, name)) { + return true + } + } + } + return false +} + +// complete_interface_check does a MxN check for all M interfaces vs all N types, to determine what types implement what interfaces. +// It short circuits most checks when an interface can not possibly be implemented by a type. +pub fn (mut t Table) complete_interface_check() { + util.timing_start(@METHOD) + defer { + util.timing_measure(@METHOD) + } + for tk, mut tsym in t.type_symbols { + if tsym.kind != .struct_ { + continue + } + info := tsym.info as Struct + for _, mut idecl in t.interfaces { + if idecl.methods.len > tsym.methods.len { + continue + } + if idecl.fields.len > info.fields.len { + continue + } + // empty interface only generate type cast functions of the current module + if idecl.methods.len == 0 && idecl.fields.len == 0 + && tsym.mod != t.get_type_symbol(idecl.typ).mod { + continue + } + if t.does_type_implement_interface(tk, idecl.typ) { + $if trace_types_implementing_each_interface ? { + eprintln('>>> tsym.mod: $tsym.mod | tsym.name: $tsym.name | tk: $tk | idecl.name: $idecl.name | idecl.typ: $idecl.typ') + } + t.iface_types[idecl.name] << tk + } + } + } +} + +// bitsize_to_type returns a type corresponding to the bit_size +// Examples: +// +// `8 > i8` +// +// `32 > int` +// +// `123 > panic()` +// +// `128 > [16]byte` +// +// `608 > [76]byte` +pub fn (mut t Table) bitsize_to_type(bit_size int) Type { + match bit_size { + 8 { + return i8_type + } + 16 { + return i16_type + } + 32 { + return int_type + } + 64 { + return i64_type + } + else { + if bit_size % 8 != 0 { // there is no way to do `i2131(32)` so this should never be reached + t.panic('compiler bug: bitsizes must be multiples of 8') + } + return new_type(t.find_or_register_array_fixed(byte_type, bit_size / 8, empty_expr())) + } + } +} + +pub fn (mut t Table) does_type_implement_interface(typ Type, inter_typ Type) bool { + if typ.idx() == inter_typ.idx() { + // same type -> already casted to the interface + return true + } + if inter_typ.idx() == error_type_idx && typ.idx() == none_type_idx { + // `none` "implements" the Error interface + return true + } + typ_sym := t.get_type_symbol(typ) + if typ_sym.language != .v { + return false + } + // generic struct don't generate cast interface fn + if typ_sym.info is Struct { + if typ_sym.info.is_generic { + return false + } + } + mut inter_sym := t.get_type_symbol(inter_typ) + if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ { + return false + } + if mut inter_sym.info is Interface { + // do not check the same type more than once + for tt in inter_sym.info.types { + if tt.idx() == typ.idx() { + return true + } + } + // verify methods + for imethod in inter_sym.info.methods { + if method := typ_sym.find_method(imethod.name) { + msg := t.is_same_method(imethod, method) + if msg.len > 0 { + return false + } + continue + } + return false + } + // verify fields + for ifield in inter_sym.info.fields { + if ifield.typ == voidptr_type { + // Allow `voidptr` fields in interfaces for now. (for example + // to enable .db check in vweb) + if t.struct_has_field(typ_sym, ifield.name) { + continue + } else { + return false + } + } + if field := t.find_field_with_embeds(typ_sym, ifield.name) { + if ifield.typ != field.typ { + return false + } else if ifield.is_mut && !(field.is_mut || field.is_global) { + return false + } + continue + } + return false + } + inter_sym.info.types << typ + if !inter_sym.info.types.contains(voidptr_type) { + inter_sym.info.types << voidptr_type + } + return true + } + return false +} + +// resolve_generic_to_concrete resolves generics to real types T => int. +// Even map[string]map[string]T can be resolved. +// This is used for resolving the generic return type of CallExpr white `unwrap_generic` is used to resolve generic usage in FnDecl. +pub fn (mut t Table) resolve_generic_to_concrete(generic_type Type, generic_names []string, concrete_types []Type) ?Type { + mut sym := t.get_type_symbol(generic_type) + if sym.name in generic_names { + index := generic_names.index(sym.name) + if index >= concrete_types.len { + return none + } + typ := concrete_types[index] + if typ == 0 { + return none + } + return typ.derive_add_muls(generic_type).clear_flag(.generic) + } + match mut sym.info { + Array { + mut elem_type := sym.info.elem_type + mut elem_sym := t.get_type_symbol(elem_type) + mut dims := 1 + for mut elem_sym.info is Array { + info := elem_sym.info as Array + elem_type = info.elem_type + elem_sym = t.get_type_symbol(elem_type) + dims++ + } + if typ := t.resolve_generic_to_concrete(elem_type, generic_names, concrete_types) { + idx := t.find_or_register_array_with_dims(typ, dims) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + ArrayFixed { + if typ := t.resolve_generic_to_concrete(sym.info.elem_type, generic_names, + concrete_types) + { + idx := t.find_or_register_array_fixed(typ, sym.info.size, None{}) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + Chan { + if typ := t.resolve_generic_to_concrete(sym.info.elem_type, generic_names, + concrete_types) + { + idx := t.find_or_register_chan(typ, typ.nr_muls() > 0) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + FnType { + mut func := sym.info.func + if func.return_type.has_flag(.generic) { + if typ := t.resolve_generic_to_concrete(func.return_type, generic_names, + concrete_types) + { + func.return_type = typ + } + } + func.params = func.params.clone() + for mut param in func.params { + if param.typ.has_flag(.generic) { + if typ := t.resolve_generic_to_concrete(param.typ, generic_names, + concrete_types) + { + param.typ = typ + } + } + } + idx := t.find_or_register_fn_type('', func, true, false) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + MultiReturn { + mut types := []Type{} + mut type_changed := false + for ret_type in sym.info.types { + if typ := t.resolve_generic_to_concrete(ret_type, generic_names, concrete_types) { + types << typ + type_changed = true + } else { + types << ret_type + } + } + if type_changed { + idx := t.find_or_register_multi_return(types) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + Map { + mut type_changed := false + mut unwrapped_key_type := sym.info.key_type + mut unwrapped_value_type := sym.info.value_type + if typ := t.resolve_generic_to_concrete(sym.info.key_type, generic_names, + concrete_types) + { + unwrapped_key_type = typ + type_changed = true + } + if typ := t.resolve_generic_to_concrete(sym.info.value_type, generic_names, + concrete_types) + { + unwrapped_value_type = typ + type_changed = true + } + if type_changed { + idx := t.find_or_register_map(unwrapped_key_type, unwrapped_value_type) + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + Struct, Interface, SumType { + if sym.info.is_generic { + mut nrt := '$sym.name<' + for i in 0 .. sym.info.generic_types.len { + if ct := t.resolve_generic_to_concrete(sym.info.generic_types[i], + generic_names, concrete_types) + { + gts := t.get_type_symbol(ct) + nrt += gts.name + if i != sym.info.generic_types.len - 1 { + nrt += ', ' + } + } + } + nrt += '>' + mut idx := t.type_idxs[nrt] + if idx == 0 { + idx = t.add_placeholder_type(nrt, .v) + } + return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic) + } + } + else {} + } + return none +} diff --git a/v_windows/v/vlib/v/ast/types.v b/v_windows/v/vlib/v/ast/types.v new file mode 100644 index 0000000..03a85dd --- /dev/null +++ b/v_windows/v/vlib/v/ast/types.v @@ -0,0 +1,1310 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// +// Type layout information (32 bits) +// flag (8 bits) | nr_muls (8 bits) | idx (16 bits) +// pack: (int(flag)<<24) | (nr_muls<<16) | u16(idx) +// unpack: +// flag: (int(type)>>24) & 0xff +// nr_muls: (int(type)>>16) & 0xff +// idx: u16(type) & 0xffff +module ast + +import strings +import v.pref + +pub type Type = int + +pub type TypeInfo = Aggregate | Alias | Array | ArrayFixed | Chan | Enum | FnType | GenericInst | + Interface | Map | MultiReturn | Struct | SumType | Thread + +pub enum Language { + v + c + js + amd64 // aka x86_64 + i386 + arm64 // 64-bit arm + arm32 // 32-bit arm + rv64 // 64-bit risc-v + rv32 // 32-bit risc-v +} + +pub fn pref_arch_to_table_language(pref_arch pref.Arch) Language { + return match pref_arch { + .amd64 { + Language.amd64 + } + .arm64 { + Language.arm64 + } + .arm32 { + Language.arm32 + } + .rv64 { + Language.rv64 + } + .rv32 { + Language.rv32 + } + .i386 { + Language.i386 + } + ._auto, ._max { + Language.v + } + } +} + +// Represents a type that only needs an identifier, e.g. int, array_int. +// A pointer type `&T` would have a TypeSymbol `T`. +// Note: For a Type, use: +// * Table.type_to_str(typ) not TypeSymbol.name. +// * Table.type_kind(typ) not TypeSymbol.kind. +// Each TypeSymbol is entered into `Table.types`. +// See also: Table.get_type_symbol. + +pub struct TypeSymbol { +pub: + parent_idx int +pub mut: + info TypeInfo + kind Kind + name string // the internal & source name of the type, i.e. `[5]int`. + cname string // the name with no dots for use in the generated C code + methods []Fn + mod string + is_public bool + language Language + idx int +} + +// max of 8 +pub enum TypeFlag { + optional + variadic + generic + shared_f + atomic_f +} + +/* +To save precious TypeFlag bits the 4 possible ShareTypes are coded in the two + bits `shared` and `atomic_or_rw` (see sharetype_from_flags() below). +*/ +pub enum ShareType { + mut_t + shared_t + atomic_t +} + +pub fn (t ShareType) str() string { + match t { + .mut_t { return 'mut' } + .shared_t { return 'shared' } + .atomic_t { return 'atomic' } + } +} + +// defines special typenames +pub fn (t Type) atomic_typename() string { + idx := t.idx() + match idx { + ast.u32_type_idx { return 'atomic_uint' } + ast.int_type_idx { return 'atomic_int' } + ast.u64_type_idx { return 'atomic_ullong' } + ast.i64_type_idx { return 'atomic_llong' } + else { return 'unknown_atomic' } + } +} + +pub fn sharetype_from_flags(is_shared bool, is_atomic bool) ShareType { + return ShareType((int(is_atomic) << 1) | int(is_shared)) +} + +pub fn (t Type) share() ShareType { + return sharetype_from_flags(t.has_flag(.shared_f), t.has_flag(.atomic_f)) +} + +// return TypeSymbol idx for `t` +[inline] +pub fn (t Type) idx() int { + return u16(t) & 0xffff +} + +[inline] +pub fn (t Type) is_void() bool { + return t == ast.void_type +} + +[inline] +pub fn (t Type) is_full() bool { + return t != 0 && t != ast.void_type +} + +// return nr_muls for `t` +[inline] +pub fn (t Type) nr_muls() int { + return (int(t) >> 16) & 0xff +} + +// return true if `t` is a pointer (nr_muls>0) +[inline] +pub fn (t Type) is_ptr() bool { + // any normal pointer, i.e. &Type, &&Type etc; + // NB: voidptr, charptr and byteptr are NOT included! + return (int(t) >> 16) & 0xff > 0 +} + +[inline] +pub fn (t Type) is_any_kind_of_pointer() bool { + return (int(t) >> 16) & 0xff > 0 || (u16(t) & 0xffff) in ast.pointer_type_idxs +} + +// set nr_muls on `t` and return it +[inline] +pub fn (t Type) set_nr_muls(nr_muls int) Type { + if nr_muls < 0 || nr_muls > 255 { + panic('set_nr_muls: nr_muls must be between 0 & 255') + } + return int(t) & 0xff00ffff | (nr_muls << 16) +} + +// increments nr_nuls on `t` and return it +[inline] +pub fn (t Type) to_ptr() Type { + nr_muls := (int(t) >> 16) & 0xff + if nr_muls == 255 { + panic('to_ptr: nr_muls is already at max of 255') + } + return int(t) & 0xff00ffff | ((nr_muls + 1) << 16) +} + +// decrement nr_muls on `t` and return it +[inline] +pub fn (t Type) deref() Type { + nr_muls := (int(t) >> 16) & 0xff + if nr_muls == 0 { + panic('deref: type `$t` is not a pointer') + } + return int(t) & 0xff00ffff | ((nr_muls - 1) << 16) +} + +// set `flag` on `t` and return `t` +[inline] +pub fn (t Type) set_flag(flag TypeFlag) Type { + return int(t) | (1 << (int(flag) + 24)) +} + +// clear `flag` on `t` and return `t` +[inline] +pub fn (t Type) clear_flag(flag TypeFlag) Type { + return int(t) & ~(1 << (int(flag) + 24)) +} + +// clear all flags +[inline] +pub fn (t Type) clear_flags() Type { + return int(t) & 0xffffff +} + +// return true if `flag` is set on `t` +[inline] +pub fn (t Type) has_flag(flag TypeFlag) bool { + return int(t) & (1 << (int(flag) + 24)) > 0 +} + +// debug returns a verbose representation of the information in ts, useful for tracing/debugging +pub fn (ts TypeSymbol) debug() []string { + mut res := []string{} + ts.dbg_common(mut res) + res << 'info: $ts.info' + res << 'methods ($ts.methods.len): ' + ts.methods.map(it.str()).join(', ') + return res +} + +// same as .debug(), but without the verbose .info and .methods fields +pub fn (ts TypeSymbol) dbg() []string { + mut res := []string{} + ts.dbg_common(mut res) + return res +} + +fn (ts TypeSymbol) dbg_common(mut res []string) { + res << 'idx: 0x$ts.idx.hex()' + res << 'parent_idx: 0x$ts.parent_idx.hex()' + res << 'mod: $ts.mod' + res << 'name: $ts.name' + res << 'cname: $ts.cname' + res << 'kind: $ts.kind' + res << 'is_public: $ts.is_public' + res << 'language: $ts.language' +} + +pub fn (t Type) str() string { + return 'ast.Type(0x$t.hex() = ${u32(t)})' +} + +// debug returns a verbose representation of the information in the type `t`, useful for tracing/debugging +pub fn (t Type) debug() []string { + mut res := []string{} + res << 'idx: 0x${t.idx().hex():-8}' + res << 'type: 0x${t.hex():-8}' + res << 'nr_muls: $t.nr_muls()' + if t.has_flag(.optional) { + res << 'optional' + } + if t.has_flag(.variadic) { + res << 'variadic' + } + if t.has_flag(.generic) { + res << 'generic' + } + if t.has_flag(.shared_f) { + res << 'shared_f' + } + if t.has_flag(.atomic_f) { + res << 'atomic_f' + } + return res +} + +// copy flags & nr_muls from `t_from` to `t` and return `t` +[inline] +pub fn (t Type) derive(t_from Type) Type { + return (0xffff0000 & t_from) | u16(t) +} + +// copy flags from `t_from` to `t` and return `t` +[inline] +pub fn (t Type) derive_add_muls(t_from Type) Type { + return Type((0xff000000 & t_from) | u16(t)).set_nr_muls(t.nr_muls() + t_from.nr_muls()) +} + +// return new type with TypeSymbol idx set to `idx` +[inline] +pub fn new_type(idx int) Type { + if idx < 1 || idx > 65535 { + panic('new_type: idx must be between 1 & 65535') + } + return idx +} + +// return new type with TypeSymbol idx set to `idx` & nr_muls set to `nr_muls` +[inline] +pub fn new_type_ptr(idx int, nr_muls int) Type { + if idx < 1 || idx > 65535 { + panic('new_type_ptr: idx must be between 1 & 65535') + } + if nr_muls < 0 || nr_muls > 255 { + panic('new_type_ptr: nr_muls must be between 0 & 255') + } + return (nr_muls << 16) | u16(idx) +} + +[inline] +pub fn (typ Type) is_pointer() bool { + // builtin pointer types (voidptr, byteptr, charptr) + return typ.idx() in ast.pointer_type_idxs +} + +[inline] +pub fn (typ Type) is_float() bool { + return typ.clear_flags() in ast.float_type_idxs +} + +[inline] +pub fn (typ Type) is_int() bool { + return typ.clear_flags() in ast.integer_type_idxs +} + +[inline] +pub fn (typ Type) is_int_valptr() bool { + return typ.idx() in ast.integer_type_idxs +} + +[inline] +pub fn (typ Type) is_float_valptr() bool { + return typ.idx() in ast.float_type_idxs +} + +[inline] +pub fn (typ Type) is_pure_int() bool { + return int(typ) in ast.integer_type_idxs +} + +[inline] +pub fn (typ Type) is_pure_float() bool { + return int(typ) in ast.float_type_idxs +} + +[inline] +pub fn (typ Type) is_signed() bool { + return typ.idx() in ast.signed_integer_type_idxs +} + +[inline] +pub fn (typ Type) is_unsigned() bool { + return typ.idx() in ast.unsigned_integer_type_idxs +} + +[inline] +pub fn (typ Type) is_int_literal() bool { + return int(typ) == ast.int_literal_type_idx +} + +[inline] +pub fn (typ Type) is_number() bool { + return typ.clear_flags() in ast.number_type_idxs +} + +[inline] +pub fn (typ Type) is_string() bool { + return typ.idx() in ast.string_type_idxs +} + +[inline] +pub fn (typ Type) is_bool() bool { + return typ.idx() == ast.bool_type_idx +} + +pub const ( + void_type_idx = 1 + voidptr_type_idx = 2 + byteptr_type_idx = 3 + charptr_type_idx = 4 + i8_type_idx = 5 + i16_type_idx = 6 + int_type_idx = 7 + i64_type_idx = 8 + byte_type_idx = 9 + u16_type_idx = 10 + u32_type_idx = 11 + u64_type_idx = 12 + f32_type_idx = 13 + f64_type_idx = 14 + char_type_idx = 15 + bool_type_idx = 16 + none_type_idx = 17 + string_type_idx = 18 + rune_type_idx = 19 + array_type_idx = 20 + map_type_idx = 21 + chan_type_idx = 22 + size_t_type_idx = 23 + any_type_idx = 24 + float_literal_type_idx = 25 + int_literal_type_idx = 26 + thread_type_idx = 27 + error_type_idx = 28 + u8_type_idx = 29 +) + +pub const ( + integer_type_idxs = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx, + byte_type_idx, u8_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, int_literal_type_idx, + rune_type_idx, + ] + signed_integer_type_idxs = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx] + unsigned_integer_type_idxs = [byte_type_idx, u16_type_idx, u32_type_idx, u64_type_idx] + float_type_idxs = [f32_type_idx, f64_type_idx, float_literal_type_idx] + number_type_idxs = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx, + byte_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, f32_type_idx, f64_type_idx, + int_literal_type_idx, float_literal_type_idx, rune_type_idx] + pointer_type_idxs = [voidptr_type_idx, byteptr_type_idx, charptr_type_idx] + string_type_idxs = [string_type_idx] +) + +pub const ( + void_type = new_type(void_type_idx) + ovoid_type = new_type(void_type_idx).set_flag(.optional) // the return type of `fn () ?` + voidptr_type = new_type(voidptr_type_idx) + byteptr_type = new_type(byteptr_type_idx) + charptr_type = new_type(charptr_type_idx) + i8_type = new_type(i8_type_idx) + int_type = new_type(int_type_idx) + i16_type = new_type(i16_type_idx) + i64_type = new_type(i64_type_idx) + byte_type = new_type(byte_type_idx) + u8_type = new_type(u8_type_idx) + u16_type = new_type(u16_type_idx) + u32_type = new_type(u32_type_idx) + u64_type = new_type(u64_type_idx) + f32_type = new_type(f32_type_idx) + f64_type = new_type(f64_type_idx) + char_type = new_type(char_type_idx) + bool_type = new_type(bool_type_idx) + none_type = new_type(none_type_idx) + string_type = new_type(string_type_idx) + rune_type = new_type(rune_type_idx) + array_type = new_type(array_type_idx) + map_type = new_type(map_type_idx) + chan_type = new_type(chan_type_idx) + any_type = new_type(any_type_idx) + float_literal_type = new_type(float_literal_type_idx) + int_literal_type = new_type(int_literal_type_idx) + thread_type = new_type(thread_type_idx) + error_type = new_type(error_type_idx) + charptr_types = [charptr_type, new_type(char_type_idx).set_nr_muls(1)] + byteptr_types = [byteptr_type, new_type(byte_type_idx).set_nr_muls(1)] + voidptr_types = [voidptr_type, new_type(voidptr_type_idx).set_nr_muls(1)] + cptr_types = merge_types(voidptr_types, byteptr_types, charptr_types) +) + +pub fn merge_types(params ...[]Type) []Type { + mut res := []Type{} + for types in params { + res << types + } + return res +} + +pub const ( + // must be in the same order as the idx consts above + builtin_type_names = ['void', 'voidptr', 'charptr', 'byteptr', 'i8', 'i16', 'int', 'i64', 'u16', + 'u32', 'u64', 'int_literal', 'f32', 'f64', 'float_literal', 'string', 'char', 'byte', 'bool', + 'none', 'array', 'array_fixed', 'map', 'chan', 'any', 'struct', 'mapnode', 'size_t', 'rune', + 'thread', 'Error', 'u8'] +) + +pub struct MultiReturn { +pub mut: + types []Type +} + +pub struct FnType { +pub mut: + is_anon bool + has_decl bool + func Fn +} + +// returns TypeSymbol kind only if there are no type modifiers +pub fn (t &Table) type_kind(typ Type) Kind { + if typ.nr_muls() > 0 || typ.has_flag(.optional) { + return Kind.placeholder + } + return t.get_type_symbol(typ).kind +} + +pub enum Kind { + placeholder + void + voidptr + byteptr + charptr + i8 + i16 + int + i64 + byte + u8 + u16 + u32 + u64 + f32 + f64 + char + size_t + rune + bool + none_ + string + array + array_fixed + map + chan + any + struct_ + generic_inst + multi_return + sum_type + alias + enum_ + function + interface_ + float_literal + int_literal + aggregate + thread +} + +pub fn (t &TypeSymbol) str() string { + return t.name +} + +[inline] +pub fn (t &TypeSymbol) enum_info() Enum { + match mut t.info { + Enum { return t.info } + else { panic('TypeSymbol.enum_info(): no enum info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) mr_info() MultiReturn { + match mut t.info { + MultiReturn { return t.info } + else { panic('TypeSymbol.mr_info(): no multi return info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) array_info() Array { + match mut t.info { + Array { return t.info } + else { panic('TypeSymbol.array_info(): no array info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) array_fixed_info() ArrayFixed { + match mut t.info { + ArrayFixed { return t.info } + else { panic('TypeSymbol.array_fixed(): no array fixed info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) chan_info() Chan { + match mut t.info { + Chan { return t.info } + else { panic('TypeSymbol.chan_info(): no chan info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) thread_info() Thread { + match mut t.info { + Thread { return t.info } + else { panic('TypeSymbol.thread_info(): no thread info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) map_info() Map { + match mut t.info { + Map { return t.info } + else { panic('TypeSymbol.map_info(): no map info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) struct_info() Struct { + match mut t.info { + Struct { return t.info } + else { panic('TypeSymbol.struct_info(): no struct info for type: $t.name') } + } +} + +[inline] +pub fn (t &TypeSymbol) sumtype_info() SumType { + match mut t.info { + SumType { return t.info } + else { panic('TypeSymbol.sumtype_info(): no sumtype info for type: $t.name') } + } +} + +pub fn (t &TypeSymbol) is_heap() bool { + if t.kind == .struct_ { + info := t.info as Struct + return info.is_heap + } else { + return false + } +} + +/* +pub fn (t TypeSymbol) str() string { + return t.name +} +*/ +pub fn (mut t Table) register_builtin_type_symbols() { + // reserve index 0 so nothing can go there + // save index check, 0 will mean not found + t.register_type_symbol(kind: .placeholder, name: 'reserved_0') + t.register_type_symbol(kind: .void, name: 'void', cname: 'void', mod: 'builtin') + t.register_type_symbol(kind: .voidptr, name: 'voidptr', cname: 'voidptr', mod: 'builtin') + t.register_type_symbol(kind: .byteptr, name: 'byteptr', cname: 'byteptr', mod: 'builtin') + t.register_type_symbol(kind: .charptr, name: 'charptr', cname: 'charptr', mod: 'builtin') + t.register_type_symbol(kind: .i8, name: 'i8', cname: 'i8', mod: 'builtin') + t.register_type_symbol(kind: .i16, name: 'i16', cname: 'i16', mod: 'builtin') + t.register_type_symbol(kind: .int, name: 'int', cname: 'int', mod: 'builtin') + t.register_type_symbol(kind: .i64, name: 'i64', cname: 'i64', mod: 'builtin') + t.register_type_symbol(kind: .byte, name: 'byte', cname: 'byte', mod: 'builtin') + t.register_type_symbol(kind: .u16, name: 'u16', cname: 'u16', mod: 'builtin') + t.register_type_symbol(kind: .u32, name: 'u32', cname: 'u32', mod: 'builtin') + t.register_type_symbol(kind: .u64, name: 'u64', cname: 'u64', mod: 'builtin') + t.register_type_symbol(kind: .f32, name: 'f32', cname: 'f32', mod: 'builtin') + t.register_type_symbol(kind: .f64, name: 'f64', cname: 'f64', mod: 'builtin') + t.register_type_symbol(kind: .char, name: 'char', cname: 'char', mod: 'builtin') + t.register_type_symbol(kind: .bool, name: 'bool', cname: 'bool', mod: 'builtin') + t.register_type_symbol(kind: .none_, name: 'none', cname: 'none', mod: 'builtin') + t.register_type_symbol(kind: .string, name: 'string', cname: 'string', mod: 'builtin') + t.register_type_symbol(kind: .rune, name: 'rune', cname: 'rune', mod: 'builtin') + t.register_type_symbol(kind: .array, name: 'array', cname: 'array', mod: 'builtin') + t.register_type_symbol(kind: .map, name: 'map', cname: 'map', mod: 'builtin') + t.register_type_symbol(kind: .chan, name: 'chan', cname: 'chan', mod: 'builtin') + t.register_type_symbol(kind: .size_t, name: 'size_t', cname: 'size_t', mod: 'builtin') + t.register_type_symbol(kind: .any, name: 'any', cname: 'any', mod: 'builtin') + t.register_type_symbol( + kind: .float_literal + name: 'float literal' + cname: 'float_literal' + mod: 'builtin' + ) + t.register_type_symbol( + kind: .int_literal + name: 'int literal' + cname: 'int_literal' + mod: 'builtin' + ) + t.register_type_symbol( + kind: .thread + name: 'thread' + cname: '__v_thread' + mod: 'builtin' + info: Thread{ + return_type: ast.void_type + } + ) + t.register_type_symbol(kind: .interface_, name: 'IError', cname: 'IError', mod: 'builtin') + t.register_type_symbol(kind: .u8, name: 'zu8', cname: 'zu8', mod: 'builtin') +} + +[inline] +pub fn (t &TypeSymbol) is_pointer() bool { + return t.kind in [.byteptr, .charptr, .voidptr] +} + +[inline] +pub fn (t &TypeSymbol) is_int() bool { + res := t.kind in [.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .int_literal, .rune] + if !res && t.kind == .alias { + return (t.info as Alias).parent_type.is_number() + } + return res +} + +[inline] +pub fn (t &TypeSymbol) is_float() bool { + return t.kind in [.f32, .f64, .float_literal] +} + +[inline] +pub fn (t &TypeSymbol) is_string() bool { + return t.kind == .string +} + +[inline] +pub fn (t &TypeSymbol) is_number() bool { + return t.is_int() || t.is_float() +} + +[inline] +pub fn (t &TypeSymbol) is_primitive() bool { + return t.is_number() || t.is_pointer() || t.is_string() +} + +[inline] +pub fn (t &TypeSymbol) is_builtin() bool { + return t.mod == 'builtin' +} + +// for debugging/errors only, perf is not an issue +pub fn (k Kind) str() string { + k_str := match k { + .placeholder { 'placeholder' } + .void { 'void' } + .voidptr { 'voidptr' } + .charptr { 'charptr' } + .byteptr { 'byteptr' } + .struct_ { 'struct' } + .int { 'int' } + .i8 { 'i8' } + .i16 { 'i16' } + .i64 { 'i64' } + .byte { 'byte' } + .u8 { 'u8' } + .u16 { 'u16' } + .u32 { 'u32' } + .u64 { 'u64' } + .int_literal { 'int_literal' } + .f32 { 'f32' } + .f64 { 'f64' } + .float_literal { 'float_literal' } + .string { 'string' } + .char { 'char' } + .bool { 'bool' } + .size_t { 'size_t' } + .none_ { 'none' } + .array { 'array' } + .array_fixed { 'array_fixed' } + .map { 'map' } + .chan { 'chan' } + .multi_return { 'multi_return' } + .sum_type { 'sum_type' } + .alias { 'alias' } + .enum_ { 'enum' } + .any { 'any' } + .function { 'function' } + .interface_ { 'interface' } + .generic_inst { 'generic_inst' } + .rune { 'rune' } + .aggregate { 'aggregate' } + .thread { 'thread' } + } + return k_str +} + +pub fn (kinds []Kind) str() string { + mut kinds_str := '' + for i, k in kinds { + kinds_str += k.str() + if i < kinds.len - 1 { + kinds_str += '_' + } + } + return kinds_str +} + +pub struct Struct { +pub: + attrs []Attr +pub mut: + embeds []Type + fields []StructField + is_typedef bool // C. [typedef] + is_union bool + is_heap bool + is_generic bool + generic_types []Type + concrete_types []Type + parent_type Type +} + +// instantiation of a generic struct +pub struct GenericInst { +pub mut: + parent_idx int // idx of the base generic struct + concrete_types []Type // concrete types, e.g. +} + +pub struct Interface { +pub mut: + types []Type // all types that implement this interface + fields []StructField + methods []Fn + ifaces []Type + // `I1 is I2` conversions + conversions map[int][]Type + // generic interface support + is_generic bool + generic_types []Type + concrete_types []Type + parent_type Type +} + +pub struct Enum { +pub: + vals []string + is_flag bool + is_multi_allowed bool +} + +pub struct Alias { +pub: + parent_type Type + language Language + is_import bool +} + +pub struct Aggregate { +mut: + fields []StructField // used for faster lookup inside the module +pub: + types []Type +} + +/* +pub struct Field { +pub: + name string +pub mut: + typ Type + default_expr Expr + has_default_expr bool + default_expr_typ Type + default_val string + attrs []Attr + is_pub bool + is_mut bool + is_global bool +} +*/ + +pub fn (f &StructField) equals(o &StructField) bool { + // TODO: f.is_mut == o.is_mut was removed here to allow read only access + // to (mut/not mut), but otherwise equal fields; some other new checks are needed: + // - if node is declared mut, and we mutate node.stmts, all stmts fields must be mutable + // - same goes for pub and global, if we call the field from another module + return f.name == o.name && f.typ == o.typ && f.is_pub == o.is_pub && f.is_global == o.is_global +} + +pub struct Array { +pub: + nr_dims int +pub mut: + elem_type Type +} + +pub struct ArrayFixed { +pub: + size int + size_expr Expr // used by fmt for e.g. ´[my_const]byte´ +pub mut: + elem_type Type +} + +pub struct Chan { +pub mut: + elem_type Type + is_mut bool +} + +pub struct Thread { +pub mut: + return_type Type +} + +pub struct Map { +pub mut: + key_type Type + value_type Type +} + +pub struct SumType { +pub: + variants []Type +pub mut: + fields []StructField + found_fields bool + // generic sumtype support + is_generic bool + generic_types []Type + concrete_types []Type + parent_type Type +} + +// human readable type name +pub fn (t &Table) type_to_str(typ Type) string { + return t.type_to_str_using_aliases(typ, map[string]string{}) +} + +// type name in code (for builtin) +pub fn (mytable &Table) type_to_code(t Type) string { + match t { + ast.int_literal_type, ast.float_literal_type { return mytable.get_type_symbol(t).kind.str() } + else { return mytable.type_to_str_using_aliases(t, map[string]string{}) } + } +} + +// import_aliases is a map of imported symbol aliases 'module.Type' => 'Type' +pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]string) string { + sym := t.get_type_symbol(typ) + mut res := sym.name + // Note, that the duplication of code in some of the match branches here + // is VERY deliberate. DO NOT be tempted to use `else {}` instead, because + // that strongly reduces the usefullness of the exhaustive checking that + // match does. + // Using else{} here led to subtle bugs in vfmt discovered *months* + // after the original code was written. + // It is important that each case here is handled *explicitly* and + // *clearly*, and that when a new kind is added, it should also be handled + // explicitly. + match sym.kind { + .int_literal, .float_literal { + res = sym.name + } + .i8, .i16, .int, .i64, .byte, .u8, .u16, .u32, .u64, .f32, .f64, .char, .rune, .string, + .bool, .none_, .byteptr, .voidptr, .charptr { + // primitive types + if sym.kind == .byteptr { + res = '&byte' + } else if sym.kind == .charptr { + res = '&char' + } else { + res = sym.kind.str() + } + } + .array { + if typ == ast.array_type { + return 'array' + } + if typ.has_flag(.variadic) { + res = t.type_to_str_using_aliases(t.value_type(typ), import_aliases) + } else { + if sym.info is Array { + elem_str := t.type_to_str_using_aliases(sym.info.elem_type, import_aliases) + res = '[]$elem_str' + } else { + res = 'array' + } + } + } + .array_fixed { + info := sym.info as ArrayFixed + elem_str := t.type_to_str_using_aliases(info.elem_type, import_aliases) + if info.size_expr is EmptyExpr { + res = '[$info.size]$elem_str' + } else { + res = '[$info.size_expr]$elem_str' + } + } + .chan { + // TODO currently the `chan` struct in builtin is not considered a struct but a chan + if sym.mod != 'builtin' && sym.name != 'chan' { + info := sym.info as Chan + mut elem_type := info.elem_type + mut mut_str := '' + if info.is_mut { + mut_str = 'mut ' + elem_type = elem_type.set_nr_muls(elem_type.nr_muls() - 1) + } + elem_str := t.type_to_str_using_aliases(elem_type, import_aliases) + res = 'chan $mut_str$elem_str' + } + } + .function { + info := sym.info as FnType + if !t.is_fmt { + res = t.fn_signature(info.func, type_only: true) + } else { + if res.starts_with('fn (') { + // fn foo () + has_names := info.func.params.any(it.name.len > 0) + res = t.fn_signature_using_aliases(info.func, import_aliases, + type_only: !has_names + ) + } else { + // FnFoo + res = t.shorten_user_defined_typenames(res, import_aliases) + } + } + } + .map { + if int(typ) == ast.map_type_idx { + return 'map' + } + info := sym.info as Map + key_str := t.type_to_str_using_aliases(info.key_type, import_aliases) + val_str := t.type_to_str_using_aliases(info.value_type, import_aliases) + res = 'map[$key_str]$val_str' + } + .multi_return { + res = '(' + info := sym.info as MultiReturn + for i, typ2 in info.types { + if i > 0 { + res += ', ' + } + res += t.type_to_str_using_aliases(typ2, import_aliases) + } + res += ')' + } + .struct_, .interface_, .sum_type { + if typ.has_flag(.generic) { + match sym.info { + Struct, Interface, SumType { + res += '<' + for i, gtyp in sym.info.generic_types { + res += t.get_type_symbol(gtyp).name + if i != sym.info.generic_types.len - 1 { + res += ', ' + } + } + res += '>' + } + else {} + } + } else { + res = t.shorten_user_defined_typenames(res, import_aliases) + } + } + .generic_inst { + info := sym.info as GenericInst + res = sym.name.all_before('<') + res += '<' + for i, ctyp in info.concrete_types { + res += t.get_type_symbol(ctyp).name + if i != info.concrete_types.len - 1 { + res += ', ' + } + } + res += '>' + res = t.shorten_user_defined_typenames(res, import_aliases) + } + .void { + if typ.has_flag(.optional) { + return '?' + } + return 'void' + } + .thread { + rtype := sym.thread_info().return_type + if rtype != 1 { + res = 'thread ' + t.type_to_str_using_aliases(rtype, import_aliases) + } + } + .alias, .any, .size_t, .aggregate, .placeholder, .enum_ { + res = t.shorten_user_defined_typenames(res, import_aliases) + } + } + mut nr_muls := typ.nr_muls() + if typ.has_flag(.shared_f) { + nr_muls-- + res = 'shared ' + res + } + if nr_muls > 0 && !typ.has_flag(.variadic) { + res = strings.repeat(`&`, nr_muls) + res + } + if typ.has_flag(.optional) { + res = '?' + res + } + return res +} + +fn (t Table) shorten_user_defined_typenames(originalname string, import_aliases map[string]string) string { + mut res := originalname + if t.cmod_prefix.len > 0 && res.starts_with(t.cmod_prefix) { + // cur_mod.Type => Type + res = res.replace_once(t.cmod_prefix, '') + } else if res in import_aliases { + res = import_aliases[res] + } else { + // types defined by the user + // mod.submod.submod2.Type => submod2.Type + mut parts := res.split('.') + if parts.len > 1 { + ind := parts.len - 2 + if t.is_fmt { + // Rejoin the module parts for correct usage of aliases + parts[ind] = parts[..ind + 1].join('.') + } + if parts[ind] in import_aliases { + parts[ind] = import_aliases[parts[ind]] + } + + res = parts[ind..].join('.') + } else { + res = parts[0] + } + } + return res +} + +pub struct FnSignatureOpts { + skip_receiver bool + type_only bool +} + +pub fn (t &Table) fn_signature(func &Fn, opts FnSignatureOpts) string { + return t.fn_signature_using_aliases(func, map[string]string{}, opts) +} + +pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string]string, opts FnSignatureOpts) string { + mut sb := strings.new_builder(20) + if !opts.skip_receiver { + sb.write_string('fn ') + // TODO write receiver + } + if !opts.type_only { + sb.write_string('$func.name') + } + sb.write_string('(') + start := int(opts.skip_receiver) + for i in start .. func.params.len { + if i != start { + sb.write_string(', ') + } + param := func.params[i] + mut typ := param.typ + if param.is_mut { + typ = typ.deref() + sb.write_string('mut ') + } + if !opts.type_only { + sb.write_string('$param.name ') + } + styp := t.type_to_str_using_aliases(typ, import_aliases) + if i == func.params.len - 1 && func.is_variadic { + sb.write_string('...$styp') + } else { + sb.write_string('$styp') + } + } + sb.write_string(')') + if func.return_type != ast.void_type { + sb.write_string(' ${t.type_to_str_using_aliases(func.return_type, import_aliases)}') + } + return sb.str() +} + +pub fn (t &TypeSymbol) embed_name() string { + // main.Abc => Abc + mut embed_name := t.name.split('.').last() + // remove generic part from name + // Abc => Abc + if embed_name.contains('<') { + embed_name = embed_name.split('<')[0] + } + return embed_name +} + +pub fn (t &TypeSymbol) has_method(name string) bool { + t.find_method(name) or { return false } + return true +} + +pub fn (t &TypeSymbol) find_method(name string) ?Fn { + for method in t.methods { + if method.name == name { + return method + } + } + return none +} + +pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn { + if m := t.find_method(name) { + return m + } + mut table := global_table + match t.info { + Struct, Interface, SumType { + if t.info.parent_type.has_flag(.generic) { + parent_sym := table.get_type_symbol(t.info.parent_type) + if x := parent_sym.find_method(name) { + match parent_sym.info { + Struct, Interface, SumType { + mut method := x + generic_names := parent_sym.info.generic_types.map(table.get_type_symbol(it).name) + if rt := table.resolve_generic_to_concrete(method.return_type, + generic_names, t.info.concrete_types) + { + method.return_type = rt + } + method.params = method.params.clone() + for mut param in method.params { + if pt := table.resolve_generic_to_concrete(param.typ, + generic_names, t.info.concrete_types) + { + param.typ = pt + } + } + method.generic_names.clear() + return method + } + else {} + } + } else { + } + } + } + else {} + } + return none +} + +pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) { + mut has_str_method := false + mut expects_ptr := false + mut nr_args := 0 + if sym_str_method := t.find_method('str') { + has_str_method = true + nr_args = sym_str_method.params.len + if nr_args > 0 { + expects_ptr = sym_str_method.params[0].typ.is_ptr() + } + } + return has_str_method, expects_ptr, nr_args +} + +pub fn (t &TypeSymbol) find_field(name string) ?StructField { + match t.info { + Aggregate { return t.info.find_field(name) } + Struct { return t.info.find_field(name) } + Interface { return t.info.find_field(name) } + SumType { return t.info.find_field(name) } + else { return none } + } +} + +fn (a &Aggregate) find_field(name string) ?StructField { + for field in a.fields { + if field.name == name { + return field + } + } + return none +} + +pub fn (i &Interface) find_field(name string) ?StructField { + for field in i.fields { + if field.name == name { + return field + } + } + return none +} + +pub fn (i &Interface) find_method(name string) ?Fn { + for method in i.methods { + if method.name == name { + return method + } + } + return none +} + +pub fn (i &Interface) has_method(name string) bool { + if _ := i.find_method(name) { + return true + } + return false +} + +pub fn (s Struct) find_field(name string) ?StructField { + for field in s.fields { + if field.name == name { + return field + } + } + return none +} + +pub fn (s Struct) get_field(name string) StructField { + if field := s.find_field(name) { + return field + } + panic('unknown field `$name`') +} + +pub fn (s &SumType) find_field(name string) ?StructField { + for field in s.fields { + if field.name == name { + return field + } + } + return none +} + +pub fn (i Interface) defines_method(name string) bool { + for method in i.methods { + if method.name == name { + return true + } + } + return false +} diff --git a/v_windows/v/vlib/v/ast/types_test.v b/v_windows/v/vlib/v/ast/types_test.v new file mode 100644 index 0000000..67e1adc --- /dev/null +++ b/v_windows/v/vlib/v/ast/types_test.v @@ -0,0 +1,81 @@ +import v.ast + +fn test_idx() { + mut t := ast.new_type(ast.void_type_idx) + assert t.idx() == ast.void_type_idx + t = ast.new_type(ast.i8_type_idx) + assert t.idx() == ast.i8_type_idx +} + +fn test_muls() { + mut t := ast.new_type(ast.void_type_idx) + idx := t.idx() + assert t.nr_muls() == 0 + for i in 0 .. 32 { + t = t.set_nr_muls(i) + assert t.nr_muls() == i + } + t = t.set_nr_muls(0) + assert t.nr_muls() == 0 + assert t.is_ptr() == false + t = t.to_ptr() + assert t.nr_muls() == 1 + assert t.is_ptr() == true + t = t.to_ptr() + assert t.nr_muls() == 2 + assert t.is_ptr() == true + t = t.deref() + assert t.nr_muls() == 1 + assert t.is_ptr() == true + t = t.deref() + assert t.nr_muls() == 0 + assert t.is_ptr() == false + assert t.idx() == idx +} + +fn test_flags() { + mut t := ast.new_type(ast.void_type_idx) + idx := t.idx() + nr_muls := t.nr_muls() + t = t.set_flag(ast.TypeFlag.optional) + assert t.has_flag(ast.TypeFlag.optional) == true + assert t.has_flag(ast.TypeFlag.variadic) == false + assert t.has_flag(ast.TypeFlag.generic) == false + t = t.set_flag(ast.TypeFlag.variadic) + assert t.has_flag(ast.TypeFlag.optional) == true + assert t.has_flag(ast.TypeFlag.variadic) == true + assert t.has_flag(ast.TypeFlag.generic) == false + t = t.set_flag(ast.TypeFlag.generic) + assert t.has_flag(ast.TypeFlag.optional) == true + assert t.has_flag(ast.TypeFlag.variadic) == true + assert t.has_flag(ast.TypeFlag.generic) == true + assert t.idx() == idx + assert t.nr_muls() == nr_muls + t = t.clear_flag(ast.TypeFlag.optional) + assert t.has_flag(ast.TypeFlag.optional) == false + assert t.has_flag(ast.TypeFlag.variadic) == true + assert t.has_flag(ast.TypeFlag.generic) == true + t = t.clear_flag(ast.TypeFlag.variadic) + assert t.has_flag(ast.TypeFlag.optional) == false + assert t.has_flag(ast.TypeFlag.variadic) == false + assert t.has_flag(ast.TypeFlag.generic) == true + t = t.clear_flag(ast.TypeFlag.generic) + assert t.has_flag(ast.TypeFlag.optional) == false + assert t.has_flag(ast.TypeFlag.variadic) == false + assert t.has_flag(ast.TypeFlag.generic) == false + assert t.idx() == idx + assert t.nr_muls() == nr_muls +} + +fn test_derive() { + mut t := ast.new_type(ast.i8_type_idx) + t = t.set_flag(ast.TypeFlag.generic) + t = t.set_flag(ast.TypeFlag.variadic) + t = t.set_nr_muls(10) + mut t2 := ast.new_type(ast.i16_type_idx) + t2 = t2.derive(t) + assert t2.has_flag(ast.TypeFlag.optional) == false + assert t2.has_flag(ast.TypeFlag.variadic) == true + assert t2.has_flag(ast.TypeFlag.generic) == true + assert t2.nr_muls() == 10 +} diff --git a/v_windows/v/vlib/v/ast/walker/walker.v b/v_windows/v/vlib/v/ast/walker/walker.v new file mode 100644 index 0000000..75e6c17 --- /dev/null +++ b/v_windows/v/vlib/v/ast/walker/walker.v @@ -0,0 +1,37 @@ +module walker + +import v.ast + +// Visitor defines a visit method which is invoked by the walker in each node it encounters. +pub interface Visitor { + visit(node &ast.Node) ? +} + +pub type InspectorFn = fn (node &ast.Node, data voidptr) bool + +struct Inspector { + inspector_callback InspectorFn +mut: + data voidptr +} + +pub fn (i &Inspector) visit(node &ast.Node) ? { + if i.inspector_callback(node, i.data) { + return + } + return error('') +} + +// inspect traverses and checks the AST node on a depth-first order and based on the data given +pub fn inspect(node &ast.Node, data voidptr, inspector_callback InspectorFn) { + walk(Inspector{inspector_callback, data}, node) +} + +// walk traverses the AST using the given visitor +pub fn walk(visitor Visitor, node &ast.Node) { + visitor.visit(node) or { return } + children := node.children() + for child_node in children { + walk(visitor, &child_node) + } +} diff --git a/v_windows/v/vlib/v/ast/walker/walker_test.v b/v_windows/v/vlib/v/ast/walker/walker_test.v new file mode 100644 index 0000000..6d90fe9 --- /dev/null +++ b/v_windows/v/vlib/v/ast/walker/walker_test.v @@ -0,0 +1,66 @@ +import v.ast +import v.ast.walker +import v.parser +import v.pref + +fn parse_text(text string) &ast.File { + tbl := ast.new_table() + prefs := pref.new_preferences() + return parser.parse_text(text, '', tbl, .skip_comments, prefs) +} + +struct NodeByOffset { + pos int +mut: + node ast.Node +} + +fn (mut n NodeByOffset) visit(node &ast.Node) ? { + node_pos := node.position() + if n.pos >= node_pos.pos && n.pos <= node_pos.pos + node_pos.len && node !is ast.File { + n.node = node + return error('') + } + return +} + +fn test_walk() { + source := ' +module main +struct Foo { + name string +} + ' + file := parse_text(source) + mut nbo := NodeByOffset{ + pos: 13 + } + walker.walk(nbo, file) + assert nbo.node is ast.Stmt + stmt := nbo.node as ast.Stmt + assert stmt is ast.StructDecl +} + +fn test_inspect() { + source := ' +module main + ' + file := parse_text(source) + walker.inspect(file, voidptr(0), fn (node &ast.Node, data voidptr) bool { + // Second visit must be ast.Stmt + if node is ast.Stmt { + if node !is ast.Module { + // Proceed to another node + return false + } + assert node is ast.Module + mod := node as ast.Module + assert mod.name == 'main' + return false + } + assert node is ast.File + // True means that the inspector must now + // inspect the ast.File's children + return true + }) +} diff --git a/v_windows/v/vlib/v/builder/builder.v b/v_windows/v/vlib/v/builder/builder.v new file mode 100644 index 0000000..12998b2 --- /dev/null +++ b/v_windows/v/vlib/v/builder/builder.v @@ -0,0 +1,467 @@ +module builder + +import os +import v.token +import v.pref +import v.util +import v.ast +import v.vmod +import v.checker +import v.transformer +import v.parser +import v.markused +import v.depgraph +import v.callgraph +import v.dotgraph + +pub struct Builder { +pub: + compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .` + module_path string +mut: + pref &pref.Preferences + checker &checker.Checker + transformer &transformer.Transformer + out_name_c string + out_name_js string + stats_lines int // size of backend generated source code in lines + stats_bytes int // size of backend generated source code in bytes +pub mut: + module_search_paths []string + parsed_files []&ast.File + cached_msvc MsvcResult + table &ast.Table + ccoptions CcompilerOptions +} + +pub fn new_builder(pref &pref.Preferences) Builder { + rdir := os.real_path(pref.path) + compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) } + mut table := ast.new_table() + table.is_fmt = false + if pref.use_color == .always { + util.emanager.set_support_color(true) + } + if pref.use_color == .never { + util.emanager.set_support_color(false) + } + msvc := find_msvc(pref.m64) or { + if pref.ccompiler == 'msvc' { + // verror('Cannot find MSVC on this OS') + } + MsvcResult{ + valid: false + } + } + util.timing_set_should_print(pref.show_timings || pref.is_verbose) + if pref.show_callgraph || pref.show_depgraph { + dotgraph.start_digraph() + } + return Builder{ + pref: pref + table: table + checker: checker.new_checker(table, pref) + transformer: transformer.new_transformer(pref) + compiled_dir: compiled_dir + cached_msvc: msvc + } +} + +pub fn (mut b Builder) front_stages(v_files []string) ? { + util.timing_start('PARSE') + b.parsed_files = parser.parse_files(v_files, b.table, b.pref) + b.parse_imports() + mut timers := util.get_timers() + timers.show('SCAN') + timers.show('PARSE') + timers.show_if_exists('PARSE stmt') + if b.pref.only_check_syntax { + return error('stop_after_parser') + } +} + +pub fn (mut b Builder) middle_stages() ? { + util.timing_start('CHECK') + b.checker.generic_insts_to_concrete() + b.checker.check_files(b.parsed_files) + util.timing_measure('CHECK') + b.print_warnings_and_errors() + util.timing_start('TRANSFORM') + b.transformer.transform_files(b.parsed_files) + util.timing_measure('TRANSFORM') + // + b.table.complete_interface_check() + if b.pref.skip_unused { + markused.mark_used(mut b.table, b.pref, b.parsed_files) + } + if b.pref.show_callgraph { + callgraph.show(mut b.table, b.pref, b.parsed_files) + } +} + +pub fn (mut b Builder) front_and_middle_stages(v_files []string) ? { + b.front_stages(v_files) ? + b.middle_stages() ? +} + +// parse all deps from already parsed files +pub fn (mut b Builder) parse_imports() { + mut done_imports := []string{} + if b.pref.is_vsh { + done_imports << 'os' + } + // TODO (joe): decide if this is correct solution. + // in the case of building a module, the actual module files + // are passed via cmd line, so they have already been parsed + // by this stage. note that if one files from a module was + // parsed (but not all of them), then this will cause a problem. + // we could add a list of parsed files instead, but I think + // there is a better solution all around, I will revisit this. + // NOTE: there is a very similar occurance with the way + // internal module test's work, and this was the reason there + // were issues with duplicate declarations, so we should sort + // that out in a similar way. + for file in b.parsed_files { + if file.mod.name != 'main' && file.mod.name !in done_imports { + done_imports << file.mod.name + } + } + // NB: b.parsed_files is appended in the loop, + // so we can not use the shorter `for in` form. + for i := 0; i < b.parsed_files.len; i++ { + ast_file := b.parsed_files[i] + for imp in ast_file.imports { + mod := imp.mod + if mod == 'builtin' { + error_with_pos('cannot import module "builtin"', ast_file.path, imp.pos) + break + } + if mod in done_imports { + continue + } + import_path := b.find_module_path(mod, ast_file.path) or { + // v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) + // break + error_with_pos('cannot import module "$mod" (not found)', ast_file.path, + imp.pos) + break + } + v_files := b.v_files_from_dir(import_path) + if v_files.len == 0 { + // v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod)) + error_with_pos('cannot import module "$mod" (no .v files in "$import_path")', + ast_file.path, imp.pos) + } + // Add all imports referenced by these libs + parsed_files := parser.parse_files(v_files, b.table, b.pref) + for file in parsed_files { + mut name := file.mod.name + if name == '' { + name = file.mod.short_name + } + if name != mod { + // v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 1 + error_with_pos('bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`', + ast_file.path, imp.pos) + } + } + b.parsed_files << parsed_files + done_imports << mod + } + } + b.resolve_deps() + // + if b.pref.print_v_files { + for p in b.parsed_files { + println(p.path) + } + exit(0) + } +} + +pub fn (mut b Builder) resolve_deps() { + graph := b.import_graph() + deps_resolved := graph.resolve() + if b.pref.is_verbose { + eprintln('------ resolved dependencies graph: ------') + eprintln(deps_resolved.display()) + eprintln('------------------------------------------') + } + if b.pref.show_depgraph { + depgraph.show(deps_resolved, b.pref.path) + } + cycles := deps_resolved.display_cycles() + if cycles.len > 1 { + verror('error: import cycle detected between the following modules: \n' + cycles) + } + mut mods := []string{} + for node in deps_resolved.nodes { + mods << node.name + } + if b.pref.is_verbose { + eprintln('------ imported modules: ------') + eprintln(mods.str()) + eprintln('-------------------------------') + } + mut reordered_parsed_files := []&ast.File{} + for m in mods { + for pf in b.parsed_files { + if m == pf.mod.name { + reordered_parsed_files << pf + // eprintln('pf.mod.name: $pf.mod.name | pf.path: $pf.path') + } + } + } + b.table.modules = mods + b.parsed_files = reordered_parsed_files +} + +// graph of all imported modules +pub fn (b &Builder) import_graph() &depgraph.DepGraph { + builtins := util.builtin_module_parts.clone() + mut graph := depgraph.new_dep_graph() + for p in b.parsed_files { + // eprintln('p.path: $p.path') + mut deps := []string{} + if p.mod.name !in builtins { + deps << 'builtin' + if b.pref.backend == .c { + // TODO JavaScript backend doesn't handle os for now + if b.pref.is_vsh && p.mod.name != 'os' { + deps << 'os' + } + } + } + for m in p.imports { + if m.mod == p.mod.name { + continue + } + deps << m.mod + } + graph.add(p.mod.name, deps) + } + $if trace_import_graph ? { + eprintln(graph.display()) + } + return graph +} + +pub fn (b Builder) v_files_from_dir(dir string) []string { + if !os.exists(dir) { + if dir == 'compiler' && os.is_dir('vlib') { + println('looks like you are trying to build V with an old command') + println('use `v -o v cmd/v` instead of `v -o v compiler`') + } + verror("$dir doesn't exist") + } else if !os.is_dir(dir) { + verror("$dir isn't a directory!") + } + mut files := os.ls(dir) or { panic(err) } + if b.pref.is_verbose { + println('v_files_from_dir ("$dir")') + } + return b.pref.should_compile_filtered_files(dir, files) +} + +pub fn (b Builder) log(s string) { + if b.pref.is_verbose { + println(s) + } +} + +pub fn (b Builder) info(s string) { + if b.pref.is_verbose { + println(s) + } +} + +[inline] +fn module_path(mod string) string { + // submodule support + return mod.replace('.', os.path_separator) +} + +// TODO: try to merge this & util.module functions to create a +// reliable multi use function. see comments in util/module.v +pub fn (b &Builder) find_module_path(mod string, fpath string) ?string { + // support @VROOT/v.mod relative paths: + mut mcache := vmod.get_cache() + vmod_file_location := mcache.get_by_file(fpath) + mod_path := module_path(mod) + mut module_lookup_paths := []string{} + if vmod_file_location.vmod_file.len != 0 + && vmod_file_location.vmod_folder !in b.module_search_paths { + module_lookup_paths << vmod_file_location.vmod_folder + } + module_lookup_paths << b.module_search_paths + module_lookup_paths << os.getwd() + // go up through parents looking for modules a folder. + // we need a proper solution that works most of the time. look at vdoc.get_parent_mod + if fpath.contains(os.path_separator + 'modules' + os.path_separator) { + parts := fpath.split(os.path_separator) + for i := parts.len - 2; i >= 0; i-- { + if parts[i] == 'modules' { + module_lookup_paths << parts[0..i + 1].join(os.path_separator) + break + } + } + } + for search_path in module_lookup_paths { + try_path := os.join_path(search_path, mod_path) + if b.pref.is_verbose { + println(' >> trying to find $mod in $try_path ..') + } + if os.is_dir(try_path) { + if b.pref.is_verbose { + println(' << found $try_path .') + } + return try_path + } + } + // look up through parents + path_parts := fpath.split(os.path_separator) + for i := path_parts.len - 2; i > 0; i-- { + p1 := path_parts[0..i].join(os.path_separator) + try_path := os.join_path(p1, mod_path) + if b.pref.is_verbose { + println(' >> trying to find $mod in $try_path ..') + } + if os.is_dir(try_path) { + return try_path + } + } + smodule_lookup_paths := module_lookup_paths.join(', ') + return error('module "$mod" not found in:\n$smodule_lookup_paths') +} + +fn (b &Builder) show_total_warns_and_errors_stats() { + if b.checker.nr_errors == 0 && b.checker.nr_warnings == 0 && b.checker.nr_notices == 0 { + return + } + if b.pref.is_stats { + estring := util.bold(b.checker.errors.len.str()) + wstring := util.bold(b.checker.warnings.len.str()) + nstring := util.bold(b.checker.nr_notices.str()) + println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices') + } +} + +fn (b &Builder) print_warnings_and_errors() { + defer { + b.show_total_warns_and_errors_stats() + } + if b.pref.output_mode == .silent { + if b.checker.nr_errors > 0 { + exit(1) + } + return + } + if b.pref.is_verbose && b.checker.nr_warnings > 1 { + println('$b.checker.nr_warnings warnings') + } + if b.pref.is_verbose && b.checker.nr_notices > 1 { + println('$b.checker.nr_notices notices') + } + if b.checker.nr_notices > 0 && !b.pref.skip_warnings { + for err in b.checker.notices { + kind := if b.pref.is_verbose { + '$err.reporter notice #$b.checker.nr_notices:' + } else { + 'notice:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + } + if b.checker.nr_warnings > 0 && !b.pref.skip_warnings { + for err in b.checker.warnings { + kind := if b.pref.is_verbose { + '$err.reporter warning #$b.checker.nr_warnings:' + } else { + 'warning:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + } + // + if b.pref.is_verbose && b.checker.nr_errors > 1 { + println('$b.checker.nr_errors errors') + } + if b.checker.nr_errors > 0 { + for err in b.checker.errors { + kind := if b.pref.is_verbose { + '$err.reporter error #$b.checker.nr_errors:' + } else { + 'error:' + } + ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) + eprintln(ferror) + if err.details.len > 0 { + eprintln('Details: $err.details') + } + } + b.show_total_warns_and_errors_stats() + exit(1) + } + if b.table.redefined_fns.len > 0 { + mut total_conflicts := 0 + for fn_name in b.table.redefined_fns { + // Find where this function was already declared + mut redefines := []FunctionRedefinition{} + mut redefine_conflicts := map[string]int{} + for file in b.parsed_files { + for stmt in file.stmts { + if stmt is ast.FnDecl { + if stmt.name == fn_name { + fheader := stmt.stringify(b.table, 'main', map[string]string{}) + redefines << FunctionRedefinition{ + fpath: file.path + fline: stmt.pos.line_nr + f: stmt + fheader: fheader + } + redefine_conflicts[fheader]++ + } + } + } + } + if redefines.len > 0 { + eprintln('redefinition of function `$fn_name`') + for redefine in redefines { + eprintln(util.formatted_error('conflicting declaration:', redefine.fheader, + redefine.fpath, redefine.f.pos)) + } + total_conflicts++ + } + } + if total_conflicts > 0 { + b.show_total_warns_and_errors_stats() + exit(1) + } + } +} + +struct FunctionRedefinition { + fpath string + fline int + fheader string + f ast.FnDecl +} + +fn error_with_pos(s string, fpath string, pos token.Position) { + ferror := util.formatted_error('builder error:', s, fpath, pos) + eprintln(ferror) + exit(1) +} + +[noreturn] +fn verror(s string) { + util.verror('builder error', s) +} diff --git a/v_windows/v/vlib/v/builder/c.v b/v_windows/v/vlib/v/builder/c.v new file mode 100644 index 0000000..20f9f67 --- /dev/null +++ b/v_windows/v/vlib/v/builder/c.v @@ -0,0 +1,63 @@ +module builder + +import os +import v.pref +import v.util +import v.gen.c + +pub fn (mut b Builder) gen_c(v_files []string) string { + b.front_and_middle_stages(v_files) or { return '' } + // TODO: move gen.cgen() to c.gen() + util.timing_start('C GEN') + res := c.gen(b.parsed_files, b.table, b.pref) + util.timing_measure('C GEN') + // println('cgen done') + // println(res) + return res +} + +pub fn (mut b Builder) build_c(v_files []string, out_file string) { + b.out_name_c = out_file + b.pref.out_name_c = os.real_path(out_file) + b.info('build_c($out_file)') + output2 := b.gen_c(v_files) + os.write_file(out_file, output2) or { panic(err) } + if b.pref.is_stats { + b.stats_lines = output2.count('\n') + 1 + b.stats_bytes = output2.len + } +} + +pub fn (mut b Builder) compile_c() { + if os.user_os() != 'windows' && b.pref.ccompiler == 'msvc' && !b.pref.out_name.ends_with('.c') { + verror('Cannot build with msvc on $os.user_os()') + } + // cgen.genln('// Generated by V') + // println('compile2()') + if b.pref.is_verbose { + println('all .v files before:') + // println(files) + } + $if windows { + b.find_win_cc() or { verror(no_compiler_error) } + // TODO Probably extend this to other OS's? + } + // v1 compiler files + // v.add_v_files_to_compile() + // v.files << v.dir + // v2 compiler + // b.set_module_lookup_paths() + mut files := b.get_builtin_files() + files << b.get_user_files() + b.set_module_lookup_paths() + if b.pref.is_verbose { + println('all .v files:') + println(files) + } + mut out_name_c := b.get_vtmp_filename(b.pref.out_name, '.tmp.c') + if b.pref.is_shared { + out_name_c = b.get_vtmp_filename(b.pref.out_name, '.tmp.so.c') + } + b.build_c(files, out_name_c) + b.cc() +} diff --git a/v_windows/v/vlib/v/builder/cc.v b/v_windows/v/vlib/v/builder/cc.v new file mode 100644 index 0000000..b7b0d13 --- /dev/null +++ b/v_windows/v/vlib/v/builder/cc.v @@ -0,0 +1,1021 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builder + +import os +import v.cflag +import v.pref +import v.util +import v.vcache +import term + +const ( + c_verror_message_marker = 'VERROR_MESSAGE ' + c_error_info = ' +================== +C error. This should never happen. + +This is a compiler bug, please report it using `v bug file.v`. + +https://github.com/vlang/v/issues/new/choose + +You can also use #help on Discord: https://discord.gg/vlang +' + no_compiler_error = ' +================== +Error: no C compiler detected. + +You can find instructions on how to install one in the V wiki: +https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows + +If you think you have one installed, make sure it is in your PATH. +If you do have one in your PATH, please raise an issue on GitHub: +https://github.com/vlang/v/issues/new/choose + +You can also use `v doctor`, to see what V knows about your current environment. + +You can also seek #help on Discord: https://discord.gg/vlang +' +) + +const ( + mingw_cc = 'x86_64-w64-mingw32-gcc' +) + +fn (mut v Builder) find_win_cc() ? { + $if !windows { + return + } + ccompiler_version_res := os.execute('$v.pref.ccompiler -v') + if ccompiler_version_res.exit_code != 0 { + if v.pref.is_verbose { + println('$v.pref.ccompiler not found, looking for msvc...') + } + find_msvc(v.pref.m64) or { + if v.pref.is_verbose { + println('msvc not found, looking for thirdparty/tcc...') + } + vpath := os.dir(pref.vexe_path()) + thirdparty_tcc := os.join_path(vpath, 'thirdparty', 'tcc', 'tcc.exe') + tcc_version_res := os.execute('$thirdparty_tcc -v') + if tcc_version_res.exit_code != 0 { + if v.pref.is_verbose { + println('tcc not found') + } + return error('tcc not found') + } + v.pref.ccompiler = thirdparty_tcc + v.pref.ccompiler_type = .tinyc + return + } + v.pref.ccompiler = 'msvc' + v.pref.ccompiler_type = .msvc + return + } + v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) +} + +fn (mut v Builder) show_c_compiler_output(res os.Result) { + println('======== C Compiler output ========') + println(res.output) + println('=================================') +} + +fn (mut v Builder) post_process_c_compiler_output(res os.Result) { + if res.exit_code == 0 { + if v.pref.reuse_tmpc { + return + } + for tmpfile in v.pref.cleanup_files { + if os.is_file(tmpfile) { + if v.pref.is_verbose { + eprintln('>> remove tmp file: $tmpfile') + } + os.rm(tmpfile) or { panic(err) } + } + } + return + } + for emsg_marker in [builder.c_verror_message_marker, 'error: include file '] { + if res.output.contains(emsg_marker) { + emessage := res.output.all_after(emsg_marker).all_before('\n').all_before('\r').trim_right('\r\n') + verror(emessage) + } + } + if v.pref.is_debug { + eword := 'error:' + khighlight := if term.can_show_color_on_stdout() { term.red(eword) } else { eword } + println(res.output.trim_right('\r\n').replace(eword, khighlight)) + } else { + if res.output.len < 30 { + println(res.output) + } else { + elines := error_context_lines(res.output, 'error:', 1, 12) + println('==================') + for eline in elines { + println(eline) + } + println('...') + println('==================') + println('(Use `v -cg` to print the entire error message)\n') + } + } + verror(builder.c_error_info) +} + +fn (mut v Builder) rebuild_cached_module(vexe string, imp_path string) string { + res := v.pref.cache_manager.exists('.o', imp_path) or { + if v.pref.is_verbose { + println('Cached $imp_path .o file not found... Building .o file for $imp_path') + } + // do run `v build-module x` always in main vfolder; x can be a relative path + pwd := os.getwd() + vroot := os.dir(vexe) + os.chdir(vroot) or {} + boptions := v.pref.build_options.join(' ') + rebuild_cmd := '$vexe $boptions build-module $imp_path' + vcache.dlog('| Builder.' + @FN, 'vexe: $vexe | imp_path: $imp_path | rebuild_cmd: $rebuild_cmd') + os.system(rebuild_cmd) + rebuilded_o := v.pref.cache_manager.exists('.o', imp_path) or { + panic('could not rebuild cache module for $imp_path, error: $err.msg') + } + os.chdir(pwd) or {} + return rebuilded_o + } + return res +} + +fn (mut v Builder) show_cc(cmd string, response_file string, response_file_content string) { + if v.pref.is_verbose || v.pref.show_cc { + println('> C compiler cmd: $cmd') + if v.pref.show_cc && !v.pref.no_rsp { + println('> C compiler response file "$response_file":') + println(response_file_content) + } + } +} + +struct CcompilerOptions { +mut: + guessed_compiler string + shared_postfix string // .so, .dll + // + // + debug_mode bool + is_cc_tcc bool + is_cc_gcc bool + is_cc_msvc bool + is_cc_clang bool + // + env_cflags string // prepended *before* everything else + env_ldflags string // appended *after* everything else + // + args []string // ordinary C options like `-O2` + wargs []string // for `-Wxyz` *exclusively* + pre_args []string // options that should go before .o_args + o_args []string // for `-o target` + source_args []string // for `x.tmp.c` + post_args []string // options that should go after .o_args + linker_flags []string // `-lm` +} + +fn (mut v Builder) setup_ccompiler_options(ccompiler string) { + mut ccoptions := CcompilerOptions{} + // + mut debug_options := ['-g'] + mut optimization_options := ['-O2'] + // arguments for the C compiler + ccoptions.args = [v.pref.cflags] + if !v.pref.no_std { + ccoptions.args << '-std=c99 -D_DEFAULT_SOURCE' + } + ccoptions.wargs = [ + '-Wall', + '-Wextra', + '-Werror', + // if anything, these should be a `v vet` warning instead: + '-Wno-unused-parameter', + '-Wno-unused', + '-Wno-type-limits', + '-Wno-tautological-compare', + // these cause various issues: + '-Wno-shadow' /* the V compiler already catches this for user code, and enabling this causes issues with e.g. the `it` variable */, + '-Wno-int-to-pointer-cast' /* gcc version of the above */, + '-Wno-trigraphs' /* see stackoverflow.com/a/8435413 */, + '-Wno-missing-braces' /* see stackoverflow.com/q/13746033 */, + // enable additional warnings: + '-Wno-unknown-warning' /* if a C compiler does not understand a certain flag, it should just ignore it */, + '-Wno-unknown-warning-option' /* clang equivalent of the above */, + '-Wdate-time', + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Winit-self', + '-Winvalid-pch', + '-Wjump-misses-init', + '-Wlogical-op', + '-Wmultichar', + '-Wnested-externs', + '-Wnull-dereference', + '-Wpacked', + '-Wpointer-arith', + '-Wswitch-enum', + ] + if v.pref.os == .ios { + ccoptions.args << '-fobjc-arc' + } + ccoptions.debug_mode = v.pref.is_debug + ccoptions.guessed_compiler = v.pref.ccompiler + if ccoptions.guessed_compiler == 'cc' && v.pref.is_prod { + // deliberately guessing only for -prod builds for performance reasons + ccversion := os.execute('cc --version') + if ccversion.exit_code == 0 { + if ccversion.output.contains('This is free software;') + && ccversion.output.contains('Free Software Foundation, Inc.') { + ccoptions.guessed_compiler = 'gcc' + } + if ccversion.output.contains('clang version ') { + ccoptions.guessed_compiler = 'clang' + } + } + } + // + ccoptions.is_cc_tcc = ccompiler.contains('tcc') || ccoptions.guessed_compiler == 'tcc' + ccoptions.is_cc_gcc = ccompiler.contains('gcc') || ccoptions.guessed_compiler == 'gcc' + ccoptions.is_cc_msvc = ccompiler.contains('msvc') || ccoptions.guessed_compiler == 'msvc' + ccoptions.is_cc_clang = ccompiler.contains('clang') || ccoptions.guessed_compiler == 'clang' + // For C++ we must be very tolerant + if ccoptions.guessed_compiler.contains('++') { + ccoptions.args << '-fpermissive' + ccoptions.args << '-w' + } + if ccoptions.is_cc_clang { + if ccoptions.debug_mode { + debug_options = ['-g', '-O0'] + } + optimization_options = ['-O3'] + mut have_flto := true + $if openbsd { + have_flto = false + } + if have_flto { + optimization_options << '-flto' + } + ccoptions.wargs << [ + '-Wno-tautological-bitwise-compare', + '-Wno-enum-conversion' /* used in vlib/sokol, where C enums in C structs are typed as V structs instead */, + '-Wno-sometimes-uninitialized' /* produced after exhaustive matches */, + '-Wno-int-to-void-pointer-cast', + ] + } + if ccoptions.is_cc_gcc { + if ccoptions.debug_mode { + debug_options = ['-g', '-no-pie'] + } + optimization_options = ['-O3', '-fno-strict-aliasing', '-flto'] + } + // + if ccoptions.debug_mode { + ccoptions.args << debug_options + // $if macos { + // args << '-ferror-limit=5000' + // } + } + if v.pref.is_prod { + // don't warn for vlib tests + if ccoptions.is_cc_tcc && !(v.parsed_files.len > 0 + && v.parsed_files.last().path.contains('vlib')) { + eprintln('Note: tcc is not recommended for -prod builds') + } + ccoptions.args << optimization_options + } + if v.pref.is_prod && !ccoptions.debug_mode { + // sokol and other C libraries that use asserts + // have much better performance when NDEBUG is defined + // See also http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf + ccoptions.args << '-DNDEBUG' + } + if v.pref.sanitize { + ccoptions.args << '-fsanitize=leak' + } + // + ccoptions.shared_postfix = '.so' + $if macos { + ccoptions.shared_postfix = '.dylib' + } $else $if windows { + ccoptions.shared_postfix = '.dll' + } + if v.pref.is_shared { + ccoptions.linker_flags << '-shared' + ccoptions.args << '-fPIC' // -Wl,-z,defs' + } + if v.pref.is_bare { + ccoptions.args << '-fno-stack-protector' + ccoptions.args << '-ffreestanding' + ccoptions.linker_flags << '-static' + ccoptions.linker_flags << '-nostdlib' + } + if ccoptions.debug_mode && os.user_os() != 'windows' && v.pref.build_mode != .build_module { + ccoptions.linker_flags << '-rdynamic' // needed for nicer symbolic backtraces + } + if v.pref.os == .freebsd { + // Needed for -usecache on FreeBSD 13, otherwise we get `ld: error: duplicate symbol: _const_math__bits__de_bruijn32` errors there + if !ccoptions.is_cc_tcc { + ccoptions.linker_flags << '-Wl,--allow-multiple-definition' + } else { + // tcc needs this, otherwise it fails to compile the runetype.h system header with: + // /usr/include/runetype.h:94: error: ';' expected (got "const") + ccoptions.args << '-D__RUNETYPE_INTERNAL' + } + } + + if ccompiler != 'msvc' && v.pref.os != .freebsd { + ccoptions.wargs << '-Werror=implicit-function-declaration' + } + if v.pref.is_liveshared || v.pref.is_livemain { + if (v.pref.os == .linux || os.user_os() == 'linux') && v.pref.build_mode != .build_module { + ccoptions.linker_flags << '-rdynamic' + } + if v.pref.os == .macos || os.user_os() == 'macos' { + ccoptions.args << '-flat_namespace' + } + } + // macOS code can include objective C TODO remove once objective C is replaced with C + if v.pref.os == .macos || v.pref.os == .ios { + if !ccoptions.is_cc_tcc { + ccoptions.source_args << '-x objective-c' + } + } + // The C file we are compiling + ccoptions.source_args << '"$v.out_name_c"' + if v.pref.os == .macos { + ccoptions.source_args << '-x none' + } + // Min macos version is mandatory I think? + if v.pref.os == .macos { + ccoptions.post_args << '-mmacosx-version-min=10.7' + } else if v.pref.os == .ios { + ccoptions.post_args << '-miphoneos-version-min=10.0' + } else if v.pref.os == .windows { + ccoptions.post_args << '-municode' + } + cflags := v.get_os_cflags() + ccoptions.o_args << cflags.c_options_only_object_files() + defines, others, libs := cflags.defines_others_libs() + ccoptions.pre_args << defines + ccoptions.pre_args << others + ccoptions.linker_flags << libs + // TODO: why is this duplicated from above? + if v.pref.use_cache && v.pref.build_mode != .build_module { + // vexe := pref.vexe_path() + // cached_modules := ['builtin', 'os', 'math', 'strconv', 'strings', 'hash'], // , 'strconv.ftoa'] + // for cfile in cached_modules { + // ofile := os.join_path(pref.default_module_path, 'cache', 'vlib', cfile.replace('.', '/') + + // '.o') + // if !os.exists(ofile) { + // println('${cfile}.o is missing. Building...') + // println('$vexe build-module vlib/$cfile') + // os.system('$vexe build-module vlib/$cfile') + // } + // args << ofile + // } + if !ccoptions.is_cc_tcc { + $if linux { + ccoptions.linker_flags << '-Xlinker -z' + ccoptions.linker_flags << '-Xlinker muldefs' + } + } + } + if ccoptions.is_cc_tcc && 'no_backtrace' !in v.pref.compile_defines { + ccoptions.post_args << '-bt25' + } + // Without these libs compilation will fail on Linux + // || os.user_os() == 'linux' + if !v.pref.is_bare && v.pref.build_mode != .build_module + && v.pref.os in [.linux, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .haiku] { + if v.pref.os in [.freebsd, .netbsd] { + // Free/NetBSD: backtrace needs execinfo library while linking + ccoptions.linker_flags << '-lexecinfo' + } + } + ccoptions.env_cflags = os.getenv('CFLAGS') + ccoptions.env_ldflags = os.getenv('LDFLAGS') + $if trace_ccoptions ? { + println('>>> setup_ccompiler_options ccompiler: $ccompiler') + println('>>> setup_ccompiler_options ccoptions: $ccoptions') + } + v.ccoptions = ccoptions + // setup the cache too, so that different compilers/options do not interfere: + v.pref.cache_manager.set_temporary_options(v.thirdparty_object_args(v.ccoptions, [ + ccoptions.guessed_compiler, + ])) +} + +fn (v &Builder) all_args(ccoptions CcompilerOptions) []string { + mut all := []string{} + all << ccoptions.env_cflags + if v.pref.is_cstrict { + all << ccoptions.wargs + } + all << ccoptions.args + all << ccoptions.o_args + all << ccoptions.pre_args + all << ccoptions.source_args + all << ccoptions.post_args + all << ccoptions.linker_flags + all << ccoptions.env_ldflags + return all +} + +fn (v &Builder) thirdparty_object_args(ccoptions CcompilerOptions, middle []string) []string { + mut all := []string{} + all << ccoptions.env_cflags + all << ccoptions.args + all << middle + all << ccoptions.env_ldflags + return all +} + +fn (mut v Builder) setup_output_name() { + if !v.pref.is_shared && v.pref.build_mode != .build_module && os.user_os() == 'windows' + && !v.pref.out_name.ends_with('.exe') { + v.pref.out_name += '.exe' + } + // Output executable name + v.log('cc() isprod=$v.pref.is_prod outname=$v.pref.out_name') + if v.pref.is_shared { + if !v.pref.out_name.ends_with(v.ccoptions.shared_postfix) { + v.pref.out_name += v.ccoptions.shared_postfix + } + } + if v.pref.build_mode == .build_module { + v.pref.out_name = v.pref.cache_manager.postfix_with_key2cpath('.o', v.pref.path) // v.out_name + if v.pref.is_verbose { + println('Building $v.pref.path to $v.pref.out_name ...') + } + v.pref.cache_manager.save('.description.txt', v.pref.path, '${v.pref.path:-30} @ $v.pref.cache_manager.vopts\n') or { + panic(err) + } + // println('v.ast.imports:') + // println(v.ast.imports) + } + if os.is_dir(v.pref.out_name) { + verror("'$v.pref.out_name' is a directory") + } + v.ccoptions.o_args << '-o "$v.pref.out_name"' +} + +fn (mut v Builder) dump_c_options(all_args []string) { + if v.pref.dump_c_flags != '' { + non_empty_args := all_args.filter(it != '').join('\n') + '\n' + if v.pref.dump_c_flags == '-' { + print(non_empty_args) + } else { + os.write_file(v.pref.dump_c_flags, non_empty_args) or { panic(err) } + } + } +} + +fn (mut v Builder) cc() { + if os.executable().contains('vfmt') { + return + } + if v.pref.is_verbose { + println('builder.cc() pref.out_name="$v.pref.out_name"') + } + if v.pref.only_check_syntax { + if v.pref.is_verbose { + println('builder.cc returning early, since pref.only_check_syntax is true') + } + return + } + if v.pref.should_output_to_stdout() { + // output to stdout + content := os.read_file(v.out_name_c) or { panic(err) } + println(content) + os.rm(v.out_name_c) or {} + return + } + // whether to just create a .c or .js file and exit, for example: `v -o v.c cmd.v` + ends_with_c := v.pref.out_name.ends_with('.c') + ends_with_js := v.pref.out_name.ends_with('.js') + if ends_with_c || ends_with_js { + v.pref.skip_running = true + msg_mv := 'os.mv_by_cp $v.out_name_c => $v.pref.out_name' + util.timing_start(msg_mv) + // v.out_name_c may be on a different partition than v.out_name + os.mv_by_cp(v.out_name_c, v.pref.out_name) or { panic(err) } + util.timing_measure(msg_mv) + return + } + // Cross compiling for Windows + if v.pref.os == .windows { + $if !windows { + v.cc_windows_cross() + return + } + } + // Cross compiling for Linux + if v.pref.os == .linux { + $if !linux { + v.cc_linux_cross() + return + } + } + // + vexe := pref.vexe_path() + vdir := os.dir(vexe) + mut tried_compilation_commands := []string{} + mut tcc_output := os.Result{} + original_pwd := os.getwd() + for { + // try to compile with the choosen compiler + // if compilation fails, retry again with another + mut ccompiler := v.pref.ccompiler + if v.pref.os == .ios { + ios_sdk := if v.pref.is_ios_simulator { 'iphonesimulator' } else { 'iphoneos' } + ios_sdk_path_res := os.execute_or_exit('xcrun --sdk $ios_sdk --show-sdk-path') + mut isysroot := ios_sdk_path_res.output.replace('\n', '') + arch := if v.pref.is_ios_simulator { + '-arch x86_64' + } else { + '-arch armv7 -arch armv7s -arch arm64' + } + ccompiler = 'xcrun --sdk iphoneos clang -isysroot $isysroot $arch' + } + v.setup_ccompiler_options(ccompiler) + v.build_thirdparty_obj_files() + v.setup_output_name() + // + mut libs := []string{} // builtin.o os.o http.o etc + if v.pref.build_mode == .build_module { + v.ccoptions.pre_args << '-c' + } else if v.pref.use_cache { + mut built_modules := []string{} + builtin_obj_path := v.rebuild_cached_module(vexe, 'vlib/builtin') + libs << builtin_obj_path + for ast_file in v.parsed_files { + if v.pref.is_test && ast_file.mod.name != 'main' { + imp_path := v.find_module_path(ast_file.mod.name, ast_file.path) or { + verror('cannot import module "$ast_file.mod.name" (not found)') + break + } + obj_path := v.rebuild_cached_module(vexe, imp_path) + libs << obj_path + built_modules << ast_file.mod.name + } + for imp_stmt in ast_file.imports { + imp := imp_stmt.mod + // strconv is already imported inside builtin, so skip generating its object file + // TODO: incase we have other modules with the same name, make sure they are vlib + // is this even doign anything? + if imp in ['strconv', 'strings'] { + continue + } + if imp in built_modules { + continue + } + if util.should_bundle_module(imp) { + continue + } + // not working + if imp == 'webview' { + continue + } + // The problem is cmd/v is in module main and imports + // the relative module named help, which is built as cmd.v.help not help + // currently this got this workign by building into main, see ast.FnDecl in cgen + if imp == 'help' { + continue + } + // we are skipping help manually above, this code will skip all relative imports + // if os.is_dir(af_base_dir + os.path_separator + mod_path) { + // continue + // } + // mod_path := imp.replace('.', os.path_separator) + // imp_path := os.join_path('vlib', mod_path) + imp_path := v.find_module_path(imp, ast_file.path) or { + verror('cannot import module "$imp" (not found)') + break + } + obj_path := v.rebuild_cached_module(vexe, imp_path) + libs << obj_path + if obj_path.ends_with('vlib/ui.o') { + v.ccoptions.post_args << '-framework Cocoa' + v.ccoptions.post_args << '-framework Carbon' + } + built_modules << imp + } + } + v.ccoptions.post_args << libs + } + // + $if windows { + if ccompiler == 'msvc' { + v.cc_msvc() + return + } + } + // + all_args := v.all_args(v.ccoptions) + v.dump_c_options(all_args) + str_args := all_args.join(' ') + mut cmd := '$ccompiler $str_args' + mut response_file := '' + mut response_file_content := str_args + if !v.pref.no_rsp { + response_file = '${v.out_name_c}.rsp' + response_file_content = str_args.replace('\\', '\\\\') + cmd = '$ccompiler "@$response_file"' + os.write_file(response_file, response_file_content) or { + verror('Unable to write to C response file "$response_file"') + } + } + if !v.ccoptions.debug_mode { + v.pref.cleanup_files << v.out_name_c + if !v.pref.no_rsp { + v.pref.cleanup_files << response_file + } + } + $if windows { + if v.ccoptions.is_cc_tcc { + def_name := v.pref.out_name[0..v.pref.out_name.len - 4] + v.pref.cleanup_files << '${def_name}.def' + } + } + // + os.chdir(vdir) or {} + tried_compilation_commands << cmd + v.show_cc(cmd, response_file, response_file_content) + // Run + ccompiler_label := 'C ${os.file_name(ccompiler):3}' + util.timing_start(ccompiler_label) + res := os.execute(cmd) + util.timing_measure(ccompiler_label) + if v.pref.show_c_output { + v.show_c_compiler_output(res) + } + os.chdir(original_pwd) or {} + vcache.dlog('| Builder.' + @FN, '> v.pref.use_cache: $v.pref.use_cache | v.pref.retry_compilation: $v.pref.retry_compilation') + vcache.dlog('| Builder.' + @FN, '> cmd res.exit_code: $res.exit_code | cmd: $cmd') + vcache.dlog('| Builder.' + @FN, '> response_file_content:\n$response_file_content') + if res.exit_code != 0 { + if ccompiler.contains('tcc.exe') { + // a TCC problem? Retry with the system cc: + if tried_compilation_commands.len > 1 { + eprintln('Recompilation loop detected (ccompiler: $ccompiler):') + for recompile_command in tried_compilation_commands { + eprintln(' $recompile_command') + } + exit(101) + } + if v.pref.retry_compilation { + tcc_output = res + v.pref.ccompiler = pref.default_c_compiler() + if v.pref.is_verbose { + eprintln('Compilation with tcc failed. Retrying with $v.pref.ccompiler ...') + } + continue + } + } + if res.exit_code == 127 { + verror('C compiler error, while attempting to run: \n' + + '-----------------------------------------------------------\n' + '$cmd\n' + + '-----------------------------------------------------------\n' + + 'Probably your C compiler is missing. \n' + + 'Please reinstall it, or make it available in your PATH.\n\n' + + missing_compiler_info()) + } + } + if !v.pref.show_c_output { + // if tcc failed once, and the system C compiler has failed as well, + // print the tcc error instead since it may contain more useful information + // see https://discord.com/channels/592103645835821068/592115457029308427/811956304314761228 + if res.exit_code != 0 && tcc_output.output != '' { + v.post_process_c_compiler_output(tcc_output) + } else { + v.post_process_c_compiler_output(res) + } + } + // Print the C command + if v.pref.is_verbose { + println('$ccompiler') + println('=========\n') + } + break + } + if v.pref.compress { + $if windows { + println('-compress does not work on Windows for now') + return + } + ret := os.system('strip $v.pref.out_name') + if ret != 0 { + println('strip failed') + return + } + // NB: upx --lzma can sometimes fail with NotCompressibleException + // See https://github.com/vlang/v/pull/3528 + mut ret2 := os.system('upx --lzma -qqq $v.pref.out_name') + if ret2 != 0 { + ret2 = os.system('upx -qqq $v.pref.out_name') + } + if ret2 != 0 { + println('upx failed') + $if macos { + println('install upx with `brew install upx`') + } + $if linux { + println('install upx\n' + 'for example, on Debian/Ubuntu run `sudo apt install upx`') + } + $if windows { + // :) + } + } + } + // if v.pref.os == .ios { + // ret := os.system('ldid2 -S $v.pref.out_name') + // if ret != 0 { + // eprintln('failed to run ldid2, try: brew install ldid') + // } + // } +} + +fn (mut b Builder) ensure_linuxroot_exists(sysroot string) { + crossrepo_url := 'https://github.com/spytheman/vlinuxroot' + sysroot_git_config_path := os.join_path(sysroot, '.git', 'config') + if os.is_dir(sysroot) && !os.exists(sysroot_git_config_path) { + // remove existing obsolete unarchived .zip file content + os.rmdir_all(sysroot) or {} + } + if !os.is_dir(sysroot) { + println('Downloading files for Linux cross compilation (~22MB) ...') + os.system('git clone $crossrepo_url $sysroot') + if !os.exists(sysroot_git_config_path) { + verror('Failed to clone `$crossrepo_url` to `$sysroot`') + } + os.chmod(os.join_path(sysroot, 'ld.lld'), 0o755) or { panic(err) } + } +} + +fn (mut b Builder) cc_linux_cross() { + b.setup_ccompiler_options(b.pref.ccompiler) + b.build_thirdparty_obj_files() + b.setup_output_name() + parent_dir := os.vmodules_dir() + if !os.exists(parent_dir) { + os.mkdir(parent_dir) or { panic(err) } + } + sysroot := os.join_path(os.vmodules_dir(), 'linuxroot') + b.ensure_linuxroot_exists(sysroot) + obj_file := b.out_name_c + '.o' + cflags := b.get_os_cflags() + defines, others, libs := cflags.defines_others_libs() + mut cc_args := []string{} + cc_args << '-w' + cc_args << '-fPIC' + cc_args << '-c' + cc_args << '-target x86_64-linux-gnu' + cc_args << defines + cc_args << '-I $sysroot/include ' + cc_args << others + cc_args << '-o "$obj_file"' + cc_args << '-c "$b.out_name_c"' + cc_args << libs + b.dump_c_options(cc_args) + cc_cmd := 'cc ' + cc_args.join(' ') + if b.pref.show_cc { + println(cc_cmd) + } + cc_res := os.execute(cc_cmd) + if cc_res.exit_code != 0 { + println('Cross compilation for Linux failed (first step, cc). Make sure you have clang installed.') + verror(cc_res.output) + return + } + mut linker_args := ['-L $sysroot/usr/lib/x86_64-linux-gnu/', '--sysroot=$sysroot', '-v', + '-o $b.pref.out_name', '-m elf_x86_64', + '-dynamic-linker /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2', + '$sysroot/crt1.o $sysroot/crti.o $obj_file', '-lc', '-lcrypto', '-lssl', '-lpthread', + '$sysroot/crtn.o', + ] + linker_args << cflags.c_options_only_object_files() + // -ldl + b.dump_c_options(linker_args) + linker_cmd := '$sysroot/ld.lld ' + linker_args.join(' ') + // s = s.replace('SYSROOT', sysroot) // TODO $ inter bug + // s = s.replace('-o hi', '-o ' + c.pref.out_name) + if b.pref.show_cc { + println(linker_cmd) + } + res := os.execute(linker_cmd) + if res.exit_code != 0 { + println('Cross compilation for Linux failed (second step, lld).') + verror(res.output) + return + } + println(b.pref.out_name + ' has been successfully compiled') +} + +fn (mut c Builder) cc_windows_cross() { + println('Cross compiling for Windows...') + c.setup_ccompiler_options(c.pref.ccompiler) + c.build_thirdparty_obj_files() + c.setup_output_name() + if !c.pref.out_name.ends_with('.exe') { + c.pref.out_name += '.exe' + } + mut args := []string{} + args << '$c.pref.cflags' + args << '-o $c.pref.out_name' + args << '-w -L.' + // + cflags := c.get_os_cflags() + // -I flags + if c.pref.ccompiler == 'msvc' { + args << cflags.c_options_before_target_msvc() + } else { + args << cflags.c_options_before_target() + } + mut optimization_options := []string{} + mut debug_options := []string{} + if c.pref.is_prod { + if c.pref.ccompiler != 'msvc' { + optimization_options = ['-O3', '-fno-strict-aliasing', '-flto'] + } + } + if c.pref.is_debug { + if c.pref.ccompiler != 'msvc' { + debug_options = ['-O0', '-g', '-gdwarf-2'] + } + } + mut libs := []string{} + if false && c.pref.build_mode == .default_mode { + builtin_o := '"$pref.default_module_path/vlib/builtin.o"' + libs << builtin_o + if !os.exists(builtin_o) { + verror('$builtin_o not found') + } + for imp in c.table.imports { + libs << '"$pref.default_module_path/vlib/${imp}.o"' + } + } + // add the thirdparty .o files, produced by all the #flag directives: + args << cflags.c_options_only_object_files() + args << c.out_name_c + if c.pref.ccompiler == 'msvc' { + args << cflags.c_options_after_target_msvc() + } else { + args << cflags.c_options_after_target() + } + /* + winroot := '${pref.default_module_path}/winroot' + if !os.is_dir(winroot) { + winroot_url := 'https://github.com/vlang/v/releases/download/v0.1.10/winroot.zip' + println('"$winroot" not found.') + println('Download it from $winroot_url and save it in ${pref.default_module_path}') + println('Unzip it afterwards.\n') + println('winroot.zip contains all library and header files needed ' + 'to cross-compile for Windows.') + exit(1) + } + mut obj_name := c.out_name + obj_name = obj_name.replace('.exe', '') + obj_name = obj_name.replace('.o.o', '.o') + include := '-I $winroot/include ' + */ + if os.user_os() !in ['macos', 'linux'] { + println(os.user_os()) + panic('your platform is not supported yet') + } + mut all_args := []string{} + all_args << optimization_options + all_args << debug_options + all_args << '-std=gnu11' + all_args << args + all_args << '-municode' + c.dump_c_options(all_args) + mut cmd := '$builder.mingw_cc ' + all_args.join(' ') + // cmd := 'clang -o $obj_name -w $include -m32 -c -target x86_64-win32 ${pref.default_module_path}/$c.out_name_c' + if c.pref.is_verbose || c.pref.show_cc { + println(cmd) + } + if os.system(cmd) != 0 { + println('Cross compilation for Windows failed. Make sure you have mingw-w64 installed.') + $if macos { + println('brew install mingw-w64') + } + $if linux { + println('Try `sudo apt install -y mingw-w64` on Debian based distros, or `sudo pacman -S mingw-w64-gcc` on Arch, etc...') + } + exit(1) + } + /* + if c.pref.build_mode != .build_module { + link_cmd := 'lld-link $obj_name $winroot/lib/libcmt.lib ' + '$winroot/lib/libucrt.lib $winroot/lib/kernel32.lib $winroot/lib/libvcruntime.lib ' + '$winroot/lib/uuid.lib' + if c.pref.show_cc { + println(link_cmd) + } + if os.system(link_cmd) != 0 { + println('Cross compilation for Windows failed. Make sure you have lld linker installed.') + exit(1) + } + // os.rm(obj_name) + } + */ + println(c.pref.out_name + ' has been successfully compiled') +} + +fn (mut b Builder) build_thirdparty_obj_files() { + b.log('build_thirdparty_obj_files: v.ast.cflags: $b.table.cflags') + for flag in b.get_os_cflags() { + if flag.value.ends_with('.o') { + rest_of_module_flags := b.get_rest_of_module_cflags(flag) + if b.pref.ccompiler == 'msvc' { + b.build_thirdparty_obj_file_with_msvc(flag.value, rest_of_module_flags) + } else { + b.build_thirdparty_obj_file(flag.value, rest_of_module_flags) + } + } + } +} + +fn (mut v Builder) build_thirdparty_obj_file(path string, moduleflags []cflag.CFlag) { + obj_path := os.real_path(path) + cfile := '${obj_path[..obj_path.len - 2]}.c' + opath := v.pref.cache_manager.postfix_with_key2cpath('.o', obj_path) + mut rebuild_reason_message := '$obj_path not found, building it in $opath ...' + if os.exists(opath) { + if os.exists(cfile) && os.file_last_mod_unix(opath) < os.file_last_mod_unix(cfile) { + rebuild_reason_message = '$opath is older than $cfile, rebuilding ...' + } else { + return + } + } + if os.exists(obj_path) { + // Some .o files are distributed with no source + // for example thirdparty\tcc\lib\openlibm.o + // the best we can do for them is just copy them, + // and hope that they work with any compiler... + os.cp(obj_path, opath) or { panic(err) } + return + } + println(rebuild_reason_message) + // + // prepare for tcc, it needs relative paths to thirdparty/tcc to work: + current_folder := os.getwd() + os.chdir(os.dir(pref.vexe_path())) or {} + // + mut all_options := []string{} + all_options << v.pref.third_party_option + all_options << moduleflags.c_options_before_target() + all_options << '-o "$opath"' + all_options << '-c "$cfile"' + cc_options := v.thirdparty_object_args(v.ccoptions, all_options).join(' ') + cmd := '$v.pref.ccompiler $cc_options' + $if trace_thirdparty_obj_files ? { + println('>>> build_thirdparty_obj_files cmd: $cmd') + } + res := os.execute(cmd) + os.chdir(current_folder) or {} + if res.exit_code != 0 { + eprintln('failed thirdparty object build cmd:\n$cmd') + verror(res.output) + return + } + v.pref.cache_manager.save('.description.txt', obj_path, '${obj_path:-30} @ $cmd\n') or { + panic(err) + } + if res.output != '' { + println(res.output) + } +} + +fn missing_compiler_info() string { + $if windows { + return 'https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows' + } + $if linux { + return 'On Debian/Ubuntu, run `sudo apt install build-essential`' + } + $if macos { + return 'Install command line XCode tools with `xcode-select --install`' + } + return '' +} + +fn error_context_lines(text string, keyword string, before int, after int) []string { + khighlight := if term.can_show_color_on_stdout() { term.red(keyword) } else { keyword } + mut eline_idx := 0 + mut lines := text.split_into_lines() + for idx, eline in lines { + if eline.contains(keyword) { + lines[idx] = lines[idx].replace(keyword, khighlight) + if eline_idx == 0 { + eline_idx = idx + } + } + } + idx_s := if eline_idx - before >= 0 { eline_idx - before } else { 0 } + idx_e := if idx_s + after < lines.len { idx_s + after } else { lines.len } + return lines[idx_s..idx_e] +} diff --git a/v_windows/v/vlib/v/builder/cflags.v b/v_windows/v/vlib/v/builder/cflags.v new file mode 100644 index 0000000..1486f69 --- /dev/null +++ b/v_windows/v/vlib/v/builder/cflags.v @@ -0,0 +1,45 @@ +module builder + +import os +import v.cflag + +// get flags for current os +fn (mut v Builder) get_os_cflags() []cflag.CFlag { + mut flags := []cflag.CFlag{} + mut ctimedefines := []string{} + if v.pref.compile_defines.len > 0 { + ctimedefines << v.pref.compile_defines + } + for mut flag in v.table.cflags { + if flag.value.ends_with('.o') { + flag.cached = v.pref.cache_manager.postfix_with_key2cpath('.o', os.real_path(flag.value)) + } + if flag.os == '' || (flag.os == 'linux' && v.pref.os == .linux) + || (flag.os == 'macos' && v.pref.os == .macos) + || (flag.os == 'darwin' && v.pref.os == .macos) + || (flag.os == 'freebsd' && v.pref.os == .freebsd) + || (flag.os == 'windows' && v.pref.os == .windows) + || (flag.os == 'mingw' && v.pref.os == .windows && v.pref.ccompiler != 'msvc') + || (flag.os == 'solaris' && v.pref.os == .solaris) { + flags << flag + } + if flag.os in ctimedefines { + flags << flag + } + } + return flags +} + +fn (mut v Builder) get_rest_of_module_cflags(c &cflag.CFlag) []cflag.CFlag { + mut flags := []cflag.CFlag{} + cflags := v.get_os_cflags() + for flag in cflags { + if c.mod == flag.mod { + if c.name == flag.name && c.value == flag.value && c.os == flag.os { + continue + } + flags << flag + } + } + return flags +} diff --git a/v_windows/v/vlib/v/builder/compile.v b/v_windows/v/vlib/v/builder/compile.v new file mode 100644 index 0000000..ac59121 --- /dev/null +++ b/v_windows/v/vlib/v/builder/compile.v @@ -0,0 +1,326 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builder + +import time +import os +import rand +import v.pref +import v.util + +fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string { + vtmp := util.get_vtmp_folder() + mut uniq := '' + if !b.pref.reuse_tmpc { + uniq = '.$rand.u64()' + } + fname := os.file_name(os.real_path(base_file_name)) + '$uniq$postfix' + return os.real_path(os.join_path(vtmp, fname)) +} + +pub fn compile(command string, pref &pref.Preferences) { + odir := os.dir(pref.out_name) + // When pref.out_name is just the name of an executable, i.e. `./v -o executable main.v` + // without a folder component, just use the current folder instead: + mut output_folder := odir + if odir.len == pref.out_name.len { + output_folder = os.getwd() + } + os.is_writable_folder(output_folder) or { + // An early error here, is better than an unclear C error later: + verror(err.msg) + } + // Construct the V object from command line arguments + mut b := new_builder(pref) + if pref.is_verbose { + println('builder.compile() pref:') + // println(pref) + } + mut sw := time.new_stopwatch() + match pref.backend { + .c { b.compile_c() } + .js_node, .js_freestanding, .js_browser { b.compile_js() } + .native { b.compile_native() } + } + mut timers := util.get_timers() + timers.show_remaining() + if pref.is_stats { + compilation_time_micros := 1 + sw.elapsed().microseconds() + scompilation_time_ms := util.bold('${f64(compilation_time_micros) / 1000.0:6.3f}') + mut all_v_source_lines, mut all_v_source_bytes := 0, 0 + for pf in b.parsed_files { + all_v_source_lines += pf.nr_lines + all_v_source_bytes += pf.nr_bytes + } + mut sall_v_source_lines := all_v_source_lines.str() + mut sall_v_source_bytes := all_v_source_bytes.str() + sall_v_source_lines = util.bold('${sall_v_source_lines:10s}') + sall_v_source_bytes = util.bold('${sall_v_source_bytes:10s}') + println(' V source code size: $sall_v_source_lines lines, $sall_v_source_bytes bytes') + // + mut slines := b.stats_lines.str() + mut sbytes := b.stats_bytes.str() + slines = util.bold('${slines:10s}') + sbytes = util.bold('${sbytes:10s}') + println('generated target code size: $slines lines, $sbytes bytes') + // + vlines_per_second := int(1_000_000.0 * f64(all_v_source_lines) / f64(compilation_time_micros)) + svlines_per_second := util.bold(vlines_per_second.str()) + println('compilation took: $scompilation_time_ms ms, compilation speed: $svlines_per_second vlines/s') + } + b.exit_on_invalid_syntax() + // running does not require the parsers anymore + unsafe { b.myfree() } + if pref.is_test || pref.is_run { + b.run_compiled_executable_and_exit() + } +} + +// Temporary, will be done by -autofree +[unsafe] +fn (mut b Builder) myfree() { + // for file in b.parsed_files { + // } + unsafe { b.parsed_files.free() } + util.free_caches() +} + +fn (b &Builder) exit_on_invalid_syntax() { + util.free_caches() + // V should exit with an exit code of 1, when there are errors, + // even when -silent is passed in combination to -check-syntax: + if b.pref.only_check_syntax { + for pf in b.parsed_files { + if pf.errors.len > 0 { + exit(1) + } + } + if b.checker.nr_errors > 0 { + exit(1) + } + } +} + +fn (mut b Builder) run_compiled_executable_and_exit() { + if b.pref.skip_running { + return + } + if b.pref.only_check_syntax { + return + } + if b.pref.should_output_to_stdout() { + return + } + if b.pref.os == .ios { + panic('Running iOS apps is not supported yet.') + } + if b.pref.is_verbose { + println('============ running $b.pref.out_name ============') + } + mut exefile := os.real_path(b.pref.out_name) + mut cmd := '"$exefile"' + if b.pref.backend.is_js() { + exefile = os.real_path('${b.pref.out_name}.js') + cmd = 'node "$exefile"' + } + for arg in b.pref.run_args { + // Determine if there are spaces in the parameters + if arg.index_byte(` `) > 0 { + cmd += ' "' + arg + '"' + } else { + cmd += ' ' + arg + } + } + if b.pref.is_verbose { + println('command to run executable: $cmd') + } + if b.pref.is_test || b.pref.is_run { + ret := os.system(cmd) + b.cleanup_run_executable_after_exit(exefile) + exit(ret) + } + exit(0) +} + +fn (mut v Builder) cleanup_run_executable_after_exit(exefile string) { + if v.pref.reuse_tmpc { + v.pref.vrun_elog('keeping executable: $exefile , because -keepc was passed') + return + } + v.pref.vrun_elog('remove run executable: $exefile') + os.rm(exefile) or { panic(err) } +} + +// 'strings' => 'VROOT/vlib/strings' +// 'installed_mod' => '~/.vmodules/installed_mod' +// 'local_mod' => '/path/to/current/dir/local_mod' +fn (mut v Builder) set_module_lookup_paths() { + // Module search order: + // 0) V test files are very commonly located right inside the folder of the + // module, which they test. Adding the parent folder of the module folder + // with the _test.v files, *guarantees* that the tested module can be found + // without needing to set custom options/flags. + // 1) search in the *same* directory, as the compiled final v program source + // (i.e. the . in `v .` or file.v in `v file.v`) + // 2) search in the modules/ in the same directory. + // 3) search in the provided paths + // By default, these are what (3) contains: + // 3.1) search in vlib/ + // 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm) + v.module_search_paths = [] + if v.pref.is_test { + v.module_search_paths << os.dir(v.compiled_dir) // pdir of _test.v + } + v.module_search_paths << v.compiled_dir + x := os.join_path(v.compiled_dir, 'modules') + if v.pref.is_verbose { + println('x: "$x"') + } + v.module_search_paths << os.join_path(v.compiled_dir, 'modules') + v.module_search_paths << v.pref.lookup_path + if v.pref.is_verbose { + v.log('v.module_search_paths:') + println(v.module_search_paths) + } +} + +pub fn (v Builder) get_builtin_files() []string { + /* + // if v.pref.build_mode == .build_module && v.pref.path == 'vlib/builtin' { // .contains('builtin/' + location { + if v.pref.build_mode == .build_module && v.pref.path == 'vlib/strconv' { // .contains('builtin/' + location { + // We are already building builtin.o, no need to import them again + if v.pref.is_verbose { + println('skipping builtin modules for builtin.o') + } + return [] + } + */ + v.log('v.pref.lookup_path: $v.pref.lookup_path') + // Lookup for built-in folder in lookup path. + // Assumption: `builtin/` folder implies usable implementation of builtin + for location in v.pref.lookup_path { + if os.exists(os.join_path(location, 'builtin')) { + mut builtin_files := []string{} + if v.pref.backend.is_js() { + builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', + 'js')) + } else { + builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin')) + } + if v.pref.is_bare { + builtin_files << v.v_files_from_dir(v.pref.bare_builtin_dir) + } + if v.pref.backend == .c { + // TODO JavaScript backend doesn't handle os for now + if v.pref.is_vsh && os.exists(os.join_path(location, 'os')) { + builtin_files << v.v_files_from_dir(os.join_path(location, 'os')) + } + } + return builtin_files + } + } + // Panic. We couldn't find the folder. + verror('`builtin/` not included on module lookup path.\nDid you forget to add vlib to the path? (Use @vlib for default vlib)') +} + +pub fn (v &Builder) get_user_files() []string { + if v.pref.path in ['vlib/builtin', 'vlib/strconv', 'vlib/strings', 'vlib/hash'] { + // This means we are building a builtin module with `v build-module vlib/strings` etc + // get_builtin_files() has already added the files in this module, + // do nothing here to avoid duplicate definition errors. + v.log('Skipping user files.') + return [] + } + mut dir := v.pref.path + v.log('get_v_files($dir)') + // Need to store user files separately, because they have to be added after + // libs, but we dont know which libs need to be added yet + mut user_files := []string{} + // See cmd/tools/preludes/README.md for more info about what preludes are + vroot := os.dir(pref.vexe_path()) + mut preludes_path := os.join_path(vroot, 'vlib', 'v', 'preludes') + if v.pref.backend == .js_node { + preludes_path = os.join_path(vroot, 'vlib', 'v', 'preludes_js') + } + if v.pref.is_livemain || v.pref.is_liveshared { + user_files << os.join_path(preludes_path, 'live.v') + } + if v.pref.is_livemain { + user_files << os.join_path(preludes_path, 'live_main.v') + } + if v.pref.is_liveshared { + user_files << os.join_path(preludes_path, 'live_shared.v') + } + if v.pref.is_test { + user_files << os.join_path(preludes_path, 'tests_assertions.v') + } + if v.pref.is_test && v.pref.is_stats { + user_files << os.join_path(preludes_path, 'tests_with_stats.v') + } + if v.pref.is_prof { + user_files << os.join_path(preludes_path, 'profiled_program.v') + } + is_test := v.pref.is_test + mut is_internal_module_test := false + if is_test { + tcontent := os.read_file(dir) or { verror('$dir does not exist') } + slines := tcontent.trim_space().split_into_lines() + for sline in slines { + line := sline.trim_space() + if line.len > 2 { + if line[0] == `/` && line[1] == `/` { + continue + } + if line.starts_with('module ') { + is_internal_module_test = true + break + } + } + } + } + if is_internal_module_test { + // v volt/slack_test.v: compile all .v files to get the environment + single_test_v_file := os.real_path(dir) + if v.pref.is_verbose { + v.log('> Compiling an internal module _test.v file $single_test_v_file .') + v.log('> That brings in all other ordinary .v files in the same module too .') + } + user_files << single_test_v_file + dir = os.dir(single_test_v_file) + } + does_exist := os.exists(dir) + if !does_exist { + verror("$dir doesn't exist") + } + is_real_file := does_exist && !os.is_dir(dir) + resolved_link := if is_real_file && os.is_link(dir) { os.real_path(dir) } else { dir } + if is_real_file && (dir.ends_with('.v') || resolved_link.ends_with('.vsh') + || dir.ends_with('.vv')) { + single_v_file := if resolved_link.ends_with('.vsh') { resolved_link } else { dir } + // Just compile one file and get parent dir + user_files << single_v_file + if v.pref.is_verbose { + v.log('> just compile one file: "$single_v_file"') + } + } else if os.is_dir(dir) { + if v.pref.is_verbose { + v.log('> add all .v files from directory "$dir" ...') + } + // Add .v files from the directory being compiled + user_files << v.v_files_from_dir(dir) + } else { + println('usage: `v file.v` or `v directory`') + ext := os.file_ext(dir) + println('unknown file extension `$ext`') + exit(1) + } + if user_files.len == 0 { + println('No input .v files') + exit(1) + } + if v.pref.is_verbose { + v.log('user_files: $user_files') + } + return user_files +} diff --git a/v_windows/v/vlib/v/builder/js.v b/v_windows/v/vlib/v/builder/js.v new file mode 100644 index 0000000..241f693 --- /dev/null +++ b/v_windows/v/vlib/v/builder/js.v @@ -0,0 +1,51 @@ +module builder + +import os +import v.pref +import v.util +import v.gen.js + +pub fn (mut b Builder) gen_js(v_files []string) string { + b.front_and_middle_stages(v_files) or { return '' } + util.timing_start('JS GEN') + res := js.gen(b.parsed_files, b.table, b.pref) + util.timing_measure('JS GEN') + return res +} + +pub fn (mut b Builder) build_js(v_files []string, out_file string) { + b.out_name_js = out_file + b.info('build_js($out_file)') + output := b.gen_js(v_files) + os.write_file(out_file, output) or { panic(err) } + if b.pref.is_stats { + b.stats_lines = output.count('\n') + 1 + b.stats_bytes = output.len + } +} + +pub fn (mut b Builder) compile_js() { + mut files := b.get_user_files() + files << b.get_builtin_files() + b.set_module_lookup_paths() + if b.pref.is_verbose { + println('all .v files:') + println(files) + } + mut name := b.pref.out_name + if !name.ends_with('.js') { + name += '.js' + } + b.build_js(files, name) +} + +fn (mut b Builder) run_js() { + cmd := 'node ' + b.pref.out_name + '.js' + res := os.execute(cmd) + if res.exit_code != 0 { + eprintln('JS compilation failed:') + verror(res.output) + return + } + println(res.output) +} diff --git a/v_windows/v/vlib/v/builder/msvc.v b/v_windows/v/vlib/v/builder/msvc.v new file mode 100644 index 0000000..903c0fb --- /dev/null +++ b/v_windows/v/vlib/v/builder/msvc.v @@ -0,0 +1,505 @@ +module builder + +import os +import v.pref +import v.util +import v.cflag + +#flag windows -l shell32 +#flag windows -l dbghelp +#flag windows -l advapi32 + +struct MsvcResult { + full_cl_exe_path string + exe_path string + um_lib_path string + ucrt_lib_path string + vs_lib_path string + um_include_path string + ucrt_include_path string + vs_include_path string + shared_include_path string + valid bool +} + +// shell32 for RegOpenKeyExW etc +// Mimics a HKEY +type RegKey = voidptr + +// Taken from the windows SDK +const ( + hkey_local_machine = RegKey(0x80000002) + key_query_value = (0x0001) + key_wow64_32key = (0x0200) + key_enumerate_sub_keys = (0x0008) +) + +// Given a root key look for one of the subkeys in 'versions' and get the path +fn find_windows_kit_internal(key RegKey, versions []string) ?string { + $if windows { + unsafe { + for version in versions { + required_bytes := u32(0) // TODO mut + result := C.RegQueryValueEx(key, version.to_wide(), 0, 0, 0, &required_bytes) + length := required_bytes / 2 + if result != 0 { + continue + } + alloc_length := (required_bytes + 2) + mut value := &u16(malloc_noscan(int(alloc_length))) + if isnil(value) { + continue + } + // + else { + } + result2 := C.RegQueryValueEx(key, version.to_wide(), 0, 0, value, &alloc_length) + if result2 != 0 { + continue + } + // We might need to manually null terminate this thing + // So just make sure that we do that + if value[length - 1] != u16(0) { + value[length] = u16(0) + } + res := string_from_wide(value) + return res + } + } + } + return error('windows kit not found') +} + +struct WindowsKit { + um_lib_path string + ucrt_lib_path string + um_include_path string + ucrt_include_path string + shared_include_path string +} + +// Try and find the root key for installed windows kits +fn find_windows_kit_root(target_arch string) ?WindowsKit { + $if windows { + wkroot := find_windows_kit_root_by_reg(target_arch) or { + if wkroot := find_windows_kit_root_by_env(target_arch) { + return wkroot + } + return err + } + + return wkroot + } $else { + return error('Host OS does not support finding a windows kit') + } +} + +// Try to find the root key for installed windows kits from registry +fn find_windows_kit_root_by_reg(target_arch string) ?WindowsKit { + $if windows { + root_key := RegKey(0) + path := 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots' + rc := C.RegOpenKeyEx(builder.hkey_local_machine, path.to_wide(), 0, builder.key_query_value | builder.key_wow64_32key | builder.key_enumerate_sub_keys, + &root_key) + + if rc != 0 { + return error('Unable to open root key') + } + // Try and find win10 kit + kit_root := find_windows_kit_internal(root_key, ['KitsRoot10', 'KitsRoot81']) or { + C.RegCloseKey(root_key) + return error('Unable to find a windows kit') + } + C.RegCloseKey(root_key) + return new_windows_kit(kit_root, target_arch) + } $else { + return error('Host OS does not support finding a windows kit') + } +} + +fn new_windows_kit(kit_root string, target_arch string) ?WindowsKit { + kit_lib := kit_root + 'Lib' + files := os.ls(kit_lib) ? + mut highest_path := '' + mut highest_int := 0 + for f in files { + no_dot := f.replace('.', '') + v_int := no_dot.int() + if v_int > highest_int { + highest_int = v_int + highest_path = f + } + } + kit_lib_highest := kit_lib + '\\$highest_path' + kit_include_highest := kit_lib_highest.replace('Lib', 'Include') + return WindowsKit{ + um_lib_path: kit_lib_highest + '\\um\\$target_arch' + ucrt_lib_path: kit_lib_highest + '\\ucrt\\$target_arch' + um_include_path: kit_include_highest + '\\um' + ucrt_include_path: kit_include_highest + '\\ucrt' + shared_include_path: kit_include_highest + '\\shared' + } +} + +fn find_windows_kit_root_by_env(target_arch string) ?WindowsKit { + kit_root := os.getenv('WindowsSdkDir') + if kit_root == '' { + return error('empty WindowsSdkDir') + } + return new_windows_kit(kit_root, target_arch) +} + +struct VsInstallation { + include_path string + lib_path string + exe_path string +} + +fn find_vs(vswhere_dir string, host_arch string, target_arch string) ?VsInstallation { + $if windows { + vsinst := find_vs_by_reg(vswhere_dir, host_arch, target_arch) or { + if vsinst := find_vs_by_env(host_arch, target_arch) { + return vsinst + } + return err + } + return vsinst + } $else { + return error('Host OS does not support finding a Visual Studio installation') + } +} + +fn find_vs_by_reg(vswhere_dir string, host_arch string, target_arch string) ?VsInstallation { + $if windows { + // Emily: + // VSWhere is guaranteed to be installed at this location now + // If its not there then end user needs to update their visual studio + // installation! + res := os.execute('"$vswhere_dir\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath') + if res.exit_code != 0 { + return error_with_code(res.output, res.exit_code) + } + res_output := res.output.trim_right('\r\n') + // println('res: "$res"') + version := os.read_file('$res_output\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt') or { + // println('Unable to find msvc version') + return error('Unable to find vs installation') + } + version2 := version // TODO remove. cgen option bug if expr + // println('version: $version') + v := if version.ends_with('\n') { version2[..version.len - 2] } else { version2 } + lib_path := '$res.output\\VC\\Tools\\MSVC\\$v\\lib\\$target_arch' + include_path := '$res.output\\VC\\Tools\\MSVC\\$v\\include' + if os.exists('$lib_path\\vcruntime.lib') { + p := '$res.output\\VC\\Tools\\MSVC\\$v\\bin\\Host$host_arch\\$target_arch' + // println('$lib_path $include_path') + return VsInstallation{ + exe_path: p + lib_path: lib_path + include_path: include_path + } + } + println('Unable to find vs installation (attempted to use lib path "$lib_path")') + return error('Unable to find vs exe folder') + } $else { + return error('Host OS does not support finding a Visual Studio installation') + } +} + +fn find_vs_by_env(host_arch string, target_arch string) ?VsInstallation { + vs_dir := os.getenv('VSINSTALLDIR') + if vs_dir == '' { + return error('empty VSINSTALLDIR') + } + + vc_tools_dir := os.getenv('VCToolsInstallDir') + if vc_tools_dir == '' { + return error('empty VCToolsInstallDir') + } + + bin_dir := '${vc_tools_dir}bin\\Host$host_arch\\$target_arch' + lib_path := '${vc_tools_dir}lib\\$target_arch' + include_path := '${vc_tools_dir}include' + + return VsInstallation{ + exe_path: bin_dir + lib_path: lib_path + include_path: include_path + } +} + +fn find_msvc(m64_target bool) ?MsvcResult { + $if windows { + processor_architecture := os.getenv('PROCESSOR_ARCHITECTURE') + vswhere_dir := if processor_architecture == 'x86' { + '%ProgramFiles%' + } else { + '%ProgramFiles(x86)%' + } + host_arch := if processor_architecture == 'x86' { 'X86' } else { 'X64' } + mut target_arch := 'X64' + if host_arch == 'X86' { + if !m64_target { + target_arch = 'X86' + } + } else if host_arch == 'X64' { + if !m64_target { + target_arch = 'X86' + } + } + wk := find_windows_kit_root(target_arch) or { return error('Unable to find windows sdk') } + vs := find_vs(vswhere_dir, host_arch, target_arch) or { + return error('Unable to find visual studio') + } + return MsvcResult{ + full_cl_exe_path: os.real_path(vs.exe_path + os.path_separator + 'cl.exe') + exe_path: vs.exe_path + um_lib_path: wk.um_lib_path + ucrt_lib_path: wk.ucrt_lib_path + vs_lib_path: vs.lib_path + um_include_path: wk.um_include_path + ucrt_include_path: wk.ucrt_include_path + vs_include_path: vs.include_path + shared_include_path: wk.shared_include_path + valid: true + } + } $else { + // This hack allows to at least see the generated .c file with `-os windows -cc msvc -o x.c` + // Please do not remove it, unless you also check that the above continues to work. + return MsvcResult{ + full_cl_exe_path: '/usr/bin/true' + valid: true + } + } +} + +pub fn (mut v Builder) cc_msvc() { + r := v.cached_msvc + if r.valid == false { + verror('Cannot find MSVC on this OS') + return + } + out_name_obj := os.real_path(v.out_name_c + '.obj') + out_name_pdb := os.real_path(v.out_name_c + '.pdb') + out_name_cmd_line := os.real_path(v.out_name_c + '.rsp') + // Default arguments + // volatile:ms enables atomic volatile (gcc _Atomic) + // -w: no warnings + // 2 unicode defines + // /Fo sets the object file name - needed so we can clean up after ourselves properly + mut a := ['-w', '/we4013', '/volatile:ms', '/Fo"$out_name_obj"'] + if v.pref.is_prod { + a << '/O2' + a << '/MD' + a << '/DNDEBUG' + } else { + a << '/MDd' + a << '/D_DEBUG' + } + if v.pref.is_debug { + // /Zi generates a .pdb + // /Fd sets the pdb file name (so its not just vc140 all the time) + a << ['/Zi', '/Fd"$out_name_pdb"'] + } + if v.pref.is_shared { + if !v.pref.out_name.ends_with('.dll') { + v.pref.out_name += '.dll' + } + // Build dll + a << '/LD' + } else if !v.pref.out_name.ends_with('.exe') { + v.pref.out_name += '.exe' + } + v.pref.out_name = os.real_path(v.pref.out_name) + // alibs := []string{} // builtin.o os.o http.o etc + if v.pref.build_mode == .build_module { + // Compile only + a << '/c' + } else if v.pref.build_mode == .default_mode { + /* + b := os.real_path( '${pref.default_module_path}/vlib/builtin.obj' ) + alibs << '"$b"' + if !os.exists(b) { + println('`builtin.obj` not found') + exit(1) + } + for imp in v.ast.imports { + if imp == 'webview' { + continue + } + alibs << '"' + os.real_path( '${pref.default_module_path}/vlib/${imp}.obj' ) + '"' + } + */ + } + if v.pref.sanitize { + println('Sanitize not supported on msvc.') + } + // The C file we are compiling + // a << '"$TmpPath/$v.out_name_c"' + a << '"' + os.real_path(v.out_name_c) + '"' + // Emily: + // Not all of these are needed (but the compiler should discard them if they are not used) + // these are the defaults used by msbuild and visual studio + mut real_libs := ['kernel32.lib', 'user32.lib', 'advapi32.lib'] + // sflags := v.get_os_cflags().msvc_string_flags() + sflags := msvc_string_flags(v.get_os_cflags()) + real_libs << sflags.real_libs + inc_paths := sflags.inc_paths + lib_paths := sflags.lib_paths + defines := sflags.defines + other_flags := sflags.other_flags + // Include the base paths + a << '-I "$r.ucrt_include_path"' + a << '-I "$r.vs_include_path"' + a << '-I "$r.um_include_path"' + a << '-I "$r.shared_include_path"' + a << defines + a << inc_paths + a << other_flags + // Libs are passed to cl.exe which passes them to the linker + a << real_libs.join(' ') + a << '/link' + a << '/NOLOGO' + a << '/OUT:"$v.pref.out_name"' + a << '/LIBPATH:"$r.ucrt_lib_path"' + a << '/LIBPATH:"$r.um_lib_path"' + a << '/LIBPATH:"$r.vs_lib_path"' + a << '/DEBUG:FULL' // required for prod builds to generate PDB + if v.pref.is_prod { + a << '/INCREMENTAL:NO' // Disable incremental linking + a << '/OPT:REF' + a << '/OPT:ICF' + } + a << lib_paths + args := a.join(' ') + // write args to a file so that we dont smash createprocess + os.write_file(out_name_cmd_line, args) or { + verror('Unable to write response file to "$out_name_cmd_line"') + } + cmd := '"$r.full_cl_exe_path" "@$out_name_cmd_line"' + // It is hard to see it at first, but the quotes above ARE balanced :-| ... + // Also the double quotes at the start ARE needed. + v.show_cc(cmd, out_name_cmd_line, args) + util.timing_start('C msvc') + res := os.execute(cmd) + if res.exit_code != 0 { + eprintln(res.output) + verror('msvc error') + return + } + util.timing_measure('C msvc') + if v.pref.show_c_output { + v.show_c_compiler_output(res) + } else { + v.post_process_c_compiler_output(res) + } + // println(res) + // println('C OUTPUT:') + // Always remove the object file - it is completely unnecessary + os.rm(out_name_obj) or { panic(err) } +} + +fn (mut v Builder) build_thirdparty_obj_file_with_msvc(path string, moduleflags []cflag.CFlag) { + msvc := v.cached_msvc + if msvc.valid == false { + verror('Cannot find MSVC on this OS') + return + } + // msvc expects .obj not .o + mut obj_path := '${path}bj' + obj_path = os.real_path(obj_path) + if os.exists(obj_path) { + // println('$obj_path already built.') + return + } + println('$obj_path not found, building it (with msvc)...') + cfiles := '${path[..path.len - 2]}.c' + flags := msvc_string_flags(moduleflags) + inc_dirs := flags.inc_paths.join(' ') + defines := flags.defines.join(' ') + include_string := '-I "$msvc.ucrt_include_path" -I "$msvc.vs_include_path" -I "$msvc.um_include_path" -I "$msvc.shared_include_path" $inc_dirs' + // println('cfiles: $cfiles') + mut oargs := []string{} + if v.pref.is_prod { + oargs << '/O2' + oargs << '/MD' + oargs << '/DNDEBUG' + } else { + oargs << '/MDd' + oargs << '/D_DEBUG' + } + str_oargs := oargs.join(' ') + cmd := '"$msvc.full_cl_exe_path" /volatile:ms $str_oargs $defines $include_string /c $cfiles /Fo"$obj_path"' + // NB: the quotes above ARE balanced. + $if trace_thirdparty_obj_files ? { + println('>>> build_thirdparty_obj_file_with_msvc cmd: $cmd') + } + res := os.execute(cmd) + if res.exit_code != 0 { + println('msvc: failed to build a thirdparty object; cmd: $cmd') + verror(res.output) + return + } + println(res.output) +} + +struct MsvcStringFlags { +mut: + real_libs []string + inc_paths []string + lib_paths []string + defines []string + other_flags []string +} + +// pub fn (cflags []CFlag) msvc_string_flags() MsvcStringFlags { +pub fn msvc_string_flags(cflags []cflag.CFlag) MsvcStringFlags { + mut real_libs := []string{} + mut inc_paths := []string{} + mut lib_paths := []string{} + mut defines := []string{} + mut other_flags := []string{} + for flag in cflags { + // println('fl: $flag.name | flag arg: $flag.value') + // We need to see if the flag contains -l + // -l isnt recognised and these libs will be passed straight to the linker + // by the compiler + if flag.name == '-l' { + if flag.value.ends_with('.dll') { + verror('MSVC cannot link against a dll (`#flag -l $flag.value`)') + } + // MSVC has no method of linking against a .dll + // TODO: we should look for .defs aswell + lib_lib := flag.value + '.lib' + real_libs << lib_lib + } else if flag.name == '-I' { + inc_paths << flag.format() + } else if flag.name == '-L' { + lib_paths << flag.value + lib_paths << flag.value + os.path_separator + 'msvc' + // The above allows putting msvc specific .lib files in a subfolder msvc/ , + // where gcc will NOT find them, but cl will do... + // NB: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough. + // When both a msvc .lib file and .dll file are present in the same folder, + // as for example for glfw3, compilation with gcc would fail. + } else if flag.value.ends_with('.o') { + // msvc expects .obj not .o + other_flags << '"${flag.value}bj"' + } else if flag.value.starts_with('-D') { + defines << '/D${flag.value[2..]}' + } else { + other_flags << flag.value + } + } + mut lpaths := []string{} + for l in lib_paths { + lpaths << '/LIBPATH:"' + os.real_path(l) + '"' + } + return MsvcStringFlags{ + real_libs: real_libs + inc_paths: inc_paths + lib_paths: lpaths + defines: defines + other_flags: other_flags + } +} diff --git a/v_windows/v/vlib/v/builder/native.v b/v_windows/v/vlib/v/builder/native.v new file mode 100644 index 0000000..c2176be --- /dev/null +++ b/v_windows/v/vlib/v/builder/native.v @@ -0,0 +1,22 @@ +module builder + +import v.pref +import v.util +import v.gen.native + +pub fn (mut b Builder) build_native(v_files []string, out_file string) { + if b.pref.os !in [.linux, .macos] { + eprintln('Warning: v -native can only generate macOS and Linux binaries for now') + } + b.front_and_middle_stages(v_files) or { return } + util.timing_start('Native GEN') + b.stats_lines, b.stats_bytes = native.gen(b.parsed_files, b.table, out_file, b.pref) + util.timing_measure('Native GEN') +} + +pub fn (mut b Builder) compile_native() { + // v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare')) + files := [b.pref.path] + b.set_module_lookup_paths() + b.build_native(files, b.pref.out_name) +} diff --git a/v_windows/v/vlib/v/callgraph/callgraph.v b/v_windows/v/vlib/v/callgraph/callgraph.v new file mode 100644 index 0000000..da57bc7 --- /dev/null +++ b/v_windows/v/vlib/v/callgraph/callgraph.v @@ -0,0 +1,129 @@ +module callgraph + +import v.ast +import v.ast.walker +import v.pref +import v.dotgraph + +// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls +// that function make transitively +pub fn show(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File) { + mut mapper := &Mapper{ + pref: pref + table: table + dg: dotgraph.new('CallGraph', 'CallGraph for $pref.path', 'green') + } + // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"]; + // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; + for afile in ast_files { + walker.walk(mapper, afile) + } + mapper.dg.finish() +} + +[heap] +struct Mapper { + pos int +mut: + pref &pref.Preferences + table &ast.Table + file &ast.File = 0 + node &ast.Node = 0 + fn_decl &ast.FnDecl = 0 + caller_name string + dot_caller_name string + is_caller_used bool + dg dotgraph.DotGraph +} + +fn (mut m Mapper) dot_normalise_node_name(name string) string { + res := name.replace_each([ + '.', + '_', + '==', + 'op_eq', + '>=', + 'op_greater_eq', + '<=', + 'op_lesser_eq', + '>', + 'op_greater', + '<', + 'op_lesser', + '+', + 'op_plus', + '-', + 'op_minus', + '/', + 'op_divide', + '*', + 'op_multiply', + '^', + 'op_xor', + '|', + 'op_or', + '&', + 'op_and', + ]) + return res +} + +fn (mut m Mapper) fn_name(fname string, receiver_type ast.Type, is_method bool) string { + if !is_method { + return fname + } + rec_sym := m.table.get_type_symbol(receiver_type) + return '${rec_sym.name}.$fname' +} + +fn (mut m Mapper) dot_fn_name(fname string, recv_type ast.Type, is_method bool) string { + if is_method { + return 'Node_method_' + int(recv_type).str() + '_' + m.dot_normalise_node_name(fname) + } + return 'Node_fn_' + m.dot_normalise_node_name(fname) +} + +fn (mut m Mapper) visit(node &ast.Node) ? { + m.node = unsafe { node } + match node { + ast.File { + m.file = unsafe { &node } + } + ast.Stmt { + match node { + ast.FnDecl { + m.is_caller_used = true + if m.pref.skip_unused { + m.is_caller_used = m.table.used_fns[node.fkey()] + } + m.fn_decl = unsafe { &node } + m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method) + m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method) + if m.is_caller_used { + m.dg.new_node(m.caller_name, + node_name: m.dot_caller_name + should_highlight: m.caller_name == 'main.main' + ) + } + } + else {} + } + } + ast.Expr { + match node { + ast.CallExpr { + if m.is_caller_used { + dot_called_name := m.dot_fn_name(node.name, node.receiver_type, + node.is_method) + // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; + m.dg.new_edge(m.dot_caller_name, dot_called_name, + should_highlight: m.caller_name == 'main.main' + ) + } + } + else {} + } + } + else {} + } +} diff --git a/v_windows/v/vlib/v/cflag/cflags.v b/v_windows/v/vlib/v/cflag/cflags.v new file mode 100644 index 0000000..30da3b6 --- /dev/null +++ b/v_windows/v/vlib/v/cflag/cflags.v @@ -0,0 +1,131 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module cflag + +import os + +// parsed cflag +pub struct CFlag { +pub: + mod string // the module in which the flag was given + os string // eg. windows | darwin | linux + name string // eg. -I + value string // eg. /path/to/include +pub mut: + cached string // eg. ~/.vmodules/cache/ea/ea9878886727367672163.o (for .o files) +} + +pub fn (c &CFlag) str() string { + return 'CFlag{ name: "$c.name" value: "$c.value" mod: "$c.mod" os: "$c.os" cached: "$c.cached" }' +} + +const fexisting_literal = r'$first_existing' + +// expand the flag value +pub fn (cf &CFlag) eval() string { + mut value := '' + cflag_eval_outer_loop: for i := 0; i < cf.value.len; i++ { + x := cf.value[i] + if x == `$` { + remainder := cf.value[i..] + if remainder.starts_with(cflag.fexisting_literal) { + sparams := remainder[cflag.fexisting_literal.len + 1..].all_before(')') + i += sparams.len + cflag.fexisting_literal.len + 1 + svalues := sparams.replace(',', '\n').split_into_lines().map(it.trim(' \'"')) + // mut found_spath := '' + for spath in svalues { + if os.exists(spath) { + // found_spath = spath + value += spath + continue cflag_eval_outer_loop + } + } + panic('>> error: none of the paths $svalues exist') + continue + } + } + value += x.ascii_str() + } + return value +} + +// format flag +pub fn (cf &CFlag) format() string { + mut value := '' + if cf.cached != '' { + value = cf.cached + } else { + value = cf.eval() + } + if cf.name in ['-l', '-Wa', '-Wl', '-Wp'] && value.len > 0 { + return '$cf.name$value'.trim_space() + } + // convert to absolute path + if cf.name == '-I' || cf.name == '-L' || value.ends_with('.o') { + value = '"' + os.real_path(value) + '"' + } + return '$cf.name $value'.trim_space() +} + +// TODO: implement msvc specific c_options_before_target and c_options_after_target ... +pub fn (cflags []CFlag) c_options_before_target_msvc() []string { + return [] +} + +pub fn (cflags []CFlag) c_options_after_target_msvc() []string { + return [] +} + +pub fn (cflags []CFlag) c_options_before_target() []string { + defines, others, _ := cflags.defines_others_libs() + mut args := []string{} + args << defines + args << others + return args +} + +pub fn (cflags []CFlag) c_options_after_target() []string { + _, _, libs := cflags.defines_others_libs() + return libs +} + +pub fn (cflags []CFlag) c_options_without_object_files() []string { + mut args := []string{} + for flag in cflags { + if flag.value.ends_with('.o') || flag.value.ends_with('.obj') { + continue + } + args << flag.format() + } + return args +} + +pub fn (cflags []CFlag) c_options_only_object_files() []string { + mut args := []string{} + for flag in cflags { + if flag.value.ends_with('.o') || flag.value.ends_with('.obj') { + args << flag.format() + } + } + return args +} + +pub fn (cflags []CFlag) defines_others_libs() ([]string, []string, []string) { + copts_without_obj_files := cflags.c_options_without_object_files() + mut defines := []string{} + mut others := []string{} + mut libs := []string{} + for copt in copts_without_obj_files { + if copt.starts_with('-l') { + libs << copt + continue + } + if copt.starts_with('-D') { + defines << copt + continue + } + others << copt + } + return defines, others, libs +} diff --git a/v_windows/v/vlib/v/checker/check_types.v b/v_windows/v/vlib/v/checker/check_types.v new file mode 100644 index 0000000..ddbcfe8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/check_types.v @@ -0,0 +1,733 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module checker + +import v.ast +import v.token + +pub fn (mut c Checker) check_expected_call_arg(got ast.Type, expected_ ast.Type, language ast.Language) ? { + mut expected := expected_ + // variadic + if expected.has_flag(.variadic) { + exp_type_sym := c.table.get_type_symbol(expected_) + exp_info := exp_type_sym.info as ast.Array + expected = exp_info.elem_type + } + if language == .c { + // allow number types to be used interchangeably + if got.is_number() && expected.is_number() { + return + } + // mode_t - currently using u32 as mode_t for C fns + // if got.idx() in [ast.int_type_idx, ast.u32_type_idx] && expected.idx() in [ast.int_type_idx, ast.u32_type_idx] { + // return + // } + // allow number to be used as size_t + if got.is_number() && expected.idx() == ast.size_t_type_idx { + return + } + // allow bool & int to be used interchangeably for C functions + if (got.idx() == ast.bool_type_idx + && expected.idx() in [ast.int_type_idx, ast.int_literal_type_idx]) + || (expected.idx() == ast.bool_type_idx + && got.idx() in [ast.int_type_idx, ast.int_literal_type_idx]) { + return + } + exp_sym := c.table.get_type_symbol(expected) + // unknown C types are set to int, allow int to be used for types like `&C.FILE` + // eg. `C.fflush(C.stderr)` - error: cannot use `int` as `&C.FILE` in argument 1 to `C.fflush` + if expected.is_ptr() && exp_sym.language == .c && exp_sym.kind in [.placeholder, .struct_] + && got == ast.int_type_idx { + return + } + } + if c.check_types(got, expected) { + return + } + idx_got := got.idx() + idx_expected := expected.idx() + if idx_got in [ast.byteptr_type_idx, ast.charptr_type_idx] + || idx_expected in [ast.byteptr_type_idx, ast.charptr_type_idx] { + igot := int(got) + iexpected := int(expected) + // TODO: remove; transitional compatibility for byteptr === &byte + if (igot == ast.byteptr_type_idx && iexpected == 65545) + || (iexpected == ast.byteptr_type_idx && igot == 65545) { + return + } + // TODO: remove; transitional compatibility for charptr === &char + if (igot == ast.charptr_type_idx && iexpected == 65551) + || (iexpected == ast.charptr_type_idx && igot == 65551) { + return + } + muls_got := got.nr_muls() + muls_expected := expected.nr_muls() + if idx_got == ast.byteptr_type_idx && idx_expected == ast.byte_type_idx + && muls_got + 1 == muls_expected { + return + } + if idx_expected == ast.byteptr_type_idx && idx_got == ast.byte_type_idx + && muls_expected + 1 == muls_got { + return + } + if idx_got == ast.charptr_type_idx && idx_expected == ast.char_type_idx + && muls_got + 1 == muls_expected { + return + } + if idx_expected == ast.charptr_type_idx && idx_got == ast.char_type_idx + && muls_expected + 1 == muls_got { + return + } + } + return error('cannot use `${c.table.type_to_str(got.clear_flag(.variadic))}` as `${c.table.type_to_str(expected.clear_flag(.variadic))}`') +} + +pub fn (mut c Checker) check_basic(got ast.Type, expected ast.Type) bool { + unalias_got, unalias_expected := c.table.unalias_num_type(got), c.table.unalias_num_type(expected) + if unalias_got.idx() == unalias_expected.idx() { + // this is returning true even if one type is a ptr + // and the other is not, is this correct behaviour? + return true + } + if (unalias_expected.is_pointer() || unalias_expected.is_number()) + && (unalias_got.is_pointer() || unalias_got.is_number()) { + return true + } + // allow pointers to be initialized with 0. TODO: use none instead + if expected.is_ptr() && unalias_got == ast.int_literal_type { + return true + } + // TODO: use sym so it can be absorbed into below [.voidptr, .any] logic + if expected.idx() == ast.array_type_idx || got.idx() == ast.array_type_idx { + return true + } + got_sym, exp_sym := c.table.get_type_symbol(got), c.table.get_type_symbol(expected) + // array/map as argument + if got_sym.kind in [.array, .map, .array_fixed] && exp_sym.kind == got_sym.kind { + if c.table.type_to_str(got) == c.table.type_to_str(expected).trim('&') { + return true + } + } + if !unalias_got.is_ptr() && got_sym.kind == .array_fixed + && (unalias_expected.is_pointer() || unalias_expected.is_ptr()) { + // fixed array needs to be a struct, not a pointer + return false + } + if exp_sym.kind in [.voidptr, .any] || got_sym.kind in [.voidptr, .any] { + return true + } + // sum type + if c.table.sumtype_has_variant(expected, c.table.mktyp(got)) { + return true + } + // type alias + if (got_sym.kind == .alias && got_sym.parent_idx == expected.idx()) + || (exp_sym.kind == .alias && exp_sym.parent_idx == got.idx()) { + return true + } + // fn type + if got_sym.kind == .function && exp_sym.kind == .function { + return c.check_matching_function_symbols(got_sym, exp_sym) + } + // allow `return 0` in a function with `?int` return type + expected_nonflagged := expected.clear_flags() + if got == ast.int_literal_type && expected_nonflagged.is_int() { + return true + } + // allow `return 0` in a function with `?f32` return type + if got == ast.float_literal_type && expected_nonflagged.is_float() { + return true + } + return false +} + +pub fn (mut c Checker) check_matching_function_symbols(got_type_sym &ast.TypeSymbol, exp_type_sym &ast.TypeSymbol) bool { + got_info := got_type_sym.info as ast.FnType + exp_info := exp_type_sym.info as ast.FnType + got_fn := got_info.func + exp_fn := exp_info.func + // we are using check() to compare return type & args as they might include + // functions themselves. TODO: optimize, only use check() when needed + if got_fn.params.len != exp_fn.params.len { + return false + } + if !c.check_basic(got_fn.return_type, exp_fn.return_type) { + return false + } + for i, got_arg in got_fn.params { + exp_arg := exp_fn.params[i] + exp_arg_is_ptr := exp_arg.typ.is_ptr() || exp_arg.typ.is_pointer() + got_arg_is_ptr := got_arg.typ.is_ptr() || got_arg.typ.is_pointer() + if exp_arg_is_ptr != got_arg_is_ptr { + exp_arg_pointedness := if exp_arg_is_ptr { 'a pointer' } else { 'NOT a pointer' } + got_arg_pointedness := if got_arg_is_ptr { 'a pointer' } else { 'NOT a pointer' } + c.add_error_detail('`$exp_fn.name`\'s expected fn argument: `$exp_arg.name` is $exp_arg_pointedness, but the passed fn argument: `$got_arg.name` is $got_arg_pointedness') + return false + } + if !c.check_basic(got_arg.typ, exp_arg.typ) { + return false + } + } + return true +} + +[inline] +fn (mut c Checker) check_shift(left_type ast.Type, right_type ast.Type, left_pos token.Position, right_pos token.Position) ast.Type { + if !left_type.is_int() { + // maybe it's an int alias? TODO move this to is_int() ? + sym := c.table.get_type_symbol(left_type) + if sym.kind == .alias && (sym.info as ast.Alias).parent_type.is_int() { + return left_type + } + if c.pref.translated && left_type == ast.bool_type { + // allow `bool << 2` in translated C code + return ast.int_type + } + c.error('invalid operation: shift on type `$sym.name`', left_pos) + return ast.void_type + } else if !right_type.is_int() { + c.error('cannot shift non-integer type `${c.table.get_type_symbol(right_type).name}` into type `${c.table.get_type_symbol(left_type).name}`', + right_pos) + return ast.void_type + } + return left_type +} + +pub fn (mut c Checker) promote(left_type ast.Type, right_type ast.Type) ast.Type { + if left_type.is_any_kind_of_pointer() { + if right_type.is_int() { + return left_type + } else { + return ast.void_type + } + } else if right_type.is_any_kind_of_pointer() { + if left_type.is_int() { + return right_type + } else { + return ast.void_type + } + } + if left_type == right_type { + return left_type // strings, self defined operators + } + if right_type.is_number() && left_type.is_number() { + return c.promote_num(left_type, right_type) + } else if left_type.has_flag(.optional) != right_type.has_flag(.optional) { + // incompatible + return ast.void_type + } else { + return left_type // default to left if not automatic promotion possible + } +} + +fn (c &Checker) promote_num(left_type ast.Type, right_type ast.Type) ast.Type { + // sort the operands to save time + mut type_hi := left_type + mut type_lo := right_type + if type_hi.idx() < type_lo.idx() { + type_hi, type_lo = type_lo, type_hi + } + idx_hi := type_hi.idx() + idx_lo := type_lo.idx() + // the following comparisons rely on the order of the indices in table/types.v + if idx_hi == ast.int_literal_type_idx { + return type_lo + } else if idx_hi == ast.float_literal_type_idx { + if idx_lo in ast.float_type_idxs { + return type_lo + } else { + return ast.void_type + } + } else if type_hi.is_float() { + if idx_hi == ast.f32_type_idx { + if idx_lo in [ast.i64_type_idx, ast.u64_type_idx] { + return ast.void_type + } else { + return type_hi + } + } else { // f64, float_literal + return type_hi + } + } else if idx_lo >= ast.byte_type_idx { // both operands are unsigned + return type_hi + } else if idx_lo >= ast.i8_type_idx + && (idx_hi <= ast.i64_type_idx || idx_hi == ast.rune_type_idx) { // both signed + return if idx_lo == ast.i64_type_idx { type_lo } else { type_hi } + } else if idx_hi - idx_lo < (ast.byte_type_idx - ast.i8_type_idx) { + return type_lo // conversion unsigned -> signed if signed type is larger + } else { + return ast.void_type // conversion signed -> unsigned not allowed + } +} + +// TODO: promote(), check_types(), symmetric_check() and check() overlap - should be rearranged +pub fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { + if got == expected { + return true + } + got_is_ptr := got.is_ptr() + exp_is_ptr := expected.is_ptr() + if got_is_ptr && exp_is_ptr { + if got.nr_muls() != expected.nr_muls() { + return false + } + } + exp_idx := expected.idx() + got_idx := got.idx() + if exp_idx == got_idx { + return true + } + if exp_idx == ast.voidptr_type_idx || exp_idx == ast.byteptr_type_idx + || (expected.is_ptr() && expected.deref().idx() == ast.byte_type_idx) { + if got.is_ptr() || got.is_pointer() { + return true + } + } + // allow direct int-literal assignment for pointers for now + // maybe in the future optionals should be used for that + if expected.is_ptr() || expected.is_pointer() { + if got == ast.int_literal_type { + return true + } + } + if got_idx == ast.voidptr_type_idx || got_idx == ast.byteptr_type_idx + || (got_idx == ast.byte_type_idx && got.is_ptr()) { + if expected.is_ptr() || expected.is_pointer() { + return true + } + } + if expected == ast.charptr_type && got == ast.char_type.to_ptr() { + return true + } + if expected.has_flag(.optional) { + sym := c.table.get_type_symbol(got) + if (sym.kind == .interface_ && sym.name == 'IError') + || got in [ast.none_type, ast.error_type] { + return true + } else if !c.check_basic(got, expected.clear_flag(.optional)) { + return false + } + } + if !c.check_basic(got, expected) { // TODO: this should go away... + return false + } + if got.is_number() && expected.is_number() { + if got == ast.rune_type && expected == ast.byte_type { + return true + } else if expected == ast.rune_type && got == ast.byte_type { + return true + } + if c.promote_num(expected, got) != expected { + // println('could not promote ${c.table.get_type_symbol(got).name} to ${c.table.get_type_symbol(expected).name}') + return false + } + } + if expected.has_flag(.generic) { + return false + } + return true +} + +pub fn (mut c Checker) check_expected(got ast.Type, expected ast.Type) ? { + if !c.check_types(got, expected) { + return error(c.expected_msg(got, expected)) + } +} + +[inline] +fn (c &Checker) expected_msg(got ast.Type, expected ast.Type) string { + exps := c.table.type_to_str(expected) + gots := c.table.type_to_str(got) + return 'expected `$exps`, not `$gots`' +} + +pub fn (mut c Checker) symmetric_check(left ast.Type, right ast.Type) bool { + // allow direct int-literal assignment for pointers for now + // maybe in the future optionals should be used for that + if right.is_ptr() || right.is_pointer() { + if left == ast.int_literal_type { + return true + } + } + // allow direct int-literal assignment for pointers for now + if left.is_ptr() || left.is_pointer() { + if right == ast.int_literal_type { + return true + } + } + return c.check_basic(left, right) +} + +pub fn (mut c Checker) get_default_fmt(ftyp ast.Type, typ ast.Type) byte { + if ftyp.has_flag(.optional) { + return `s` + } else if typ.is_float() { + return `g` + } else if typ.is_signed() || typ.is_int_literal() { + return `d` + } else if typ.is_unsigned() { + return `u` + } else if typ.is_pointer() { + return `p` + } else { + mut sym := c.table.get_type_symbol(c.unwrap_generic(ftyp)) + if sym.kind == .alias { + // string aliases should be printable + info := sym.info as ast.Alias + sym = c.table.get_type_symbol(info.parent_type) + if info.parent_type == ast.string_type { + return `s` + } + } + if sym.kind == .function { + return `s` + } + if ftyp in [ast.string_type, ast.bool_type] + || sym.kind in [.enum_, .array, .array_fixed, .struct_, .map, .multi_return, .sum_type, .interface_, .none_] + || ftyp.has_flag(.optional) || sym.has_method('str') { + return `s` + } else { + return `_` + } + } +} + +pub fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) { + mut pos := token.Position{} + match expr { + ast.Ident { + if typ.has_flag(.shared_f) { + if expr.name !in c.rlocked_names && expr.name !in c.locked_names { + action := if what == 'argument' { 'passed' } else { 'used' } + c.error('`$expr.name` is `shared` and must be `rlock`ed or `lock`ed to be $action as non-mut $what', + expr.pos) + } + } + return + } + ast.SelectorExpr { + pos = expr.pos + if typ.has_flag(.shared_f) { + expr_name := '${expr.expr}.$expr.field_name' + if expr_name !in c.rlocked_names && expr_name !in c.locked_names { + action := if what == 'argument' { 'passed' } else { 'used' } + c.error('`$expr_name` is `shared` and must be `rlock`ed or `lock`ed to be $action as non-mut $what', + expr.pos) + } + return + } else { + c.fail_if_unreadable(expr.expr, expr.expr_type, what) + } + } + ast.IndexExpr { + pos = expr.left.position().extend(expr.pos) + c.fail_if_unreadable(expr.left, expr.left_type, what) + } + else {} + } + if typ.has_flag(.shared_f) { + c.error('you have to create a handle and `rlock` it to use a `shared` element as non-mut $what', + pos) + } +} + +pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type { + inside_println_arg_save := c.inside_println_arg + c.inside_println_arg = true + for i, expr in node.exprs { + ftyp := c.expr(expr) + if ftyp == ast.void_type { + c.error('expression does not return a value', expr.position()) + } else if ftyp == ast.char_type && ftyp.nr_muls() == 0 { + c.error('expression returning type `char` cannot be used in string interpolation directly, print its address or cast it to an integer instead', + expr.position()) + } + c.fail_if_unreadable(expr, ftyp, 'interpolation object') + node.expr_types << ftyp + typ := c.table.unalias_num_type(ftyp) + mut fmt := node.fmts[i] + // analyze and validate format specifier + if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `S`, `p`, + `_`, + ] { + c.error('unknown format specifier `${fmt:c}`', node.fmt_poss[i]) + } + if fmt == `_` { // set default representation for type if none has been given + fmt = c.get_default_fmt(ftyp, typ) + if fmt == `_` { + if typ != ast.void_type { + c.error('no known default format for type `${c.table.get_type_name(ftyp)}`', + node.fmt_poss[i]) + } + } else { + node.fmts[i] = fmt + node.need_fmts[i] = false + } + } else { // check if given format specifier is valid for type + if node.precisions[i] != 987698 && !typ.is_float() { + c.error('precision specification only valid for float types', node.fmt_poss[i]) + } + if node.pluss[i] && !typ.is_number() { + c.error('plus prefix only allowed for numbers', node.fmt_poss[i]) + } + if (typ.is_unsigned() && fmt !in [`u`, `x`, `X`, `o`, `c`]) + || (typ.is_signed() && fmt !in [`d`, `x`, `X`, `o`, `c`]) + || (typ.is_int_literal() && fmt !in [`d`, `c`, `x`, `X`, `o`, `u`, `x`, `X`, `o`]) + || (typ.is_float() && fmt !in [`E`, `F`, `G`, `e`, `f`, `g`]) + || (typ.is_pointer() && fmt !in [`p`, `x`, `X`]) + || (typ.is_string() && fmt !in [`s`, `S`]) + || (typ.idx() in [ast.i64_type_idx, ast.f64_type_idx] && fmt == `c`) { + c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`', + node.fmt_poss[i]) + } + node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ) + } + // check recursive str + if c.table.cur_fn.is_method && c.table.cur_fn.name == 'str' + && c.table.cur_fn.receiver.name == expr.str() { + c.error('cannot call `str()` method recursively', expr.position()) + } + } + c.inside_println_arg = inside_println_arg_save + return ast.string_type +} + +const hex_lit_overflow_message = 'hex character literal overflows string' + +pub fn (mut c Checker) string_lit(mut node ast.StringLiteral) ast.Type { + mut idx := 0 + for idx < node.val.len { + match node.val[idx] { + `\\` { + mut start_pos := token.Position{ + ...node.pos + col: node.pos.col + 1 + idx + } + start_idx := idx + idx++ + next_ch := node.val[idx] or { return ast.string_type } + if next_ch == `x` { + idx++ + mut ch := node.val[idx] or { return ast.string_type } + mut hex_char_count := 0 + for ch.is_hex_digit() { + hex_char_count++ + end_pos := token.Position{ + ...start_pos + len: idx + 1 - start_idx + } + match hex_char_count { + 1...5 {} + 6 { + first_digit := node.val[idx - 5] - 48 + second_digit := node.val[idx - 4] - 48 + if first_digit > 1 { + c.error(checker.hex_lit_overflow_message, end_pos) + } else if first_digit == 1 && second_digit > 0 { + c.error(checker.hex_lit_overflow_message, end_pos) + } + } + else { + c.error(checker.hex_lit_overflow_message, end_pos) + } + } + idx++ + ch = node.val[idx] or { return ast.string_type } + } + } + } + else { + idx++ + } + } + } + return ast.string_type +} + +pub fn (mut c Checker) int_lit(mut node ast.IntegerLiteral) ast.Type { + if node.val.len < 17 { + // can not be a too large number, no need for more expensive checks + return ast.int_literal_type + } + lit := node.val.replace('_', '').all_after('-') + is_neg := node.val.starts_with('-') + limit := if is_neg { '9223372036854775808' } else { '18446744073709551615' } + message := 'integer literal $node.val overflows int' + + if lit.len > limit.len { + c.error(message, node.pos) + } else if lit.len == limit.len { + for i, digit in lit { + if digit > limit[i] { + c.error(message, node.pos) + } else if digit < limit[i] { + break + } + } + } + + return ast.int_literal_type +} + +pub fn (mut c Checker) infer_fn_generic_types(f ast.Fn, mut call_expr ast.CallExpr) { + mut inferred_types := []ast.Type{} + for gi, gt_name in f.generic_names { + // skip known types + if gi < call_expr.concrete_types.len { + inferred_types << call_expr.concrete_types[gi] + continue + } + mut typ := ast.void_type + for i, param in f.params { + mut to_set := ast.void_type + // resolve generic struct receiver + if i == 0 && call_expr.is_method && param.typ.has_flag(.generic) { + sym := c.table.get_type_symbol(call_expr.receiver_type) + match sym.info { + ast.Struct, ast.Interface, ast.SumType { + if c.table.cur_fn.generic_names.len > 0 { // in generic fn + if gt_name in c.table.cur_fn.generic_names + && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { + idx := c.table.cur_fn.generic_names.index(gt_name) + typ = c.table.cur_concrete_types[idx] + } + } else { // in non-generic fn + receiver_generic_names := sym.info.generic_types.map(c.table.get_type_symbol(it).name) + if gt_name in receiver_generic_names + && sym.info.generic_types.len == sym.info.concrete_types.len { + idx := receiver_generic_names.index(gt_name) + typ = sym.info.concrete_types[idx] + } + } + } + else {} + } + } + arg_i := if i != 0 && call_expr.is_method { i - 1 } else { i } + if call_expr.args.len <= arg_i { + break + } + arg := call_expr.args[arg_i] + param_type_sym := c.table.get_type_symbol(param.typ) + + if param.typ.has_flag(.generic) && param_type_sym.name == gt_name { + to_set = c.table.mktyp(arg.typ) + sym := c.table.get_type_symbol(arg.typ) + if sym.info is ast.FnType { + mut func := sym.info.func + func.name = '' + idx := c.table.find_or_register_fn_type(c.mod, func, true, false) + to_set = ast.new_type(idx).derive(arg.typ) + } + if arg.expr.is_auto_deref_var() { + to_set = to_set.deref() + } + // resolve &T &&T ... + if param.typ.nr_muls() > 0 && to_set.nr_muls() > 0 { + to_set = to_set.set_nr_muls(0) + } + // If the parent fn param is a generic too + if to_set.has_flag(.generic) { + to_set = c.unwrap_generic(to_set) + } + } else if param.typ.has_flag(.generic) { + arg_sym := c.table.get_type_symbol(arg.typ) + if param.typ.has_flag(.variadic) { + to_set = c.table.mktyp(arg.typ) + } else if arg_sym.kind == .array && param_type_sym.kind == .array { + mut arg_elem_info := arg_sym.info as ast.Array + mut param_elem_info := param_type_sym.info as ast.Array + mut arg_elem_sym := c.table.get_type_symbol(arg_elem_info.elem_type) + mut param_elem_sym := c.table.get_type_symbol(param_elem_info.elem_type) + for { + if arg_elem_sym.kind == .array && param_elem_sym.kind == .array + && param_elem_sym.name !in c.table.cur_fn.generic_names { + arg_elem_info = arg_elem_sym.info as ast.Array + arg_elem_sym = c.table.get_type_symbol(arg_elem_info.elem_type) + param_elem_info = param_elem_sym.info as ast.Array + param_elem_sym = c.table.get_type_symbol(param_elem_info.elem_type) + } else { + to_set = arg_elem_info.elem_type + break + } + } + } else if arg_sym.kind == .array_fixed && param_type_sym.kind == .array_fixed { + mut arg_elem_info := arg_sym.info as ast.ArrayFixed + mut param_elem_info := param_type_sym.info as ast.ArrayFixed + mut arg_elem_sym := c.table.get_type_symbol(arg_elem_info.elem_type) + mut param_elem_sym := c.table.get_type_symbol(param_elem_info.elem_type) + for { + if arg_elem_sym.kind == .array_fixed && param_elem_sym.kind == .array_fixed + && param_elem_sym.name !in c.table.cur_fn.generic_names { + arg_elem_info = arg_elem_sym.info as ast.ArrayFixed + arg_elem_sym = c.table.get_type_symbol(arg_elem_info.elem_type) + param_elem_info = param_elem_sym.info as ast.ArrayFixed + param_elem_sym = c.table.get_type_symbol(param_elem_info.elem_type) + } else { + to_set = arg_elem_info.elem_type + break + } + } + } else if arg_sym.kind == .map && param_type_sym.kind == .map { + arg_map_info := arg_sym.info as ast.Map + param_map_info := param_type_sym.info as ast.Map + if param_map_info.key_type.has_flag(.generic) + && c.table.get_type_symbol(param_map_info.key_type).name == gt_name { + typ = arg_map_info.key_type + } + if param_map_info.value_type.has_flag(.generic) + && c.table.get_type_symbol(param_map_info.value_type).name == gt_name { + typ = arg_map_info.value_type + } + } else if arg_sym.kind in [.struct_, .interface_, .sum_type] { + mut generic_types := []ast.Type{} + mut concrete_types := []ast.Type{} + match mut arg_sym.info { + ast.Struct, ast.Interface, ast.SumType { + generic_types = arg_sym.info.generic_types + concrete_types = arg_sym.info.concrete_types + } + else {} + } + generic_names := generic_types.map(c.table.get_type_symbol(it).name) + if gt_name in generic_names && generic_types.len == concrete_types.len { + idx := generic_names.index(gt_name) + typ = concrete_types[idx] + } + } + } + + if to_set != ast.void_type { + if typ != ast.void_type { + // try to promote + // only numbers so we don't promote pointers + if typ.is_number() && to_set.is_number() { + promoted := c.promote_num(typ, to_set) + if promoted != ast.void_type { + to_set = promoted + } + } + if !c.check_types(typ, to_set) { + c.error('inferred generic type `$gt_name` is ambiguous: got `${c.table.get_type_symbol(to_set).name}`, expected `${c.table.get_type_symbol(typ).name}`', + arg.pos) + } + } + typ = to_set + } + } + if typ == ast.void_type { + c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos) + return + } + if c.pref.is_verbose { + s := c.table.type_to_str(typ) + println('inferred `$f.name<$s>`') + } + inferred_types << typ + call_expr.concrete_types << typ + } + if c.table.register_fn_concrete_types(f.name, inferred_types) { + c.need_recheck_generic_fns = true + } +} diff --git a/v_windows/v/vlib/v/checker/checker.v b/v_windows/v/vlib/v/checker/checker.v new file mode 100644 index 0000000..eb83fa7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/checker.v @@ -0,0 +1,8392 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module checker + +import os +import strings +import time +import v.ast +import v.vmod +import v.token +import v.pref +import v.util +import v.util.version +import v.errors +import v.pkgconfig +import v.gen.native + +const int_min = int(0x80000000) + +const int_max = int(0x7FFFFFFF) + +const ( + valid_comp_if_os = ['windows', 'ios', 'macos', 'mach', 'darwin', 'hpux', 'gnu', + 'qnx', 'linux', 'freebsd', 'openbsd', 'netbsd', 'bsd', 'dragonfly', 'android', 'solaris', + 'haiku', 'serenity', 'vinix'] + valid_comp_if_compilers = ['gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus'] + valid_comp_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32'] + valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian'] + valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc', + 'no_bounds_checking', 'freestanding', 'threads', 'js_node', 'js_browser', 'js_freestanding'] + valid_comp_not_user_defined = all_valid_comptime_idents() + array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', + 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop'] + vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead' +) + +fn all_valid_comptime_idents() []string { + mut res := []string{} + res << checker.valid_comp_if_os + res << checker.valid_comp_if_compilers + res << checker.valid_comp_if_platforms + res << checker.valid_comp_if_cpu_features + res << checker.valid_comp_if_other + return res +} + +[heap] +pub struct Checker { + pref &pref.Preferences // Preferences shared from V struct +pub mut: + table &ast.Table + file &ast.File = 0 + nr_errors int + nr_warnings int + nr_notices int + should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time. + errors []errors.Error + warnings []errors.Warning + notices []errors.Notice + error_lines []int // to avoid printing multiple errors for the same line + expected_type ast.Type + expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type + const_decl string + const_deps []string + const_names []string + global_names []string + locked_names []string // vars that are currently locked + rlocked_names []string // vars that are currently read-locked + in_for_count int // if checker is currently in a for loop + // checked_ident string // to avoid infinite checker loops + returns bool + scope_returns bool + mod string // current module name + is_builtin_mod bool // are we in `builtin`? + inside_unsafe bool + inside_const bool + inside_anon_fn bool + inside_ref_lit bool + inside_defer bool + inside_fn_arg bool // `a`, `b` in `a.f(b)` + inside_ct_attr bool // true inside [if expr] + skip_flags bool // should `#flag` and `#include` be skipped + fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc + ct_cond_stack []ast.Expr +mut: + files []ast.File + expr_level int // to avoid infinite recursion segfaults due to compiler bugs + inside_sql bool // to handle sql table fields pseudo variables + cur_orm_ts ast.TypeSymbol + error_details []string + vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path** + vweb_gen_types []ast.Type // vweb route checks + prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type, stopping unwrapping then + loop_label string // set when inside a labelled for loop + timers &util.Timers = util.new_timers(false) + comptime_fields_type map[string]ast.Type + fn_scope &ast.Scope = voidptr(0) + main_fn_decl_node ast.FnDecl + match_exhaustive_cutoff_limit int = 10 + // TODO: these are here temporarily and used for deprecations; remove soon + using_new_err_struct bool + inside_selector_expr bool + inside_println_arg bool + inside_decl_rhs bool + need_recheck_generic_fns bool // need recheck generic fns because there are cascaded nested generic fn +} + +pub fn new_checker(table &ast.Table, pref &pref.Preferences) &Checker { + mut timers_should_print := false + $if time_checking ? { + timers_should_print = true + } + return &Checker{ + table: table + pref: pref + timers: util.new_timers(timers_should_print) + match_exhaustive_cutoff_limit: pref.checker_match_exhaustive_cutoff_limit + } +} + +pub fn (mut c Checker) check(ast_file &ast.File) { + c.change_current_file(ast_file) + for i, ast_import in ast_file.imports { + for sym in ast_import.syms { + full_name := ast_import.mod + '.' + sym.name + if full_name in c.const_names { + c.error('cannot selectively import constant `$sym.name` from `$ast_import.mod`, import `$ast_import.mod` and use `$full_name` instead', + sym.pos) + } + } + for j in 0 .. i { + if ast_import.mod == ast_file.imports[j].mod { + c.error('`$ast_import.mod` was already imported on line ${ + ast_file.imports[j].mod_pos.line_nr + 1}', ast_import.mod_pos) + } + } + } + for mut stmt in ast_file.stmts { + if stmt is ast.ConstDecl || stmt is ast.ExprStmt { + c.expr_level = 0 + c.stmt(stmt) + } + if c.should_abort { + return + } + } + for mut stmt in ast_file.stmts { + if stmt is ast.GlobalDecl { + c.expr_level = 0 + c.stmt(stmt) + } + if c.should_abort { + return + } + } + for mut stmt in ast_file.stmts { + if stmt !is ast.ConstDecl && stmt !is ast.GlobalDecl && stmt !is ast.ExprStmt { + c.expr_level = 0 + c.stmt(stmt) + } + if c.should_abort { + return + } + } + c.check_scope_vars(c.file.scope) +} + +pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) { + if !c.pref.is_repl && !c.file.is_test { + for _, obj in sc.objects { + match obj { + ast.Var { + if !obj.is_used && obj.name[0] != `_` { + c.warn('unused variable: `$obj.name`', obj.pos) + } + if obj.is_mut && !obj.is_changed && !c.is_builtin_mod && obj.name != 'it' { + // if obj.is_mut && !obj.is_changed && !c.is_builtin { //TODO C error bad field not checked + // c.warn('`$obj.name` is declared as mutable, but it was never changed', + // obj.pos) + } + } + else {} + } + } + } + for child in sc.children { + c.check_scope_vars(child) + } +} + +// not used right now +pub fn (mut c Checker) check2(ast_file &ast.File) []errors.Error { + c.change_current_file(ast_file) + for stmt in ast_file.stmts { + c.stmt(stmt) + } + return c.errors +} + +pub fn (mut c Checker) change_current_file(file &ast.File) { + c.file = unsafe { file } + c.vmod_file_content = '' + c.mod = file.mod.name +} + +pub fn (mut c Checker) check_files(ast_files []&ast.File) { + // c.files = ast_files + mut has_main_mod_file := false + mut has_main_fn := false + mut files_from_main_module := []&ast.File{} + for i in 0 .. ast_files.len { + file := unsafe { ast_files[i] } + c.timers.start('checker_check $file.path') + c.check(file) + if file.mod.name == 'main' { + files_from_main_module << file + has_main_mod_file = true + if c.file_has_main_fn(file) { + has_main_fn = true + } + } + c.timers.show('checker_check $file.path') + } + if has_main_mod_file && !has_main_fn && files_from_main_module.len > 0 { + if c.pref.is_script && !c.pref.is_test { + // files_from_main_module contain preludes at the start + mut the_main_file := files_from_main_module.last() + the_main_file.stmts << ast.FnDecl{ + name: 'main.main' + mod: 'main' + is_main: true + file: the_main_file.path + return_type: ast.void_type + scope: &ast.Scope{ + parent: 0 + } + } + has_main_fn = true + } + } + c.timers.start('checker_post_process_generic_fns') + last_file := c.file + // post process generic functions. must be done after all files have been + // checked, to eunsure all generic calls are processed as this information + // is needed when the generic type is auto inferred from the call argument + // Check more times if there are more new registered fn concrete types + for { + for file in ast_files { + if file.generic_fns.len > 0 { + c.change_current_file(file) + c.post_process_generic_fns() + } + } + if !c.need_recheck_generic_fns { + break + } + c.need_recheck_generic_fns = false + } + // restore the original c.file && c.mod after post processing + c.change_current_file(last_file) + c.timers.show('checker_post_process_generic_fns') + + c.timers.start('checker_verify_all_vweb_routes') + c.verify_all_vweb_routes() + c.timers.show('checker_verify_all_vweb_routes') + + if c.pref.is_test { + mut n_test_fns := 0 + for _, f in c.table.fns { + if f.is_test { + n_test_fns++ + } + } + if n_test_fns == 0 { + c.add_error_detail('The name of a test function in V, should start with `test_`.') + c.add_error_detail('The test function should take 0 parameters, and no return type. Example:') + c.add_error_detail('fn test_xyz(){ assert 2 + 2 == 4 }') + c.error('a _test.v file should have *at least* one `test_` function', token.Position{}) + } + } + // Make sure fn main is defined in non lib builds + if c.pref.build_mode == .build_module || c.pref.is_test { + return + } + if c.pref.is_shared { + // shared libs do not need to have a main + return + } + if !has_main_mod_file { + c.error('project must include a `main` module or be a shared library (compile with `v -shared`)', + token.Position{}) + } else if !has_main_fn { + c.error('function `main` must be declared in the main module', token.Position{}) + } +} + +// do checks specific to files in main module +// returns `true` if a main function is in the file +fn (mut c Checker) file_has_main_fn(file &ast.File) bool { + mut has_main_fn := false + for stmt in file.stmts { + if stmt is ast.FnDecl { + if stmt.name == 'main.main' { + if has_main_fn { + c.error('function `main` is already defined', stmt.pos) + } + has_main_fn = true + if stmt.params.len > 0 { + c.error('function `main` cannot have arguments', stmt.pos) + } + if stmt.return_type != ast.void_type { + c.error('function `main` cannot return values', stmt.pos) + } + if stmt.no_body { + c.error('function `main` must declare a body', stmt.pos) + } + } else if stmt.attrs.contains('console') { + c.error('only `main` can have the `[console]` attribute', stmt.pos) + } + } + } + return has_main_fn +} + +fn (mut c Checker) check_valid_snake_case(name string, identifier string, pos token.Position) { + if !c.pref.is_vweb && !c.pref.translated && name.len > 0 + && (name[0] == `_` || name.contains('._')) { + c.error('$identifier `$name` cannot start with `_`', pos) + } + if !c.pref.experimental && !c.pref.translated && util.contains_capital(name) { + c.error('$identifier `$name` cannot contain uppercase letters, use snake_case instead', + pos) + } +} + +fn stripped_name(name string) string { + idx := name.last_index('.') or { -1 } + return name[(idx + 1)..] +} + +fn (mut c Checker) check_valid_pascal_case(name string, identifier string, pos token.Position) { + sname := stripped_name(name) + if sname.len > 0 && !sname[0].is_capital() && !c.pref.translated { + c.error('$identifier `$name` must begin with capital letter', pos) + } +} + +pub fn (mut c Checker) type_decl(node ast.TypeDecl) { + match node { + ast.AliasTypeDecl { c.alias_type_decl(node) } + ast.FnTypeDecl { c.fn_type_decl(node) } + ast.SumTypeDecl { c.sum_type_decl(node) } + } +} + +pub fn (mut c Checker) alias_type_decl(node ast.AliasTypeDecl) { + // TODO Remove when `u8` isn't an alias in builtin anymore + if c.file.mod.name != 'builtin' { + c.check_valid_pascal_case(node.name, 'type alias', node.pos) + } + c.ensure_type_exists(node.parent_type, node.type_pos) or { return } + typ_sym := c.table.get_type_symbol(node.parent_type) + if typ_sym.kind in [.placeholder, .int_literal, .float_literal] { + c.error('unknown type `$typ_sym.name`', node.type_pos) + } else if typ_sym.kind == .alias { + orig_sym := c.table.get_type_symbol((typ_sym.info as ast.Alias).parent_type) + c.error('type `$typ_sym.str()` is an alias, use the original alias type `$orig_sym.name` instead', + node.type_pos) + } else if typ_sym.kind == .chan { + c.error('aliases of `chan` types are not allowed.', node.type_pos) + } +} + +pub fn (mut c Checker) fn_type_decl(node ast.FnTypeDecl) { + c.check_valid_pascal_case(node.name, 'fn type', node.pos) + typ_sym := c.table.get_type_symbol(node.typ) + fn_typ_info := typ_sym.info as ast.FnType + fn_info := fn_typ_info.func + c.ensure_type_exists(fn_info.return_type, fn_info.return_type_pos) or {} + ret_sym := c.table.get_type_symbol(fn_info.return_type) + if ret_sym.kind == .placeholder { + c.error('unknown type `$ret_sym.name`', fn_info.return_type_pos) + } + for arg in fn_info.params { + c.ensure_type_exists(arg.typ, arg.type_pos) or { return } + arg_sym := c.table.get_type_symbol(arg.typ) + if arg_sym.kind == .placeholder { + c.error('unknown type `$arg_sym.name`', arg.type_pos) + } + } +} + +pub fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) { + c.check_valid_pascal_case(node.name, 'sum type', node.pos) + mut names_used := []string{} + for variant in node.variants { + if variant.typ.is_ptr() { + c.error('sum type cannot hold a reference type', variant.pos) + } + c.ensure_type_exists(variant.typ, variant.pos) or {} + mut sym := c.table.get_type_symbol(variant.typ) + if sym.name in names_used { + c.error('sum type $node.name cannot hold the type `$sym.name` more than once', + variant.pos) + } else if sym.kind in [.placeholder, .int_literal, .float_literal] { + c.error('unknown type `$sym.name`', variant.pos) + } else if sym.kind == .interface_ { + c.error('sum type cannot hold an interface', variant.pos) + } + if sym.name.trim_prefix(sym.mod + '.') == node.name { + c.error('sum type cannot hold itself', variant.pos) + } + names_used << sym.name + } +} + +pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, iface_embeds []ast.InterfaceEmbedding) []ast.InterfaceEmbedding { + // eprintln('> expand_iface_embeds: idecl.name: $idecl.name | level: $level | iface_embeds.len: $iface_embeds.len') + if level > 100 { + c.error('too many interface embedding levels: $level, for interface `$idecl.name`', + idecl.pos) + return [] + } + if iface_embeds.len == 0 { + return [] + } + mut res := map[int]ast.InterfaceEmbedding{} + mut ares := []ast.InterfaceEmbedding{} + for ie in iface_embeds { + if iface_decl := c.table.interfaces[ie.typ] { + mut list := iface_decl.ifaces + if !iface_decl.are_ifaces_expanded { + list = c.expand_iface_embeds(idecl, level + 1, iface_decl.ifaces) + c.table.interfaces[ie.typ].ifaces = list + c.table.interfaces[ie.typ].are_ifaces_expanded = true + } + for partial in list { + res[partial.typ] = partial + } + } + res[ie.typ] = ie + } + for _, v in res { + ares << v + } + return ares +} + +pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) { + c.check_valid_pascal_case(node.name, 'interface name', node.pos) + mut decl_sym := c.table.get_type_symbol(node.typ) + if mut decl_sym.info is ast.Interface { + if node.ifaces.len > 0 { + all_ifaces := c.expand_iface_embeds(node, 0, node.ifaces) + // eprintln('> node.name: $node.name | node.ifaces.len: $node.ifaces.len | all_ifaces: $all_ifaces.len') + node.ifaces = all_ifaces + mut emnames := map[string]int{} + mut emnames_ds := map[string]bool{} + mut emnames_ds_info := map[string]bool{} + mut efnames := map[string]int{} + mut efnames_ds_info := map[string]bool{} + for i, m in node.methods { + emnames[m.name] = i + emnames_ds[m.name] = true + emnames_ds_info[m.name] = true + } + for i, f in node.fields { + efnames[f.name] = i + efnames_ds_info[f.name] = true + } + // + for iface in all_ifaces { + isym := c.table.get_type_symbol(iface.typ) + if isym.kind != .interface_ { + c.error('interface `$node.name` tries to embed `$isym.name`, but `$isym.name` is not an interface, but `$isym.kind`', + iface.pos) + continue + } + isym_info := isym.info as ast.Interface + for f in isym_info.fields { + if !efnames_ds_info[f.name] { + efnames_ds_info[f.name] = true + decl_sym.info.fields << f + } + } + for m in isym_info.methods { + if !emnames_ds_info[m.name] { + emnames_ds_info[m.name] = true + decl_sym.info.methods << m.new_method_with_receiver_type(node.typ) + } + } + for m in isym.methods { + if !emnames_ds[m.name] { + emnames_ds[m.name] = true + decl_sym.methods << m.new_method_with_receiver_type(node.typ) + } + } + if iface_decl := c.table.interfaces[iface.typ] { + for f in iface_decl.fields { + if f.name in efnames { + // already existing method name, check for conflicts + ifield := node.fields[efnames[f.name]] + if field := c.table.find_field_with_embeds(isym, f.name) { + if ifield.typ != field.typ { + exp := c.table.type_to_str(ifield.typ) + got := c.table.type_to_str(field.typ) + c.error('embedded interface `$iface_decl.name` conflicts existing field: `$ifield.name`, expecting type: `$exp`, got type: `$got`', + ifield.pos) + } + } + } else { + efnames[f.name] = node.fields.len + node.fields << f + } + } + for m in iface_decl.methods { + if m.name in emnames { + // already existing field name, check for conflicts + imethod := node.methods[emnames[m.name]] + if em_fn := decl_sym.find_method(imethod.name) { + if m_fn := isym.find_method(m.name) { + msg := c.table.is_same_method(m_fn, em_fn) + if msg.len > 0 { + em_sig := c.table.fn_signature(em_fn, skip_receiver: true) + m_sig := c.table.fn_signature(m_fn, skip_receiver: true) + c.error('embedded interface `$iface_decl.name` causes conflict: $msg, for interface method `$em_sig` vs `$m_sig`', + imethod.pos) + } + } + } + } else { + emnames[m.name] = node.methods.len + mut new_method := m.new_method_with_receiver_type(node.typ) + new_method.pos = iface.pos + node.methods << new_method + } + } + } + } + } + for i, method in node.methods { + if node.language == .v { + c.check_valid_snake_case(method.name, 'method name', method.pos) + } + c.ensure_type_exists(method.return_type, method.return_type_pos) or { return } + for param in method.params { + c.ensure_type_exists(param.typ, param.pos) or { return } + } + for field in node.fields { + field_sym := c.table.get_type_symbol(field.typ) + if field.name == method.name && field_sym.kind == .function { + c.error('type `$decl_sym.name` has both field and method named `$method.name`', + method.pos) + } + } + for j in 0 .. i { + if method.name == node.methods[j].name { + c.error('duplicate method name `$method.name`', method.pos) + } + } + } + for i, field in node.fields { + if node.language == .v { + c.check_valid_snake_case(field.name, 'field name', field.pos) + } + c.ensure_type_exists(field.typ, field.pos) or { return } + if field.typ == node.typ { + c.error('recursive interface fields are not allowed because they cannot be initialised', + field.type_pos) + } + for j in 0 .. i { + if field.name == node.fields[j].name { + c.error('field name `$field.name` duplicate', field.pos) + } + } + } + } +} + +pub fn (mut c Checker) struct_decl(mut node ast.StructDecl) { + if node.language == .v && !c.is_builtin_mod { + c.check_valid_pascal_case(node.name, 'struct name', node.pos) + } + mut struct_sym := c.table.find_type(node.name) or { ast.TypeSymbol{} } + mut has_generic_types := false + if mut struct_sym.info is ast.Struct { + for embed in node.embeds { + if embed.typ.has_flag(.generic) { + has_generic_types = true + } + embed_sym := c.table.get_type_symbol(embed.typ) + if embed_sym.kind != .struct_ { + c.error('`$embed_sym.name` is not a struct', embed.pos) + } else { + info := embed_sym.info as ast.Struct + if info.is_heap && !embed.typ.is_ptr() { + struct_sym.info.is_heap = true + } + } + } + for attr in node.attrs { + if attr.name == 'typedef' && node.language != .c { + c.error('`typedef` attribute can only be used with C structs', node.pos) + } + } + for i, field in node.fields { + c.ensure_type_exists(field.typ, field.type_pos) or { return } + if field.typ.has_flag(.generic) { + has_generic_types = true + } + if node.language == .v { + c.check_valid_snake_case(field.name, 'field name', field.pos) + } + sym := c.table.get_type_symbol(field.typ) + for j in 0 .. i { + if field.name == node.fields[j].name { + c.error('field name `$field.name` duplicate', field.pos) + } + } + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if info.is_heap && !field.typ.is_ptr() { + struct_sym.info.is_heap = true + } + } + if field.has_default_expr { + c.expected_type = field.typ + mut field_expr_type := c.expr(field.default_expr) + if !field.typ.has_flag(.optional) { + c.check_expr_opt_call(field.default_expr, field_expr_type) + } + struct_sym.info.fields[i].default_expr_typ = field_expr_type + c.check_expected(field_expr_type, field.typ) or { + if sym.kind == .interface_ + && c.type_implements(field_expr_type, field.typ, field.pos) { + if !field_expr_type.is_ptr() && !field_expr_type.is_pointer() + && !c.inside_unsafe { + field_expr_type_sym := c.table.get_type_symbol(field_expr_type) + if field_expr_type_sym.kind != .interface_ { + c.mark_as_referenced(mut &node.fields[i].default_expr, + true) + } + } + } else { + c.error('incompatible initializer for field `$field.name`: $err.msg', + field.default_expr.position()) + } + } + // Check for unnecessary inits like ` = 0` and ` = ''` + if field.typ.is_ptr() { + continue + } + if field.default_expr is ast.IntegerLiteral { + if field.default_expr.val == '0' { + c.warn('unnecessary default value of `0`: struct fields are zeroed by default', + field.default_expr.pos) + } + } else if field.default_expr is ast.StringLiteral { + if field.default_expr.val == '' { + c.warn("unnecessary default value of '': struct fields are zeroed by default", + field.default_expr.pos) + } + } else if field.default_expr is ast.BoolLiteral { + if field.default_expr.val == false { + c.warn('unnecessary default value `false`: struct fields are zeroed by default', + field.default_expr.pos) + } + } + } + } + if node.generic_types.len == 0 && has_generic_types { + c.error('generic struct declaration must specify the generic type names, e.g. Foo', + node.pos) + } + } +} + +fn (mut c Checker) unwrap_generic_type(typ ast.Type, generic_names []string, concrete_types []ast.Type) ast.Type { + mut final_concrete_types := []ast.Type{} + mut fields := []ast.StructField{} + mut nrt := '' + mut c_nrt := '' + ts := c.table.get_type_symbol(typ) + match mut ts.info { + ast.Struct, ast.Interface, ast.SumType { + if !ts.info.is_generic { + return typ + } + nrt = '$ts.name<' + c_nrt = '${ts.cname}_T_' + for i in 0 .. ts.info.generic_types.len { + if ct := c.table.resolve_generic_to_concrete(ts.info.generic_types[i], + generic_names, concrete_types) + { + gts := c.table.get_type_symbol(ct) + nrt += gts.name + c_nrt += gts.cname + if i != ts.info.generic_types.len - 1 { + nrt += ', ' + c_nrt += '_' + } + } + } + nrt += '>' + idx := c.table.type_idxs[nrt] + if idx != 0 && c.table.type_symbols[idx].kind != .placeholder { + return ast.new_type(idx).derive(typ).clear_flag(.generic) + } else { + // fields type translate to concrete type + fields = ts.info.fields.clone() + for i in 0 .. fields.len { + if fields[i].typ.has_flag(.generic) { + sym := c.table.get_type_symbol(fields[i].typ) + if sym.kind == .struct_ && fields[i].typ.idx() != typ.idx() { + fields[i].typ = c.unwrap_generic_type(fields[i].typ, generic_names, + concrete_types) + } else { + if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ, + generic_names, concrete_types) + { + fields[i].typ = t_typ + } + } + } + } + // update concrete types + for i in 0 .. ts.info.generic_types.len { + if t_typ := c.table.resolve_generic_to_concrete(ts.info.generic_types[i], + generic_names, concrete_types) + { + final_concrete_types << t_typ + } + } + if final_concrete_types.len > 0 { + for method in ts.methods { + c.table.register_fn_concrete_types(method.name, final_concrete_types) + } + } + } + } + else {} + } + match mut ts.info { + ast.Struct { + mut info := ts.info + info.is_generic = false + info.concrete_types = final_concrete_types + info.parent_type = typ + info.fields = fields + new_idx := c.table.register_type_symbol( + kind: .struct_ + name: nrt + cname: util.no_dots(c_nrt) + mod: c.mod + info: info + ) + return ast.new_type(new_idx).derive(typ).clear_flag(.generic) + } + ast.Interface { + // resolve generic types inside methods + mut imethods := ts.info.methods.clone() + for mut method in imethods { + if t := c.table.resolve_generic_to_concrete(method.return_type, generic_names, + concrete_types) + { + method.return_type = t + } + for mut param in method.params { + if t := c.table.resolve_generic_to_concrete(param.typ, generic_names, + concrete_types) + { + param.typ = t + } + } + } + mut all_methods := ts.methods + for imethod in imethods { + for mut method in all_methods { + if imethod.name == method.name { + method = imethod + } + } + } + mut info := ts.info + info.is_generic = false + info.concrete_types = final_concrete_types + info.parent_type = typ + info.fields = fields + info.methods = imethods + new_idx := c.table.register_type_symbol( + kind: .interface_ + name: nrt + cname: util.no_dots(c_nrt) + mod: c.mod + info: info + ) + mut ts_copy := c.table.get_type_symbol(new_idx) + for method in all_methods { + ts_copy.register_method(method) + } + return ast.new_type(new_idx).derive(typ).clear_flag(.generic) + } + else {} + } + return typ +} + +// generic struct instantiations to concrete types +pub fn (mut c Checker) generic_insts_to_concrete() { + for mut typ in c.table.type_symbols { + if typ.kind == .generic_inst { + info := typ.info as ast.GenericInst + parent := c.table.type_symbols[info.parent_idx] + if parent.kind == .placeholder { + typ.kind = .placeholder + continue + } + match parent.info { + ast.Struct { + mut parent_info := parent.info as ast.Struct + if !parent_info.is_generic { + util.verror('generic error', 'struct `$parent.name` is not a generic struct, cannot instantiate to the concrete types') + continue + } + mut fields := parent_info.fields.clone() + if parent_info.generic_types.len == info.concrete_types.len { + generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name) + for i in 0 .. fields.len { + if fields[i].typ.has_flag(.generic) { + sym := c.table.get_type_symbol(fields[i].typ) + if sym.kind == .struct_ && fields[i].typ.idx() != info.parent_idx { + fields[i].typ = c.unwrap_generic_type(fields[i].typ, + generic_names, info.concrete_types) + } else { + if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ, + generic_names, info.concrete_types) + { + fields[i].typ = t_typ + } + } + } + } + parent_info.is_generic = false + parent_info.concrete_types = info.concrete_types.clone() + parent_info.fields = fields + parent_info.parent_type = ast.new_type(info.parent_idx).set_flag(.generic) + typ.info = ast.Struct{ + ...parent_info + is_generic: false + concrete_types: info.concrete_types.clone() + fields: fields + parent_type: ast.new_type(info.parent_idx).set_flag(.generic) + } + typ.is_public = true + typ.kind = parent.kind + } + parent_sym := c.table.get_type_symbol(parent_info.parent_type) + for method in parent_sym.methods { + c.table.register_fn_concrete_types(method.name, info.concrete_types) + } + } + ast.Interface { + mut parent_info := parent.info as ast.Interface + if !parent_info.is_generic { + util.verror('generic error', 'interface `$parent.name` is not a generic interface, cannot instantiate to the concrete types') + continue + } + if parent_info.generic_types.len == info.concrete_types.len { + mut fields := parent_info.fields.clone() + generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name) + for i in 0 .. fields.len { + if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ, + generic_names, info.concrete_types) + { + fields[i].typ = t_typ + } + } + mut imethods := parent_info.methods.clone() + for mut method in imethods { + method.generic_names.clear() + if pt := c.table.resolve_generic_to_concrete(method.return_type, + generic_names, info.concrete_types) + { + method.return_type = pt + } + method.params = method.params.clone() + for mut param in method.params { + if pt := c.table.resolve_generic_to_concrete(param.typ, + generic_names, info.concrete_types) + { + param.typ = pt + } + } + typ.register_method(method) + } + mut all_methods := parent.methods + for imethod in imethods { + for mut method in all_methods { + if imethod.name == method.name { + method = imethod + } + } + } + typ.info = ast.Interface{ + ...parent_info + is_generic: false + concrete_types: info.concrete_types.clone() + fields: fields + methods: imethods + parent_type: ast.new_type(info.parent_idx).set_flag(.generic) + } + typ.is_public = true + typ.kind = parent.kind + typ.methods = all_methods + } + } + ast.SumType { + mut parent_info := parent.info as ast.SumType + if !parent_info.is_generic { + util.verror('generic error', 'sumtype `$parent.name` is not a generic sumtype, cannot instantiate to the concrete types') + continue + } + if parent_info.generic_types.len == info.concrete_types.len { + mut fields := parent_info.fields.clone() + mut variants := parent_info.variants.clone() + generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name) + for i in 0 .. fields.len { + if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ, + generic_names, info.concrete_types) + { + fields[i].typ = t_typ + } + } + for i in 0 .. variants.len { + if t_typ := c.table.resolve_generic_to_concrete(variants[i], + generic_names, info.concrete_types) + { + variants[i] = t_typ + } + } + typ.info = ast.SumType{ + ...parent_info + is_generic: false + concrete_types: info.concrete_types.clone() + fields: fields + variants: variants + parent_type: ast.new_type(info.parent_idx).set_flag(.generic) + } + typ.is_public = true + typ.kind = parent.kind + } + } + else {} + } + } + } +} + +pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { + if node.typ == ast.void_type { + // Short syntax `({foo: bar})` + if c.expected_type == ast.void_type { + c.error('unexpected short struct syntax', node.pos) + return ast.void_type + } + sym := c.table.get_type_symbol(c.expected_type) + if sym.kind == .array { + node.typ = c.table.value_type(c.expected_type) + } else { + node.typ = c.expected_type + } + } + struct_sym := c.table.get_type_symbol(node.typ) + if struct_sym.info is ast.Struct { + if struct_sym.info.generic_types.len > 0 && struct_sym.info.concrete_types.len == 0 + && c.table.cur_concrete_types.len == 0 { + c.error('generic struct init must specify type parameter, e.g. Foo', + node.pos) + } + } else if struct_sym.info is ast.Alias { + parent_sym := c.table.get_type_symbol(struct_sym.info.parent_type) + // e.g. ´x := MyMapAlias{}´, should be a cast to alias type ´x := MyMapAlias(map[...]...)´ + if parent_sym.kind == .map { + alias_str := c.table.type_to_str(node.typ) + map_str := c.table.type_to_str(struct_sym.info.parent_type) + c.error('direct map alias init is not possible, use `${alias_str}($map_str{})` instead', + node.pos) + return ast.void_type + } + } + unwrapped_struct_type := c.unwrap_generic_type(node.typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + c.ensure_type_exists(unwrapped_struct_type, node.pos) or {} + type_sym := c.table.get_type_symbol(node.typ) + if !c.inside_unsafe && type_sym.kind == .sum_type { + c.note('direct sum type init (`x := SumType{}`) will be removed soon', node.pos) + } + // Make sure the first letter is capital, do not allow e.g. `x := string{}`, + // but `x := T{}` is ok. + if !c.is_builtin_mod && !c.inside_unsafe && type_sym.language == .v + && c.table.cur_concrete_types.len == 0 { + pos := type_sym.name.last_index('.') or { -1 } + first_letter := type_sym.name[pos + 1] + if !first_letter.is_capital() { + c.error('cannot initialize builtin type `$type_sym.name`', node.pos) + } + } + if type_sym.kind == .sum_type && node.fields.len == 1 { + sexpr := node.fields[0].expr.str() + c.error('cast to sum type using `${type_sym.name}($sexpr)` not `$type_sym.name{$sexpr}`', + node.pos) + } + if type_sym.kind == .interface_ { + c.error('cannot instantiate interface `$type_sym.name`', node.pos) + } + if type_sym.info is ast.Alias { + if type_sym.info.parent_type.is_number() { + c.error('cannot instantiate number type alias `$type_sym.name`', node.pos) + return ast.void_type + } + } + // allow init structs from generic if they're private except the type is from builtin module + if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.language != .c + && (type_sym.mod != c.mod && !(node.typ.has_flag(.generic) && type_sym.mod != 'builtin')) { + c.error('type `$type_sym.name` is private', node.pos) + } + if type_sym.kind == .struct_ { + info := type_sym.info as ast.Struct + if info.attrs.len > 0 && info.attrs[0].name == 'noinit' && type_sym.mod != c.mod { + c.error('struct `$type_sym.name` is declared with a `[noinit]` attribute, so ' + + 'it cannot be initialized with `$type_sym.name{}`', node.pos) + } + } + if type_sym.name.len == 1 && c.table.cur_fn.generic_names.len == 0 { + c.error('unknown struct `$type_sym.name`', node.pos) + return 0 + } + match type_sym.kind { + .placeholder { + c.error('unknown struct: $type_sym.name', node.pos) + return ast.void_type + } + // string & array are also structs but .kind of string/array + .struct_, .string, .array, .alias { + mut info := ast.Struct{} + if type_sym.kind == .alias { + info_t := type_sym.info as ast.Alias + sym := c.table.get_type_symbol(info_t.parent_type) + if sym.kind == .placeholder { // pending import symbol did not resolve + c.error('unknown struct: $type_sym.name', node.pos) + return ast.void_type + } + if sym.kind == .struct_ { + info = sym.info as ast.Struct + } else { + c.error('alias type name: $sym.name is not struct type', node.pos) + } + } else { + info = type_sym.info as ast.Struct + } + if node.is_short { + exp_len := info.fields.len + got_len := node.fields.len + if exp_len != got_len { + amount := if exp_len < got_len { 'many' } else { 'few' } + c.error('too $amount fields in `$type_sym.name` literal (expecting $exp_len, got $got_len)', + node.pos) + } + } + mut inited_fields := []string{} + for i, mut field in node.fields { + mut field_info := ast.StructField{} + mut embed_type := ast.Type(0) + mut is_embed := false + mut field_name := '' + if node.is_short { + if i >= info.fields.len { + // It doesn't make sense to check for fields that don't exist. + // We should just stop here. + break + } + field_info = info.fields[i] + field_name = field_info.name + node.fields[i].name = field_name + } else { + field_name = field.name + mut exists := true + field_info = info.find_field(field_name) or { + exists = false + ast.StructField{} + } + if !exists { + for embed in info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if embed_sym.embed_name() == field_name { + exists = true + embed_type = embed + is_embed = true + break + } + embed_struct_info := embed_sym.info as ast.Struct + if embed_field_info := embed_struct_info.find_field(field_name) { + exists = true + field_info = embed_field_info + break + } + } + } + if !exists { + c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`', + field.pos) + continue + } + if field_name in inited_fields { + c.error('duplicate field name in struct literal: `$field_name`', + field.pos) + continue + } + } + mut expr_type := ast.Type(0) + mut expected_type := ast.Type(0) + if is_embed { + expected_type = embed_type + c.expected_type = expected_type + expr_type = c.expr(field.expr) + expr_type_sym := c.table.get_type_symbol(expr_type) + if expr_type != ast.void_type && expr_type_sym.kind != .placeholder { + c.check_expected(expr_type, embed_type) or { + c.error('cannot assign to field `$field_info.name`: $err.msg', + field.pos) + } + } + node.fields[i].typ = expr_type + node.fields[i].expected_type = embed_type + } else { + inited_fields << field_name + field_type_sym := c.table.get_type_symbol(field_info.typ) + expected_type = field_info.typ + c.expected_type = expected_type + expr_type = c.expr(field.expr) + if !field_info.typ.has_flag(.optional) { + expr_type = c.check_expr_opt_call(field.expr, expr_type) + } + expr_type_sym := c.table.get_type_symbol(expr_type) + if field_type_sym.kind == .interface_ { + if c.type_implements(expr_type, field_info.typ, field.pos) { + if !expr_type.is_ptr() && !expr_type.is_pointer() + && expr_type_sym.kind != .interface_ && !c.inside_unsafe { + c.mark_as_referenced(mut &field.expr, true) + } + } + } else if expr_type != ast.void_type && expr_type_sym.kind != .placeholder { + c.check_expected(expr_type, field_info.typ) or { + c.error('cannot assign to field `$field_info.name`: $err.msg', + field.pos) + } + } + if field_info.typ.has_flag(.shared_f) { + if !expr_type.has_flag(.shared_f) && expr_type.is_ptr() { + c.error('`shared` field must be initialized with `shared` or value', + field.pos) + } + } else { + if field_info.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() + && !expr_type.is_number() { + c.error('reference field must be initialized with reference', + field.pos) + } + } + node.fields[i].typ = expr_type + node.fields[i].expected_type = field_info.typ + } + if expr_type.is_ptr() && expected_type.is_ptr() { + if mut field.expr is ast.Ident { + if mut field.expr.obj is ast.Var { + mut obj := unsafe { &field.expr.obj } + if c.fn_scope != voidptr(0) { + obj = c.fn_scope.find_var(obj.name) or { obj } + } + if obj.is_stack_obj && !c.inside_unsafe { + sym := c.table.get_type_symbol(obj.typ.set_nr_muls(0)) + if !sym.is_heap() && !c.pref.translated { + suggestion := if sym.kind == .struct_ { + 'declaring `$sym.name` as `[heap]`' + } else { + 'wrapping the `$sym.name` object in a `struct` declared as `[heap]`' + } + c.error('`$field.expr.name` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.', + field.expr.pos) + } + } + } + } + } + } + // Check uninitialized refs/sum types + for field in info.fields { + if field.has_default_expr || field.name in inited_fields { + continue + } + if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !node.has_update_expr + && !c.pref.translated { + c.error('reference field `${type_sym.name}.$field.name` must be initialized', + node.pos) + } + // Do not allow empty uninitialized interfaces + sym := c.table.get_type_symbol(field.typ) + if sym.kind == .interface_ { + // TODO: should be an error instead, but first `ui` needs updating. + c.note('interface field `${type_sym.name}.$field.name` must be initialized', + node.pos) + } + // Do not allow empty uninitialized sum types + /* + sym := c.table.get_type_symbol(field.typ) + if sym.kind == .sum_type { + c.warn('sum type field `${type_sym.name}.$field.name` must be initialized', + node.pos) + } + */ + // Check for `[required]` struct attr + if field.attrs.contains('required') && !node.is_short && !node.has_update_expr { + mut found := false + for init_field in node.fields { + if field.name == init_field.name { + found = true + break + } + } + if !found { + c.error('field `${type_sym.name}.$field.name` must be initialized', + node.pos) + } + } + } + } + else {} + } + if node.has_update_expr { + update_type := c.expr(node.update_expr) + node.update_expr_type = update_type + if c.table.type_kind(update_type) != .struct_ { + s := c.table.type_to_str(update_type) + c.error('expected struct, found `$s`', node.update_expr.position()) + } else if update_type != node.typ { + from_sym := c.table.get_type_symbol(update_type) + to_sym := c.table.get_type_symbol(node.typ) + from_info := from_sym.info as ast.Struct + to_info := to_sym.info as ast.Struct + // TODO this check is too strict + if !c.check_struct_signature(from_info, to_info) { + c.error('struct `$from_sym.name` is not compatible with struct `$to_sym.name`', + node.update_expr.position()) + } + } + if !node.update_expr.is_lvalue() { + // cgen will repeat `update_expr` for each field + // so enforce an lvalue for efficiency + c.error('expression is not an lvalue', node.update_expr.position()) + } + } + return node.typ +} + +fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) { + match mut expr { + ast.FloatLiteral { + if expr.val.f64() == 0.0 { + oper := if op_kind == .div { 'division' } else { 'modulo' } + c.error('$oper by zero', expr.pos) + } + } + ast.IntegerLiteral { + if expr.val.int() == 0 { + oper := if op_kind == .div { 'division' } else { 'modulo' } + c.error('$oper by zero', expr.pos) + } + } + ast.CastExpr { + c.check_div_mod_by_zero(expr.expr, op_kind) + } + else {} + } +} + +pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { + former_expected_type := c.expected_type + defer { + c.expected_type = former_expected_type + } + left_type := c.expr(node.left) + node.left_type = left_type + c.expected_type = left_type + right_type := c.expr(node.right) + node.right_type = right_type + if left_type.is_number() && !left_type.is_ptr() + && right_type in [ast.int_literal_type, ast.float_literal_type] { + node.right_type = left_type + } + if right_type.is_number() && !right_type.is_ptr() + && left_type in [ast.int_literal_type, ast.float_literal_type] { + node.left_type = right_type + } + mut right_sym := c.table.get_type_symbol(right_type) + right_final := c.table.get_final_type_symbol(right_type) + mut left_sym := c.table.get_type_symbol(left_type) + left_final := c.table.get_final_type_symbol(left_type) + left_pos := node.left.position() + right_pos := node.right.position() + left_right_pos := left_pos.extend(right_pos) + if left_type.is_any_kind_of_pointer() + && node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] { + if (right_type.is_any_kind_of_pointer() && node.op != .minus) + || (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus]) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + c.error('invalid operator `$node.op` to `$left_name` and `$right_name`', left_right_pos) + } else if node.op in [.plus, .minus] { + if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() { + c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_right_pos) + } + if left_type == ast.voidptr_type { + c.error('`$node.op` cannot be used with `voidptr`', left_pos) + } + } + } + mut return_type := left_type + if node.op != .key_is { + match mut node.left { + ast.Ident, ast.SelectorExpr { + if node.left.is_mut { + c.error('remove unnecessary `mut`', node.left.mut_pos) + } + } + else {} + } + } + eq_ne := node.op in [.eq, .ne] + // Single side check + // Place these branches according to ops' usage frequency to accelerate. + // TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented. + // TODO: Some of the checks are not single side. Should find a better way to organize them. + match node.op { + // .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {} + .eq, .ne { + is_mismatch := + (left_sym.kind == .alias && right_sym.kind in [.struct_, .array, .sum_type]) + || (right_sym.kind == .alias && left_sym.kind in [.struct_, .array, .sum_type]) + if is_mismatch { + c.error('possible type mismatch of compared values of `$node.op` operation', + left_right_pos) + } + } + .key_in, .not_in { + match right_final.kind { + .array { + elem_type := right_final.array_info().elem_type + // if left_default.kind != right_sym.kind { + c.check_expected(left_type, elem_type) or { + c.error('left operand to `$node.op` does not match the array element type: $err.msg', + left_right_pos) + } + } + .map { + map_info := right_final.map_info() + c.check_expected(left_type, map_info.key_type) or { + c.error('left operand to `$node.op` does not match the map key type: $err.msg', + left_right_pos) + } + node.left_type = map_info.key_type + } + else { + c.error('`$node.op.str()` can only be used with an array/map/string', + node.pos) + } + } + return ast.bool_type + } + .plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { // binary operators that expect matching types + if right_sym.info is ast.Alias && (right_sym.info as ast.Alias).language != .c + && c.mod == c.table.type_to_str(right_type).split('.')[0] + && c.table.get_type_symbol((right_sym.info as ast.Alias).parent_type).is_primitive() { + right_sym = c.table.get_type_symbol((right_sym.info as ast.Alias).parent_type) + } + if left_sym.info is ast.Alias && (left_sym.info as ast.Alias).language != .c + && c.mod == c.table.type_to_str(left_type).split('.')[0] + && c.table.get_type_symbol((left_sym.info as ast.Alias).parent_type).is_primitive() { + left_sym = c.table.get_type_symbol((left_sym.info as ast.Alias).parent_type) + } + // Check if the alias type is not a primitive then allow using operator overloading for aliased `arrays` and `maps` + if left_sym.kind == .alias && left_sym.info is ast.Alias + && !(c.table.get_type_symbol((left_sym.info as ast.Alias).parent_type).is_primitive()) { + if left_sym.has_method(node.op.str()) { + if method := left_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = left_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if right_sym.kind == .alias && right_sym.info is ast.Alias + && !(c.table.get_type_symbol((right_sym.info as ast.Alias).parent_type).is_primitive()) { + if right_sym.has_method(node.op.str()) { + if method := right_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = right_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } + if left_sym.kind in [.array, .array_fixed, .map, .struct_] { + if left_sym.has_method(node.op.str()) { + if method := left_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = left_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if right_sym.kind in [.array, .array_fixed, .map, .struct_] { + if right_sym.has_method(node.op.str()) { + if method := right_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = right_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if node.left.is_auto_deref_var() || node.right.is_auto_deref_var() { + deref_left_type := if node.left.is_auto_deref_var() { + left_type.deref() + } else { + left_type + } + deref_right_type := if node.right.is_auto_deref_var() { + right_type.deref() + } else { + right_type + } + left_name := c.table.type_to_str(c.table.mktyp(deref_left_type)) + right_name := c.table.type_to_str(c.table.mktyp(deref_right_type)) + if left_name != right_name { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } else { + unaliased_left_type := c.table.unalias_num_type(left_type) + unalias_right_type := c.table.unalias_num_type(right_type) + mut promoted_type := c.promote(unaliased_left_type, unalias_right_type) + // substract pointers is allowed in unsafe block + is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer() + && right_type.is_any_kind_of_pointer() && node.op == .minus + if is_allowed_pointer_arithmetic { + promoted_type = ast.int_type + } + if promoted_type.idx() == ast.void_type_idx { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } else if promoted_type.has_flag(.optional) { + s := c.table.type_to_str(promoted_type) + c.error('`$node.op` cannot be used with `$s`', node.pos) + } else if promoted_type.is_float() { + if node.op in [.mod, .xor, .amp, .pipe] { + side := if left_type == promoted_type { 'left' } else { 'right' } + pos := if left_type == promoted_type { left_pos } else { right_pos } + name := if left_type == promoted_type { + left_sym.name + } else { + right_sym.name + } + if node.op == .mod { + c.error('float modulo not allowed, use math.fmod() instead', + pos) + } else { + c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`', + pos) + } + } + } + if node.op in [.div, .mod] { + c.check_div_mod_by_zero(node.right, node.op) + } + return_type = promoted_type + } + } + .gt, .lt, .ge, .le { + if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] { + c.error('only `==` and `!=` are defined on arrays', node.pos) + } else if left_sym.kind == .struct_ && right_sym.kind == .struct_ + && node.op in [.eq, .lt] { + if !(left_sym.has_method(node.op.str()) && right_sym.has_method(node.op.str())) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } + if left_sym.kind == .struct_ && right_sym.kind == .struct_ { + if !left_sym.has_method('<') && node.op in [.ge, .le] { + c.error('cannot use `$node.op` as `<` operator method is not defined', + left_right_pos) + } else if !left_sym.has_method('<') && node.op == .gt { + c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) + } + } + } + .left_shift { + if left_final.kind == .array { + if !node.is_stmt { + c.error('array append cannot be used in an expression', node.pos) + } + // `array << elm` + c.check_expr_opt_call(node.right, right_type) + node.auto_locked, _ = c.fail_if_immutable(node.left) + left_value_type := c.table.value_type(c.unwrap_generic(left_type)) + left_value_sym := c.table.get_type_symbol(c.unwrap_generic(left_value_type)) + if left_value_sym.kind == .interface_ { + if right_final.kind != .array { + // []Animal << Cat + if c.type_implements(right_type, left_value_type, right_pos) { + if !right_type.is_ptr() && !right_type.is_pointer() && !c.inside_unsafe + && right_sym.kind != .interface_ { + c.mark_as_referenced(mut &node.right, true) + } + } + } else { + // []Animal << []Cat + c.type_implements(c.table.value_type(right_type), left_value_type, + right_pos) + } + return ast.void_type + } + // []T << T or []T << []T + unwrapped_right_type := c.unwrap_generic(right_type) + if c.check_types(unwrapped_right_type, left_value_type) + || c.check_types(unwrapped_right_type, left_type) { + return ast.void_type + } + c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos) + return ast.void_type + } else { + return c.check_shift(left_type, right_type, left_pos, right_pos) + } + } + .right_shift { + return c.check_shift(left_type, right_type, left_pos, right_pos) + } + .key_is, .not_is { + right_expr := node.right + mut typ := match right_expr { + ast.TypeNode { + right_expr.typ + } + ast.None { + ast.none_type_idx + } + else { + c.error('invalid type `$right_expr`', right_expr.position()) + ast.Type(0) + } + } + if typ != ast.Type(0) { + typ_sym := c.table.get_type_symbol(typ) + op := node.op.str() + if typ_sym.kind == .placeholder { + c.error('$op: type `$typ_sym.name` does not exist', right_expr.position()) + } + if left_sym.kind !in [.interface_, .sum_type] { + c.error('`$op` can only be used with interfaces and sum types', node.pos) + } else if mut left_sym.info is ast.SumType { + if typ !in left_sym.info.variants { + c.error('`$left_sym.name` has no variant `$right_sym.name`', node.pos) + } + } + } + return ast.bool_type + } + .arrow { // `chan <- elem` + if left_sym.kind == .chan { + chan_info := left_sym.chan_info() + elem_type := chan_info.elem_type + if !c.check_types(right_type, elem_type) { + c.error('cannot push `$right_sym.name` on `$left_sym.name`', right_pos) + } + if chan_info.is_mut { + // TODO: The error message of the following could be more specific... + c.fail_if_immutable(node.right) + } + if elem_type.is_ptr() && !right_type.is_ptr() { + c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`', + right_pos) + } + c.stmts(node.or_block.stmts) + } else { + c.error('cannot push on non-channel `$left_sym.name`', left_pos) + } + return ast.void_type + } + .and, .logical_or { + if !c.pref.translated { + if node.left_type != ast.bool_type_idx { + c.error('left operand for `$node.op` is not a boolean', node.left.position()) + } + if node.right_type != ast.bool_type_idx { + c.error('right operand for `$node.op` is not a boolean', node.right.position()) + } + } + if mut node.left is ast.InfixExpr { + if node.left.op != node.op && node.left.op in [.logical_or, .and] { + // for example: `(a && b) || c` instead of `a && b || c` + c.error('ambiguous boolean expression. use `()` to ensure correct order of operations', + node.pos) + } + } + } + else {} + } + // TODO: Absorb this block into the above single side check block to accelerate. + if left_type == ast.bool_type && node.op !in [.eq, .ne, .logical_or, .and] { + c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`', + node.pos) + } else if left_type == ast.string_type && node.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] { + // TODO broken !in + c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`', + node.pos) + } else if left_sym.kind == .enum_ && right_sym.kind == .enum_ && !eq_ne { + left_enum := left_sym.info as ast.Enum + right_enum := right_sym.info as ast.Enum + if left_enum.is_flag && right_enum.is_flag { + // `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators + if node.op !in [.pipe, .amp] { + c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed', + node.pos) + } + } else if !c.pref.translated { + // Regular enums + c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed', + node.pos) + } + } + // sum types can't have any infix operation except of `is`, `eq`, `ne`. + // `is` is checked before and doesn't reach this. + if c.table.type_kind(left_type) == .sum_type && !eq_ne { + c.error('cannot use operator `$node.op` with `$left_sym.name`', node.pos) + } else if c.table.type_kind(right_type) == .sum_type && !eq_ne { + c.error('cannot use operator `$node.op` with `$right_sym.name`', node.pos) + } + // TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int ` + left_is_optional := left_type.has_flag(.optional) + right_is_optional := right_type.has_flag(.optional) + if (left_is_optional && !right_is_optional) || (!left_is_optional && right_is_optional) { + c.error('unwrapped optional cannot be used in an infix expression', left_right_pos) + } + // Dual sides check (compatibility check) + if !(c.symmetric_check(left_type, right_type) && c.symmetric_check(right_type, left_type)) + && !c.pref.translated && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() { + // for type-unresolved consts + if left_type == ast.void_type || right_type == ast.void_type { + return ast.void_type + } + if left_type.nr_muls() > 0 && right_type.is_int() { + // pointer arithmetic is fine, it is checked in other places + return return_type + } + c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`', + left_right_pos) + } + /* + if (node.left is ast.InfixExpr && + (node.left as ast.InfixExpr).op == .inc) || + (node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) { + c.warn('`++` and `--` are statements, not expressions', node.pos) + } + */ + return if node.op.is_relational() { ast.bool_type } else { return_type } +} + +// returns name and position of variable that needs write lock +// also sets `is_changed` to true (TODO update the name to reflect this?) +fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { + mut to_lock := '' // name of variable that needs lock + mut pos := token.Position{} // and its position + mut explicit_lock_needed := false + match mut expr { + ast.CastExpr { + // TODO + return '', pos + } + ast.ComptimeSelector { + return '', pos + } + ast.Ident { + if expr.obj is ast.Var { + mut v := expr.obj as ast.Var + if !v.is_mut && !c.pref.translated && !c.inside_unsafe { + c.error('`$expr.name` is immutable, declare it with `mut` to make it mutable', + expr.pos) + } + v.is_changed = true + if v.typ.share() == .shared_t { + if expr.name !in c.locked_names { + if c.locked_names.len > 0 || c.rlocked_names.len > 0 { + if expr.name in c.rlocked_names { + c.error('$expr.name has an `rlock` but needs a `lock`', + expr.pos) + } else { + c.error('$expr.name must be added to the `lock` list above', + expr.pos) + } + } + to_lock = expr.name + pos = expr.pos + } + } + } else if expr.obj is ast.ConstField && expr.name in c.const_names { + c.error('cannot modify constant `$expr.name`', expr.pos) + } + } + ast.IndexExpr { + left_sym := c.table.get_type_symbol(expr.left_type) + mut elem_type := ast.Type(0) + mut kind := '' + match left_sym.info { + ast.Array { + elem_type, kind = left_sym.info.elem_type, 'array' + } + ast.ArrayFixed { + elem_type, kind = left_sym.info.elem_type, 'fixed array' + } + ast.Map { + elem_type, kind = left_sym.info.value_type, 'map' + } + else {} + } + if elem_type.has_flag(.shared_f) { + c.error('you have to create a handle and `lock` it to modify `shared` $kind element', + expr.left.position().extend(expr.pos)) + } + to_lock, pos = c.fail_if_immutable(expr.left) + } + ast.ParExpr { + to_lock, pos = c.fail_if_immutable(expr.expr) + } + ast.PrefixExpr { + to_lock, pos = c.fail_if_immutable(expr.right) + } + ast.SelectorExpr { + if expr.expr_type == 0 { + return '', pos + } + // retrieve ast.Field + c.ensure_type_exists(expr.expr_type, expr.pos) or { return '', pos } + mut typ_sym := c.table.get_final_type_symbol(c.unwrap_generic(expr.expr_type)) + match typ_sym.kind { + .struct_ { + struct_info := typ_sym.info as ast.Struct + mut has_field := true + mut field_info := struct_info.find_field(expr.field_name) or { + has_field = false + ast.StructField{} + } + if !has_field { + for embed in struct_info.embeds { + embed_sym := c.table.get_type_symbol(embed) + embed_struct_info := embed_sym.info as ast.Struct + if embed_field_info := embed_struct_info.find_field(expr.field_name) { + has_field = true + field_info = embed_field_info + break + } + } + } + if !has_field { + type_str := c.table.type_to_str(expr.expr_type) + c.error('unknown field `${type_str}.$expr.field_name`', expr.pos) + return '', pos + } + if field_info.typ.has_flag(.shared_f) { + expr_name := '${expr.expr}.$expr.field_name' + if expr_name !in c.locked_names { + if c.locked_names.len > 0 || c.rlocked_names.len > 0 { + if expr_name in c.rlocked_names { + c.error('$expr_name has an `rlock` but needs a `lock`', + expr.pos) + } else { + c.error('$expr_name must be added to the `lock` list above', + expr.pos) + } + } + to_lock = expr_name + pos = expr.pos + } + } else { + if !field_info.is_mut && !c.pref.translated { + type_str := c.table.type_to_str(expr.expr_type) + c.error('field `$expr.field_name` of struct `$type_str` is immutable', + expr.pos) + } + to_lock, pos = c.fail_if_immutable(expr.expr) + } + if to_lock != '' { + // No automatic lock for struct access + explicit_lock_needed = true + } + } + .interface_ { + interface_info := typ_sym.info as ast.Interface + mut field_info := interface_info.find_field(expr.field_name) or { + type_str := c.table.type_to_str(expr.expr_type) + c.error('unknown field `${type_str}.$expr.field_name`', expr.pos) + return '', pos + } + if !field_info.is_mut { + type_str := c.table.type_to_str(expr.expr_type) + c.error('field `$expr.field_name` of interface `$type_str` is immutable', + expr.pos) + } + c.fail_if_immutable(expr.expr) + } + .array, .string { + // This should only happen in `builtin` + if c.file.mod.name != 'builtin' { + c.error('`$typ_sym.kind` can not be modified', expr.pos) + } + } + .aggregate, .placeholder { + c.fail_if_immutable(expr.expr) + } + else { + c.error('unexpected symbol `$typ_sym.kind`', expr.pos) + } + } + } + ast.CallExpr { + // TODO: should only work for builtin method + if expr.name == 'slice' { + to_lock, pos = c.fail_if_immutable(expr.left) + if to_lock != '' { + // No automatic lock for array slicing (yet(?)) + explicit_lock_needed = true + } + } + } + ast.ArrayInit { + c.error('array literal can not be modified', expr.pos) + return '', pos + } + ast.StructInit { + return '', pos + } + ast.InfixExpr { + return '', pos + } + else { + if !expr.is_lit() { + c.error('unexpected expression `$expr.type_name()`', expr.position()) + } + } + } + if explicit_lock_needed { + c.error('`$to_lock` is `shared` and needs explicit lock for `$expr.type_name()`', + pos) + to_lock = '' + } + return to_lock, pos +} + +pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { + // First check everything that applies to both fns and methods + // TODO merge logic from method_call and fn_call + /* + for i, call_arg in node.args { + if call_arg.is_mut { + c.fail_if_immutable(call_arg.expr) + if !arg.is_mut { + tok := call_arg.share.str() + c.error('`$node.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`', + call_arg.expr.position()) + } else if arg.typ.share() != call_arg.share { + c.error('wrong shared type', call_arg.expr.position()) + } + } else { + if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) { + tok := call_arg.share.str() + c.error('`$node.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${i+1}`', + call_arg.expr.position()) + } + } + } + */ + // Now call `method_call` or `fn_call` for specific checks. + old_inside_fn_arg := c.inside_fn_arg + c.inside_fn_arg = true + typ := if node.is_method { c.method_call(mut node) } else { c.fn_call(mut node) } + c.inside_fn_arg = old_inside_fn_arg + // autofree: mark args that have to be freed (after saving them in tmp exprs) + free_tmp_arg_vars := c.pref.autofree && !c.is_builtin_mod && node.args.len > 0 + && !node.args[0].typ.has_flag(.optional) + if free_tmp_arg_vars && !c.inside_const { + for i, arg in node.args { + if arg.typ != ast.string_type { + continue + } + if arg.expr is ast.Ident || arg.expr is ast.StringLiteral + || arg.expr is ast.SelectorExpr { + // Simple expressions like variables, string literals, selector expressions + // (`x.field`) can't result in allocations and don't need to be assigned to + // temporary vars. + // Only expressions like `str + 'b'` need to be freed. + continue + } + node.args[i].is_tmp_autofree = true + } + // TODO copy pasta from above + if node.receiver_type == ast.string_type && !(node.left is ast.Ident + || node.left is ast.StringLiteral || node.left is ast.SelectorExpr) { + node.free_receiver = true + } + } + c.expected_or_type = node.return_type.clear_flag(.optional) + c.stmts(node.or_block.stmts) + c.expected_or_type = ast.void_type + if node.or_block.kind == .propagate && !c.table.cur_fn.return_type.has_flag(.optional) + && !c.inside_const { + if !c.table.cur_fn.is_main { + c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', + node.or_block.pos) + } + } + return typ +} + +fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) { + if node.args.len != 1 { + c.error('expected 1 argument, but got $node.args.len', node.pos) + // Finish early so that it doesn't fail later + return + } + elem_sym := c.table.get_type_symbol(elem_typ) + arg_expr := node.args[0].expr + match arg_expr { + ast.AnonFn { + if arg_expr.decl.params.len > 1 { + c.error('function needs exactly 1 argument', arg_expr.decl.pos) + } else if is_map && (arg_expr.decl.return_type == ast.void_type + || arg_expr.decl.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', arg_expr.decl.pos) + } else if !is_map && (arg_expr.decl.return_type != ast.bool_type + || arg_expr.decl.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', + arg_expr.decl.pos) + } + } + ast.Ident { + if arg_expr.kind == .function { + func := c.table.find_fn(arg_expr.name) or { + c.error('$arg_expr.name does not exist', arg_expr.pos) + return + } + if func.params.len > 1 { + c.error('function needs exactly 1 argument', node.pos) + } else if is_map + && (func.return_type == ast.void_type || func.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', + arg_expr.pos) + } else if !is_map + && (func.return_type != ast.bool_type || func.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', + arg_expr.pos) + } + } else if arg_expr.kind == .variable { + if arg_expr.obj is ast.Var { + expr := arg_expr.obj.expr + if expr is ast.AnonFn { + // copied from above + if expr.decl.params.len > 1 { + c.error('function needs exactly 1 argument', expr.decl.pos) + } else if is_map && (expr.decl.return_type == ast.void_type + || expr.decl.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', + expr.decl.pos) + } else if !is_map && (expr.decl.return_type != ast.bool_type + || expr.decl.params[0].typ != elem_typ) { + c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`', + expr.decl.pos) + } + return + } + } + // NOTE: bug accessing typ field on sumtype variant (not cast properly). + // leaving this here as the resulting issue is notoriously hard to debug. + // if !is_map && arg_expr.info.typ != ast.bool_type { + if !is_map && arg_expr.var_info().typ != ast.bool_type { + c.error('type mismatch, should be bool', arg_expr.pos) + } + } + } + ast.CallExpr { + if is_map && arg_expr.return_type in [ast.void_type, 0] { + c.error('type mismatch, `$arg_expr.name` does not return anything', arg_expr.pos) + } else if !is_map && arg_expr.return_type != ast.bool_type { + c.error('type mismatch, `$arg_expr.name` must return a bool', arg_expr.pos) + } + } + else {} + } +} + +pub fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ? { + nr_args := node.args.len + nr_params := if node.is_method && f.params.len > 0 { f.params.len - 1 } else { f.params.len } + mut min_required_params := f.params.len + if node.is_method { + min_required_params-- + } + if f.is_variadic { + min_required_params-- + } + if min_required_params < 0 { + min_required_params = 0 + } + if nr_args < min_required_params { + if min_required_params == nr_args + 1 { + last_typ := f.params.last().typ + last_sym := c.table.get_type_symbol(last_typ) + if last_sym.kind == .struct_ { + // allow empty trailing struct syntax arg (`f()` where `f` is `fn(ConfigStruct)`) + node.args << ast.CallArg{ + expr: ast.StructInit{ + typ: last_typ + } + typ: last_typ + } + return + } + } + c.error('expected $min_required_params arguments, but got $nr_args', node.pos) + return error('') + } else if !f.is_variadic && nr_args > nr_params { + unexpected_args_pos := node.args[min_required_params].pos.extend(node.args.last().pos) + c.error('expected $min_required_params arguments, but got $nr_args', unexpected_args_pos) + return error('') + } +} + +pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { + left_type := c.expr(node.left) + c.expected_type = left_type + mut is_generic := left_type.has_flag(.generic) + // x is Bar, x.foo() -> x.foo() + if is_generic && node.concrete_types.len == 0 { + rec_sym := c.table.get_type_symbol(left_type) + if rec_sym.info is ast.Struct { + node.concrete_types = rec_sym.info.generic_types + } + } + node.left_type = left_type + // Set default values for .return_type & .receiver_type too, + // or there will be hard to diagnose 0 type panics in cgen. + node.return_type = left_type + node.receiver_type = left_type + left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type)) + method_name := node.name + mut unknown_method_msg := if field := c.table.find_field(left_type_sym, method_name) { + 'unknown method `$field.name` did you mean to access the field with the same name instead?' + } else { + 'unknown method or field: `${left_type_sym.name}.$method_name`' + } + if left_type.has_flag(.optional) { + c.error('optional type cannot be called directly', node.left.position()) + return ast.void_type + } + if left_type_sym.kind in [.sum_type, .interface_] { + if method_name == 'type_name' { + return ast.string_type + } + if method_name == 'type_idx' { + return ast.int_type + } + } + if left_type == ast.void_type { + c.error('`void` type has no methods', node.left.position()) + return ast.void_type + } + mut has_generic := false // x.foo() instead of x.foo() + mut concrete_types := []ast.Type{} + for concrete_type in node.concrete_types { + if concrete_type.has_flag(.generic) { + has_generic = true + concrete_types << c.unwrap_generic(concrete_type) + } else { + concrete_types << concrete_type + } + } + if has_generic { + if c.table.register_fn_concrete_types(node.name, concrete_types) { + c.need_recheck_generic_fns = true + } + } + // TODO: remove this for actual methods, use only for compiler magic + // FIXME: Argument count != 1 will break these + if left_type_sym.kind == .array && method_name in checker.array_builtin_methods { + return c.array_builtin_method_call(mut node, left_type, left_type_sym) + } else if left_type_sym.kind == .map && method_name in ['clone', 'keys', 'move', 'delete'] { + return c.map_builtin_method_call(mut node, left_type, left_type_sym) + } else if left_type_sym.kind == .array && method_name in ['insert', 'prepend'] { + if method_name == 'insert' { + if node.args.len != 2 { + c.error('`array.insert()` should have 2 arguments, e.g. `insert(1, val)`', + node.pos) + return ast.void_type + } else { + arg_type := c.expr(node.args[0].expr) + if arg_type !in [ast.int_type, ast.int_literal_type] { + c.error('the first argument of `array.insert()` should be integer', + node.args[0].expr.position()) + return ast.void_type + } + } + } else { + if node.args.len != 1 { + c.error('`array.prepend()` should have 1 argument, e.g. `prepend(val)`', + node.pos) + return ast.void_type + } + } + info := left_type_sym.info as ast.Array + arg_expr := if method_name == 'insert' { node.args[1].expr } else { node.args[0].expr } + arg_type := c.expr(arg_expr) + arg_sym := c.table.get_type_symbol(arg_type) + if !c.check_types(arg_type, info.elem_type) && !c.check_types(left_type, arg_type) { + c.error('cannot $method_name `$arg_sym.name` to `$left_type_sym.name`', arg_expr.position()) + } + } else if c.table.get_final_type_symbol(left_type).kind == .array + && method_name in ['first', 'last', 'pop'] { + info := c.table.get_final_type_symbol(left_type).info + if info is ast.Array { + node.return_type = info.elem_type + return info.elem_type + } + } else if left_type_sym.kind == .thread && method_name == 'wait' { + info := left_type_sym.info as ast.Thread + if node.args.len > 0 { + c.error('wait() does not have any arguments', node.args[0].pos) + } + node.return_type = info.return_type + return info.return_type + } else if left_type_sym.kind == .char && left_type.nr_muls() == 0 && method_name == 'str' { + c.error('calling `.str()` on type `char` is not allowed, use its address or cast it to an integer instead', + node.left.position().extend(node.pos)) + return ast.void_type + } + mut method := ast.Fn{} + mut has_method := false + mut is_method_from_embed := false + if m := c.table.type_find_method(left_type_sym, method_name) { + method = m + has_method = true + } else { + if left_type_sym.info is ast.Struct { + if left_type_sym.info.parent_type != 0 { + type_sym := c.table.get_type_symbol(left_type_sym.info.parent_type) + if m := c.table.type_find_method(type_sym, method_name) { + method = m + has_method = true + is_generic = true + } + } + } + if !has_method { + has_method = true + mut embed_type := ast.Type(0) + method, embed_type = c.table.type_find_method_from_embeds(left_type_sym, method_name) or { + if err.msg != '' { + c.error(err.msg, node.pos) + } + has_method = false + ast.Fn{}, ast.Type(0) + } + if embed_type != 0 { + is_method_from_embed = true + node.from_embed_type = embed_type + } + } + if left_type_sym.kind == .aggregate { + // the error message contains the problematic type + unknown_method_msg = err.msg + } + } + if has_method { + node.is_noreturn = method.is_noreturn + if !method.is_pub && !c.pref.is_test && method.mod != c.mod { + // If a private method is called outside of the module + // its receiver type is defined in, show an error. + // println('warn $method_name lef.mod=$left_type_sym.mod c.mod=$c.mod') + c.error('method `${left_type_sym.name}.$method_name` is private', node.pos) + } + rec_share := method.params[0].typ.share() + if rec_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { + c.error('method with `shared` receiver cannot be called inside `lock`/`rlock` block', + node.pos) + } + if method.params[0].is_mut { + to_lock, pos := c.fail_if_immutable(node.left) + if !node.left.is_lvalue() { + c.error('cannot pass expression as `mut`', node.left.position()) + } + // node.is_mut = true + if to_lock != '' && rec_share != .shared_t { + c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`', + pos) + } + } else { + c.fail_if_unreadable(node.left, left_type, 'receiver') + } + if (!left_type_sym.is_builtin() && method.mod != 'builtin') && method.language == .v + && method.no_body { + c.error('cannot call a method that does not have a body', node.pos) + } + if method.return_type == ast.void_type && method.is_conditional && method.ctdefine_idx != -1 { + node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut method.attrs[method.ctdefine_idx]) + } + c.check_expected_arg_count(mut node, method) or { return method.return_type } + mut exp_arg_typ := ast.Type(0) // type of 1st arg for special builtin methods + mut param_is_mut := false + mut no_type_promotion := false + if left_type_sym.kind == .chan { + elem_typ := (left_type_sym.info as ast.Chan).elem_type + if method_name == 'try_push' { + exp_arg_typ = elem_typ.to_ptr() + } else if method_name == 'try_pop' { + exp_arg_typ = elem_typ + param_is_mut = true + no_type_promotion = true + } + } + // if method_name == 'clone' { + // println('CLONE nr args=$method.args.len') + // } + // node.args << method.args[0].typ + // node.exp_arg_types << method.args[0].typ + for i, mut arg in node.args { + if i > 0 || exp_arg_typ == ast.Type(0) { + exp_arg_typ = if method.is_variadic && i >= method.params.len - 1 { + method.params[method.params.len - 1].typ + } else { + method.params[i + 1].typ + } + param_is_mut = false + no_type_promotion = false + } + exp_arg_sym := c.table.get_type_symbol(exp_arg_typ) + c.expected_type = exp_arg_typ + got_arg_typ := c.check_expr_opt_call(arg.expr, c.expr(arg.expr)) + node.args[i].typ = got_arg_typ + if no_type_promotion { + if got_arg_typ != exp_arg_typ { + c.error('cannot use `${c.table.get_type_symbol(got_arg_typ).name}` as argument for `$method.name` (`$exp_arg_sym.name` expected)', + arg.pos) + } + } + if method.is_variadic && got_arg_typ.has_flag(.variadic) && node.args.len - 1 > i { + c.error('when forwarding a variadic variable, it must be the final argument', + arg.pos) + } + mut final_arg_sym := exp_arg_sym + if method.is_variadic && exp_arg_sym.info is ast.Array { + final_arg_sym = c.table.get_type_symbol(exp_arg_sym.array_info().elem_type) + } + // Handle expected interface + if final_arg_sym.kind == .interface_ { + if c.type_implements(got_arg_typ, exp_arg_typ, arg.expr.position()) { + if !got_arg_typ.is_ptr() && !got_arg_typ.is_pointer() && !c.inside_unsafe { + got_arg_typ_sym := c.table.get_type_symbol(got_arg_typ) + if got_arg_typ_sym.kind != .interface_ { + c.mark_as_referenced(mut &arg.expr, true) + } + } + } + continue + } + if exp_arg_typ.has_flag(.generic) { + continue + } + c.check_expected_call_arg(got_arg_typ, c.unwrap_generic(exp_arg_typ), node.language) or { + // str method, allow type with str method if fn arg is string + // Passing an int or a string array produces a c error here + // Deleting this condition results in propper V error messages + // if arg_typ_sym.kind == .string && typ_sym.has_method('str') { + // continue + // } + if got_arg_typ != ast.void_type { + c.error('$err.msg in argument ${i + 1} to `${left_type_sym.name}.$method_name`', + arg.pos) + } + } + param := if method.is_variadic && i >= method.params.len - 1 { + method.params[method.params.len - 1] + } else { + method.params[i + 1] + } + param_is_mut = param_is_mut || param.is_mut + param_share := param.typ.share() + if param_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { + c.error('method with `shared` arguments cannot be called inside `lock`/`rlock` block', + arg.pos) + } + if arg.is_mut { + to_lock, pos := c.fail_if_immutable(arg.expr) + if !param_is_mut { + tok := arg.share.str() + c.error('`$node.name` parameter `$param.name` is not `$tok`, `$tok` is not needed`', + arg.expr.position()) + } else { + if param_share != arg.share { + c.error('wrong shared type', arg.expr.position()) + } + if to_lock != '' && param_share != .shared_t { + c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`', + pos) + } + } + } else { + if param_is_mut { + tok := arg.share.str() + c.error('`$node.name` parameter `$param.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ + i + 1}`', arg.expr.position()) + } else { + c.fail_if_unreadable(arg.expr, got_arg_typ, 'argument') + } + } + } + if method.is_unsafe && !c.inside_unsafe { + c.warn('method `${left_type_sym.name}.$method_name` must be called from an `unsafe` block', + node.pos) + } + if !c.table.cur_fn.is_deprecated && method.is_deprecated { + c.deprecate_fnmethod('method', '${left_type_sym.name}.$method.name', method, + node) + } + // TODO: typ optimize.. this node can get processed more than once + if node.expected_arg_types.len == 0 { + for i in 1 .. method.params.len { + node.expected_arg_types << method.params[i].typ + } + } + if is_method_from_embed { + node.receiver_type = node.from_embed_type.derive(method.params[0].typ) + } else if is_generic { + // We need the receiver to be T in cgen. + // TODO: cant we just set all these to the concrete type in checker? then no need in gen + node.receiver_type = left_type.derive(method.params[0].typ).set_flag(.generic) + } else { + node.receiver_type = method.params[0].typ + } + if method.generic_names.len != node.concrete_types.len { + // no type arguments given in call, attempt implicit instantiation + c.infer_fn_generic_types(method, mut node) + concrete_types = node.concrete_types + } + // resolve return generics struct to concrete type + if method.generic_names.len > 0 && method.return_type.has_flag(.generic) { + node.return_type = c.unwrap_generic_type(method.return_type, method.generic_names, + concrete_types) + } else { + node.return_type = method.return_type + } + if node.concrete_types.len > 0 && method.return_type != 0 { + if typ := c.table.resolve_generic_to_concrete(method.return_type, method.generic_names, + concrete_types) + { + node.return_type = typ + return typ + } + } + if node.concrete_types.len > 0 && method.generic_names.len == 0 { + c.error('a non generic function called like a generic one', node.concrete_list_pos) + } + if node.concrete_types.len > method.generic_names.len { + c.error('too many generic parameters got $node.concrete_types.len, expected $method.generic_names.len', + node.concrete_list_pos) + } + if method.generic_names.len > 0 { + return node.return_type + } + return method.return_type + } + // TODO: str methods + if method_name == 'str' { + if left_type_sym.kind == .interface_ { + iname := left_type_sym.name + c.error('interface `$iname` does not have a .str() method. Use typeof() instead', + node.pos) + } + node.receiver_type = left_type + node.return_type = ast.string_type + if node.args.len > 0 { + c.error('.str() method calls should have no arguments', node.pos) + } + c.fail_if_unreadable(node.left, left_type, 'receiver') + return ast.string_type + } + // call struct field fn type + // TODO: can we use SelectorExpr for all? this dosent really belong here + if field := c.table.find_field(left_type_sym, method_name) { + field_type_sym := c.table.get_type_symbol(c.unwrap_generic(field.typ)) + if field_type_sym.kind == .function { + // node.is_method = false + node.is_field = true + info := field_type_sym.info as ast.FnType + node.return_type = info.func.return_type + mut earg_types := []ast.Type{} + for mut arg in node.args { + targ := c.check_expr_opt_call(arg.expr, c.expr(arg.expr)) + arg.typ = targ + earg_types << targ + } + node.expected_arg_types = earg_types + return info.func.return_type + } + } + if left_type != ast.void_type { + suggestion := util.new_suggestion(method_name, left_type_sym.methods.map(it.name)) + c.error(suggestion.say(unknown_method_msg), node.pos) + } + return ast.void_type +} + +fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_type_sym ast.TypeSymbol) ast.Type { + method_name := node.name + mut ret_type := ast.void_type + match method_name { + 'clone', 'move' { + if method_name[0] == `m` { + c.fail_if_immutable(node.left) + } + if node.left.is_auto_deref_var() { + ret_type = left_type.deref() + } else { + ret_type = left_type + } + } + 'keys' { + info := left_type_sym.info as ast.Map + typ := c.table.find_or_register_array(info.key_type) + ret_type = ast.Type(typ) + } + 'delete' { + c.fail_if_immutable(node.left) + if node.args.len != 1 { + c.error('expected 1 argument, but got $node.args.len', node.pos) + } + info := left_type_sym.info as ast.Map + arg_type := c.expr(node.args[0].expr) + c.check_expected_call_arg(arg_type, info.key_type, node.language) or { + c.error('$err.msg in argument 1 to `Map.delete`', node.args[0].pos) + } + } + else {} + } + node.receiver_type = left_type.to_ptr() + node.return_type = ret_type + return node.return_type +} + +fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_type_sym ast.TypeSymbol) ast.Type { + method_name := node.name + mut elem_typ := ast.void_type + if method_name == 'slice' && !c.is_builtin_mod { + c.error('.slice() is a private method, use `x[start..end]` instead', node.pos) + } + array_info := left_type_sym.info as ast.Array + elem_typ = array_info.elem_type + if method_name in ['filter', 'map', 'any', 'all'] { + // position of `it` doesn't matter + scope_register_it(mut node.scope, node.pos, elem_typ) + } else if method_name == 'sort' { + if node.left is ast.CallExpr { + c.error('the `sort()` method can be called only on mutable receivers, but `$node.left` is a call expression', + node.pos) + } + c.fail_if_immutable(node.left) + // position of `a` and `b` doesn't matter, they're the same + scope_register_a_b(mut node.scope, node.pos, elem_typ) + + if node.args.len > 1 { + c.error('expected 0 or 1 argument, but got $node.args.len', node.pos) + } else if node.args.len == 1 { + if node.args[0].expr is ast.InfixExpr { + if node.args[0].expr.op !in [.gt, .lt] { + c.error('`.sort()` can only use `<` or `>` comparison', node.pos) + } + left_name := '${node.args[0].expr.left}'[0] + right_name := '${node.args[0].expr.right}'[0] + if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] { + c.error('`.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)`', + node.pos) + } else if left_name == right_name { + c.error('`.sort()` cannot use same argument', node.pos) + } + } else { + c.error( + '`.sort()` requires a `<` or `>` comparison as the first and only argument' + + '\ne.g. `users.sort(a.id < b.id)`', node.pos) + } + } else if !(c.table.get_type_symbol(elem_typ).has_method('<') + || c.table.unalias_num_type(elem_typ) in [ast.int_type, ast.int_type.to_ptr(), ast.string_type, ast.string_type.to_ptr(), ast.i8_type, ast.i16_type, ast.i64_type, ast.byte_type, ast.rune_type, ast.u16_type, ast.u32_type, ast.u64_type, ast.f32_type, ast.f64_type, ast.char_type, ast.bool_type, ast.float_literal_type, ast.int_literal_type, ast.size_t_type_idx]) { + c.error('custom sorting condition must be supplied for type `${c.table.type_to_str(elem_typ)}`', + node.pos) + } + } else if method_name == 'wait' { + elem_sym := c.table.get_type_symbol(elem_typ) + if elem_sym.kind == .thread { + if node.args.len != 0 { + c.error('`.wait()` does not have any arguments', node.args[0].pos) + } + thread_ret_type := elem_sym.thread_info().return_type + if thread_ret_type.has_flag(.optional) { + c.error('`.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`.', + node.pos) + } + node.return_type = c.table.find_or_register_array(thread_ret_type) + } else { + c.error('`$left_type_sym.name` has no method `wait()` (only thread handles and arrays of them have)', + node.left.position()) + } + } + // map/filter are supposed to have 1 arg only + mut arg_type := left_type + for arg in node.args { + arg_type = c.check_expr_opt_call(arg.expr, c.expr(arg.expr)) + } + if method_name == 'map' { + // check fn + c.check_map_and_filter(true, elem_typ, node) + arg_sym := c.table.get_type_symbol(arg_type) + ret_type := match arg_sym.info { + ast.FnType { arg_sym.info.func.return_type } + else { arg_type } + } + node.return_type = c.table.find_or_register_array(ret_type) + } else if method_name == 'filter' { + // check fn + c.check_map_and_filter(false, elem_typ, node) + } else if method_name in ['any', 'all'] { + c.check_map_and_filter(false, elem_typ, node) + node.return_type = ast.bool_type + } else if method_name == 'clone' { + // need to return `array_xxx` instead of `array` + // in ['clone', 'str'] { + node.receiver_type = left_type.to_ptr() + if node.left.is_auto_deref_var() { + node.return_type = left_type.deref() + } else { + node.return_type = node.receiver_type.set_nr_muls(0) + } + } else if method_name == 'sort' { + node.return_type = ast.void_type + } else if method_name == 'contains' { + // c.warn('use `value in arr` instead of `arr.contains(value)`', node.pos) + node.return_type = ast.bool_type + } else if method_name == 'index' { + node.return_type = ast.int_type + } else if method_name in ['first', 'last', 'pop'] { + node.return_type = array_info.elem_type + if method_name == 'pop' { + node.receiver_type = left_type.to_ptr() + } else { + node.receiver_type = left_type + } + } + return node.return_type +} + +pub fn (mut c Checker) fn_call(mut node ast.CallExpr) ast.Type { + fn_name := node.name + if fn_name == 'main' { + c.error('the `main` function cannot be called in the program', node.pos) + } + mut has_generic := false // foo() instead of foo() + mut concrete_types := []ast.Type{} + for concrete_type in node.concrete_types { + if concrete_type.has_flag(.generic) { + has_generic = true + concrete_types << c.unwrap_generic(concrete_type) + } else { + concrete_types << concrete_type + } + } + if !isnil(c.table.cur_fn) && c.table.cur_concrete_types.len == 0 && has_generic { + c.error('generic fn using generic types cannot be called outside of generic fn', + node.pos) + } + if has_generic { + mut no_exists := true + if c.mod != '' && !fn_name.contains('.') { + // Need to prepend the module when adding a generic type to a function + no_exists = c.table.register_fn_concrete_types(c.mod + '.' + fn_name, concrete_types) + } else { + no_exists = c.table.register_fn_concrete_types(fn_name, concrete_types) + } + if no_exists { + c.need_recheck_generic_fns = true + } + } + if fn_name == 'json.encode' { + } else if fn_name == 'json.decode' && node.args.len > 0 { + if node.args.len != 2 { + c.error("json.decode expects 2 arguments, a type and a string (e.g `json.decode(T, '')`)", + node.pos) + return ast.void_type + } + expr := node.args[0].expr + if expr !is ast.TypeNode { + typ := expr.type_name() + c.error('json.decode: first argument needs to be a type, got `$typ`', node.pos) + return ast.void_type + } + c.expected_type = ast.string_type + node.args[1].typ = c.expr(node.args[1].expr) + if node.args[1].typ != ast.string_type { + c.error('json.decode: second argument needs to be a string', node.pos) + } + typ := expr as ast.TypeNode + ret_type := typ.typ.set_flag(.optional) + node.return_type = ret_type + return ret_type + } + // look for function in format `mod.fn` or `fn` (builtin) + mut func := ast.Fn{} + mut found := false + mut found_in_args := false + // anon fn direct call + if mut node.left is ast.AnonFn { + // it was set to anon for checker errors, clear for gen + node.name = '' + c.expr(node.left) + if node.left.typ != ast.Type(0) { + anon_fn_sym := c.table.get_type_symbol(node.left.typ) + func = (anon_fn_sym.info as ast.FnType).func + found = true + } + } + // try prefix with current module as it would have never gotten prefixed + if !found && !fn_name.contains('.') && node.mod != 'builtin' { + name_prefixed := '${node.mod}.$fn_name' + if f := c.table.find_fn(name_prefixed) { + node.name = name_prefixed + found = true + func = f + c.table.fns[name_prefixed].usages++ + } + } + if !found && node.left is ast.IndexExpr { + c.expr(node.left) + expr := node.left as ast.IndexExpr + sym := c.table.get_type_symbol(expr.left_type) + if sym.kind == .array { + info := sym.info as ast.Array + elem_typ := c.table.get_type_symbol(info.elem_type) + if elem_typ.info is ast.FnType { + return elem_typ.info.func.return_type + } + } else if sym.kind == .map { + info := sym.info as ast.Map + value_typ := c.table.get_type_symbol(info.value_type) + if value_typ.info is ast.FnType { + return value_typ.info.func.return_type + } + } else if sym.kind == .array_fixed { + info := sym.info as ast.ArrayFixed + elem_typ := c.table.get_type_symbol(info.elem_type) + if elem_typ.info is ast.FnType { + return elem_typ.info.func.return_type + } + } + found = true + return ast.string_type + } + // already prefixed (mod.fn) or C/builtin/main + if !found { + if f := c.table.find_fn(fn_name) { + found = true + func = f + c.table.fns[fn_name].usages++ + } + } + mut is_native_builtin := false + if !found && c.pref.backend == .native { + if fn_name in native.builtins { + c.table.fns[fn_name].usages++ + found = true + func = c.table.fns[fn_name] + is_native_builtin = true + } + } + if !found && c.pref.is_vsh { + os_name := 'os.$fn_name' + if f := c.table.find_fn(os_name) { + if f.generic_names.len == node.concrete_types.len { + c.table.fn_generic_types[os_name] = c.table.fn_generic_types['${node.mod}.$node.name'] + } + node.name = os_name + found = true + func = f + c.table.fns[os_name].usages++ + } + } + if is_native_builtin { + return ast.void_type + } + // check for arg (var) of fn type + if !found { + if v := node.scope.find_var(fn_name) { + if v.typ != 0 { + generic_vts := c.table.get_type_symbol(v.typ) + if generic_vts.kind == .function { + info := generic_vts.info as ast.FnType + func = info.func + found = true + found_in_args = true + } else { + vts := c.table.get_type_symbol(c.unwrap_generic(v.typ)) + if vts.kind == .function { + info := vts.info as ast.FnType + func = info.func + found = true + found_in_args = true + } + } + } + } + } + // global fn? + if !found { + if obj := c.file.global_scope.find(fn_name) { + sym := c.table.get_type_symbol(obj.typ) + if sym.kind == .function { + found = true + func = (sym.info as ast.FnType).func + } + } + } + if !found { + c.error('unknown function: $fn_name', node.pos) + return ast.void_type + } + node.is_noreturn = func.is_noreturn + if !found_in_args { + if node.scope.known_var(fn_name) { + c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`', + node.pos) + } + } + if !func.is_pub && func.language == .v && func.name.len > 0 && func.mod.len > 0 + && func.mod != c.mod { + c.error('function `$func.name` is private', node.pos) + } + if !isnil(c.table.cur_fn) && !c.table.cur_fn.is_deprecated && func.is_deprecated { + c.deprecate_fnmethod('function', func.name, func, node) + } + if func.is_unsafe && !c.inside_unsafe + && (func.language != .c || (func.name[2] in [`m`, `s`] && func.mod == 'builtin')) { + // builtin C.m*, C.s* only - temp + c.warn('function `$func.name` must be called from an `unsafe` block', node.pos) + } + node.is_keep_alive = func.is_keep_alive + if func.mod != 'builtin' && func.language == .v && func.no_body && !c.pref.translated + && !func.is_unsafe { + c.error('cannot call a function that does not have a body', node.pos) + } + for concrete_type in node.concrete_types { + c.ensure_type_exists(concrete_type, node.concrete_list_pos) or {} + } + if func.generic_names.len > 0 && node.args.len == 0 && node.concrete_types.len == 0 { + c.error('no argument generic function must add concrete types, e.g. foo()', + node.pos) + return func.return_type + } + if func.return_type == ast.void_type && func.is_conditional && func.ctdefine_idx != -1 { + node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut func.attrs[func.ctdefine_idx]) + } + // dont check number of args for JS functions since arguments are not required + if node.language != .js { + c.check_expected_arg_count(mut node, func) or { return func.return_type } + } + // println / eprintln / panic can print anything + if fn_name in ['println', 'print', 'eprintln', 'eprint', 'panic'] && node.args.len > 0 { + c.inside_println_arg = true + c.expected_type = ast.string_type + node.args[0].typ = c.expr(node.args[0].expr) + arg := node.args[0] + c.check_expr_opt_call(arg.expr, arg.typ) + if arg.typ.is_void() { + c.error('`$fn_name` can not print void expressions', node.pos) + } else if arg.typ == ast.char_type && arg.typ.nr_muls() == 0 { + c.error('`$fn_name` cannot print type `char` directly, print its address or cast it to an integer instead', + node.pos) + } + c.fail_if_unreadable(arg.expr, arg.typ, 'argument to print') + c.inside_println_arg = false + node.return_type = ast.void_type + /* + // TODO: optimize `struct T{} fn (t &T) str() string {return 'abc'} mut a := []&T{} a << &T{} println(a[0])` + // It currently generates: + // `println(T_str_no_ptr(*(*(T**)array_get(a, 0))));` + // ... which works, but could be just: + // `println(T_str(*(T**)array_get(a, 0)));` + prexpr := node.args[0].expr + prtyp := node.args[0].typ + prtyp_sym := c.table.get_type_symbol(prtyp) + prtyp_is_ptr := prtyp.is_ptr() + prhas_str, prexpects_ptr, prnr_args := prtyp_sym.str_method_info() + eprintln('>>> println hack typ: ${prtyp} | sym.name: ${prtyp_sym.name} | is_ptr: $prtyp_is_ptr | has_str: $prhas_str | expects_ptr: $prexpects_ptr | nr_args: $prnr_args | expr: ${prexpr.str()} ') + */ + return func.return_type + } + // `return error(err)` -> `return err` + if fn_name == 'error' && node.args.len == 1 { + arg := node.args[0] + node.args[0].typ = c.expr(arg.expr) + if node.args[0].typ == ast.error_type { + c.warn('`error($arg)` can be shortened to just `$arg`', node.pos) + } + } + // TODO: typ optimize.. this node can get processed more than once + if node.expected_arg_types.len == 0 { + for param in func.params { + node.expected_arg_types << param.typ + } + } + for i, mut call_arg in node.args { + param := if func.is_variadic && i >= func.params.len - 1 { + func.params[func.params.len - 1] + } else { + func.params[i] + } + if func.is_variadic && call_arg.expr is ast.ArrayDecompose { + if i > func.params.len - 1 { + c.error('too many arguments in call to `$func.name`', node.pos) + } + } + c.expected_type = param.typ + + e_sym := c.table.get_type_symbol(c.expected_type) + if call_arg.expr is ast.MapInit && e_sym.kind == .struct_ { + c.error('cannot initialize a struct with a map', call_arg.pos) + continue + } else if call_arg.expr is ast.StructInit && e_sym.kind == .map { + c.error('cannot initialize a map with a struct', call_arg.pos) + continue + } + + typ := c.check_expr_opt_call(call_arg.expr, c.expr(call_arg.expr)) + node.args[i].typ = typ + typ_sym := c.table.get_type_symbol(typ) + param_typ_sym := c.table.get_type_symbol(param.typ) + if func.is_variadic && typ.has_flag(.variadic) && node.args.len - 1 > i { + c.error('when forwarding a variadic variable, it must be the final argument', + call_arg.pos) + } + arg_share := param.typ.share() + if arg_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { + c.error('function with `shared` arguments cannot be called inside `lock`/`rlock` block', + call_arg.pos) + } + if call_arg.is_mut && func.language == .v { + to_lock, pos := c.fail_if_immutable(call_arg.expr) + if !call_arg.expr.is_lvalue() { + c.error('cannot pass expression as `mut`', call_arg.expr.position()) + } + if !param.is_mut { + tok := call_arg.share.str() + c.error('`$node.name` parameter `$param.name` is not `$tok`, `$tok` is not needed`', + call_arg.expr.position()) + } else { + if param.typ.share() != call_arg.share { + c.error('wrong shared type', call_arg.expr.position()) + } + if to_lock != '' && !param.typ.has_flag(.shared_f) { + c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`', + pos) + } + } + } else { + if param.is_mut { + tok := call_arg.share.str() + c.error('`$node.name` parameter `$param.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ + i + 1}`', call_arg.expr.position()) + } else { + c.fail_if_unreadable(call_arg.expr, typ, 'argument') + } + } + mut final_param_sym := param_typ_sym + if func.is_variadic && param_typ_sym.info is ast.Array { + final_param_sym = c.table.get_type_symbol(param_typ_sym.array_info().elem_type) + } + // NB: Casting to voidptr is used as an escape mechanism, so: + // 1. allow passing *explicit* voidptr (native or through cast) to functions + // expecting voidptr or ...voidptr + // ... but 2. disallow passing non-pointers - that is very rarely what the user wanted, + // it can lead to codegen errors (except for 'magic' functions like `json.encode` that, + // the compiler has special codegen support for), so it should be opt in, that is it + // shoould require an explicit voidptr(x) cast (and probably unsafe{} ?) . + if call_arg.typ != param.typ + && (param.typ == ast.voidptr_type || final_param_sym.idx == ast.voidptr_type_idx) + && !call_arg.typ.is_any_kind_of_pointer() && func.language == .v + && !call_arg.expr.is_lvalue() && func.name != 'json.encode' { + c.error('expression cannot be passed as `voidptr`', call_arg.expr.position()) + } + // Handle expected interface + if final_param_sym.kind == .interface_ { + if c.type_implements(typ, param.typ, call_arg.expr.position()) { + if !typ.is_ptr() && !typ.is_pointer() && !c.inside_unsafe + && typ_sym.kind != .interface_ { + c.mark_as_referenced(mut &call_arg.expr, true) + } + } + continue + } + c.check_expected_call_arg(typ, c.unwrap_generic(param.typ), node.language) or { + // str method, allow type with str method if fn arg is string + // Passing an int or a string array produces a c error here + // Deleting this condition results in propper V error messages + // if arg_typ_sym.kind == .string && typ_sym.has_method('str') { + // continue + // } + if typ_sym.kind == .void && param_typ_sym.kind == .string { + continue + } + if param.typ.has_flag(.generic) { + continue + } + if c.pref.translated { + // Allow enums to be used as ints and vice versa in translated code + if param.typ == ast.int_type && typ_sym.kind == .enum_ { + continue + } + if typ == ast.int_type && param_typ_sym.kind == .enum_ { + continue + } + } + c.error('$err.msg in argument ${i + 1} to `$fn_name`', call_arg.pos) + } + // Warn about automatic (de)referencing, which will be removed soon. + if func.language != .c && !c.inside_unsafe && typ.nr_muls() != param.typ.nr_muls() + && !(call_arg.is_mut && param.is_mut) && !(!call_arg.is_mut && !param.is_mut) + && param.typ !in [ast.byteptr_type, ast.charptr_type, ast.voidptr_type] { + // sym := c.table.get_type_symbol(typ) + c.warn('automatic referencing/dereferencing is deprecated and will be removed soon (got: $typ.nr_muls() references, expected: $param.typ.nr_muls() references)', + call_arg.pos) + } + } + if func.generic_names.len != node.concrete_types.len { + // no type arguments given in call, attempt implicit instantiation + c.infer_fn_generic_types(func, mut node) + concrete_types = node.concrete_types + } + if func.generic_names.len > 0 { + for i, mut call_arg in node.args { + param := if func.is_variadic && i >= func.params.len - 1 { + func.params[func.params.len - 1] + } else { + func.params[i] + } + c.expected_type = param.typ + typ := c.check_expr_opt_call(call_arg.expr, c.expr(call_arg.expr)) + + if param.typ.has_flag(.generic) && func.generic_names.len == node.concrete_types.len { + if unwrap_typ := c.table.resolve_generic_to_concrete(param.typ, func.generic_names, + concrete_types) + { + utyp := c.unwrap_generic(typ) + unwrap_sym := c.table.get_type_symbol(unwrap_typ) + if unwrap_sym.kind == .interface_ { + if c.type_implements(utyp, unwrap_typ, call_arg.expr.position()) { + if !utyp.is_ptr() && !utyp.is_pointer() && !c.inside_unsafe + && c.table.get_type_symbol(utyp).kind != .interface_ { + c.mark_as_referenced(mut &call_arg.expr, true) + } + } + continue + } + c.check_expected_call_arg(utyp, unwrap_typ, node.language) or { + c.error('$err.msg in argument ${i + 1} to `$fn_name`', call_arg.pos) + } + } + } + } + } + // resolve return generics struct to concrete type + if func.generic_names.len > 0 && func.return_type.has_flag(.generic) { + node.return_type = c.unwrap_generic_type(func.return_type, func.generic_names, + concrete_types) + } else { + node.return_type = func.return_type + } + if node.concrete_types.len > 0 && func.return_type != 0 { + if typ := c.table.resolve_generic_to_concrete(func.return_type, func.generic_names, + concrete_types) + { + node.return_type = typ + return typ + } + } + if node.concrete_types.len > 0 && func.generic_names.len == 0 { + c.error('a non generic function called like a generic one', node.concrete_list_pos) + } + + if node.concrete_types.len > func.generic_names.len { + c.error('too many generic parameters got $node.concrete_types.len, expected $func.generic_names.len', + node.concrete_list_pos) + } + if func.generic_names.len > 0 { + return node.return_type + } + return func.return_type +} + +fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, node ast.CallExpr) { + start_message := '$kind `$name`' + mut deprecation_message := '' + now := time.now() + mut after_time := now + for attr in the_fn.attrs { + if attr.name == 'deprecated' && attr.arg != '' { + deprecation_message = attr.arg + } + if attr.name == 'deprecated_after' && attr.arg != '' { + after_time = time.parse_iso8601(attr.arg) or { + c.error('invalid time format', attr.pos) + time.now() + } + } + } + error_time := after_time.add_days(180) + if error_time < now { + c.error(semicolonize('$start_message has been deprecated since $after_time.ymmdd()', + deprecation_message), node.pos) + } else if after_time < now { + c.warn(semicolonize('$start_message has been deprecated since $after_time.ymmdd(), it will be an error after $error_time.ymmdd()', + deprecation_message), node.pos) + } else if after_time == now { + c.warn(semicolonize('$start_message has been deprecated', deprecation_message), + node.pos) + } else { + c.note(semicolonize('$start_message will be deprecated after $after_time.ymmdd(), and will become an error after $error_time.ymmdd()', + deprecation_message), node.pos) + } +} + +fn semicolonize(main string, details string) string { + if details == '' { + return main + } + return '$main; $details' +} + +fn (mut c Checker) resolve_generic_interface(typ ast.Type, interface_type ast.Type, pos token.Position) ast.Type { + utyp := c.unwrap_generic(typ) + typ_sym := c.table.get_type_symbol(utyp) + mut inter_sym := c.table.get_type_symbol(interface_type) + + if mut inter_sym.info is ast.Interface { + if inter_sym.info.is_generic { + mut inferred_types := []ast.Type{} + generic_names := inter_sym.info.generic_types.map(c.table.get_type_name(it)) + // inferring interface generic types + for gt_name in generic_names { + mut inferred_type := ast.void_type + for ifield in inter_sym.info.fields { + if ifield.typ.has_flag(.generic) && c.table.get_type_name(ifield.typ) == gt_name { + if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { + inferred_type = field.typ + } + } + } + for imethod in inter_sym.info.methods { + method := typ_sym.find_method(imethod.name) or { + typ_sym.find_method_with_generic_parent(imethod.name) or { ast.Fn{} } + } + if imethod.return_type.has_flag(.generic) { + imret_sym := c.table.get_type_symbol(imethod.return_type) + mret_sym := c.table.get_type_symbol(method.return_type) + if imret_sym.info is ast.MultiReturn && mret_sym.info is ast.MultiReturn { + for i, mr_typ in imret_sym.info.types { + if mr_typ.has_flag(.generic) + && c.table.get_type_name(mr_typ) == gt_name { + inferred_type = mret_sym.info.types[i] + } + } + } else if c.table.get_type_name(imethod.return_type) == gt_name { + mut ret_typ := method.return_type + if imethod.return_type.has_flag(.optional) { + ret_typ = ret_typ.clear_flag(.optional) + } + inferred_type = ret_typ + } + } + for i, iparam in imethod.params { + param := method.params[i] or { ast.Param{} } + if iparam.typ.has_flag(.generic) + && c.table.get_type_name(iparam.typ) == gt_name { + inferred_type = param.typ + } + } + } + if inferred_type == ast.void_type { + c.error('could not infer generic type `$gt_name` in interface', pos) + return interface_type + } + inferred_types << inferred_type + } + // add concrete types to method + for imethod in inter_sym.info.methods { + if inferred_types !in c.table.fn_generic_types[imethod.name] { + c.table.fn_generic_types[imethod.name] << inferred_types + } + } + inter_sym.info.concrete_types = inferred_types + return c.unwrap_generic_type(interface_type, generic_names, inter_sym.info.concrete_types) + } + } + return interface_type +} + +fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Position) bool { + $if debug_interface_type_implements ? { + eprintln('> type_implements typ: $typ.debug() (`${c.table.type_to_str(typ)}`) | inter_typ: $interface_type.debug() (`${c.table.type_to_str(interface_type)}`)') + } + utyp := c.unwrap_generic(typ) + typ_sym := c.table.get_type_symbol(utyp) + mut inter_sym := c.table.get_type_symbol(interface_type) + if mut inter_sym.info is ast.Interface { + mut generic_type := interface_type + mut generic_info := inter_sym.info + if inter_sym.info.parent_type.has_flag(.generic) { + parent_sym := c.table.get_type_symbol(inter_sym.info.parent_type) + if parent_sym.info is ast.Interface { + generic_type = inter_sym.info.parent_type + generic_info = parent_sym.info + } + } + mut inferred_type := interface_type + if generic_info.is_generic { + inferred_type = c.resolve_generic_interface(typ, generic_type, pos) + if inferred_type == 0 { + return false + } + } + if inter_sym.info.is_generic { + return c.type_implements(typ, inferred_type, pos) + } + } + // do not check the same type more than once + if mut inter_sym.info is ast.Interface { + for t in inter_sym.info.types { + if t.idx() == utyp.idx() { + return true + } + } + } + styp := c.table.type_to_str(utyp) + if utyp.idx() == interface_type.idx() { + // same type -> already casted to the interface + return true + } + if interface_type.idx() == ast.error_type_idx && utyp.idx() == ast.none_type_idx { + // `none` "implements" the Error interface + return true + } + if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ { + c.error('cannot implement interface `$inter_sym.name` with a different interface `$styp`', + pos) + } + imethods := if inter_sym.kind == .interface_ { + (inter_sym.info as ast.Interface).methods + } else { + inter_sym.methods + } + // voidptr is an escape hatch, it should be allowed to be passed + if utyp != ast.voidptr_type { + // Verify methods + for imethod in imethods { + method := typ_sym.find_method(imethod.name) or { + typ_sym.find_method_with_generic_parent(imethod.name) or { + c.error("`$styp` doesn't implement method `$imethod.name` of interface `$inter_sym.name`", + pos) + continue + } + } + msg := c.table.is_same_method(imethod, method) + if msg.len > 0 { + sig := c.table.fn_signature(imethod, skip_receiver: true) + c.add_error_detail('$inter_sym.name has `$sig`') + c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.name`: $msg', + pos) + return false + } + } + } + // Verify fields + if mut inter_sym.info is ast.Interface { + for ifield in inter_sym.info.fields { + if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { + if ifield.typ != field.typ { + exp := c.table.type_to_str(ifield.typ) + got := c.table.type_to_str(field.typ) + c.error('`$styp` incorrectly implements field `$ifield.name` of interface `$inter_sym.name`, expected `$exp`, got `$got`', + pos) + return false + } else if ifield.is_mut && !(field.is_mut || field.is_global) { + c.error('`$styp` incorrectly implements interface `$inter_sym.name`, field `$ifield.name` must be mutable', + pos) + return false + } + continue + } + // voidptr is an escape hatch, it should be allowed to be passed + if utyp != ast.voidptr_type { + c.error("`$styp` doesn't implement field `$ifield.name` of interface `$inter_sym.name`", + pos) + } + } + inter_sym.info.types << utyp + } + return true +} + +// return the actual type of the expression, once the optional is handled +pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Type { + if expr is ast.CallExpr { + if expr.return_type.has_flag(.optional) { + if expr.or_block.kind == .absent { + if c.inside_defer { + c.error('${expr.name}() returns an option, so it should have an `or {}` block at the end', + expr.pos) + } else { + c.error('${expr.name}() returns an option, so it should have either an `or {}` block, or `?` at the end', + expr.pos) + } + } else { + c.check_or_expr(expr.or_block, ret_type, expr.return_type.clear_flag(.optional)) + } + return ret_type.clear_flag(.optional) + } else if expr.or_block.kind == .block { + c.error('unexpected `or` block, the function `$expr.name` does not return an optional', + expr.or_block.pos) + } else if expr.or_block.kind == .propagate { + c.error('unexpected `?`, the function `$expr.name` does not return an optional', + expr.or_block.pos) + } + } else if expr is ast.IndexExpr { + if expr.or_expr.kind != .absent { + c.check_or_expr(expr.or_expr, ret_type, ret_type) + } + } + return ret_type +} + +pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type) { + if node.kind == .propagate { + if !c.table.cur_fn.return_type.has_flag(.optional) && c.table.cur_fn.name != 'main.main' + && !c.inside_const { + c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', + node.pos) + } + return + } + stmts_len := node.stmts.len + if stmts_len == 0 { + if ret_type != ast.void_type { + // x := f() or {} + c.error('assignment requires a non empty `or {}` block', node.pos) + } + // allow `f() or {}` + return + } + last_stmt := node.stmts[stmts_len - 1] + if ret_type != ast.void_type { + match last_stmt { + ast.ExprStmt { + c.expected_type = ret_type + c.expected_or_type = ret_type.clear_flag(.optional) + last_stmt_typ := c.expr(last_stmt.expr) + c.expected_or_type = ast.void_type + type_fits := c.check_types(last_stmt_typ, ret_type) + && last_stmt_typ.nr_muls() == ret_type.nr_muls() + is_noreturn := is_noreturn_callexpr(last_stmt.expr) + if type_fits || is_noreturn { + return + } + expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional)) + if last_stmt.typ == ast.void_type { + c.error('`or` block must provide a default value of type `$expected_type_name`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)', + last_stmt.pos) + } else { + type_name := c.table.type_to_str(last_stmt_typ) + c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`', + last_stmt.pos) + } + return + } + ast.BranchStmt { + if last_stmt.kind !in [.key_continue, .key_break] { + c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block', + last_stmt.pos) + return + } + } + ast.Return {} + else { + expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional)) + c.error('last statement in the `or {}` block should be an expression of type `$expected_type_name` or exit parent scope', + node.pos) + return + } + } + } else { + match last_stmt { + ast.ExprStmt { + if last_stmt.typ == ast.void_type { + return + } + if is_noreturn_callexpr(last_stmt.expr) { + return + } + if c.check_types(last_stmt.typ, expr_return_type) { + return + } + // opt_returning_string() or { ... 123 } + type_name := c.table.type_to_str(last_stmt.typ) + expr_return_type_name := c.table.type_to_str(expr_return_type) + c.error('the default expression type in the `or` block should be `$expr_return_type_name`, instead you gave a value of type `$type_name`', + last_stmt.expr.position()) + } + else {} + } + } +} + +fn is_noreturn_callexpr(expr ast.Expr) bool { + if expr is ast.CallExpr { + return expr.is_noreturn + } + return false +} + +pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { + prevent_sum_type_unwrapping_once := c.prevent_sum_type_unwrapping_once + c.prevent_sum_type_unwrapping_once = false + + using_new_err_struct_save := c.using_new_err_struct + // TODO remove; this avoids a breaking change in syntax + if '$node.expr' == 'err' { + c.using_new_err_struct = true + } + + // T.name, typeof(expr).name + mut name_type := 0 + match mut node.expr { + ast.Ident { + name := node.expr.name + valid_generic := util.is_generic_type_name(name) && name in c.table.cur_fn.generic_names + if valid_generic { + name_type = ast.Type(c.table.find_type_idx(name)).set_flag(.generic) + } + } + // Note: in future typeof() should be a type known at compile-time + // sum types should not be handled dynamically + ast.TypeOf { + name_type = c.expr(node.expr.expr) + } + else {} + } + if name_type > 0 { + node.name_type = name_type + match node.gkind_field { + .name { + return ast.string_type + } + .typ { + return ast.int_type + } + else { + if node.field_name == 'name' { + return ast.string_type + } else if node.field_name == 'idx' { + return ast.int_type + } + c.error('invalid field `.$node.field_name` for type `$node.expr`', node.pos) + return ast.string_type + } + } + } + + old_selector_expr := c.inside_selector_expr + c.inside_selector_expr = true + typ := c.expr(node.expr) + c.inside_selector_expr = old_selector_expr + c.using_new_err_struct = using_new_err_struct_save + if typ == ast.void_type_idx { + c.error('`void` type has no fields', node.pos) + return ast.void_type + } + node.expr_type = typ + if node.expr_type.has_flag(.optional) && !(node.expr is ast.Ident + && (node.expr as ast.Ident).kind == .constant) { + c.error('cannot access fields of an optional, handle the error with `or {...}` or propagate it with `?`', + node.pos) + } + field_name := node.field_name + sym := c.table.get_type_symbol(typ) + if (typ.has_flag(.variadic) || sym.kind == .array_fixed) && field_name == 'len' { + node.typ = ast.int_type + return ast.int_type + } + if sym.kind == .chan { + if field_name == 'closed' { + node.typ = ast.bool_type + return ast.bool_type + } else if field_name in ['len', 'cap'] { + node.typ = ast.u32_type + return ast.u32_type + } + } + mut unknown_field_msg := 'type `$sym.name` has no field named `$field_name`' + mut has_field := false + mut field := ast.StructField{} + if field_name.len > 0 && field_name[0].is_capital() && sym.info is ast.Struct + && sym.language == .v { + // x.Foo.y => access the embedded struct + for embed in sym.info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if embed_sym.embed_name() == field_name { + node.typ = embed + return embed + } + } + } else { + if f := c.table.find_field(sym, field_name) { + has_field = true + field = f + } else { + // look for embedded field + has_field = true + mut embed_type := ast.Type(0) + field, embed_type = c.table.find_field_from_embeds(sym, field_name) or { + if err.msg != '' { + c.error(err.msg, node.pos) + } + has_field = false + ast.StructField{}, ast.Type(0) + } + node.from_embed_type = embed_type + if sym.kind in [.aggregate, .sum_type] { + unknown_field_msg = err.msg + } + } + if !c.inside_unsafe { + if sym.info is ast.Struct { + if sym.info.is_union && node.next_token !in token.assign_tokens { + c.warn('reading a union field (or its address) requires `unsafe`', + node.pos) + } + } + } + if typ.has_flag(.generic) && !has_field { + gs := c.table.get_type_symbol(c.unwrap_generic(typ)) + if f := c.table.find_field(gs, field_name) { + has_field = true + field = f + } else { + // look for embedded field + has_field = true + mut embed_type := ast.Type(0) + field, embed_type = c.table.find_field_from_embeds(gs, field_name) or { + if err.msg != '' { + c.error(err.msg, node.pos) + } + has_field = false + ast.StructField{}, ast.Type(0) + } + node.from_embed_type = embed_type + } + } + } + if has_field { + if sym.mod != c.mod && !field.is_pub && sym.language != .c { + unwrapped_sym := c.table.get_type_symbol(c.unwrap_generic(typ)) + c.error('field `${unwrapped_sym.name}.$field_name` is not public', node.pos) + } + field_sym := c.table.get_type_symbol(field.typ) + if field_sym.kind in [.sum_type, .interface_] { + if !prevent_sum_type_unwrapping_once { + if scope_field := node.scope.find_struct_field(node.expr.str(), typ, field_name) { + return scope_field.smartcasts.last() + } + } + } + node.typ = field.typ + return field.typ + } + if sym.kind !in [.struct_, .aggregate, .interface_, .sum_type] { + if sym.kind != .placeholder { + unwrapped_sym := c.table.get_type_symbol(c.unwrap_generic(typ)) + c.error('`$unwrapped_sym.name` has no property `$node.field_name`', node.pos) + } + } else { + if sym.info is ast.Struct { + suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name)) + c.error(suggestion.say(unknown_field_msg), node.pos) + } + c.error(unknown_field_msg, node.pos) + } + return ast.void_type +} + +// TODO: non deferred +pub fn (mut c Checker) return_stmt(mut node ast.Return) { + c.expected_type = c.table.cur_fn.return_type + mut expected_type := c.unwrap_generic(c.expected_type) + expected_type_sym := c.table.get_type_symbol(expected_type) + if node.exprs.len > 0 && c.table.cur_fn.return_type == ast.void_type { + c.error('unexpected argument, current function does not return anything', node.exprs[0].position()) + return + } else if node.exprs.len == 0 && !(c.expected_type == ast.void_type + || expected_type_sym.kind == .void) { + stype := c.table.type_to_str(expected_type) + arg := if expected_type_sym.kind == .multi_return { 'arguments' } else { 'argument' } + c.error('expected `$stype` $arg', node.pos) + return + } + if node.exprs.len == 0 { + return + } + exp_is_optional := expected_type.has_flag(.optional) + mut expected_types := [expected_type] + if expected_type_sym.info is ast.MultiReturn { + expected_types = expected_type_sym.info.types + if c.table.cur_concrete_types.len > 0 { + expected_types = expected_types.map(c.unwrap_generic(it)) + } + } + mut got_types := []ast.Type{} + for expr in node.exprs { + typ := c.expr(expr) + // Unpack multi return types + sym := c.table.get_type_symbol(typ) + if sym.kind == .multi_return { + for t in sym.mr_info().types { + got_types << t + } + } else { + got_types << typ + } + } + node.types = got_types + $if debug_manualfree ? { + cfn := c.table.cur_fn + if cfn.is_manualfree { + pnames := cfn.params.map(it.name) + for expr in node.exprs { + if expr is ast.Ident { + if expr.name in pnames { + c.note('returning a parameter in a fn marked with `[manualfree]` can cause double freeing in the caller', + node.pos) + } + } + } + } + } + // allow `none` & `error` return types for function that returns optional + option_type_idx := c.table.type_idxs['Option'] + got_types_0_idx := got_types[0].idx() + if exp_is_optional + && got_types_0_idx in [ast.none_type_idx, ast.error_type_idx, option_type_idx] { + if got_types_0_idx == ast.none_type_idx && expected_type == ast.ovoid_type { + c.error('returning `none` in functions, that have a `?` result type is not allowed anymore, either `return error(message)` or just `return` instead', + node.pos) + } + return + } + if expected_types.len > 0 && expected_types.len != got_types.len { + arg := if expected_types.len == 1 { 'argument' } else { 'arguments' } + c.error('expected $expected_types.len $arg, but got $got_types.len', node.pos) + return + } + for i, exp_type in expected_types { + got_typ := c.unwrap_generic(got_types[i]) + if got_typ.has_flag(.optional) && (!exp_type.has_flag(.optional) + || c.table.type_to_str(got_typ) != c.table.type_to_str(exp_type)) { + pos := node.exprs[i].position() + c.error('cannot use `${c.table.type_to_str(got_typ)}` as type `${c.table.type_to_str(exp_type)}` in return argument', + pos) + } + if !c.check_types(got_typ, exp_type) { + got_typ_sym := c.table.get_type_symbol(got_typ) + mut exp_typ_sym := c.table.get_type_symbol(exp_type) + pos := node.exprs[i].position() + if node.exprs[i].is_auto_deref_var() { + continue + } + if exp_typ_sym.kind == .interface_ { + if c.type_implements(got_typ, exp_type, node.pos) { + if !got_typ.is_ptr() && !got_typ.is_pointer() && got_typ_sym.kind != .interface_ + && !c.inside_unsafe { + c.mark_as_referenced(mut &node.exprs[i], true) + } + } + continue + } + c.error('cannot use `$got_typ_sym.name` as type `${c.table.type_to_str(exp_type)}` in return argument', + pos) + } + if (got_typ.is_ptr() || got_typ.is_pointer()) + && (!exp_type.is_ptr() && !exp_type.is_pointer()) { + pos := node.exprs[i].position() + if node.exprs[i].is_auto_deref_var() { + continue + } + c.error('fn `$c.table.cur_fn.name` expects you to return a non reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', + pos) + } + if (exp_type.is_ptr() || exp_type.is_pointer()) + && (!got_typ.is_ptr() && !got_typ.is_pointer()) && got_typ != ast.int_literal_type { + pos := node.exprs[i].position() + if node.exprs[i].is_auto_deref_var() { + continue + } + c.error('fn `$c.table.cur_fn.name` expects you to return a reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', + pos) + } + if exp_type.is_ptr() && got_typ.is_ptr() { + mut r_expr := &node.exprs[i] + if mut r_expr is ast.Ident { + if mut r_expr.obj is ast.Var { + mut obj := unsafe { &r_expr.obj } + if c.fn_scope != voidptr(0) { + obj = c.fn_scope.find_var(r_expr.obj.name) or { obj } + } + if obj.is_stack_obj && !c.inside_unsafe { + type_sym := c.table.get_type_symbol(obj.typ.set_nr_muls(0)) + if !type_sym.is_heap() && !c.pref.translated { + suggestion := if type_sym.kind == .struct_ { + 'declaring `$type_sym.name` as `[heap]`' + } else { + 'wrapping the `$type_sym.name` object in a `struct` declared as `[heap]`' + } + c.error('`$r_expr.name` cannot be returned outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.', + r_expr.pos) + } + } + } + } + } + } + if exp_is_optional && node.exprs.len > 0 { + expr0 := node.exprs[0] + if expr0 is ast.CallExpr { + if expr0.or_block.kind == .propagate { + c.error('`?` is not needed, use `return ${expr0.name}()`', expr0.pos) + } + } + } +} + +pub fn (mut c Checker) const_decl(mut node ast.ConstDecl) { + mut field_names := []string{} + mut field_order := []int{} + if node.fields.len == 0 { + c.warn('const block must have at least 1 declaration', node.pos) + } + for i, field in node.fields { + // TODO Check const name once the syntax is decided + if field.name in c.const_names { + name_pos := token.Position{ + ...field.pos + len: util.no_cur_mod(field.name, c.mod).len + } + c.error('duplicate const `$field.name`', name_pos) + } + c.const_names << field.name + field_names << field.name + field_order << i + } + mut needs_order := false + mut done_fields := []int{} + for i, mut field in node.fields { + c.const_decl = field.name + c.const_deps << field.name + mut typ := c.check_expr_opt_call(field.expr, c.expr(field.expr)) + if ct_value := eval_comptime_const_expr(field.expr, 0) { + field.comptime_expr_value = ct_value + if ct_value is u64 { + typ = ast.u64_type + } + } + node.fields[i].typ = c.table.mktyp(typ) + for cd in c.const_deps { + for j, f in node.fields { + if j != i && cd in field_names && cd == f.name && j !in done_fields { + needs_order = true + x := field_order[j] + field_order[j] = field_order[i] + field_order[i] = x + break + } + } + } + done_fields << i + c.const_deps = [] + } + if needs_order { + mut ordered_fields := []ast.ConstField{} + for order in field_order { + ordered_fields << node.fields[order] + } + node.fields = ordered_fields + } +} + +pub fn (mut c Checker) enum_decl(node ast.EnumDecl) { + c.check_valid_pascal_case(node.name, 'enum name', node.pos) + mut seen := []i64{} + if node.fields.len == 0 { + c.error('enum cannot be empty', node.pos) + } + /* + if node.is_pub && c.mod == 'builtin' { + c.error('`builtin` module cannot have enums', node.pos) + } + */ + for i, field in node.fields { + if !c.pref.experimental && util.contains_capital(field.name) { + // TODO C2V uses hundreds of enums with capitals, remove -experimental check once it's handled + c.error('field name `$field.name` cannot contain uppercase letters, use snake_case instead', + field.pos) + } + for j in 0 .. i { + if field.name == node.fields[j].name { + c.error('field name `$field.name` duplicate', field.pos) + } + } + if field.has_expr { + match field.expr { + ast.IntegerLiteral { + val := field.expr.val.i64() + if val < checker.int_min || val > checker.int_max { + c.error('enum value `$val` overflows int', field.expr.pos) + } else if !node.is_multi_allowed && i64(val) in seen { + c.error('enum value `$val` already exists', field.expr.pos) + } + seen << i64(val) + } + ast.PrefixExpr {} + else { + if field.expr is ast.Ident { + if field.expr.language == .c { + continue + } + } + mut pos := field.expr.position() + if pos.pos == 0 { + pos = field.pos + } + c.error('default value for enum has to be an integer', pos) + } + } + } else { + if seen.len > 0 { + last := seen[seen.len - 1] + if last == checker.int_max { + c.error('enum value overflows', field.pos) + } else if !node.is_multi_allowed && last + 1 in seen { + c.error('enum value `${last + 1}` already exists', field.pos) + } + seen << last + 1 + } else { + seen << 0 + } + } + } +} + +pub fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { + c.expected_type = ast.none_type // TODO a hack to make `x := if ... work` + defer { + c.expected_type = ast.void_type + } + right_first := node.right[0] + node.left_types = [] + mut right_len := node.right.len + mut right_type0 := ast.void_type + for i, right in node.right { + if right is ast.CallExpr || right is ast.IfExpr || right is ast.LockExpr + || right is ast.MatchExpr { + right_type := c.expr(right) + if i == 0 { + right_type0 = right_type + node.right_types = [ + c.check_expr_opt_call(right, right_type0), + ] + } + right_type_sym := c.table.get_type_symbol(right_type) + if right_type_sym.kind == .multi_return { + if node.right.len > 1 { + c.error('cannot use multi-value $right_type_sym.name in single-value context', + right.position()) + } + node.right_types = right_type_sym.mr_info().types + right_len = node.right_types.len + } else if right_type == ast.void_type { + right_len = 0 + } + } + } + if node.left.len != right_len { + if right_first is ast.CallExpr { + c.error('assignment mismatch: $node.left.len variable(s) but `${right_first.name}()` returns $right_len value(s)', + node.pos) + } else { + c.error('assignment mismatch: $node.left.len variable(s) $right_len value(s)', + node.pos) + } + return + } + + is_decl := node.op == .decl_assign + for i, left in node.left { + if left is ast.CallExpr { + // ban `foo() = 10` + c.error('cannot call function `${left.name}()` on the left side of an assignment', + left.pos) + } else if left is ast.PrefixExpr { + // ban `*foo() = 10` + if left.right is ast.CallExpr && left.op == .mul { + c.error('cannot dereference a function call on the left side of an assignment, use a temporary variable', + left.pos) + } + } else if left is ast.IndexExpr { + if left.index is ast.RangeExpr { + c.error('cannot reassign using range expression on the left side of an assignment', + left.pos) + } + } + is_blank_ident := left.is_blank_ident() + mut left_type := ast.void_type + if !is_decl && !is_blank_ident { + if left is ast.Ident || left is ast.SelectorExpr { + c.prevent_sum_type_unwrapping_once = true + } + left_type = c.expr(left) + c.expected_type = c.unwrap_generic(left_type) + // `map = {}` + if left_type != 0 { + sym := c.table.get_type_symbol(left_type) + if sym.kind == .map && node.right[i] is ast.StructInit { + c.warn('assigning a struct literal to a map is deprecated - use `map{}` instead', + node.right[i].position()) + node.right[i] = ast.MapInit{} + } + } + } + if node.right_types.len < node.left.len { // first type or multi return types added above + old_inside_ref_lit := c.inside_ref_lit + if left is ast.Ident { + if left.info is ast.IdentVar { + c.inside_ref_lit = c.inside_ref_lit || left.info.share == .shared_t + } + } + c.inside_decl_rhs = is_decl + right_type := c.expr(node.right[i]) + c.inside_decl_rhs = false + c.inside_ref_lit = old_inside_ref_lit + if node.right_types.len == i { + node.right_types << c.check_expr_opt_call(node.right[i], right_type) + } + } + right := if i < node.right.len { node.right[i] } else { node.right[0] } + mut right_type := node.right_types[i] + if right is ast.Ident { + right_sym := c.table.get_type_symbol(right_type) + if right_sym.info is ast.Struct { + if right_sym.info.generic_types.len > 0 { + if obj := right.scope.find(right.name) { + right_type = obj.typ + } + } + } + } + if is_decl { + // check generic struct init and return unwrap generic struct type + if right is ast.StructInit { + if right.typ.has_flag(.generic) { + c.expr(right) + right_type = right.typ + } + } else if right is ast.PrefixExpr { + if right.op == .amp && right.right is ast.StructInit { + right_type = c.expr(right) + } + } + if right.is_auto_deref_var() { + left_type = c.table.mktyp(right_type.deref()) + } else { + left_type = c.table.mktyp(right_type) + } + if left_type == ast.int_type { + if right is ast.IntegerLiteral { + mut is_large := right.val.len > 13 + if !is_large && right.val.len > 8 { + val := right.val.i64() + is_large = val > checker.int_max || val < checker.int_min + } + if is_large { + c.error('overflow in implicit type `int`, use explicit type casting instead', + right.pos) + } + } + } + } else { + // Make sure the variable is mutable + c.fail_if_immutable(left) + // left_type = c.expr(left) + } + if right_type.is_ptr() && left_type.is_ptr() { + if mut right is ast.Ident { + if mut right.obj is ast.Var { + mut obj := unsafe { &right.obj } + if c.fn_scope != voidptr(0) { + obj = c.fn_scope.find_var(right.obj.name) or { obj } + } + if obj.is_stack_obj && !c.inside_unsafe { + type_sym := c.table.get_type_symbol(obj.typ.set_nr_muls(0)) + if !type_sym.is_heap() && !c.pref.translated { + suggestion := if type_sym.kind == .struct_ { + 'declaring `$type_sym.name` as `[heap]`' + } else { + 'wrapping the `$type_sym.name` object in a `struct` declared as `[heap]`' + } + c.error('`$right.name` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.', + right.pos) + } + } + } + } + } + node.left_types << left_type + match mut left { + ast.Ident { + if left.kind == .blank_ident { + left_type = right_type + node.left_types[i] = right_type + if node.op !in [.assign, .decl_assign] { + c.error('cannot modify blank `_` identifier', left.pos) + } + } else if left.info !is ast.IdentVar { + c.error('cannot assign to $left.kind `$left.name`', left.pos) + } else { + if is_decl { + c.check_valid_snake_case(left.name, 'variable name', left.pos) + } + mut ident_var_info := left.info as ast.IdentVar + if ident_var_info.share == .shared_t { + left_type = left_type.set_flag(.shared_f) + if is_decl { + if left_type.nr_muls() > 1 { + c.error('shared cannot be multi level reference', left.pos) + } + left_type = left_type.set_nr_muls(1) + } + } else if left_type.has_flag(.shared_f) { + left_type = left_type.clear_flag(.shared_f) + } + if ident_var_info.share == .atomic_t { + left_type = left_type.set_flag(.atomic_f) + } + node.left_types[i] = left_type + ident_var_info.typ = left_type + left.info = ident_var_info + if left_type != 0 { + match mut left.obj { + ast.Var { + left.obj.typ = left_type + if left.obj.is_auto_deref { + left.obj.is_used = true + } + if !left_type.is_ptr() { + if c.table.get_type_symbol(left_type).is_heap() { + left.obj.is_auto_heap = true + } + } + if left_type in ast.unsigned_integer_type_idxs { + if right is ast.IntegerLiteral { + if right.val[0] == `-` { + c.error('Cannot assign negative value to unsigned integer type', + right.pos) + } + } + } + } + ast.GlobalField { + left.obj.typ = left_type + } + else {} + } + } + if is_decl { + full_name := '${left.mod}.$left.name' + if obj := c.file.global_scope.find(full_name) { + if obj is ast.ConstField { + c.warn('duplicate of a const name `$full_name`', left.pos) + } + } + } + } + } + ast.PrefixExpr { + // Do now allow `*x = y` outside `unsafe` + if left.op == .mul { + if !c.inside_unsafe && !c.pref.translated { + c.error('modifying variables via dereferencing can only be done in `unsafe` blocks', + node.pos) + } else { + // mark `p` in `*p = val` as used: + match mut left.right { + ast.Ident { + match mut left.right.obj { + ast.Var { + left.right.obj.is_used = true + } + else {} + } + } + else {} + } + } + } + if is_decl { + c.error('non-name on the left side of `:=`', left.pos) + } + } + else { + if mut left is ast.IndexExpr { + // eprintln('>>> left.is_setter: ${left.is_setter:10} | left.is_map: ${left.is_map:10} | left.is_array: ${left.is_array:10}') + if left.is_map && left.is_setter { + left.recursive_mapset_is_setter(true) + } + } + if is_decl { + c.error('non-name `$left` on left side of `:=`', left.position()) + } + } + } + left_type_unwrapped := c.unwrap_generic(left_type) + right_type_unwrapped := c.unwrap_generic(right_type) + if right_type_unwrapped == 0 { + // right type was a generic `T` + continue + } + if c.pref.translated { + // TODO fix this in C2V instead, for example cast enums to int before using `|` on them. + // TODO replace all c.pref.translated checks with `$if !translated` for performance + continue + } + if left_type_unwrapped == 0 { + continue + } + left_sym := c.table.get_type_symbol(left_type_unwrapped) + right_sym := c.table.get_type_symbol(right_type_unwrapped) + if left_sym.kind == .array && !c.inside_unsafe && node.op in [.assign, .decl_assign] + && right_sym.kind == .array && (left is ast.Ident && !left.is_blank_ident()) + && right is ast.Ident { + // Do not allow `a = b`, only `a = b.clone()` + c.error('use `array2 $node.op.str() array1.clone()` instead of `array2 $node.op.str() array1` (or use `unsafe`)', + node.pos) + } + if left_sym.kind == .array_fixed && !c.inside_unsafe && node.op in [.assign, .decl_assign] + && right_sym.kind == .array_fixed && (left is ast.Ident && !left.is_blank_ident()) + && right is ast.Ident { + if right_sym.info is ast.ArrayFixed { + if right_sym.info.elem_type.is_ptr() { + c.error('assignment from one fixed array to another with a pointer element type is prohibited outside of `unsafe`', + node.pos) + } + } + } + if left_sym.kind == .map && node.op in [.assign, .decl_assign] && right_sym.kind == .map + && ((right is ast.Ident && right.is_auto_deref_var()) + || !right_type.is_ptr()) && !left.is_blank_ident() && right.is_lvalue() { + // Do not allow `a = b` + c.error('cannot copy map: call `move` or `clone` method (or use a reference)', + right.position()) + } + left_is_ptr := left_type.is_ptr() || left_sym.is_pointer() + if left_is_ptr && !left.is_auto_deref_var() { + if !c.inside_unsafe && node.op !in [.assign, .decl_assign] { + // ptr op= + c.warn('pointer arithmetic is only allowed in `unsafe` blocks', node.pos) + } + right_is_ptr := right_type.is_ptr() || right_sym.is_pointer() + if !right_is_ptr && node.op == .assign && right_type_unwrapped.is_number() { + c.error('cannot assign to `$left`: ' + + c.expected_msg(right_type_unwrapped, left_type_unwrapped), right.position()) + } + if (right is ast.StructInit || !right_is_ptr) && !(right_sym.is_number() + || left_type.has_flag(.shared_f)) { + left_name := c.table.type_to_str(left_type_unwrapped) + mut rtype := right_type_unwrapped + if rtype.is_ptr() { + rtype = rtype.deref() + } + right_name := c.table.type_to_str(rtype) + c.error('mismatched types `$left_name` and `$right_name`', node.pos) + } + } + // Single side check + match node.op { + .assign {} // No need to do single side check for =. But here put it first for speed. + .plus_assign, .minus_assign { + if left_type == ast.string_type { + if node.op != .plus_assign { + c.error('operator `$node.op` not defined on left operand type `$left_sym.name`', + left.position()) + } + if right_type != ast.string_type { + c.error('invalid right operand: $left_sym.name $node.op $right_sym.name', + right.position()) + } + } else if !left_sym.is_number() + && left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] { + c.error('operator `$node.op` not defined on left operand type `$left_sym.name`', + left.position()) + } else if !right_sym.is_number() + && left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] { + c.error('invalid right operand: $left_sym.name $node.op $right_sym.name', + right.position()) + } + } + .mult_assign, .div_assign { + if !left_sym.is_number() + && !c.table.get_final_type_symbol(left_type_unwrapped).is_int() + && left_sym.kind !in [.struct_, .alias] { + c.error('operator $node.op.str() not defined on left operand type `$left_sym.name`', + left.position()) + } else if !right_sym.is_number() + && !c.table.get_final_type_symbol(left_type_unwrapped).is_int() + && left_sym.kind !in [.struct_, .alias] { + c.error('operator $node.op.str() not defined on right operand type `$right_sym.name`', + right.position()) + } + } + .and_assign, .or_assign, .xor_assign, .mod_assign, .left_shift_assign, + .right_shift_assign { + if !left_sym.is_int() + && !c.table.get_final_type_symbol(left_type_unwrapped).is_int() { + c.error('operator $node.op.str() not defined on left operand type `$left_sym.name`', + left.position()) + } else if !right_sym.is_int() + && !c.table.get_final_type_symbol(right_type_unwrapped).is_int() { + c.error('operator $node.op.str() not defined on right operand type `$right_sym.name`', + right.position()) + } + } + else {} + } + if node.op in [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .div_assign] + && ((left_sym.kind == .struct_ && right_sym.kind == .struct_) + || left_sym.kind == .alias) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + parent_sym := c.table.get_final_type_symbol(left_type) + if left_sym.kind == .alias && right_sym.kind != .alias { + c.error('mismatched types `$left_name` and `$right_name`', node.pos) + } + extracted_op := match node.op { + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .mod_assign { '%' } + .mult_assign { '*' } + else { 'unknown op' } + } + if method := left_sym.find_method(extracted_op) { + if method.return_type != left_type { + c.error('operator `$extracted_op` must return `$left_name` to be used as an assignment operator', + node.pos) + } + } else { + if parent_sym.is_primitive() { + c.error('cannot use operator methods on type alias for `$parent_sym.name`', + node.pos) + } + if left_name == right_name { + c.error('undefined operation `$left_name` $extracted_op `$right_name`', + node.pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', node.pos) + } + } + } + if !is_blank_ident && !left.is_auto_deref_var() && !right.is_auto_deref_var() + && right_sym.kind != .placeholder && left_sym.kind != .interface_ + && !right_type.has_flag(.generic) && !left_type.has_flag(.generic) { + // Dual sides check (compatibility check) + c.check_expected(right_type_unwrapped, left_type_unwrapped) or { + // allow for ptr += 2 + if left_type_unwrapped.is_ptr() && right_type_unwrapped.is_int() + && node.op in [.plus_assign, .minus_assign] { + if !c.inside_unsafe { + c.warn('pointer arithmetic is only allowed in `unsafe` blocks', + node.pos) + } + } else { + c.error('cannot assign to `$left`: $err.msg', right.position()) + } + } + } + if left_sym.kind == .interface_ { + if c.type_implements(right_type, left_type, right.position()) { + if !right_type.is_ptr() && !right_type.is_pointer() && right_sym.kind != .interface_ + && !c.inside_unsafe { + c.mark_as_referenced(mut &node.right[i], true) + } + } + } + } + // this needs to run after the assign stmt left exprs have been run through checker + // so that ident.obj is set + // Check `x := &y` and `mut x := <-ch` + if right_first is ast.PrefixExpr { + right_node := right_first + left_first := node.left[0] + if left_first is ast.Ident { + assigned_var := left_first + mut is_shared := false + if left_first.info is ast.IdentVar { + is_shared = left_first.info.share == .shared_t + } + old_inside_ref_lit := c.inside_ref_lit + c.inside_ref_lit = (c.inside_ref_lit || right_node.op == .amp || is_shared) + c.expr(right_node.right) + c.inside_ref_lit = old_inside_ref_lit + if right_node.op == .amp { + if right_node.right is ast.Ident { + if right_node.right.obj is ast.Var { + v := right_node.right.obj + right_type0 = v.typ + if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe { + c.error('`$right_node.right.name` is immutable, cannot have a mutable reference to it', + right_node.pos) + } + } else if right_node.right.obj is ast.ConstField { + if assigned_var.is_mut && !c.inside_unsafe { + c.error('`$right_node.right.name` is immutable, cannot have a mutable reference to it', + right_node.pos) + } + } + } + } + if right_node.op == .arrow { + if assigned_var.is_mut { + right_sym := c.table.get_type_symbol(right_type0) + if right_sym.kind == .chan { + chan_info := right_sym.chan_info() + if chan_info.elem_type.is_ptr() && !chan_info.is_mut { + c.error('cannot have a mutable reference to object from `$right_sym.name`', + right_node.pos) + } + } + } + } + } + } + if node.left_types.len != node.left.len { + c.error('assign statement left type number mismatch', node.pos) + } +} + +fn scope_register_it(mut s ast.Scope, pos token.Position, typ ast.Type) { + s.register(ast.Var{ + name: 'it' + pos: pos + typ: typ + is_used: true + }) +} + +fn scope_register_a_b(mut s ast.Scope, pos token.Position, typ ast.Type) { + s.register(ast.Var{ + name: 'a' + pos: pos + typ: typ.to_ptr() + is_used: true + }) + s.register(ast.Var{ + name: 'b' + pos: pos + typ: typ.to_ptr() + is_used: true + }) +} + +fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) { + sym := c.table.get_type_symbol(c.expr(expr)) + if sym.kind !in [.int, .int_literal] { + c.error('array $para needs to be an int', pos) + } +} + +pub fn (mut c Checker) ensure_sumtype_array_has_default_value(node ast.ArrayInit) { + sym := c.table.get_type_symbol(node.elem_type) + if sym.kind == .sum_type && !node.has_default { + c.error('cannot initialize sum type array without default value', node.pos) + } +} + +pub fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type { + mut elem_type := ast.void_type + // []string - was set in parser + if node.typ != ast.void_type { + if node.exprs.len == 0 { + if node.has_cap { + c.check_array_init_para_type('cap', node.cap_expr, node.pos) + } + if node.has_len { + c.check_array_init_para_type('len', node.len_expr, node.pos) + } + } + if node.has_default { + default_expr := node.default_expr + default_typ := c.check_expr_opt_call(default_expr, c.expr(default_expr)) + c.check_expected(default_typ, node.elem_type) or { + c.error(err.msg, default_expr.position()) + } + } + if node.has_len { + if node.has_len && !node.has_default { + elem_type_sym := c.table.get_type_symbol(node.elem_type) + if elem_type_sym.kind == .interface_ { + c.error('cannot instantiate an array of interfaces without also giving a default `init:` value', + node.len_expr.position()) + } + } + c.ensure_sumtype_array_has_default_value(node) + } + c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {} + return node.typ + } + if node.is_fixed { + c.ensure_sumtype_array_has_default_value(node) + c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {} + } + // a = [] + if node.exprs.len == 0 { + // a := fn_returing_opt_array() or { [] } + if c.expected_type == ast.void_type && c.expected_or_type != ast.void_type { + c.expected_type = c.expected_or_type + } + mut type_sym := c.table.get_type_symbol(c.expected_type) + if type_sym.kind != .array || type_sym.array_info().elem_type == ast.void_type { + c.error('array_init: no type specified (maybe: `[]Type{}` instead of `[]`)', + node.pos) + return ast.void_type + } + // TODO: seperate errors once bug is fixed with `x := if expr { ... } else { ... }` + // if c.expected_type == ast.void_type { + // c.error('array_init: use `[]Type{}` instead of `[]`', node.pos) + // return ast.void_type + // } + array_info := type_sym.array_info() + node.elem_type = array_info.elem_type + // clear optional flag incase of: `fn opt_arr ?[]int { return [] }` + return c.expected_type.clear_flag(.optional) + } + // [1,2,3] + if node.exprs.len > 0 && node.elem_type == ast.void_type { + mut expected_value_type := ast.void_type + mut expecting_interface_array := false + if c.expected_type != 0 { + expected_value_type = c.table.value_type(c.expected_type) + if c.table.get_type_symbol(expected_value_type).kind == .interface_ { + // Array of interfaces? (`[dog, cat]`) Save the interface type (`Animal`) + expecting_interface_array = true + } + } + // expecting_interface_array := c.expected_type != 0 && + // c.table.get_type_symbol(c.table.value_type(c.expected_type)).kind == .interface_ + // + // if expecting_interface_array { + // println('ex $c.expected_type') + // } + for i, mut expr in node.exprs { + typ := c.check_expr_opt_call(expr, c.expr(expr)) + node.expr_types << typ + // The first element's type + if expecting_interface_array { + if i == 0 { + elem_type = expected_value_type + c.expected_type = elem_type + c.type_implements(typ, elem_type, expr.position()) + } + if !typ.is_ptr() && !typ.is_pointer() && !c.inside_unsafe { + typ_sym := c.table.get_type_symbol(typ) + if typ_sym.kind != .interface_ { + c.mark_as_referenced(mut &expr, true) + } + } + continue + } + // The first element's type + if i == 0 { + if expr.is_auto_deref_var() { + elem_type = c.table.mktyp(typ.deref()) + } else { + elem_type = c.table.mktyp(typ) + } + c.expected_type = elem_type + continue + } + c.check_expected(typ, elem_type) or { + c.error('invalid array element: $err.msg', expr.position()) + } + } + if node.is_fixed { + idx := c.table.find_or_register_array_fixed(elem_type, node.exprs.len, ast.empty_expr()) + if elem_type.has_flag(.generic) { + node.typ = ast.new_type(idx).set_flag(.generic) + } else { + node.typ = ast.new_type(idx) + } + } else { + idx := c.table.find_or_register_array(elem_type) + if elem_type.has_flag(.generic) { + node.typ = ast.new_type(idx).set_flag(.generic) + } else { + node.typ = ast.new_type(idx) + } + } + node.elem_type = elem_type + } else if node.is_fixed && node.exprs.len == 1 && node.elem_type != ast.void_type { + // [50]byte + mut fixed_size := i64(0) + init_expr := node.exprs[0] + c.expr(init_expr) + match init_expr { + ast.IntegerLiteral { + fixed_size = init_expr.val.int() + } + ast.Ident { + if init_expr.obj is ast.ConstField { + if comptime_value := eval_comptime_const_expr(init_expr.obj.expr, + 0) + { + fixed_size = comptime_value.i64() or { fixed_size } + } + } else { + c.error('non-constant array bound `$init_expr.name`', init_expr.pos) + } + } + ast.InfixExpr { + if comptime_value := eval_comptime_const_expr(init_expr, 0) { + fixed_size = comptime_value.i64() or { fixed_size } + } + } + else { + c.error('expecting `int` for fixed size', node.pos) + } + } + if fixed_size <= 0 { + c.error('fixed size cannot be zero or negative (fixed_size: $fixed_size)', + init_expr.position()) + } + idx := c.table.find_or_register_array_fixed(node.elem_type, int(fixed_size), init_expr) + if node.elem_type.has_flag(.generic) { + node.typ = ast.new_type(idx).set_flag(.generic) + } else { + node.typ = ast.new_type(idx) + } + if node.has_default { + c.expr(node.default_expr) + } + } + return node.typ +} + +[inline] +fn (mut c Checker) check_loop_label(label string, pos token.Position) { + if label.len == 0 { + // ignore + return + } + if c.loop_label.len != 0 { + c.error('nesting of labelled `for` loops is not supported', pos) + return + } + c.loop_label = label +} + +fn (mut c Checker) stmt(node ast.Stmt) { + $if trace_checker ? { + stmt_pos := node.pos + eprintln('checking file: ${c.file.path:-30} | stmt pos: ${stmt_pos.str():-45} | stmt') + } + // c.expected_type = ast.void_type + match mut node { + ast.EmptyStmt { + if c.pref.is_verbose { + eprintln('Checker.stmt() EmptyStmt') + print_backtrace() + } + } + ast.NodeError {} + ast.AsmStmt { + c.asm_stmt(mut node) + } + ast.AssertStmt { + c.assert_stmt(node) + } + ast.AssignStmt { + c.assign_stmt(mut node) + } + ast.Block { + c.block(node) + } + ast.BranchStmt { + c.branch_stmt(node) + } + ast.CompFor { + c.comp_for(node) + } + ast.ConstDecl { + c.inside_const = true + c.const_decl(mut node) + c.inside_const = false + } + ast.DeferStmt { + if node.idx_in_fn < 0 { + node.idx_in_fn = c.table.cur_fn.defer_stmts.len + c.table.cur_fn.defer_stmts << unsafe { &node } + } + if c.locked_names.len != 0 || c.rlocked_names.len != 0 { + c.error('defers are not allowed in lock statements', node.pos) + } + for i, ident in node.defer_vars { + mut id := ident + if id.info is ast.IdentVar { + if id.comptime && id.name in checker.valid_comp_not_user_defined { + node.defer_vars[i] = ast.Ident{ + scope: 0 + name: '' + } + continue + } + mut info := id.info as ast.IdentVar + typ := c.ident(mut id) + if typ == ast.error_type_idx { + continue + } + info.typ = typ + id.info = info + node.defer_vars[i] = id + } + } + c.inside_defer = true + c.stmts(node.stmts) + c.inside_defer = false + } + ast.EnumDecl { + c.enum_decl(node) + } + ast.ExprStmt { + node.typ = c.expr(node.expr) + c.expected_type = ast.void_type + mut or_typ := ast.void_type + match node.expr { + ast.IndexExpr { + if node.expr.or_expr.kind != .absent { + node.is_expr = true + or_typ = node.typ + } + } + ast.PrefixExpr { + if node.expr.or_block.kind != .absent { + node.is_expr = true + or_typ = node.typ + } + } + else {} + } + c.check_expr_opt_call(node.expr, or_typ) + // TODO This should work, even if it's prolly useless .-. + // node.typ = c.check_expr_opt_call(node.expr, ast.void_type) + } + ast.FnDecl { + c.fn_decl(mut node) + } + ast.ForCStmt { + c.for_c_stmt(node) + } + ast.ForInStmt { + c.for_in_stmt(mut node) + } + ast.ForStmt { + c.for_stmt(mut node) + } + ast.GlobalDecl { + c.global_decl(mut node) + } + ast.GotoLabel {} + ast.GotoStmt { + if c.inside_defer { + c.error('goto is not allowed in defer statements', node.pos) + } + if !c.inside_unsafe { + c.warn('`goto` requires `unsafe` (consider using labelled break/continue)', + node.pos) + } + if node.name !in c.table.cur_fn.label_names { + c.error('unknown label `$node.name`', node.pos) + } + // TODO: check label doesn't bypass variable declarations + } + ast.HashStmt { + c.hash_stmt(mut node) + } + ast.Import { + c.import_stmt(node) + } + ast.InterfaceDecl { + c.interface_decl(mut node) + } + ast.Module { + c.mod = node.name + c.is_builtin_mod = node.name in ['builtin', 'os', 'strconv'] + c.check_valid_snake_case(node.name, 'module name', node.pos) + } + ast.Return { + // c.returns = true + c.return_stmt(mut node) + c.scope_returns = true + } + ast.SqlStmt { + c.sql_stmt(mut node) + } + ast.StructDecl { + c.struct_decl(mut node) + } + ast.TypeDecl { + c.type_decl(node) + } + } +} + +fn (mut c Checker) assert_stmt(node ast.AssertStmt) { + cur_exp_typ := c.expected_type + assert_type := c.check_expr_opt_call(node.expr, c.expr(node.expr)) + if assert_type != ast.bool_type_idx { + atype_name := c.table.get_type_symbol(assert_type).name + c.error('assert can be used only with `bool` expressions, but found `$atype_name` instead', + node.pos) + } + c.expected_type = cur_exp_typ +} + +fn (mut c Checker) block(node ast.Block) { + if node.is_unsafe { + c.inside_unsafe = true + c.stmts(node.stmts) + c.inside_unsafe = false + } else { + c.stmts(node.stmts) + } +} + +fn (mut c Checker) branch_stmt(node ast.BranchStmt) { + if c.inside_defer { + c.error('`$node.kind.str()` is not allowed in defer statements', node.pos) + } + if c.in_for_count == 0 { + c.error('$node.kind.str() statement not within a loop', node.pos) + } + if node.label.len > 0 { + if node.label != c.loop_label { + c.error('invalid label name `$node.label`', node.pos) + } + } +} + +fn (mut c Checker) for_c_stmt(node ast.ForCStmt) { + c.in_for_count++ + prev_loop_label := c.loop_label + if node.has_init { + c.stmt(node.init) + } + c.expr(node.cond) + if node.has_inc { + c.stmt(node.inc) + } + c.check_loop_label(node.label, node.pos) + c.stmts(node.stmts) + c.loop_label = prev_loop_label + c.in_for_count-- +} + +fn (mut c Checker) comp_for(node ast.CompFor) { + typ := c.unwrap_generic(node.typ) + sym := c.table.get_type_symbol(typ) + if sym.kind == .placeholder || typ.has_flag(.generic) { + c.error('unknown type `$sym.name`', node.typ_pos) + } + if node.kind == .fields { + c.comptime_fields_type[node.val_var] = node.typ + } + c.stmts(node.stmts) +} + +fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { + c.in_for_count++ + prev_loop_label := c.loop_label + typ := c.expr(node.cond) + typ_idx := typ.idx() + if node.key_var.len > 0 && node.key_var != '_' { + c.check_valid_snake_case(node.key_var, 'variable name', node.pos) + } + if node.val_var.len > 0 && node.val_var != '_' { + c.check_valid_snake_case(node.val_var, 'variable name', node.pos) + } + if node.is_range { + high_type := c.expr(node.high) + high_type_idx := high_type.idx() + if typ_idx in ast.integer_type_idxs && high_type_idx !in ast.integer_type_idxs { + c.error('range types do not match', node.cond.position()) + } else if typ_idx in ast.float_type_idxs || high_type_idx in ast.float_type_idxs { + c.error('range type can not be float', node.cond.position()) + } else if typ_idx == ast.bool_type_idx || high_type_idx == ast.bool_type_idx { + c.error('range type can not be bool', node.cond.position()) + } else if typ_idx == ast.string_type_idx || high_type_idx == ast.string_type_idx { + c.error('range type can not be string', node.cond.position()) + } + if high_type in [ast.int_type, ast.int_literal_type] { + node.val_type = typ + } else { + node.val_type = high_type + } + node.scope.update_var_type(node.val_var, node.val_type) + } else { + sym := c.table.get_final_type_symbol(typ) + if sym.kind == .struct_ { + // iterators + next_fn := sym.find_method('next') or { + c.error('a struct must have a `next()` method to be an iterator', node.cond.position()) + return + } + if !next_fn.return_type.has_flag(.optional) { + c.error('iterator method `next()` must return an optional', node.cond.position()) + } + // the receiver + if next_fn.params.len != 1 { + c.error('iterator method `next()` must have 0 parameters', node.cond.position()) + } + val_type := next_fn.return_type.clear_flag(.optional) + node.cond_type = typ + node.kind = sym.kind + node.val_type = val_type + node.scope.update_var_type(node.val_var, val_type) + } else { + if sym.kind == .map && !(node.key_var.len > 0 && node.val_var.len > 0) { + c.error( + 'declare a key and a value variable when ranging a map: `for key, val in map {`\n' + + 'use `_` if you do not need the variable', node.pos) + } + if node.key_var.len > 0 { + key_type := match sym.kind { + .map { sym.map_info().key_type } + else { ast.int_type } + } + node.key_type = key_type + node.scope.update_var_type(node.key_var, key_type) + } + mut value_type := c.table.value_type(typ) + if value_type == ast.void_type || typ.has_flag(.optional) { + if typ != ast.void_type { + c.error('for in: cannot index `${c.table.type_to_str(typ)}`', node.cond.position()) + } + } + if node.val_is_mut { + value_type = value_type.to_ptr() + match node.cond { + ast.Ident { + if node.cond.obj is ast.Var { + obj := node.cond.obj as ast.Var + if !obj.is_mut { + c.error('`$obj.name` is immutable, it cannot be changed', + node.cond.pos) + } + } + } + ast.ArrayInit { + c.error('array literal is immutable, it cannot be changed', node.cond.pos) + } + ast.MapInit { + c.error('map literal is immutable, it cannot be changed', node.cond.pos) + } + else {} + } + } + node.cond_type = typ + node.kind = sym.kind + node.val_type = value_type + node.scope.update_var_type(node.val_var, value_type) + } + } + c.check_loop_label(node.label, node.pos) + c.stmts(node.stmts) + c.loop_label = prev_loop_label + c.in_for_count-- +} + +fn (mut c Checker) for_stmt(mut node ast.ForStmt) { + c.in_for_count++ + prev_loop_label := c.loop_label + c.expected_type = ast.bool_type + typ := c.expr(node.cond) + if !node.is_inf && typ.idx() != ast.bool_type_idx && !c.pref.translated { + c.error('non-bool used as for condition', node.pos) + } + if node.cond is ast.InfixExpr { + infix := node.cond + if infix.op == .key_is { + if (infix.left is ast.Ident || infix.left is ast.SelectorExpr) + && infix.right is ast.TypeNode { + is_variable := if mut infix.left is ast.Ident { + infix.left.kind == .variable + } else { + true + } + left_type := c.expr(infix.left) + left_sym := c.table.get_type_symbol(left_type) + if is_variable { + if left_sym.kind in [.sum_type, .interface_] { + c.smartcast(infix.left, infix.left_type, infix.right.typ, mut + node.scope) + } + } + } + } + } + // TODO: update loop var type + // how does this work currenly? + c.check_loop_label(node.label, node.pos) + c.stmts(node.stmts) + c.loop_label = prev_loop_label + c.in_for_count-- +} + +fn (mut c Checker) global_decl(mut node ast.GlobalDecl) { + for mut field in node.fields { + c.check_valid_snake_case(field.name, 'global name', field.pos) + if field.name in c.global_names { + c.error('duplicate global `$field.name`', field.pos) + } + sym := c.table.get_type_symbol(field.typ) + if sym.kind == .placeholder { + c.error('unknown type `$sym.name`', field.typ_pos) + } + if field.has_expr { + field.typ = c.expr(field.expr) + mut v := c.file.global_scope.find_global(field.name) or { + panic('internal compiler error - could not find global in scope') + } + v.typ = c.table.mktyp(field.typ) + } + c.global_names << field.name + } +} + +fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { + ret_type := c.call_expr(mut node.call_expr) + if node.call_expr.or_block.kind != .absent { + c.error('optional handling cannot be done in `go` call. Do it when calling `.wait()`', + node.call_expr.or_block.pos) + } + // Make sure there are no mutable arguments + for arg in node.call_expr.args { + if arg.is_mut && !arg.typ.is_ptr() { + c.error('function in `go` statement cannot contain mutable non-reference arguments', + arg.expr.position()) + } + } + if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr() + && !node.call_expr.left_type.is_ptr() { + c.error('method in `go` statement cannot have non-reference mutable receiver', + node.call_expr.left.position()) + } + return c.table.find_or_register_thread(ret_type) +} + +fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { + if stmt.is_goto { + c.warn('inline assembly goto is not supported, it will most likely not work', + stmt.pos) + } + if c.pref.backend.is_js() { + c.error('inline assembly is not supported in the js backend', stmt.pos) + } + if c.pref.backend == .c && c.pref.ccompiler_type == .msvc { + c.error('msvc compiler does not support inline assembly', stmt.pos) + } + mut aliases := c.asm_ios(stmt.output, mut stmt.scope, true) + aliases2 := c.asm_ios(stmt.input, mut stmt.scope, false) + aliases << aliases2 + for template in stmt.templates { + if template.is_directive { + /* + align n[,value] + .skip n[,value] + .space n[,value] + .byte value1[,...] + .word value1[,...] + .short value1[,...] + .int value1[,...] + .long value1[,...] + .quad immediate_value1[,...] + .globl symbol + .global symbol + .section section + .text + .data + .bss + .fill repeat[,size[,value]] + .org n + .previous + .string string[,...] + .asciz string[,...] + .ascii string[,...] + */ + if template.name !in ['skip', 'space', 'byte', 'word', 'short', 'int', 'long', 'quad', + 'globl', 'global', 'section', 'text', 'data', 'bss', 'fill', 'org', 'previous', + 'string', 'asciz', 'ascii'] { // all tcc-supported assembler directives + c.error('unknown assembler directive: `$template.name`', template.pos) + } + } + for mut arg in template.args { + c.asm_arg(arg, stmt, aliases) + } + } + for mut clob in stmt.clobbered { + c.asm_arg(clob.reg, stmt, aliases) + } +} + +fn (mut c Checker) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt, aliases []string) { + match mut arg { + ast.AsmAlias {} + ast.AsmAddressing { + if arg.scale !in [-1, 1, 2, 4, 8] { + c.error('scale must be one of 1, 2, 4, or 8', arg.pos) + } + c.asm_arg(arg.displacement, stmt, aliases) + c.asm_arg(arg.base, stmt, aliases) + c.asm_arg(arg.index, stmt, aliases) + } + ast.BoolLiteral {} // all of these are guarented to be correct. + ast.FloatLiteral {} + ast.CharLiteral {} + ast.IntegerLiteral {} + ast.AsmRegister {} // if the register is not found, the parser will register it as an alias + ast.AsmDisp {} + string {} + } +} + +fn (mut c Checker) asm_ios(ios []ast.AsmIO, mut scope ast.Scope, output bool) []string { + mut aliases := []string{} + for io in ios { + typ := c.expr(io.expr) + if output { + c.fail_if_immutable(io.expr) + } + if io.alias != '' { + aliases << io.alias + if io.alias in scope.objects { + scope.objects[io.alias] = ast.Var{ + name: io.alias + expr: io.expr + is_arg: true + typ: typ + orig_type: typ + pos: io.pos + } + } + } + } + + return aliases +} + +fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { + if c.skip_flags { + return + } + if c.ct_cond_stack.len > 0 { + node.ct_conds = c.ct_cond_stack.clone() + } + if c.pref.backend.is_js() { + if !c.file.path.ends_with('.js.v') { + c.error('hash statements are only allowed in backend specific files such "x.js.v"', + node.pos) + } + if c.mod == 'main' { + c.error('hash statements are not allowed in the main module. Place them in a separate module.', + node.pos) + } + return + } + match node.kind { + 'include' { + mut flag := node.main + if flag.contains('@VROOT') { + // c.note(checker.vroot_is_deprecated_message, node.pos) + vroot := util.resolve_vmodroot(flag.replace('@VROOT', '@VMODROOT'), c.file.path) or { + c.error(err.msg, node.pos) + return + } + node.val = 'include $vroot' + node.main = vroot + flag = vroot + } + if flag.contains('@VEXEROOT') { + vroot := flag.replace('@VEXEROOT', os.dir(pref.vexe_path())) + node.val = 'include $vroot' + node.main = vroot + flag = vroot + } + if flag.contains('@VMODROOT') { + vroot := util.resolve_vmodroot(flag, c.file.path) or { + c.error(err.msg, node.pos) + return + } + node.val = 'include $vroot' + node.main = vroot + flag = vroot + } + if flag.contains('\$env(') { + env := util.resolve_env_value(flag, true) or { + c.error(err.msg, node.pos) + return + } + node.main = env + } + flag_no_comment := flag.all_before('//').trim_space() + if !((flag_no_comment.starts_with('"') && flag_no_comment.ends_with('"')) + || (flag_no_comment.starts_with('<') && flag_no_comment.ends_with('>'))) { + c.error('including C files should use either `"header_file.h"` or `` quoting', + node.pos) + } + } + 'pkgconfig' { + args := if node.main.contains('--') { + node.main.split(' ') + } else { + '--cflags --libs $node.main'.split(' ') + } + mut m := pkgconfig.main(args) or { + c.error(err.msg, node.pos) + return + } + cflags := m.run() or { + c.error(err.msg, node.pos) + return + } + c.table.parse_cflag(cflags, c.mod, c.pref.compile_defines_all) or { + c.error(err.msg, node.pos) + return + } + } + 'flag' { + // #flag linux -lm + mut flag := node.main + if flag.contains('@VROOT') { + // c.note(checker.vroot_is_deprecated_message, node.pos) + flag = util.resolve_vmodroot(flag.replace('@VROOT', '@VMODROOT'), c.file.path) or { + c.error(err.msg, node.pos) + return + } + } + if flag.contains('@VEXEROOT') { + // expand `@VEXEROOT` to its absolute path + flag = flag.replace('@VEXEROOT', os.dir(pref.vexe_path())) + } + if flag.contains('@VMODROOT') { + flag = util.resolve_vmodroot(flag, c.file.path) or { + c.error(err.msg, node.pos) + return + } + } + if flag.contains('\$env(') { + flag = util.resolve_env_value(flag, true) or { + c.error(err.msg, node.pos) + return + } + } + for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] { + if flag.contains(deprecated) { + if !flag.contains('@VMODROOT') { + c.error('$deprecated had been deprecated, use @VMODROOT instead.', + node.pos) + } + } + } + // println('adding flag "$flag"') + c.table.parse_cflag(flag, c.mod, c.pref.compile_defines_all) or { + c.error(err.msg, node.pos) + } + } + else { + if node.kind != 'define' { + c.error('expected `#define`, `#flag`, `#include` or `#pkgconfig` not $node.val', + node.pos) + } + } + } +} + +fn (mut c Checker) import_stmt(node ast.Import) { + c.check_valid_snake_case(node.alias, 'module alias', node.pos) + for sym in node.syms { + name := '${node.mod}.$sym.name' + if sym.name[0].is_capital() { + if type_sym := c.table.find_type(name) { + if type_sym.kind != .placeholder { + if !type_sym.is_public { + c.error('module `$node.mod` type `$sym.name` is private', sym.pos) + } + continue + } + } + c.error('module `$node.mod` has no type `$sym.name`', sym.pos) + continue + } + if func := c.table.find_fn(name) { + if !func.is_pub { + c.error('module `$node.mod` function `${sym.name}()` is private', sym.pos) + } + continue + } + if _ := c.file.global_scope.find_const(name) { + continue + } + c.error('module `$node.mod` has no constant or function `$sym.name`', sym.pos) + } +} + +fn (mut c Checker) stmts(stmts []ast.Stmt) { + mut unreachable := token.Position{ + line_nr: -1 + } + c.expected_type = ast.void_type + for stmt in stmts { + if c.scope_returns { + if unreachable.line_nr == -1 { + unreachable = stmt.pos + } + } + c.stmt(stmt) + } + if unreachable.line_nr >= 0 { + c.error('unreachable code', unreachable) + } + c.find_unreachable_statements_after_noreturn_calls(stmts) + c.scope_returns = false + c.expected_type = ast.void_type +} + +pub fn (mut c Checker) find_unreachable_statements_after_noreturn_calls(stmts []ast.Stmt) { + mut prev_stmt_was_noreturn_call := false + for stmt in stmts { + match stmt { + ast.ExprStmt { + if stmt.expr is ast.CallExpr { + if prev_stmt_was_noreturn_call { + c.error('unreachable code after a [noreturn] call', stmt.pos) + return + } + prev_stmt_was_noreturn_call = stmt.expr.is_noreturn + } + } + else { + prev_stmt_was_noreturn_call = false + } + } + } +} + +pub fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { + if typ.has_flag(.generic) { + if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + { + return t_typ + } + } + return typ +} + +// TODO node must be mut +pub fn (mut c Checker) expr(node ast.Expr) ast.Type { + c.expr_level++ + defer { + c.expr_level-- + } + // c.expr_level set to 150 so that stack overflow does not occur on windows + if c.expr_level > 150 { + c.error('checker: too many expr levels: $c.expr_level ', node.position()) + return ast.void_type + } + match mut node { + ast.NodeError {} + ast.EmptyExpr { + c.error('checker.expr(): unhandled EmptyExpr', token.Position{}) + } + ast.CTempVar { + return node.typ + } + ast.AnonFn { + return c.anon_fn(mut node) + } + ast.ArrayDecompose { + typ := c.expr(node.expr) + type_sym := c.table.get_type_symbol(typ) + if type_sym.kind != .array { + c.error('decomposition can only be used on arrays', node.expr.position()) + return ast.void_type + } + array_info := type_sym.info as ast.Array + elem_type := array_info.elem_type.set_flag(.variadic) + node.expr_type = typ + node.arg_type = elem_type + return elem_type + } + ast.ArrayInit { + return c.array_init(mut node) + } + ast.AsCast { + node.expr_type = c.expr(node.expr) + expr_type_sym := c.table.get_type_symbol(node.expr_type) + type_sym := c.table.get_type_symbol(node.typ) + if expr_type_sym.kind == .sum_type { + c.ensure_type_exists(node.typ, node.pos) or {} + if !c.table.sumtype_has_variant(node.expr_type, node.typ) { + c.error('cannot cast `$expr_type_sym.name` to `$type_sym.name`', node.pos) + } + } else if expr_type_sym.kind == .interface_ && type_sym.kind == .interface_ { + c.ensure_type_exists(node.typ, node.pos) or {} + } else if node.expr_type != node.typ { + mut s := 'cannot cast non-sum type `$expr_type_sym.name` using `as`' + if type_sym.kind == .sum_type { + s += ' - use e.g. `${type_sym.name}(some_expr)` instead.' + } + c.error(s, node.pos) + } + return node.typ + } + ast.Assoc { + v := node.scope.find_var(node.var_name) or { panic(err) } + for i, _ in node.fields { + c.expr(node.exprs[i]) + } + node.typ = v.typ + return v.typ + } + ast.BoolLiteral { + return ast.bool_type + } + ast.CastExpr { + return c.cast_expr(mut node) + } + ast.CallExpr { + mut ret_type := c.call_expr(mut node) + if !ret_type.has_flag(.optional) { + if node.or_block.kind == .block { + c.error('unexpected `or` block, the function `$node.name` does not return an optional', + node.or_block.pos) + } else if node.or_block.kind == .propagate { + c.error('unexpected `?`, the function `$node.name` does not return an optional', + node.or_block.pos) + } + } + if ret_type.has_flag(.optional) && node.or_block.kind != .absent { + ret_type = ret_type.clear_flag(.optional) + } + return ret_type + } + ast.ChanInit { + return c.chan_init(mut node) + } + ast.CharLiteral { + // return int_literal, not rune, so that we can do "bytes << `A`" without a cast etc + // return ast.int_literal_type + return ast.rune_type + // return ast.byte_type + } + ast.Comment { + return ast.void_type + } + ast.AtExpr { + return c.at_expr(mut node) + } + ast.ComptimeCall { + return c.comptime_call(mut node) + } + ast.ComptimeSelector { + node.left_type = c.unwrap_generic(c.expr(node.left)) + expr_type := c.unwrap_generic(c.expr(node.field_expr)) + expr_sym := c.table.get_type_symbol(expr_type) + if expr_type != ast.string_type { + c.error('expected `string` instead of `$expr_sym.name` (e.g. `field.name`)', + node.field_expr.position()) + } + if node.field_expr is ast.SelectorExpr { + left_pos := node.field_expr.expr.position() + if c.comptime_fields_type.len == 0 { + c.error('compile time field access can only be used when iterating over `T.fields`', + left_pos) + } + expr_name := node.field_expr.expr.str() + if expr_name in c.comptime_fields_type { + return c.comptime_fields_type[expr_name] + } + c.error('unknown `\$for` variable `$expr_name`', left_pos) + } else { + c.error('expected selector expression e.g. `$(field.name)`', node.field_expr.position()) + } + return ast.void_type + } + ast.ConcatExpr { + return c.concat_expr(mut node) + } + ast.DumpExpr { + node.expr_type = c.expr(node.expr) + if node.expr_type.idx() == ast.void_type_idx { + c.error('dump expression can not be void', node.expr.position()) + return ast.void_type + } + tsym := c.table.get_type_symbol(node.expr_type) + c.table.dumps[int(node.expr_type)] = tsym.cname + node.cname = tsym.cname + return node.expr_type + } + ast.EnumVal { + return c.enum_val(mut node) + } + ast.FloatLiteral { + return ast.float_literal_type + } + ast.GoExpr { + return c.go_expr(mut node) + } + ast.Ident { + // c.checked_ident = node.name + res := c.ident(mut node) + // c.checked_ident = '' + return res + } + ast.IfExpr { + return c.if_expr(mut node) + } + ast.IfGuardExpr { + node.expr_type = c.expr(node.expr) + if !node.expr_type.has_flag(.optional) { + mut no_opt := true + match mut node.expr { + ast.IndexExpr { + no_opt = false + node.expr_type = node.expr_type.set_flag(.optional) + node.expr.is_option = true + } + ast.PrefixExpr { + if node.expr.op == .arrow { + no_opt = false + node.expr_type = node.expr_type.set_flag(.optional) + node.expr.is_option = true + } + } + else {} + } + if no_opt { + c.error('expression should return an option', node.expr.position()) + } + } + return ast.bool_type + } + ast.IndexExpr { + return c.index_expr(mut node) + } + ast.InfixExpr { + return c.infix_expr(mut node) + } + ast.IntegerLiteral { + return c.int_lit(mut node) + } + ast.LockExpr { + return c.lock_expr(mut node) + } + ast.MapInit { + return c.map_init(mut node) + } + ast.MatchExpr { + return c.match_expr(mut node) + } + ast.PostfixExpr { + return c.postfix_expr(mut node) + } + ast.PrefixExpr { + return c.prefix_expr(mut node) + } + ast.None { + return ast.none_type + } + ast.OrExpr { + // never happens + return ast.void_type + } + // ast.OrExpr2 { + // return node.typ + // } + ast.ParExpr { + if node.expr is ast.ParExpr { + c.warn('redundant parentheses are used', node.pos) + } + return c.expr(node.expr) + } + ast.RangeExpr { + // never happens + return ast.void_type + } + ast.SelectExpr { + return c.select_expr(mut node) + } + ast.SelectorExpr { + return c.selector_expr(mut node) + } + ast.SizeOf { + if !node.is_type { + node.typ = c.expr(node.expr) + } + return ast.u32_type + } + ast.IsRefType { + if !node.is_type { + node.typ = c.expr(node.expr) + } + return ast.bool_type + } + ast.OffsetOf { + return c.offset_of(node) + } + ast.SqlExpr { + return c.sql_expr(mut node) + } + ast.StringLiteral { + if node.language == .c { + // string literal starts with "c": `C.printf(c'hello')` + return ast.byte_type.set_nr_muls(1) + } + return c.string_lit(mut node) + } + ast.StringInterLiteral { + return c.string_inter_lit(mut node) + } + ast.StructInit { + if node.unresolved { + return c.expr(ast.resolve_init(node, c.unwrap_generic(node.typ), c.table)) + } + return c.struct_init(mut node) + } + ast.TypeNode { + return node.typ + } + ast.TypeOf { + node.expr_type = c.expr(node.expr) + return ast.string_type + } + ast.UnsafeExpr { + return c.unsafe_expr(mut node) + } + ast.Likely { + ltype := c.expr(node.expr) + if !c.check_types(ltype, ast.bool_type) { + ltype_sym := c.table.get_type_symbol(ltype) + lname := if node.is_likely { '_likely_' } else { '_unlikely_' } + c.error('`${lname}()` expects a boolean expression, instead it got `$ltype_sym.name`', + node.pos) + } + return ast.bool_type + } + } + return ast.void_type +} + +// pub fn (mut c Checker) asm_reg(mut node ast.AsmRegister) ast.Type { +// name := node.name + +// for bit_size, array in ast.x86_no_number_register_list { +// if name in array { +// return c.table.bitsize_to_type(bit_size) +// } +// } +// for bit_size, array in ast.x86_with_number_register_list { +// if name in array { +// return c.table.bitsize_to_type(bit_size) +// } +// } +// c.error('invalid register name: `$name`', node.pos) +// return ast.void_type +// } + +pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { + node.expr_type = c.expr(node.expr) // type to be casted + from_type_sym := c.table.get_type_symbol(node.expr_type) + to_type_sym := c.table.get_type_symbol(node.typ) // type to be used as cast + if to_type_sym.language != .c { + c.ensure_type_exists(node.typ, node.pos) or {} + } + if from_type_sym.kind == .byte && node.expr_type.is_ptr() && to_type_sym.kind == .string + && !node.typ.is_ptr() { + c.error('to convert a C string buffer pointer to a V string, use x.vstring() instead of string(x)', + node.pos) + } + if node.expr_type == ast.void_type { + c.error('expression does not return a value so it cannot be cast', node.expr.position()) + } + if node.expr_type == ast.byte_type && to_type_sym.kind == .string { + c.error('can not cast type `byte` to string, use `${node.expr.str()}.str()` instead.', + node.pos) + } + if to_type_sym.kind == .sum_type { + if node.expr_type in [ast.int_literal_type, ast.float_literal_type] { + node.expr_type = c.promote_num(node.expr_type, if node.expr_type == ast.int_literal_type { + ast.int_type + } else { + ast.f64_type + }) + } + if !c.table.sumtype_has_variant(node.typ, node.expr_type) && !node.typ.has_flag(.optional) { + c.error('cannot cast `$from_type_sym.name` to `$to_type_sym.name`', node.pos) + } + } else if mut to_type_sym.info is ast.Alias { + if !c.check_types(node.expr_type, to_type_sym.info.parent_type) { + parent_type_sym := c.table.get_type_symbol(to_type_sym.info.parent_type) + c.error('cannot convert type `$from_type_sym.name` to `$to_type_sym.name` (alias to `$parent_type_sym.name`)', + node.pos) + } + } else if node.typ == ast.string_type + && (from_type_sym.kind in [.int_literal, .int, .byte, .byteptr, .bool] + || (from_type_sym.kind == .array && from_type_sym.name == 'array_byte')) { + type_name := c.table.type_to_str(node.expr_type) + c.error('cannot cast type `$type_name` to string, use `x.str()` instead', node.pos) + } else if node.expr_type == ast.string_type { + if to_type_sym.kind != .alias { + mut error_msg := 'cannot cast a string' + if mut node.expr is ast.StringLiteral { + if node.expr.val.len == 1 { + error_msg += ", for denoting characters use `$node.expr.val` instead of '$node.expr.val'" + } + } + c.error(error_msg, node.pos) + } + } else if to_type_sym.kind == .byte && node.expr_type != ast.voidptr_type + && from_type_sym.kind != .enum_ && !node.expr_type.is_int() && !node.expr_type.is_float() + && node.expr_type != ast.bool_type && !node.expr_type.is_ptr() { + type_name := c.table.type_to_str(node.expr_type) + c.error('cannot cast type `$type_name` to `byte`', node.pos) + } else if to_type_sym.kind == .struct_ && !node.typ.is_ptr() + && !(to_type_sym.info as ast.Struct).is_typedef { + // For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard + if from_type_sym.kind == .struct_ && !node.expr_type.is_ptr() { + c.warn('casting to struct is deprecated, use e.g. `Struct{...expr}` instead', + node.pos) + from_type_info := from_type_sym.info as ast.Struct + to_type_info := to_type_sym.info as ast.Struct + if !c.check_struct_signature(from_type_info, to_type_info) { + c.error('cannot convert struct `$from_type_sym.name` to struct `$to_type_sym.name`', + node.pos) + } + } else { + type_name := c.table.type_to_str(node.expr_type) + // dump(node.typ) + // dump(node.expr_type) + // dump(type_name) + // dump(to_type_sym.debug()) + c.error('cannot cast `$type_name` to struct', node.pos) + } + } else if to_type_sym.kind == .interface_ { + if c.type_implements(node.expr_type, node.typ, node.pos) { + if !node.expr_type.is_ptr() && !node.expr_type.is_pointer() + && from_type_sym.kind != .interface_ && !c.inside_unsafe { + c.mark_as_referenced(mut &node.expr, true) + } + } + } else if node.typ == ast.bool_type && node.expr_type != ast.bool_type && !c.inside_unsafe { + c.error('cannot cast to bool - use e.g. `some_int != 0` instead', node.pos) + } else if node.expr_type == ast.none_type && !node.typ.has_flag(.optional) { + type_name := c.table.type_to_str(node.typ) + c.error('cannot cast `none` to `$type_name`', node.pos) + } else if from_type_sym.kind == .struct_ && !node.expr_type.is_ptr() { + if (node.typ.is_ptr() || to_type_sym.kind !in [.sum_type, .interface_]) && !c.is_builtin_mod { + type_name := c.table.type_to_str(node.typ) + c.error('cannot cast struct to `$type_name`', node.pos) + } + } else if node.expr_type.has_flag(.optional) || node.expr_type.has_flag(.variadic) { + // variadic case can happen when arrays are converted into variadic + msg := if node.expr_type.has_flag(.optional) { 'an optional' } else { 'a variadic' } + c.error('cannot type cast $msg', node.pos) + } else if !c.inside_unsafe && node.typ.is_ptr() && node.expr_type.is_ptr() + && node.typ.deref() != ast.char_type && node.expr_type.deref() != ast.char_type { + ft := c.table.type_to_str(node.expr_type) + tt := c.table.type_to_str(node.typ) + c.warn('casting `$ft` to `$tt` is only allowed in `unsafe` code', node.pos) + } else if from_type_sym.kind == .array_fixed && !node.expr_type.is_ptr() { + c.warn('cannot cast a fixed array (use e.g. `&arr[0]` instead)', node.pos) + } + if node.has_arg { + c.expr(node.arg) + } + + // checks on int literal to enum cast if the value represents a value on the enum + if to_type_sym.kind == .enum_ { + if node.expr is ast.IntegerLiteral { + enum_typ_name := c.table.get_type_name(node.typ) + node_val := (node.expr as ast.IntegerLiteral).val.int() + + if enum_decl := c.table.enum_decls[to_type_sym.name] { + mut in_range := false + mut enum_val := 0 + + for enum_field in enum_decl.fields { + // check if the field of the enum value is an integer literal + if enum_field.expr is ast.IntegerLiteral { + enum_val = enum_field.expr.val.int() + } + + if node_val == enum_val { + in_range = true + break + } + + enum_val += 1 + } + + if !in_range { + c.warn('$node_val does not represents a value of enum $enum_typ_name', + node.pos) + } + } + } + } + + node.typname = c.table.get_type_symbol(node.typ).name + + return node.typ +} + +fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { + node.sym = c.table.get_type_symbol(c.unwrap_generic(c.expr(node.left))) + if node.is_env { + env_value := util.resolve_env_value("\$env('$node.args_var')", false) or { + c.error(err.msg, node.env_pos) + return ast.string_type + } + node.env_value = env_value + return ast.string_type + } + if node.is_embed { + c.file.embedded_files << node.embed_file + return c.table.find_type_idx('v.embed_file.EmbedFileData') + } + if node.is_vweb { + // TODO assoc parser bug + pref_ := *c.pref + pref2 := &pref.Preferences{ + ...pref_ + is_vweb: true + } + mut c2 := new_checker(c.table, pref2) + c2.check(node.vweb_tmpl) + mut i := 0 // tmp counter var for skipping first three tmpl vars + for k, _ in c2.file.scope.children[0].objects { + if i < 2 { + // Skip first three because they are tmpl vars see vlib/vweb/tmpl/tmpl.v + i++ + continue + } + if k in c.fn_scope.objects && c.fn_scope.objects[k] is ast.Var { + mut vsc := c.fn_scope.objects[k] as ast.Var + vsc.is_used = true + c.fn_scope.objects[k] = vsc + } + } + c.warnings << c2.warnings + c.errors << c2.errors + c.notices << c2.notices + c.nr_warnings += c2.nr_warnings + c.nr_errors += c2.nr_errors + c.nr_notices += c2.nr_notices + } + if node.method_name == 'html' { + rtyp := c.table.find_type_idx('vweb.Result') + node.result_type = rtyp + return rtyp + } + if node.method_name == 'method' { + for i, arg in node.args { + // check each arg expression + node.args[i].typ = c.expr(arg.expr) + } + // assume string for now + return ast.string_type + } + if node.is_vweb { + return ast.string_type + } + // s.$my_str() + v := node.scope.find_var(node.method_name) or { + c.error('unknown identifier `$node.method_name`', node.method_pos) + return ast.void_type + } + if v.typ != ast.string_type { + s := c.expected_msg(v.typ, ast.string_type) + c.error('invalid string method call: $s', node.method_pos) + return ast.void_type + } + // note: we should use a compile-time evaluation function rather than handle here + // mut variables will not work after init + mut method_name := '' + if v.expr is ast.StringLiteral { + method_name = v.expr.val + } else { + c.error('todo: not a string literal', node.method_pos) + } + f := node.sym.find_method(method_name) or { + c.error('could not find method `$method_name`', node.method_pos) + return ast.void_type + } + // println(f.name + ' ' + c.table.type_to_str(f.return_type)) + node.result_type = f.return_type + return f.return_type +} + +fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { + match node.kind { + .fn_name { + node.val = c.table.cur_fn.name.all_after_last('.') + } + .method_name { + fname := c.table.cur_fn.name.all_after_last('.') + if c.table.cur_fn.is_method { + node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') + + '.' + fname + } else { + node.val = fname + } + } + .mod_name { + node.val = c.table.cur_fn.mod + } + .struct_name { + if c.table.cur_fn.is_method { + node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') + } else { + node.val = '' + } + } + .vexe_path { + node.val = pref.vexe_path() + } + .file_path { + node.val = os.real_path(c.file.path) + } + .line_nr { + node.val = (node.pos.line_nr + 1).str() + } + .column_nr { + node.val = (node.pos.col + 1).str() + } + .vhash { + node.val = version.vhash() + } + .vmod_file { + // cache the vmod content, do not read it many times + if c.vmod_file_content.len == 0 { + mut mcache := vmod.get_cache() + vmod_file_location := mcache.get_by_file(c.file.path) + if vmod_file_location.vmod_file.len == 0 { + c.error('@VMOD_FILE can be used only in projects, that have v.mod file', + node.pos) + } + vmod_content := os.read_file(vmod_file_location.vmod_file) or { '' } + c.vmod_file_content = vmod_content.replace('\r\n', '\n') // normalise EOLs just in case + } + node.val = c.vmod_file_content + } + .vroot_path { + node.val = os.dir(pref.vexe_path()) + } + .vexeroot_path { + node.val = os.dir(pref.vexe_path()) + } + .vmodroot_path { + mut mcache := vmod.get_cache() + vmod_file_location := mcache.get_by_file(c.file.path) + node.val = os.dir(vmod_file_location.vmod_file) + } + .unknown { + c.error('unknown @ identifier: ${node.name}. Available identifiers: $token.valid_at_tokens', + node.pos) + } + } + return ast.string_type +} + +pub fn (mut c Checker) ident(mut node ast.Ident) ast.Type { + // TODO: move this + if c.const_deps.len > 0 { + mut name := node.name + if !name.contains('.') && node.mod != 'builtin' { + name = '${node.mod}.$node.name' + } + if name == c.const_decl { + c.error('cycle in constant `$c.const_decl`', node.pos) + return ast.void_type + } + c.const_deps << name + } + if node.kind == .blank_ident { + if node.tok_kind !in [.assign, .decl_assign] { + c.error('undefined ident: `_` (may only be used in assignments)', node.pos) + } + return ast.void_type + } + // second use + if node.kind in [.constant, .global, .variable] { + info := node.info as ast.IdentVar + // Got a var with type T, return current generic type + return info.typ + } else if node.kind == .function { + info := node.info as ast.IdentFn + return info.typ + } else if node.kind == .unresolved { + // first use + if node.tok_kind == .assign && node.is_mut { + c.error('`mut` not allowed with `=` (use `:=` to declare a variable)', node.pos) + } + if obj := node.scope.find(node.name) { + match mut obj { + ast.GlobalField { + node.kind = .global + node.info = ast.IdentVar{ + typ: obj.typ + } + node.obj = obj + return obj.typ + } + ast.Var { + // incase var was not marked as used yet (vweb tmpl) + // obj.is_used = true + if node.pos.pos < obj.pos.pos { + c.error('undefined variable `$node.name` (used before declaration)', + node.pos) + } + is_sum_type_cast := obj.smartcasts.len != 0 + && !c.prevent_sum_type_unwrapping_once + c.prevent_sum_type_unwrapping_once = false + mut typ := if is_sum_type_cast { obj.smartcasts.last() } else { obj.typ } + if typ == 0 { + if mut obj.expr is ast.Ident { + if obj.expr.kind == .unresolved { + c.error('unresolved variable: `$node.name`', node.pos) + return ast.void_type + } + } + if mut obj.expr is ast.IfGuardExpr { + // new variable from if guard shouldn't have the optional flag for further use + // a temp variable will be generated which unwraps it + if_guard_var_type := c.expr(obj.expr.expr) + typ = if_guard_var_type.clear_flag(.optional) + } else { + typ = c.expr(obj.expr) + } + } + is_optional := typ.has_flag(.optional) + node.kind = .variable + node.info = ast.IdentVar{ + typ: typ + is_optional: is_optional + } + if typ == ast.error_type && c.expected_type == ast.string_type + && !c.using_new_err_struct && !c.inside_selector_expr + && !c.inside_println_arg && !c.file.mod.name.contains('v.') + && !c.is_builtin_mod { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <- TODO: remove; this prevents a failure in the `performance-regressions` CI job + c.warn('string errors are deprecated; use `err.msg` instead', + node.pos) + } + // if typ == ast.t_type { + // sym := c.table.get_type_symbol(c.cur_generic_type) + // println('IDENT T unresolved $node.name typ=$sym.name') + // Got a var with type T, return current generic type + // typ = c.cur_generic_type + // } + // } else { + if !is_sum_type_cast { + obj.typ = typ + } + node.obj = obj + // unwrap optional (`println(x)`) + if is_optional { + return typ.clear_flag(.optional) + } + return typ + } + else {} + } + } + mut name := node.name + // check for imported symbol + if name in c.file.imported_symbols { + name = c.file.imported_symbols[name] + } + // prepend mod to look for fn call or const + else if !name.contains('.') && node.mod != 'builtin' { + name = '${node.mod}.$node.name' + } + if obj := c.file.global_scope.find(name) { + match mut obj { + ast.ConstField { + if !(obj.is_pub || obj.mod == c.mod || c.pref.is_test) { + c.error('constant `$obj.name` is private', node.pos) + } + mut typ := obj.typ + if typ == 0 { + c.inside_const = true + typ = c.expr(obj.expr) + c.inside_const = false + if obj.expr is ast.CallExpr { + if obj.expr.or_block.kind != .absent { + typ = typ.clear_flag(.optional) + } + } + } + node.name = name + node.kind = .constant + node.info = ast.IdentVar{ + typ: typ + } + obj.typ = typ + node.obj = obj + return typ + } + else {} + } + } + // Non-anon-function object (not a call), e.g. `onclick(my_click)` + if func := c.table.find_fn(name) { + fn_type := ast.new_type(c.table.find_or_register_fn_type(node.mod, func, false, + true)) + node.name = name + node.kind = .function + node.info = ast.IdentFn{ + typ: fn_type + } + return fn_type + } + } + if node.language == .c { + if node.name == 'C.NULL' { + return ast.voidptr_type + } + return ast.int_type + } + if c.inside_sql { + if field := c.table.find_field(c.cur_orm_ts, node.name) { + return field.typ + } + } + if node.kind == .unresolved && node.mod != 'builtin' { + // search in the `builtin` idents, for example + // main.compare_f32 may actually be builtin.compare_f32 + saved_mod := node.mod + node.mod = 'builtin' + builtin_type := c.ident(mut node) + if builtin_type != ast.void_type { + return builtin_type + } + node.mod = saved_mod + } + if node.tok_kind == .assign { + c.error('undefined ident: `$node.name` (use `:=` to declare a variable)', node.pos) + } else if node.name == 'errcode' { + c.error('undefined ident: `errcode`; did you mean `err.code`?', node.pos) + } else { + if c.inside_ct_attr { + c.note('`[if $node.name]` is deprecated. Use `[if $node.name?]` instead', + node.pos) + } else { + c.error('undefined ident: `$node.name`', node.pos) + } + } + if c.table.known_type(node.name) { + // e.g. `User` in `json.decode(User, '...')` + return ast.void_type + } + return ast.void_type +} + +pub fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type { + mut mr_types := []ast.Type{} + for expr in node.vals { + mr_types << c.expr(expr) + } + if node.vals.len == 1 { + typ := mr_types[0] + node.return_type = typ + return typ + } else { + typ := c.table.find_or_register_multi_return(mr_types) + ast.new_type(typ) + node.return_type = typ + return typ + } +} + +pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { + node.is_expr = c.expected_type != ast.void_type + node.expected_type = c.expected_type + cond_type := c.expr(node.cond) + // we setting this here rather than at the end of the method + // since it is used in c.match_exprs() it saves checking twice + node.cond_type = c.table.mktyp(cond_type) + c.ensure_type_exists(node.cond_type, node.pos) or { return ast.void_type } + c.check_expr_opt_call(node.cond, cond_type) + cond_type_sym := c.table.get_type_symbol(cond_type) + node.is_sum_type = cond_type_sym.kind in [.interface_, .sum_type] + c.match_exprs(mut node, cond_type_sym) + c.expected_type = cond_type + mut first_iteration := true + mut ret_type := ast.void_type + mut nbranches_with_return := 0 + mut nbranches_without_return := 0 + for branch in node.branches { + c.stmts(branch.stmts) + if node.is_expr { + if branch.stmts.len > 0 { + // ignore last statement - workaround + // currently the last statement in a match branch does not have an + // expected value set, so e.g. IfExpr.is_expr is not set. + // probably any mismatch will be caught by not producing a value instead + for st in branch.stmts[0..branch.stmts.len - 1] { + // must not contain C statements + st.check_c_expr() or { + c.error('`match` expression branch has $err.msg', st.pos) + } + } + } else if ret_type != ast.void_type { + c.error('`match` expression requires an expression as the last statement of every branch', + branch.branch_pos) + } + } + // If the last statement is an expression, return its type + if branch.stmts.len > 0 { + mut stmt := branch.stmts[branch.stmts.len - 1] + match mut stmt { + ast.ExprStmt { + if node.is_expr { + c.expected_type = node.expected_type + } + expr_type := c.expr(stmt.expr) + if first_iteration { + if node.is_expr && !node.expected_type.has_flag(.optional) + && c.table.get_type_symbol(node.expected_type).kind == .sum_type { + ret_type = node.expected_type + } else { + ret_type = expr_type + } + stmt.typ = expr_type + } else if node.is_expr && ret_type != expr_type { + if !c.check_types(ret_type, expr_type) + && !c.check_types(expr_type, ret_type) { + ret_sym := c.table.get_type_symbol(ret_type) + is_noreturn := is_noreturn_callexpr(stmt.expr) + if !(node.is_expr && ret_sym.kind == .sum_type) && !is_noreturn { + c.error('return type mismatch, it should be `$ret_sym.name`', + stmt.expr.position()) + } + } + } + } + else { + if node.is_expr && ret_type != ast.void_type { + c.error('`match` expression requires an expression as the last statement of every branch', + stmt.pos) + } + } + } + } + first_iteration = false + if has_return := c.has_return(branch.stmts) { + if has_return { + nbranches_with_return++ + } else { + nbranches_without_return++ + } + } + } + if nbranches_with_return > 0 { + if nbranches_with_return == node.branches.len { + // an exhaustive match, and all branches returned + c.returns = true + } + if nbranches_without_return > 0 { + // some of the branches did not return + c.returns = false + } + } + // if ret_type != ast.void_type { + // node.is_expr = c.expected_type != ast.void_type + // node.expected_type = c.expected_type + // } + node.return_type = ret_type + cond_var := c.get_base_name(&node.cond) + if cond_var != '' { + mut cond_is_auto_heap := false + for branch in node.branches { + if v := branch.scope.find_var(cond_var) { + if v.is_auto_heap { + cond_is_auto_heap = true + break + } + } + } + if cond_is_auto_heap { + for branch in node.branches { + mut v := branch.scope.find_var(cond_var) or { continue } + v.is_auto_heap = true + } + } + } + return ret_type +} + +fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol) { + // branch_exprs is a histogram of how many times + // an expr was used in the match + mut branch_exprs := map[string]int{} + for branch_i, _ in node.branches { + mut branch := node.branches[branch_i] + mut expr_types := []ast.TypeNode{} + for k, expr in branch.exprs { + mut key := '' + if expr is ast.RangeExpr { + mut low := i64(0) + mut high := i64(0) + c.expected_type = node.expected_type + low_expr := expr.low + high_expr := expr.high + if low_expr is ast.IntegerLiteral { + if high_expr is ast.IntegerLiteral { + low = low_expr.val.i64() + high = high_expr.val.i64() + } else { + c.error('mismatched range types', low_expr.pos) + } + } else if low_expr is ast.CharLiteral { + if high_expr is ast.CharLiteral { + low = low_expr.val[0] + high = high_expr.val[0] + } else { + c.error('mismatched range types', low_expr.pos) + } + } else { + typ := c.table.type_to_str(c.expr(expr.low)) + c.error('cannot use type `$typ` in match range', branch.pos) + } + high_low_cutoff := 1000 + if high - low > high_low_cutoff { + c.warn('more than $high_low_cutoff possibilities ($low ... $high) in match range', + branch.pos) + } + for i in low .. high + 1 { + key = i.str() + val := if key in branch_exprs { branch_exprs[key] } else { 0 } + if val == 1 { + c.error('match case `$key` is handled more than once', branch.pos) + } + branch_exprs[key] = val + 1 + } + continue + } + match expr { + ast.TypeNode { + key = c.table.type_to_str(expr.typ) + expr_types << expr + } + ast.EnumVal { + key = expr.val + } + else { + key = expr.str() + } + } + val := if key in branch_exprs { branch_exprs[key] } else { 0 } + if val == 1 { + c.error('match case `$key` is handled more than once', branch.pos) + } + c.expected_type = node.cond_type + expr_type := c.expr(expr) + if expr_type.idx() == 0 { + // parser failed, stop checking + return + } + expr_type_sym := c.table.get_type_symbol(expr_type) + if cond_type_sym.kind == .interface_ { + // TODO + // This generates a memory issue with TCC + // Needs to be checked later when TCC errors are fixed + // Current solution is to move expr.position() to its own statement + // c.type_implements(expr_type, c.expected_type, expr.position()) + expr_pos := expr.position() + if c.type_implements(expr_type, c.expected_type, expr_pos) { + if !expr_type.is_ptr() && !expr_type.is_pointer() && !c.inside_unsafe { + if expr_type_sym.kind != .interface_ { + c.mark_as_referenced(mut &branch.exprs[k], true) + } + } + } + } else if mut cond_type_sym.info is ast.SumType { + if expr_type !in cond_type_sym.info.variants { + expr_str := c.table.type_to_str(expr_type) + expect_str := c.table.type_to_str(node.cond_type) + c.error('`$expect_str` has no variant `$expr_str`', expr.position()) + } + } else if cond_type_sym.info is ast.Alias && expr_type_sym.info is ast.Struct { + expr_str := c.table.type_to_str(expr_type) + expect_str := c.table.type_to_str(node.cond_type) + c.error('cannot match alias type `$expect_str` with `$expr_str`', expr.position()) + } else if !c.check_types(expr_type, node.cond_type) { + expr_str := c.table.type_to_str(expr_type) + expect_str := c.table.type_to_str(node.cond_type) + c.error('cannot match `$expect_str` with `$expr_str`', expr.position()) + } + branch_exprs[key] = val + 1 + } + // when match is type matching, then register smart cast for every branch + if expr_types.len > 0 { + if cond_type_sym.kind in [.sum_type, .interface_] { + mut expr_type := ast.Type(0) + if expr_types.len > 1 { + mut agg_name := strings.new_builder(20) + mut agg_cname := strings.new_builder(20) + agg_name.write_string('(') + for i, expr in expr_types { + if i > 0 { + agg_name.write_string(' | ') + agg_cname.write_string('___') + } + type_str := c.table.type_to_str(expr.typ) + name := if c.is_builtin_mod { type_str } else { '${c.mod}.$type_str' } + agg_name.write_string(name) + agg_cname.write_string(util.no_dots(name)) + } + agg_name.write_string(')') + name := agg_name.str() + existing_idx := c.table.type_idxs[name] + if existing_idx > 0 { + expr_type = existing_idx + } else { + expr_type = c.table.register_type_symbol(ast.TypeSymbol{ + name: name + cname: agg_cname.str() + kind: .aggregate + mod: c.mod + info: ast.Aggregate{ + types: expr_types.map(it.typ) + } + }) + } + } else { + expr_type = expr_types[0].typ + } + c.smartcast(node.cond, node.cond_type, expr_type, mut branch.scope) + } + } + } + // check that expressions are exhaustive + // this is achieved either by putting an else + // or, when the match is on a sum type or an enum + // by listing all variants or values + mut is_exhaustive := true + mut unhandled := []string{} + if node.cond_type == ast.bool_type { + variants := ['true', 'false'] + for v in variants { + if v !in branch_exprs { + is_exhaustive = false + unhandled << '`$v`' + } + } + } else { + match mut cond_type_sym.info { + ast.SumType { + for v in cond_type_sym.info.variants { + v_str := c.table.type_to_str(v) + if v_str !in branch_exprs { + is_exhaustive = false + unhandled << '`$v_str`' + } + } + } + // + ast.Enum { + for v in cond_type_sym.info.vals { + if v !in branch_exprs { + is_exhaustive = false + unhandled << '`.$v`' + } + } + } + else { + is_exhaustive = false + } + } + } + mut else_branch := node.branches[node.branches.len - 1] + mut has_else := else_branch.is_else + if !has_else { + for i, branch in node.branches { + if branch.is_else && i != node.branches.len - 1 { + c.error('`else` must be the last branch of `match`', branch.pos) + else_branch = branch + has_else = true + } + } + } + if is_exhaustive { + if has_else { + c.error('match expression is exhaustive, `else` is unnecessary', else_branch.pos) + } + return + } + if has_else { + return + } + mut err_details := 'match must be exhaustive' + if unhandled.len > 0 { + err_details += ' (add match branches for: ' + if unhandled.len < c.match_exhaustive_cutoff_limit { + err_details += unhandled.join(', ') + } else { + remaining := unhandled.len - c.match_exhaustive_cutoff_limit + err_details += unhandled[0..c.match_exhaustive_cutoff_limit].join(', ') + if remaining > 0 { + err_details += ', and $remaining others ...' + } + } + err_details += ' or `else {}` at the end)' + } else { + err_details += ' (add `else {}` at the end)' + } + c.error(err_details, node.pos) +} + +// smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope +fn (c Checker) smartcast(expr ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope) { + sym := c.table.get_type_symbol(cur_type) + to_type := if sym.kind == .interface_ { to_type_.to_ptr() } else { to_type_ } + match expr { + ast.SelectorExpr { + mut is_mut := false + mut smartcasts := []ast.Type{} + expr_sym := c.table.get_type_symbol(expr.expr_type) + mut orig_type := 0 + if field := c.table.find_field(expr_sym, expr.field_name) { + if field.is_mut { + if root_ident := expr.root_ident() { + if v := scope.find_var(root_ident.name) { + is_mut = v.is_mut + } + } + } + if orig_type == 0 { + orig_type = field.typ + } + } + if field := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) { + smartcasts << field.smartcasts + } + // smartcast either if the value is immutable or if the mut argument is explicitly given + if !is_mut || expr.is_mut { + smartcasts << to_type + scope.register_struct_field(expr.expr.str(), ast.ScopeStructField{ + struct_type: expr.expr_type + name: expr.field_name + typ: cur_type + smartcasts: smartcasts + pos: expr.pos + orig_type: orig_type + }) + } + } + ast.Ident { + mut is_mut := false + mut smartcasts := []ast.Type{} + mut is_already_casted := false + mut orig_type := 0 + if mut expr.obj is ast.Var { + is_mut = expr.obj.is_mut + smartcasts << expr.obj.smartcasts + is_already_casted = expr.obj.pos.pos == expr.pos.pos + if orig_type == 0 { + orig_type = expr.obj.typ + } + } + // smartcast either if the value is immutable or if the mut argument is explicitly given + if (!is_mut || expr.is_mut) && !is_already_casted { + smartcasts << to_type + scope.register(ast.Var{ + name: expr.name + typ: cur_type + pos: expr.pos + is_used: true + is_mut: expr.is_mut + smartcasts: smartcasts + orig_type: orig_type + }) + } + } + else {} + } +} + +pub fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type { + node.is_expr = c.expected_type != ast.void_type + node.expected_type = c.expected_type + for branch in node.branches { + c.stmt(branch.stmt) + match branch.stmt { + ast.ExprStmt { + if branch.is_timeout { + if !branch.stmt.typ.is_int() { + tsym := c.table.get_type_symbol(branch.stmt.typ) + c.error('invalid type `$tsym.name` for timeout - expected integer number of nanoseconds aka `time.Duration`', + branch.stmt.pos) + } + } else { + if branch.stmt.expr is ast.InfixExpr { + if branch.stmt.expr.left !is ast.Ident + && branch.stmt.expr.left !is ast.SelectorExpr + && branch.stmt.expr.left !is ast.IndexExpr { + c.error('channel in `select` key must be predefined', branch.stmt.expr.left.position()) + } + } else { + c.error('invalid expression for `select` key', branch.stmt.expr.position()) + } + } + } + ast.AssignStmt { + expr := branch.stmt.right[0] + match expr { + ast.PrefixExpr { + if expr.right !is ast.Ident && expr.right !is ast.SelectorExpr + && expr.right !is ast.IndexExpr { + c.error('channel in `select` key must be predefined', expr.right.position()) + } + if expr.or_block.kind != .absent { + err_prefix := if expr.or_block.kind == .block { + 'or block' + } else { + 'error propagation' + } + c.error('$err_prefix not allowed in `select` key', expr.or_block.pos) + } + } + else { + c.error('`<-` receive expression expected', branch.stmt.right[0].position()) + } + } + } + else { + if !branch.is_else { + c.error('receive or send statement expected as `select` key', branch.stmt.pos) + } + } + } + c.stmts(branch.stmts) + } + return ast.bool_type +} + +pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type { + if c.rlocked_names.len > 0 || c.locked_names.len > 0 { + c.error('nested `lock`/`rlock` not allowed', node.pos) + } + for i in 0 .. node.lockeds.len { + e_typ := c.expr(node.lockeds[i]) + id_name := node.lockeds[i].str() + if !e_typ.has_flag(.shared_f) { + obj_type := if node.lockeds[i] is ast.Ident { 'variable' } else { 'struct element' } + c.error('`$id_name` must be declared as `shared` $obj_type to be locked', + node.lockeds[i].position()) + } + if id_name in c.locked_names { + c.error('`$id_name` is already locked', node.lockeds[i].position()) + } else if id_name in c.rlocked_names { + c.error('`$id_name` is already read-locked', node.lockeds[i].position()) + } + if node.is_rlock[i] { + c.rlocked_names << id_name + } else { + c.locked_names << id_name + } + } + c.stmts(node.stmts) + c.rlocked_names = [] + c.locked_names = [] + // handle `x := rlock a { a.getval() }` + mut ret_type := ast.void_type + if node.stmts.len > 0 { + last_stmt := node.stmts[node.stmts.len - 1] + if last_stmt is ast.ExprStmt { + ret_type = last_stmt.typ + } + } + if ret_type != ast.void_type { + node.is_expr = true + } + node.typ = ret_type + return ret_type +} + +pub fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) ast.Type { + c.inside_unsafe = true + t := c.expr(node.expr) + c.inside_unsafe = false + return t +} + +fn (mut c Checker) smartcast_if_conds(node ast.Expr, mut scope ast.Scope) { + if node is ast.InfixExpr { + if node.op == .and { + c.smartcast_if_conds(node.left, mut scope) + c.smartcast_if_conds(node.right, mut scope) + } else if node.op == .key_is { + right_expr := node.right + mut right_type := match right_expr { + ast.TypeNode { + right_expr.typ + } + ast.None { + ast.none_type_idx + } + else { + c.error('invalid type `$right_expr`', right_expr.position()) + ast.Type(0) + } + } + right_type = c.unwrap_generic(right_type) + if right_type != ast.Type(0) { + left_sym := c.table.get_type_symbol(node.left_type) + right_sym := c.table.get_type_symbol(right_type) + expr_type := c.unwrap_generic(c.expr(node.left)) + if left_sym.kind == .interface_ { + if right_sym.kind != .interface_ { + c.type_implements(right_type, expr_type, node.pos) + } else { + return + } + } else if !c.check_types(right_type, expr_type) { + expect_str := c.table.type_to_str(right_type) + expr_str := c.table.type_to_str(expr_type) + c.error('cannot use type `$expect_str` as type `$expr_str`', node.pos) + } + if (node.left is ast.Ident || node.left is ast.SelectorExpr) + && node.right is ast.TypeNode { + is_variable := if mut node.left is ast.Ident { + node.left.kind == .variable + } else { + true + } + if is_variable { + if left_sym.kind in [.interface_, .sum_type] { + c.smartcast(node.left, node.left_type, right_type, mut scope) + } + } + } + } + } + } +} + +pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { + if_kind := if node.is_comptime { '\$if' } else { 'if' } + mut node_is_expr := false + if node.branches.len > 0 && node.has_else { + stmts := node.branches[0].stmts + if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt + && (stmts[stmts.len - 1] as ast.ExprStmt).typ != ast.void_type { + node_is_expr = true + } + } + if c.expected_type == ast.void_type && node_is_expr { + c.expected_type = c.expected_or_type + } + expr_required := c.expected_type != ast.void_type + former_expected_type := c.expected_type + node.typ = ast.void_type + mut nbranches_with_return := 0 + mut nbranches_without_return := 0 + mut should_skip := false // Whether the current branch should be skipped + mut found_branch := false // Whether a matching branch was found- skip the rest + mut is_comptime_type_is_expr := false // if `$if T is string` + for i in 0 .. node.branches.len { + mut branch := node.branches[i] + if branch.cond is ast.ParExpr { + c.error('unnecessary `()` in `$if_kind` condition, use `$if_kind expr {` instead of `$if_kind (expr) {`.', + branch.pos) + } + if !node.has_else || i < node.branches.len - 1 { + if node.is_comptime { + should_skip = c.comp_if_branch(branch.cond, branch.pos) + node.branches[i].pkg_exist = !should_skip + } else { + // check condition type is boolean + c.expected_type = ast.bool_type + cond_typ := c.expr(branch.cond) + if (cond_typ.idx() != ast.bool_type_idx || cond_typ.has_flag(.optional)) + && !c.pref.translated { + c.error('non-bool type `${c.table.type_to_str(cond_typ)}` used as if condition', + branch.cond.position()) + } + } + } + if node.is_comptime { // Skip checking if needed + // smartcast field type on comptime if + mut comptime_field_name := '' + if branch.cond is ast.InfixExpr { + if branch.cond.op == .key_is { + if branch.cond.right !is ast.TypeNode { + c.error('invalid `\$if` condition: expected a type', branch.cond.right.position()) + return 0 + } + got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ) + sym := c.table.get_type_symbol(got_type) + if sym.kind == .placeholder || got_type.has_flag(.generic) { + c.error('unknown type `$sym.name`', branch.cond.right.position()) + } + left := branch.cond.left + if left is ast.SelectorExpr { + comptime_field_name = left.expr.str() + c.comptime_fields_type[comptime_field_name] = got_type + is_comptime_type_is_expr = true + } else if branch.cond.right is ast.TypeNode && left is ast.TypeNode + && sym.kind == .interface_ { + // is interface + checked_type := c.unwrap_generic(left.typ) + should_skip = !c.table.does_type_implement_interface(checked_type, + got_type) + } else if left is ast.TypeNode { + is_comptime_type_is_expr = true + left_type := c.unwrap_generic(left.typ) + if left_type != got_type { + should_skip = true + } + } + } + } + cur_skip_flags := c.skip_flags + if found_branch { + c.skip_flags = true + } else if should_skip { + c.skip_flags = true + should_skip = false // Reset the value of `should_skip` for the next branch + } else if !is_comptime_type_is_expr { + found_branch = true // If a branch wasn't skipped, the rest must be + } + if c.fn_level == 0 && c.pref.output_cross_c { + // do not skip any of the branches for top level `$if OS {` + // statements, in `-os cross` mode + found_branch = false + c.skip_flags = false + c.ct_cond_stack << branch.cond + } + if !c.skip_flags { + c.stmts(branch.stmts) + } else if c.pref.output_cross_c { + mut is_freestanding_block := false + if branch.cond is ast.Ident { + if branch.cond.name == 'freestanding' { + is_freestanding_block = true + } + } + if is_freestanding_block { + branch.stmts = [] + node.branches[i].stmts = [] + } + c.stmts(branch.stmts) + } else if !is_comptime_type_is_expr { + node.branches[i].stmts = [] + } + if comptime_field_name.len > 0 { + c.comptime_fields_type.delete(comptime_field_name) + } + c.skip_flags = cur_skip_flags + if c.fn_level == 0 && c.pref.output_cross_c && c.ct_cond_stack.len > 0 { + c.ct_cond_stack.delete_last() + } + } else { + // smartcast sumtypes and interfaces when using `is` + c.smartcast_if_conds(branch.cond, mut branch.scope) + c.stmts(branch.stmts) + } + if expr_required { + if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt { + mut last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt + c.expected_type = former_expected_type + if c.expected_type.has_flag(.optional) { + if node.typ == ast.void_type { + node.is_expr = true + node.typ = c.expected_type + } + } + if c.expected_type.has_flag(.generic) { + if node.typ == ast.void_type { + node.is_expr = true + node.typ = c.unwrap_generic(c.expected_type) + } + continue + } + last_expr.typ = c.expr(last_expr.expr) + if !c.check_types(last_expr.typ, node.typ) { + if node.typ == ast.void_type { + // first branch of if expression + node.is_expr = true + node.typ = last_expr.typ + continue + } else if node.typ in [ast.float_literal_type, ast.int_literal_type] { + if node.typ == ast.int_literal_type { + if last_expr.typ.is_int() || last_expr.typ.is_float() { + node.typ = last_expr.typ + continue + } + } else { // node.typ == float_literal + if last_expr.typ.is_float() { + node.typ = last_expr.typ + continue + } + } + } + if last_expr.typ in [ast.float_literal_type, ast.int_literal_type] { + if last_expr.typ == ast.int_literal_type { + if node.typ.is_int() || node.typ.is_float() { + continue + } + } else { // expr_type == float_literal + if node.typ.is_float() { + continue + } + } + } + if node.is_expr + && c.table.get_type_symbol(former_expected_type).kind == .sum_type { + continue + } + c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(last_expr.typ)}`', + node.pos) + } + } else { + c.error('`$if_kind` expression requires an expression as the last statement of every branch', + branch.pos) + } + for st in branch.stmts { + // must not contain C statements + st.check_c_expr() or { c.error('`if` expression branch has $err.msg', st.pos) } + } + } + // Also check for returns inside a comp.if's statements, even if its contents aren't parsed + if has_return := c.has_return(branch.stmts) { + if has_return { + nbranches_with_return++ + } else { + nbranches_without_return++ + } + } + } + if nbranches_with_return > 0 { + if nbranches_with_return == node.branches.len { + // if/else... where all branches returned + c.returns = true + } + if !node.has_else { + // `if cond { return ... }` means that when cond is false, execution continues + c.returns = false + } + if nbranches_without_return > 0 { + // some of the branches did not return + c.returns = false + } + } + // if only untyped literals were given default to int/f64 + if node.typ == ast.int_literal_type { + node.typ = ast.int_type + } else if node.typ == ast.float_literal_type { + node.typ = ast.f64_type + } + if expr_required && !node.has_else { + d := if node.is_comptime { '$' } else { '' } + c.error('`$if_kind` expression needs `${d}else` clause', node.pos) + } + return node.typ +} + +// comp_if_branch checks the condition of a compile-time `if` branch. It returns `true` +// if that branch's contents should be skipped (targets a different os for example) +fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { + // TODO: better error messages here + match cond { + ast.BoolLiteral { + return !cond.val + } + ast.ParExpr { + return c.comp_if_branch(cond.expr, pos) + } + ast.PrefixExpr { + if cond.op != .not { + c.error('invalid `\$if` condition', cond.pos) + } + return !c.comp_if_branch(cond.right, cond.pos) + } + ast.PostfixExpr { + if cond.op != .question { + c.error('invalid \$if postfix operator', cond.pos) + } else if cond.expr is ast.Ident { + return cond.expr.name !in c.pref.compile_defines_all + } else { + c.error('invalid `\$if` condition', cond.pos) + } + } + ast.InfixExpr { + match cond.op { + .and { + l := c.comp_if_branch(cond.left, cond.pos) + r := c.comp_if_branch(cond.right, cond.pos) + return l || r // skip (return true) if at least one should be skipped + } + .logical_or { + l := c.comp_if_branch(cond.left, cond.pos) + r := c.comp_if_branch(cond.right, cond.pos) + return l && r // skip (return true) only if both should be skipped + } + .key_is, .not_is { + if cond.left is ast.TypeNode && cond.right is ast.TypeNode { + // `$if Foo is Interface {` + sym := c.table.get_type_symbol(cond.right.typ) + if sym.kind != .interface_ { + c.expr(cond.left) + // c.error('`$sym.name` is not an interface', cond.right.position()) + } + return false + } else if cond.left is ast.SelectorExpr || cond.left is ast.TypeNode { + // `$if method.@type is string` + c.expr(cond.left) + return false + } else { + c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check', + cond.left.position()) + } + } + .eq, .ne { + if cond.left is ast.SelectorExpr && cond.right is ast.IntegerLiteral { + // $if method.args.len == 1 + } else if cond.left is ast.Ident { + // $if version == 2 + left_type := c.expr(cond.left) + right_type := c.expr(cond.right) + expr := c.find_definition(cond.left) or { + c.error(err.msg, cond.left.pos) + return false + } + if !c.check_types(right_type, left_type) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + c.error('mismatched types `$left_name` and `$right_name`', + cond.pos) + } + // :) + // until `v.eval` is stable, I can't think of a better way to do this + different := expr.str() != cond.right.str() + return if cond.op == .eq { different } else { !different } + } else { + c.error('invalid `\$if` condition: ${cond.left.type_name()}1', + cond.pos) + } + } + else { + c.error('invalid `\$if` condition', cond.pos) + } + } + } + ast.Ident { + cname := cond.name + if cname in checker.valid_comp_if_os { + mut is_os_target_different := false + if !c.pref.output_cross_c { + target_os := c.pref.os.str().to_lower() + is_os_target_different = cname != target_os + } + return is_os_target_different + } else if cname in checker.valid_comp_if_compilers { + return pref.cc_from_string(cname) != c.pref.ccompiler_type + } else if cname in checker.valid_comp_if_platforms { + if cname == 'aarch64' { + c.note('use `arm64` instead of `aarch64`', pos) + } + match cname { + 'amd64' { return c.pref.arch != .amd64 } + 'i386' { return c.pref.arch != .i386 } + 'aarch64' { return c.pref.arch != .arm64 } + 'arm64' { return c.pref.arch != .arm64 } + 'arm32' { return c.pref.arch != .arm32 } + 'rv64' { return c.pref.arch != .rv64 } + 'rv32' { return c.pref.arch != .rv32 } + else { return false } + } + } else if cname in checker.valid_comp_if_cpu_features { + return false + } else if cname in checker.valid_comp_if_other { + match cname { + 'js' { return !c.pref.backend.is_js() } + 'debug' { return !c.pref.is_debug } + 'prod' { return !c.pref.is_prod } + 'test' { return !c.pref.is_test } + 'glibc' { return false } // TODO + 'threads' { return c.table.gostmts == 0 } + 'prealloc' { return !c.pref.prealloc } + 'no_bounds_checking' { return cname !in c.pref.compile_defines_all } + 'freestanding' { return !c.pref.is_bare || c.pref.output_cross_c } + else { return false } + } + } else if cname !in c.pref.compile_defines_all { + if cname == 'linux_or_macos' { + c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead', + cond.pos) + return false + } + // `$if some_var {}`, or `[if user_defined_tag] fn abc(){}` + typ := c.expr(cond) + if cond.obj !is ast.Var && cond.obj !is ast.ConstField + && cond.obj !is ast.GlobalField { + if !c.inside_ct_attr { + c.error('unknown var: `$cname`', pos) + } + return false + } + expr := c.find_obj_definition(cond.obj) or { + c.error(err.msg, cond.pos) + return false + } + if !c.check_types(typ, ast.bool_type) { + type_name := c.table.type_to_str(typ) + c.error('non-bool type `$type_name` used as \$if condition', cond.pos) + } + // :) + // until `v.eval` is stable, I can't think of a better way to do this + return !(expr as ast.BoolLiteral).val + } + } + ast.ComptimeCall { + if cond.is_pkgconfig { + mut m := pkgconfig.main([cond.args_var]) or { + c.error(err.msg, cond.pos) + return true + } + m.run() or { return true } + } + } + else { + c.error('invalid `\$if` condition', pos) + } + } + return false +} + +fn (mut c Checker) find_definition(ident ast.Ident) ?ast.Expr { + match ident.kind { + .unresolved, .blank_ident { return none } + .variable, .constant { return c.find_obj_definition(ident.obj) } + .global { return error('$ident.name is a global variable') } + .function { return error('$ident.name is a function') } + } +} + +fn (mut c Checker) find_obj_definition(obj ast.ScopeObject) ?ast.Expr { + // TODO: remove once we have better type inference + mut name := '' + match obj { + ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister { name = obj.name } + } + mut expr := ast.empty_expr() + if obj is ast.Var { + if obj.is_mut { + return error('`$name` is mut and may have changed since its definition') + } + expr = obj.expr + } else if obj is ast.ConstField { + expr = obj.expr + } else { + return error('`$name` is a global variable and is unknown at compile time') + } + if expr is ast.Ident { + return c.find_definition(expr as ast.Ident) // TODO: smartcast + } + if !expr.is_lit() { + return error('definition of `$name` is unknown at compile time') + } + return expr +} + +fn (c &Checker) has_return(stmts []ast.Stmt) ?bool { + // complexity means either more match or ifs + mut has_complexity := false + for s in stmts { + if s is ast.ExprStmt { + if s.expr is ast.IfExpr || s.expr is ast.MatchExpr { + has_complexity = true + break + } + } + } + // if the inner complexity covers all paths with returns there is no need for further checks + if !has_complexity || !c.returns { + return has_top_return(stmts) + } + return none +} + +pub fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) ast.Type { + typ := c.unwrap_generic(c.expr(node.expr)) + typ_sym := c.table.get_type_symbol(typ) + is_non_void_pointer := (typ.is_ptr() || typ.is_pointer()) && typ_sym.kind != .voidptr + if !c.inside_unsafe && is_non_void_pointer && !node.expr.is_auto_deref_var() { + c.warn('pointer arithmetic is only allowed in `unsafe` blocks', node.pos) + } + if !(typ_sym.is_number() || (c.inside_unsafe && is_non_void_pointer)) { + c.error('invalid operation: $node.op.str() (non-numeric type `$typ_sym.name`)', + node.pos) + } else { + node.auto_locked, _ = c.fail_if_immutable(node.expr) + } + return typ +} + +pub fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) { + match mut node { + ast.Ident { + if mut node.obj is ast.Var { + mut obj := unsafe { &node.obj } + if c.fn_scope != voidptr(0) { + obj = c.fn_scope.find_var(node.obj.name) or { obj } + } + type_sym := c.table.get_type_symbol(obj.typ.set_nr_muls(0)) + if obj.is_stack_obj && !type_sym.is_heap() && !c.pref.translated { + suggestion := if type_sym.kind == .struct_ { + 'declaring `$type_sym.name` as `[heap]`' + } else { + 'wrapping the `$type_sym.name` object in a `struct` declared as `[heap]`' + } + if !c.pref.translated { + mischief := if as_interface { + 'used as interface object' + } else { + 'referenced' + } + c.error('`$node.name` cannot be $mischief outside `unsafe` blocks as it might be stored on stack. Consider ${suggestion}.', + node.pos) + } + } else if type_sym.kind == .array_fixed { + c.error('cannot reference fixed array `$node.name` outside `unsafe` blocks as it is supposed to be stored on stack', + node.pos) + } else { + match type_sym.kind { + .struct_ { + info := type_sym.info as ast.Struct + if !info.is_heap { + node.obj.is_auto_heap = true + } + } + else { + node.obj.is_auto_heap = true + } + } + } + } + } + ast.SelectorExpr { + if !node.expr_type.is_ptr() { + c.mark_as_referenced(mut &node.expr, as_interface) + } + } + ast.IndexExpr { + c.mark_as_referenced(mut &node.left, as_interface) + } + else {} + } +} + +pub fn (mut c Checker) get_base_name(node &ast.Expr) string { + match node { + ast.Ident { + return node.name + } + ast.SelectorExpr { + return c.get_base_name(&node.expr) + } + ast.IndexExpr { + return c.get_base_name(&node.left) + } + else { + return '' + } + } +} + +pub fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { + old_inside_ref_lit := c.inside_ref_lit + c.inside_ref_lit = c.inside_ref_lit || node.op == .amp + right_type := c.expr(node.right) + c.inside_ref_lit = old_inside_ref_lit + node.right_type = right_type + if node.op == .amp { + if mut node.right is ast.PrefixExpr { + if node.right.op == .amp { + c.error('unexpected `&`, expecting expression', node.right.pos) + } + } + } + // TODO: testing ref/deref strategy + if node.op == .amp && !right_type.is_ptr() { + mut expr := node.right + // if ParExpr get the innermost expr + for mut expr is ast.ParExpr { + expr = expr.expr + } + match expr { + ast.BoolLiteral, ast.CallExpr, ast.CharLiteral, ast.FloatLiteral, ast.IntegerLiteral, + ast.InfixExpr, ast.StringLiteral, ast.StringInterLiteral { + c.error('cannot take the address of $expr', node.pos) + } + else {} + } + if mut node.right is ast.IndexExpr { + typ_sym := c.table.get_type_symbol(node.right.left_type) + mut is_mut := false + if mut node.right.left is ast.Ident { + ident := node.right.left + // TODO: temporary, remove this + ident_obj := ident.obj + if ident_obj is ast.Var { + is_mut = ident_obj.is_mut + } + } + if typ_sym.kind == .map { + c.error('cannot take the address of map values', node.right.pos) + } + if !c.inside_unsafe { + if typ_sym.kind == .array && is_mut { + c.error('cannot take the address of mutable array elements outside unsafe blocks', + node.right.pos) + } + } + } + if !c.inside_fn_arg && !c.inside_unsafe { + c.mark_as_referenced(mut &node.right, false) + } + return right_type.to_ptr() + } else if node.op == .amp && node.right !is ast.CastExpr { + if !c.inside_fn_arg && !c.inside_unsafe { + c.mark_as_referenced(mut &node.right, false) + } + if node.right.is_auto_deref_var() { + return right_type + } else { + return right_type.to_ptr() + } + } + if node.op == .mul { + if right_type.is_ptr() { + return right_type.deref() + } + if !right_type.is_pointer() { + s := c.table.type_to_str(right_type) + c.error('invalid indirect of `$s`', node.pos) + } + } + if node.op == .bit_not && !right_type.is_int() && !c.pref.translated { + c.error('operator ~ only defined on int types', node.pos) + } + if node.op == .not && right_type != ast.bool_type_idx && !c.pref.translated { + c.error('! operator can only be used with bool types', node.pos) + } + // FIXME + // there are currently other issues to investigate if right_type + // is unwraped directly as initialization, so do it here + right_sym := c.table.get_final_type_symbol(c.unwrap_generic(right_type)) + if node.op == .minus && !right_sym.is_number() { + c.error('- operator can only be used with numeric types', node.pos) + } + if node.op == .arrow { + if right_sym.kind == .chan { + c.stmts(node.or_block.stmts) + return right_sym.chan_info().elem_type + } + c.error('<- operator can only be used with `chan` types', node.pos) + } + return right_type +} + +fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_type ast.Type, pos token.Position, range_index bool) { + index_type_sym := c.table.get_type_symbol(index_type) + // println('index expr left=$typ_sym.name $node.pos.line_nr') + // if typ_sym.kind == .array && (!(ast.type_idx(index_type) in ast.number_type_idxs) && + // index_type_sym.kind != .enum_) { + if typ_sym.kind in [.array, .array_fixed, .string] { + if !(index_type.is_int() || index_type_sym.kind == .enum_) { + type_str := if typ_sym.kind == .string { + 'non-integer string index `$index_type_sym.name`' + } else { + 'non-integer index `$index_type_sym.name` (array type `$typ_sym.name`)' + } + c.error('$type_str', pos) + } + if index is ast.IntegerLiteral { + if index.val[0] == `-` { + c.error('negative index `$index.val`', index.pos) + } else if typ_sym.kind == .array_fixed { + i := index.val.int() + info := typ_sym.info as ast.ArrayFixed + if (!range_index && i >= info.size) || (range_index && i > info.size) { + c.error('index out of range (index: $i, len: $info.size)', index.pos) + } + } + } + if index_type.has_flag(.optional) { + type_str := if typ_sym.kind == .string { + '(type `$typ_sym.name`)' + } else { + '(array type `$typ_sym.name`)' + } + c.error('cannot use optional as index $type_str', pos) + } + } +} + +pub fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { + mut typ := c.expr(node.left) + mut typ_sym := c.table.get_final_type_symbol(typ) + node.left_type = typ + for { + match typ_sym.kind { + .map { + node.is_map = true + break + } + .array { + node.is_array = true + break + } + .array_fixed { + node.is_farray = true + break + } + .any { + gname := typ_sym.name + typ = c.unwrap_generic(typ) + node.left_type = typ + typ_sym = c.table.get_final_type_symbol(typ) + if typ.is_ptr() { + continue + } else { + c.error('generic type $gname does not support indexing, pass an array, or a reference instead, e.g. []$gname or &$gname', + node.pos) + } + } + else { + break + } + } + } + if typ_sym.kind !in [.array, .array_fixed, .string, .map] && !typ.is_ptr() + && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) { + c.error('type `$typ_sym.name` does not support indexing', node.pos) + } + if typ_sym.kind == .string && !typ.is_ptr() && node.is_setter { + c.error('cannot assign to s[i] since V strings are immutable\n' + + '(note, that variables may be mutable but string values are always immutable, like in Go and Java)', + node.pos) + } + if !c.inside_unsafe && ((typ.is_ptr() && !typ.has_flag(.shared_f) + && !node.left.is_auto_deref_var()) || typ.is_pointer()) { + mut is_ok := false + if mut node.left is ast.Ident { + if node.left.obj is ast.Var { + v := node.left.obj as ast.Var + // `mut param []T` function parameter + is_ok = v.is_mut && v.is_arg && !typ.deref().is_ptr() + } + } + if !is_ok && !c.pref.translated { + c.warn('pointer indexing is only allowed in `unsafe` blocks', node.pos) + } + } + if mut node.index is ast.RangeExpr { // [1..2] + if node.index.has_low { + index_type := c.expr(node.index.low) + c.check_index(typ_sym, node.index.low, index_type, node.pos, true) + } + if node.index.has_high { + index_type := c.expr(node.index.high) + c.check_index(typ_sym, node.index.high, index_type, node.pos, true) + } + // array[1..2] => array + // fixed_array[1..2] => array + if typ_sym.kind == .array_fixed { + elem_type := c.table.value_type(typ) + idx := c.table.find_or_register_array(elem_type) + typ = ast.new_type(idx) + } else { + typ = typ.set_nr_muls(0) + } + } else { // [1] + if typ_sym.kind == .map { + info := typ_sym.info as ast.Map + c.expected_type = info.key_type + index_type := c.expr(node.index) + if !c.check_types(index_type, info.key_type) { + err := c.expected_msg(index_type, info.key_type) + c.error('invalid key: $err', node.pos) + } + } else { + index_type := c.expr(node.index) + c.check_index(typ_sym, node.index, index_type, node.pos, false) + } + value_type := c.table.value_type(typ) + if value_type != ast.void_type { + typ = value_type + } + } + c.stmts(node.or_expr.stmts) + c.check_expr_opt_call(node, typ) + return typ +} + +// `.green` or `Color.green` +// If a short form is used, `expected_type` needs to be an enum +// with this value. +pub fn (mut c Checker) enum_val(mut node ast.EnumVal) ast.Type { + mut typ_idx := if node.enum_name == '' { + c.expected_type.idx() + } else { + c.table.find_type_idx(node.enum_name) + } + if typ_idx == 0 { + // Handle `builtin` enums like `ChanState`, so that `x := ChanState.closed` works. + // In the checker the name for such enums was set to `main.ChanState` instead of + // just `ChanState`. + if node.enum_name.starts_with('${c.mod}.') { + typ_idx = c.table.find_type_idx(node.enum_name['${c.mod}.'.len..]) + if typ_idx == 0 { + c.error('unknown enum `$node.enum_name` (type_idx=0)', node.pos) + return ast.void_type + } + } + } + mut typ := ast.new_type(typ_idx) + if c.pref.translated { + // TODO make more strict + node.typ = typ + return typ + } + if typ == ast.void_type { + c.error('not an enum', node.pos) + return ast.void_type + } + mut typ_sym := c.table.get_type_symbol(typ) + if typ_sym.kind == .array && node.enum_name.len == 0 { + array_info := typ_sym.info as ast.Array + typ = array_info.elem_type + typ_sym = c.table.get_type_symbol(typ) + } + if typ_sym.kind != .enum_ && !c.pref.translated { + // TODO in C int fields can be compared to enums, need to handle that in C2V + c.error('expected type is not an enum (`$typ_sym.name`)', node.pos) + return ast.void_type + } + if typ_sym.info !is ast.Enum { + c.error('not an enum', node.pos) + return ast.void_type + } + if !(typ_sym.is_public || typ_sym.mod == c.mod) { + c.error('enum `$typ_sym.name` is private', node.pos) + } + info := typ_sym.enum_info() + if node.val !in info.vals { + suggestion := util.new_suggestion(node.val, info.vals) + c.error(suggestion.say('enum `$typ_sym.name` does not have a value `$node.val`'), + node.pos) + } + node.typ = typ + return typ +} + +pub fn (mut c Checker) chan_init(mut node ast.ChanInit) ast.Type { + if node.typ != 0 { + info := c.table.get_type_symbol(node.typ).chan_info() + node.elem_type = info.elem_type + if node.has_cap { + c.check_array_init_para_type('cap', node.cap_expr, node.pos) + } + return node.typ + } else { + c.error('`chan` of unknown type', node.pos) + return node.typ + } +} + +pub fn (mut c Checker) offset_of(node ast.OffsetOf) ast.Type { + sym := c.table.get_final_type_symbol(node.struct_type) + if sym.kind != .struct_ { + c.error('first argument of __offsetof must be struct', node.pos) + return ast.u32_type + } + if !c.table.struct_has_field(sym, node.field) { + c.error('struct `$sym.name` has no field called `$node.field`', node.pos) + } + return ast.u32_type +} + +pub fn (mut c Checker) check_dup_keys(node &ast.MapInit, i int) { + key_i := node.keys[i] + if key_i is ast.StringLiteral { + for j in 0 .. i { + key_j := node.keys[j] + if key_j is ast.StringLiteral { + if key_i.val == key_j.val { + c.error('duplicate key "$key_i.val" in map literal', key_i.pos) + } + } + } + } else if key_i is ast.IntegerLiteral { + for j in 0 .. i { + key_j := node.keys[j] + if key_j is ast.IntegerLiteral { + if key_i.val == key_j.val { + c.error('duplicate key "$key_i.val" in map literal', key_i.pos) + } + } + } + } +} + +pub fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type { + // `map = {}` + if node.keys.len == 0 && node.vals.len == 0 && node.typ == 0 { + sym := c.table.get_type_symbol(c.expected_type) + if sym.kind == .map { + info := sym.map_info() + node.typ = c.expected_type + node.key_type = info.key_type + node.value_type = info.value_type + return node.typ + } else { + c.error('invalid empty map initilization syntax, use e.g. map[string]int{} instead', + node.pos) + } + } + // `x := map[string]string` - set in parser + if node.typ != 0 { + info := c.table.get_type_symbol(node.typ).map_info() + c.ensure_type_exists(info.key_type, node.pos) or {} + c.ensure_type_exists(info.value_type, node.pos) or {} + node.key_type = c.unwrap_generic(info.key_type) + node.value_type = c.unwrap_generic(info.value_type) + return node.typ + } + if node.keys.len > 0 && node.vals.len > 0 { + mut key0_type := ast.void_type + mut val0_type := ast.void_type + use_expected_type := c.expected_type != ast.void_type && !c.inside_const + && c.table.get_type_symbol(c.expected_type).kind == .map + if use_expected_type { + sym := c.table.get_type_symbol(c.expected_type) + info := sym.map_info() + key0_type = c.unwrap_generic(info.key_type) + val0_type = c.unwrap_generic(info.value_type) + } else { + // `{'age': 20}` + key0_type = c.table.mktyp(c.expr(node.keys[0])) + if node.keys[0].is_auto_deref_var() { + key0_type = key0_type.deref() + } + val0_type = c.table.mktyp(c.expr(node.vals[0])) + if node.vals[0].is_auto_deref_var() { + val0_type = val0_type.deref() + } + } + mut same_key_type := true + for i, key in node.keys { + if i == 0 && !use_expected_type { + continue + } + val := node.vals[i] + key_type := c.expr(key) + c.expected_type = val0_type + val_type := c.expr(val) + if !c.check_types(key_type, key0_type) || (i == 0 && key_type.is_number() + && key0_type.is_number() && key0_type != c.table.mktyp(key_type)) { + msg := c.expected_msg(key_type, key0_type) + c.error('invalid map key: $msg', key.position()) + same_key_type = false + } + if !c.check_types(val_type, val0_type) || (i == 0 && val_type.is_number() + && val0_type.is_number() && val0_type != c.table.mktyp(val_type)) { + msg := c.expected_msg(val_type, val0_type) + c.error('invalid map value: $msg', val.position()) + } + } + if same_key_type { + for i in 1 .. node.keys.len { + c.check_dup_keys(node, i) + } + } + key0_type = c.unwrap_generic(key0_type) + val0_type = c.unwrap_generic(val0_type) + mut map_type := ast.new_type(c.table.find_or_register_map(key0_type, val0_type)) + node.typ = map_type + node.key_type = key0_type + node.value_type = val0_type + return map_type + } + return node.typ +} + +// call this *before* calling error or warn +pub fn (mut c Checker) add_error_detail(s string) { + c.error_details << s +} + +pub fn (mut c Checker) warn(s string, pos token.Position) { + allow_warnings := !(c.pref.is_prod || c.pref.warns_are_errors) // allow warnings only in dev builds + c.warn_or_error(s, pos, allow_warnings) +} + +pub fn (mut c Checker) error(message string, pos token.Position) { + $if checker_exit_on_first_error ? { + eprintln('\n\n>> checker error: $message, pos: $pos') + print_backtrace() + exit(1) + } + if c.pref.translated && message.starts_with('mismatched types') { + // TODO move this + return + } + if c.pref.is_verbose { + print_backtrace() + } + msg := message.replace('`Array_', '`[]') + c.warn_or_error(msg, pos, false) +} + +// check `to` has all fields of `from` +fn (c &Checker) check_struct_signature(from ast.Struct, to ast.Struct) bool { + // Note: `to` can have extra fields + if from.fields.len == 0 { + return false + } + for field in from.fields { + filtered := to.fields.filter(it.name == field.name) + if filtered.len != 1 { + // field doesn't exist + return false + } + counterpart := filtered[0] + if field.typ != counterpart.typ { + // field has different tye + return false + } + if field.is_pub != counterpart.is_pub { + // field is not public while the other one is + return false + } + if field.is_mut != counterpart.is_mut { + // field is not mutable while the other one is + return false + } + } + return true +} + +pub fn (mut c Checker) note(message string, pos token.Position) { + if c.pref.message_limit >= 0 && c.nr_notices >= c.pref.message_limit { + c.should_abort = true + return + } + mut details := '' + if c.error_details.len > 0 { + details = c.error_details.join('\n') + c.error_details = [] + } + wrn := errors.Notice{ + reporter: errors.Reporter.checker + pos: pos + file_path: c.file.path + message: message + details: details + } + c.file.notices << wrn + c.notices << wrn + c.nr_notices++ +} + +fn (mut c Checker) warn_or_error(message string, pos token.Position, warn bool) { + // add backtrace to issue struct, how? + // if c.pref.is_verbose { + // print_backtrace() + // } + mut details := '' + if c.error_details.len > 0 { + details = c.error_details.join('\n') + c.error_details = [] + } + if warn && !c.pref.skip_warnings { + c.nr_warnings++ + if c.pref.message_limit >= 0 && c.nr_warnings >= c.pref.message_limit { + c.should_abort = true + return + } + wrn := errors.Warning{ + reporter: errors.Reporter.checker + pos: pos + file_path: c.file.path + message: message + details: details + } + c.file.warnings << wrn + c.warnings << wrn + return + } + if !warn { + if c.pref.fatal_errors { + exit(1) + } + c.nr_errors++ + if c.pref.message_limit >= 0 && c.errors.len >= c.pref.message_limit { + c.should_abort = true + return + } + if pos.line_nr !in c.error_lines { + err := errors.Error{ + reporter: errors.Reporter.checker + pos: pos + file_path: c.file.path + message: message + details: details + } + c.file.errors << err + c.errors << err + c.error_lines << pos.line_nr + } + } +} + +// for debugging only +fn (c &Checker) fileis(s string) bool { + return c.file.path.contains(s) +} + +fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type { + c.inside_sql = true + defer { + c.inside_sql = false + } + sym := c.table.get_type_symbol(node.table_expr.typ) + c.ensure_type_exists(node.table_expr.typ, node.pos) or { return ast.void_type } + c.cur_orm_ts = sym + info := sym.info as ast.Struct + fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name) + mut sub_structs := map[int]ast.SqlExpr{} + for f in fields.filter((c.table.type_symbols[int(it.typ)].kind == .struct_ + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) + && c.table.get_type_name(it.typ) != 'time.Time') { + typ := if c.table.get_type_symbol(f.typ).kind == .struct_ { + f.typ + } else if c.table.get_type_symbol(f.typ).kind == .array { + c.table.get_type_symbol(f.typ).array_info().elem_type + } else { + ast.Type(0) + } + mut n := ast.SqlExpr{ + pos: node.pos + has_where: true + typ: typ + db_expr: node.db_expr + table_expr: ast.TypeNode{ + pos: node.table_expr.pos + typ: typ + } + } + tmp_inside_sql := c.inside_sql + c.sql_expr(mut n) + c.inside_sql = tmp_inside_sql + n.where_expr = ast.InfixExpr{ + op: .eq + pos: n.pos + left: ast.Ident{ + language: .v + tok_kind: .eq + scope: c.fn_scope + obj: ast.Var{} + mod: 'main' + name: 'id' + is_mut: false + kind: .unresolved + info: ast.IdentVar{} + } + right: ast.Ident{ + language: .c + mod: 'main' + tok_kind: .eq + obj: ast.Var{} + is_mut: false + scope: c.fn_scope + info: ast.IdentVar{ + typ: ast.int_type + } + } + left_type: ast.int_type + right_type: ast.int_type + auto_locked: '' + or_block: ast.OrExpr{} + } + + sub_structs[int(typ)] = n + } + node.fields = fields + node.sub_structs = sub_structs.move() + if node.has_where { + c.expr(node.where_expr) + } + if node.has_offset { + c.expr(node.offset_expr) + } + if node.has_limit { + c.expr(node.limit_expr) + } + if node.has_order { + c.expr(node.order_expr) + } + c.expr(node.db_expr) + return node.typ +} + +fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type { + c.expr(node.db_expr) + mut typ := ast.void_type + for mut line in node.lines { + a := c.sql_stmt_line(mut line) + if a != ast.void_type { + typ = a + } + } + return typ +} + +fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type { + c.inside_sql = true + defer { + c.inside_sql = false + } + c.ensure_type_exists(node.table_expr.typ, node.pos) or { return ast.void_type } + table_sym := c.table.get_type_symbol(node.table_expr.typ) + c.cur_orm_ts = table_sym + if table_sym.info !is ast.Struct { + c.error('unknown type `$table_sym.name`', node.pos) + return ast.void_type + } + info := table_sym.info as ast.Struct + fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) + mut sub_structs := map[int]ast.SqlStmtLine{} + for f in fields.filter(((c.table.type_symbols[int(it.typ)].kind == .struct_) + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) + && c.table.get_type_name(it.typ) != 'time.Time') { + typ := if c.table.get_type_symbol(f.typ).kind == .struct_ { + f.typ + } else if c.table.get_type_symbol(f.typ).kind == .array { + c.table.get_type_symbol(f.typ).array_info().elem_type + } else { + ast.Type(0) + } + mut object_var_name := '${node.object_var_name}.$f.name' + if typ != f.typ { + object_var_name = node.object_var_name + } + mut n := ast.SqlStmtLine{ + pos: node.pos + kind: node.kind + table_expr: ast.TypeNode{ + pos: node.table_expr.pos + typ: typ + } + object_var_name: object_var_name + } + tmp_inside_sql := c.inside_sql + c.sql_stmt_line(mut n) + c.inside_sql = tmp_inside_sql + sub_structs[typ] = n + } + node.fields = fields + node.sub_structs = sub_structs.move() + for i, column in node.updated_columns { + field := node.fields.filter(it.name == column)[0] + node.updated_columns[i] = c.fetch_field_name(field) + } + if node.kind == .update { + for expr in node.update_exprs { + c.expr(expr) + } + } + if node.where_expr !is ast.EmptyExpr { + c.expr(node.where_expr) + } + + return ast.void_type +} + +fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Position, table_name string) []ast.StructField { + fields := info.fields.filter((it.typ in [ast.string_type, ast.int_type, ast.bool_type] + || c.table.type_symbols[int(it.typ)].kind == .struct_ + || (c.table.get_type_symbol(it.typ).kind == .array + && c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_)) + && !it.attrs.contains('skip')) + if fields.len == 0 { + c.error('V orm: select: empty fields in `$table_name`', pos) + return []ast.StructField{} + } + if fields[0].name != 'id' { + c.error('V orm: `id int` must be the first field in `$table_name`', pos) + } + return fields +} + +fn (mut c Checker) fetch_field_name(field ast.StructField) string { + mut name := field.name + for attr in field.attrs { + if attr.kind == .string && attr.name == 'sql' && attr.arg != '' { + name = attr.arg + break + } + } + sym := c.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + name = '${name}_id' + } + return name +} + +fn (mut c Checker) post_process_generic_fns() { + // Loop thru each generic function concrete type. + // Check each specific fn instantiation. + for i in 0 .. c.file.generic_fns.len { + mut node := c.file.generic_fns[i] + c.mod = node.mod + for concrete_types in c.table.fn_generic_types[node.name] { + c.table.cur_concrete_types = concrete_types + c.fn_decl(mut node) + if node.name == 'vweb.run' { + for ct in concrete_types { + if ct !in c.vweb_gen_types { + c.vweb_gen_types << ct + } + } + } + } + c.table.cur_concrete_types = [] + } +} + +fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool { + if node.ct_evaled { + return node.ct_skip + } + if node.ct_expr is ast.Ident { + if node.ct_opt { + if node.ct_expr.name in checker.valid_comp_not_user_defined { + c.error('optional `[if expression ?]` tags, can be used only for user defined identifiers', + node.pos) + node.ct_skip = true + } else { + node.ct_skip = node.ct_expr.name !in c.pref.compile_defines + } + node.ct_evaled = true + return node.ct_skip + } else { + if node.ct_expr.name !in checker.valid_comp_not_user_defined { + c.note('`[if $node.ct_expr.name]` is deprecated. Use `[if $node.ct_expr.name ?]` instead', + node.pos) + node.ct_skip = node.ct_expr.name !in c.pref.compile_defines + node.ct_evaled = true + return node.ct_skip + } else { + if node.ct_expr.name in c.pref.compile_defines { + // explicitly allow custom user overrides with `-d linux` for example, for easier testing: + node.ct_skip = false + node.ct_evaled = true + return node.ct_skip + } + } + } + } + c.inside_ct_attr = true + node.ct_skip = c.comp_if_branch(node.ct_expr, node.pos) + c.inside_ct_attr = false + node.ct_evaled = true + return node.ct_skip +} + +fn (mut c Checker) fn_decl(mut node ast.FnDecl) { + if node.generic_names.len > 0 && c.table.cur_concrete_types.len == 0 { + // Just remember the generic function for now. + // It will be processed later in c.post_process_generic_fns, + // after all other normal functions are processed. + // This is done so that all generic function calls can + // have a chance to populate c.table.fn_generic_types with + // the correct concrete types. + c.file.generic_fns << node + return + } + // save all the state that fn_decl or inner statements/expressions + // could potentially modify, since functions can be nested, due to + // anonymous function support, and ensure that it is restored, when + // fn_decl returns: + prev_fn_scope := c.fn_scope + prev_in_for_count := c.in_for_count + prev_inside_defer := c.inside_defer + prev_inside_unsafe := c.inside_unsafe + prev_inside_anon_fn := c.inside_anon_fn + prev_returns := c.returns + c.fn_level++ + c.in_for_count = 0 + c.inside_defer = false + c.inside_unsafe = false + c.returns = false + defer { + c.fn_level-- + c.returns = prev_returns + c.inside_anon_fn = prev_inside_anon_fn + c.inside_unsafe = prev_inside_unsafe + c.inside_defer = prev_inside_defer + c.in_for_count = prev_in_for_count + c.fn_scope = prev_fn_scope + } + // Check generics fn/method without generic type parameters + mut need_generic_names := false + if node.generic_names.len == 0 { + if node.return_type.has_flag(.generic) { + need_generic_names = true + } else { + for param in node.params { + if param.typ.has_flag(.generic) { + need_generic_names = true + break + } + } + } + if need_generic_names { + c.error('generic function declaration must specify generic type names, e.g. foo', + node.pos) + } + } + if node.language == .v && !c.is_builtin_mod && !node.is_anon { + c.check_valid_snake_case(node.name, 'function name', node.pos) + } + if node.name == 'main.main' { + c.main_fn_decl_node = node + } + if node.return_type != ast.void_type { + if ct_attr_idx := node.attrs.find_comptime_define() { + sexpr := node.attrs[ct_attr_idx].ct_expr.str() + c.error('only functions that do NOT return values can have `[if $sexpr]` tags', + node.pos) + } + if node.generic_names.len > 0 { + gs := c.table.get_type_symbol(node.return_type) + if gs.info is ast.Struct { + if gs.info.is_generic && !node.return_type.has_flag(.generic) { + c.error('return generic struct in fn declaration must specify the generic type names, e.g. Foo', + node.return_type_pos) + } + } + } + return_sym := c.table.get_type_symbol(node.return_type) + if return_sym.info is ast.MultiReturn { + for multi_type in return_sym.info.types { + multi_sym := c.table.get_type_symbol(multi_type) + if multi_type == ast.error_type { + c.error('type `IError` cannot be used in multi-return, return an option instead', + node.return_type_pos) + } else if multi_type.has_flag(.optional) { + c.error('option cannot be used in multi-return, return an option instead', + node.return_type_pos) + } else if multi_sym.kind == .array_fixed { + c.error('fixed array cannot be used in multi-return', node.return_type_pos) + } + } + } else if return_sym.kind == .array_fixed { + c.error('fixed array cannot be returned by function', node.return_type_pos) + } + } else { + for mut a in node.attrs { + if a.kind == .comptime_define { + node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut a) + } + } + } + if node.is_method { + mut sym := c.table.get_type_symbol(node.receiver.typ) + if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' { + // TODO `node.map in array_builtin_methods` + c.error('method overrides built-in array method', node.pos) + } else if sym.kind == .sum_type && node.name == 'type_name' { + c.error('method overrides built-in sum type method', node.pos) + } else if sym.kind == .sum_type && node.name == 'type_idx' { + c.error('method overrides built-in sum type method', node.pos) + } else if sym.kind == .multi_return { + c.error('cannot define method on multi-value', node.method_type_pos) + } + if sym.name.len == 1 { + // One letter types are reserved for generics. + c.error('unknown type `$sym.name`', node.receiver_pos) + return + } + // make sure interface does not implement its own interface methods + if sym.kind == .interface_ && sym.has_method(node.name) { + if mut sym.info is ast.Interface { + // if the method is in info.methods then it is an interface method + if sym.info.has_method(node.name) { + c.error('interface `$sym.name` cannot implement its own interface method `$node.name`', + node.pos) + } + } + } + if mut sym.info is ast.Struct { + if field := c.table.find_field(sym, node.name) { + field_sym := c.table.get_type_symbol(field.typ) + if field_sym.kind == .function { + c.error('type `$sym.name` has both field and method named `$node.name`', + node.pos) + } + } + } + // needed for proper error reporting during vweb route checking + if node.method_idx < sym.methods.len { + sym.methods[node.method_idx].source_fn = voidptr(node) + } else { + c.error('method index: $node.method_idx >= sym.methods.len: $sym.methods.len', + node.pos) + } + } + if node.language == .v { + // Make sure all types are valid + for arg in node.params { + c.ensure_type_exists(arg.typ, arg.type_pos) or { return } + if !arg.typ.is_ptr() { // value parameter, i.e. on stack - check for `[heap]` + arg_typ_sym := c.table.get_type_symbol(arg.typ) + if arg_typ_sym.kind == .struct_ { + info := arg_typ_sym.info as ast.Struct + if info.is_heap { // set auto_heap to promote value parameter + mut v := node.scope.find_var(arg.name) or { continue } + v.is_auto_heap = true + } + } + } + } + } + if node.language == .v && node.name.after_char(`.`) == 'init' && !node.is_method + && node.params.len == 0 { + if node.is_pub { + c.error('fn `init` must not be public', node.pos) + } + if node.return_type != ast.void_type { + c.error('fn `init` cannot have a return type', node.pos) + } + } + if node.return_type != ast.Type(0) { + c.ensure_type_exists(node.return_type, node.return_type_pos) or { return } + if node.language == .v && node.is_method && node.name == 'str' { + if node.return_type != ast.string_type { + c.error('.str() methods should return `string`', node.pos) + } + if node.params.len != 1 { + c.error('.str() methods should have 0 arguments', node.pos) + } + } + if node.language == .v && node.is_method + && node.name in ['+', '-', '*', '%', '/', '<', '=='] { + if node.params.len != 2 { + c.error('operator methods should have exactly 1 argument', node.pos) + } else { + receiver_sym := c.table.get_type_symbol(node.receiver.typ) + param_sym := c.table.get_type_symbol(node.params[1].typ) + if param_sym.kind == .string && receiver_sym.kind == .string { + // bypass check for strings + // TODO there must be a better way to handle that + } else if param_sym.kind !in [.struct_, .alias] + || receiver_sym.kind !in [.struct_, .alias] { + c.error('operator methods are only allowed for struct and type alias', + node.pos) + } else { + parent_sym := c.table.get_final_type_symbol(node.receiver.typ) + if node.rec_mut { + c.error('receiver cannot be `mut` for operator overloading', node.receiver_pos) + } else if node.params[1].is_mut { + c.error('argument cannot be `mut` for operator overloading', node.pos) + } else if node.receiver.typ != node.params[1].typ { + c.error('expected `$receiver_sym.name` not `$param_sym.name` - both operands must be the same type for operator overloading', + node.params[1].type_pos) + } else if node.name in ['<', '=='] && node.return_type != ast.bool_type { + c.error('operator comparison methods should return `bool`', node.pos) + } else if parent_sym.is_primitive() { + c.error('cannot define operator methods on type alias for `$parent_sym.name`', + node.pos) + } + } + } + } + } + // TODO c.pref.is_vet + if node.language == .v && !node.is_method && node.params.len == 0 && node.is_test { + if !c.pref.is_test { + // simple heuristic + for st in node.stmts { + if st is ast.AssertStmt { + c.warn('tests will not be run, because filename does not end with `_test.v`', + node.pos) + break + } + } + } + if node.return_type != ast.void_type_idx + && node.return_type.clear_flag(.optional) != ast.void_type_idx { + c.error('test functions should either return nothing at all, or be marked to return `?`', + node.pos) + } + } + c.expected_type = ast.void_type + c.table.cur_fn = unsafe { node } + // c.table.cur_fn = node + // Add return if `fn(...) ? {...}` have no return at end + if node.return_type != ast.void_type && node.return_type.has_flag(.optional) + && (node.stmts.len == 0 || node.stmts[node.stmts.len - 1] !is ast.Return) { + sym := c.table.get_type_symbol(node.return_type) + if sym.kind == .void { + node.stmts << ast.Return{ + pos: node.pos + } + } + } + c.fn_scope = node.scope + c.stmts(node.stmts) + node_has_top_return := has_top_return(node.stmts) + node.has_return = c.returns || node_has_top_return + c.check_noreturn_fn_decl(mut node) + if node.language == .v && !node.no_body && node.return_type != ast.void_type && !node.has_return + && !node.is_noreturn { + if c.inside_anon_fn { + c.error('missing return at the end of an anonymous function', node.pos) + } else if !node.attrs.contains('_naked') { + c.error('missing return at end of function `$node.name`', node.pos) + } + } + if node.is_method { + sym := c.table.get_type_symbol(node.receiver.typ) + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if info.is_generic && c.table.cur_fn.generic_names.len == 0 { + c.error('receiver must specify the generic type names, e.g. Foo', node.method_type_pos) + } + } + } + node.source_file = c.file +} + +fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { + keep_fn := c.table.cur_fn + keep_inside_anon := c.inside_anon_fn + defer { + c.table.cur_fn = keep_fn + c.inside_anon_fn = keep_inside_anon + } + c.table.cur_fn = unsafe { &node.decl } + c.inside_anon_fn = true + for mut var in node.inherited_vars { + parent_var := node.decl.scope.parent.find_var(var.name) or { + panic('unexpected checker error: cannot find parent of inherited variable `$var.name`') + } + if var.is_mut && !parent_var.is_mut { + c.error('original `$parent_var.name` is immutable, declare it with `mut` to make it mutable', + var.pos) + } + var.typ = parent_var.typ + } + c.stmts(node.decl.stmts) + c.fn_decl(mut node.decl) + return node.typ +} + +// NB: has_top_return/1 should be called on *already checked* stmts, +// which do have their stmt.expr.is_noreturn set properly: +fn has_top_return(stmts []ast.Stmt) bool { + for stmt in stmts { + match stmt { + ast.Return { + return true + } + ast.Block { + if has_top_return(stmt.stmts) { + return true + } + } + ast.ExprStmt { + if stmt.expr is ast.CallExpr { + if stmt.expr.is_noreturn { + return true + } + } + } + else {} + } + } + return false +} + +fn (mut c Checker) verify_vweb_params_for_method(node ast.Fn) (bool, int, int) { + margs := node.params.len - 1 // first arg is the receiver/this + if node.attrs.len == 0 { + // allow non custom routed methods, with 1:1 mapping + return true, -1, margs + } + if node.params.len > 1 { + for param in node.params[1..] { + param_sym := c.table.get_final_type_symbol(param.typ) + if !(param_sym.is_string() || param_sym.is_number() || param_sym.is_float() + || param_sym.kind == .bool) { + c.error('invalid type `$param_sym.name` for parameter `$param.name` in vweb app method `$node.name`', + param.pos) + } + } + } + mut route_attributes := 0 + for a in node.attrs { + if a.name.starts_with('/') { + route_attributes += a.name.count(':') + } + } + return route_attributes == margs, route_attributes, margs +} + +fn (mut c Checker) verify_all_vweb_routes() { + if c.vweb_gen_types.len == 0 { + return + } + c.table.used_vweb_types = c.vweb_gen_types + typ_vweb_result := c.table.find_type_idx('vweb.Result') + old_file := c.file + for vgt in c.vweb_gen_types { + sym_app := c.table.get_type_symbol(vgt) + for m in sym_app.methods { + if m.return_type == typ_vweb_result { + is_ok, nroute_attributes, nargs := c.verify_vweb_params_for_method(m) + if !is_ok { + f := &ast.FnDecl(m.source_fn) + if isnil(f) { + continue + } + if f.return_type == typ_vweb_result && f.receiver.typ == m.params[0].typ + && f.name == m.name { + c.change_current_file(f.source_file) // setup of file path for the warning + c.warn('mismatched parameters count between vweb method `${sym_app.name}.$m.name` ($nargs) and route attribute $m.attrs ($nroute_attributes)', + f.pos) + } + } + } + } + } + c.change_current_file(old_file) +} + +fn (mut c Checker) trace(fbase string, message string) { + if c.file.path_base == fbase { + println('> c.trace | ${fbase:-10s} | $message') + } +} + +fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Position) ? { + if typ == 0 { + c.error('unknown type', pos) + return + } + sym := c.table.get_type_symbol(typ) + match sym.kind { + .placeholder { + if sym.language == .v && !sym.name.starts_with('C.') { + c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'), + pos) + return + } + } + .int_literal, .float_literal { + // Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different + // suggestions due to f32 comparision issue. + if !c.is_builtin_mod { + msg := if sym.kind == .int_literal { + 'unknown type `$sym.name`.\nDid you mean `int`?' + } else { + 'unknown type `$sym.name`.\nDid you mean `f64`?' + } + c.error(msg, pos) + return + } + } + .array { + c.ensure_type_exists((sym.info as ast.Array).elem_type, pos) ? + } + .array_fixed { + c.ensure_type_exists((sym.info as ast.ArrayFixed).elem_type, pos) ? + } + .map { + info := sym.info as ast.Map + c.ensure_type_exists(info.key_type, pos) ? + c.ensure_type_exists(info.value_type, pos) ? + } + else {} + } +} diff --git a/v_windows/v/vlib/v/checker/comptime_const_eval.v b/v_windows/v/vlib/v/checker/comptime_const_eval.v new file mode 100644 index 0000000..5bb0781 --- /dev/null +++ b/v_windows/v/vlib/v/checker/comptime_const_eval.v @@ -0,0 +1,159 @@ +module checker + +import v.ast + +fn eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.ComptTimeConstValue { + if nlevel > 100 { + // protect against a too deep comptime eval recursion + return none + } + match expr { + ast.IntegerLiteral { + x := expr.val.u64() + if x > 9223372036854775807 { + return x + } + return expr.val.i64() + } + ast.StringLiteral { + return expr.val + } + ast.CharLiteral { + runes := expr.val.runes() + if runes.len > 0 { + return runes[0] + } + return none + } + ast.Ident { + if expr.obj is ast.ConstField { + // an existing constant? + return eval_comptime_const_expr(expr.obj.expr, nlevel + 1) + } + } + ast.CastExpr { + cast_expr_value := eval_comptime_const_expr(expr.expr, nlevel + 1) or { return none } + if expr.typ == ast.i8_type { + return cast_expr_value.i8() or { return none } + } + if expr.typ == ast.i16_type { + return cast_expr_value.i16() or { return none } + } + if expr.typ == ast.int_type { + return cast_expr_value.int() or { return none } + } + if expr.typ == ast.i64_type { + return cast_expr_value.i64() or { return none } + } + // + if expr.typ == ast.byte_type { + return cast_expr_value.byte() or { return none } + } + if expr.typ == ast.u16_type { + return cast_expr_value.u16() or { return none } + } + if expr.typ == ast.u32_type { + return cast_expr_value.u32() or { return none } + } + if expr.typ == ast.u64_type { + return cast_expr_value.u64() or { return none } + } + // + if expr.typ == ast.f32_type { + return cast_expr_value.f32() or { return none } + } + if expr.typ == ast.f64_type { + return cast_expr_value.f64() or { return none } + } + } + ast.InfixExpr { + left := eval_comptime_const_expr(expr.left, nlevel + 1) ? + right := eval_comptime_const_expr(expr.right, nlevel + 1) ? + if left is string && right is string { + match expr.op { + .plus { + return left + right + } + else { + return none + } + } + } else if left is u64 && right is i64 { + match expr.op { + .plus { return i64(left) + i64(right) } + .minus { return i64(left) - i64(right) } + .mul { return i64(left) * i64(right) } + .div { return i64(left) / i64(right) } + .mod { return i64(left) % i64(right) } + .xor { return i64(left) ^ i64(right) } + .pipe { return i64(left) | i64(right) } + .amp { return i64(left) & i64(right) } + .left_shift { return i64(left) << i64(right) } + .right_shift { return i64(left) >> i64(right) } + else { return none } + } + } else if left is i64 && right is u64 { + match expr.op { + .plus { return i64(left) + i64(right) } + .minus { return i64(left) - i64(right) } + .mul { return i64(left) * i64(right) } + .div { return i64(left) / i64(right) } + .mod { return i64(left) % i64(right) } + .xor { return i64(left) ^ i64(right) } + .pipe { return i64(left) | i64(right) } + .amp { return i64(left) & i64(right) } + .left_shift { return i64(left) << i64(right) } + .right_shift { return i64(left) >> i64(right) } + else { return none } + } + } else if left is u64 && right is u64 { + match expr.op { + .plus { return left + right } + .minus { return left - right } + .mul { return left * right } + .div { return left / right } + .mod { return left % right } + .xor { return left ^ right } + .pipe { return left | right } + .amp { return left & right } + .left_shift { return left << right } + .right_shift { return left >> right } + else { return none } + } + } else if left is i64 && right is i64 { + match expr.op { + .plus { return left + right } + .minus { return left - right } + .mul { return left * right } + .div { return left / right } + .mod { return left % right } + .xor { return left ^ right } + .pipe { return left | right } + .amp { return left & right } + .left_shift { return left << right } + .right_shift { return left >> right } + else { return none } + } + } else if left is byte && right is byte { + match expr.op { + .plus { return left + right } + .minus { return left - right } + .mul { return left * right } + .div { return left / right } + .mod { return left % right } + .xor { return left ^ right } + .pipe { return left | right } + .amp { return left & right } + .left_shift { return left << right } + .right_shift { return left >> right } + else { return none } + } + } + } + else { + // eprintln('>>> nlevel: $nlevel | another $expr.type_name() | $expr ') + return none + } + } + return none +} diff --git a/v_windows/v/vlib/v/checker/noreturn.v b/v_windows/v/vlib/v/checker/noreturn.v new file mode 100644 index 0000000..ca4a288 --- /dev/null +++ b/v_windows/v/vlib/v/checker/noreturn.v @@ -0,0 +1,112 @@ +module checker + +import v.ast + +fn (mut c Checker) check_noreturn_fn_decl(mut node ast.FnDecl) { + if !node.is_noreturn { + return + } + if node.no_body { + return + } + if uses_return_stmt(node.stmts) { + c.error('[noreturn] functions cannot use return statements', node.pos) + } + if node.return_type != ast.void_type { + c.error('[noreturn] functions cannot have return types', node.pos) + } else { + if node.stmts.len != 0 { + mut is_valid_end_of_noreturn_fn := false + last_stmt := node.stmts.last() + match last_stmt { + ast.ExprStmt { + if last_stmt.expr is ast.CallExpr { + if last_stmt.expr.should_be_skipped { + c.error('[noreturn] functions cannot end with a skippable `[if ..]` call', + last_stmt.pos) + } + if last_stmt.expr.is_noreturn { + is_valid_end_of_noreturn_fn = true + } + } + } + ast.ForStmt { + if last_stmt.is_inf && last_stmt.stmts.len == 0 { + is_valid_end_of_noreturn_fn = true + } + } + else {} + } + if !is_valid_end_of_noreturn_fn { + c.error('[noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop', + last_stmt.pos) + } + } + } +} + +fn uses_return_stmt(stmts []ast.Stmt) bool { + if stmts.len == 0 { + return false + } + for stmt in stmts { + match stmt { + ast.Return { + return true + } + ast.Block { + if uses_return_stmt(stmt.stmts) { + return true + } + } + ast.ExprStmt { + match stmt.expr { + ast.CallExpr { + if uses_return_stmt(stmt.expr.or_block.stmts) { + return true + } + } + ast.MatchExpr { + for b in stmt.expr.branches { + if uses_return_stmt(b.stmts) { + return true + } + } + } + ast.SelectExpr { + for b in stmt.expr.branches { + if uses_return_stmt(b.stmts) { + return true + } + } + } + ast.IfExpr { + for b in stmt.expr.branches { + if uses_return_stmt(b.stmts) { + return true + } + } + } + else {} + } + } + ast.ForStmt { + if uses_return_stmt(stmt.stmts) { + return true + } + } + ast.ForCStmt { + if uses_return_stmt(stmt.stmts) { + return true + } + } + ast.ForInStmt { + if uses_return_stmt(stmt.stmts) { + return true + } + } + else {} + } + } + return false +} diff --git a/v_windows/v/vlib/v/checker/tests/.gitattributes b/v_windows/v/vlib/v/checker/tests/.gitattributes new file mode 100644 index 0000000..20f8e01 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/.gitattributes @@ -0,0 +1,2 @@ +*_crlf_* binary +*_lf_* binary diff --git a/v_windows/v/vlib/v/checker/tests/.gitignore b/v_windows/v/vlib/v/checker/tests/.gitignore new file mode 100644 index 0000000..868d19e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/.gitignore @@ -0,0 +1,4 @@ +*.v +*.c +!*_test.v +!modules/**/*.v \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.out b/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.out new file mode 100644 index 0000000..3734061 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/a_test_file_with_0_test_fns_test.vv:1:1: error: a _test.v file should have *at least* one `test_` function + 1 | fn abc() {} + | ^ +Details: The name of a test function in V, should start with `test_`. +The test function should take 0 parameters, and no return type. Example: +fn test_xyz(){ assert 2 + 2 == 4 } diff --git a/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.vv b/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.vv new file mode 100644 index 0000000..45f81ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/a_test_file_with_0_test_fns_test.vv @@ -0,0 +1 @@ +fn abc() {} diff --git a/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.out new file mode 100644 index 0000000..c1c8699 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/add_op_wrong_type_err.vv:3:13: error: mismatched types `Aaa` and `int literal` + 1 | struct Aaa{} + 2 | fn main() { + 3 | println(Aaa{} + 10) + | ~~~~~~~~~~ + 4 | println(10 + Aaa{}) + 5 | println([1,2,3] + 10) +vlib/v/checker/tests/add_op_wrong_type_err.vv:4:13: error: mismatched types `int literal` and `Aaa` + 2 | fn main() { + 3 | println(Aaa{} + 10) + 4 | println(10 + Aaa{}) + | ~~~~~~~~~~ + 5 | println([1,2,3] + 10) + 6 | println(10 + [1,2,3]) +vlib/v/checker/tests/add_op_wrong_type_err.vv:5:13: error: mismatched types `[]int` and `int literal` + 3 | println(Aaa{} + 10) + 4 | println(10 + Aaa{}) + 5 | println([1,2,3] + 10) + | ~~~~~~~~~~~~ + 6 | println(10 + [1,2,3]) + 7 | a := map[string]int +vlib/v/checker/tests/add_op_wrong_type_err.vv:6:13: error: mismatched types `int literal` and `[]int` + 4 | println(10 + Aaa{}) + 5 | println([1,2,3] + 10) + 6 | println(10 + [1,2,3]) + | ~~~~~~~~~~~~ + 7 | a := map[string]int + 8 | println(a + 10) +vlib/v/checker/tests/add_op_wrong_type_err.vv:8:13: error: mismatched types `map[string]int` and `int literal` + 6 | println(10 + [1,2,3]) + 7 | a := map[string]int + 8 | println(a + 10) + | ~~~~~~ + 9 | println(10 + a) + 10 | } +vlib/v/checker/tests/add_op_wrong_type_err.vv:9:13: error: mismatched types `int literal` and `map[string]int` + 7 | a := map[string]int + 8 | println(a + 10) + 9 | println(10 + a) + | ~~~~~~ + 10 | } + diff --git a/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.vv new file mode 100644 index 0000000..f02798b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/add_op_wrong_type_err.vv @@ -0,0 +1,10 @@ +struct Aaa{} +fn main() { + println(Aaa{} + 10) + println(10 + Aaa{}) + println([1,2,3] + 10) + println(10 + [1,2,3]) + a := map[string]int + println(a + 10) + println(10 + a) +} diff --git a/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.out b/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.out new file mode 100644 index 0000000..34cfbdb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv:17:7: error: undefined operation `Tuple` - `Tuple` + 15 | mut a := new_tuple(12, 4.5, 6.7, 6) + 16 | b := new_tuple(12, 4.5, 6.7, 6) + 17 | a -= b + | ~~ + 18 | println(a - b) + 19 | } +vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv:18:13: error: undefined operation `Tuple` - `Tuple` + 16 | b := new_tuple(12, 4.5, 6.7, 6) + 17 | a -= b + 18 | println(a - b) + | ~~~~~ + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv b/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv new file mode 100644 index 0000000..84d7034 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_array_unknown_op_overloading_err.vv @@ -0,0 +1,19 @@ +type Tuple = []f64 + +fn new_tuple(x f64, y f64, z f64, w f64) Tuple { + return Tuple([x, y, z, w]) +} + +fn (a Tuple) + (b Tuple) Tuple { + mut res := []f64{len: a.len} + for i := 0; i < a.len; i++ { + res[i] = a[i] + b[i] + } + return Tuple(res) +} +fn main() { + mut a := new_tuple(12, 4.5, 6.7, 6) + b := new_tuple(12, 4.5, 6.7, 6) + a -= b + println(a - b) +} diff --git a/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.out b/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.out new file mode 100644 index 0000000..6a93514 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv:19:10: error: undefined operation `Map` - `Map` + 17 | mut a := new_map() + 18 | b := new_map() + 19 | println(a - b) + | ~~~~~ + 20 | a -= b + 21 | } +vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv:20:7: error: undefined operation `Map` - `Map` + 18 | b := new_map() + 19 | println(a - b) + 20 | a -= b + | ~~ + 21 | } diff --git a/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv b/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv new file mode 100644 index 0000000..e1332f8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_map_unknown_op_overloading_err.vv @@ -0,0 +1,21 @@ +type Map = map[string]string + +pub fn new_map() Map { + return Map({ + '23': 'str' + }) +} + +fn (a Map) + (b Map) Map { + str := b['23'] + return Map({ + '34': str + '12' + }) +} + +fn main() { + mut a := new_map() + b := new_map() + println(a - b) + a -= b +} diff --git a/v_windows/v/vlib/v/checker/tests/alias_type_exists.out b/v_windows/v/vlib/v/checker/tests/alias_type_exists.out new file mode 100644 index 0000000..d79df8a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_type_exists.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/alias_type_exists.vv:1:15: error: unknown type `Bird` + 1 | type Pigeon = Bird + | ~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/alias_type_exists.vv b/v_windows/v/vlib/v/checker/tests/alias_type_exists.vv new file mode 100644 index 0000000..da63abd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/alias_type_exists.vv @@ -0,0 +1 @@ +type Pigeon = Bird diff --git a/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.out b/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.out new file mode 100644 index 0000000..019eef5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/ambiguous_field_method_err.vv:22:4: error: ambiguous method `test` + 20 | fn main() { + 21 | b := Bar{} + 22 | b.test() + | ~~~~~~ + 23 | n := b.name + 24 | } +vlib/v/checker/tests/ambiguous_field_method_err.vv:23:9: error: ambiguous field `name` + 21 | b := Bar{} + 22 | b.test() + 23 | n := b.name + | ~~~~ + 24 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.vv b/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.vv new file mode 100644 index 0000000..fa32755 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ambiguous_field_method_err.vv @@ -0,0 +1,24 @@ +struct Foo { + name int = 5 +} +struct Bar { + Foo + Foo2 +} + +struct Foo2 { + name string +} +fn (f Foo2) test() { + println(f) +} + +fn (f Foo) test() { + println(f) +} + +fn main() { + b := Bar{} + b.test() + n := b.name +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.out b/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.out new file mode 100644 index 0000000..b88ef1a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/ambiguous_function_call.vv:2:2: error: ambiguous call to: `foo1`, may refer to fn `foo1` or variable `foo1` + 1 | fn foo1(foo1 int) { + 2 | foo1(foo1 + 1) + | ~~~~~~~~~~~~~~ + 3 | } + 4 | +vlib/v/checker/tests/ambiguous_function_call.vv:7:2: error: ambiguous call to: `foo2`, may refer to fn `foo2` or variable `foo2` + 5 | fn foo2() { + 6 | foo2 := 1 + 7 | foo2(foo2) + | ~~~~~~~~~~ + 8 | } + 9 | diff --git a/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.vv b/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.vv new file mode 100644 index 0000000..2d21224 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ambiguous_function_call.vv @@ -0,0 +1,13 @@ +fn foo1(foo1 int) { + foo1(foo1 + 1) +} + +fn foo2() { + foo2 := 1 + foo2(foo2) +} + +fn main() { + foo1(5) + foo2() +} diff --git a/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.out b/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.out new file mode 100644 index 0000000..8b9eb5d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.out @@ -0,0 +1,45 @@ +vlib/v/checker/tests/any_int_float_ban_err.vv:1:12: error: unknown type `int_literal` + 1 | type Foo = int_literal | float_literal + | ~~~~~~~~~~~ + 2 | type Fo2 = int_literal + 3 | +vlib/v/checker/tests/any_int_float_ban_err.vv:2:12: error: unknown type `int_literal` + 1 | type Foo = int_literal | float_literal + 2 | type Fo2 = int_literal + | ~~~~~~~~~~~ + 3 | + 4 | struct Int { +vlib/v/checker/tests/any_int_float_ban_err.vv:5:7: error: unknown type `int_literal` + 3 | + 4 | struct Int { + 5 | i int_literal + | ~~~~~~~~~~~ + 6 | f float_literal + 7 | } +vlib/v/checker/tests/any_int_float_ban_err.vv:6:7: error: unknown type `float_literal` + 4 | struct Int { + 5 | i int_literal + 6 | f float_literal + | ~~~~~~~~~~~~~ + 7 | } + 8 | +vlib/v/checker/tests/any_int_float_ban_err.vv:9:10: error: unknown type `int_literal` + 7 | } + 8 | + 9 | fn foo(i int_literal) int_literal { + | ~~~~~~~~~~~ + 10 | return i + 11 | } +vlib/v/checker/tests/any_int_float_ban_err.vv:13:11: error: unknown type `int_literal` + 11 | } + 12 | + 13 | fn foo2() int_literal { + | ~~~~~~~~~~~ + 14 | return 1 + 15 | } +vlib/v/checker/tests/any_int_float_ban_err.vv:14:12: error: cannot use `int literal` as type `int_literal` in return argument + 12 | + 13 | fn foo2() int_literal { + 14 | return 1 + | ^ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.vv b/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.vv new file mode 100644 index 0000000..15fab98 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/any_int_float_ban_err.vv @@ -0,0 +1,15 @@ +type Foo = int_literal | float_literal +type Fo2 = int_literal + +struct Int { + i int_literal + f float_literal +} + +fn foo(i int_literal) int_literal { + return i +} + +fn foo2() int_literal { + return 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/append_err.out b/v_windows/v/vlib/v/checker/tests/append_err.out new file mode 100644 index 0000000..4c87f24 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/append_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/append_err.vv:3:7: error: cannot append `string` to `[]int` + 1 | fn main() { + 2 | mut l := []int{} + 3 | l << 'test' + | ~~~~~~ + 4 | + 5 | _ = l << 3 +vlib/v/checker/tests/append_err.vv:5:8: error: array append cannot be used in an expression + 3 | l << 'test' + 4 | + 5 | _ = l << 3 + | ~~ + 6 | _ = (l << 3).len + 7 | } +vlib/v/checker/tests/append_err.vv:6:9: error: array append cannot be used in an expression + 4 | + 5 | _ = l << 3 + 6 | _ = (l << 3).len + | ~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/append_err.vv b/v_windows/v/vlib/v/checker/tests/append_err.vv new file mode 100644 index 0000000..bc506b6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/append_err.vv @@ -0,0 +1,7 @@ +fn main() { + mut l := []int{} + l << 'test' + + _ = l << 3 + _ = (l << 3).len +} diff --git a/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.out b/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.out new file mode 100644 index 0000000..0cd37f3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/array_append_array_type_mismatch_err.vv:3:8: error: cannot append `[]int` to `[]byte` + 1 | fn main() { + 2 | mut bc := []byte{} + 3 | bc << [0xCA, 0xFE, 0xBA, 0xBE] + | ~~~~~~~~~~~~~~~~~~~~~~~~ + 4 | println(bc) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.vv b/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.vv new file mode 100644 index 0000000..45de913 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_append_array_type_mismatch_err.vv @@ -0,0 +1,5 @@ +fn main() { + mut bc := []byte{} + bc << [0xCA, 0xFE, 0xBA, 0xBE] + println(bc) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.out b/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.out new file mode 100644 index 0000000..da5c05d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/array_builtin_redefinition.vv:7:1: error: method overrides built-in array method + 5 | // fn (a []Abc) repeat() int { return 0 } + 6 | // fn (a []Abc) reverse() int { return 0 } + 7 | fn (a []Abc) map() int { return 0 } + | ~~~~~~~~~~~~~~~~~~~~~~ + 8 | // fn (a []Abc) slice() int { return 0 } + 9 | // fn (a []Abc) sort() int { return 0 } diff --git a/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.vv b/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.vv new file mode 100644 index 0000000..809b86a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_builtin_redefinition.vv @@ -0,0 +1,11 @@ +type Abc = int + +// fn (a []Abc) filter() int { return 0 } +// fn (a []Abc) clone() int { return 0 } +// fn (a []Abc) repeat() int { return 0 } +// fn (a []Abc) reverse() int { return 0 } +fn (a []Abc) map() int { return 0 } +// fn (a []Abc) slice() int { return 0 } +// fn (a []Abc) sort() int { return 0 } +// fn (a []Abc) contains() int { return 0 } +// fn (a []Abc) index() int { return 0 } diff --git a/v_windows/v/vlib/v/checker/tests/array_cmp_err.out b/v_windows/v/vlib/v/checker/tests/array_cmp_err.out new file mode 100644 index 0000000..885a514 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_cmp_err.out @@ -0,0 +1,26 @@ +vlib/v/checker/tests/array_cmp_err.vv:2:26: error: only `==` and `!=` are defined on arrays + 1 | fn main() { + 2 | println([2, 3, 4, 6] < [2, 5]) + | ^ + 3 | println([2, 3, 4, 6] > [2, 5]) + 4 | println([2, 3, 1, 6] >= [3, 5, 7]) +vlib/v/checker/tests/array_cmp_err.vv:3:26: error: only `==` and `!=` are defined on arrays + 1 | fn main() { + 2 | println([2, 3, 4, 6] < [2, 5]) + 3 | println([2, 3, 4, 6] > [2, 5]) + | ^ + 4 | println([2, 3, 1, 6] >= [3, 5, 7]) + 5 | println([2, 3, 6, 8] <= [2, 5, 8, 9]) +vlib/v/checker/tests/array_cmp_err.vv:4:26: error: only `==` and `!=` are defined on arrays + 2 | println([2, 3, 4, 6] < [2, 5]) + 3 | println([2, 3, 4, 6] > [2, 5]) + 4 | println([2, 3, 1, 6] >= [3, 5, 7]) + | ~~ + 5 | println([2, 3, 6, 8] <= [2, 5, 8, 9]) + 6 | } +vlib/v/checker/tests/array_cmp_err.vv:5:26: error: only `==` and `!=` are defined on arrays + 3 | println([2, 3, 4, 6] > [2, 5]) + 4 | println([2, 3, 1, 6] >= [3, 5, 7]) + 5 | println([2, 3, 6, 8] <= [2, 5, 8, 9]) + | ~~ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_cmp_err.vv b/v_windows/v/vlib/v/checker/tests/array_cmp_err.vv new file mode 100644 index 0000000..ec60eb9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_cmp_err.vv @@ -0,0 +1,6 @@ +fn main() { + println([2, 3, 4, 6] < [2, 5]) + println([2, 3, 4, 6] > [2, 5]) + println([2, 3, 1, 6] >= [3, 5, 7]) + println([2, 3, 6, 8] <= [2, 5, 8, 9]) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_a.out b/v_windows/v/vlib/v/checker/tests/array_declare_element_a.out new file mode 100644 index 0000000..15335cc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_a.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/array_declare_element_a.vv:2:5: error: non-name `arr[0]` on left side of `:=` + 1 | fn main() { + 2 | arr[0] := 2 + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_a.vv b/v_windows/v/vlib/v/checker/tests/array_declare_element_a.vv new file mode 100644 index 0000000..ac07498 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_a.vv @@ -0,0 +1,3 @@ +fn main() { + arr[0] := 2 +} diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_b.out b/v_windows/v/vlib/v/checker/tests/array_declare_element_b.out new file mode 100644 index 0000000..a898165 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/array_declare_element_b.vv:3:5: error: non-name `arr[1]` on left side of `:=` + 1 | fn main() { + 2 | arr := [1, 2] + 3 | arr[1] := 1 + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_b.vv b/v_windows/v/vlib/v/checker/tests/array_declare_element_b.vv new file mode 100644 index 0000000..a87a0e5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_b.vv @@ -0,0 +1,4 @@ +fn main() { + arr := [1, 2] + arr[1] := 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_c.out b/v_windows/v/vlib/v/checker/tests/array_declare_element_c.out new file mode 100644 index 0000000..d6e2aaf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_c.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/array_declare_element_c.vv:3:8: error: non-name `arr[1][0]` on left side of `:=` + 1 | fn main() { + 2 | arr := [[1, 2], [0, 3]] + 3 | arr[1][0] := 1 + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_declare_element_c.vv b/v_windows/v/vlib/v/checker/tests/array_declare_element_c.vv new file mode 100644 index 0000000..aacad55 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_declare_element_c.vv @@ -0,0 +1,4 @@ +fn main() { + arr := [[1, 2], [0, 3]] + arr[1][0] := 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/array_element_type.out b/v_windows/v/vlib/v/checker/tests/array_element_type.out new file mode 100644 index 0000000..71a50e2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_element_type.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/array_element_type.vv:2:8: error: unknown type `abc` + 1 | fn main() { + 2 | _ = []abc{} + | ~~~ + 3 | _ = [2, ''] + 4 | } +vlib/v/checker/tests/array_element_type.vv:3:10: error: invalid array element: expected `int`, not `string` + 1 | fn main() { + 2 | _ = []abc{} + 3 | _ = [2, ''] + | ~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_element_type.vv b/v_windows/v/vlib/v/checker/tests/array_element_type.vv new file mode 100644 index 0000000..95fd275 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_element_type.vv @@ -0,0 +1,4 @@ +fn main() { + _ = []abc{} + _ = [2, ''] +} diff --git a/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.out b/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.out new file mode 100644 index 0000000..cd0b1fc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/array_filter_fn_err.vv:2:25: error: function needs exactly 1 argument + 1 | fn main() { + 2 | a1 := [1,2,3,4].filter(fn(a int, b int) bool { return a > 0 }) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 3 | println(a1) + 4 | +vlib/v/checker/tests/array_filter_fn_err.vv:5:25: error: type mismatch, should use `fn(a int) bool {...}` + 3 | println(a1) + 4 | + 5 | a2 := [1,2,3,4].filter(fn(a string) bool { return a.len > 0 }) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | println(a2) + 7 | +vlib/v/checker/tests/array_filter_fn_err.vv:8:18: error: function needs exactly 1 argument + 6 | println(a2) + 7 | + 8 | a3 := [1,2,3,4].filter(fil1) + | ~~~~~~~~~~~~ + 9 | println(a3) + 10 | +vlib/v/checker/tests/array_filter_fn_err.vv:11:25: error: type mismatch, should use `fn(a int) bool {...}` + 9 | println(a3) + 10 | + 11 | a4 := [1,2,3,4].filter(fil2) + | ~~~~ + 12 | println(a4) + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.vv b/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.vv new file mode 100644 index 0000000..27c3867 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_filter_fn_err.vv @@ -0,0 +1,21 @@ +fn main() { + a1 := [1,2,3,4].filter(fn(a int, b int) bool { return a > 0 }) + println(a1) + + a2 := [1,2,3,4].filter(fn(a string) bool { return a.len > 0 }) + println(a2) + + a3 := [1,2,3,4].filter(fil1) + println(a3) + + a4 := [1,2,3,4].filter(fil2) + println(a4) +} + +fn fil1(a int, b int) bool { + return a > 0 +} + +fn fil2(a string) bool { + return a.len > 0 +} diff --git a/v_windows/v/vlib/v/checker/tests/array_index.out b/v_windows/v/vlib/v/checker/tests/array_index.out new file mode 100644 index 0000000..d2e6795 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_index.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/array_index.vv:3:7: error: non-integer index `float literal` (array type `[2]int`) + 1 | fn fixed() { + 2 | a := [1,2]! + 3 | _ = a[0.2] + | ~~~~~ + 4 | _ = a[1] // OK + 5 | _ = a[-1] +vlib/v/checker/tests/array_index.vv:5:8: error: negative index `-1` + 3 | _ = a[0.2] + 4 | _ = a[1] // OK + 5 | _ = a[-1] + | ~~ + 6 | _ = a[2] + 7 | _ = a[1..2] // OK +vlib/v/checker/tests/array_index.vv:6:8: error: index out of range (index: 2, len: 2) + 4 | _ = a[1] // OK + 5 | _ = a[-1] + 6 | _ = a[2] + | ^ + 7 | _ = a[1..2] // OK + 8 | _ = a[2..2] // empty, OK +vlib/v/checker/tests/array_index.vv:9:11: error: index out of range (index: 3, len: 2) + 7 | _ = a[1..2] // OK + 8 | _ = a[2..2] // empty, OK + 9 | _ = a[1..3] + | ^ + 10 | _ = a[3..3] + 11 | } +vlib/v/checker/tests/array_index.vv:10:8: error: index out of range (index: 3, len: 2) + 8 | _ = a[2..2] // empty, OK + 9 | _ = a[1..3] + 10 | _ = a[3..3] + | ^ + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_index.vv b/v_windows/v/vlib/v/checker/tests/array_index.vv new file mode 100644 index 0000000..74200ff --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_index.vv @@ -0,0 +1,11 @@ +fn fixed() { + a := [1,2]! + _ = a[0.2] + _ = a[1] // OK + _ = a[-1] + _ = a[2] + _ = a[1..2] // OK + _ = a[2..2] // empty, OK + _ = a[1..3] + _ = a[3..3] +} diff --git a/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.out b/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.out new file mode 100644 index 0000000..129d9db --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv:4:7: error: cannot initialize sum type array without default value + 2 | + 3 | fn main() { + 4 | a := []Foo{len: 10} + | ~~~~~~ + 5 | println(a) + 6 | +vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv:7:13: error: cannot initialize sum type array without default value + 5 | println(a) + 6 | + 7 | fixed_a := [10]Foo{} + | ~~~~~~~~~ + 8 | println(fixed_a) + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv b/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv new file mode 100644 index 0000000..2494d52 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_init_sum_type_without_init_value_and_len_err.vv @@ -0,0 +1,9 @@ +type Foo = int | string + +fn main() { + a := []Foo{len: 10} + println(a) + + fixed_a := [10]Foo{} + println(fixed_a) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.out b/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.out new file mode 100644 index 0000000..1f438e4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/array_insert_prepend_args_err.vv:8:13: error: `array.insert()` should have 2 arguments, e.g. `insert(1, val)` + 6 | fn main() { + 7 | tree := Node{} + 8 | tree.child.insert(Node{}) + | ~~~~~~~~~~~~~~ + 9 | tree.child.insert(2.1, Node{}) + 10 | tree.child.insert('abc', Node{}) +vlib/v/checker/tests/array_insert_prepend_args_err.vv:9:20: error: the first argument of `array.insert()` should be integer + 7 | tree := Node{} + 8 | tree.child.insert(Node{}) + 9 | tree.child.insert(2.1, Node{}) + | ~~~ + 10 | tree.child.insert('abc', Node{}) + 11 | tree.child.insert(Node{}, 2) +vlib/v/checker/tests/array_insert_prepend_args_err.vv:10:20: error: the first argument of `array.insert()` should be integer + 8 | tree.child.insert(Node{}) + 9 | tree.child.insert(2.1, Node{}) + 10 | tree.child.insert('abc', Node{}) + | ~~~~~ + 11 | tree.child.insert(Node{}, 2) + 12 | tree.child.prepend() +vlib/v/checker/tests/array_insert_prepend_args_err.vv:11:20: error: the first argument of `array.insert()` should be integer + 9 | tree.child.insert(2.1, Node{}) + 10 | tree.child.insert('abc', Node{}) + 11 | tree.child.insert(Node{}, 2) + | ~~~~~~ + 12 | tree.child.prepend() + 13 | } +vlib/v/checker/tests/array_insert_prepend_args_err.vv:12:13: error: `array.prepend()` should have 1 argument, e.g. `prepend(val)` + 10 | tree.child.insert('abc', Node{}) + 11 | tree.child.insert(Node{}, 2) + 12 | tree.child.prepend() + | ~~~~~~~~~ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.vv b/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.vv new file mode 100644 index 0000000..90e27fd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_insert_prepend_args_err.vv @@ -0,0 +1,13 @@ +struct Node { +mut: + child []Node +} + +fn main() { + tree := Node{} + tree.child.insert(Node{}) + tree.child.insert(2.1, Node{}) + tree.child.insert('abc', Node{}) + tree.child.insert(Node{}, 2) + tree.child.prepend() +} diff --git a/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.out b/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.out new file mode 100644 index 0000000..78f0a64 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.out @@ -0,0 +1,56 @@ +vlib/v/checker/tests/array_insert_type_mismatch.vv:3:14: error: cannot insert `float literal` to `[]int` + 1 | fn main() { + 2 | mut a := [1, 2] + 3 | a.insert(1, 2.3) + | ~~~ + 4 | a.insert(1, 'abc') + 5 | a.insert(1, [1.1, 2.2]) +vlib/v/checker/tests/array_insert_type_mismatch.vv:4:14: error: cannot insert `string` to `[]int` + 2 | mut a := [1, 2] + 3 | a.insert(1, 2.3) + 4 | a.insert(1, 'abc') + | ~~~~~ + 5 | a.insert(1, [1.1, 2.2]) + 6 | a.insert(1, ['aa', 'bb', 'cc']) +vlib/v/checker/tests/array_insert_type_mismatch.vv:5:14: error: cannot insert `[]f64` to `[]int` + 3 | a.insert(1, 2.3) + 4 | a.insert(1, 'abc') + 5 | a.insert(1, [1.1, 2.2]) + | ~~~~~~~~~~ + 6 | a.insert(1, ['aa', 'bb', 'cc']) + 7 | a.insert(1, [[1]]) +vlib/v/checker/tests/array_insert_type_mismatch.vv:6:14: error: cannot insert `[]string` to `[]int` + 4 | a.insert(1, 'abc') + 5 | a.insert(1, [1.1, 2.2]) + 6 | a.insert(1, ['aa', 'bb', 'cc']) + | ~~~~~~~~~~~~~~~~~~ + 7 | a.insert(1, [[1]]) + 8 | a.insert(1, [[['aa']]]) +vlib/v/checker/tests/array_insert_type_mismatch.vv:7:14: error: cannot insert `[][]int` to `[]int` + 5 | a.insert(1, [1.1, 2.2]) + 6 | a.insert(1, ['aa', 'bb', 'cc']) + 7 | a.insert(1, [[1]]) + | ~~~~~ + 8 | a.insert(1, [[['aa']]]) + 9 | println(a) +vlib/v/checker/tests/array_insert_type_mismatch.vv:8:14: error: cannot insert `[][][]string` to `[]int` + 6 | a.insert(1, ['aa', 'bb', 'cc']) + 7 | a.insert(1, [[1]]) + 8 | a.insert(1, [[['aa']]]) + | ~~~~~~~~~~ + 9 | println(a) + 10 | +vlib/v/checker/tests/array_insert_type_mismatch.vv:12:14: error: cannot insert `[][][]int` to `[][]int` + 10 | + 11 | mut b := [[1, 2, 3]] + 12 | b.insert(0, [[[2]]]) + | ~~~~~~~ + 13 | b.insert(0, [[[['aa']]]]) + 14 | println(b) +vlib/v/checker/tests/array_insert_type_mismatch.vv:13:14: error: cannot insert `[][][][]string` to `[][]int` + 11 | mut b := [[1, 2, 3]] + 12 | b.insert(0, [[[2]]]) + 13 | b.insert(0, [[[['aa']]]]) + | ~~~~~~~~~~~~ + 14 | println(b) + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.vv b/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.vv new file mode 100644 index 0000000..518b44a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_insert_type_mismatch.vv @@ -0,0 +1,15 @@ +fn main() { + mut a := [1, 2] + a.insert(1, 2.3) + a.insert(1, 'abc') + a.insert(1, [1.1, 2.2]) + a.insert(1, ['aa', 'bb', 'cc']) + a.insert(1, [[1]]) + a.insert(1, [[['aa']]]) + println(a) + + mut b := [[1, 2, 3]] + b.insert(0, [[[2]]]) + b.insert(0, [[[['aa']]]]) + println(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.out b/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.out new file mode 100644 index 0000000..eaec4f9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/array_literal_modify_err.vv:2:24: error: array append cannot be used in an expression + 1 | fn main() { + 2 | mut nums := [1, 2, 3] << 4 + | ~~ + 3 | println(nums) + 4 | } +vlib/v/checker/tests/array_literal_modify_err.vv:3:2: error: `println` can not print void expressions + 1 | fn main() { + 2 | mut nums := [1, 2, 3] << 4 + 3 | println(nums) + | ~~~~~~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.vv b/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.vv new file mode 100644 index 0000000..79c7f97 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_literal_modify_err.vv @@ -0,0 +1,4 @@ +fn main() { + mut nums := [1, 2, 3] << 4 + println(nums) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.out b/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.out new file mode 100644 index 0000000..8e06495 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/array_map_arg_mismatch.vv:3:4: error: expected 1 argument, but got 0 + 1 | fn main() { + 2 | a := [1, 2, 3] + 3 | a.map() + | ~~~~~ + 4 | a.map(it * 2, 3) + 5 | } +vlib/v/checker/tests/array_map_arg_mismatch.vv:4:4: error: expected 1 argument, but got 2 + 2 | a := [1, 2, 3] + 3 | a.map() + 4 | a.map(it * 2, 3) + | ~~~~~~~~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.vv b/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.vv new file mode 100644 index 0000000..629707b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_arg_mismatch.vv @@ -0,0 +1,5 @@ +fn main() { + a := [1, 2, 3] + a.map() + a.map(it * 2, 3) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_map_fn_err.out b/v_windows/v/vlib/v/checker/tests/array_map_fn_err.out new file mode 100644 index 0000000..b4df650 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_fn_err.out @@ -0,0 +1,41 @@ +vlib/v/checker/tests/array_map_fn_err.vv:2:22: error: function needs exactly 1 argument + 1 | fn main() { + 2 | a1 := [1,2,3,4].map(fn(a int, b int) int {return a + b}) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 3 | println(a1) + 4 | +vlib/v/checker/tests/array_map_fn_err.vv:5:22: error: type mismatch, should use `fn(a int) T {...}` + 3 | println(a1) + 4 | + 5 | a2 := [1,2,3,4].map(fn(a string) string { return a }) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | println(a2) + 7 | +vlib/v/checker/tests/array_map_fn_err.vv:8:22: error: type mismatch, should use `fn(a int) T {...}` + 6 | println(a2) + 7 | + 8 | a3 := [1,2,3,4].map(fn(a string) {}) + | ~~~~~~~~~~~~~~~ + 9 | println(a3) + 10 | +vlib/v/checker/tests/array_map_fn_err.vv:11:18: error: function needs exactly 1 argument + 9 | println(a3) + 10 | + 11 | a4 := [1,2,3,4].map(add1) + | ~~~~~~~~~ + 12 | println(a4) + 13 | +vlib/v/checker/tests/array_map_fn_err.vv:14:22: error: type mismatch, should use `fn(a int) T {...}` + 12 | println(a4) + 13 | + 14 | a5 := [1,2,3,4].map(add2) + | ~~~~ + 15 | println(a5) + 16 | +vlib/v/checker/tests/array_map_fn_err.vv:17:22: error: type mismatch, should use `fn(a int) T {...}` + 15 | println(a5) + 16 | + 17 | a6 := [1,2,3,4].map(do_nothing) + | ~~~~~~~~~~ + 18 | println(a6) + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_map_fn_err.vv b/v_windows/v/vlib/v/checker/tests/array_map_fn_err.vv new file mode 100644 index 0000000..50d0241 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_fn_err.vv @@ -0,0 +1,30 @@ +fn main() { + a1 := [1,2,3,4].map(fn(a int, b int) int {return a + b}) + println(a1) + + a2 := [1,2,3,4].map(fn(a string) string { return a }) + println(a2) + + a3 := [1,2,3,4].map(fn(a string) {}) + println(a3) + + a4 := [1,2,3,4].map(add1) + println(a4) + + a5 := [1,2,3,4].map(add2) + println(a5) + + a6 := [1,2,3,4].map(do_nothing) + println(a6) +} + +fn add1(a int, b int) int { + return a + b +} + +fn add2(a string) string { + return a +} + +fn do_nothing(a string) { +} diff --git a/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.out b/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.out new file mode 100644 index 0000000..9bd41e1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/array_map_void_fn_err.vv:3:12: error: type mismatch, `println` does not return anything + 1 | fn main(){ + 2 | array := [1,2,3,4] + 3 | array.map(println(it)) + | ~~~~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.vv b/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.vv new file mode 100644 index 0000000..e429c8a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_map_void_fn_err.vv @@ -0,0 +1,4 @@ +fn main(){ + array := [1,2,3,4] + array.map(println(it)) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.out b/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.out new file mode 100644 index 0000000..bfd51b0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/array_of_interfaces_with_len_without_init.vv:14:37: error: cannot instantiate an array of interfaces without also giving a default `init:` value + 12 | + 13 | fn main() { + 14 | mut parsed_lines := []MObject{len: 9} + | ^ + 15 | println(parsed_lines) + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.vv b/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.vv new file mode 100644 index 0000000..d9bc2d7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_of_interfaces_with_len_without_init.vv @@ -0,0 +1,16 @@ +interface MObject { + give_string() string +} + +struct LeStruct { + le_string string +} + +fn (a LeStruct) give_string() string { + return 'V' +} + +fn main() { + mut parsed_lines := []MObject{len: 9} + println(parsed_lines) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.out b/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.out new file mode 100644 index 0000000..7931d92 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.out @@ -0,0 +1,35 @@ +vlib/v/checker/tests/array_or_map_assign_err.vv:3:5: error: use `array2 := array1.clone()` instead of `array2 := array1` (or use `unsafe`) + 1 | fn main() { + 2 | a1 := [1, 2, 3] + 3 | a2 := a1 + | ~~ + 4 | mut a3 := []int{} + 5 | a3 = a1 +vlib/v/checker/tests/array_or_map_assign_err.vv:5:5: error: use `array2 = array1.clone()` instead of `array2 = array1` (or use `unsafe`) + 3 | a2 := a1 + 4 | mut a3 := []int{} + 5 | a3 = a1 + | ^ + 6 | + 7 | m1 := {'one': 1} +vlib/v/checker/tests/array_or_map_assign_err.vv:8:8: error: cannot copy map: call `move` or `clone` method (or use a reference) + 6 | + 7 | m1 := {'one': 1} + 8 | m2 := m1 + | ~~ + 9 | mut m3 := map[string]int{} + 10 | m3 = m1 +vlib/v/checker/tests/array_or_map_assign_err.vv:10:7: error: cannot copy map: call `move` or `clone` method (or use a reference) + 8 | m2 := m1 + 9 | mut m3 := map[string]int{} + 10 | m3 = m1 + | ~~ + 11 | + 12 | _ = a2 +vlib/v/checker/tests/array_or_map_assign_err.vv:25:8: error: cannot copy map: call `move` or `clone` method (or use a reference) + 23 | + 24 | fn foo(mut m map[string]int) { + 25 | m2 := m + | ^ + 26 | m['foo'] = 100 + 27 | println(m) diff --git a/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.vv b/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.vv new file mode 100644 index 0000000..f2463c8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_or_map_assign_err.vv @@ -0,0 +1,29 @@ +fn main() { + a1 := [1, 2, 3] + a2 := a1 + mut a3 := []int{} + a3 = a1 + + m1 := {'one': 1} + m2 := m1 + mut m3 := map[string]int{} + m3 = m1 + + _ = a2 + _ = m2 + + mut m := {'foo':1} + foo(mut m) + + _ = a3 + _ = m1 + _ = m2 + _ = m3 +} + +fn foo(mut m map[string]int) { + m2 := m + m['foo'] = 100 + println(m) + println(m2) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.out b/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.out new file mode 100644 index 0000000..8e74b7f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.out @@ -0,0 +1,56 @@ +vlib/v/checker/tests/array_prepend_type_mismatch.vv:3:12: error: cannot prepend `float literal` to `[]int` + 1 | fn main() { + 2 | mut a := [1, 2] + 3 | a.prepend(2.3) + | ~~~ + 4 | a.prepend('abc') + 5 | a.prepend([2.2, 3.3]) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:4:12: error: cannot prepend `string` to `[]int` + 2 | mut a := [1, 2] + 3 | a.prepend(2.3) + 4 | a.prepend('abc') + | ~~~~~ + 5 | a.prepend([2.2, 3.3]) + 6 | a.prepend(['aa', 'bb', 'cc']) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:5:12: error: cannot prepend `[]f64` to `[]int` + 3 | a.prepend(2.3) + 4 | a.prepend('abc') + 5 | a.prepend([2.2, 3.3]) + | ~~~~~~~~~~ + 6 | a.prepend(['aa', 'bb', 'cc']) + 7 | a.prepend([[1]]) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:6:12: error: cannot prepend `[]string` to `[]int` + 4 | a.prepend('abc') + 5 | a.prepend([2.2, 3.3]) + 6 | a.prepend(['aa', 'bb', 'cc']) + | ~~~~~~~~~~~~~~~~~~ + 7 | a.prepend([[1]]) + 8 | a.prepend([[['aa']]]) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:7:12: error: cannot prepend `[][]int` to `[]int` + 5 | a.prepend([2.2, 3.3]) + 6 | a.prepend(['aa', 'bb', 'cc']) + 7 | a.prepend([[1]]) + | ~~~~~ + 8 | a.prepend([[['aa']]]) + 9 | println(a) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:8:12: error: cannot prepend `[][][]string` to `[]int` + 6 | a.prepend(['aa', 'bb', 'cc']) + 7 | a.prepend([[1]]) + 8 | a.prepend([[['aa']]]) + | ~~~~~~~~~~ + 9 | println(a) + 10 | +vlib/v/checker/tests/array_prepend_type_mismatch.vv:12:12: error: cannot prepend `[][][]int` to `[][]int` + 10 | + 11 | mut b := [[1, 2, 3]] + 12 | b.prepend([[[2]]]) + | ~~~~~~~ + 13 | b.prepend([[[['aa']]]]) + 14 | println(b) +vlib/v/checker/tests/array_prepend_type_mismatch.vv:13:12: error: cannot prepend `[][][][]string` to `[][]int` + 11 | mut b := [[1, 2, 3]] + 12 | b.prepend([[[2]]]) + 13 | b.prepend([[[['aa']]]]) + | ~~~~~~~~~~~~ + 14 | println(b) + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.vv b/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.vv new file mode 100644 index 0000000..925156e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_prepend_type_mismatch.vv @@ -0,0 +1,15 @@ +fn main() { + mut a := [1, 2] + a.prepend(2.3) + a.prepend('abc') + a.prepend([2.2, 3.3]) + a.prepend(['aa', 'bb', 'cc']) + a.prepend([[1]]) + a.prepend([[['aa']]]) + println(a) + + mut b := [[1, 2, 3]] + b.prepend([[[2]]]) + b.prepend([[[['aa']]]]) + println(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_sort_err.out b/v_windows/v/vlib/v/checker/tests/array_sort_err.out new file mode 100644 index 0000000..04c3dbf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_sort_err.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/array_sort_err.vv:3:6: error: expected 0 or 1 argument, but got 2 + 1 | fn main() { + 2 | mut arr := [3, 2, 1] + 3 | arr.sort(a < b, a) + | ~~~~~~~~~~~~~~ + 4 | arr.sort(a == b) + 5 | arr.sort(a > a) +vlib/v/checker/tests/array_sort_err.vv:4:9: error: `.sort()` can only use `<` or `>` comparison + 2 | mut arr := [3, 2, 1] + 3 | arr.sort(a < b, a) + 4 | arr.sort(a == b) + | ~~~~~~~~~~~~ + 5 | arr.sort(a > a) + 6 | arr.sort(c > d) +vlib/v/checker/tests/array_sort_err.vv:5:9: error: `.sort()` cannot use same argument + 3 | arr.sort(a < b, a) + 4 | arr.sort(a == b) + 5 | arr.sort(a > a) + | ~~~~~~~~~~~ + 6 | arr.sort(c > d) + 7 | } +vlib/v/checker/tests/array_sort_err.vv:6:9: error: `.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)` + 4 | arr.sort(a == b) + 5 | arr.sort(a > a) + 6 | arr.sort(c > d) + | ~~~~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/array_sort_err.vv b/v_windows/v/vlib/v/checker/tests/array_sort_err.vv new file mode 100644 index 0000000..821cfd0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_sort_err.vv @@ -0,0 +1,7 @@ +fn main() { + mut arr := [3, 2, 1] + arr.sort(a < b, a) + arr.sort(a == b) + arr.sort(a > a) + arr.sort(c > d) +} diff --git a/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.out b/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.out new file mode 100644 index 0000000..dd83a0b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/array_sort_struct_no_body_err.vv:6:5: error: custom sorting condition must be supplied for type `Foo` + 4 | + 5 | mut arr := []Foo{} + 6 | arr.sort() + | ~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.vv b/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.vv new file mode 100644 index 0000000..6b53ea9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/array_sort_struct_no_body_err.vv @@ -0,0 +1,6 @@ +struct Foo { + bar int +} + +mut arr := []Foo{} +arr.sort() diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.out b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.out new file mode 100644 index 0000000..9f838fd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.vv:4:2: error: cannot push on non-channel `i64` + 2 | ch := i64(3) + 3 | obj := 5 + 4 | ch <- obj + | ~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.vv b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.vv new file mode 100644 index 0000000..d205205 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_a.vv @@ -0,0 +1,5 @@ +fn main() { + ch := i64(3) + obj := 5 + ch <- obj +} diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.out b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.out new file mode 100644 index 0000000..61d6c99 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.vv:4:8: error: cannot assign to `obj`: expected `int`, not `string` + 2 | ch := chan string{} + 3 | mut obj := 9 + 4 | obj = <-ch + | ~~ + 5 | _ = obj + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.vv b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.vv new file mode 100644 index 0000000..3e842d9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_left_type_err_b.vv @@ -0,0 +1,6 @@ +fn main() { + ch := chan string{} + mut obj := 9 + obj = <-ch + _ = obj +} diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.out b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.out new file mode 100644 index 0000000..e16afc8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.vv:4:8: error: cannot push `string` on `chan u64` + 2 | ch := chan u64{cap: 10} + 3 | obj := 'test' + 4 | ch <- obj + | ~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.vv b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.vv new file mode 100644 index 0000000..f74184d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_a.vv @@ -0,0 +1,5 @@ +fn main() { + ch := chan u64{cap: 10} + obj := 'test' + ch <- obj +} diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.out b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.out new file mode 100644 index 0000000..ae9be38 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.vv:3:9: error: <- operator can only be used with `chan` types + 1 | fn main() { + 2 | ch := i64(3) + 3 | obj := <-ch + | ~~ + 4 | println(obj) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.vv b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.vv new file mode 100644 index 0000000..48de2bf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/arrow_op_wrong_right_type_err_b.vv @@ -0,0 +1,5 @@ +fn main() { + ch := i64(3) + obj := <-ch + println(obj) +} diff --git a/v_windows/v/vlib/v/checker/tests/asm_immutable_err.out b/v_windows/v/vlib/v/checker/tests/asm_immutable_err.out new file mode 100644 index 0000000..2153ae9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/asm_immutable_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/asm_immutable_err.vv:9:9: error: `c` is immutable, declare it with `mut` to make it mutable + 7 | add eax, b + 8 | mov c, eax + 9 | ; =r (c) // output + | ^ + 10 | ; r (a) // input + 11 | r (b) \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/asm_immutable_err.vv b/v_windows/v/vlib/v/checker/tests/asm_immutable_err.vv new file mode 100644 index 0000000..e991206 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/asm_immutable_err.vv @@ -0,0 +1,16 @@ +fn main() { + a := 100 + b := 20 + c := 0 + asm amd64 { + mov eax, a + add eax, b + mov c, eax + ; =r (c) // output + ; r (a) // input + r (b) + } + println('a: $a') // 100 + println('b: $b') // 20 + println('c: $c') // 120 +} diff --git a/v_windows/v/vlib/v/checker/tests/assert_optional_err.out b/v_windows/v/vlib/v/checker/tests/assert_optional_err.out new file mode 100644 index 0000000..9d2f49a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assert_optional_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assert_optional_err.vv:4:9: error: assert can be used only with `bool` expressions, but found `void` instead + 2 | + 3 | fn main(){ + 4 | assert os.truncate("testfile.txt", 6666) or { panic(err) } + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assert_optional_err.vv b/v_windows/v/vlib/v/checker/tests/assert_optional_err.vv new file mode 100644 index 0000000..7036616 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assert_optional_err.vv @@ -0,0 +1,5 @@ +import os + +fn main(){ + assert os.truncate("testfile.txt", 6666) or { panic(err) } +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.out b/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.out new file mode 100644 index 0000000..b04c3f7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/assign_array_init_with_no_type.vv:2:11: error: array_init: no type specified (maybe: `[]Type{}` instead of `[]`) + 1 | fn main() { + 2 | mut x := [] + | ~~ + 3 | println(x) + 4 | } +vlib/v/checker/tests/assign_array_init_with_no_type.vv:3:2: error: `println` can not print void expressions + 1 | fn main() { + 2 | mut x := [] + 3 | println(x) + | ~~~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.vv b/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.vv new file mode 100644 index 0000000..cc06a55 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_array_init_with_no_type.vv @@ -0,0 +1,4 @@ +fn main() { + mut x := [] + println(x) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.out b/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.out new file mode 100644 index 0000000..d26ab1b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.vv:8:2: error: cannot dereference a function call on the left side of an assignment, use a temporary variable + 6 | + 7 | fn main() { + 8 | *foo('s') = 1 + | ^ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.vv b/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.vv new file mode 100644 index 0000000..50aa22b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_deref_fn_call_on_left_side_err.vv @@ -0,0 +1,9 @@ +struct Foo{} + +fn foo(s string) &Foo { + return &Foo{} +} + +fn main() { + *foo('s') = 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.out new file mode 100644 index 0000000..cba2860 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_a.vv:3:2: error: operator <<= not defined on left operand type `f64` + 1 | fn main() { + 2 | mut foo := 0.5 + 3 | foo <<= 1 + | ~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.vv new file mode 100644 index 0000000..8ea403d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_a.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 0.5 + foo <<= 1 + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.out new file mode 100644 index 0000000..e5f01ca --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_b.vv:3:9: error: operator %= not defined on right operand type `string` + 1 | fn main() { + 2 | mut foo := 10 + 3 | foo %= 'hello' + | ~~~~~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.vv new file mode 100644 index 0000000..db28266 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_b.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 10 + foo %= 'hello' + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.out new file mode 100644 index 0000000..338ce5b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_c.vv:3:2: error: operator *= not defined on left operand type `string` + 1 | fn main() { + 2 | mut foo := 'hello' + 3 | foo *= 10 + | ~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.vv new file mode 100644 index 0000000..98409ac --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_c.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 'hello' + foo *= 10 + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.out new file mode 100644 index 0000000..7753629 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_d.vv:3:9: error: operator /= not defined on right operand type `bool` + 1 | fn main() { + 2 | mut foo := 1.5 + 3 | foo /= true + | ~~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.vv new file mode 100644 index 0000000..ca5c0ee --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_d.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 1.5 + foo /= true + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.out new file mode 100644 index 0000000..b8acd99 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_e.vv:3:2: error: operator `-=` not defined on left operand type `string` + 1 | fn main() { + 2 | mut foo := 'hello' + 3 | foo -= `a` + | ~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.vv new file mode 100644 index 0000000..b6a4473 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_e.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 'hello' + foo -= `a` + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.out new file mode 100644 index 0000000..7cdaa51 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_f.vv:3:9: error: invalid right operand: int -= bool + 1 | fn main() { + 2 | mut foo := 10 + 3 | foo -= false + | ~~~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.vv new file mode 100644 index 0000000..70213c4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_f.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 10 + foo -= false + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.out new file mode 100644 index 0000000..e017d7b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_g.vv:3:2: error: operator `+=` not defined on left operand type `bool` + 1 | fn main() { + 2 | mut foo := true + 3 | foo += false + | ~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.vv new file mode 100644 index 0000000..c9380f4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_g.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := true + foo += false + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.out new file mode 100644 index 0000000..07d2b6e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_h.vv:3:9: error: invalid right operand: string += bool + 1 | fn main() { + 2 | mut foo := 'hello' + 3 | foo += false + | ~~~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.vv new file mode 100644 index 0000000..51b2207 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_h.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 'hello' + foo += false + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.out b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.out new file mode 100644 index 0000000..c8450f0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_type_err_i.vv:3:9: error: invalid right operand: f64 += string + 1 | fn main() { + 2 | mut foo := 1.5 + 3 | foo += 'hello' + | ~~~~~~~ + 4 | _ = foo + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.vv new file mode 100644 index 0000000..2debbae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_type_err_i.vv @@ -0,0 +1,5 @@ +fn main() { + mut foo := 1.5 + foo += 'hello' + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.out new file mode 100644 index 0000000..d7aaac0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_a.vv:2:7: error: undefined variable: `a` + 1 | fn main() { + 2 | a := a + | ^ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.vv new file mode 100644 index 0000000..05ffdf4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_a.vv @@ -0,0 +1,4 @@ +fn main() { + a := a + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.out new file mode 100644 index 0000000..f43a641 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_b.vv:2:10: error: undefined variable: `a` + 1 | fn main() { + 2 | a, b := a, b + | ^ + 3 | println('$a, $b') + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.vv new file mode 100644 index 0000000..b77ab6c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_b.vv @@ -0,0 +1,4 @@ +fn main() { + a, b := a, b + println('$a, $b') +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.out new file mode 100644 index 0000000..a68f329 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_c.vv:2:10: error: undefined variable: `a` + 1 | fn main() { + 2 | a, b := a + 1, b * 3 + | ^ + 3 | println('$a, $b') + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.vv new file mode 100644 index 0000000..b845893 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_c.vv @@ -0,0 +1,4 @@ +fn main() { + a, b := a + 1, b * 3 + println('$a, $b') +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.out new file mode 100644 index 0000000..4a8df88 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_d.vv:2:9: error: undefined variable: `s` + 1 | fn main() { + 2 | s := '$s' + | ^ + 3 | println(s) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.vv new file mode 100644 index 0000000..f9acc47 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_d.vv @@ -0,0 +1,4 @@ +fn main() { + s := '$s' + println(s) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.out new file mode 100644 index 0000000..e505e4c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_e.vv:2:11: error: undefined variable: `a` + 1 | fn main() { + 2 | a, b := -a, -b + | ^ + 3 | println(s) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.vv new file mode 100644 index 0000000..47e4ed5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_e.vv @@ -0,0 +1,4 @@ +fn main() { + a, b := -a, -b + println(s) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.out new file mode 100644 index 0000000..1b1f926 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_f.vv:2:12: error: undefined variable: `a` + 1 | fn main() { + 2 | a, b := (-a + 1), 1 + | ^ + 3 | println('$a, $b') + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.vv new file mode 100644 index 0000000..79ec348 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_f.vv @@ -0,0 +1,4 @@ +fn main() { + a, b := (-a + 1), 1 + println('$a, $b') +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.out new file mode 100644 index 0000000..277bbaf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/assign_expr_undefined_err_g.vv:2:14: error: undefined variable: `file` + 1 | fn main() { + 2 | mut file := file.open_file('bees.pdf', 'rw', 0o666) + | ~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.vv new file mode 100644 index 0000000..b589a78 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_g.vv @@ -0,0 +1,3 @@ +fn main() { + mut file := file.open_file('bees.pdf', 'rw', 0o666) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.out new file mode 100644 index 0000000..9c647c8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_expr_undefined_err_h.vv:6:9: error: undefined variable: `n` + 4 | + 5 | fn main() { + 6 | n := f(n) + | ^ + 7 | println(n) + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.vv new file mode 100644 index 0000000..d872d7e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_h.vv @@ -0,0 +1,8 @@ +fn f(i int) int { + return i +} + +fn main() { + n := f(n) + println(n) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.out new file mode 100644 index 0000000..74705af --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_i.vv:2:23: error: undefined variable: `a` + 1 | fn main() { + 2 | mut a := []int{init: a} + | ^ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.vv new file mode 100644 index 0000000..26d689e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_i.vv @@ -0,0 +1,4 @@ +fn main() { + mut a := []int{init: a} + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.out new file mode 100644 index 0000000..5ca8f5a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_j.vv:2:12: error: undefined variable: `a` + 1 | fn main() { + 2 | mut a := [a] + | ^ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.vv new file mode 100644 index 0000000..0406da7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_j.vv @@ -0,0 +1,4 @@ +fn main() { + mut a := [a] + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.out b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.out new file mode 100644 index 0000000..1ddc411 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_expr_undefined_err_k.vv:2:19: error: undefined variable: `a` + 1 | fn main() { + 2 | mut a := {'one': a} + | ^ + 3 | println(a) + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.vv new file mode 100644 index 0000000..edca5ca --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_undefined_err_k.vv @@ -0,0 +1,4 @@ +fn main() { + mut a := {'one': a} + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.out b/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.out new file mode 100644 index 0000000..5c7e5cf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv:2:7: error: undefined variable `b` (used before declaration) + 1 | fn main() { + 2 | a := b + | ^ + 3 | b := c + 4 | c := a +vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv:3:7: error: undefined variable `c` (used before declaration) + 1 | fn main() { + 2 | a := b + 3 | b := c + | ^ + 4 | c := a + 5 | _ = a diff --git a/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv b/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv new file mode 100644 index 0000000..279d6f5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_expr_unresolved_variables_err_chain.vv @@ -0,0 +1,8 @@ +fn main() { + a := b + b := c + c := a + _ = a + _ = b + _ = c +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.out b/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.out new file mode 100644 index 0000000..6a1a663 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/assign_fn_call_on_left_side_err.vv:6:2: error: cannot call function `foo()` on the left side of an assignment + 4 | + 5 | fn main() { + 6 | foo('s') = 1 + | ~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.vv b/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.vv new file mode 100644 index 0000000..69c26dd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_fn_call_on_left_side_err.vv @@ -0,0 +1,7 @@ +fn foo(s string) int { + return 1 +} + +fn main() { + foo('s') = 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.out b/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.out new file mode 100644 index 0000000..635e020 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/assign_multi_immutable_err.vv:4:2: error: `a` is immutable, declare it with `mut` to make it mutable + 2 | a := 10 + 3 | b := 20 + 4 | a, b = 1, 2 + | ^ + 5 | + 6 | println('$a, $b') +vlib/v/checker/tests/assign_multi_immutable_err.vv:18:5: error: cannot assign to function `error` + 16 | + 17 | fn assign_fn() { + 18 | _, error = g() + | ~~~~~ + 19 | g = f() + 20 | } +vlib/v/checker/tests/assign_multi_immutable_err.vv:19:2: error: cannot assign to function `g` + 17 | fn assign_fn() { + 18 | _, error = g() + 19 | g = f() + | ^ + 20 | } + 21 | diff --git a/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.vv b/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.vv new file mode 100644 index 0000000..cfed449 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_multi_immutable_err.vv @@ -0,0 +1,21 @@ +fn main() { + a := 10 + b := 20 + a, b = 1, 2 + + println('$a, $b') +} + +fn f() int { + return 2 +} + +fn g() (int, int) { + return 1, 2 +} + +fn assign_fn() { + _, error = g() + g = f() +} + diff --git a/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.out b/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.out new file mode 100644 index 0000000..ded3db7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.out @@ -0,0 +1,81 @@ +vlib/v/checker/tests/assign_multi_mismatch.vv:5:3: error: assignment mismatch: 1 variable(s) 2 value(s) + 3 | } + 4 | + 5 | _ := 0, 0 + | ~~ + 6 | _ := f() + 7 | _, _ := f() +vlib/v/checker/tests/assign_multi_mismatch.vv:6:3: error: assignment mismatch: 1 variable(s) but `f()` returns 2 value(s) + 4 | + 5 | _ := 0, 0 + 6 | _ := f() + | ~~ + 7 | _, _ := f() + 8 | _, _ := 0, f() +vlib/v/checker/tests/assign_multi_mismatch.vv:8:12: error: cannot use multi-value (int, int) in single-value context + 6 | _ := f() + 7 | _, _ := f() + 8 | _, _ := 0, f() + | ~~~ + 9 | _, _ := f(), 0 + 10 | _, _, _ := 0, f() +vlib/v/checker/tests/assign_multi_mismatch.vv:9:9: error: cannot use multi-value (int, int) in single-value context + 7 | _, _ := f() + 8 | _, _ := 0, f() + 9 | _, _ := f(), 0 + | ~~~ + 10 | _, _, _ := 0, f() + 11 | _, _, _ := f(), 0 +vlib/v/checker/tests/assign_multi_mismatch.vv:10:15: error: cannot use multi-value (int, int) in single-value context + 8 | _, _ := 0, f() + 9 | _, _ := f(), 0 + 10 | _, _, _ := 0, f() + | ~~~ + 11 | _, _, _ := f(), 0 + 12 | _, _ := f(), f() +vlib/v/checker/tests/assign_multi_mismatch.vv:11:12: error: cannot use multi-value (int, int) in single-value context + 9 | _, _ := f(), 0 + 10 | _, _, _ := 0, f() + 11 | _, _, _ := f(), 0 + | ~~~ + 12 | _, _ := f(), f() + 13 | _, _, _, _ := f(), f() +vlib/v/checker/tests/assign_multi_mismatch.vv:12:9: error: cannot use multi-value (int, int) in single-value context + 10 | _, _, _ := 0, f() + 11 | _, _, _ := f(), 0 + 12 | _, _ := f(), f() + | ~~~ + 13 | _, _, _, _ := f(), f() + 14 | +vlib/v/checker/tests/assign_multi_mismatch.vv:13:15: error: cannot use multi-value (int, int) in single-value context + 11 | _, _, _ := f(), 0 + 12 | _, _ := f(), f() + 13 | _, _, _, _ := f(), f() + | ~~~ + 14 | + 15 | _, _ := 0, match 4 { +vlib/v/checker/tests/assign_multi_mismatch.vv:19:3: error: assignment mismatch: 1 variable(s) 2 value(s) + 17 | else { 1 } + 18 | } + 19 | _ := match 4 { + | ~~ + 20 | 1 { f() } + 21 | else { f() } +vlib/v/checker/tests/assign_multi_mismatch.vv:23:12: error: cannot use multi-value (int, int) in single-value context + 21 | else { f() } + 22 | } + 23 | _, _ := 0, match 4 { + | ~~~~~~~~~ + 24 | 1 { f() } + 25 | else { f() } +vlib/v/checker/tests/assign_multi_mismatch.vv:29:3: error: assignment mismatch: 1 variable(s) 2 value(s) + 27 | + 28 | _, _ := 0, if true { 0 } else { 1 } + 29 | _ := if true { f() } else { f() } + | ~~ + 30 | _, _ := 0, if true { f() } else { f() } +vlib/v/checker/tests/assign_multi_mismatch.vv:30:12: error: cannot use multi-value (int, int) in single-value context + 28 | _, _ := 0, if true { 0 } else { 1 } + 29 | _ := if true { f() } else { f() } + 30 | _, _ := 0, if true { f() } else { f() } + | ~~ diff --git a/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.vv b/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.vv new file mode 100644 index 0000000..d25815c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_multi_mismatch.vv @@ -0,0 +1,30 @@ +fn f() (int, int) { + return 0, 0 +} + +_ := 0, 0 +_ := f() +_, _ := f() +_, _ := 0, f() +_, _ := f(), 0 +_, _, _ := 0, f() +_, _, _ := f(), 0 +_, _ := f(), f() +_, _, _, _ := f(), f() + +_, _ := 0, match 4 { + 1 { 0 } + else { 1 } +} +_ := match 4 { + 1 { f() } + else { f() } +} +_, _ := 0, match 4 { + 1 { f() } + else { f() } +} + +_, _ := 0, if true { 0 } else { 1 } +_ := if true { f() } else { f() } +_, _ := 0, if true { f() } else { f() } diff --git a/v_windows/v/vlib/v/checker/tests/assign_mut.out b/v_windows/v/vlib/v/checker/tests/assign_mut.out new file mode 100644 index 0000000..93a1d06 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_mut.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_mut.vv:3:11: error: expecting `:=` (e.g. `mut x :=`) + 1 | fn main() { + 2 | mut z := 1 + 3 | mut z = 1 + | ^ + 4 | mut i := 2 + 5 | i, mut z = 2,3 diff --git a/v_windows/v/vlib/v/checker/tests/assign_mut.vv b/v_windows/v/vlib/v/checker/tests/assign_mut.vv new file mode 100644 index 0000000..be80643 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_mut.vv @@ -0,0 +1,6 @@ +fn main() { + mut z := 1 + mut z = 1 + mut i := 2 + i, mut z = 2,3 +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.out b/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.out new file mode 100644 index 0000000..9e1b60a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_sumtype2_err.vv:13:3: error: cannot assign to field `decl`: expected `Decl`, not `Stmt` + 11 | stmt := Stmt(Decl{}) + 12 | _ := File{ + 13 | decl: stmt + | ~~~~~~~~~~ + 14 | } + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.vv b/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.vv new file mode 100644 index 0000000..29e36ce --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_sumtype2_err.vv @@ -0,0 +1,15 @@ +type Stmt = Decl | Expr + +struct Decl {} +struct Expr {} + +struct File { + decl Decl +} + +fn main() { + stmt := Stmt(Decl{}) + _ := File{ + decl: stmt + } +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.out b/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.out new file mode 100644 index 0000000..ebb4b7a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_sumtype_err.vv:13:9: error: cannot assign to `decl`: expected `Decl`, not `Stmt` + 11 | stmt := Stmt(Decl{}) + 12 | mut decl := Decl{} + 13 | decl = stmt + | ~~~~ + 14 | _ = decl + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.vv b/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.vv new file mode 100644 index 0000000..94e6fa1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_sumtype_err.vv @@ -0,0 +1,15 @@ +type Stmt = Decl | Expr + +struct Decl {} +struct Expr {} + +struct File { + decl Decl +} + +fn main() { + stmt := Stmt(Decl{}) + mut decl := Decl{} + decl = stmt + _ = decl +} diff --git a/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.out b/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.out new file mode 100644 index 0000000..63599be --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/assign_to_typeless_variable_err.vv:2:10: error: invalid empty map initilization syntax, use e.g. map[string]int{} instead + 1 | fn main() { + 2 | val := {} + | ~~ + 3 | val = 1 + 4 | } +vlib/v/checker/tests/assign_to_typeless_variable_err.vv:3:3: error: `val` is immutable, declare it with `mut` to make it mutable + 1 | fn main() { + 2 | val := {} + 3 | val = 1 + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.vv b/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.vv new file mode 100644 index 0000000..01dbd8b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/assign_to_typeless_variable_err.vv @@ -0,0 +1,4 @@ +fn main() { + val := {} + val = 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.out b/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.out new file mode 100644 index 0000000..e6a1cac --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/bad_types_in_string_inter_lit.vv:1:12: error: expression does not return a value + 1 | println('${exit(0)}') + | ~~~~~~~ + 2 | println('${char(48)}') +vlib/v/checker/tests/bad_types_in_string_inter_lit.vv:2:12: error: expression returning type `char` cannot be used in string interpolation directly, print its address or cast it to an integer instead + 1 | println('${exit(0)}') + 2 | println('${char(48)}') + | ~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.vv b/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.vv new file mode 100644 index 0000000..e4dc26c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bad_types_in_string_inter_lit.vv @@ -0,0 +1,2 @@ +println('${exit(0)}') +println('${char(48)}') diff --git a/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.out b/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.out new file mode 100644 index 0000000..772f15d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/bin_lit_without_digit_err.vv:2:14: error: number part of this binary is not provided + 1 | fn main() { + 2 | println(0b**) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.vv b/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.vv new file mode 100644 index 0000000..a85579d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bin_lit_without_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0b**) +} diff --git a/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.out b/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.out new file mode 100644 index 0000000..e2f993a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/bin_lit_wrong_digit_err.vv:2:18: error: this binary number has unsuitable digit `2` + 1 | fn main() { + 2 | println(0b1112) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.vv b/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.vv new file mode 100644 index 0000000..b8347fa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bin_lit_wrong_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0b1112) +} diff --git a/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.out b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.out new file mode 100644 index 0000000..e8d7a31 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/bit_op_wrong_left_type_err.vv:2:10: error: left type of `&` cannot be non-integer type `float literal` + 1 | fn main() { + 2 | println(0.5 & 1) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.vv b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.vv new file mode 100644 index 0000000..60b0da5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_left_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0.5 & 1) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.out b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.out new file mode 100644 index 0000000..7b24344 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/bit_op_wrong_right_type_err.vv:2:14: error: right type of `|` cannot be non-integer type `float literal` + 1 | fn main() { + 2 | println(1 | 0.5) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.vv b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.vv new file mode 100644 index 0000000..d9da95c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bit_op_wrong_right_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(1 | 0.5) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.out b/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.out new file mode 100644 index 0000000..9385664 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/blank_ident_invalid_use.vv:2:8: error: undefined ident: `_` (may only be used in assignments) + 1 | fn main() { + 2 | _ := [_] + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.vv b/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.vv new file mode 100644 index 0000000..4be90be --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/blank_ident_invalid_use.vv @@ -0,0 +1,3 @@ +fn main() { + _ := [_] +} diff --git a/v_windows/v/vlib/v/checker/tests/blank_modify.out b/v_windows/v/vlib/v/checker/tests/blank_modify.out new file mode 100644 index 0000000..e0d02e2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/blank_modify.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/blank_modify.vv:2:2: error: cannot modify blank `_` identifier + 1 | fn main() { + 2 | _ += 1 + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/blank_modify.vv b/v_windows/v/vlib/v/checker/tests/blank_modify.vv new file mode 100644 index 0000000..0a22021 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/blank_modify.vv @@ -0,0 +1,3 @@ +fn main() { + _ += 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.out b/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.out new file mode 100644 index 0000000..44a1643 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/bool_string_cast_err.vv:2:13: error: cannot cast type `bool` to string, use `x.str()` instead + 1 | fn main() { + 2 | println(string(true)) + | ~~~~~~~~~~~~ + 3 | println(string(false)) + 4 | } +vlib/v/checker/tests/bool_string_cast_err.vv:3:13: error: cannot cast type `bool` to string, use `x.str()` instead + 1 | fn main() { + 2 | println(string(true)) + 3 | println(string(false)) + | ~~~~~~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.vv b/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.vv new file mode 100644 index 0000000..c261155 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/bool_string_cast_err.vv @@ -0,0 +1,4 @@ +fn main() { + println(string(true)) + println(string(false)) +} diff --git a/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.out b/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.out new file mode 100644 index 0000000..82b7ab5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/break_anon_fn_err.vv:4:4: error: break statement not within a loop + 2 | for true { + 3 | _ := fn () int { + 4 | break + | ~~~~~ + 5 | return 3 + 6 | }() diff --git a/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.vv b/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.vv new file mode 100644 index 0000000..26586e6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/break_anon_fn_err.vv @@ -0,0 +1,8 @@ +fn main() { + for true { + _ := fn () int { + break + return 3 + }() + } +} diff --git a/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.out b/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.out new file mode 100644 index 0000000..a462c84 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/c_fn_surplus_args.vv:6:7: error: expected 0 arguments, but got 1 + 4 | + 5 | fn main() { + 6 | C.no(1) // allowed + | ^ + 7 | C.y1() + 8 | C.y1(1) // ok +vlib/v/checker/tests/c_fn_surplus_args.vv:7:4: error: expected 1 arguments, but got 0 + 5 | fn main() { + 6 | C.no(1) // allowed + 7 | C.y1() + | ~~~~ + 8 | C.y1(1) // ok + 9 | C.y1(1, 2) +vlib/v/checker/tests/c_fn_surplus_args.vv:9:10: error: expected 1 arguments, but got 2 + 7 | C.y1() + 8 | C.y1(1) // ok + 9 | C.y1(1, 2) + | ^ + 10 | C.ret() // ok + 11 | C.ret(1) +vlib/v/checker/tests/c_fn_surplus_args.vv:11:8: error: expected 0 arguments, but got 1 + 9 | C.y1(1, 2) + 10 | C.ret() // ok + 11 | C.ret(1) + | ^ + 12 | // avoid cgen whilst warning, later above should error + 13 | main() +vlib/v/checker/tests/c_fn_surplus_args.vv:13:2: error: the `main` function cannot be called in the program + 11 | C.ret(1) + 12 | // avoid cgen whilst warning, later above should error + 13 | main() + | ~~~~~~ + 14 | C.af() // ok + 15 | C.af(3) +vlib/v/checker/tests/c_fn_surplus_args.vv:15:7: error: expected 0 arguments, but got 1 + 13 | main() + 14 | C.af() // ok + 15 | C.af(3) + | ^ + 16 | } + 17 | diff --git a/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.vv b/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.vv new file mode 100644 index 0000000..e7c6466 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/c_fn_surplus_args.vv @@ -0,0 +1,19 @@ +fn C.no() // untyped +fn C.y1(int) +fn C.ret()byte + +fn main() { + C.no(1) // allowed + C.y1() + C.y1(1) // ok + C.y1(1, 2) + C.ret() // ok + C.ret(1) + // avoid cgen whilst warning, later above should error + main() + C.af() // ok + C.af(3) +} + +[trusted] +fn C.af()int diff --git a/v_windows/v/vlib/v/checker/tests/cannot_assign_array.out b/v_windows/v/vlib/v/checker/tests/cannot_assign_array.out new file mode 100644 index 0000000..fc82be4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_assign_array.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/cannot_assign_array.vv:9:11: error: cannot assign to `ctx.vb`: expected `string`, not `[8]f64` + 7 | mut ctx := Context{} + 8 | x := 2.32 + 9 | ctx.vb = [1.1, x, 3.3, 4.4, 5.0, 6.0, 7.0, 8.9]! + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/cannot_assign_array.vv b/v_windows/v/vlib/v/checker/tests/cannot_assign_array.vv new file mode 100644 index 0000000..d6aba2d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_assign_array.vv @@ -0,0 +1,10 @@ +struct Context { + pub mut: + vb string +} + +fn main() { + mut ctx := Context{} + x := 2.32 + ctx.vb = [1.1, x, 3.3, 4.4, 5.0, 6.0, 7.0, 8.9]! +} diff --git a/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.out b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.out new file mode 100644 index 0000000..6a7960c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/cannot_cast_to_alias.vv:6:7: error: cannot convert type `int literal` to `MyType` (alias to `string`) + 4 | + 5 | fn main() { + 6 | _ := MyType(5) + | ~~~~~~~~~ + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.vv b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.vv new file mode 100644 index 0000000..1198570 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_alias.vv @@ -0,0 +1,7 @@ +module main + +type MyType = string + +fn main() { + _ := MyType(5) +} diff --git a/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.out b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.out new file mode 100644 index 0000000..9fe4c10 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/cannot_cast_to_struct.vv:10:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 8 | + 9 | fn main() { + 10 | _ := Test(Abc{}) + | ~~~~~~~~~~~ + 11 | sum := Alphabet(Xyz{}) + 12 | _ = Xyz(sum) +vlib/v/checker/tests/cannot_cast_to_struct.vv:12:6: error: cannot cast `Alphabet` to struct + 10 | _ := Test(Abc{}) + 11 | sum := Alphabet(Xyz{}) + 12 | _ = Xyz(sum) + | ~~~~~~~~ + 13 | _ = Xyz(5) + 14 | s := Abc{} +vlib/v/checker/tests/cannot_cast_to_struct.vv:13:6: error: cannot cast `int literal` to struct + 11 | sum := Alphabet(Xyz{}) + 12 | _ = Xyz(sum) + 13 | _ = Xyz(5) + | ~~~~~~ + 14 | s := Abc{} + 15 | _ = Xyz(&s) +vlib/v/checker/tests/cannot_cast_to_struct.vv:15:6: error: cannot cast `&Abc` to struct + 13 | _ = Xyz(5) + 14 | s := Abc{} + 15 | _ = Xyz(&s) + | ~~~~~~~ + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.vv b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.vv new file mode 100644 index 0000000..64cf7fa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cannot_cast_to_struct.vv @@ -0,0 +1,16 @@ +struct Abc {} +struct Xyz {} +type Alphabet = Abc | Xyz + +struct Test { + abc Alphabet +} + +fn main() { + _ := Test(Abc{}) + sum := Alphabet(Xyz{}) + _ = Xyz(sum) + _ = Xyz(5) + s := Abc{} + _ = Xyz(&s) +} diff --git a/v_windows/v/vlib/v/checker/tests/cast_err.out b/v_windows/v/vlib/v/checker/tests/cast_err.out new file mode 100644 index 0000000..c78a99d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_err.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/cast_err.vv:3:6: error: cannot cast to bool - use e.g. `some_int != 0` instead + 1 | fn test_bool_cast() { + 2 | v := 3 + 3 | _ = bool(v) + | ~~~~~~~ + 4 | _ = bool(&v) + 5 | _ = bool([2]) +vlib/v/checker/tests/cast_err.vv:4:6: error: cannot cast to bool - use e.g. `some_int != 0` instead + 2 | v := 3 + 3 | _ = bool(v) + 4 | _ = bool(&v) + | ~~~~~~~~ + 5 | _ = bool([2]) + 6 | } +vlib/v/checker/tests/cast_err.vv:5:6: error: cannot cast to bool - use e.g. `some_int != 0` instead + 3 | _ = bool(v) + 4 | _ = bool(&v) + 5 | _ = bool([2]) + | ~~~~~~~~~ + 6 | } + 7 | +vlib/v/checker/tests/cast_err.vv:9:6: error: unknown type `Foo` + 7 | + 8 | fn unknown() { + 9 | _ = Foo(3) + | ~~~~~~ + 10 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/cast_err.vv b/v_windows/v/vlib/v/checker/tests/cast_err.vv new file mode 100644 index 0000000..bcf8ad3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_err.vv @@ -0,0 +1,10 @@ +fn test_bool_cast() { + v := 3 + _ = bool(v) + _ = bool(&v) + _ = bool([2]) +} + +fn unknown() { + _ = Foo(3) +} diff --git a/v_windows/v/vlib/v/checker/tests/cast_string_err.out b/v_windows/v/vlib/v/checker/tests/cast_string_err.out new file mode 100644 index 0000000..3c09bf4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_string_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/cast_string_err.vv:2:7: error: cannot cast type `int literal` to string, use `x.str()` instead + 1 | fn main() { + 2 | a := string(1) + | ~~~~~~~~~ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/cast_string_err.vv b/v_windows/v/vlib/v/checker/tests/cast_string_err.vv new file mode 100644 index 0000000..56247ce --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_string_err.vv @@ -0,0 +1,4 @@ +fn main() { + a := string(1) + println(a) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.out b/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.out new file mode 100644 index 0000000..1b04218 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/cast_string_with_byte_err.vv:2:12: error: can not cast type `byte` to string, use `by.str()` instead. + 1 | for by in 'abc' { + 2 | println(string(by)) + | ~~~~~~~~~~ + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.vv b/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.vv new file mode 100644 index 0000000..4626e28 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_string_with_byte_err.vv @@ -0,0 +1,3 @@ +for by in 'abc' { + println(string(by)) +} diff --git a/v_windows/v/vlib/v/checker/tests/cast_void.out b/v_windows/v/vlib/v/checker/tests/cast_void.out new file mode 100644 index 0000000..ddd389a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_void.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/cast_void.vv:1:12: error: expression does not return a value so it cannot be cast + 1 | num := int(print('')) + | ~~~~~~~~~ + 2 | println(num) diff --git a/v_windows/v/vlib/v/checker/tests/cast_void.vv b/v_windows/v/vlib/v/checker/tests/cast_void.vv new file mode 100644 index 0000000..1b61f14 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/cast_void.vv @@ -0,0 +1,2 @@ +num := int(print('')) +println(num) diff --git a/v_windows/v/vlib/v/checker/tests/chan_args.out b/v_windows/v/vlib/v/checker/tests/chan_args.out new file mode 100644 index 0000000..605bcfe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_args.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/chan_args.vv:4:19: error: cannot use `int` as `&f64` in argument 1 to `chan f64.try_push` + 2 | ch := chan f64{cap: 5} + 3 | a := 2 + 4 | _ := ch.try_push(a) + | ^ + 5 | _ := ch.try_push(2.5) + 6 | b := 2.5 +vlib/v/checker/tests/chan_args.vv:5:19: error: cannot use `float literal` as `&f64` in argument 1 to `chan f64.try_push` + 3 | a := 2 + 4 | _ := ch.try_push(a) + 5 | _ := ch.try_push(2.5) + | ~~~ + 6 | b := 2.5 + 7 | _ := ch.try_pop(b) +vlib/v/checker/tests/chan_args.vv:7:18: error: `try_pop` parameter `obj` is `mut`, you need to provide `mut` e.g. `mut arg1` + 5 | _ := ch.try_push(2.5) + 6 | b := 2.5 + 7 | _ := ch.try_pop(b) + | ^ + 8 | // this should work: + 9 | _ := ch.try_push(b) +vlib/v/checker/tests/chan_args.vv:11:22: error: cannot use `int` as argument for `try_pop` (`f64` expected) + 9 | _ := ch.try_push(b) + 10 | mut c := 7 + 11 | _ := ch.try_pop(mut c) + | ^ + 12 | mut x := 12.5 + 13 | // this should work: diff --git a/v_windows/v/vlib/v/checker/tests/chan_args.vv b/v_windows/v/vlib/v/checker/tests/chan_args.vv new file mode 100644 index 0000000..ce16cd8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_args.vv @@ -0,0 +1,15 @@ +fn main() { + ch := chan f64{cap: 5} + a := 2 + _ := ch.try_push(a) + _ := ch.try_push(2.5) + b := 2.5 + _ := ch.try_pop(b) + // this should work: + _ := ch.try_push(b) + mut c := 7 + _ := ch.try_pop(mut c) + mut x := 12.5 + // this should work: + _ := ch.try_pop(mut x) +} diff --git a/v_windows/v/vlib/v/checker/tests/chan_mut.out b/v_windows/v/vlib/v/checker/tests/chan_mut.out new file mode 100644 index 0000000..e82419b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_mut.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/chan_mut.vv:8:8: error: `v` is immutable, declare it with `mut` to make it mutable + 6 | fn f(ch chan mut St) { + 7 | v := St{} + 8 | ch <- v + | ^ + 9 | mut w := St{} + 10 | ch <- w +vlib/v/checker/tests/chan_mut.vv:10:8: error: cannot push non-reference `St` on `chan mut St` + 8 | ch <- v + 9 | mut w := St{} + 10 | ch <- w + | ^ + 11 | x := &St{} + 12 | ch <- x +vlib/v/checker/tests/chan_mut.vv:12:8: error: `x` is immutable, declare it with `mut` to make it mutable + 10 | ch <- w + 11 | x := &St{} + 12 | ch <- x + | ^ + 13 | mut y := St{} + 14 | ch <- y +vlib/v/checker/tests/chan_mut.vv:14:8: error: cannot push non-reference `St` on `chan mut St` + 12 | ch <- x + 13 | mut y := St{} + 14 | ch <- y + | ^ + 15 | mut z := &St{n: 7} + 16 | // this works diff --git a/v_windows/v/vlib/v/checker/tests/chan_mut.vv b/v_windows/v/vlib/v/checker/tests/chan_mut.vv new file mode 100644 index 0000000..1250c51 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_mut.vv @@ -0,0 +1,27 @@ +struct St{ +mut: + n int +} + +fn f(ch chan mut St) { + v := St{} + ch <- v + mut w := St{} + ch <- w + x := &St{} + ch <- x + mut y := St{} + ch <- y + mut z := &St{n: 7} + // this works + ch <- z +} + +fn main() { + c := chan mut St{} + go f(c) + mut y := <-c + z := <-c + println(y) + println(z) +} diff --git a/v_windows/v/vlib/v/checker/tests/chan_ref.out b/v_windows/v/vlib/v/checker/tests/chan_ref.out new file mode 100644 index 0000000..3eeb76b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_ref.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/chan_ref.vv:10:8: error: cannot push non-reference `St` on `chan &St` + 8 | fn f(ch chan &St, mut sem sync.Semaphore) { + 9 | w := St{} + 10 | ch <- w + | ^ + 11 | mut x := St{} + 12 | ch <- x +vlib/v/checker/tests/chan_ref.vv:12:8: error: cannot push non-reference `St` on `chan &St` + 10 | ch <- w + 11 | mut x := St{} + 12 | ch <- x + | ^ + 13 | // the following works + 14 | y := &St{} diff --git a/v_windows/v/vlib/v/checker/tests/chan_ref.vv b/v_windows/v/vlib/v/checker/tests/chan_ref.vv new file mode 100644 index 0000000..ad3dcec --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/chan_ref.vv @@ -0,0 +1,33 @@ +import sync + +struct St{ +mut: + n int +} + +fn f(ch chan &St, mut sem sync.Semaphore) { + w := St{} + ch <- w + mut x := St{} + ch <- x + // the following works + y := &St{} + ch <- y + mut z := &St{} + ch <- z + sem.wait() + println(z) +} + +fn main() { + c := chan &St{} + mut sem := sync.new_semaphore() + go f(c, mut sem) + y := <-c + // this should fail + mut z := <-c + z.n = 9 + sem.post() + println(y) + println(z) +} diff --git a/v_windows/v/vlib/v/checker/tests/char_str.out b/v_windows/v/vlib/v/checker/tests/char_str.out new file mode 100644 index 0000000..9779e64 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/char_str.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/char_str.vv:1:1: error: calling `.str()` on type `char` is not allowed, use its address or cast it to an integer instead + 1 | char(91).str() + | ~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/char_str.vv b/v_windows/v/vlib/v/checker/tests/char_str.vv new file mode 100644 index 0000000..88d9a9b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/char_str.vv @@ -0,0 +1 @@ +char(91).str() diff --git a/v_windows/v/vlib/v/checker/tests/closure_immutable.out b/v_windows/v/vlib/v/checker/tests/closure_immutable.out new file mode 100644 index 0000000..bb93b27 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/closure_immutable.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/closure_immutable.vv:4:3: error: `a` is immutable, declare it with `mut` to make it mutable + 2 | a := 1 + 3 | f1 := fn [a] () { + 4 | a++ + | ^ + 5 | println(a) + 6 | } +vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable, declare it with `mut` to make it mutable + 5 | println(a) + 6 | } + 7 | f2 := fn [mut a] () { + | ^ + 8 | a++ + 9 | println(a) +vlib/v/checker/tests/closure_immutable.vv:13:3: error: `b` is immutable, declare it with `mut` to make it mutable + 11 | mut b := 2 + 12 | f3 := fn [b] () { + 13 | b++ + | ^ + 14 | println(b) + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/closure_immutable.vv b/v_windows/v/vlib/v/checker/tests/closure_immutable.vv new file mode 100644 index 0000000..7b7369d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/closure_immutable.vv @@ -0,0 +1,19 @@ +fn my_fn() { + a := 1 + f1 := fn [a] () { + a++ + println(a) + } + f2 := fn [mut a] () { + a++ + println(a) + } + mut b := 2 + f3 := fn [b] () { + b++ + println(b) + } + f1() + f2() + f3() +} diff --git a/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.out b/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.out new file mode 100644 index 0000000..3b8aae9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.vv:12:7: error: possible type mismatch of compared values of `==` operation + 10 | x := ityp == ast.string_type + 11 | // the next line should produce at least a warning, or even an error, without an explicit cast: + 12 | z := isym == ast.string_type + | ~~~~~~~~~~~~~~~~~~~~~~~ + 13 | println(typeof(isym).name) + 14 | println(typeof(ast.string_type).name) diff --git a/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.vv b/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.vv new file mode 100644 index 0000000..6a307d3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comparing_typesymbol_to_a_type_should_not_compile.vv @@ -0,0 +1,17 @@ +import v.ast + +fn main() { + t := ast.new_table() + ityp := ast.int_type + isym := t.get_type_symbol(ityp) + println(ityp.debug()) + println(isym) + println(isym.debug()) + x := ityp == ast.string_type + // the next line should produce at least a warning, or even an error, without an explicit cast: + z := isym == ast.string_type + println(typeof(isym).name) + println(typeof(ast.string_type).name) + println(x) + println(z) +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_call_method.out b/v_windows/v/vlib/v/checker/tests/comptime_call_method.out new file mode 100644 index 0000000..de71203 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_call_method.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comptime_call_method.vv:10:14: error: undefined ident: `wrong` + 8 | s1 := S1{} + 9 | $for method in S1.methods { + 10 | s1.$method(wrong) + | ~~~~~ + 11 | } + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/comptime_call_method.vv b/v_windows/v/vlib/v/checker/tests/comptime_call_method.vv new file mode 100644 index 0000000..2c21789 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_call_method.vv @@ -0,0 +1,12 @@ +struct S1 {} + +fn (t S1) m(s string) int { + return 7 +} + +fn test_methods_arg() { + s1 := S1{} + $for method in S1.methods { + s1.$method(wrong) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.out b/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.out new file mode 100644 index 0000000..17aae7c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/comptime_call_no_unused_var.vv:11:8: error: unknown identifier `w` + 9 | abc := 'print' + 10 | test.$abc() // OK + 11 | test.$w() + | ^ + 12 | v := 4 + 13 | test.$v() +vlib/v/checker/tests/comptime_call_no_unused_var.vv:13:8: error: invalid string method call: expected `string`, not `int` + 11 | test.$w() + 12 | v := 4 + 13 | test.$v() + | ^ + 14 | s := 'x' + 'y' + 15 | test.$s() +vlib/v/checker/tests/comptime_call_no_unused_var.vv:15:8: error: todo: not a string literal + 13 | test.$v() + 14 | s := 'x' + 'y' + 15 | test.$s() + | ^ + 16 | s2 := 'x' + 17 | test.$s2() +vlib/v/checker/tests/comptime_call_no_unused_var.vv:17:8: error: could not find method `x` + 15 | test.$s() + 16 | s2 := 'x' + 17 | test.$s2() + | ~~ + 18 | } diff --git a/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.vv b/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.vv new file mode 100644 index 0000000..b38c031 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_call_no_unused_var.vv @@ -0,0 +1,18 @@ +struct Test {} + +fn (test Test) print() { + println('test') +} + +fn main() { + test := Test{} + abc := 'print' + test.$abc() // OK + test.$w() + v := 4 + test.$v() + s := 'x' + 'y' + test.$s() + s2 := 'x' + test.$s2() +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.run.out new file mode 100644 index 0000000..098d716 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.run.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/comptime_env/env_parser_errors_1.vv:1:1: error: supply an env variable name like HOME, PATH or USER + 1 | #flag -I $env('')/xyz + | ~~~~~~~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.vv b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.vv new file mode 100644 index 0000000..83b2ff3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_1.vv @@ -0,0 +1 @@ +#flag -I $env('')/xyz diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.run.out new file mode 100644 index 0000000..0cc6a11 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.run.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/comptime_env/env_parser_errors_2.vv:1:1: error: cannot use string interpolation in compile time $env() expression + 1 | #flag -I $env('$ABC')/xyz + | ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.vv b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.vv new file mode 100644 index 0000000..abd0b76 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_2.vv @@ -0,0 +1 @@ +#flag -I $env('$ABC')/xyz diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.run.out new file mode 100644 index 0000000..eab0231 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.run.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/comptime_env/env_parser_errors_3.vv:1:1: error: no "$env('...')" could be found in "-I $env()/xyz". + 1 | #flag -I $env()/xyz + | ~~~~~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.vv b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.vv new file mode 100644 index 0000000..98fc6fe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/env_parser_errors_3.vv @@ -0,0 +1 @@ +#flag -I $env()/xyz diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.run.out new file mode 100644 index 0000000..962652a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.run.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/comptime_env/using_comptime_env.vv:1:1: error: the environment variable "VAR" does not exist. + 1 | #flag -I $env('VAR')/xyz + | ~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | #include "$env('VAR')/stdio.h" + 3 | +vlib/v/checker/tests/comptime_env/using_comptime_env.vv:2:1: error: the environment variable "VAR" does not exist. + 1 | #flag -I $env('VAR')/xyz + 2 | #include "$env('VAR')/stdio.h" + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 3 | + 4 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var.run.out new file mode 100644 index 0000000..012aa03 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var.run.out @@ -0,0 +1,2 @@ +/usr/include +done diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var_invalid.run.out b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var_invalid.run.out new file mode 100644 index 0000000..0739bb2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.var_invalid.run.out @@ -0,0 +1 @@ +builder error: '/opt/invalid/path/stdio.h' not found diff --git a/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.vv b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.vv new file mode 100644 index 0000000..f49508c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_env/using_comptime_env.vv @@ -0,0 +1,8 @@ +#flag -I $env('VAR')/xyz +#include "$env('VAR')/stdio.h" + +fn main() { + env := $env('VAR') + println(env) + println('done') +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out new file mode 100644 index 0000000..bc3e1f9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv:9:6: error: expected selector expression e.g. `$(field.name)` + 7 | mut t := T{} + 8 | name := 'test' + 9 | t.$(name) = '3' + | ~~~~ + 10 | } + 11 | diff --git a/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv new file mode 100644 index 0000000..bbbf854 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv @@ -0,0 +1,14 @@ +struct Foo { + test int + name string +} + +fn test() { + mut t := T{} + name := 'test' + t.$(name) = '3' +} + +fn main() { + test() +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.out b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.out new file mode 100644 index 0000000..c53955c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/comptime_field_selector_not_name_err.vv:10:8: error: expected `string` instead of `FieldData` (e.g. `field.name`) + 8 | $for f in T.fields { + 9 | $if f.typ is string { + 10 | t.$(f) = '3' + | ^ + 11 | fv := Foo{} + 12 | _ = t.$(fv.name) +vlib/v/checker/tests/comptime_field_selector_not_name_err.vv:12:12: error: unknown `$for` variable `fv` + 10 | t.$(f) = '3' + 11 | fv := Foo{} + 12 | _ = t.$(fv.name) + | ~~ + 13 | } + 14 | } +vlib/v/checker/tests/comptime_field_selector_not_name_err.vv:15:10: error: undefined ident: `f` + 13 | } + 14 | } + 15 | _ = t.$(f.name) + | ^ + 16 | } + 17 | diff --git a/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv new file mode 100644 index 0000000..9efd144 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv @@ -0,0 +1,20 @@ +struct Foo { + test int + name string +} + +fn test() { + mut t := T{} + $for f in T.fields { + $if f.typ is string { + t.$(f) = '3' + fv := Foo{} + _ = t.$(fv.name) + } + } + _ = t.$(f.name) +} + +fn main() { + test() +} diff --git a/v_windows/v/vlib/v/checker/tests/comptime_for.out b/v_windows/v/vlib/v/checker/tests/comptime_for.out new file mode 100644 index 0000000..0eea091 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_for.out @@ -0,0 +1,55 @@ +vlib/v/checker/tests/comptime_for.vv:2:12: error: unknown type `Huh` + 1 | fn unknown() { + 2 | $for m in Huh.methods {} + | ~~~ + 3 | $for f in Huh.fields {} + 4 | $for f in T.fields { +vlib/v/checker/tests/comptime_for.vv:3:12: error: unknown type `Huh` + 1 | fn unknown() { + 2 | $for m in Huh.methods {} + 3 | $for f in Huh.fields {} + | ~~~ + 4 | $for f in T.fields { + 5 | $if f.typ is Huh {} +vlib/v/checker/tests/comptime_for.vv:4:12: error: unknown type `T` + 2 | $for m in Huh.methods {} + 3 | $for f in Huh.fields {} + 4 | $for f in T.fields { + | ^ + 5 | $if f.typ is Huh {} + 6 | $if f.typ is T {} +vlib/v/checker/tests/comptime_for.vv:5:16: error: unknown type `Huh` + 3 | $for f in Huh.fields {} + 4 | $for f in T.fields { + 5 | $if f.typ is Huh {} + | ~~~ + 6 | $if f.typ is T {} + 7 | } +vlib/v/checker/tests/comptime_for.vv:6:16: error: unknown type `T` + 4 | $for f in T.fields { + 5 | $if f.typ is Huh {} + 6 | $if f.typ is T {} + | ^ + 7 | } + 8 | _ = m +vlib/v/checker/tests/comptime_for.vv:8:6: error: undefined ident: `m` + 6 | $if f.typ is T {} + 7 | } + 8 | _ = m + | ^ + 9 | } + 10 | +vlib/v/checker/tests/comptime_for.vv:14:16: error: unknown type `U` + 12 | $for f in T.fields { + 13 | $if f.typ is T {} + 14 | $if f.typ is U {} + | ^ + 15 | } + 16 | _ = f +vlib/v/checker/tests/comptime_for.vv:16:6: error: undefined ident: `f` + 14 | $if f.typ is U {} + 15 | } + 16 | _ = f + | ^ + 17 | } + 18 | diff --git a/v_windows/v/vlib/v/checker/tests/comptime_for.vv b/v_windows/v/vlib/v/checker/tests/comptime_for.vv new file mode 100644 index 0000000..a6d67a0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/comptime_for.vv @@ -0,0 +1,23 @@ +fn unknown() { + $for m in Huh.methods {} + $for f in Huh.fields {} + $for f in T.fields { + $if f.typ is Huh {} + $if f.typ is T {} + } + _ = m +} + +fn gf() { + $for f in T.fields { + $if f.typ is T {} + $if f.typ is U {} + } + _ = f +} + +struct S1 { + i int +} + +gf() diff --git a/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.out b/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.out new file mode 100644 index 0000000..8b55d3f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/const_array_unknown_type_err.vv:1:11: error: unknown type `BB`. +Did you mean `AA`? + 1 | type AA = [20]BB + | ~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.vv b/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.vv new file mode 100644 index 0000000..88a2bf7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_array_unknown_type_err.vv @@ -0,0 +1 @@ +type AA = [20]BB diff --git a/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.out b/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.out new file mode 100644 index 0000000..e3b3544 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_define_in_function_err.vv:2:2: error: const can only be defined at the top level (outside of functions) + 1 | fn main() { + 2 | const (a = 1) + | ~~~~~ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.vv b/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.vv new file mode 100644 index 0000000..e6da548 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_define_in_function_err.vv @@ -0,0 +1,4 @@ +fn main() { + const (a = 1) + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/const_field_add_err.out b/v_windows/v/vlib/v/checker/tests/const_field_add_err.out new file mode 100644 index 0000000..1250582 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_add_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_field_add_err.vv:6:2: error: cannot modify constant `a` + 4 | + 5 | fn main() { + 6 | a += 1 + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_add_err.vv b/v_windows/v/vlib/v/checker/tests/const_field_add_err.vv new file mode 100644 index 0000000..24a6bb5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_add_err.vv @@ -0,0 +1,7 @@ +const ( + a = 1 +) + +fn main() { + a += 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/const_field_dec_err.out b/v_windows/v/vlib/v/checker/tests/const_field_dec_err.out new file mode 100644 index 0000000..e0b9acb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_dec_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_field_dec_err.vv:6:2: error: cannot modify constant `a` + 4 | + 5 | fn main() { + 6 | a-- + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_dec_err.vv b/v_windows/v/vlib/v/checker/tests/const_field_dec_err.vv new file mode 100644 index 0000000..0f99839 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_dec_err.vv @@ -0,0 +1,7 @@ +const ( + a = 1 +) + +fn main() { + a-- +} diff --git a/v_windows/v/vlib/v/checker/tests/const_field_inc_err.out b/v_windows/v/vlib/v/checker/tests/const_field_inc_err.out new file mode 100644 index 0000000..adeee60 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_inc_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_field_inc_err.vv:6:2: error: cannot modify constant `a` + 4 | + 5 | fn main() { + 6 | a++ + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_inc_err.vv b/v_windows/v/vlib/v/checker/tests/const_field_inc_err.vv new file mode 100644 index 0000000..9d0d96e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_inc_err.vv @@ -0,0 +1,7 @@ +const ( + a = 1 +) + +fn main() { + a++ +} diff --git a/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.out new file mode 100644 index 0000000..e887c13 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/const_field_name_duplicate_err.vv:3:2: error: duplicate const `aaa` + 1 | const ( + 2 | aaa = 1 + 3 | aaa = 2 + | ~~~ + 4 | ) + 5 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.vv new file mode 100644 index 0000000..f489f51 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_name_duplicate_err.vv @@ -0,0 +1,7 @@ +const ( + aaa = 1 + aaa = 2 +) +fn main() { + println(aaa) +} diff --git a/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.out b/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.out new file mode 100644 index 0000000..ff997e4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_field_name_snake_case.vv:2:2: warning: const names cannot contain uppercase letters, use snake_case instead + 1 | const ( + 2 | Red = 1 + | ~~~ + 3 | ) + 4 | fn main() { println(Red) } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.vv b/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.vv new file mode 100644 index 0000000..cd8af8e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_name_snake_case.vv @@ -0,0 +1,4 @@ +const ( + Red = 1 +) +fn main() { println(Red) } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_sub_err.out b/v_windows/v/vlib/v/checker/tests/const_field_sub_err.out new file mode 100644 index 0000000..782520b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_sub_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/const_field_sub_err.vv:6:2: error: cannot modify constant `a` + 4 | + 5 | fn main() { + 6 | a -= 1 + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/const_field_sub_err.vv b/v_windows/v/vlib/v/checker/tests/const_field_sub_err.vv new file mode 100644 index 0000000..e6c021b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/const_field_sub_err.vv @@ -0,0 +1,7 @@ +const ( + a = 1 +) + +fn main() { + a -= 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/ctdefine.out b/v_windows/v/vlib/v/checker/tests/ctdefine.out new file mode 100644 index 0000000..008a7d6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ctdefine.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/ctdefine.vv:4:1: error: only functions that do NOT return values can have `[if test]` tags + 2 | + 3 | [if test] + 4 | fn only_called_in_test() string { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | return 'bah' + 6 | } +vlib/v/checker/tests/ctdefine.vv:1:1: error: project must include a `main` module or be a shared library (compile with `v -shared`) + 1 | module notmain + | ^ + 2 | + 3 | [if test] diff --git a/v_windows/v/vlib/v/checker/tests/ctdefine.vv b/v_windows/v/vlib/v/checker/tests/ctdefine.vv new file mode 100644 index 0000000..20d1eb0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ctdefine.vv @@ -0,0 +1,6 @@ +module notmain + +[if test] +fn only_called_in_test() string { + return 'bah' +} diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.mysymbol.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.mysymbol.run.out new file mode 100644 index 0000000..97a3e70 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.mysymbol.run.out @@ -0,0 +1,2 @@ +optional compitme define works +non optional comptime define works diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.out new file mode 100644 index 0000000..b916d1d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/custom_comptime_define_error.vv:6:13: error: undefined ident: `mysymbol` + 4 | println('optional compitme define works') + 5 | } + 6 | $if mysymbol { + | ~~~~~~~~ + 7 | // this will produce a checker error when `-d mysymbol` is not given on the CLI + 8 | println('non optional comptime define works') diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.vv b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.vv new file mode 100644 index 0000000..34f9967 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_error.vv @@ -0,0 +1,10 @@ +fn main() { + $if mysymbol? { + // this should not produce checker errors, but will print only with `-d mysymbol` + println('optional compitme define works') + } + $if mysymbol { + // this will produce a checker error when `-d mysymbol` is not given on the CLI + println('non optional comptime define works') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.cg.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.cg.run.out new file mode 100644 index 0000000..b8cdbba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.cg.run.out @@ -0,0 +1,3 @@ +main with debug +foo, x: 123 +done diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.bar.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.bar.run.out new file mode 100644 index 0000000..702459b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.bar.run.out @@ -0,0 +1,3 @@ +foo, x: 123 +bar, x: 456 +done diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.run.out new file mode 100644 index 0000000..8cdd401 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.debug.run.out @@ -0,0 +1,2 @@ +foo, x: 123 +done diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.g.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.g.run.out new file mode 100644 index 0000000..b8cdbba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.g.run.out @@ -0,0 +1,3 @@ +main with debug +foo, x: 123 +done diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.run.out new file mode 100644 index 0000000..19f86f4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.run.out @@ -0,0 +1 @@ +done diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.vv b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.vv new file mode 100644 index 0000000..380f738 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_debug.vv @@ -0,0 +1,18 @@ +[if debug] +fn foo(x int) { + println('foo, x: $x') +} + +[if bar ?] +fn bar(x int) { + println('bar, x: $x') +} + +fn main() { + $if debug { + println('main with debug') + } + foo(123) // will not be called if `-d debug` is not passed + bar(456) // will not be called if `-d bar` is not passed + println('done') +} diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.mydebug.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.mydebug.run.out new file mode 100644 index 0000000..7cf4d92 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.mydebug.run.out @@ -0,0 +1,4 @@ +start +message: verbose message +message: debugging system +end diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.nodebug.run.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.nodebug.run.out new file mode 100644 index 0000000..5d0fb3b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.nodebug.run.out @@ -0,0 +1,2 @@ +start +end diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.out b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.vv b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.vv new file mode 100644 index 0000000..23e946a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/custom_comptime_define_if_flag.vv @@ -0,0 +1,13 @@ +// Calls to edebug/1 should be executed only when `-d mydebug` is passed on the CLI +// Otherwise they will not be present *at all* in the generated code. +[if mydebug ?] +fn edebug(message string) { + println('message: $message') +} + +fn main() { + println('start') + edebug('verbose message') + edebug('debugging system') + println('end') +} diff --git a/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.out b/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.out new file mode 100644 index 0000000..9166f4c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/dec_lit_wrong_digit_err.vv:2:18: error: this number has unsuitable digit `q` + 1 | fn main() { + 2 | println(12345qrst+10) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.vv b/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.vv new file mode 100644 index 0000000..c8c9583 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/dec_lit_wrong_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(12345qrst+10) +} diff --git a/v_windows/v/vlib/v/checker/tests/decompose_type_err.out b/v_windows/v/vlib/v/checker/tests/decompose_type_err.out new file mode 100644 index 0000000..95d56f6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/decompose_type_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/decompose_type_err.vv:4:13: error: decomposition can only be used on arrays + 2 | + 3 | fn main() { + 4 | varargs(...123) + | ~~~ + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/decompose_type_err.vv b/v_windows/v/vlib/v/checker/tests/decompose_type_err.vv new file mode 100644 index 0000000..d5b43ba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/decompose_type_err.vv @@ -0,0 +1,5 @@ +fn varargs(a ...int) { println(a) } + +fn main() { + varargs(...123) +} diff --git a/v_windows/v/vlib/v/checker/tests/defer_in_for.out b/v_windows/v/vlib/v/checker/tests/defer_in_for.out new file mode 100644 index 0000000..59faded --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/defer_in_for.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_in_for.vv:4:13: error: `break` is not allowed in defer statements + 2 | for true { + 3 | defer { + 4 | break + | ~~~~~ + 5 | } + 6 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/defer_in_for.vv b/v_windows/v/vlib/v/checker/tests/defer_in_for.vv new file mode 100644 index 0000000..bb2525f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/defer_in_for.vv @@ -0,0 +1,7 @@ +fn main() { + for true { + defer { + break + } + } +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/defer_optional.out b/v_windows/v/vlib/v/checker/tests/defer_optional.out new file mode 100644 index 0000000..184e35e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/defer_optional.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_optional.vv:5:3: error: opt() returns an option, so it should have an `or {}` block at the end + 3 | fn thing() ?string { + 4 | defer { + 5 | opt() + | ~~~~~ + 6 | } + 7 | return 'ok' \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/defer_optional.vv b/v_windows/v/vlib/v/checker/tests/defer_optional.vv new file mode 100644 index 0000000..f153430 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/defer_optional.vv @@ -0,0 +1,8 @@ +fn opt() ? {} + +fn thing() ?string { + defer { + opt() + } + return 'ok' +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/deprecations.out b/v_windows/v/vlib/v/checker/tests/deprecations.out new file mode 100644 index 0000000..6c1e8f8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/deprecations.out @@ -0,0 +1,55 @@ +vlib/v/checker/tests/deprecations.vv:60:2: notice: function `future` will be deprecated after 3000-12-30, and will become an error after 3001-06-28; custom message 4 + 58 | + 59 | fn main() { + 60 | future() + | ~~~~~~~~ + 61 | past() + 62 | simply_deprecated() +vlib/v/checker/tests/deprecations.vv:67:4: notice: method `Abc.future` will be deprecated after 3000-11-01, and will become an error after 3001-04-30; custom message 1 + 65 | // + 66 | a := Abc{} + 67 | a.future() + | ~~~~~~~~ + 68 | a.past() + 69 | a.simply_deprecated() +vlib/v/checker/tests/deprecations.vv:61:2: error: function `past` has been deprecated since 2021-03-01; custom message 5 + 59 | fn main() { + 60 | future() + 61 | past() + | ~~~~~~ + 62 | simply_deprecated() + 63 | just_deprecated() +vlib/v/checker/tests/deprecations.vv:62:2: error: function `simply_deprecated` has been deprecated; custom message 7 + 60 | future() + 61 | past() + 62 | simply_deprecated() + | ~~~~~~~~~~~~~~~~~~~ + 63 | just_deprecated() + 64 | ancient() +vlib/v/checker/tests/deprecations.vv:63:2: error: function `just_deprecated` has been deprecated + 61 | past() + 62 | simply_deprecated() + 63 | just_deprecated() + | ~~~~~~~~~~~~~~~~~ + 64 | ancient() + 65 | // +vlib/v/checker/tests/deprecations.vv:64:2: error: function `ancient` has been deprecated since 1990-03-01; custom message 6 + 62 | simply_deprecated() + 63 | just_deprecated() + 64 | ancient() + | ~~~~~~~~~ + 65 | // + 66 | a := Abc{} +vlib/v/checker/tests/deprecations.vv:68:4: error: method `Abc.past` has been deprecated since 2021-03-01; custom message 2 + 66 | a := Abc{} + 67 | a.future() + 68 | a.past() + | ~~~~~~ + 69 | a.simply_deprecated() + 70 | } +vlib/v/checker/tests/deprecations.vv:69:4: error: method `Abc.simply_deprecated` has been deprecated; custom message 3 + 67 | a.future() + 68 | a.past() + 69 | a.simply_deprecated() + | ~~~~~~~~~~~~~~~~~~~ + 70 | } diff --git a/v_windows/v/vlib/v/checker/tests/deprecations.vv b/v_windows/v/vlib/v/checker/tests/deprecations.vv new file mode 100644 index 0000000..a0a2b8f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/deprecations.vv @@ -0,0 +1,70 @@ +// methods using [deprecated_after]: +struct Abc { + x int +} + +fn (a Abc) str() string { + return 'Abc { x: $a.x }' +} + +[deprecated: 'custom message 1'] +[deprecated_after: '3000-11-01'] +fn (a Abc) future() { + dump(@METHOD) + dump(a) +} + +[deprecated: 'custom message 2'] +[deprecated_after: '2021-03-01'] +fn (a Abc) past() { + dump(@METHOD) + dump(a) +} + +[deprecated: 'custom message 3'] +fn (a Abc) simply_deprecated() { + dump(@METHOD) + dump(a) +} + +// functions using [deprecated_after]: +[deprecated: 'custom message 4'] +[deprecated_after: '3000-12-30'] +fn future() { + dump(@FN) +} + +[deprecated: 'custom message 5'] +[deprecated_after: '2021-03-01'] +fn past() { + dump(@FN) +} + +[deprecated: 'custom message 6'] +[deprecated_after: '1990-03-01'] +fn ancient() { + dump(@FN) +} + +[deprecated: 'custom message 7'] +fn simply_deprecated() { + dump(@FN) +} + +[deprecated] +fn just_deprecated() { + dump(@FN) +} + +fn main() { + future() + past() + simply_deprecated() + just_deprecated() + ancient() + // + a := Abc{} + a.future() + a.past() + a.simply_deprecated() +} diff --git a/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.out b/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.out new file mode 100644 index 0000000..d198c66 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/direct_map_alias_init_err.vv:4:7: error: direct map alias init is not possible, use `WordSet(map[string]bool{})` instead + 2 | + 3 | fn main() { + 4 | _ := WordSet{} + | ~~~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.vv b/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.vv new file mode 100644 index 0000000..5921f5b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/direct_map_alias_init_err.vv @@ -0,0 +1,5 @@ +type WordSet = map[string]bool + +fn main() { + _ := WordSet{} +} diff --git a/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.out b/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.out new file mode 100644 index 0000000..3e4422f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:4:7: error: invalid operator `+` to `&int` and `&int` + 2 | x := 5 + 3 | p := &x + 4 | _ := p + p //should be error + | ~~~~~ + 5 | _ := p * p //should be error + 6 | _ := p * 2 //should be error +vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:5:7: error: invalid operator `*` to `&int` and `&int` + 3 | p := &x + 4 | _ := p + p //should be error + 5 | _ := p * p //should be error + | ~~~~~ + 6 | _ := p * 2 //should be error + 7 | _ := p + 5 //OK but only in unsafe block, r is *int +vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:6:7: error: invalid operator `*` to `&int` and `int literal` + 4 | _ := p + p //should be error + 5 | _ := p * p //should be error + 6 | _ := p * 2 //should be error + | ~~~~~ + 7 | _ := p + 5 //OK but only in unsafe block, r is *int + 8 | _ := p - p //OK even in safe code, but n should be isize +vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:7:7: error: pointer arithmetic is only allowed in `unsafe` blocks + 5 | _ := p * p //should be error + 6 | _ := p * 2 //should be error + 7 | _ := p + 5 //OK but only in unsafe block, r is *int + | ~~~~~ + 8 | _ := p - p //OK even in safe code, but n should be isize + 9 | } +vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv:8:7: error: pointer arithmetic is only allowed in `unsafe` blocks + 6 | _ := p * 2 //should be error + 7 | _ := p + 5 //OK but only in unsafe block, r is *int + 8 | _ := p - p //OK even in safe code, but n should be isize + | ~~~~~ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv b/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv new file mode 100644 index 0000000..0fc91cd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/disallow_pointer_arithmetic_err.vv @@ -0,0 +1,9 @@ +fn main() { + x := 5 + p := &x + _ := p + p //should be error + _ := p * p //should be error + _ := p * 2 //should be error + _ := p + 5 //OK but only in unsafe block, r is *int + _ := p - p //OK even in safe code, but n should be isize +} diff --git a/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.out b/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.out new file mode 100644 index 0000000..b29d963 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.out @@ -0,0 +1,40 @@ +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:2:21: error: division by zero + 1 | fn main() { + 2 | println(u64(1)/u64(0)) + | ^ + 3 | println(u64(1)%u64(0)) + 4 | +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:3:21: error: modulo by zero + 1 | fn main() { + 2 | println(u64(1)/u64(0)) + 3 | println(u64(1)%u64(0)) + | ^ + 4 | + 5 | println(u32(1)/u32(0x0)) +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:5:21: error: division by zero + 3 | println(u64(1)%u64(0)) + 4 | + 5 | println(u32(1)/u32(0x0)) + | ~~~ + 6 | println(u32(1)%u32(0x0)) + 7 | +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:6:21: error: modulo by zero + 4 | + 5 | println(u32(1)/u32(0x0)) + 6 | println(u32(1)%u32(0x0)) + | ~~~ + 7 | + 8 | println(u16(1)/u16(0b0)) +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:8:21: error: division by zero + 6 | println(u32(1)%u32(0x0)) + 7 | + 8 | println(u16(1)/u16(0b0)) + | ~~~ + 9 | println(u16(1)%u16(0b0)) + 10 | } +vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv:9:21: error: modulo by zero + 7 | + 8 | println(u16(1)/u16(0b0)) + 9 | println(u16(1)%u16(0b0)) + | ~~~ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv b/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv new file mode 100644 index 0000000..215ae9e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/div_mod_by_cast_zero_int_err.vv @@ -0,0 +1,10 @@ +fn main() { + println(u64(1)/u64(0)) + println(u64(1)%u64(0)) + + println(u32(1)/u32(0x0)) + println(u32(1)%u32(0x0)) + + println(u16(1)/u16(0b0)) + println(u16(1)%u16(0b0)) +} diff --git a/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.out new file mode 100644 index 0000000..f79c286 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/div_op_wrong_type_err.vv:3:13: error: mismatched types `Aaa` and `int literal` + 1 | struct Aaa{} + 2 | fn main() { + 3 | println(Aaa{} / 10) + | ~~~~~~~~~~ + 4 | println(10 / Aaa{}) + 5 | println([1,2,3] / 10) +vlib/v/checker/tests/div_op_wrong_type_err.vv:4:13: error: mismatched types `int literal` and `Aaa` + 2 | fn main() { + 3 | println(Aaa{} / 10) + 4 | println(10 / Aaa{}) + | ~~~~~~~~~~ + 5 | println([1,2,3] / 10) + 6 | println(10 / [1,2,3]) +vlib/v/checker/tests/div_op_wrong_type_err.vv:5:13: error: mismatched types `[]int` and `int literal` + 3 | println(Aaa{} / 10) + 4 | println(10 / Aaa{}) + 5 | println([1,2,3] / 10) + | ~~~~~~~~~~~~ + 6 | println(10 / [1,2,3]) + 7 | a := map[string]int +vlib/v/checker/tests/div_op_wrong_type_err.vv:6:13: error: mismatched types `int literal` and `[]int` + 4 | println(10 / Aaa{}) + 5 | println([1,2,3] / 10) + 6 | println(10 / [1,2,3]) + | ~~~~~~~~~~~~ + 7 | a := map[string]int + 8 | println(a / 10) +vlib/v/checker/tests/div_op_wrong_type_err.vv:8:13: error: mismatched types `map[string]int` and `int literal` + 6 | println(10 / [1,2,3]) + 7 | a := map[string]int + 8 | println(a / 10) + | ~~~~~~ + 9 | println(10 / a) + 10 | } +vlib/v/checker/tests/div_op_wrong_type_err.vv:9:13: error: mismatched types `int literal` and `map[string]int` + 7 | a := map[string]int + 8 | println(a / 10) + 9 | println(10 / a) + | ~~~~~~ + 10 | } + diff --git a/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.vv new file mode 100644 index 0000000..fb2f1cd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/div_op_wrong_type_err.vv @@ -0,0 +1,10 @@ +struct Aaa{} +fn main() { + println(Aaa{} / 10) + println(10 / Aaa{}) + println([1,2,3] / 10) + println(10 / [1,2,3]) + a := map[string]int + println(a / 10) + println(10 / a) +} diff --git a/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.out b/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.out new file mode 100644 index 0000000..f6bbda2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/division_by_cast_zero_float_err.vv:2:23: error: division by zero + 1 | fn main() { + 2 | println(f32(1.0)/f32(0.0)) + | ~~~ + 3 | println(f64(1.0)/f64(0.0)) + 4 | } +vlib/v/checker/tests/division_by_cast_zero_float_err.vv:3:23: error: division by zero + 1 | fn main() { + 2 | println(f32(1.0)/f32(0.0)) + 3 | println(f64(1.0)/f64(0.0)) + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.vv b/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.vv new file mode 100644 index 0000000..6feee15 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_cast_zero_float_err.vv @@ -0,0 +1,4 @@ +fn main() { + println(f32(1.0)/f32(0.0)) + println(f64(1.0)/f64(0.0)) +} diff --git a/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.out b/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.out new file mode 100644 index 0000000..7a2ee8d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/division_by_zero_float_err.vv:2:14: error: division by zero + 1 | fn main() { + 2 | println(1.0/0.0) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.vv b/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.vv new file mode 100644 index 0000000..b5c1e7c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_zero_float_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(1.0/0.0) +} diff --git a/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.out b/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.out new file mode 100644 index 0000000..6b2ed19 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.out @@ -0,0 +1,26 @@ +vlib/v/checker/tests/division_by_zero_int_err.vv:2:12: error: division by zero + 1 | fn main() { + 2 | println(1/0) + | ^ + 3 | println(1/0x0) + 4 | println(1/0b0) +vlib/v/checker/tests/division_by_zero_int_err.vv:3:12: error: division by zero + 1 | fn main() { + 2 | println(1/0) + 3 | println(1/0x0) + | ~~~ + 4 | println(1/0b0) + 5 | println(1/0o0) +vlib/v/checker/tests/division_by_zero_int_err.vv:4:12: error: division by zero + 2 | println(1/0) + 3 | println(1/0x0) + 4 | println(1/0b0) + | ~~~ + 5 | println(1/0o0) + 6 | } +vlib/v/checker/tests/division_by_zero_int_err.vv:5:12: error: division by zero + 3 | println(1/0x0) + 4 | println(1/0b0) + 5 | println(1/0o0) + | ~~~ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.vv b/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.vv new file mode 100644 index 0000000..4316fda --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/division_by_zero_int_err.vv @@ -0,0 +1,6 @@ +fn main() { + println(1/0) + println(1/0x0) + println(1/0b0) + println(1/0o0) +} diff --git a/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.out b/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.out new file mode 100644 index 0000000..ac6f1b5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/dump_of_void_expr.vv:3:6: error: dump expression can not be void + 1 | fn abc() {} + 2 | + 3 | dump(abc()) + | ~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.vv b/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.vv new file mode 100644 index 0000000..57e8597 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/dump_of_void_expr.vv @@ -0,0 +1,3 @@ +fn abc() {} + +dump(abc()) diff --git a/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.out b/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.out new file mode 100644 index 0000000..b4befdd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/duplicate_field_method_err.vv:5:1: error: type `St` has both field and method named `attr` + 3 | } + 4 | + 5 | fn (s St) attr() {} + | ~~~~~~~~~~~~~~~~ + 6 | + 7 | interface Foo { +vlib/v/checker/tests/duplicate_field_method_err.vv:9:2: error: type `Foo` has both field and method named `bar` + 7 | interface Foo { + 8 | bar fn () + 9 | bar() + | ~~~~~ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.vv b/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.vv new file mode 100644 index 0000000..2c34f78 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/duplicate_field_method_err.vv @@ -0,0 +1,10 @@ +struct St{ + attr fn() +} + +fn (s St) attr() {} + +interface Foo { + bar fn () + bar() +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_as_int_err.out b/v_windows/v/vlib/v/checker/tests/enum_as_int_err.out new file mode 100644 index 0000000..cef283d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_as_int_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/enum_as_int_err.vv:9:10: error: cannot assign to `color`: expected `Color`, not `int literal` + 7 | mut color := Color.red + 8 | mut foo := 1 + 9 | color = 1 + | ^ + 10 | foo = Color.red + 11 | println(color == 0) +vlib/v/checker/tests/enum_as_int_err.vv:10:8: error: cannot assign to `foo`: expected `int`, not `Color` + 8 | mut foo := 1 + 9 | color = 1 + 10 | foo = Color.red + | ~~~~~~~~~ + 11 | println(color == 0) + 12 | _ = foo +vlib/v/checker/tests/enum_as_int_err.vv:11:10: error: infix expr: cannot use `int literal` (right expression) as `Color` + 9 | color = 1 + 10 | foo = Color.red + 11 | println(color == 0) + | ~~~~~~~~~~ + 12 | _ = foo + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_as_int_err.vv b/v_windows/v/vlib/v/checker/tests/enum_as_int_err.vv new file mode 100644 index 0000000..47605ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_as_int_err.vv @@ -0,0 +1,13 @@ +enum Color { + red + blue +} + +fn main() { + mut color := Color.red + mut foo := 1 + color = 1 + foo = Color.red + println(color == 0) + _ = foo +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_cast.out b/v_windows/v/vlib/v/checker/tests/enum_cast.out new file mode 100644 index 0000000..053fe1b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_cast.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/enum_cast.vv:6:13: error: 12 does not represents a value of enum Color + 4 | println(Color(0)) + 5 | println(Color(10)) + 6 | println(Color(12)) + | ~~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_cast.vv b/v_windows/v/vlib/v/checker/tests/enum_cast.vv new file mode 100644 index 0000000..40e9ec7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_cast.vv @@ -0,0 +1,7 @@ +enum Color { red green = 10 blue } + +fn main() { + println(Color(0)) + println(Color(10)) + println(Color(12)) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/enum_empty.out b/v_windows/v/vlib/v/checker/tests/enum_empty.out new file mode 100644 index 0000000..b2b432d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_empty.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/enum_empty.vv:1:1: error: enum cannot be empty + 1 | enum Empty {} + | ~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/enum_empty.vv b/v_windows/v/vlib/v/checker/tests/enum_empty.vv new file mode 100644 index 0000000..7afdbc0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_empty.vv @@ -0,0 +1 @@ +enum Empty {} diff --git a/v_windows/v/vlib/v/checker/tests/enum_err.out b/v_windows/v/vlib/v/checker/tests/enum_err.out new file mode 100644 index 0000000..9256d7f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/enum_err.vv:4:13: error: default value for enum has to be an integer + 2 | + 3 | enum Color { + 4 | green = 'green' + | ~~~~~~~ + 5 | yellow = 1+1 + 6 | blue +vlib/v/checker/tests/enum_err.vv:5:14: error: default value for enum has to be an integer + 3 | enum Color { + 4 | green = 'green' + 5 | yellow = 1+1 + | ~~~ + 6 | blue + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_err.vv b/v_windows/v/vlib/v/checker/tests/enum_err.vv new file mode 100644 index 0000000..1710b84 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_err.vv @@ -0,0 +1,11 @@ +module main + +enum Color { + green = 'green' + yellow = 1+1 + blue +} + +fn main(){ + println('hello') +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.out new file mode 100644 index 0000000..6f1de8d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/enum_field_name_duplicate_err.vv:5:2: error: field name `green` duplicate + 3 | yellow + 4 | blue + 5 | green + | ~~~~~ + 6 | } + 7 | diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.vv new file mode 100644 index 0000000..790d39d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_name_duplicate_err.vv @@ -0,0 +1,10 @@ +enum Color { + green + yellow + blue + green +} + +fn main(){ + println('hello') +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_overflow.out b/v_windows/v/vlib/v/checker/tests/enum_field_overflow.out new file mode 100644 index 0000000..26f5843 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_overflow.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/enum_field_overflow.vv:4:2: error: enum value overflows + 2 | red + 3 | green = 2147483647 + 4 | blue + | ~~~~ + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_overflow.vv b/v_windows/v/vlib/v/checker/tests/enum_field_overflow.vv new file mode 100644 index 0000000..8c79d29 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_overflow.vv @@ -0,0 +1,5 @@ +enum Color { + red + green = 2147483647 + blue +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.out b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.out new file mode 100644 index 0000000..ce72e69 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/enum_field_value_duplicate_a.vv:3:10: error: enum value `0` already exists + 1 | enum Color { + 2 | red + 3 | green = 0 + | ^ + 4 | blue = 1 + 5 | alpha = 1 +vlib/v/checker/tests/enum_field_value_duplicate_a.vv:5:10: error: enum value `1` already exists + 3 | green = 0 + 4 | blue = 1 + 5 | alpha = 1 + | ^ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.vv b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.vv new file mode 100644 index 0000000..49cf3c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_a.vv @@ -0,0 +1,6 @@ +enum Color { + red + green = 0 + blue = 1 + alpha = 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.out b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.out new file mode 100644 index 0000000..c1a9ae2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/enum_field_value_duplicate_b.vv:4:2: error: enum value `0` already exists + 2 | red // 0 + 3 | green = -1 + 4 | blue // -1 + 1 = 0 + | ~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.vv b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.vv new file mode 100644 index 0000000..232a265 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_duplicate_b.vv @@ -0,0 +1,5 @@ +enum Color { + red // 0 + green = -1 + blue // -1 + 1 = 0 +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.out b/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.out new file mode 100644 index 0000000..c6bb9c0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/enum_field_value_overflow.vv:3:10: error: enum value `2147483648` overflows int + 1 | enum Color { + 2 | red + 3 | green = 2147483648 + | ~~~~~~~~~~ + 4 | blue + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.vv b/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.vv new file mode 100644 index 0000000..cb9fb9a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_field_value_overflow.vv @@ -0,0 +1,5 @@ +enum Color { + red + green = 2147483648 + blue +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_op_err.out b/v_windows/v/vlib/v/checker/tests/enum_op_err.out new file mode 100644 index 0000000..1392251 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_op_err.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/enum_op_err.vv:8:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed + 6 | + 7 | fn main() { + 8 | println(Color.red > Color.green) + | ^ + 9 | println(Color.red + Color.green) + 10 | println(Color.red && Color.green) +vlib/v/checker/tests/enum_op_err.vv:9:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed + 7 | fn main() { + 8 | println(Color.red > Color.green) + 9 | println(Color.red + Color.green) + | ^ + 10 | println(Color.red && Color.green) + 11 | println(Color.red | Color.green) +vlib/v/checker/tests/enum_op_err.vv:10:10: error: left operand for `&&` is not a boolean + 8 | println(Color.red > Color.green) + 9 | println(Color.red + Color.green) + 10 | println(Color.red && Color.green) + | ~~~~~~~~~ + 11 | println(Color.red | Color.green) + 12 | println(Color.red & Color.green) +vlib/v/checker/tests/enum_op_err.vv:11:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed + 9 | println(Color.red + Color.green) + 10 | println(Color.red && Color.green) + 11 | println(Color.red | Color.green) + | ^ + 12 | println(Color.red & Color.green) + 13 | } +vlib/v/checker/tests/enum_op_err.vv:12:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed + 10 | println(Color.red && Color.green) + 11 | println(Color.red | Color.green) + 12 | println(Color.red & Color.green) + | ^ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_op_err.vv b/v_windows/v/vlib/v/checker/tests/enum_op_err.vv new file mode 100644 index 0000000..7fa48e4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_op_err.vv @@ -0,0 +1,13 @@ +enum Color { + red + blue + green +} + +fn main() { + println(Color.red > Color.green) + println(Color.red + Color.green) + println(Color.red && Color.green) + println(Color.red | Color.green) + println(Color.red & Color.green) +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.out b/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.out new file mode 100644 index 0000000..fb3e228 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/enum_op_flag_err.vv:9:24: error: only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed + 7 | + 8 | fn main() { + 9 | println(FilePerm.read > FilePerm.write) + | ^ + 10 | println(FilePerm.write + FilePerm.exec) + 11 | println(FilePerm.write && FilePerm.exec) +vlib/v/checker/tests/enum_op_flag_err.vv:10:25: error: only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed + 8 | fn main() { + 9 | println(FilePerm.read > FilePerm.write) + 10 | println(FilePerm.write + FilePerm.exec) + | ^ + 11 | println(FilePerm.write && FilePerm.exec) + 12 | } +vlib/v/checker/tests/enum_op_flag_err.vv:11:10: error: left operand for `&&` is not a boolean + 9 | println(FilePerm.read > FilePerm.write) + 10 | println(FilePerm.write + FilePerm.exec) + 11 | println(FilePerm.write && FilePerm.exec) + | ~~~~~~~~~~~~~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.vv b/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.vv new file mode 100644 index 0000000..0701255 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_op_flag_err.vv @@ -0,0 +1,12 @@ +[flag] +enum FilePerm { + read + write + exec +} + +fn main() { + println(FilePerm.read > FilePerm.write) + println(FilePerm.write + FilePerm.exec) + println(FilePerm.write && FilePerm.exec) +} diff --git a/v_windows/v/vlib/v/checker/tests/enum_single_letter.out b/v_windows/v/vlib/v/checker/tests/enum_single_letter.out new file mode 100644 index 0000000..b509c5e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_single_letter.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/enum_single_letter.vv:1:6: error: single letter capital names are reserved for generic template types. + 1 | enum E { + | ^ + 2 | v w + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/enum_single_letter.vv b/v_windows/v/vlib/v/checker/tests/enum_single_letter.vv new file mode 100644 index 0000000..ef7bc65 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/enum_single_letter.vv @@ -0,0 +1,4 @@ +enum E { + v w +} +println(E.v) diff --git a/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.out new file mode 100644 index 0000000..d5f43cf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.out @@ -0,0 +1,139 @@ +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:12:10: error: infix expr: cannot use `int literal` (right expression) as `Aaa` + 10 | + 11 | fn main() { + 12 | println(Aaa{} == 10) + | ~~~~~~~~~~~ + 13 | println(10 == Aaa{}) + 14 | println(Aaa{} != 10) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:13:10: error: infix expr: cannot use `Aaa` (right expression) as `int literal` + 11 | fn main() { + 12 | println(Aaa{} == 10) + 13 | println(10 == Aaa{}) + | ~~~~~~~~~~~ + 14 | println(Aaa{} != 10) + 15 | println(10 != Aaa{}) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:14:10: error: infix expr: cannot use `int literal` (right expression) as `Aaa` + 12 | println(Aaa{} == 10) + 13 | println(10 == Aaa{}) + 14 | println(Aaa{} != 10) + | ~~~~~~~~~~~ + 15 | println(10 != Aaa{}) + 16 | +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:15:10: error: infix expr: cannot use `Aaa` (right expression) as `int literal` + 13 | println(10 == Aaa{}) + 14 | println(Aaa{} != 10) + 15 | println(10 != Aaa{}) + | ~~~~~~~~~~~ + 16 | + 17 | println(Aaa{0} == AAaa{0}) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:17:10: error: possible type mismatch of compared values of `==` operation + 15 | println(10 != Aaa{}) + 16 | + 17 | println(Aaa{0} == AAaa{0}) + | ~~~~~~~~~~~~~~~~~ + 18 | println(AAaa{0} == Aaa{0}) + 19 | println(AAaa{1} != Aaa{1}) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:18:10: error: possible type mismatch of compared values of `==` operation + 16 | + 17 | println(Aaa{0} == AAaa{0}) + 18 | println(AAaa{0} == Aaa{0}) + | ~~~~~~~~~~~~~~~~~ + 19 | println(AAaa{1} != Aaa{1}) + 20 | println(Aaa{1} != AAaa{1}) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:19:10: error: possible type mismatch of compared values of `!=` operation + 17 | println(Aaa{0} == AAaa{0}) + 18 | println(AAaa{0} == Aaa{0}) + 19 | println(AAaa{1} != Aaa{1}) + | ~~~~~~~~~~~~~~~~~ + 20 | println(Aaa{1} != AAaa{1}) + 21 | +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:20:10: error: possible type mismatch of compared values of `!=` operation + 18 | println(AAaa{0} == Aaa{0}) + 19 | println(AAaa{1} != Aaa{1}) + 20 | println(Aaa{1} != AAaa{1}) + | ~~~~~~~~~~~~~~~~~ + 21 | + 22 | arr := Arr([0]) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:23:10: error: possible type mismatch of compared values of `==` operation + 21 | + 22 | arr := Arr([0]) + 23 | println(arr == [0]) + | ~~~~~~~~~~ + 24 | println([1] == arr) + 25 | println(arr != [0]) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:24:10: error: possible type mismatch of compared values of `==` operation + 22 | arr := Arr([0]) + 23 | println(arr == [0]) + 24 | println([1] == arr) + | ~~~~~~~~~~ + 25 | println(arr != [0]) + 26 | println([1] != arr) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:25:10: error: possible type mismatch of compared values of `!=` operation + 23 | println(arr == [0]) + 24 | println([1] == arr) + 25 | println(arr != [0]) + | ~~~~~~~~~~ + 26 | println([1] != arr) + 27 | +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:26:10: error: possible type mismatch of compared values of `!=` operation + 24 | println([1] == arr) + 25 | println(arr != [0]) + 26 | println([1] != arr) + | ~~~~~~~~~~ + 27 | + 28 | arr_aaa := ArrAaa(arr) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:29:10: error: possible type mismatch of compared values of `==` operation + 27 | + 28 | arr_aaa := ArrAaa(arr) + 29 | println(arr_aaa == arr) + | ~~~~~~~~~~~~~~ + 30 | println(arr == arr_aaa) + 31 | println(arr_aaa != arr) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:30:10: error: possible type mismatch of compared values of `==` operation + 28 | arr_aaa := ArrAaa(arr) + 29 | println(arr_aaa == arr) + 30 | println(arr == arr_aaa) + | ~~~~~~~~~~~~~~ + 31 | println(arr_aaa != arr) + 32 | println(arr != arr_aaa) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:31:10: error: possible type mismatch of compared values of `!=` operation + 29 | println(arr_aaa == arr) + 30 | println(arr == arr_aaa) + 31 | println(arr_aaa != arr) + | ~~~~~~~~~~~~~~ + 32 | println(arr != arr_aaa) + 33 | +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:32:10: error: possible type mismatch of compared values of `!=` operation + 30 | println(arr == arr_aaa) + 31 | println(arr_aaa != arr) + 32 | println(arr != arr_aaa) + | ~~~~~~~~~~~~~~ + 33 | + 34 | println(arr_aaa == [0]) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:34:10: error: infix expr: cannot use `[]int` (right expression) as `ArrAaa` + 32 | println(arr != arr_aaa) + 33 | + 34 | println(arr_aaa == [0]) + | ~~~~~~~~~~~~~~ + 35 | println([1] == arr_aaa) + 36 | println(arr_aaa != [0]) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:35:10: error: infix expr: cannot use `ArrAaa` (right expression) as `[]int` + 33 | + 34 | println(arr_aaa == [0]) + 35 | println([1] == arr_aaa) + | ~~~~~~~~~~~~~~ + 36 | println(arr_aaa != [0]) + 37 | println([1] != arr_aaa) +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:36:10: error: infix expr: cannot use `[]int` (right expression) as `ArrAaa` + 34 | println(arr_aaa == [0]) + 35 | println([1] == arr_aaa) + 36 | println(arr_aaa != [0]) + | ~~~~~~~~~~~~~~ + 37 | println([1] != arr_aaa) + 38 | } +vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv:37:10: error: infix expr: cannot use `ArrAaa` (right expression) as `[]int` + 35 | println([1] == arr_aaa) + 36 | println(arr_aaa != [0]) + 37 | println([1] != arr_aaa) + | ~~~~~~~~~~~~~~ + 38 | } diff --git a/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv new file mode 100644 index 0000000..36e7d17 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/eq_ne_op_wrong_type_err.vv @@ -0,0 +1,38 @@ +struct Aaa { + i int +} + +type AAaa = Aaa + +type Arr = []int + +type ArrAaa = Aaa | Arr + +fn main() { + println(Aaa{} == 10) + println(10 == Aaa{}) + println(Aaa{} != 10) + println(10 != Aaa{}) + + println(Aaa{0} == AAaa{0}) + println(AAaa{0} == Aaa{0}) + println(AAaa{1} != Aaa{1}) + println(Aaa{1} != AAaa{1}) + + arr := Arr([0]) + println(arr == [0]) + println([1] == arr) + println(arr != [0]) + println([1] != arr) + + arr_aaa := ArrAaa(arr) + println(arr_aaa == arr) + println(arr == arr_aaa) + println(arr_aaa != arr) + println(arr != arr_aaa) + + println(arr_aaa == [0]) + println([1] == arr_aaa) + println(arr_aaa != [0]) + println([1] != arr_aaa) +} diff --git a/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.out b/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.out new file mode 100644 index 0000000..c4aceee --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/error_fn_with_0_args.vv:2:9: error: expected 1 arguments, but got 0 + 1 | fn abc() ? { + 2 | return error() + | ~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.vv b/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.vv new file mode 100644 index 0000000..ebbfe47 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_fn_with_0_args.vv @@ -0,0 +1,3 @@ +fn abc() ? { + return error() +} diff --git a/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.out b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.out new file mode 100644 index 0000000..5e4630e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/error_with_comment_with_crlf_ending.vv:2:6: error: unexpected name `should` + 1 | // Empty lines don't cause an issue but as soon as there's a comment with a CRLF ending, it fails + 2 | This should cause an error! + | ~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.vv b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.vv new file mode 100644 index 0000000..256462a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_crlf_ending.vv @@ -0,0 +1,2 @@ +// Empty lines don't cause an issue but as soon as there's a comment with a CRLF ending, it fails +This should cause an error! \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.out b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.out new file mode 100644 index 0000000..fcf3524 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/error_with_comment_with_lf_ending.vv:2:6: error: unexpected name `should` + 1 | // Empty lines don't cause an issue but as soon as there's a comment with a CRLF ending, it fails + 2 | This should cause an error! + | ~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.vv b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.vv new file mode 100644 index 0000000..003d44e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_comment_with_lf_ending.vv @@ -0,0 +1,2 @@ +// Empty lines don't cause an issue but as soon as there's a comment with a CRLF ending, it fails +This should cause an error! \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.out b/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.out new file mode 100644 index 0000000..d1b88fa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv:9:2: error: undefined ident: `a` (use `:=` to declare a variable) + 7 | fn main() { + 8 | func1() + 9 | a = 2 + | ^ + 10 | println('a is $a') + 11 | } +vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv:10:17: error: undefined ident: `a` + 8 | func1() + 9 | a = 2 + 10 | println('a is $a') + | ^ + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv b/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv new file mode 100644 index 0000000..09b22be --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_several_comments_with_crlf_ending.vv @@ -0,0 +1,11 @@ +// Comment line 1 +// Comment line 2 +fn func1() { + println('Inside func 1') +} + +fn main() { + func1() + a = 2 + println('a is $a') +} diff --git a/v_windows/v/vlib/v/checker/tests/error_with_unicode.out b/v_windows/v/vlib/v/checker/tests/error_with_unicode.out new file mode 100644 index 0000000..78ea832 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_unicode.out @@ -0,0 +1,56 @@ +vlib/v/checker/tests/error_with_unicode.vv:5:17: error: cannot use `int literal` as `string` in argument 2 to `f1` + 3 | + 4 | fn main() { + 5 | f1('🐀🐈', 0) + | ^ + 6 | f2(0, '🐟🐧') + 7 | mut n := 0 +vlib/v/checker/tests/error_with_unicode.vv:6:8: error: cannot use `string` as `int` in argument 2 to `f2` + 4 | fn main() { + 5 | f1('🐀🐈', 0) + 6 | f2(0, '🐟🐧') + | ~~~~~~ + 7 | mut n := 0 + 8 | n = '漢字' +vlib/v/checker/tests/error_with_unicode.vv:8:6: error: cannot assign to `n`: expected `int`, not `string` + 6 | f2(0, '🐟🐧') + 7 | mut n := 0 + 8 | n = '漢字' + | ~~~~~~ + 9 | n = 'ひらがな' + 10 | n = '简体字' +vlib/v/checker/tests/error_with_unicode.vv:9:6: error: cannot assign to `n`: expected `int`, not `string` + 7 | mut n := 0 + 8 | n = '漢字' + 9 | n = 'ひらがな' + | ~~~~~~~~~~ + 10 | n = '简体字' + 11 | n = '繁體字' +vlib/v/checker/tests/error_with_unicode.vv:10:6: error: cannot assign to `n`: expected `int`, not `string` + 8 | n = '漢字' + 9 | n = 'ひらがな' + 10 | n = '简体字' + | ~~~~~~~~ + 11 | n = '繁體字' + 12 | n = '한글' +vlib/v/checker/tests/error_with_unicode.vv:11:6: error: cannot assign to `n`: expected `int`, not `string` + 9 | n = 'ひらがな' + 10 | n = '简体字' + 11 | n = '繁體字' + | ~~~~~~~~ + 12 | n = '한글' + 13 | n = 'Кириллица' +vlib/v/checker/tests/error_with_unicode.vv:12:6: error: cannot assign to `n`: expected `int`, not `string` + 10 | n = '简体字' + 11 | n = '繁體字' + 12 | n = '한글' + | ~~~~~~ + 13 | n = 'Кириллица' + 14 | _ = n +vlib/v/checker/tests/error_with_unicode.vv:13:6: error: cannot assign to `n`: expected `int`, not `string` + 11 | n = '繁體字' + 12 | n = '한글' + 13 | n = 'Кириллица' + | ~~~~~~~~~~~ + 14 | _ = n + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/error_with_unicode.vv b/v_windows/v/vlib/v/checker/tests/error_with_unicode.vv new file mode 100644 index 0000000..8299e4e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/error_with_unicode.vv @@ -0,0 +1,15 @@ +fn f1(_ string, _ string) {} +fn f2(_ int, _ int) {} + +fn main() { + f1('🐀🐈', 0) + f2(0, '🐟🐧') + mut n := 0 + n = '漢字' + n = 'ひらがな' + n = '简体字' + n = '繁體字' + n = '한글' + n = 'Кириллица' + _ = n +} diff --git a/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.out b/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.out new file mode 100644 index 0000000..e150341 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/expression_should_return_an_option.vv:28:10: error: expression should return an option + 26 | } + 27 | // should be an checker error: + 28 | if x := return_string() { + | ~~~~~~~~~~~~~~~ + 29 | println('x: $x') + 30 | } diff --git a/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.vv b/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.vv new file mode 100644 index 0000000..6b25dbc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/expression_should_return_an_option.vv @@ -0,0 +1,31 @@ +fn return_optional(fail bool) ?string { + if fail { + return error('nope') + } + return 'foo' +} + +fn return_string() string { + return 'foo' +} + +fn main() { + // works + if r := return_optional(false) { + println(r) + } + // works + if r := return_optional(false) { + println(r) + } else { + println(err) + } + // works + return_optional(true) or { + println(err) + } + // should be an checker error: + if x := return_string() { + println('x: $x') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.out b/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.out new file mode 100644 index 0000000..e62f5db --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/filter_func_return_nonbool_err.vv:2:25: error: type mismatch, `stringsss` must return a bool + 1 | fn main() { + 2 | list := [1,2,3].filter(stringsss(it)) + | ~~~~~~~~~~~~~ + 3 | } + 4 | diff --git a/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.vv b/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.vv new file mode 100644 index 0000000..a6ecd7a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/filter_func_return_nonbool_err.vv @@ -0,0 +1,7 @@ +fn main() { + list := [1,2,3].filter(stringsss(it)) +} + +fn stringsss(arg int) string { + return '' +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.out b/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.out new file mode 100644 index 0000000..d01f0fd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/filter_on_non_arr_err.vv:2:14: error: unknown method or field: `string.filter` + 1 | fn main() { + 2 | _ := 'test'.filter(it == `t`) + | ~~~~~~~~~~~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.vv b/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.vv new file mode 100644 index 0000000..ee1bb7c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/filter_on_non_arr_err.vv @@ -0,0 +1,3 @@ +fn main() { + _ := 'test'.filter(it == `t`) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_conv.out b/v_windows/v/vlib/v/checker/tests/fixed_array_conv.out new file mode 100644 index 0000000..211f4b5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_conv.out @@ -0,0 +1,41 @@ +vlib/v/checker/tests/fixed_array_conv.vv:3:3: error: mismatched types `voidptr` and `[2]int` + 1 | arr := [2,3]! + 2 | mut p := voidptr(0) + 3 | p = arr + | ^ + 4 | mut ip := &int(0) + 5 | ip = arr +vlib/v/checker/tests/fixed_array_conv.vv:5:4: error: mismatched types `&int` and `[2]int` + 3 | p = arr + 4 | mut ip := &int(0) + 5 | ip = arr + | ^ + 6 | _ = &int(arr) + 7 | _ = p +vlib/v/checker/tests/fixed_array_conv.vv:6:5: error: cannot cast a fixed array (use e.g. `&arr[0]` instead) + 4 | mut ip := &int(0) + 5 | ip = arr + 6 | _ = &int(arr) + | ~~~~~~~~~ + 7 | _ = p + 8 | _ = ip +vlib/v/checker/tests/fixed_array_conv.vv:11:13: error: cannot use `[2]int` as `voidptr` in argument 1 to `memdup` + 9 | + 10 | unsafe { + 11 | _ = memdup(arr, 1) + | ~~~ + 12 | _ = tos(arr, 1) + 13 | fn (p &int){}(arr) +vlib/v/checker/tests/fixed_array_conv.vv:12:10: error: cannot use `[2]int` as `&byte` in argument 1 to `tos` + 10 | unsafe { + 11 | _ = memdup(arr, 1) + 12 | _ = tos(arr, 1) + | ~~~ + 13 | fn (p &int){}(arr) + 14 | } +vlib/v/checker/tests/fixed_array_conv.vv:13:16: error: cannot use `[2]int` as `&int` in argument 1 to `anon` + 11 | _ = memdup(arr, 1) + 12 | _ = tos(arr, 1) + 13 | fn (p &int){}(arr) + | ~~~ + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_conv.vv b/v_windows/v/vlib/v/checker/tests/fixed_array_conv.vv new file mode 100644 index 0000000..8c343c4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_conv.vv @@ -0,0 +1,14 @@ +arr := [2,3]! +mut p := voidptr(0) +p = arr +mut ip := &int(0) +ip = arr +_ = &int(arr) +_ = p +_ = ip + +unsafe { + _ = memdup(arr, 1) + _ = tos(arr, 1) + fn (p &int){}(arr) +} diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.out b/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.out new file mode 100644 index 0000000..0734920 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/fixed_array_non_const_size_err.vv:4:12: error: non-constant array bound `size` + 2 | size := 2 + 3 | + 4 | array := [size]int{} + | ~~~~ + 5 | + 6 | println(array) diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.vv b/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.vv new file mode 100644 index 0000000..eada0cd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_non_const_size_err.vv @@ -0,0 +1,7 @@ +fn main() { + size := 2 + + array := [size]int{} + + println(array) +} diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.out b/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.out new file mode 100644 index 0000000..8e3c9ea --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/fixed_array_size_err.vv:4:8: error: fixed size cannot be zero or negative (fixed_size: -1) + 2 | + 3 | fn main() { + 4 | a := [size]int{} + | ~~~~ + 5 | b := [0]byte{} + 6 | println(a) +vlib/v/checker/tests/fixed_array_size_err.vv:5:8: error: fixed size cannot be zero or negative (fixed_size: 0) + 3 | fn main() { + 4 | a := [size]int{} + 5 | b := [0]byte{} + | ^ + 6 | println(a) + 7 | println(b) diff --git a/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.vv b/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.vv new file mode 100644 index 0000000..eb27c2b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fixed_array_size_err.vv @@ -0,0 +1,8 @@ +const size = -1 + +fn main() { + a := [size]int{} + b := [0]byte{} + println(a) + println(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.out b/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.out new file mode 100644 index 0000000..8d7b1d8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/float_lit_exp_not_integer_err.vv:2:17: error: exponential part should be integer + 1 | fn main() { + 2 | println(45e2.5) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.vv b/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.vv new file mode 100644 index 0000000..a1bd559 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_exp_not_integer_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(45e2.5) +} diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.out b/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.out new file mode 100644 index 0000000..c5be9e2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/float_lit_exp_without_digit_err.vv:2:14: error: exponent has no digits + 1 | fn main() { + 2 | println(2E) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.vv b/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.vv new file mode 100644 index 0000000..6d3d9f1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_exp_without_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(2E) +} diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.out b/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.out new file mode 100644 index 0000000..cbf085b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/float_lit_too_many_points_err.vv:2:19: error: too many decimal points in number + 1 | fn main() { + 2 | println(123.45.67) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.vv b/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.vv new file mode 100644 index 0000000..e787cab --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_lit_too_many_points_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(123.45.67) +} diff --git a/v_windows/v/vlib/v/checker/tests/float_modulo_err.out b/v_windows/v/vlib/v/checker/tests/float_modulo_err.out new file mode 100644 index 0000000..a3ee9bf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_modulo_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/float_modulo_err.vv:2:13: error: float modulo not allowed, use math.fmod() instead + 1 | fn main() { + 2 | println(3.0 % 2.0) + | ~~~ + 3 | println(f32(3.0) % f32(2.0)) + 4 | } +vlib/v/checker/tests/float_modulo_err.vv:3:13: error: float modulo not allowed, use math.fmod() instead + 1 | fn main() { + 2 | println(3.0 % 2.0) + 3 | println(f32(3.0) % f32(2.0)) + | ~~~~~~~~ + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/float_modulo_err.vv b/v_windows/v/vlib/v/checker/tests/float_modulo_err.vv new file mode 100644 index 0000000..6a4d87d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/float_modulo_err.vv @@ -0,0 +1,4 @@ +fn main() { + println(3.0 % 2.0) + println(f32(3.0) % f32(2.0)) +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_args.out b/v_windows/v/vlib/v/checker/tests/fn_args.out new file mode 100644 index 0000000..2df630b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_args.out @@ -0,0 +1,35 @@ +vlib/v/checker/tests/fn_args.vv:9:6: error: cannot use `&int` as `byte` in argument 1 to `uu8` + 7 | fn basic() { + 8 | v := 4 + 9 | uu8(&v) + | ~~ + 10 | arr([5]!) + 11 | fun(fn (i &int) {}) +vlib/v/checker/tests/fn_args.vv:10:6: error: cannot use `[1]int` as `[]int` in argument 1 to `arr` + 8 | v := 4 + 9 | uu8(&v) + 10 | arr([5]!) + | ~~~~ + 11 | fun(fn (i &int) {}) + 12 | fun(fn (ii ...int) {}) +vlib/v/checker/tests/fn_args.vv:11:6: error: cannot use `fn (&int)` as `fn (int)` in argument 1 to `fun` + 9 | uu8(&v) + 10 | arr([5]!) + 11 | fun(fn (i &int) {}) + | ~~~~~~~~~~~~~~ + 12 | fun(fn (ii ...int) {}) + 13 | } +Details: ``'s expected fn argument: `` is NOT a pointer, but the passed fn argument: `i` is a pointer +vlib/v/checker/tests/fn_args.vv:12:6: error: cannot use `fn (...int)` as `fn (int)` in argument 1 to `fun` + 10 | arr([5]!) + 11 | fun(fn (i &int) {}) + 12 | fun(fn (ii ...int) {}) + | ~~~~~~~~~~~~~~~~~ + 13 | } + 14 | +vlib/v/checker/tests/fn_args.vv:22:4: error: cannot use `int` as `&S1` in argument 1 to `f` + 20 | fn ptr() { + 21 | v := 4 + 22 | f(v) + | ^ + 23 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_args.vv b/v_windows/v/vlib/v/checker/tests/fn_args.vv new file mode 100644 index 0000000..5cfdff5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_args.vv @@ -0,0 +1,23 @@ +fn uu8(a byte) {} + +fn arr(a []int) {} + +fn fun(a fn (int)) {} + +fn basic() { + v := 4 + uu8(&v) + arr([5]!) + fun(fn (i &int) {}) + fun(fn (ii ...int) {}) +} + +struct S1 { +} + +fn f(p &S1) {} + +fn ptr() { + v := 4 + f(v) +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_call_no_body.out b/v_windows/v/vlib/v/checker/tests/fn_call_no_body.out new file mode 100644 index 0000000..376fc56 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_call_no_body.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/fn_call_no_body.vv:7:9: error: cannot call a method that does not have a body + 5 | + 6 | fn main() { + 7 | Foo(0).f() + | ~~~ + 8 | f() + 9 | } +vlib/v/checker/tests/fn_call_no_body.vv:8:2: error: cannot call a function that does not have a body + 6 | fn main() { + 7 | Foo(0).f() + 8 | f() + | ~~~ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_call_no_body.vv b/v_windows/v/vlib/v/checker/tests/fn_call_no_body.vv new file mode 100644 index 0000000..1058977 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_call_no_body.vv @@ -0,0 +1,9 @@ +type Foo = int +fn (_ Foo) f() + +fn f() + +fn main() { + Foo(0).f() + f() +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_duplicate.out b/v_windows/v/vlib/v/checker/tests/fn_duplicate.out new file mode 100644 index 0000000..3cf949c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_duplicate.out @@ -0,0 +1,13 @@ +redefinition of function `main.f` +vlib/v/checker/tests/fn_duplicate.vv:1:1: conflicting declaration: fn f() + 1 | fn f() { + | ~~~~~~ + 2 | + 3 | } +vlib/v/checker/tests/fn_duplicate.vv:5:1: conflicting declaration: fn f(i int) + 3 | } + 4 | + 5 | fn f(i int) { + | ~~~~~~~~~~~ + 6 | + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_duplicate.vv b/v_windows/v/vlib/v/checker/tests/fn_duplicate.vv new file mode 100644 index 0000000..08c1dfb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_duplicate.vv @@ -0,0 +1,7 @@ +fn f() { + +} + +fn f(i int) { + +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_init_sig.out b/v_windows/v/vlib/v/checker/tests/fn_init_sig.out new file mode 100644 index 0000000..08cb17b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_init_sig.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/fn_init_sig.vv:1:1: error: fn `init` cannot have a return type + 1 | fn init() int { + | ~~~~~~~~~~~~~ + 2 | return 1 + 3 | } +vlib/v/checker/tests/fn_init_sig.vv:4:1: error: fn `init` must not be public + 2 | return 1 + 3 | } + 4 | pub fn init() int { + | ~~~~~~~~~~~~~~~~~ + 5 | return 1 + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_init_sig.vv b/v_windows/v/vlib/v/checker/tests/fn_init_sig.vv new file mode 100644 index 0000000..bff6d49 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_init_sig.vv @@ -0,0 +1,6 @@ +fn init() int { + return 1 +} +pub fn init() int { + return 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.out b/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.out new file mode 100644 index 0000000..d15d937 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/fn_return_array_sort_err.vv:6:14: error: the `sort()` method can be called only on mutable receivers, but `ret_array()` is a call expression + 4 | + 5 | fn main() { + 6 | ret_array().sort() + | ~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.vv b/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.vv new file mode 100644 index 0000000..3d28f79 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_return_array_sort_err.vv @@ -0,0 +1,7 @@ +fn ret_array() []int { + return [1, 3, 2] +} + +fn main() { + ret_array().sort() +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_return_or_err.out b/v_windows/v/vlib/v/checker/tests/fn_return_or_err.out new file mode 100644 index 0000000..ebb4494 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_return_or_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does not return an optional + 4 | + 5 | pub fn next(mut v []Typ) Typ { + 6 | return v.pop() or { Typ{} } + | ~~~~~~~~~~~~ + 7 | } + 8 | diff --git a/v_windows/v/vlib/v/checker/tests/fn_return_or_err.vv b/v_windows/v/vlib/v/checker/tests/fn_return_or_err.vv new file mode 100644 index 0000000..c303b0a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_return_or_err.vv @@ -0,0 +1,13 @@ +module main + +pub struct Typ {} + +pub fn next(mut v []Typ) Typ { + return v.pop() or { Typ{} } +} + +fn main() { + mut v := [Typ{}] + last := next(mut v) + println('$last') +} diff --git a/v_windows/v/vlib/v/checker/tests/fn_type_exists.out b/v_windows/v/vlib/v/checker/tests/fn_type_exists.out new file mode 100644 index 0000000..c6aa40f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_type_exists.out @@ -0,0 +1,10 @@ +vlib/v/checker/tests/fn_type_exists.vv:1:34: error: unknown type `Pants` + 1 | type PantsCreator = fn (a Shirt) Pants + | ~~~~~ + 2 | + 3 | type PantsConsumer = fn (p Pants) +vlib/v/checker/tests/fn_type_exists.vv:3:28: error: unknown type `Pants` + 1 | type PantsCreator = fn (a Shirt) Pants + 2 | + 3 | type PantsConsumer = fn (p Pants) + | ~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/fn_type_exists.vv b/v_windows/v/vlib/v/checker/tests/fn_type_exists.vv new file mode 100644 index 0000000..04ead19 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_type_exists.vv @@ -0,0 +1,3 @@ +type PantsCreator = fn (a Shirt) Pants + +type PantsConsumer = fn (p Pants) diff --git a/v_windows/v/vlib/v/checker/tests/fn_var.out b/v_windows/v/vlib/v/checker/tests/fn_var.out new file mode 100644 index 0000000..d3487a1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_var.out @@ -0,0 +1,23 @@ +vlib/v/checker/tests/fn_var.vv:1:10: error: missing return at the end of an anonymous function + 1 | mut f := fn(i int) byte {} + | ~~~~~~~~~~~~~~~~~ + 2 | f = 4 + 3 | mut p := &f +vlib/v/checker/tests/fn_var.vv:2:5: error: cannot assign to `f`: expected `fn (int) byte`, not `int literal` + 1 | mut f := fn(i int) byte {} + 2 | f = 4 + | ^ + 3 | mut p := &f + 4 | p = &[f] +vlib/v/checker/tests/fn_var.vv:4:5: error: cannot assign to `p`: expected `&fn (int) byte`, not `&[]fn (int) byte` + 2 | f = 4 + 3 | mut p := &f + 4 | p = &[f] + | ^ + 5 | _ = p + 6 | i := 0 +vlib/v/checker/tests/fn_var.vv:8:31: error: undefined ident: `i` + 6 | i := 0 + 7 | println(i) + 8 | f = fn(mut a []int) { println(i) } + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/fn_var.vv b/v_windows/v/vlib/v/checker/tests/fn_var.vv new file mode 100644 index 0000000..0a48b5d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_var.vv @@ -0,0 +1,8 @@ +mut f := fn(i int) byte {} +f = 4 +mut p := &f +p = &[f] +_ = p +i := 0 +println(i) +f = fn(mut a []int) { println(i) } diff --git a/v_windows/v/vlib/v/checker/tests/fn_variadic.out b/v_windows/v/vlib/v/checker/tests/fn_variadic.out new file mode 100644 index 0000000..f14183a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_variadic.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/fn_variadic.vv:3:8: error: when forwarding a variadic variable, it must be the final argument + 1 | fn f(vi ...int) int { + 2 | _ = f(...vi) // OK + 3 | _ = f(...vi, 2) + | ~~~~~ + 4 | return 0 + 5 | } +vlib/v/checker/tests/fn_variadic.vv:11:10: error: when forwarding a variadic variable, it must be the final argument + 9 | fn (s S1) m(vi ...int) int { + 10 | _ = s.m(...vi) // OK + 11 | _ = s.m(...vi, 2) + | ~~~~~ + 12 | return 0 + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/fn_variadic.vv b/v_windows/v/vlib/v/checker/tests/fn_variadic.vv new file mode 100644 index 0000000..b7faad7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/fn_variadic.vv @@ -0,0 +1,13 @@ +fn f(vi ...int) int { + _ = f(...vi) // OK + _ = f(...vi, 2) + return 0 +} + +struct S1 {} + +fn (s S1) m(vi ...int) int { + _ = s.m(...vi) // OK + _ = s.m(...vi, 2) + return 0 +} diff --git a/v_windows/v/vlib/v/checker/tests/for_in_index_optional.out b/v_windows/v/vlib/v/checker/tests/for_in_index_optional.out new file mode 100644 index 0000000..bbab32a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_index_optional.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/for_in_index_optional.vv:3:18: error: for in: cannot index `?[]string` + 1 | import os + 2 | fn main() { + 3 | for file in os.ls('.') { + | ~~~~~~~ + 4 | println(file) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/for_in_index_optional.vv b/v_windows/v/vlib/v/checker/tests/for_in_index_optional.vv new file mode 100644 index 0000000..c384c79 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_index_optional.vv @@ -0,0 +1,6 @@ +import os +fn main() { + for file in os.ls('.') { + println(file) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/for_in_index_type.out b/v_windows/v/vlib/v/checker/tests/for_in_index_type.out new file mode 100644 index 0000000..376ba73 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_index_type.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/for_in_index_type.vv:2:11: error: for in: cannot index `int literal` + 1 | fn main() { + 2 | for a in 52 { + | ~~ + 3 | println(a) + 4 | } +vlib/v/checker/tests/for_in_index_type.vv:3:3: error: `println` can not print void expressions + 1 | fn main() { + 2 | for a in 52 { + 3 | println(a) + | ~~~~~~~~~~ + 4 | } + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/for_in_index_type.vv b/v_windows/v/vlib/v/checker/tests/for_in_index_type.vv new file mode 100644 index 0000000..cbc26ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_index_type.vv @@ -0,0 +1,5 @@ +fn main() { + for a in 52 { + println(a) + } +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.out b/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.out new file mode 100644 index 0000000..0eb7516 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/for_in_map_one_variable_err.vv:3:6: error: declare a key and a value variable when ranging a map: `for key, val in map {` +use `_` if you do not need the variable + 1 | fn main() { + 2 | kvs := {'foo':'bar'} + 3 | for k in kvs { + | ^ + 4 | println('$k') + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.vv b/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.vv new file mode 100644 index 0000000..601c613 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_map_one_variable_err.vv @@ -0,0 +1,6 @@ +fn main() { + kvs := {'foo':'bar'} + for k in kvs { + println('$k') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.out b/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.out new file mode 100644 index 0000000..3a47a75 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/for_in_mut_val_type.vv:3:15: error: `a1` is immutable, it cannot be changed + 1 | fn main() { + 2 | a1 := [1, 2, 3] + 3 | for mut j in a1 { + | ~~ + 4 | j *= 2 + 5 | } +vlib/v/checker/tests/for_in_mut_val_type.vv:7:15: error: `a2` is immutable, it cannot be changed + 5 | } + 6 | a2 := [1, 2, 3]! + 7 | for mut j in a2 { + | ~~ + 8 | j *= 2 + 9 | } +vlib/v/checker/tests/for_in_mut_val_type.vv:11:18: error: `m` is immutable, it cannot be changed + 9 | } + 10 | m := {'aa': 1, 'bb': 2} + 11 | for _, mut j in m { + | ^ + 12 | j *= 2 + 13 | } +vlib/v/checker/tests/for_in_mut_val_type.vv:14:15: error: array literal is immutable, it cannot be changed + 12 | j *= 2 + 13 | } + 14 | for mut j in [1, 2, 3] { + | ~~~~~~~~~ + 15 | j *= 2 + 16 | } +vlib/v/checker/tests/for_in_mut_val_type.vv:17:15: error: array literal is immutable, it cannot be changed + 15 | j *= 2 + 16 | } + 17 | for mut j in [1, 2, 3]! { + | ~~~~~~~~~~ + 18 | j *= 2 + 19 | } +vlib/v/checker/tests/for_in_mut_val_type.vv:20:18: error: map literal is immutable, it cannot be changed + 18 | j *= 2 + 19 | } + 20 | for _, mut j in {'aa': 1, 'bb': 2} { + | ~~~~~~~~~~~~~~~~~~ + 21 | j *= 2 + 22 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.vv b/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.vv new file mode 100644 index 0000000..afd714b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_mut_val_type.vv @@ -0,0 +1,23 @@ +fn main() { + a1 := [1, 2, 3] + for mut j in a1 { + j *= 2 + } + a2 := [1, 2, 3]! + for mut j in a2 { + j *= 2 + } + m := {'aa': 1, 'bb': 2} + for _, mut j in m { + j *= 2 + } + for mut j in [1, 2, 3] { + j *= 2 + } + for mut j in [1, 2, 3]! { + j *= 2 + } + for _, mut j in {'aa': 1, 'bb': 2} { + j *= 2 + } +} diff --git a/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.out b/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.out new file mode 100644 index 0000000..d969830 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/for_in_range_not_match_type.vv:2:11: error: range types do not match + 1 | fn main() { + 2 | for i in 10..10.5 { + | ~~ + 3 | println(i) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.vv b/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.vv new file mode 100644 index 0000000..8b15863 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_range_not_match_type.vv @@ -0,0 +1,5 @@ +fn main() { + for i in 10..10.5 { + println(i) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.out b/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.out new file mode 100644 index 0000000..4d3d531 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/for_in_range_string_type.vv:2:11: error: range type can not be string + 1 | fn main() { + 2 | for i in 'a'..'b' { + | ~~~ + 3 | println(i) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.vv b/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.vv new file mode 100644 index 0000000..a459398 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_in_range_string_type.vv @@ -0,0 +1,5 @@ +fn main() { + for i in 'a'..'b' { + println(i) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/for_match_err.out b/v_windows/v/vlib/v/checker/tests/for_match_err.out new file mode 100644 index 0000000..9c90a9e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_match_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/for_match_err.vv:3:7: error: cannot use `match` in `for` loop + 1 | fn main() { + 2 | mut a := 2 + 3 | for match a { + | ~~~~~ + 4 | 2 { + 5 | println('a == 2') diff --git a/v_windows/v/vlib/v/checker/tests/for_match_err.vv b/v_windows/v/vlib/v/checker/tests/for_match_err.vv new file mode 100644 index 0000000..910f39f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/for_match_err.vv @@ -0,0 +1,15 @@ +fn main() { + mut a := 2 + for match a { + 2 { + println('a == 2') + a = 0 + } + 0 { + println('a == 0') + } + else { + println('unexpected branch') + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.out b/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.out new file mode 100644 index 0000000..e676a78 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/function_arg_mutable_err.vv:1:18: error: mutable arguments are only allowed for arrays, interfaces, maps, pointers, structs or their aliases +return values instead: `fn foo(mut n int) {` => `fn foo(n int) int {` + 1 | fn mod_ptr(mut a int) { + | ~~~ + 2 | a = 77 + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.vv b/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.vv new file mode 100644 index 0000000..30165cb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_arg_mutable_err.vv @@ -0,0 +1,8 @@ +fn mod_ptr(mut a int) { + a = 77 +} + +fn main() { + println('hello') +} + diff --git a/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.out b/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.out new file mode 100644 index 0000000..e540131 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/function_arg_redefinition.vv:1:17: error: redefinition of parameter `para1` + 1 | fn f(para1 int, para1 f32) int { + | ~~~~~ + 2 | return para1 + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.vv b/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.vv new file mode 100644 index 0000000..0618fba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_arg_redefinition.vv @@ -0,0 +1,7 @@ +fn f(para1 int, para1 f32) int { + return para1 +} + +fn main() { + a := f(11, 1.1) +} diff --git a/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.out b/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.out new file mode 100644 index 0000000..d9ad12e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:8:13: error: expected 1 arguments, but got 3 + 6 | + 7 | fn main() { + 8 | test(true, false, 1) + | ~~~~~~~~ + 9 | test() + 10 | test2(true, false, 1) +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:9:2: error: expected 1 arguments, but got 0 + 7 | fn main() { + 8 | test(true, false, 1) + 9 | test() + | ~~~~~~ + 10 | test2(true, false, 1) + 11 | test2(true) +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:10:21: error: expected 2 arguments, but got 3 + 8 | test(true, false, 1) + 9 | test() + 10 | test2(true, false, 1) + | ^ + 11 | test2(true) + 12 | } +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:11:2: error: expected 2 arguments, but got 1 + 9 | test() + 10 | test2(true, false, 1) + 11 | test2(true) + | ~~~~~~~~~~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.vv b/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.vv new file mode 100644 index 0000000..00fe212 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_count_of_args_mismatch_err.vv @@ -0,0 +1,12 @@ +fn test(b bool) { +} + +fn test2(b bool, v T) { +} + +fn main() { + test(true, false, 1) + test() + test2(true, false, 1) + test2(true) +} diff --git a/v_windows/v/vlib/v/checker/tests/function_missing_return_type.out b/v_windows/v/vlib/v/checker/tests/function_missing_return_type.out new file mode 100644 index 0000000..73292af --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_missing_return_type.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/function_missing_return_type.vv:1:1: error: missing return at end of function `h` + 1 | fn h() int { + | ~~~~~~~~~~ + 2 | } + 3 | +vlib/v/checker/tests/function_missing_return_type.vv:12:1: error: missing return at end of function `abc` + 10 | } + 11 | + 12 | fn (s Abc) abc() &int { + | ~~~~~~~~~~~~~~~~~~~~~ + 13 | if true { + 14 | return &s.x diff --git a/v_windows/v/vlib/v/checker/tests/function_missing_return_type.vv b/v_windows/v/vlib/v/checker/tests/function_missing_return_type.vv new file mode 100644 index 0000000..74d8cc1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_missing_return_type.vv @@ -0,0 +1,22 @@ +fn h() int { +} + +struct Abc { + x int +} + +fn (s Abc) panic(message string) { + println('called ${@METHOD} with message: $message') +} + +fn (s Abc) abc() &int { + if true { + return &s.x + } + s.panic(@FN) +} + +fn main() { + d := h() + println('$d') +} diff --git a/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.out b/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.out new file mode 100644 index 0000000..f72b6e0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/function_variadic_arg_array_decompose.vv:11:10: error: too many arguments in call to `sum` + 9 | fn main() { + 10 | b := [5, 6, 7] + 11 | println(sum(1, 2, ...b)) + | ~~~~~~~~~~~~~~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.vv b/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.vv new file mode 100644 index 0000000..27fa0c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_variadic_arg_array_decompose.vv @@ -0,0 +1,12 @@ +fn sum(a ...int) int { + mut total := 0 + for x in a { + total += x + } + return total +} + +fn main() { + b := [5, 6, 7] + println(sum(1, 2, ...b)) +} diff --git a/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.out b/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.out new file mode 100644 index 0000000..07e4fc8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/function_wrong_arg_type.vv:7:9: error: cannot use `f64` as `int` in argument 1 to `f` + 5 | fn main() { + 6 | a := 12.3 + 7 | q := f(a) + | ^ + 8 | println('$q') + 9 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.vv b/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.vv new file mode 100644 index 0000000..6cd3fbb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_wrong_arg_type.vv @@ -0,0 +1,9 @@ +fn f(x int) int { + return x+x +} + +fn main() { + a := 12.3 + q := f(a) + println('$q') +} diff --git a/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.out b/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.out new file mode 100644 index 0000000..070d573 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/function_wrong_return_type.vv:2:9: error: cannot use `float literal` as type `int` in return argument + 1 | fn h() int { + 2 | return 3.14 + | ~~~~ + 3 | } + 4 | diff --git a/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.vv b/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.vv new file mode 100644 index 0000000..0c47628 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/function_wrong_return_type.vv @@ -0,0 +1,8 @@ +fn h() int { + return 3.14 +} + +fn main() { + d := h() + println('$d') +} diff --git a/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.out b/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.out new file mode 100644 index 0000000..70150ab --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv:26:1: error: generic function declaration must specify generic type names, e.g. foo + 24 | } + 25 | + 26 | fn g_worker(g Generic) { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + 27 | t := <-g.ch + 28 | handle(t) +vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv:32:1: error: generic function declaration must specify generic type names, e.g. foo + 30 | } + 31 | + 32 | fn handle(t T) { + | ~~~~~~~~~~~~~~ + 33 | println("hi") + 34 | } +vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv:23:9: error: cannot use `Generic` as type `Generic` in return argument + 21 | go g_worker(g) + 22 | + 23 | return g + | ^ + 24 | } + 25 | diff --git a/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv b/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv new file mode 100644 index 0000000..cc001e9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_fn_decl_without_generic_names_err.vv @@ -0,0 +1,34 @@ +struct Generic { + ch chan T +} + +struct Concrete { + msg string +} + +fn main() { + g := create_generic_t() + g.ch <- Concrete{ + msg: 'hello' + } +} + +fn create_generic_t() Generic { + g := Generic{ + ch: chan T{} + } + + go g_worker(g) + + return g +} + +fn g_worker(g Generic) { + t := <-g.ch + handle(t) + // println("${t.msg}") +} + +fn handle(t T) { + println("hi") +} diff --git a/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.out b/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.out new file mode 100644 index 0000000..eb4ee7c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/generic_param_used_as_an_array_err.vv:2:10: error: generic type T does not support indexing, pass an array, or a reference instead, e.g. []T or &T + 1 | fn test(arr T) { + 2 | a := arr[1] + | ~~~ + 3 | println(a) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.vv b/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.vv new file mode 100644 index 0000000..f545b77 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_param_used_as_an_array_err.vv @@ -0,0 +1,12 @@ +fn test(arr T) { + a := arr[1] + println(a) +} + +fn main() { + a := [1, 2, 3]! + test(a) + + b := ['a', 'b', 'c']! + test(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.out b/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.out new file mode 100644 index 0000000..72bd593 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/generic_sumtype_invalid_variant.vv:5:7: error: `MultiGeneric` has no variant `u64` + 3 | fn main() { + 4 | mut m := MultiGeneric(true) + 5 | if m is u64 { + | ~~ + 6 | println('hi') + 7 | } +vlib/v/checker/tests/generic_sumtype_invalid_variant.vv:8:7: error: `MultiGeneric` has no variant `X` + 6 | println('hi') + 7 | } + 8 | if m is X { + | ~~ + 9 | println('hi again') + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.vv b/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.vv new file mode 100644 index 0000000..3cde35b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generic_sumtype_invalid_variant.vv @@ -0,0 +1,11 @@ +type MultiGeneric = X | Y | Z + +fn main() { + mut m := MultiGeneric(true) + if m is u64 { + println('hi') + } + if m is X { + println('hi again') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out b/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out new file mode 100644 index 0000000..f6913f5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:6:15: error: cannot use `int literal` as `bool` in argument 1 to `foo` + 4 | + 5 | fn main() { + 6 | foo(1) + | ^ + 7 | foo(2.2) + 8 | foo(true) +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:7:15: error: cannot use `float literal` as `bool` in argument 1 to `foo` + 5 | fn main() { + 6 | foo(1) + 7 | foo(2.2) + | ~~~ + 8 | foo(true) + 9 | foo('aaa') +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:8:17: error: cannot use `bool` as `string` in argument 1 to `foo` + 6 | foo(1) + 7 | foo(2.2) + 8 | foo(true) + | ~~~~ + 9 | foo('aaa') + 10 | } +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:9:14: error: cannot use `string` as `int` in argument 1 to `foo` + 7 | foo(2.2) + 8 | foo(true) + 9 | foo('aaa') + | ~~~~~ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv new file mode 100644 index 0000000..1960a54 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv @@ -0,0 +1,10 @@ +fn foo(b T) { + println(b) +} + +fn main() { + foo(1) + foo(2.2) + foo(true) + foo('aaa') +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.out b/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.out new file mode 100644 index 0000000..3f23d6b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv:3:13: error: cannot use `[]rune` as `string` in argument 2 to `foo_str` + 1 | fn main() { + 2 | x := 'ab'.runes()[..1] + 3 | foo_str(1, x) + | ^ + 4 | + 5 | foo := Foo{} +vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv:6:11: error: cannot use `[]rune` as `string` in argument 1 to `Foo.info` + 4 | + 5 | foo := Foo{} + 6 | foo.info(x) + | ^ + 7 | } + 8 | diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv new file mode 100644 index 0000000..fdbdf4d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_multi_args_mismatch.vv @@ -0,0 +1,17 @@ +fn main() { + x := 'ab'.runes()[..1] + foo_str(1, x) + + foo := Foo{} + foo.info(x) +} + +fn foo_str(b T, a string) { +} + +struct Foo { + t T +} + +fn (f Foo) info(a string) { +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.out b/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.out new file mode 100644 index 0000000..4212808 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_fn_called_no_arg_err.vv:13:10: error: no argument generic function must add concrete types, e.g. foo() + 11 | + 12 | fn main() { + 13 | q := new_queue() + | ~~~~~~~~~~~ + 14 | println(q) + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.vv new file mode 100644 index 0000000..a05e1d5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_no_arg_err.vv @@ -0,0 +1,15 @@ +struct Queue{ + buffer []T +} + +fn new_queue() Queue { + q := Queue{ + buffer: []T{cap: 1024} + } + return q +} + +fn main() { + q := new_queue() + println(q) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.out b/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.out new file mode 100644 index 0000000..ec84b67 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.vv:4:2: error: generic fn using generic types cannot be called outside of generic fn + 2 | + 3 | fn main() { + 4 | foo() + | ~~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.vv new file mode 100644 index 0000000..c8b72c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_outside_of_generic_fn.vv @@ -0,0 +1,5 @@ +fn foo() {} + +fn main() { + foo() +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.out b/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.out new file mode 100644 index 0000000..457e95a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.vv:12:16: error: cannot use `[]int` as `int` in argument 1 to `max` + 10 | + 11 | fn main() { + 12 | b := max([1, 2, 3, 4]) + | ~~~~~~~~~~~~ + 13 | println(b) + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.vv new file mode 100644 index 0000000..d8b811b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_called_variadic_arg_mismatch.vv @@ -0,0 +1,14 @@ +fn max(a ...T) T { + mut max := a[0] + for item in a[1..] { + if max < item { + max = item + } + } + return max +} + +fn main() { + b := max([1, 2, 3, 4]) + println(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.out b/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.out new file mode 100644 index 0000000..12a579c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_fn_return_generic_struct_err.vv:13:32: error: return generic struct in fn declaration must specify the generic type names, e.g. Foo + 11 | } + 12 | + 13 | pub fn new_channel_struct() GenericChannelStruct { + | ~~~~~~~~~~~~~~~~~~~~ + 14 | d := GenericChannelStruct{ + 15 | ch: chan T{} diff --git a/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.vv b/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.vv new file mode 100644 index 0000000..0f9a5d5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_fn_return_generic_struct_err.vv @@ -0,0 +1,18 @@ +struct GenericChannelStruct { + ch chan T +} + +struct Simple { + msg string +} + +fn main() { + new_channel_struct() +} + +pub fn new_channel_struct() GenericChannelStruct { + d := GenericChannelStruct{ + ch: chan T{} + } + return d +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.out b/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.out new file mode 100644 index 0000000..8d09c8f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.out @@ -0,0 +1 @@ +generic error: struct `main.Test` is not a generic struct, cannot instantiate to the concrete types diff --git a/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.vv b/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.vv new file mode 100644 index 0000000..6647877 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_inst_non_generic_struct_err.vv @@ -0,0 +1,6 @@ +struct Test { +} + +fn main() { + println(Test{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.out b/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.out new file mode 100644 index 0000000..1a8b84d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_method_receiver_type_err.vv:6:11: error: receiver must specify the generic type names, e.g. Foo + 4 | } + 5 | + 6 | pub fn (x Node) str() string { + | ~~~~ + 7 | return 'Value is : ${u16(x.val)}\nName is : $x.name' + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.vv b/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.vv new file mode 100644 index 0000000..8a6763c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_method_receiver_type_err.vv @@ -0,0 +1,16 @@ +struct Node { + val T + name string +} + +pub fn (x Node) str() string { + return 'Value is : ${u16(x.val)}\nName is : $x.name' +} + +fn main() { + xx := Node{ + val: u16(11) + name: 'man' + } + println(xx.str()) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.out b/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.out new file mode 100644 index 0000000..0c2c68e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.vv:4:15: error: a non generic function called like a generic one + 2 | + 3 | fn main() { + 4 | x := math.sin(1.0) + | ~~~~~ + 5 | println(x) + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.vv b/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.vv new file mode 100644 index 0000000..d25ddd1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_non_generic_fn_called_like_a_generic_one.vv @@ -0,0 +1,6 @@ +import math + +fn main() { + x := math.sin(1.0) + println(x) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.out b/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.out new file mode 100644 index 0000000..bd3b2c3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generics_struct_declaration_err.vv:5:1: error: generic struct declaration must specify the generic type names, e.g. Foo + 3 | } + 4 | + 5 | struct MyGenericChannelStruct { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | GenericChannelStruct + 7 | msg string diff --git a/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.vv b/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.vv new file mode 100644 index 0000000..ef8fe6b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_struct_declaration_err.vv @@ -0,0 +1,24 @@ +struct GenericChannelStruct { + ch chan T +} + +struct MyGenericChannelStruct { + GenericChannelStruct + msg string +} + +struct Simple { + msg string +} + +fn main() { + new_channel_struct() +} + +pub fn new_channel_struct() GenericChannelStruct { + d := GenericChannelStruct{ + ch: chan T{} + } + + return d +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.out b/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.out new file mode 100644 index 0000000..c55c336 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/generics_struct_init_err.vv:58:8: error: generic struct init must specify type parameter, e.g. Foo + 56 | ret = holder_call_12(neg, 3) + 57 | assert ret == -3 + 58 | ret = FnHolder1{neg}.call(4) + | ~~~~~~~~~~~~~~ + 59 | assert ret == -4 + 60 | +vlib/v/checker/tests/generics_struct_init_err.vv:67:8: error: generic struct init must specify type parameter, e.g. Foo + 65 | ret = holder_call_22(neg, 5) + 66 | assert ret == -5 + 67 | ret = FnHolder2{neg}.call(6) + | ~~~~~~~~~~~~~~ + 68 | assert ret == -6 + 69 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.vv b/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.vv new file mode 100644 index 0000000..4116799 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_struct_init_err.vv @@ -0,0 +1,69 @@ +fn neg(a int) int { + return -a +} + +struct FnHolder1 { + func T +} + +fn (self FnHolder1) call(a int) int { + return self.func(a) +} + +struct FnHolder2 { + func fn (int) int +} + +fn (self FnHolder2) call(a int) int { + return self.func(a) +} + +fn holder_call_1(func T, a int) int { + h := FnHolder1{func} + return h.call(a) +} + +fn holder_call_2(func T, a int) int { + h := FnHolder2{func} + return h.call(a) +} + +fn holder_call_11(func T, a int) int { + f := func + h := FnHolder1{f} + return h.call(a) +} + +fn holder_call_21(func T, a int) int { + f := func + h := FnHolder2{f} + return h.call(a) +} + +fn holder_call_12(func T, a int) int { + return FnHolder1{func}.call(a) +} + +fn holder_call_22(func T, a int) int { + return FnHolder2{func}.call(a) +} + +fn main() { + mut ret := holder_call_1(neg, 1) + assert ret == -1 + ret = holder_call_11(neg, 2) + assert ret == -2 + ret = holder_call_12(neg, 3) + assert ret == -3 + ret = FnHolder1{neg}.call(4) + assert ret == -4 + + ret = holder_call_2(neg, 3) + assert ret == -3 + ret = holder_call_21(neg, 4) + assert ret == -4 + ret = holder_call_22(neg, 5) + assert ret == -5 + ret = FnHolder2{neg}.call(6) + assert ret == -6 +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.out b/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.out new file mode 100644 index 0000000..caea12a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/generics_too_many_parameters.vv:6:8: error: too many generic parameters got 5, expected 1 + 4 | + 5 | fn main() { + 6 | foo(1) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.vv b/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.vv new file mode 100644 index 0000000..980e1ff --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_too_many_parameters.vv @@ -0,0 +1,7 @@ +fn foo(b T) { + println(b) +} + +fn main() { + foo(1) +} diff --git a/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.out b/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.out new file mode 100644 index 0000000..affad87 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/generics_type_ambiguous.vv:7:19: error: inferred generic type `B` is ambiguous: got `int`, expected `string` + 5 | + 6 | fn main() { + 7 | test(2, 2, "2", 2) + | ^ + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.vv b/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.vv new file mode 100644 index 0000000..9eb0f39 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/generics_type_ambiguous.vv @@ -0,0 +1,8 @@ +fn test (a T, b T, c B, d B) { + println("$a $b $c $d") +} + + +fn main() { + test(2, 2, "2", 2) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.out b/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.out new file mode 100644 index 0000000..2c3865e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/globals/assign_no_value.vv:1:19: error: undefined ident: `int` + 1 | __global ( test = int ) + | ~~~ diff --git a/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.vv b/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.vv new file mode 100644 index 0000000..352847e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/assign_no_value.vv @@ -0,0 +1 @@ +__global ( test = int ) \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.out b/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.out new file mode 100644 index 0000000..05d2e26 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/globals/incorrect_name_global.vv:1:12: error: global name `A` cannot contain uppercase letters, use snake_case instead + 1 | __global ( A = int(1) ) + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.vv b/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.vv new file mode 100644 index 0000000..c9550d8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/incorrect_name_global.vv @@ -0,0 +1 @@ +__global ( A = int(1) ) diff --git a/v_windows/v/vlib/v/checker/tests/globals/no_type.out b/v_windows/v/vlib/v/checker/tests/globals/no_type.out new file mode 100644 index 0000000..71899d9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/no_type.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/globals/no_type.vv:1:17: error: expecting type declaration + 1 | __global ( test ) + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/globals/no_type.vv b/v_windows/v/vlib/v/checker/tests/globals/no_type.vv new file mode 100644 index 0000000..264ed9d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/no_type.vv @@ -0,0 +1 @@ +__global ( test ) diff --git a/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.out b/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.out new file mode 100644 index 0000000..f0a6cbc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/globals/unexpected_eof.vv:3:1: error: unexpected eof, expecting ´)´ + 1 | __global ( + 2 | x string diff --git a/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.vv b/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.vv new file mode 100644 index 0000000..ab9c9c6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/unexpected_eof.vv @@ -0,0 +1,2 @@ +__global ( + x string diff --git a/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.out b/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.out new file mode 100644 index 0000000..60b44bb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/globals/unknown_typ.vv:1:12: error: unknown type `foo` + 1 | __global x foo + | ~~~ + 2 | __global ( + 3 | y = float(5.0) +vlib/v/checker/tests/globals/unknown_typ.vv:3:6: error: unknown function: float + 1 | __global x foo + 2 | __global ( + 3 | y = float(5.0) + | ~~~~~~~~~~ + 4 | ) diff --git a/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.vv b/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.vv new file mode 100644 index 0000000..9d7fdd1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals/unknown_typ.vv @@ -0,0 +1,4 @@ +__global x foo +__global ( + y = float(5.0) +) diff --git a/v_windows/v/vlib/v/checker/tests/globals_error.out b/v_windows/v/vlib/v/checker/tests/globals_error.out new file mode 100644 index 0000000..1dcbb36 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_error.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/globals_error.vv:2:1: error: use `v -enable-globals ...` to enable globals + 1 | + 2 | __global ( rfcnt int ) + | ~~~~~~~~ + 3 | + 4 | fn abc(){ diff --git a/v_windows/v/vlib/v/checker/tests/globals_error.run.out b/v_windows/v/vlib/v/checker/tests/globals_error.run.out new file mode 100644 index 0000000..525f325 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_error.run.out @@ -0,0 +1 @@ +rfcnt: 2 diff --git a/v_windows/v/vlib/v/checker/tests/globals_error.vv b/v_windows/v/vlib/v/checker/tests/globals_error.vv new file mode 100644 index 0000000..51048dc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_error.vv @@ -0,0 +1,12 @@ + +__global ( rfcnt int ) + +fn abc(){ + rfcnt = 2 +} + +fn main(){ + rfcnt = 1 + abc() + println('rfcnt: $rfcnt') +} diff --git a/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.run.out b/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.run.out new file mode 100644 index 0000000..586eac7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.run.out @@ -0,0 +1,4 @@ +[vlib/v/checker/tests/globals_run/function_stored_in_global.vv:14] voidptr(main.abc) == voidptr(cpu_get_id): true +[vlib/v/checker/tests/globals_run/function_stored_in_global.vv:15] main.cpu_get_id(): 123 +[vlib/v/checker/tests/globals_run/function_stored_in_global.vv:16] abc(): 123 +[vlib/v/checker/tests/globals_run/function_stored_in_global.vv:17] abc() == main.cpu_get_id(): true diff --git a/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.vv b/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.vv new file mode 100644 index 0000000..e74825e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_run/function_stored_in_global.vv @@ -0,0 +1,20 @@ +__global ( + cpu_get_id fn () u64 +) +fn current() u64 { + return cpu_get_id() +} + +fn abc() u64 { + return 123 +} + +fn main() { + cpu_get_id = abc + dump(voidptr(abc) == voidptr(cpu_get_id)) + dump(cpu_get_id()) + dump(abc()) + dump(abc() == cpu_get_id()) + assert abc() == cpu_get_id() + assert voidptr(abc) == voidptr(cpu_get_id) +} diff --git a/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.run.out b/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.run.out new file mode 100644 index 0000000..7968dd1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.run.out @@ -0,0 +1,3 @@ +[vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv:30] cpu_locals.map(it.x): [123, 456] +[vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv:33] x.x: 123 +[vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv:36] y.x: 456 diff --git a/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv b/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv new file mode 100644 index 0000000..272df4c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/globals_run/global_array_indexed_by_global_fn.vv @@ -0,0 +1,40 @@ +struct Local { + x int +} + +__global ( + cpu_locals []&Local +) + +__global ( + cpu_get_id fn () u64 + cpu_set_id fn (u64) +) + +fn abc0() u64 { + return 0 +} + +fn abc1() u64 { + return 1 +} + +pub fn current() &Local { + return cpu_locals[cpu_get_id()] +} + +fn main() { + cpu_locals = []&Local{} + cpu_locals << &Local{123} + cpu_locals << &Local{456} + dump(cpu_locals.map(it.x)) + cpu_get_id = abc0 + x := current() + dump(x.x) + cpu_get_id = abc1 + y := current() + dump(y.x) + assert x != y + assert x.x == 123 + assert y.x == 456 +} diff --git a/v_windows/v/vlib/v/checker/tests/go_expr.out b/v_windows/v/vlib/v/checker/tests/go_expr.out new file mode 100644 index 0000000..e19c961 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_expr.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/go_expr.vv:2:5: error: expression in `go` must be a function call + 1 | fn main() { + 2 | go 1 + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/go_expr.vv b/v_windows/v/vlib/v/checker/tests/go_expr.vv new file mode 100644 index 0000000..969b29d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_expr.vv @@ -0,0 +1,3 @@ +fn main() { + go 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/go_mut_arg.out b/v_windows/v/vlib/v/checker/tests/go_mut_arg.out new file mode 100644 index 0000000..70271b1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_mut_arg.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/go_mut_arg.vv:14:16: error: function in `go` statement cannot contain mutable non-reference arguments + 12 | a: 0 + 13 | } + 14 | go change(mut x) + | ^ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/go_mut_arg.vv b/v_windows/v/vlib/v/checker/tests/go_mut_arg.vv new file mode 100644 index 0000000..af86f15 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_mut_arg.vv @@ -0,0 +1,15 @@ +struct St { +mut: + a int +} + +fn change(mut a St) { + a.a++ +} + +fn main() { + mut x := St{ + a: 0 + } + go change(mut x) +} diff --git a/v_windows/v/vlib/v/checker/tests/go_mut_receiver.out b/v_windows/v/vlib/v/checker/tests/go_mut_receiver.out new file mode 100644 index 0000000..f8b2681 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_mut_receiver.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/go_mut_receiver.vv:14:5: error: method in `go` statement cannot have non-reference mutable receiver + 12 | a: 0 + 13 | } + 14 | go x.change() + | ^ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/go_mut_receiver.vv b/v_windows/v/vlib/v/checker/tests/go_mut_receiver.vv new file mode 100644 index 0000000..0b64d51 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_mut_receiver.vv @@ -0,0 +1,15 @@ +struct St { +mut: + a int +} + +fn (mut a St) change() { + a.a++ +} + +fn main() { + mut x := St{ + a: 0 + } + go x.change() +} diff --git a/v_windows/v/vlib/v/checker/tests/go_wait_or.out b/v_windows/v/vlib/v/checker/tests/go_wait_or.out new file mode 100644 index 0000000..146f4fb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_wait_or.out @@ -0,0 +1,69 @@ +vlib/v/checker/tests/go_wait_or.vv:11:17: error: unexpected `?`, the function `wait` does not return an optional + 9 | go d(1) + 10 | ] + 11 | r := tg.wait() ? + | ^ + 12 | println(r) + 13 | s := tg[0].wait() or { panic('problem') } +vlib/v/checker/tests/go_wait_or.vv:13:20: error: unexpected `or` block, the function `wait` does not return an optional + 11 | r := tg.wait() ? + 12 | println(r) + 13 | s := tg[0].wait() or { panic('problem') } + | ~~~~~~~~~~~~~~~~~~~~~~~ + 14 | println(s) + 15 | tg2 := [ +vlib/v/checker/tests/go_wait_or.vv:19:13: error: unexpected `or` block, the function `wait` does not return an optional + 17 | go e(1) + 18 | ] + 19 | tg2.wait() or { panic('problem') } + | ~~~~~~~~~~~~~~~~~~~~~~~ + 20 | tg2[0].wait() ? + 21 | tg3 := [ +vlib/v/checker/tests/go_wait_or.vv:20:16: error: unexpected `?`, the function `wait` does not return an optional + 18 | ] + 19 | tg2.wait() or { panic('problem') } + 20 | tg2[0].wait() ? + | ^ + 21 | tg3 := [ + 22 | go f(0) +vlib/v/checker/tests/go_wait_or.vv:25:6: error: `.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`. + 23 | go f(1) + 24 | ] + 25 | tg3.wait() or { panic('problem') } + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 26 | for t in tg3 { + 27 | a := t.wait() +vlib/v/checker/tests/go_wait_or.vv:27:10: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end + 25 | tg3.wait() or { panic('problem') } + 26 | for t in tg3 { + 27 | a := t.wait() + | ~~~~~~ + 28 | println(a) + 29 | } +vlib/v/checker/tests/go_wait_or.vv:31:15: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end + 29 | } + 30 | for i, _ in tg3 { + 31 | a := tg3[i].wait() + | ~~~~~~ + 32 | println(a) + 33 | } +vlib/v/checker/tests/go_wait_or.vv:38:6: error: `.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`. + 36 | go g(1) + 37 | ] + 38 | tg4.wait() + | ~~~~~~ + 39 | tg4[0].wait() + 40 | go g(3) or { panic('problem') } +vlib/v/checker/tests/go_wait_or.vv:39:9: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end + 37 | ] + 38 | tg4.wait() + 39 | tg4[0].wait() + | ~~~~~~ + 40 | go g(3) or { panic('problem') } + 41 | } +vlib/v/checker/tests/go_wait_or.vv:40:10: error: optional handling cannot be done in `go` call. Do it when calling `.wait()` + 38 | tg4.wait() + 39 | tg4[0].wait() + 40 | go g(3) or { panic('problem') } + | ~~~~~~~~~~~~~~~~~~~~~~~ + 41 | } diff --git a/v_windows/v/vlib/v/checker/tests/go_wait_or.vv b/v_windows/v/vlib/v/checker/tests/go_wait_or.vv new file mode 100644 index 0000000..3522f90 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/go_wait_or.vv @@ -0,0 +1,41 @@ +fn d(n int) f64 { return f64(n) } +fn e(n int) { } +fn f(n int) ?f64 { return f64(n) } +fn g(n int) ? { } + +fn main() { + tg := [ + go d(0) + go d(1) + ] + r := tg.wait() ? + println(r) + s := tg[0].wait() or { panic('problem') } + println(s) + tg2 := [ + go e(0) + go e(1) + ] + tg2.wait() or { panic('problem') } + tg2[0].wait() ? + tg3 := [ + go f(0) + go f(1) + ] + tg3.wait() or { panic('problem') } + for t in tg3 { + a := t.wait() + println(a) + } + for i, _ in tg3 { + a := tg3[i].wait() + println(a) + } + tg4 := [ + go g(0) + go g(1) + ] + tg4.wait() + tg4[0].wait() + go g(3) or { panic('problem') } +} diff --git a/v_windows/v/vlib/v/checker/tests/goto_label.out b/v_windows/v/vlib/v/checker/tests/goto_label.out new file mode 100644 index 0000000..9312085 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/goto_label.out @@ -0,0 +1,63 @@ +vlib/v/checker/tests/goto_label.vv:5:7: error: unknown label `a1` + 3 | goto f2 + 4 | f1: + 5 | goto a1 + | ~~ + 6 | _ = fn(){ + 7 | goto f1 +vlib/v/checker/tests/goto_label.vv:7:8: error: unknown label `f1` + 5 | goto a1 + 6 | _ = fn(){ + 7 | goto f1 + | ~~ + 8 | goto f2 + 9 | goto a1 +vlib/v/checker/tests/goto_label.vv:8:8: error: unknown label `f2` + 6 | _ = fn(){ + 7 | goto f1 + 8 | goto f2 + | ~~ + 9 | goto a1 + 10 | a1: +vlib/v/checker/tests/goto_label.vv:9:8: error: `goto` requires `unsafe` (consider using labelled break/continue) + 7 | goto f1 + 8 | goto f2 + 9 | goto a1 + | ~~ + 10 | a1: + 11 | goto a1 +vlib/v/checker/tests/goto_label.vv:11:8: error: `goto` requires `unsafe` (consider using labelled break/continue) + 9 | goto a1 + 10 | a1: + 11 | goto a1 + | ~~ + 12 | } + 13 | f2: +vlib/v/checker/tests/goto_label.vv:14:7: error: unknown label `a1` + 12 | } + 13 | f2: + 14 | goto a1 + | ~~ + 15 | goto f1 // back + 16 | goto f2 +vlib/v/checker/tests/goto_label.vv:22:7: error: unknown label `f1` + 20 | goto g1 // forward + 21 | g1: + 22 | goto f1 + | ~~ + 23 | goto a1 + 24 | goto g1 // back +vlib/v/checker/tests/goto_label.vv:23:7: error: unknown label `a1` + 21 | g1: + 22 | goto f1 + 23 | goto a1 + | ~~ + 24 | goto g1 // back + 25 | goto undefined +vlib/v/checker/tests/goto_label.vv:25:7: error: unknown label `undefined` + 23 | goto a1 + 24 | goto g1 // back + 25 | goto undefined + | ~~~~~~~~~ + 26 | }} + 27 | diff --git a/v_windows/v/vlib/v/checker/tests/goto_label.vv b/v_windows/v/vlib/v/checker/tests/goto_label.vv new file mode 100644 index 0000000..23b2cd7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/goto_label.vv @@ -0,0 +1,33 @@ +fn f() {unsafe { + goto f1 // forward + goto f2 + f1: + goto a1 + _ = fn(){ + goto f1 + goto f2 + goto a1 + a1: + goto a1 + } + f2: + goto a1 + goto f1 // back + goto f2 +}} + +fn g() {unsafe { + goto g1 // forward + g1: + goto f1 + goto a1 + goto g1 // back + goto undefined +}} + +// implicit main +unsafe { + goto m1 + m1: + goto m1 +} diff --git a/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.out b/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.out new file mode 100644 index 0000000..f6407ba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/hex_lit_without_digit_err.vv:2:14: error: number part of this hexadecimal is not provided + 1 | fn main() { + 2 | println(0x) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.vv b/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.vv new file mode 100644 index 0000000..7063035 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_lit_without_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0x) +} diff --git a/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.out b/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.out new file mode 100644 index 0000000..16ebb52 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/hex_lit_wrong_digit_err.vv:2:18: error: this hexadecimal number has unsuitable digit `g` + 1 | fn main() { + 2 | println(0x111ghi) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.vv b/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.vv new file mode 100644 index 0000000..1b7b07d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_lit_wrong_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0x111ghi) +} diff --git a/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.out b/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.out new file mode 100644 index 0000000..9475150 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.out @@ -0,0 +1,18 @@ +vlib/v/checker/tests/hex_literal_overflow.vv:1:7: error: hex character literal overflows string + 1 | a := '\x11ffff' + | ~~~~~~~~ + 2 | b := '\x20ffff' + 3 | c := '\x10fffff' +vlib/v/checker/tests/hex_literal_overflow.vv:2:7: error: hex character literal overflows string + 1 | a := '\x11ffff' + 2 | b := '\x20ffff' + | ~~~~~~~~ + 3 | c := '\x10fffff' + 4 | println(a) +vlib/v/checker/tests/hex_literal_overflow.vv:3:7: error: hex character literal overflows string + 1 | a := '\x11ffff' + 2 | b := '\x20ffff' + 3 | c := '\x10fffff' + | ~~~~~~~~~ + 4 | println(a) + 5 | println(b) diff --git a/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.vv b/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.vv new file mode 100644 index 0000000..60903f8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/hex_literal_overflow.vv @@ -0,0 +1,6 @@ +a := '\x11ffff' +b := '\x20ffff' +c := '\x10fffff' +println(a) +println(b) +println(c) diff --git a/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.out b/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.out new file mode 100644 index 0000000..60d6483 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/ierror_in_return_tuple.vv:1:29: error: type `IError` cannot be used in multi-return, return an option instead + 1 | fn return_ierror_in_tuple() (bool, IError) { + | ~~~~~~~~~~~~~~ + 2 | return false, error('') + 3 | } +vlib/v/checker/tests/ierror_in_return_tuple.vv:5:29: error: option cannot be used in multi-return, return an option instead + 3 | } + 4 | + 5 | fn return_option_in_tuple() (bool, ?bool) { + | ~~~~~~~~~~~~~ + 6 | return false, error('') + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.vv b/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.vv new file mode 100644 index 0000000..4717528 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ierror_in_return_tuple.vv @@ -0,0 +1,7 @@ +fn return_ierror_in_tuple() (bool, IError) { + return false, error('') +} + +fn return_option_in_tuple() (bool, ?bool) { + return false, error('') +} diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.out b/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.out new file mode 100644 index 0000000..2fdeabc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/if_expr_last_stmt.vv:4:7: error: `if` expression requires an expression as the last statement of every branch + 2 | _ = if true { + 3 | 1 + 4 | } else if false { + | ~~~~~~~~~~~~~ + 5 | } else { + 6 | } +vlib/v/checker/tests/if_expr_last_stmt.vv:5:7: error: `if` expression requires an expression as the last statement of every branch + 3 | 1 + 4 | } else if false { + 5 | } else { + | ~~~~ + 6 | } + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.vv b/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.vv new file mode 100644 index 0000000..eb5dd0c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_last_stmt.vv @@ -0,0 +1,7 @@ +fn main() { + _ = if true { + 1 + } else if false { + } else { + } +} diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.out b/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.out new file mode 100644 index 0000000..0c45b88 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/if_expr_mismatch.vv:2:7: error: mismatched types `string` and `int literal` + 1 | fn main() { + 2 | s := if true { '12' } else { 12 } + | ~~ + 3 | println(s) + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.vv b/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.vv new file mode 100644 index 0000000..597135b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_mismatch.vv @@ -0,0 +1,4 @@ +fn main() { + s := if true { '12' } else { 12 } + println(s) +} diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_no_else.out b/v_windows/v/vlib/v/checker/tests/if_expr_no_else.out new file mode 100644 index 0000000..87fb3e7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_no_else.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/if_expr_no_else.vv:2:9: error: `if` expression needs `else` clause + 1 | fn main() { + 2 | _ = if true { 1 } + | ~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_no_else.vv b/v_windows/v/vlib/v/checker/tests/if_expr_no_else.vv new file mode 100644 index 0000000..d416cc9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_no_else.vv @@ -0,0 +1,3 @@ +fn main() { + _ = if true { 1 } +} diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.out b/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.out new file mode 100644 index 0000000..9adbc4a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/if_expr_optional_err.vv:7:5: error: non-bool type `?bool` used as if condition + 5 | fn main() { + 6 | + 7 | if get_bool() { + | ~~~~~~~~~~ + 8 | println("Using plain lists") + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.vv b/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.vv new file mode 100644 index 0000000..88d617d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_expr_optional_err.vv @@ -0,0 +1,10 @@ +fn get_bool() ?bool { + return true +} + +fn main() { + + if get_bool() { + println("Using plain lists") + } +} diff --git a/v_windows/v/vlib/v/checker/tests/if_match_expr.out b/v_windows/v/vlib/v/checker/tests/if_match_expr.out new file mode 100644 index 0000000..81c13a7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_expr.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/if_match_expr.vv:8:6: error: `if` expression branch has unsupported statement (`v.ast.ForStmt`) + 6 | if true {1} else {-1} // result + 7 | } else { + 8 | for {break} + | ^ + 9 | {} + 10 | match true {true {} else {}} // statement not expression +vlib/v/checker/tests/if_match_expr.vv:9:2: error: `if` expression branch has unsupported statement (`v.ast.Block`) + 7 | } else { + 8 | for {break} + 9 | {} + | ^ + 10 | match true {true {} else {}} // statement not expression + 11 | _ = match true {true {1} else {-1}} // OK +vlib/v/checker/tests/if_match_expr.vv:10:2: error: `if` expression branch has unsupported statement (`v.ast.MatchExpr`) + 8 | for {break} + 9 | {} + 10 | match true {true {} else {}} // statement not expression + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | _ = match true {true {1} else {-1}} // OK + 12 | match true {true {1} else {-1}} // result +vlib/v/checker/tests/if_match_expr.vv:17:3: error: `match` expression branch has unsupported statement (`v.ast.IfExpr`) + 15 | _ = match true { + 16 | true { + 17 | if true {} // statement not expression + | ~~ + 18 | _ = if true {1} else {-1} // OK + 19 | if true {1} else {-1} // result +vlib/v/checker/tests/if_match_expr.vv:22:10: error: `match` expression branch has unsupported statement (`v.ast.AssertStmt`) + 20 | } + 21 | else { + 22 | assert true + | ~~~~ + 23 | match true {true {} else {}} // statement not expression + 24 | _ = match true {true {1} else {-1}} // OK +vlib/v/checker/tests/if_match_expr.vv:23:3: error: `match` expression branch has unsupported statement (`v.ast.MatchExpr`) + 21 | else { + 22 | assert true + 23 | match true {true {} else {}} // statement not expression + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 24 | _ = match true {true {1} else {-1}} // OK + 25 | match true {true {1} else {-1}} // result diff --git a/v_windows/v/vlib/v/checker/tests/if_match_expr.vv b/v_windows/v/vlib/v/checker/tests/if_match_expr.vv new file mode 100644 index 0000000..655f25a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_expr.vv @@ -0,0 +1,27 @@ +// only C expressions are allowed for each statement + +_ = if true { + if true {} // FIXME should error, if statement + _ = if true {1} else {-1} // OK + if true {1} else {-1} // result +} else { + for {break} + {} + match true {true {} else {}} // statement not expression + _ = match true {true {1} else {-1}} // OK + match true {true {1} else {-1}} // result +} + +_ = match true { + true { + if true {} // statement not expression + _ = if true {1} else {-1} // OK + if true {1} else {-1} // result + } + else { + assert true + match true {true {} else {}} // statement not expression + _ = match true {true {1} else {-1}} // OK + match true {true {1} else {-1}} // result + } +} diff --git a/v_windows/v/vlib/v/checker/tests/if_match_expr_err.out b/v_windows/v/vlib/v/checker/tests/if_match_expr_err.out new file mode 100644 index 0000000..2c16775 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_expr_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/if_match_expr_err.vv:3:8: error: cannot use `match` with `if` statements + 1 | fn main() { + 2 | a := 0 + 3 | if match a { + | ~~~~~ + 4 | 0 { + 5 | println('a is zero') diff --git a/v_windows/v/vlib/v/checker/tests/if_match_expr_err.vv b/v_windows/v/vlib/v/checker/tests/if_match_expr_err.vv new file mode 100644 index 0000000..511bbc7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_expr_err.vv @@ -0,0 +1,13 @@ +fn main() { + a := 0 + if match a { + 0 { + println('a is zero') + } + else { + println('unreachable branch') + } + } 5 < 7 { + println('5 is less than 7') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/if_match_result.out b/v_windows/v/vlib/v/checker/tests/if_match_result.out new file mode 100644 index 0000000..53a154e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_result.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/if_match_result.vv:2:3: error: assignment mismatch: 1 variable(s) 0 value(s) + 1 | // missing results + 2 | _ = match 4 { + | ^ + 3 | 1 {} + 4 | else {} +vlib/v/checker/tests/if_match_result.vv:6:5: error: `if` expression requires an expression as the last statement of every branch + 4 | else {} + 5 | } + 6 | _ = if true { + | ~~~~~~~ + 7 | } else { + 8 | } +vlib/v/checker/tests/if_match_result.vv:7:3: error: `if` expression requires an expression as the last statement of every branch + 5 | } + 6 | _ = if true { + 7 | } else { + | ~~~~ + 8 | } + 9 | +vlib/v/checker/tests/if_match_result.vv:11:3: error: assignment mismatch: 1 variable(s) 0 value(s) + 9 | + 10 | // void results + 11 | _ = match 4 { + | ^ + 12 | 1 {println('')} + 13 | else {exit(0)} +vlib/v/checker/tests/if_match_result.vv:15:3: error: assignment mismatch: 1 variable(s) 0 value(s) + 13 | else {exit(0)} + 14 | } + 15 | _ = if true { + | ^ + 16 | println('') + 17 | } else { diff --git a/v_windows/v/vlib/v/checker/tests/if_match_result.vv b/v_windows/v/vlib/v/checker/tests/if_match_result.vv new file mode 100644 index 0000000..ad85b13 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_match_result.vv @@ -0,0 +1,19 @@ +// missing results +_ = match 4 { + 1 {} + else {} +} +_ = if true { +} else { +} + +// void results +_ = match 4 { + 1 {println('')} + else {exit(0)} +} +_ = if true { + println('') +} else { + exit(0) +} diff --git a/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.out b/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.out new file mode 100644 index 0000000..08c9765 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/if_non_bool_cond.vv:2:5: error: non-bool type `string` used as if condition + 1 | fn main() { + 2 | if '10' { + | ~~~~ + 3 | println('10') + 4 | } +vlib/v/checker/tests/if_non_bool_cond.vv:6:5: error: non-bool type `int literal` used as if condition + 4 | } + 5 | + 6 | if 5 { + | ^ + 7 | println(5) + 8 | } +vlib/v/checker/tests/if_non_bool_cond.vv:10:5: error: non-bool type `void` used as if condition + 8 | } + 9 | + 10 | if println('v') { + | ~~~~~~~~~~~~ + 11 | println('println') + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.vv b/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.vv new file mode 100644 index 0000000..626aafe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/if_non_bool_cond.vv @@ -0,0 +1,13 @@ +fn main() { + if '10' { + println('10') + } + + if 5 { + println(5) + } + + if println('v') { + println('println') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_arg.out b/v_windows/v/vlib/v/checker/tests/immutable_arg.out new file mode 100644 index 0000000..6f46625 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_arg.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_arg.vv:13:8: error: `a` is immutable, declare it with `mut` to make it mutable + 11 | fn main() { + 12 | a := St{e: 2} + 13 | f(mut a) + | ^ + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_arg.vv b/v_windows/v/vlib/v/checker/tests/immutable_arg.vv new file mode 100644 index 0000000..08e799d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_arg.vv @@ -0,0 +1,14 @@ +struct St { +mut: + e int +} + +fn f(mut x St) { + x.e++ + println(x) +} + +fn main() { + a := St{e: 2} + f(mut a) +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.out b/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.out new file mode 100644 index 0000000..b091e67 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_array_field_assign.vv:9:4: error: field `i` of struct `Aaa` is immutable + 7 | i: [0] + 8 | } + 9 | a.i[0] = 3 + | ^ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.vv b/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.vv new file mode 100644 index 0000000..adadfd6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_field_assign.vv @@ -0,0 +1,10 @@ +struct Aaa { + i []int +} + +fn main() { + mut a := Aaa{ + i: [0] + } + a.i[0] = 3 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.out b/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.out new file mode 100644 index 0000000..ec34ee2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_array_field_shift.vv:14:4: error: field `a` of struct `Bbb` is immutable + 12 | a: Aaa{} + 13 | } + 14 | b.a.i << 3 + | ^ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.vv b/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.vv new file mode 100644 index 0000000..f04054b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_field_shift.vv @@ -0,0 +1,15 @@ +struct Aaa { +mut: + i []int +} + +struct Bbb { + a Aaa +} + +fn main() { + mut b := Bbb{ + a: Aaa{} + } + b.a.i << 3 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.out b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.out new file mode 100644 index 0000000..3675fe2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_array_struct_assign.vv:8:2: error: `a` is immutable, declare it with `mut` to make it mutable + 6 | fn main() { + 7 | a := Aaa{} + 8 | a.i[0] += 3 + | ^ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.vv b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.vv new file mode 100644 index 0000000..b150d9d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_assign.vv @@ -0,0 +1,9 @@ +struct Aaa { +pub mut: + i []int +} + +fn main() { + a := Aaa{} + a.i[0] += 3 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.out b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.out new file mode 100644 index 0000000..b9a25fc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_array_struct_shift.vv:8:2: error: `a` is immutable, declare it with `mut` to make it mutable + 6 | fn main() { + 7 | a := []Aaa{} + 8 | a[0].i << 3 + | ^ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.vv b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.vv new file mode 100644 index 0000000..89e4d6c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_struct_shift.vv @@ -0,0 +1,9 @@ +struct Aaa { +pub mut: + i []int +} + +fn main() { + a := []Aaa{} + a[0].i << 3 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_var.out b/v_windows/v/vlib/v/checker/tests/immutable_array_var.out new file mode 100644 index 0000000..6e45b41 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_var.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_array_var.vv:3:2: error: `a` is immutable, declare it with `mut` to make it mutable + 1 | fn main() { + 2 | a := [1, 2] + 3 | a << 3 + | ^ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_array_var.vv b/v_windows/v/vlib/v/checker/tests/immutable_array_var.vv new file mode 100644 index 0000000..1afa343 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_array_var.vv @@ -0,0 +1,4 @@ +fn main() { + a := [1, 2] + a << 3 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.out b/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.out new file mode 100644 index 0000000..059e3bc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/immutable_builtin_modify.vv:2:3: error: `string` can not be modified + 1 | s := '' + 2 | s.len = 123 + | ~~~ + 3 | // + 4 | b := []byte{} +vlib/v/checker/tests/immutable_builtin_modify.vv:5:3: error: `array` can not be modified + 3 | // + 4 | b := []byte{} + 5 | b.len = 34 + | ~~~ diff --git a/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.vv b/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.vv new file mode 100644 index 0000000..e6cb706 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_builtin_modify.vv @@ -0,0 +1,5 @@ +s := '' +s.len = 123 +// +b := []byte{} +b.len = 34 diff --git a/v_windows/v/vlib/v/checker/tests/immutable_field.out b/v_windows/v/vlib/v/checker/tests/immutable_field.out new file mode 100644 index 0000000..05b6214 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_field.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_field.vv:8:4: error: field `i1` of struct `Aaa` is immutable + 6 | fn main() { + 7 | a := Aaa{1} + 8 | a.i1 = 2 + | ~~ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_field.vv b/v_windows/v/vlib/v/checker/tests/immutable_field.vv new file mode 100644 index 0000000..487d913 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_field.vv @@ -0,0 +1,9 @@ +struct Aaa { +pub: + i1 int +} + +fn main() { + a := Aaa{1} + a.i1 = 2 +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.out b/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.out new file mode 100644 index 0000000..fb2c4d8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/immutable_field_postfix.vv:7:4: error: field `i` of struct `Aaa` is immutable + 5 | fn main() { + 6 | mut a := Aaa{} + 7 | a.i++ + | ^ + 8 | a.i-- + 9 | } +vlib/v/checker/tests/immutable_field_postfix.vv:8:4: error: field `i` of struct `Aaa` is immutable + 6 | mut a := Aaa{} + 7 | a.i++ + 8 | a.i-- + | ^ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.vv b/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.vv new file mode 100644 index 0000000..0689c70 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_field_postfix.vv @@ -0,0 +1,9 @@ +struct Aaa { + i int +} + +fn main() { + mut a := Aaa{} + a.i++ + a.i-- +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_interface_field.out b/v_windows/v/vlib/v/checker/tests/immutable_interface_field.out new file mode 100644 index 0000000..0e421af --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_interface_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/immutable_interface_field.vv:10:4: error: field `i1` of interface `&Bbb` is immutable + 8 | + 9 | fn mutate_interface(mut b Bbb) { + 10 | b.i1 = 2 + | ~~ + 11 | } + 12 | \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/immutable_interface_field.vv b/v_windows/v/vlib/v/checker/tests/immutable_interface_field.vv new file mode 100644 index 0000000..c3aabef --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_interface_field.vv @@ -0,0 +1,16 @@ +struct Aaa { + i1 int +} + +interface Bbb { + i1 int +} + +fn mutate_interface(mut b Bbb) { + b.i1 = 2 +} + +fn main() { + mut a := Aaa{1} + mutate_interface(mut a) +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_map.out b/v_windows/v/vlib/v/checker/tests/immutable_map.out new file mode 100644 index 0000000..3c5f7e3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_map.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/immutable_map.vv:3:2: error: `m` is immutable, declare it with `mut` to make it mutable + 1 | fn main() { + 2 | m := map[string]int + 3 | m['test']++ + | ^ + 4 | m['test']-- + 5 | _ = m.move() +vlib/v/checker/tests/immutable_map.vv:4:2: error: `m` is immutable, declare it with `mut` to make it mutable + 2 | m := map[string]int + 3 | m['test']++ + 4 | m['test']-- + | ^ + 5 | _ = m.move() + 6 | m.delete('s') +vlib/v/checker/tests/immutable_map.vv:5:6: error: `m` is immutable, declare it with `mut` to make it mutable + 3 | m['test']++ + 4 | m['test']-- + 5 | _ = m.move() + | ^ + 6 | m.delete('s') + 7 | } +vlib/v/checker/tests/immutable_map.vv:6:2: error: `m` is immutable, declare it with `mut` to make it mutable + 4 | m['test']-- + 5 | _ = m.move() + 6 | m.delete('s') + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_map.vv b/v_windows/v/vlib/v/checker/tests/immutable_map.vv new file mode 100644 index 0000000..f3e0008 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_map.vv @@ -0,0 +1,7 @@ +fn main() { + m := map[string]int + m['test']++ + m['test']-- + _ = m.move() + m.delete('s') +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_rec.out b/v_windows/v/vlib/v/checker/tests/immutable_rec.out new file mode 100644 index 0000000..4c76ed4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_rec.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/immutable_rec.vv:13:2: error: `a` is immutable, declare it with `mut` to make it mutable + 11 | fn main() { + 12 | a := St{e: 2} + 13 | a.f() + | ^ + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_rec.vv b/v_windows/v/vlib/v/checker/tests/immutable_rec.vv new file mode 100644 index 0000000..acbc007 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_rec.vv @@ -0,0 +1,14 @@ +struct St { +mut: + e int +} + +fn (mut x St) f() { + x.e++ + println(x) +} + +fn main() { + a := St{e: 2} + a.f() +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.out b/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.out new file mode 100644 index 0000000..86c87c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/immutable_struct_postfix.vv:8:2: error: `a` is immutable, declare it with `mut` to make it mutable + 6 | fn main() { + 7 | a := Aaa{} + 8 | a.i++ + | ^ + 9 | a.i-- + 10 | } +vlib/v/checker/tests/immutable_struct_postfix.vv:9:2: error: `a` is immutable, declare it with `mut` to make it mutable + 7 | a := Aaa{} + 8 | a.i++ + 9 | a.i-- + | ^ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.vv b/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.vv new file mode 100644 index 0000000..dbd3e78 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_struct_postfix.vv @@ -0,0 +1,10 @@ +struct Aaa { +mut: + i int +} + +fn main() { + a := Aaa{} + a.i++ + a.i-- +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_var.out b/v_windows/v/vlib/v/checker/tests/immutable_var.out new file mode 100644 index 0000000..012ef94 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_var.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/immutable_var.vv:3:2: error: `a` is immutable, declare it with `mut` to make it mutable + 1 | fn main() { + 2 | a := 1 + 3 | a = 2 + | ^ + 4 | _ = a + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_var.vv b/v_windows/v/vlib/v/checker/tests/immutable_var.vv new file mode 100644 index 0000000..aeeaef1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_var.vv @@ -0,0 +1,5 @@ +fn main() { + a := 1 + a = 2 + _ = a +} diff --git a/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.out b/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.out new file mode 100644 index 0000000..ca05696 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/immutable_var_postfix.vv:3:2: error: `a` is immutable, declare it with `mut` to make it mutable + 1 | fn main() { + 2 | a := 1 + 3 | a++ + | ^ + 4 | a-- + 5 | } +vlib/v/checker/tests/immutable_var_postfix.vv:4:2: error: `a` is immutable, declare it with `mut` to make it mutable + 2 | a := 1 + 3 | a++ + 4 | a-- + | ^ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.vv b/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.vv new file mode 100644 index 0000000..ff1e94d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/immutable_var_postfix.vv @@ -0,0 +1,5 @@ +fn main() { + a := 1 + a++ + a-- +} diff --git a/v_windows/v/vlib/v/checker/tests/import_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/import_duplicate_err.out new file mode 100644 index 0000000..3c04980 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_duplicate_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/import_duplicate_err.vv:2:8: error: `time` was already imported on line 1 + 1 | import time + 2 | import time + | ~~~~ + 3 | fn main() { + 4 | println(time.now().unix_time()) diff --git a/v_windows/v/vlib/v/checker/tests/import_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/import_duplicate_err.vv new file mode 100644 index 0000000..f0f7d8d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_duplicate_err.vv @@ -0,0 +1,5 @@ +import time +import time +fn main() { + println(time.now().unix_time()) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_middle_err.out b/v_windows/v/vlib/v/checker/tests/import_middle_err.out new file mode 100644 index 0000000..81c6639 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_middle_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/import_middle_err.vv:5:1: error: `import x` can only be declared at the beginning of the file + 3 | println('hello, world') + 4 | } + 5 | import os + | ~~~~~~ + 6 | fn main() { + 7 | println(time.now()) diff --git a/v_windows/v/vlib/v/checker/tests/import_middle_err.vv b/v_windows/v/vlib/v/checker/tests/import_middle_err.vv new file mode 100644 index 0000000..f38e462 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_middle_err.vv @@ -0,0 +1,8 @@ +import time +fn show() { + println('hello, world') +} +import os +fn main() { + println(time.now()) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.out b/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.out new file mode 100644 index 0000000..5a5315b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_mod_as_mod_err.vv:1:16: error: import alias `math as math` is redundant + 1 | import math as math + | ~~~~ + 2 | + 3 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.vv b/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.vv new file mode 100644 index 0000000..2a12534 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_mod_as_mod_err.vv @@ -0,0 +1,5 @@ +import math as math + +fn main() { + println(math.e) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.out b/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.out new file mode 100644 index 0000000..4caef12 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_mod_sub_as_sub_err.vv:1:25: error: import alias `encoding.utf8 as utf8` is redundant + 1 | import encoding.utf8 as utf8 + | ~~~~ + 2 | + 3 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.vv b/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.vv new file mode 100644 index 0000000..81846bf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_mod_sub_as_sub_err.vv @@ -0,0 +1,5 @@ +import encoding.utf8 as utf8 + +fn main() { + println(utf8.validate_str('añçá')) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.out b/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.out new file mode 100644 index 0000000..7688794 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_multiple_modules_err.vv:1:13: error: cannot import multiple modules at a time + 1 | import time math + | ~~~~ + 2 | fn main() { + 3 | println(time.now().unix_time()) diff --git a/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.vv b/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.vv new file mode 100644 index 0000000..d5d8033 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_multiple_modules_err.vv @@ -0,0 +1,4 @@ +import time math +fn main() { + println(time.now().unix_time()) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_not_found_err.out b/v_windows/v/vlib/v/checker/tests/import_not_found_err.out new file mode 100644 index 0000000..a41edfc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_not_found_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_not_found_err.vv:1:1: builder error: cannot import module "notexist" (not found) + 1 | import notexist + | ~~~~~~~~~~~~~~~ + 2 | fn main() { + 3 | println(notexist.name) diff --git a/v_windows/v/vlib/v/checker/tests/import_not_found_err.vv b/v_windows/v/vlib/v/checker/tests/import_not_found_err.vv new file mode 100644 index 0000000..5e89bde --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_not_found_err.vv @@ -0,0 +1,4 @@ +import notexist +fn main() { + println(notexist.name) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.out b/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.out new file mode 100644 index 0000000..14a935e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/import_not_same_line_err.vv:2:2: error: `import` statements must be a single line + 1 | import + 2 | time + | ~~~~ + 3 | fn main() { + 4 | println(time.now()) diff --git a/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.vv b/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.vv new file mode 100644 index 0000000..ad6401e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_not_same_line_err.vv @@ -0,0 +1,5 @@ +import + time +fn main() { + println(time.now()) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_empty.out b/v_windows/v/vlib/v/checker/tests/import_symbol_empty.out new file mode 100644 index 0000000..0592fed --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_empty.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_empty.vv:1:12: error: empty `os` import set, remove `{}` + 1 | import os {} + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_empty.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_empty.vv new file mode 100644 index 0000000..252aceb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_empty.vv @@ -0,0 +1 @@ +import os {} diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.out b/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.out new file mode 100644 index 0000000..5d74a05 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.out @@ -0,0 +1,11 @@ +vlib/v/checker/tests/import_symbol_fn_err.vv:1:17: error: module `crypto` has no constant or function `userper` + 1 | import crypto { userper } + | ~~~~~~~ + 2 | fn main() { + 3 | usurper() +vlib/v/checker/tests/import_symbol_fn_err.vv:3:3: error: unknown function: usurper + 1 | import crypto { userper } + 2 | fn main() { + 3 | usurper() + | ~~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.vv new file mode 100644 index 0000000..04ba99b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_fn_err.vv @@ -0,0 +1,4 @@ +import crypto { userper } +fn main() { + usurper() +} diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.out b/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.out new file mode 100644 index 0000000..2193b15 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_invalid.vv:1:17: error: import syntax error, please specify a valid fn or type name + 1 | import crypto { *_v } + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.vv new file mode 100644 index 0000000..485c151 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_invalid.vv @@ -0,0 +1 @@ +import crypto { *_v } diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.out b/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.out new file mode 100644 index 0000000..e5685a4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.out @@ -0,0 +1,60 @@ +vlib/v/checker/tests/import_symbol_private_err.vv:11:6: notice: interface field `io.ReaderWriterImpl.r` must be initialized + 9 | since(now()) + 10 | _ = {'h': 2}.exists('h') + 11 | _ = ReaderWriterImpl{} + | ~~~~~~~~~~~~~~~~~~ + 12 | } +vlib/v/checker/tests/import_symbol_private_err.vv:11:6: notice: interface field `io.ReaderWriterImpl.w` must be initialized + 9 | since(now()) + 10 | _ = {'h': 2}.exists('h') + 11 | _ = ReaderWriterImpl{} + | ~~~~~~~~~~~~~~~~~~ + 12 | } +vlib/v/checker/tests/import_symbol_private_err.vv:3:20: error: module `time` function `since()` is private + 1 | import v.scanner + 2 | import v.parser + 3 | import time { now, since } + | ~~~~~ + 4 | import io { ReaderWriterImpl } + 5 | +vlib/v/checker/tests/import_symbol_private_err.vv:4:13: error: module `io` type `ReaderWriterImpl` is private + 2 | import v.parser + 3 | import time { now, since } + 4 | import io { ReaderWriterImpl } + | ~~~~~~~~~~~~~~~~ + 5 | + 6 | fn main() { +vlib/v/checker/tests/import_symbol_private_err.vv:7:18: error: constant `v.scanner.single_quote` is private + 5 | + 6 | fn main() { + 7 | println(scanner.single_quote) + | ~~~~~~~~~~~~ + 8 | println(parser.State.html) + 9 | since(now()) +vlib/v/checker/tests/import_symbol_private_err.vv:8:17: error: enum `v.parser.State` is private + 6 | fn main() { + 7 | println(scanner.single_quote) + 8 | println(parser.State.html) + | ~~~~~~~~~~ + 9 | since(now()) + 10 | _ = {'h': 2}.exists('h') +vlib/v/checker/tests/import_symbol_private_err.vv:9:2: error: function `time.since` is private + 7 | println(scanner.single_quote) + 8 | println(parser.State.html) + 9 | since(now()) + | ~~~~~~~~~~~~ + 10 | _ = {'h': 2}.exists('h') + 11 | _ = ReaderWriterImpl{} +vlib/v/checker/tests/import_symbol_private_err.vv:10:15: error: method `map[string]int.exists` is private + 8 | println(parser.State.html) + 9 | since(now()) + 10 | _ = {'h': 2}.exists('h') + | ~~~~~~~~~~~ + 11 | _ = ReaderWriterImpl{} + 12 | } +vlib/v/checker/tests/import_symbol_private_err.vv:11:6: error: type `io.ReaderWriterImpl` is private + 9 | since(now()) + 10 | _ = {'h': 2}.exists('h') + 11 | _ = ReaderWriterImpl{} + | ~~~~~~~~~~~~~~~~~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.vv new file mode 100644 index 0000000..fd4411b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_private_err.vv @@ -0,0 +1,12 @@ +import v.scanner +import v.parser +import time { now, since } +import io { ReaderWriterImpl } + +fn main() { + println(scanner.single_quote) + println(parser.State.html) + since(now()) + _ = {'h': 2}.exists('h') + _ = ReaderWriterImpl{} +} diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.out b/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.out new file mode 100644 index 0000000..2c1cd9b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/import_symbol_type_err.vv:1:17: error: module `crypto` has no type `Coin` + 1 | import crypto { Coin } + | ~~~~ + 2 | fn main() { + 3 | println(Coin{}) +vlib/v/checker/tests/import_symbol_type_err.vv:3:11: error: unknown type `crypto.Coin`. +Did you mean `crypto.Hash`? + 1 | import crypto { Coin } + 2 | fn main() { + 3 | println(Coin{}) + | ~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.vv new file mode 100644 index 0000000..7eccd97 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_type_err.vv @@ -0,0 +1,4 @@ +import crypto { Coin } +fn main() { + println(Coin{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.out b/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.out new file mode 100644 index 0000000..2ab6a66 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/import_symbol_unclosed.vv:1:28: error: import syntax error, no closing `}` + 1 | import crypto.sha256 { sum ] + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.vv b/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.vv new file mode 100644 index 0000000..799a636 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_symbol_unclosed.vv @@ -0,0 +1 @@ +import crypto.sha256 { sum ] diff --git a/v_windows/v/vlib/v/checker/tests/import_syntax_err.out b/v_windows/v/vlib/v/checker/tests/import_syntax_err.out new file mode 100644 index 0000000..06b3499 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_syntax_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_syntax_err.vv:1:12: error: cannot import multiple modules at a time + 1 | import time, os + | ^ + 2 | fn main() { + 3 | println(time.now()) diff --git a/v_windows/v/vlib/v/checker/tests/import_syntax_err.vv b/v_windows/v/vlib/v/checker/tests/import_syntax_err.vv new file mode 100644 index 0000000..2649ca6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_syntax_err.vv @@ -0,0 +1,4 @@ +import time, os +fn main() { + println(time.now()) +} diff --git a/v_windows/v/vlib/v/checker/tests/import_unused_warning.out b/v_windows/v/vlib/v/checker/tests/import_unused_warning.out new file mode 100644 index 0000000..38df8cd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_unused_warning.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/import_unused_warning.vv:1:8: warning: module 'time' is imported but never used + 1 | import time + | ~~~~ + 2 | fn main() { + 3 | println('hello, world') diff --git a/v_windows/v/vlib/v/checker/tests/import_unused_warning.vv b/v_windows/v/vlib/v/checker/tests/import_unused_warning.vv new file mode 100644 index 0000000..1fb573f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/import_unused_warning.vv @@ -0,0 +1,4 @@ +import time +fn main() { + println('hello, world') +} diff --git a/v_windows/v/vlib/v/checker/tests/in_mismatch_type.out b/v_windows/v/vlib/v/checker/tests/in_mismatch_type.out new file mode 100644 index 0000000..1a3b16c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/in_mismatch_type.out @@ -0,0 +1,77 @@ +vlib/v/checker/tests/in_mismatch_type.vv:10:5: error: left operand to `in` does not match the array element type: expected `string`, not `int literal` + 8 | } + 9 | s := 'abcd' + 10 | if 1 in a_s { + | ~~~~~~~~ + 11 | println('ok') + 12 | } +vlib/v/checker/tests/in_mismatch_type.vv:13:5: error: left operand to `in` does not match the map key type: expected `string`, not `int literal` + 11 | println('ok') + 12 | } + 13 | if 2 in m { + | ~~~~~~ + 14 | println('yeah') + 15 | } +vlib/v/checker/tests/in_mismatch_type.vv:16:7: error: `in` can only be used with an array/map/string + 14 | println('yeah') + 15 | } + 16 | if 3 in s { + | ~~ + 17 | println('dope') + 18 | } +vlib/v/checker/tests/in_mismatch_type.vv:19:9: error: `in` can only be used with an array/map/string + 17 | println('dope') + 18 | } + 19 | if `a` in s { + | ~~ + 20 | println("oh no :'(") + 21 | } +vlib/v/checker/tests/in_mismatch_type.vv:22:7: error: `in` can only be used with an array/map/string + 20 | println("oh no :'(") + 21 | } + 22 | if 1 in 12 { + | ~~ + 23 | println('right') + 24 | } +vlib/v/checker/tests/in_mismatch_type.vv:25:5: error: left operand to `in` does not match the map key type: expected `string`, not `Int` + 23 | println('right') + 24 | } + 25 | if Int(2) in m { + | ~~~~~~~~~~~ + 26 | println('yeah') + 27 | } +vlib/v/checker/tests/in_mismatch_type.vv:28:5: error: left operand to `in` does not match the array element type: expected `int`, not `string` + 26 | println('yeah') + 27 | } + 28 | if '3' in a_i { + | ~~~~~~~~~~ + 29 | println('sure') + 30 | } +vlib/v/checker/tests/in_mismatch_type.vv:31:5: error: left operand to `in` does not match the array element type: expected `int`, not `string` + 29 | println('sure') + 30 | } + 31 | if '2' in a_i { + | ~~~~~~~~~~ + 32 | println('all right') + 33 | } +vlib/v/checker/tests/in_mismatch_type.vv:34:5: error: left operand to `!in` does not match the array element type: expected `string`, not `int literal` + 32 | println('all right') + 33 | } + 34 | if 1 !in a_s { + | ~~~~~~~~~ + 35 | println('ok') + 36 | } +vlib/v/checker/tests/in_mismatch_type.vv:37:5: error: left operand to `!in` does not match the array element type: expected `int`, not `string` + 35 | println('ok') + 36 | } + 37 | if '1' !in a_i { + | ~~~~~~~~~~~ + 38 | println('good') + 39 | } +vlib/v/checker/tests/in_mismatch_type.vv:41:5: error: left operand to `!in` does not match the map key type: expected `string`, not `int literal` + 39 | } + 40 | + 41 | if 5 !in m { + | ~~~~~~~ + 42 | println('yay') + 43 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/in_mismatch_type.vv b/v_windows/v/vlib/v/checker/tests/in_mismatch_type.vv new file mode 100644 index 0000000..2fc2bde --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/in_mismatch_type.vv @@ -0,0 +1,44 @@ +type Int = int + +fn main() { + a_i := [1, 2, 3] + a_s := ['1', '2', '3'] + m := { + 'test': 1 + } + s := 'abcd' + if 1 in a_s { + println('ok') + } + if 2 in m { + println('yeah') + } + if 3 in s { + println('dope') + } + if `a` in s { + println("oh no :'(") + } + if 1 in 12 { + println('right') + } + if Int(2) in m { + println('yeah') + } + if '3' in a_i { + println('sure') + } + if '2' in a_i { + println('all right') + } + if 1 !in a_s { + println('ok') + } + if '1' !in a_i { + println('good') + } + + if 5 !in m { + println('yay') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.out b/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.out new file mode 100644 index 0000000..610ef46 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/incorrect_for_in_name_variable.vv:3:6: error: variable name `_aa` cannot start with `_` + 1 | fn main() { + 2 | a := [1,2,3] + 3 | for _aa in a { + | ~~~ + 4 | println(_aa) + 5 | } + \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.vv b/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.vv new file mode 100644 index 0000000..888fd60 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_for_in_name_variable.vv @@ -0,0 +1,6 @@ +fn main() { + a := [1,2,3] + for _aa in a { + println(_aa) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.out new file mode 100644 index 0000000..820e8d5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_alias_type.vv:1:1: error: type alias `integer` must begin with capital letter + 1 | type integer = int + | ~~~~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.vv new file mode 100644 index 0000000..7e2d363 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_alias_type.vv @@ -0,0 +1 @@ +type integer = int diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_const.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_const.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_const.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_const.vv new file mode 100644 index 0000000..50f553f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_const.vv @@ -0,0 +1,3 @@ +const ( + _my_const = 0 +) diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.out new file mode 100644 index 0000000..27a4392 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/incorrect_name_enum.vv:1:1: error: enum name `color` must begin with capital letter + 1 | enum color { + | ~~~~~~~~~~ + 2 | green + 3 | yellow \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.vv new file mode 100644 index 0000000..90c4d4d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum.vv @@ -0,0 +1,4 @@ +enum color { + green + yellow +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.out new file mode 100644 index 0000000..04f2748 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/incorrect_name_enum_field.vv:2:5: error: field name `Green` cannot contain uppercase letters, use snake_case instead + 1 | enum Color { + 2 | Green + | ~~~~~ + 3 | red + 4 | blue \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.vv new file mode 100644 index 0000000..8cb0031 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_enum_field.vv @@ -0,0 +1,5 @@ +enum Color { + Green + red + blue +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.out new file mode 100644 index 0000000..4ded743 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_fn_type.vv:1:1: error: fn type `callback` must begin with capital letter + 1 | type callback = fn () + | ~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.vv new file mode 100644 index 0000000..f565315 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_fn_type.vv @@ -0,0 +1 @@ +type callback = fn () diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_function.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_function.out new file mode 100644 index 0000000..0826b36 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_function.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_function.vv:1:1: error: function name `_my_fn` cannot start with `_` + 1 | fn _my_fn() {} + | ~~~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_function.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_function.vv new file mode 100644 index 0000000..b75a1a2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_function.vv @@ -0,0 +1 @@ +fn _my_fn() {} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.out new file mode 100644 index 0000000..95a49b9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_interface.vv:1:1: error: interface name `_MyInterface` must begin with capital letter + 1 | interface _MyInterface {} + | ~~~~~~~~~~~~~~~~~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.vv new file mode 100644 index 0000000..abe6cb8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface.vv @@ -0,0 +1 @@ +interface _MyInterface {} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.out new file mode 100644 index 0000000..daeda3e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/incorrect_name_interface_method.vv:2:5: error: method name `_speak` cannot start with `_` + 1 | interface MyInterface { + 2 | _speak() + | ~~~~~~~~ + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.vv new file mode 100644 index 0000000..2ab12be --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_interface_method.vv @@ -0,0 +1,3 @@ +interface MyInterface { + _speak() +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_module.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_module.out new file mode 100644 index 0000000..4edc08e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_module.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_module.vv:1:1: error: module name `_A` cannot start with `_` + 1 | module _A + | ~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_module.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_module.vv new file mode 100644 index 0000000..50526ca --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_module.vv @@ -0,0 +1 @@ +module _A diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.out new file mode 100644 index 0000000..03f1a93 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/incorrect_name_struct.vv:1:8: error: struct name `abc` must begin with capital letter + 1 | struct abc {} + | ~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.vv new file mode 100644 index 0000000..0c77ebc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct.vv @@ -0,0 +1 @@ +struct abc {} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.out new file mode 100644 index 0000000..22fddfc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/incorrect_name_struct_field.vv:2:5: error: field name `_a` cannot start with `_` + 1 | struct Abc { + 2 | _a int + | ~~~~~~ + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.vv new file mode 100644 index 0000000..b1b2205 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_struct_field.vv @@ -0,0 +1,3 @@ +struct Abc { + _a int +} diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.out new file mode 100644 index 0000000..91a0412 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/incorrect_name_sum_type.vv:1:1: error: sum type `integer` must begin with capital letter + 1 | type integer = i8 | i16 | int | i64 + | ~~~~~~~~~~~~ + 2 | type Integer = i8 | i16 | int | i64 + 3 | +vlib/v/checker/tests/incorrect_name_sum_type.vv:4:1: error: method overrides built-in sum type method + 2 | type Integer = i8 | i16 | int | i64 + 3 | + 4 | fn (i Integer) type_name() { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | } + 6 | diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.vv new file mode 100644 index 0000000..e73300d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_sum_type.vv @@ -0,0 +1,6 @@ +type integer = i8 | i16 | int | i64 +type Integer = i8 | i16 | int | i64 + +fn (i Integer) type_name() { +} + diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.out b/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.out new file mode 100644 index 0000000..7a3de82 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/incorrect_name_variable.vv:2:2: error: variable name `_abc` cannot start with `_` + 1 | fn main() { + 2 | _abc := 1 + | ~~~~ + 3 | _ = _abc + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.vv b/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.vv new file mode 100644 index 0000000..d4d4295 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/incorrect_name_variable.vv @@ -0,0 +1,4 @@ +fn main() { + _abc := 1 + _ = _abc +} diff --git a/v_windows/v/vlib/v/checker/tests/index_expr.out b/v_windows/v/vlib/v/checker/tests/index_expr.out new file mode 100644 index 0000000..90aa0a2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/index_expr.out @@ -0,0 +1,69 @@ +vlib/v/checker/tests/index_expr.vv:3:7: error: type `int` does not support indexing + 1 | fn test_invalid_index() { + 2 | v := 4 + 3 | _ = v[0] + | ~~~ + 4 | + 5 | a := [2] +vlib/v/checker/tests/index_expr.vv:6:7: error: non-integer index `[]int` (array type `[]int`) + 4 | + 5 | a := [2] + 6 | _ = a[a] + | ~~~ + 7 | _ = a[-1] + 8 | } +vlib/v/checker/tests/index_expr.vv:7:8: error: negative index `-1` + 5 | a := [2] + 6 | _ = a[a] + 7 | _ = a[-1] + | ~~ + 8 | } + 9 | +vlib/v/checker/tests/index_expr.vv:12:7: error: type `int` does not support indexing + 10 | fn test_invalid_slice() { + 11 | v := 4 + 12 | _ = v[1..] + | ~~~~~ + 13 | _ = v[..1] + 14 | +vlib/v/checker/tests/index_expr.vv:13:7: error: type `int` does not support indexing + 11 | v := 4 + 12 | _ = v[1..] + 13 | _ = v[..1] + | ~~~~~ + 14 | + 15 | a := [2] +vlib/v/checker/tests/index_expr.vv:16:7: error: non-integer index `[]int` (array type `[]int`) + 14 | + 15 | a := [2] + 16 | _ = a[a..] + | ~~~~~ + 17 | _ = a[..a] + 18 | _ = a[-1..] +vlib/v/checker/tests/index_expr.vv:17:7: error: non-integer index `[]int` (array type `[]int`) + 15 | a := [2] + 16 | _ = a[a..] + 17 | _ = a[..a] + | ~~~~~ + 18 | _ = a[-1..] + 19 | _ = a[..-1] +vlib/v/checker/tests/index_expr.vv:18:8: error: negative index `-1` + 16 | _ = a[a..] + 17 | _ = a[..a] + 18 | _ = a[-1..] + | ~~ + 19 | _ = a[..-1] + 20 | _ = a[-1..-2] +vlib/v/checker/tests/index_expr.vv:19:10: error: negative index `-1` + 17 | _ = a[..a] + 18 | _ = a[-1..] + 19 | _ = a[..-1] + | ~~ + 20 | _ = a[-1..-2] + 21 | } +vlib/v/checker/tests/index_expr.vv:20:8: error: negative index `-1` + 18 | _ = a[-1..] + 19 | _ = a[..-1] + 20 | _ = a[-1..-2] + | ~~ + 21 | } diff --git a/v_windows/v/vlib/v/checker/tests/index_expr.vv b/v_windows/v/vlib/v/checker/tests/index_expr.vv new file mode 100644 index 0000000..072661d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/index_expr.vv @@ -0,0 +1,21 @@ +fn test_invalid_index() { + v := 4 + _ = v[0] + + a := [2] + _ = a[a] + _ = a[-1] +} + +fn test_invalid_slice() { + v := 4 + _ = v[1..] + _ = v[..1] + + a := [2] + _ = a[a..] + _ = a[..a] + _ = a[-1..] + _ = a[..-1] + _ = a[-1..-2] +} diff --git a/v_windows/v/vlib/v/checker/tests/infix_err.out b/v_windows/v/vlib/v/checker/tests/infix_err.out new file mode 100644 index 0000000..3637169 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/infix_err.out @@ -0,0 +1,81 @@ +vlib/v/checker/tests/infix_err.vv:7:5: error: mismatched types `string` and `?string` + 5 | return none + 6 | } + 7 | _ = '' + f() + | ~~~~~~~~ + 8 | _ = f() + '' + 9 | _ = f() + f() +vlib/v/checker/tests/infix_err.vv:8:5: error: mismatched types `?string` and `string` + 6 | } + 7 | _ = '' + f() + 8 | _ = f() + '' + | ~~~~~~~~ + 9 | _ = f() + f() + 10 | +vlib/v/checker/tests/infix_err.vv:9:9: error: `+` cannot be used with `?string` + 7 | _ = '' + f() + 8 | _ = f() + '' + 9 | _ = f() + f() + | ^ + 10 | + 11 | _ = 4 + g() +vlib/v/checker/tests/infix_err.vv:11:7: error: `+` cannot be used with `?int` + 9 | _ = f() + f() + 10 | + 11 | _ = 4 + g() + | ^ + 12 | _ = int(0) + g() // FIXME not detected + 13 | _ = g() + int(3) +vlib/v/checker/tests/infix_err.vv:12:5: error: unwrapped optional cannot be used in an infix expression + 10 | + 11 | _ = 4 + g() + 12 | _ = int(0) + g() // FIXME not detected + | ~~~~~~~~~~~~ + 13 | _ = g() + int(3) + 14 | _ = g() + 3 +vlib/v/checker/tests/infix_err.vv:13:9: error: `+` cannot be used with `?int` + 11 | _ = 4 + g() + 12 | _ = int(0) + g() // FIXME not detected + 13 | _ = g() + int(3) + | ^ + 14 | _ = g() + 3 + 15 | +vlib/v/checker/tests/infix_err.vv:14:9: error: `+` cannot be used with `?int` + 12 | _ = int(0) + g() // FIXME not detected + 13 | _ = g() + int(3) + 14 | _ = g() + 3 + | ^ + 15 | + 16 | // binary operands +vlib/v/checker/tests/infix_err.vv:17:5: error: left operand for `&&` is not a boolean + 15 | + 16 | // binary operands + 17 | _ = 1 && 2 + | ^ + 18 | _ = true || 2 + 19 | +vlib/v/checker/tests/infix_err.vv:18:13: error: right operand for `||` is not a boolean + 16 | // binary operands + 17 | _ = 1 && 2 + 18 | _ = true || 2 + | ^ + 19 | + 20 | // boolean expressions +vlib/v/checker/tests/infix_err.vv:21:22: error: ambiguous boolean expression. use `()` to ensure correct order of operations + 19 | + 20 | // boolean expressions + 21 | _ = 1 == 1 && 2 == 2 || 3 == 3 + | ~~ + 22 | _ = 1 == 1 + 23 | && 2 == 2 || 3 == 3 +vlib/v/checker/tests/infix_err.vv:23:12: error: ambiguous boolean expression. use `()` to ensure correct order of operations + 21 | _ = 1 == 1 && 2 == 2 || 3 == 3 + 22 | _ = 1 == 1 + 23 | && 2 == 2 || 3 == 3 + | ~~ + 24 | && 4 == 4 +vlib/v/checker/tests/infix_err.vv:24:2: error: ambiguous boolean expression. use `()` to ensure correct order of operations + 22 | _ = 1 == 1 + 23 | && 2 == 2 || 3 == 3 + 24 | && 4 == 4 + | ~~ diff --git a/v_windows/v/vlib/v/checker/tests/infix_err.vv b/v_windows/v/vlib/v/checker/tests/infix_err.vv new file mode 100644 index 0000000..13381b4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/infix_err.vv @@ -0,0 +1,24 @@ +fn f() ?string { + return none +} +fn g() ?int { + return none +} +_ = '' + f() +_ = f() + '' +_ = f() + f() + +_ = 4 + g() +_ = int(0) + g() // FIXME not detected +_ = g() + int(3) +_ = g() + 3 + +// binary operands +_ = 1 && 2 +_ = true || 2 + +// boolean expressions +_ = 1 == 1 && 2 == 2 || 3 == 3 +_ = 1 == 1 + && 2 == 2 || 3 == 3 + && 4 == 4 diff --git a/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.out b/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.out new file mode 100644 index 0000000..c9d0e2d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/int_modulo_by_zero_err.vv:2:17: error: modulo by zero + 1 | fn main() { + 2 | println(3 % 0) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.vv b/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.vv new file mode 100644 index 0000000..8945861 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/int_modulo_by_zero_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(3 % 0) +} diff --git a/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.out b/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.out new file mode 100644 index 0000000..b2476ce --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/interface_implementing_interface.vv:15:10: error: cannot implement interface `Thing` with a different interface `Animal` + 13 | dog := Dog{} + 14 | animal := Animal(dog) + 15 | thing := Thing(animal) + | ~~~~~~~~~~~~~ + 16 | println(thing) \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.vv b/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.vv new file mode 100644 index 0000000..a71e9ea --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_implementing_interface.vv @@ -0,0 +1,16 @@ +interface Thing { + kind string +} + +interface Animal { + kind string +} + +struct Dog { + kind string = 'labrador' +} + +dog := Dog{} +animal := Animal(dog) +thing := Thing(animal) +println(thing) diff --git a/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.out b/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.out new file mode 100644 index 0000000..afa4876 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/interface_implementing_own_interface_method.vv:5:1: error: interface `Animal` cannot implement its own interface method `speak` + 3 | } + 4 | + 5 | fn (a Animal) speak() { + | ~~~~~~~~~~~~~~~~~~~~~ + 6 | println('speaking') + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.vv b/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.vv new file mode 100644 index 0000000..9881bd7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_implementing_own_interface_method.vv @@ -0,0 +1,7 @@ +interface Animal { + speak() +} + +fn (a Animal) speak() { + println('speaking') +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/interface_init_err.out b/v_windows/v/vlib/v/checker/tests/interface_init_err.out new file mode 100644 index 0000000..2a5e6ad --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_init_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/interface_init_err.vv:15:7: notice: interface field `Server.handler` must be initialized + 13 | + 14 | fn main() { + 15 | _ := Server{} + | ~~~~~~~~ + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/interface_init_err.vv b/v_windows/v/vlib/v/checker/tests/interface_init_err.vv new file mode 100644 index 0000000..e2def73 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_init_err.vv @@ -0,0 +1,16 @@ +interface Handler { + foo string + handle(int) int +} + +struct Server { + handler Handler +} + +fn (s Server) handle(x int) int { + return x +} + +fn main() { + _ := Server{} +} diff --git a/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.out b/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.out new file mode 100644 index 0000000..ac3f23d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/interface_return_parameter_err.vv:2:17: error: unknown type `Baz`. +Did you mean `Foo`? + 1 | interface Foo { + 2 | bar(string) []Baz + | ~~~~~ + 3 | bar2(Bax) string + 4 | } +vlib/v/checker/tests/interface_return_parameter_err.vv:3:10: error: unknown type `Bax`. +Did you mean `Foo`? + 1 | interface Foo { + 2 | bar(string) []Baz + 3 | bar2(Bax) string + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.vv b/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.vv new file mode 100644 index 0000000..e6168cf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_return_parameter_err.vv @@ -0,0 +1,4 @@ +interface Foo { + bar(string) []Baz + bar2(Bax) string +} diff --git a/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.out b/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.out new file mode 100644 index 0000000..669fb01 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/interface_too_many_embedding_levels.vv:9:1: error: too many interface embedding levels: 101, for interface `I103` + 7 | } + 8 | + 9 | interface I103 { + | ~~~~~~~~~~~~~~~~ + 10 | I102 + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.vv b/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.vv new file mode 100644 index 0000000..6975ef0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interface_too_many_embedding_levels.vv @@ -0,0 +1,431 @@ +interface I1 { + I0 +} + +interface I2 { + I1 +} + +interface I103 { + I102 +} + +interface I102 { + I101 +} + +interface I101 { + I100 +} + +interface I3 { + I2 +} + +interface I4 { + I3 +} + +interface I5 { + I4 +} + +interface I6 { + I5 +} + +interface I7 { + I6 +} + +interface I8 { + I7 +} + +interface I9 { + I8 +} + +interface I10 { + I9 +} + +interface I11 { + I10 +} + +interface I12 { + I11 +} + +interface I13 { + I12 +} + +interface I14 { + I13 +} + +interface I15 { + I14 +} + +interface I16 { + I15 +} + +interface I17 { + I16 +} + +interface I18 { + I17 +} + +interface I19 { + I18 +} + +interface I20 { + I19 +} + +interface I21 { + I20 +} + +interface I22 { + I21 +} + +interface I23 { + I22 +} + +interface I24 { + I23 +} + +interface I25 { + I24 +} + +interface I26 { + I25 +} + +interface I27 { + I26 +} + +interface I28 { + I27 +} + +interface I29 { + I28 +} + +interface I30 { + I29 +} + +interface I31 { + I30 +} + +interface I32 { + I31 +} + +interface I33 { + I32 +} + +interface I34 { + I33 +} + +interface I35 { + I34 +} + +interface I36 { + I35 +} + +interface I37 { + I36 +} + +interface I38 { + I37 +} + +interface I39 { + I38 +} + +interface I40 { + I39 +} + +interface I41 { + I40 +} + +interface I42 { + I41 +} + +interface I43 { + I42 +} + +interface I44 { + I43 +} + +interface I45 { + I44 +} + +interface I46 { + I45 +} + +interface I47 { + I46 +} + +interface I48 { + I47 +} + +interface I49 { + I48 +} + +interface I50 { + I49 +} + +interface I51 { + I50 +} + +interface I52 { + I51 +} + +interface I53 { + I52 +} + +interface I54 { + I53 +} + +interface I55 { + I54 +} + +interface I56 { + I55 +} + +interface I57 { + I56 +} + +interface I58 { + I57 +} + +interface I59 { + I58 +} + +interface I60 { + I59 +} + +interface I61 { + I60 +} + +interface I62 { + I61 +} + +interface I63 { + I62 +} + +interface I64 { + I63 +} + +interface I65 { + I64 +} + +interface I66 { + I65 +} + +interface I67 { + I66 +} + +interface I68 { + I67 +} + +interface I69 { + I68 +} + +interface I70 { + I69 +} + +interface I71 { + I70 +} + +interface I72 { + I71 +} + +interface I73 { + I72 +} + +interface I74 { + I73 +} + +interface I75 { + I74 +} + +interface I76 { + I75 +} + +interface I77 { + I76 +} + +interface I78 { + I77 +} + +interface I79 { + I78 +} + +interface I80 { + I79 +} + +interface I81 { + I80 +} + +interface I82 { + I81 +} + +interface I83 { + I82 +} + +interface I84 { + I83 +} + +interface I85 { + I84 +} + +interface I86 { + I85 +} + +interface I87 { + I86 +} + +interface I88 { + I87 +} + +interface I89 { + I88 +} + +interface I90 { + I89 +} + +interface I91 { + I90 +} + +interface I92 { + I91 +} + +interface I93 { + I92 +} + +interface I94 { + I93 +} + +interface I95 { + I94 +} + +interface I96 { + I95 +} + +interface I97 { + I96 +} + +interface I98 { + I97 +} + +interface I99 { + I98 +} + +interface I100 { + I99 +} + +interface I0 { + m999() int +} + +struct Abc { + x int = 123 +} + +fn (s Abc) m999() int { + return 999 +} + +fn main() { + a := Abc{} + dump(a) + i := I103(a) + dump(i) + assert i.m999() == 999 +} diff --git a/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.out b/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.out new file mode 100644 index 0000000..c47f72f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/interpolation_recursive_str_err.vv:8:8: error: cannot call `str()` method recursively + 6 | + 7 | fn (t Test) str() string { + 8 | _ = '$t' + | ^ + 9 | return 'test' + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.vv b/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.vv new file mode 100644 index 0000000..4f3f3d8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/interpolation_recursive_str_err.vv @@ -0,0 +1,15 @@ +module main + +struct Test { + a int +} + +fn (t Test) str() string { + _ = '$t' + return 'test' +} + +fn main() { + a := Test{} + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/invalid_char_err.out b/v_windows/v/vlib/v/checker/tests/invalid_char_err.out new file mode 100644 index 0000000..e4a2b5c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_char_err.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/invalid_char_err.vv:1:1: error: invalid character `🐈` + 1 | 🐈println('') + | ^ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/invalid_char_err.vv b/v_windows/v/vlib/v/checker/tests/invalid_char_err.vv new file mode 100644 index 0000000..0a27589 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_char_err.vv @@ -0,0 +1 @@ +🐈println('') diff --git a/v_windows/v/vlib/v/checker/tests/invalid_property.out b/v_windows/v/vlib/v/checker/tests/invalid_property.out new file mode 100644 index 0000000..e7839a4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_property.out @@ -0,0 +1,18 @@ +vlib/v/checker/tests/invalid_property.vv:2:7: error: `string` has no property `length` + 1 | s :='' + 2 | _ = s.length + | ~~~~~~ + 3 | _ = [1,2].foo + 4 | +vlib/v/checker/tests/invalid_property.vv:3:11: error: `[]int` has no property `foo` + 1 | s :='' + 2 | _ = s.length + 3 | _ = [1,2].foo + | ~~~ + 4 | + 5 | mut fa := [3,4]! +vlib/v/checker/tests/invalid_property.vv:6:8: error: `[2]int` has no property `bar` + 4 | + 5 | mut fa := [3,4]! + 6 | _ = fa.bar + | ~~~ diff --git a/v_windows/v/vlib/v/checker/tests/invalid_property.vv b/v_windows/v/vlib/v/checker/tests/invalid_property.vv new file mode 100644 index 0000000..427c2e7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_property.vv @@ -0,0 +1,6 @@ +s :='' +_ = s.length +_ = [1,2].foo + +mut fa := [3,4]! +_ = fa.bar diff --git a/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.out b/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.out new file mode 100644 index 0000000..7333400 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/invalid_vweb_param_type.vv:8:24: error: invalid type `[]bool` for parameter `list` in vweb app method `index` + 6 | + 7 | ['/:list'; get] + 8 | fn (mut app App) index(list []bool) vweb.Result { + | ~~~~ + 9 | return app.text('') + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.vv b/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.vv new file mode 100644 index 0000000..6274830 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invalid_vweb_param_type.vv @@ -0,0 +1,12 @@ +import vweb + +struct App { + vweb.Context +} + +['/:list'; get] +fn (mut app App) index(list []bool) vweb.Result { + return app.text('') +} + +vweb.run(&App{}, 5000) diff --git a/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.out b/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.out new file mode 100644 index 0000000..ad8c86f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.out @@ -0,0 +1,26 @@ +vlib/v/checker/tests/invert_other_types_bits_error.vv:2:13: error: operator ~ only defined on int types + 1 | fn main() { + 2 | println(~3.0) + | ^ + 3 | println(~10.5) + 4 | println(~'2') +vlib/v/checker/tests/invert_other_types_bits_error.vv:3:13: error: operator ~ only defined on int types + 1 | fn main() { + 2 | println(~3.0) + 3 | println(~10.5) + | ^ + 4 | println(~'2') + 5 | println(~[2, 4, 6]) +vlib/v/checker/tests/invert_other_types_bits_error.vv:4:13: error: operator ~ only defined on int types + 2 | println(~3.0) + 3 | println(~10.5) + 4 | println(~'2') + | ^ + 5 | println(~[2, 4, 6]) + 6 | } +vlib/v/checker/tests/invert_other_types_bits_error.vv:5:13: error: operator ~ only defined on int types + 3 | println(~10.5) + 4 | println(~'2') + 5 | println(~[2, 4, 6]) + | ^ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.vv b/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.vv new file mode 100644 index 0000000..ad74f64 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/invert_other_types_bits_error.vv @@ -0,0 +1,6 @@ +fn main() { + println(~3.0) + println(~10.5) + println(~'2') + println(~[2, 4, 6]) +} diff --git a/v_windows/v/vlib/v/checker/tests/is_type_invalid.out b/v_windows/v/vlib/v/checker/tests/is_type_invalid.out new file mode 100644 index 0000000..0459e96 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/is_type_invalid.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/is_type_invalid.vv:14:12: error: `IoS` has no variant `byte` + 12 | + 13 | fn main() { + 14 | if IoS(1) is byte { + | ~~ + 15 | println('not cool') + 16 | } +vlib/v/checker/tests/is_type_invalid.vv:18:7: error: `Cat` doesn't implement method `speak` of interface `Animal` + 16 | } + 17 | a := Animal(Dog{}) + 18 | if a is Cat { + | ~~ + 19 | println('not cool either') + 20 | } diff --git a/v_windows/v/vlib/v/checker/tests/is_type_invalid.vv b/v_windows/v/vlib/v/checker/tests/is_type_invalid.vv new file mode 100644 index 0000000..2e7f774 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/is_type_invalid.vv @@ -0,0 +1,21 @@ +type IoS = int | string + +interface Animal { + speak() +} + +struct Dog {} + +fn (d Dog) speak() {} + +struct Cat {} + +fn main() { + if IoS(1) is byte { + println('not cool') + } + a := Animal(Dog{}) + if a is Cat { + println('not cool either') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/is_type_not_exist.out b/v_windows/v/vlib/v/checker/tests/is_type_not_exist.out new file mode 100644 index 0000000..1c52d7a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/is_type_not_exist.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/is_type_not_exist.vv:8:10: error: is: type `SomethingThatDontExist` does not exist + 6 | + 7 | fn fn_with_sum_type_param(i Integer) { + 8 | if i is SomethingThatDontExist { + | ~~~~~~~~~~~~~~~~~~~~~~ + 9 | println('It should fail !') + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/is_type_not_exist.vv b/v_windows/v/vlib/v/checker/tests/is_type_not_exist.vv new file mode 100644 index 0000000..e50859b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/is_type_not_exist.vv @@ -0,0 +1,11 @@ +type Integer = i8 | i16 | int | i64 + +fn main() { + fn_with_sum_type_param(1) +} + +fn fn_with_sum_type_param(i Integer) { + if i is SomethingThatDontExist { + println('It should fail !') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/labelled_break_continue.out b/v_windows/v/vlib/v/checker/tests/labelled_break_continue.out new file mode 100644 index 0000000..caadcfc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/labelled_break_continue.out @@ -0,0 +1,35 @@ +vlib/v/checker/tests/labelled_break_continue.vv:7:14: error: invalid label name `L2` + 5 | i++ + 6 | for { + 7 | if i < 7 {continue L2} + | ~~~~~~~~ + 8 | else {break L2} + 9 | } +vlib/v/checker/tests/labelled_break_continue.vv:8:10: error: invalid label name `L2` + 6 | for { + 7 | if i < 7 {continue L2} + 8 | else {break L2} + | ~~~~~ + 9 | } + 10 | } +vlib/v/checker/tests/labelled_break_continue.vv:15:14: error: invalid label name `L1` + 13 | i = e + 14 | for { + 15 | if i < 3 {continue L1} + | ~~~~~~~~ + 16 | else {break L1} + 17 | } +vlib/v/checker/tests/labelled_break_continue.vv:16:10: error: invalid label name `L1` + 14 | for { + 15 | if i < 3 {continue L1} + 16 | else {break L1} + | ~~~~~ + 17 | } + 18 | } +vlib/v/checker/tests/labelled_break_continue.vv:21:11: error: nesting of labelled `for` loops is not supported + 19 | // check nested loops (not supported ATM) + 20 | L3: for ;; i++ { + 21 | L4: for { + | ^ + 22 | if i < 17 {continue L3} + 23 | else {break L3} diff --git a/v_windows/v/vlib/v/checker/tests/labelled_break_continue.vv b/v_windows/v/vlib/v/checker/tests/labelled_break_continue.vv new file mode 100644 index 0000000..3447ab1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/labelled_break_continue.vv @@ -0,0 +1,26 @@ +fn main() { + mut i := 4 + // check branching to a later loop + L1: for { + i++ + for { + if i < 7 {continue L2} + else {break L2} + } + } + // check branching to an earlier loop + L2: for e in [1,2,3,4] { + i = e + for { + if i < 3 {continue L1} + else {break L1} + } + } + // check nested loops (not supported ATM) + L3: for ;; i++ { + L4: for { + if i < 17 {continue L3} + else {break L3} + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/lock_already_locked.out b/v_windows/v/vlib/v/checker/tests/lock_already_locked.out new file mode 100644 index 0000000..29696aa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_already_locked.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/lock_already_locked.vv:11:3: error: nested `lock`/`rlock` not allowed + 9 | } + 10 | lock a { + 11 | rlock a { + | ~~~~~ + 12 | a.x++ + 13 | } +vlib/v/checker/tests/lock_already_locked.vv:15:10: error: `a` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 13 | } + 14 | } + 15 | println(a.x) + | ^ + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/lock_already_locked.vv b/v_windows/v/vlib/v/checker/tests/lock_already_locked.vv new file mode 100644 index 0000000..7bb6365 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_already_locked.vv @@ -0,0 +1,16 @@ +struct St { +mut: + x int +} + +fn main() { + shared a := &St{ + x: 5 + } + lock a { + rlock a { + a.x++ + } + } + println(a.x) +} diff --git a/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.out b/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.out new file mode 100644 index 0000000..b7e3fda --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/lock_already_rlocked.vv:11:3: error: nested `lock`/`rlock` not allowed + 9 | } + 10 | rlock a { + 11 | lock a { + | ~~~~ + 12 | a.x++ + 13 | } +vlib/v/checker/tests/lock_already_rlocked.vv:15:10: error: `a` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 13 | } + 14 | } + 15 | println(a.x) + | ^ + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.vv b/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.vv new file mode 100644 index 0000000..c8e8236 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_already_rlocked.vv @@ -0,0 +1,16 @@ +struct St { +mut: + x int +} + +fn main() { + shared a := &St{ + x: 5 + } + rlock a { + lock a { + a.x++ + } + } + println(a.x) +} diff --git a/v_windows/v/vlib/v/checker/tests/lock_const.out b/v_windows/v/vlib/v/checker/tests/lock_const.out new file mode 100644 index 0000000..2b923da --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_const.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/lock_const.vv:7:8: error: `a` must be declared as `shared` variable to be locked + 5 | fn main() { + 6 | mut c := 0 + 7 | rlock a { + | ^ + 8 | c = a + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/lock_const.vv b/v_windows/v/vlib/v/checker/tests/lock_const.vv new file mode 100644 index 0000000..df16a6a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_const.vv @@ -0,0 +1,11 @@ +const ( + a = 5 +) + +fn main() { + mut c := 0 + rlock a { + c = a + } + println(c) +} diff --git a/v_windows/v/vlib/v/checker/tests/lock_needed.out b/v_windows/v/vlib/v/checker/tests/lock_needed.out new file mode 100644 index 0000000..637cbb9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_needed.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/lock_needed.vv:10:2: error: `abc` is `shared` and needs explicit lock for `v.ast.SelectorExpr` + 8 | x: 5 + 9 | } + 10 | abc.x++ + | ~~~ + 11 | println(abc.x) + 12 | } +vlib/v/checker/tests/lock_needed.vv:11:10: error: `abc` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 9 | } + 10 | abc.x++ + 11 | println(abc.x) + | ~~~ + 12 | } + 13 | +vlib/v/checker/tests/lock_needed.vv:25:12: error: `a.st` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 23 | } + 24 | } + 25 | println(a.st.x) + | ~~ + 26 | } + 27 | +vlib/v/checker/tests/lock_needed.vv:30:10: error: `a` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 28 | fn g() { + 29 | shared a := []f64{len: 10, init: 7.5} + 30 | println(a[3]) + | ^ + 31 | } diff --git a/v_windows/v/vlib/v/checker/tests/lock_needed.vv b/v_windows/v/vlib/v/checker/tests/lock_needed.vv new file mode 100644 index 0000000..001aa7f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_needed.vv @@ -0,0 +1,31 @@ +struct St { +mut: + x int +} + +fn main() { + shared abc := &St{ + x: 5 + } + abc.x++ + println(abc.x) +} + +struct Abc { +mut: + st shared St +} + +fn f() { + mut a := Abc{ + st: St{ + x: 9 + } + } + println(a.st.x) +} + +fn g() { + shared a := []f64{len: 10, init: 7.5} + println(a[3]) +} diff --git a/v_windows/v/vlib/v/checker/tests/lock_nonshared.out b/v_windows/v/vlib/v/checker/tests/lock_nonshared.out new file mode 100644 index 0000000..00a05c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_nonshared.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/lock_nonshared.vv:10:7: error: `a` must be declared as `shared` variable to be locked + 8 | x: 5 + 9 | } + 10 | lock a { + | ^ + 11 | a.x++ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/lock_nonshared.vv b/v_windows/v/vlib/v/checker/tests/lock_nonshared.vv new file mode 100644 index 0000000..1524285 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/lock_nonshared.vv @@ -0,0 +1,14 @@ +struct St { +mut: + x int +} + +fn main() { + mut a := &St{ + x: 5 + } + lock a { + a.x++ + } + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/main_and_script_err.out b/v_windows/v/vlib/v/checker/tests/main_and_script_err.out new file mode 100644 index 0000000..71b5f75 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_and_script_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/main_and_script_err.vv:1:1: error: function `main` is already defined + 1 | fn main() { + | ^ + 2 | println('main') + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/main_and_script_err.vv b/v_windows/v/vlib/v/checker/tests/main_and_script_err.vv new file mode 100644 index 0000000..e746460 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_and_script_err.vv @@ -0,0 +1,4 @@ +fn main() { + println('main') +} +println('out') diff --git a/v_windows/v/vlib/v/checker/tests/main_args_err.out b/v_windows/v/vlib/v/checker/tests/main_args_err.out new file mode 100644 index 0000000..ee5f426 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_args_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/main_args_err.vv:1:1: error: function `main` cannot have arguments + 1 | fn main(a string) { + | ~~~~~~~~~~~~~~~~~ + 2 | println(a) + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/main_args_err.vv b/v_windows/v/vlib/v/checker/tests/main_args_err.vv new file mode 100644 index 0000000..69c9508 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_args_err.vv @@ -0,0 +1,3 @@ +fn main(a string) { + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/main_called_err.out b/v_windows/v/vlib/v/checker/tests/main_called_err.out new file mode 100644 index 0000000..1f1e253 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_called_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/main_called_err.vv:2:2: error: the `main` function cannot be called in the program + 1 | fn main() { + 2 | main() + | ~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/main_called_err.vv b/v_windows/v/vlib/v/checker/tests/main_called_err.vv new file mode 100644 index 0000000..2be2db1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_called_err.vv @@ -0,0 +1,3 @@ +fn main() { + main() +} diff --git a/v_windows/v/vlib/v/checker/tests/main_no_body_err.out b/v_windows/v/vlib/v/checker/tests/main_no_body_err.out new file mode 100644 index 0000000..09e4a9f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_no_body_err.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/main_no_body_err.vv:1:1: error: function `main` must declare a body + 1 | fn main() + | ~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/main_no_body_err.vv b/v_windows/v/vlib/v/checker/tests/main_no_body_err.vv new file mode 100644 index 0000000..d955fb2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_no_body_err.vv @@ -0,0 +1 @@ +fn main() diff --git a/v_windows/v/vlib/v/checker/tests/main_return_err.out b/v_windows/v/vlib/v/checker/tests/main_return_err.out new file mode 100644 index 0000000..c9ce435 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_return_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/main_return_err.vv:1:1: error: function `main` cannot return values + 1 | fn main() f64 { + | ~~~~~~~~~~~~~ + 2 | return 1.23 + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/main_return_err.vv b/v_windows/v/vlib/v/checker/tests/main_return_err.vv new file mode 100644 index 0000000..de52fe0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/main_return_err.vv @@ -0,0 +1,3 @@ +fn main() f64 { + return 1.23 +} diff --git a/v_windows/v/vlib/v/checker/tests/map_delete.out b/v_windows/v/vlib/v/checker/tests/map_delete.out new file mode 100644 index 0000000..ecfbca0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_delete.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/map_delete.vv:5:11: error: cannot use `int literal` as `string` in argument 1 to `Map.delete` + 3 | '1': 1 + 4 | } + 5 | m.delete(1) + | ^ + 6 | m.delete(1, 2) + 7 | m2 := { +vlib/v/checker/tests/map_delete.vv:6:4: error: expected 1 argument, but got 2 + 4 | } + 5 | m.delete(1) + 6 | m.delete(1, 2) + | ~~~~~~~~~~~~ + 7 | m2 := { + 8 | '1': 1 +vlib/v/checker/tests/map_delete.vv:10:2: error: `m2` is immutable, declare it with `mut` to make it mutable + 8 | '1': 1 + 9 | } + 10 | m2.delete('1') + | ~~ + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/map_delete.vv b/v_windows/v/vlib/v/checker/tests/map_delete.vv new file mode 100644 index 0000000..c9cd093 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_delete.vv @@ -0,0 +1,11 @@ +fn main() { + mut m := { + '1': 1 + } + m.delete(1) + m.delete(1, 2) + m2 := { + '1': 1 + } + m2.delete('1') +} diff --git a/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.out b/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.out new file mode 100644 index 0000000..943e7b1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/map_func_void_return_err.vv:2:22: error: type mismatch, `voids` does not return anything + 1 | fn main() { + 2 | list := [1,2,3].map(voids(it)) + | ~~~~~~~~~ + 3 | } + 4 | diff --git a/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.vv b/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.vv new file mode 100644 index 0000000..bd5446e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_func_void_return_err.vv @@ -0,0 +1,7 @@ +fn main() { + list := [1,2,3].map(voids(it)) +} + +fn voids(arg int) { + println(arg) +} diff --git a/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.out b/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.out new file mode 100644 index 0000000..c7fec6d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/map_init_invalid_syntax.vv:2:7: error: invalid empty map initilization syntax, use e.g. map[string]int{} instead + 1 | fn main() { + 2 | a := {} + | ~~ + 3 | println(a) + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.vv b/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.vv new file mode 100644 index 0000000..cf83b10 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_invalid_syntax.vv @@ -0,0 +1,4 @@ +fn main() { + a := {} + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.out new file mode 100644 index 0000000..61c061e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/map_init_key_duplicate_err.vv:5:3: error: duplicate key "foo" in map literal + 3 | 'foo': 'bar' + 4 | 'abc': 'abc' + 5 | 'foo': 'bar' + | ~~~~~ + 6 | } + 7 | println(a) +vlib/v/checker/tests/map_init_key_duplicate_err.vv:9:15: error: duplicate key "2" in map literal + 7 | println(a) + 8 | + 9 | _ = {2:0 3:0 2:0} + | ^ + 10 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.vv new file mode 100644 index 0000000..2309e1f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_key_duplicate_err.vv @@ -0,0 +1,10 @@ +fn main() { + a := { + 'foo': 'bar' + 'abc': 'abc' + 'foo': 'bar' + } + println(a) + + _ = {2:0 3:0 2:0} +} diff --git a/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.out b/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.out new file mode 100644 index 0000000..d58d7f8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/map_init_wrong_type.vv:3:15: error: invalid map value: expected `f32`, not `float literal` + 1 | fn main() { + 2 | mut a := map[string]f32{} + 3 | a = { 'x': 12.3 } + | ~~~~ + 4 | _ = {2:0 3:0 "hi":0} + 5 | _ = {2:0 3:`@` 4:0} +vlib/v/checker/tests/map_init_wrong_type.vv:4:17: error: invalid map key: expected `int`, not `string` + 2 | mut a := map[string]f32{} + 3 | a = { 'x': 12.3 } + 4 | _ = {2:0 3:0 "hi":0} + | ~~~~ + 5 | _ = {2:0 3:`@` 4:0} + 6 | _ = a +vlib/v/checker/tests/map_init_wrong_type.vv:5:15: error: invalid map value: expected `int`, not `rune` + 3 | a = { 'x': 12.3 } + 4 | _ = {2:0 3:0 "hi":0} + 5 | _ = {2:0 3:`@` 4:0} + | ~~~ + 6 | _ = a + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.vv b/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.vv new file mode 100644 index 0000000..56b7b96 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_init_wrong_type.vv @@ -0,0 +1,7 @@ +fn main() { + mut a := map[string]f32{} + a = { 'x': 12.3 } + _ = {2:0 3:0 "hi":0} + _ = {2:0 3:`@` 4:0} + _ = a +} diff --git a/v_windows/v/vlib/v/checker/tests/map_ops.out b/v_windows/v/vlib/v/checker/tests/map_ops.out new file mode 100644 index 0000000..d30508b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_ops.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/map_ops.vv:3:7: error: invalid key: expected `int`, not `rune` + 1 | fn test_map() { + 2 | mut m := map[int]string + 3 | _ = m[`!`] + | ~~~~~ + 4 | m['hi'] = 8 + 5 | m[&m] += 4 +vlib/v/checker/tests/map_ops.vv:4:3: error: invalid key: expected `int`, not `string` + 2 | mut m := map[int]string + 3 | _ = m[`!`] + 4 | m['hi'] = 8 + | ~~~~~~ + 5 | m[&m] += 4 + 6 | } +vlib/v/checker/tests/map_ops.vv:5:3: error: invalid key: expected `int`, not `&map[int]string` + 3 | _ = m[`!`] + 4 | m['hi'] = 8 + 5 | m[&m] += 4 + | ~~~~ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/map_ops.vv b/v_windows/v/vlib/v/checker/tests/map_ops.vv new file mode 100644 index 0000000..ac71947 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_ops.vv @@ -0,0 +1,6 @@ +fn test_map() { + mut m := map[int]string + _ = m[`!`] + m['hi'] = 8 + m[&m] += 4 +} diff --git a/v_windows/v/vlib/v/checker/tests/map_unknown_value.out b/v_windows/v/vlib/v/checker/tests/map_unknown_value.out new file mode 100644 index 0000000..d5f268b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_unknown_value.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/map_unknown_value.vv:2:23: error: unknown type `DoesNotExist` + 1 | struct App { + 2 | my_map map[string]DoesNotExist + | ~~~~~~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/map_unknown_value.vv b/v_windows/v/vlib/v/checker/tests/map_unknown_value.vv new file mode 100644 index 0000000..90cb87d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/map_unknown_value.vv @@ -0,0 +1,3 @@ +struct App { + my_map map[string]DoesNotExist +} diff --git a/v_windows/v/vlib/v/checker/tests/match_alias_type_err.out b/v_windows/v/vlib/v/checker/tests/match_alias_type_err.out new file mode 100644 index 0000000..49ddf1f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_alias_type_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/match_alias_type_err.vv:13:3: error: cannot match alias type `Stmt` with `SelectStmt` + 11 | + 12 | match stmt { + 13 | SelectStmt { panic('select') } + | ~~~~~~~~~~ + 14 | else { /* why? */ } + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_alias_type_err.vv b/v_windows/v/vlib/v/checker/tests/match_alias_type_err.vv new file mode 100644 index 0000000..2673231 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_alias_type_err.vv @@ -0,0 +1,16 @@ +type Stmt = SelectStmt + +struct SelectStmt {} + +fn parse(sql string) Stmt { + return SelectStmt{} +} + +fn main() { + stmt := parse('select 123') + + match stmt { + SelectStmt { panic('select') } + else { /* why? */ } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.out b/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.out new file mode 100644 index 0000000..11263d5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/match_duplicate_branch.vv:15:3: error: match case `St1` is handled more than once + 13 | match i { + 14 | St1 { println('St1') } + 15 | St1 { println('St1') } + | ~~~ + 16 | St2 { println('St2') } + 17 | } +vlib/v/checker/tests/match_duplicate_branch.vv:20:3: error: match case `St1` is handled more than once + 18 | match i { + 19 | St1 { println('St1') } + 20 | St1 { println('St1') } + | ~~~ + 21 | else { println('else') } + 22 | } +vlib/v/checker/tests/match_duplicate_branch.vv:29:3: error: match case `green` is handled more than once + 27 | .red { println('red') } + 28 | .green { println('green') } + 29 | .green { println('green') } + | ~~~~~~ + 30 | .blue { println('blue') } + 31 | } +vlib/v/checker/tests/match_duplicate_branch.vv:34:3: error: match case `green` is handled more than once + 32 | match c { + 33 | .red, .green { println('red green') } + 34 | .green { println('green') } + | ~~~~~~ + 35 | else { println('else') } + 36 | } +vlib/v/checker/tests/match_duplicate_branch.vv:43:3: error: match case `2` is handled more than once + 41 | 1 { println('1') } + 42 | 2 { println('2') } + 43 | 2 { println('3') } + | ^ + 44 | else { println('else') } + 45 | } +vlib/v/checker/tests/match_duplicate_branch.vv:51:3: error: match case `3` is handled more than once + 49 | match i { + 50 | 1...5 { println('1 to 5') } + 51 | 3 { println('3') } + | ^ + 52 | else { println('else') } + 53 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.vv b/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.vv new file mode 100644 index 0000000..dd5a92c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_duplicate_branch.vv @@ -0,0 +1,61 @@ +enum Color { + red + green + blue +} + +struct St1 {} +struct St2 {} + +type St = St1 | St2 + +fn test_sum_type(i St) { + match i { + St1 { println('St1') } + St1 { println('St1') } + St2 { println('St2') } + } + match i { + St1 { println('St1') } + St1 { println('St1') } + else { println('else') } + } +} + +fn test_enum(c Color) { + match c { + .red { println('red') } + .green { println('green') } + .green { println('green') } + .blue { println('blue') } + } + match c { + .red, .green { println('red green') } + .green { println('green') } + else { println('else') } + } +} + +fn test_int(i int) { + match i { + 1 { println('1') } + 2 { println('2') } + 2 { println('3') } + else { println('else') } + } +} + +fn test_range(i int) { + match i { + 1...5 { println('1 to 5') } + 3 { println('3') } + else { println('else') } + } +} + +fn main() { + test_sum_type(St1{}) + test_enum(.red) + test_int(2) + test_range(4) +} diff --git a/v_windows/v/vlib/v/checker/tests/match_else_last_expr.out b/v_windows/v/vlib/v/checker/tests/match_else_last_expr.out new file mode 100644 index 0000000..c3331f1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_else_last_expr.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/match_else_last_expr.vv:4:3: error: `else` must be the last branch of `match` + 2 | match 1 { + 3 | 1 { println('1') } + 4 | else { println('else') } + | ~~~~ + 5 | 4 { println('4') } + 6 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/match_else_last_expr.vv b/v_windows/v/vlib/v/checker/tests/match_else_last_expr.vv new file mode 100644 index 0000000..3aa5cc5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_else_last_expr.vv @@ -0,0 +1,7 @@ +fn main() { + match 1 { + 1 { println('1') } + else { println('else') } + 4 { println('4') } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.out b/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.out new file mode 100644 index 0000000..e42355f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/match_expr_and_expected_type_error.vv:3:3: error: cannot match `rune` with `string` + 1 | ch := `a` + 2 | match ch { + 3 | 'a' {} + | ~~~ + 4 | else {} + 5 | } +vlib/v/checker/tests/match_expr_and_expected_type_error.vv:9:3: error: cannot match `int` with `string` + 7 | i := 123 + 8 | match i { + 9 | 'a' {} + | ~~~ + 10 | else {} + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.vv b/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.vv new file mode 100644 index 0000000..ef1e62c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_and_expected_type_error.vv @@ -0,0 +1,11 @@ +ch := `a` +match ch { + 'a' {} + else {} +} + +i := 123 +match i { + 'a' {} + else {} +} diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_else.out b/v_windows/v/vlib/v/checker/tests/match_expr_else.out new file mode 100644 index 0000000..cf70833 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_else.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/match_expr_else.vv:5:6: error: match must be exhaustive (add match branches for: `f64` or `else {}` at the end) + 3 | fn main() { + 4 | x := AA('test') + 5 | _ = match x { + | ~~~~~~~~~ + 6 | int { + 7 | 'int' +vlib/v/checker/tests/match_expr_else.vv:23:3: error: match expression is exhaustive, `else` is unnecessary + 21 | 'f64' + 22 | } + 23 | else { + | ~~~~ + 24 | 'else' + 25 | } +vlib/v/checker/tests/match_expr_else.vv:34:3: error: `else` must be the last branch of `match` + 32 | 'string' + 33 | } + 34 | else { + | ~~~~ + 35 | 'else' + 36 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_else.vv b/v_windows/v/vlib/v/checker/tests/match_expr_else.vv new file mode 100644 index 0000000..7c5551e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_else.vv @@ -0,0 +1,41 @@ +type AA = int | string | f64 + +fn main() { + x := AA('test') + _ = match x { + int { + 'int' + } + string { + 'string' + } + } + _ = match x { + int { + 'int' + } + string { + 'string' + } + f64 { + 'f64' + } + else { + 'else' + } + } + _ = match x { + int { + 'int' + } + string { + 'string' + } + else { + 'else' + } + f64 { + 'f64' + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.out b/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.out new file mode 100644 index 0000000..927da5a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/match_expr_empty_branch.vv:3:2: error: `match` expression requires an expression as the last statement of every branch + 1 | _ := match true { + 2 | true { 0 } + 3 | false {} + | ~~~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.vv b/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.vv new file mode 100644 index 0000000..2a8650d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_empty_branch.vv @@ -0,0 +1,4 @@ +_ := match true { + true { 0 } + false {} +} diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.out b/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.out new file mode 100644 index 0000000..6b5e161 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/match_expr_non_void_stmt_last.vv:9:4: error: `match` expression requires an expression as the last statement of every branch + 7 | } + 8 | []int { + 9 | return arr.str() + | ~~~~~~~~~~~~~~~~ + 10 | } + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.vv b/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.vv new file mode 100644 index 0000000..0737152 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_expr_non_void_stmt_last.vv @@ -0,0 +1,17 @@ +type Arr = []int | []string + +fn (arr Arr) str() string { + return match arr { + []string { + arr.join(' ') + } + []int { + return arr.str() + } + } +} + +fn main() { + println(Arr([0, 0])) + println(Arr(['0', '0'])) +} diff --git a/v_windows/v/vlib/v/checker/tests/match_invalid_type.out b/v_windows/v/vlib/v/checker/tests/match_invalid_type.out new file mode 100644 index 0000000..4c6b98b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_invalid_type.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/match_invalid_type.vv:5:3: error: `IoS` has no variant `byte` + 3 | fn sum() { + 4 | match IoS(1) { + 5 | byte { + | ~~~~ + 6 | println('not cool') + 7 | } +vlib/v/checker/tests/match_invalid_type.vv:4:2: error: match must be exhaustive (add match branches for: `int`, `string` or `else {}` at the end) + 2 | + 3 | fn sum() { + 4 | match IoS(1) { + | ~~~~~~~~~~~~~~ + 5 | byte { + 6 | println('not cool') +vlib/v/checker/tests/match_invalid_type.vv:24:3: error: `Cat` doesn't implement method `speak` of interface `Animal` + 22 | a := Animal(Dog{}) + 23 | match a { + 24 | Cat { + | ~~~ + 25 | println('not cool either') + 26 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_invalid_type.vv b/v_windows/v/vlib/v/checker/tests/match_invalid_type.vv new file mode 100644 index 0000000..3a5dca6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_invalid_type.vv @@ -0,0 +1,29 @@ +type IoS = int | string + +fn sum() { + match IoS(1) { + byte { + println('not cool') + } + } +} + +interface Animal { + speak() +} + +struct Dog {} + +fn (d Dog) speak() {} + +struct Cat {} + +fn iface() { + a := Animal(Dog{}) + match a { + Cat { + println('not cool either') + } + else {} + } +} diff --git a/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.out b/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.out new file mode 100644 index 0000000..3a8e8fe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/match_return_mismatch_type_err.vv:4:10: error: return type mismatch, it should be `string` + 2 | a := match 1 { + 3 | 1 { 'aa' } + 4 | else { 22 } + | ~~ + 5 | } + 6 | println(a) diff --git a/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.vv b/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.vv new file mode 100644 index 0000000..c422126 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_return_mismatch_type_err.vv @@ -0,0 +1,7 @@ +fn main() { + a := match 1 { + 1 { 'aa' } + else { 22 } + } + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.out b/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.out new file mode 100644 index 0000000..9f2201f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/match_sumtype_multiple_types.vv:26:13: error: type `Charlie` has no field or method `char` + 24 | match l { + 25 | Alfa, Charlie { + 26 | assert l.char == `a` + | ~~~~ + 27 | assert l.letter() == 'a' + 28 | } +vlib/v/checker/tests/match_sumtype_multiple_types.vv:27:13: error: unknown method: `Charlie.letter` + 25 | Alfa, Charlie { + 26 | assert l.char == `a` + 27 | assert l.letter() == 'a' + | ~~~~~~~~ + 28 | } + 29 | Bravo { \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.vv b/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.vv new file mode 100644 index 0000000..eca5954 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_sumtype_multiple_types.vv @@ -0,0 +1,33 @@ +struct Alfa { + char rune +} + +fn (a Alfa) letter() rune { + return a.char +} + +struct Bravo { + char rune +} + +fn (b Bravo) letter() rune { + return b.char +} + +struct Charlie {} + +type NATOAlphabet = Alfa | Bravo | Charlie + +fn method_not_exists() { + a := Alfa{} + l := NATOAlphabet(a) + match l { + Alfa, Charlie { + assert l.char == `a` + assert l.letter() == 'a' + } + Bravo { + assert false + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/match_undefined_cond.out b/v_windows/v/vlib/v/checker/tests/match_undefined_cond.out new file mode 100644 index 0000000..ddfed08 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_undefined_cond.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/match_undefined_cond.vv:4:15: error: undefined ident: `Asd` + 2 | + 3 | fn main() { + 4 | res := match Asd { + | ~~~ + 5 | 1 { 'foo' } + 6 | 2 { 'test' } +vlib/v/checker/tests/match_undefined_cond.vv:5:3: error: cannot match `void` with `int literal` + 3 | fn main() { + 4 | res := match Asd { + 5 | 1 { 'foo' } + | ^ + 6 | 2 { 'test' } + 7 | else { '' } +vlib/v/checker/tests/match_undefined_cond.vv:6:3: error: cannot match `void` with `int literal` + 4 | res := match Asd { + 5 | 1 { 'foo' } + 6 | 2 { 'test' } + | ^ + 7 | else { '' } + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/match_undefined_cond.vv b/v_windows/v/vlib/v/checker/tests/match_undefined_cond.vv new file mode 100644 index 0000000..952e4b7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/match_undefined_cond.vv @@ -0,0 +1,10 @@ +type Asd = int + +fn main() { + res := match Asd { + 1 { 'foo' } + 2 { 'test' } + else { '' } + } + _ = res +} diff --git a/v_windows/v/vlib/v/checker/tests/method_array_slice.out b/v_windows/v/vlib/v/checker/tests/method_array_slice.out new file mode 100644 index 0000000..dde9055 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_array_slice.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/method_array_slice.vv:5:12: error: .slice() is a private method, use `x[start..end]` instead + 3 | fn main() { + 4 | a := os.args.clone() + 5 | println(a.slice(1)) + | ~~~~~~~~ + 6 | println(a[1..]) + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/method_array_slice.vv b/v_windows/v/vlib/v/checker/tests/method_array_slice.vv new file mode 100644 index 0000000..cfb77f3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_array_slice.vv @@ -0,0 +1,7 @@ +import os + +fn main() { + a := os.args.clone() + println(a.slice(1)) + println(a[1..]) +} diff --git a/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.out b/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.out new file mode 100644 index 0000000..2017a6f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/method_generic_infer_err.vv:9:7: error: could not infer generic type `T` in call to `func` + 7 | fn main() { + 8 | data := Data{} + 9 | data.func() + | ~~~~~~ + 10 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.vv b/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.vv new file mode 100644 index 0000000..226c4c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_generic_infer_err.vv @@ -0,0 +1,10 @@ +struct Data {} + +fn (_ Data) func() T { + return T{} +} + +fn main() { + data := Data{} + data.func() +} diff --git a/v_windows/v/vlib/v/checker/tests/method_op_alias_err.out b/v_windows/v/vlib/v/checker/tests/method_op_alias_err.out new file mode 100644 index 0000000..195ab56 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_op_alias_err.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/method_op_alias_err.vv:4:18: error: expected `Foo` not `Foo2` - both operands must be the same type for operator overloading + 2 | type Foo2 = string + 3 | + 4 | fn (f Foo) + (f1 Foo2) Foo2 { + | ~~~~ + 5 | return Foo2(f + f1) + 6 | } +vlib/v/checker/tests/method_op_alias_err.vv:5:17: error: infix expr: cannot use `string` (right expression) as `string` + 3 | + 4 | fn (f Foo) + (f1 Foo2) Foo2 { + 5 | return Foo2(f + f1) + | ~~~~~~ + 6 | } + 7 | +vlib/v/checker/tests/method_op_alias_err.vv:8:1: error: cannot define operator methods on type alias for `string` + 6 | } + 7 | + 8 | fn (f Foo) * (f1 Foo) Foo { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + 9 | return Foo(f + f1) + 10 | } +vlib/v/checker/tests/method_op_alias_err.vv:14:6: error: mismatched types `Foo` and `string` + 12 | fn main() { + 13 | mut f := Foo('fg') + 14 | f += 'fg' + | ~~ + 15 | f *= Foo2('2') + 16 | f -= Foo('fo') +vlib/v/checker/tests/method_op_alias_err.vv:15:9: error: cannot assign to `f`: expected `Foo`, not `Foo2` + 13 | mut f := Foo('fg') + 14 | f += 'fg' + 15 | f *= Foo2('2') + | ~~~~~~~~~ + 16 | f -= Foo('fo') + 17 | println(f) +vlib/v/checker/tests/method_op_alias_err.vv:16:6: error: cannot use operator methods on type alias for `string` + 14 | f += 'fg' + 15 | f *= Foo2('2') + 16 | f -= Foo('fo') + | ~~ + 17 | println(f) + 18 | } diff --git a/v_windows/v/vlib/v/checker/tests/method_op_alias_err.vv b/v_windows/v/vlib/v/checker/tests/method_op_alias_err.vv new file mode 100644 index 0000000..d5b4ecd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_op_alias_err.vv @@ -0,0 +1,18 @@ +type Foo = string +type Foo2 = string + +fn (f Foo) + (f1 Foo2) Foo2 { + return Foo2(f + f1) +} + +fn (f Foo) * (f1 Foo) Foo { + return Foo(f + f1) +} + +fn main() { + mut f := Foo('fg') + f += 'fg' + f *= Foo2('2') + f -= Foo('fo') + println(f) +} diff --git a/v_windows/v/vlib/v/checker/tests/method_op_err.out b/v_windows/v/vlib/v/checker/tests/method_op_err.out new file mode 100644 index 0000000..547ceea --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_op_err.out @@ -0,0 +1,69 @@ +vlib/v/checker/tests/method_op_err.vv:11:1: error: operator methods should have exactly 1 argument + 9 | } + 10 | + 11 | fn (u User) + () { + | ~~~~~~~~~~~~~~~~ + 12 | } + 13 | +vlib/v/checker/tests/method_op_err.vv:14:18: error: expected `User` not `Foo` - both operands must be the same type for operator overloading + 12 | } + 13 | + 14 | fn (u User) - (f Foo) User { + | ~~~ + 15 | return User{u.a - f.a, u.b-f.a} + 16 | } +vlib/v/checker/tests/method_op_err.vv:18:9: error: receiver cannot be `mut` for operator overloading + 16 | } + 17 | + 18 | fn (mut u User) * (u1 User) User { + | ~~~~~~ + 19 | return User{} + 20 | } +vlib/v/checker/tests/method_op_err.vv:22:1: error: argument cannot be `mut` for operator overloading + 20 | } + 21 | + 22 | fn (u User) / (mut u1 User) User { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 23 | return User{} + 24 | } +vlib/v/checker/tests/method_op_err.vv:32:13: error: infix expr: cannot use `Foo` (right expression) as `User` + 30 | fn main() { + 31 | println(User{3, 4}) + 32 | println(User{3, 4} - Foo{3, 3}) + | ~~~~~~~~~~~~~~~~~~~~~~ + 33 | println(User{3, 2} < User{2, 4}) + 34 | println(User{3, 4} < Foo{3, 4}) +vlib/v/checker/tests/method_op_err.vv:33:13: error: undefined operation `User` < `User` + 31 | println(User{3, 4}) + 32 | println(User{3, 4} - Foo{3, 3}) + 33 | println(User{3, 2} < User{2, 4}) + | ~~~~~~~~~~~~~~~~~~~~~~~ + 34 | println(User{3, 4} < Foo{3, 4}) + 35 | mut u := User{3, 4} +vlib/v/checker/tests/method_op_err.vv:34:13: error: mismatched types `User` and `Foo` + 32 | println(User{3, 4} - Foo{3, 3}) + 33 | println(User{3, 2} < User{2, 4}) + 34 | println(User{3, 4} < Foo{3, 4}) + | ~~~~~~~~~~~~~~~~~~~~~~ + 35 | mut u := User{3, 4} + 36 | _ = u +vlib/v/checker/tests/method_op_err.vv:37:10: error: cannot assign to `u`: expected `User`, not `int literal` + 35 | mut u := User{3, 4} + 36 | _ = u + 37 | u += 12 + | ~~ + 38 | u %= User{1, 3} + 39 | u += User{2, 3} +vlib/v/checker/tests/method_op_err.vv:38:5: error: operator %= not defined on left operand type `User` + 36 | _ = u + 37 | u += 12 + 38 | u %= User{1, 3} + | ^ + 39 | u += User{2, 3} + 40 | } +vlib/v/checker/tests/method_op_err.vv:39:7: error: operator `+` must return `User` to be used as an assignment operator + 37 | u += 12 + 38 | u %= User{1, 3} + 39 | u += User{2, 3} + | ~~ + 40 | } diff --git a/v_windows/v/vlib/v/checker/tests/method_op_err.vv b/v_windows/v/vlib/v/checker/tests/method_op_err.vv new file mode 100644 index 0000000..24a82dd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_op_err.vv @@ -0,0 +1,40 @@ +struct User { + a int + b int +} + +struct Foo { + a int + b int +} + +fn (u User) + () { +} + +fn (u User) - (f Foo) User { + return User{u.a - f.a, u.b-f.a} +} + +fn (mut u User) * (u1 User) User { + return User{} +} + +fn (u User) / (mut u1 User) User { + return User{} +} + +fn (u User) + (u1 User) Foo { + return Foo{a: u.a + u1.a, b: u.b + u1.b} +} + +fn main() { + println(User{3, 4}) + println(User{3, 4} - Foo{3, 3}) + println(User{3, 2} < User{2, 4}) + println(User{3, 4} < Foo{3, 4}) + mut u := User{3, 4} + _ = u + u += 12 + u %= User{1, 3} + u += User{2, 3} +} diff --git a/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.out b/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.out new file mode 100644 index 0000000..c5175b3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/method_wrong_arg_type.vv:10:9: error: cannot use `MyEnum` as `string` in argument 1 to `Sss.info` + 8 | e := MyEnum.x + 9 | s := Sss{} + 10 | s.info(e) + | ^ + 11 | } + 12 | +vlib/v/checker/tests/method_wrong_arg_type.vv:18:8: error: cannot use `int` as `&Sss` in argument 1 to `Sss.ptr` + 16 | s := Sss{} + 17 | v := 4 + 18 | s.ptr(v) + | ^ + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.vv b/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.vv new file mode 100644 index 0000000..d44a4cd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/method_wrong_arg_type.vv @@ -0,0 +1,19 @@ +enum MyEnum { x y z } +pub fn (e MyEnum) str() string { return int(e).str() } + +struct Sss { } +fn (s Sss) info(msg string) { println(msg) } + +fn enum_str() { + e := MyEnum.x + s := Sss{} + s.info(e) +} + +fn (s Sss) ptr(p &Sss) {} + +fn ptr_arg() { + s := Sss{} + v := 4 + s.ptr(v) +} diff --git a/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.out new file mode 100644 index 0000000..b6895aa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.out @@ -0,0 +1,62 @@ +vlib/v/checker/tests/minus_op_wrong_type_err.vv:10:10: error: mismatched types `Aaa` and `int literal` + 8 | + 9 | fn main() { + 10 | println(Aaa{} - 10) + | ~~~~~~~~~~ + 11 | println(10 - Aaa{}) + 12 | println([1, 2, 3] - 10) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:11:10: error: mismatched types `int literal` and `Aaa` + 9 | fn main() { + 10 | println(Aaa{} - 10) + 11 | println(10 - Aaa{}) + | ~~~~~~~~~~ + 12 | println([1, 2, 3] - 10) + 13 | println(10 - [1, 2, 3]) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:12:10: error: mismatched types `[]int` and `int literal` + 10 | println(Aaa{} - 10) + 11 | println(10 - Aaa{}) + 12 | println([1, 2, 3] - 10) + | ~~~~~~~~~~~~~~ + 13 | println(10 - [1, 2, 3]) + 14 | a := map[string]int{} +vlib/v/checker/tests/minus_op_wrong_type_err.vv:13:10: error: mismatched types `int literal` and `[]int` + 11 | println(10 - Aaa{}) + 12 | println([1, 2, 3] - 10) + 13 | println(10 - [1, 2, 3]) + | ~~~~~~~~~~~~~~ + 14 | a := map[string]int{} + 15 | println(a - 10) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:15:10: error: mismatched types `map[string]int` and `int literal` + 13 | println(10 - [1, 2, 3]) + 14 | a := map[string]int{} + 15 | println(a - 10) + | ~~~~~~ + 16 | println(10 - a) + 17 | println(-Aaa{}) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:16:10: error: mismatched types `int literal` and `map[string]int` + 14 | a := map[string]int{} + 15 | println(a - 10) + 16 | println(10 - a) + | ~~~~~~ + 17 | println(-Aaa{}) + 18 | println(-a) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:17:10: error: - operator can only be used with numeric types + 15 | println(a - 10) + 16 | println(10 - a) + 17 | println(-Aaa{}) + | ^ + 18 | println(-a) + 19 | println(-Color.red) +vlib/v/checker/tests/minus_op_wrong_type_err.vv:18:10: error: - operator can only be used with numeric types + 16 | println(10 - a) + 17 | println(-Aaa{}) + 18 | println(-a) + | ^ + 19 | println(-Color.red) + 20 | } +vlib/v/checker/tests/minus_op_wrong_type_err.vv:19:10: error: - operator can only be used with numeric types + 17 | println(-Aaa{}) + 18 | println(-a) + 19 | println(-Color.red) + | ^ + 20 | } diff --git a/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.vv new file mode 100644 index 0000000..e015064 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/minus_op_wrong_type_err.vv @@ -0,0 +1,20 @@ +struct Aaa {} + +enum Color { + red + green + blue +} + +fn main() { + println(Aaa{} - 10) + println(10 - Aaa{}) + println([1, 2, 3] - 10) + println(10 - [1, 2, 3]) + a := map[string]int{} + println(a - 10) + println(10 - a) + println(-Aaa{}) + println(-a) + println(-Color.red) +} diff --git a/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.out b/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.out new file mode 100644 index 0000000..a1a1d8a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/mismatched_ptr_op_ptr.vv:5:17: error: mismatched types `&string` and `string` + 3 | unsafe { + 4 | b := &a + 5 | println(b+*b) + | ~~~ + 6 | println(b+b) + 7 | } +vlib/v/checker/tests/mismatched_ptr_op_ptr.vv:6:17: error: invalid operator `+` to `&string` and `&string` + 4 | b := &a + 5 | println(b+*b) + 6 | println(b+b) + | ~~~ + 7 | } + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.vv b/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.vv new file mode 100644 index 0000000..21a859d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mismatched_ptr_op_ptr.vv @@ -0,0 +1,8 @@ +fn main() { + a := '1' + unsafe { + b := &a + println(b+*b) + println(b+b) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.out b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.out new file mode 100644 index 0000000..c6dc4b2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.out @@ -0,0 +1 @@ +builder error: Header file , needed for module `main` was not found. Please install the corresponding development headers. diff --git a/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.vv b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.vv new file mode 100644 index 0000000..d8bdb47 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_1.vv @@ -0,0 +1,6 @@ +module main + +// The following header file is intentionally missing. +// The #include does not have the optional explanation part +// after a `#` sign: +#include diff --git a/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out new file mode 100644 index 0000000..176ee7b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.out @@ -0,0 +1 @@ +builder error: Header file , needed for module `main` was not found. Please install missing C library. diff --git a/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv new file mode 100644 index 0000000..a76cc0b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv @@ -0,0 +1,6 @@ +module main + +// The following header file is intentionally missing. +// The part after `#` is an explanation message, that V will +// show, when it is not found: +#include # Please install missing C library diff --git a/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.out new file mode 100644 index 0000000..6ae53be --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.out @@ -0,0 +1,56 @@ +vlib/v/checker/tests/mod_op_wrong_type_err.vv:3:10: error: float modulo not allowed, use math.fmod() instead + 1 | struct Aaa{} + 2 | fn main() { + 3 | println(0.5 % 1) + | ~~~ + 4 | println(1 % 0.5) + 5 | println([1,2,3] % 1) +vlib/v/checker/tests/mod_op_wrong_type_err.vv:4:14: error: float modulo not allowed, use math.fmod() instead + 2 | fn main() { + 3 | println(0.5 % 1) + 4 | println(1 % 0.5) + | ~~~ + 5 | println([1,2,3] % 1) + 6 | println(1 % [1,2,3]) +vlib/v/checker/tests/mod_op_wrong_type_err.vv:5:10: error: mismatched types `[]int` and `int literal` + 3 | println(0.5 % 1) + 4 | println(1 % 0.5) + 5 | println([1,2,3] % 1) + | ~~~~~~~~~~~ + 6 | println(1 % [1,2,3]) + 7 | a := Aaa{} +vlib/v/checker/tests/mod_op_wrong_type_err.vv:6:10: error: mismatched types `int literal` and `[]int` + 4 | println(1 % 0.5) + 5 | println([1,2,3] % 1) + 6 | println(1 % [1,2,3]) + | ~~~~~~~~~~~ + 7 | a := Aaa{} + 8 | println(a % 1) +vlib/v/checker/tests/mod_op_wrong_type_err.vv:8:10: error: mismatched types `Aaa` and `int literal` + 6 | println(1 % [1,2,3]) + 7 | a := Aaa{} + 8 | println(a % 1) + | ~~~~~ + 9 | println(1 % a) + 10 | b := map[string]int +vlib/v/checker/tests/mod_op_wrong_type_err.vv:9:10: error: mismatched types `int literal` and `Aaa` + 7 | a := Aaa{} + 8 | println(a % 1) + 9 | println(1 % a) + | ~~~~~ + 10 | b := map[string]int + 11 | println(b % 1) +vlib/v/checker/tests/mod_op_wrong_type_err.vv:11:10: error: mismatched types `map[string]int` and `int literal` + 9 | println(1 % a) + 10 | b := map[string]int + 11 | println(b % 1) + | ~~~~~ + 12 | println(1 % b) + 13 | } +vlib/v/checker/tests/mod_op_wrong_type_err.vv:12:10: error: mismatched types `int literal` and `map[string]int` + 10 | b := map[string]int + 11 | println(b % 1) + 12 | println(1 % b) + | ~~~~~ + 13 | } + diff --git a/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.vv new file mode 100644 index 0000000..b699714 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mod_op_wrong_type_err.vv @@ -0,0 +1,13 @@ +struct Aaa{} +fn main() { + println(0.5 % 1) + println(1 % 0.5) + println([1,2,3] % 1) + println(1 % [1,2,3]) + a := Aaa{} + println(a % 1) + println(1 % a) + b := map[string]int + println(b % 1) + println(1 % b) +} diff --git a/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.out b/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.out new file mode 100644 index 0000000..c54a42d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/modify_const_with_ref.vv:11:11: error: `constant` is immutable, cannot have a mutable reference to it + 9 | mut unused_var := Foo{} + 10 | unused_var = Foo{} + 11 | mut c := &constant + | ^ + 12 | c.value = 200 + 13 | } +vlib/v/checker/tests/modify_const_with_ref.vv:9:6: error: unused variable: `unused_var` + 7 | + 8 | fn main() { + 9 | mut unused_var := Foo{} + | ~~~~~~~~~~ + 10 | unused_var = Foo{} + 11 | mut c := &constant diff --git a/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.vv b/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.vv new file mode 100644 index 0000000..872460e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modify_const_with_ref.vv @@ -0,0 +1,13 @@ +struct Foo { +mut: + value int +} + +const constant = Foo{ 100 } + +fn main() { + mut unused_var := Foo{} + unused_var = Foo{} + mut c := &constant + c.value = 200 +} diff --git a/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.out b/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.out new file mode 100644 index 0000000..6efd5f2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/module_not_at_same_line_err.vv:2:1: error: `module` and `main` must be at same line + 1 | module + 2 | main + | ~~~~ + 3 | fn main() { + 4 | println('hello, world') diff --git a/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.vv b/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.vv new file mode 100644 index 0000000..a0e52f9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/module_not_at_same_line_err.vv @@ -0,0 +1,5 @@ +module +main +fn main() { + println('hello, world') +} diff --git a/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore.out b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore.out new file mode 100644 index 0000000..a0cc315 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/modules/module_alias_started_with_underscore/main.v:3:1: error: module alias `_` cannot start with `_` + 1 | module main + 2 | + 3 | import underscore as _ + | ~~~~~~~~~~~~~~~~~~~~~~ + 4 | + 5 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/main.v b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/main.v new file mode 100644 index 0000000..39f80a1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/main.v @@ -0,0 +1,7 @@ +module main + +import underscore as _ + +fn main() { + _.foo() +} diff --git a/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/underscore.v b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/underscore.v new file mode 100644 index 0000000..ec83c1c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/module_alias_started_with_underscore/underscore.v @@ -0,0 +1,5 @@ +module underscore + +pub fn foo() { + println('bar') +} diff --git a/v_windows/v/vlib/v/checker/tests/modules/overload_return_type.out b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type.out new file mode 100644 index 0000000..dbdf6a5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/modules/overload_return_type/main.v:14:8: error: cannot assign to `two`: expected `point.Point`, not `int` + 12 | y: 1 + 13 | } + 14 | two = one + two + | ~~~~~~~~~ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/main.v b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/main.v new file mode 100644 index 0000000..b682c49 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/main.v @@ -0,0 +1,15 @@ +module main + +import point { Point } + +fn main() { + one := Point{ + x: 1 + y: 2 + } + mut two := Point{ + x: 5 + y: 1 + } + two = one + two +} diff --git a/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/point.v b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/point.v new file mode 100644 index 0000000..a26932b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/modules/overload_return_type/point.v @@ -0,0 +1,11 @@ +module point + +pub struct Point { +mut: + x int + y int +} + +pub fn (a Point) + (b Point) int { + return a.x + b.x +} diff --git a/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.out b/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.out new file mode 100644 index 0000000..6bd5ff9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.out @@ -0,0 +1,57 @@ +vlib/v/checker/tests/mul_op_wrong_type_err.vv:5:13: error: mismatched types `Aaa` and `int literal` + 3 | struct Aaa{} + 4 | fn main() { + 5 | println(Aaa{} * 10) + | ~~~~~~~~~~ + 6 | println(10 * Aaa{}) + 7 | println([1,2,3] * 10) +vlib/v/checker/tests/mul_op_wrong_type_err.vv:6:13: error: mismatched types `int literal` and `Aaa` + 4 | fn main() { + 5 | println(Aaa{} * 10) + 6 | println(10 * Aaa{}) + | ~~~~~~~~~~ + 7 | println([1,2,3] * 10) + 8 | println(10 * [1,2,3]) +vlib/v/checker/tests/mul_op_wrong_type_err.vv:7:13: error: mismatched types `[]int` and `int literal` + 5 | println(Aaa{} * 10) + 6 | println(10 * Aaa{}) + 7 | println([1,2,3] * 10) + | ~~~~~~~~~~~~ + 8 | println(10 * [1,2,3]) + 9 | a := map[string]int +vlib/v/checker/tests/mul_op_wrong_type_err.vv:8:13: error: mismatched types `int literal` and `[]int` + 6 | println(10 * Aaa{}) + 7 | println([1,2,3] * 10) + 8 | println(10 * [1,2,3]) + | ~~~~~~~~~~~~ + 9 | a := map[string]int + 10 | println(a * 10) +vlib/v/checker/tests/mul_op_wrong_type_err.vv:10:13: error: mismatched types `map[string]int` and `int literal` + 8 | println(10 * [1,2,3]) + 9 | a := map[string]int + 10 | println(a * 10) + | ~~~~~~ + 11 | println(10 * a) + 12 | c1 := cmplx.complex(1,-2) +vlib/v/checker/tests/mul_op_wrong_type_err.vv:11:13: error: mismatched types `int literal` and `map[string]int` + 9 | a := map[string]int + 10 | println(a * 10) + 11 | println(10 * a) + | ~~~~~~ + 12 | c1 := cmplx.complex(1,-2) + 13 | c2 := c1 * 2.0 +vlib/v/checker/tests/mul_op_wrong_type_err.vv:13:8: error: infix expr: cannot use `float literal` (right expression) as `math.complex.Complex` + 11 | println(10 * a) + 12 | c1 := cmplx.complex(1,-2) + 13 | c2 := c1 * 2.0 + | ~~~~~~~~ + 14 | println(c2) + 15 | c3 := 2.0 * c1 +vlib/v/checker/tests/mul_op_wrong_type_err.vv:15:8: error: infix expr: cannot use `math.complex.Complex` (right expression) as `float literal` + 13 | c2 := c1 * 2.0 + 14 | println(c2) + 15 | c3 := 2.0 * c1 + | ~~~~~~~~ + 16 | println(c3) + 17 | } + diff --git a/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.vv b/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.vv new file mode 100644 index 0000000..a1e3394 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mul_op_wrong_type_err.vv @@ -0,0 +1,17 @@ +import math +import math.complex as cmplx +struct Aaa{} +fn main() { + println(Aaa{} * 10) + println(10 * Aaa{}) + println([1,2,3] * 10) + println(10 * [1,2,3]) + a := map[string]int + println(a * 10) + println(10 * a) + c1 := cmplx.complex(1,-2) + c2 := c1 * 2.0 + println(c2) + c3 := 2.0 * c1 + println(c3) +} diff --git a/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.out new file mode 100644 index 0000000..731b9f7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/multi_const_field_name_duplicate_err.vv:2:8: error: duplicate const `aaa` + 1 | const (aaa = 1) + 2 | const (aaa = 2) + | ~~~ + 3 | fn main() { + 4 | println(aaa) diff --git a/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.vv new file mode 100644 index 0000000..883abac --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_const_field_name_duplicate_err.vv @@ -0,0 +1,5 @@ +const (aaa = 1) +const (aaa = 2) +fn main() { + println(aaa) +} diff --git a/v_windows/v/vlib/v/checker/tests/multi_names_err.out b/v_windows/v/vlib/v/checker/tests/multi_names_err.out new file mode 100644 index 0000000..a022208 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_names_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/multi_names_err.vv:2:4: error: unexpected name `a` + 1 | fn main() { + 2 | a a a a := 1 + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/multi_names_err.vv b/v_windows/v/vlib/v/checker/tests/multi_names_err.vv new file mode 100644 index 0000000..cff1441 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_names_err.vv @@ -0,0 +1,3 @@ +fn main() { + a a a a := 1 +} diff --git a/v_windows/v/vlib/v/checker/tests/multi_value_method_err.out b/v_windows/v/vlib/v/checker/tests/multi_value_method_err.out new file mode 100644 index 0000000..f5be76a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_value_method_err.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/multi_value_method_err.vv:1:7: error: cannot define method on multi-value + 1 | fn (v (int, int)) f() {} + | ~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/multi_value_method_err.vv b/v_windows/v/vlib/v/checker/tests/multi_value_method_err.vv new file mode 100644 index 0000000..a84f0b6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multi_value_method_err.vv @@ -0,0 +1 @@ +fn (v (int, int)) f() {} diff --git a/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.out b/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.out new file mode 100644 index 0000000..6fd4127 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/multiple_pointer_yield_err.vv:9:8: error: unexpected `&`, expecting expression + 7 | fn main() { + 8 | f := Fail{} + 9 | foo(&&&f) + | ^ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.vv b/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.vv new file mode 100644 index 0000000..0cad12b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/multiple_pointer_yield_err.vv @@ -0,0 +1,10 @@ +struct Fail { + name string +} + +fn foo(bar &&&Fail) {} + +fn main() { + f := Fail{} + foo(&&&f) +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_arg.out b/v_windows/v/vlib/v/checker/tests/mut_arg.out new file mode 100644 index 0000000..f18c285 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_arg.out @@ -0,0 +1,25 @@ +vlib/v/checker/tests/mut_arg.vv:6:3: error: `f` parameter `par` is `mut`, you need to provide `mut` e.g. `mut arg1` + 4 | } + 5 | + 6 | f([3,4]) + | ~~~~~ + 7 | mut a := [1,2] + 8 | f(a) +vlib/v/checker/tests/mut_arg.vv:8:3: error: `f` parameter `par` is `mut`, you need to provide `mut` e.g. `mut arg1` + 6 | f([3,4]) + 7 | mut a := [1,2] + 8 | f(a) + | ^ + 9 | + 10 | g(mut [3,4]) +vlib/v/checker/tests/mut_arg.vv:10:7: error: array literal can not be modified + 8 | f(a) + 9 | + 10 | g(mut [3,4]) + | ~~~~~ + 11 | g(mut a) +vlib/v/checker/tests/mut_arg.vv:11:7: error: `g` parameter `par` is not `mut`, `mut` is not needed` + 9 | + 10 | g(mut [3,4]) + 11 | g(mut a) + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/mut_arg.vv b/v_windows/v/vlib/v/checker/tests/mut_arg.vv new file mode 100644 index 0000000..0a2afdd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_arg.vv @@ -0,0 +1,11 @@ +fn f(mut par []int) { +} +fn g(par []int) { +} + +f([3,4]) +mut a := [1,2] +f(a) + +g(mut [3,4]) +g(mut a) diff --git a/v_windows/v/vlib/v/checker/tests/mut_args_warning.out b/v_windows/v/vlib/v/checker/tests/mut_args_warning.out new file mode 100644 index 0000000..726f4b8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_args_warning.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/mut_args_warning.vv:1:8: warning: use `mut f Foo` instead of `f mut Foo` + 1 | fn f(x mut []int) { x[0] = 1 } + | ~~~ + 2 | fn main() { + 3 | mut x := [0] diff --git a/v_windows/v/vlib/v/checker/tests/mut_args_warning.vv b/v_windows/v/vlib/v/checker/tests/mut_args_warning.vv new file mode 100644 index 0000000..c29b27d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_args_warning.vv @@ -0,0 +1,5 @@ +fn f(x mut []int) { x[0] = 1 } +fn main() { + mut x := [0] + f(mut x) +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.out b/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.out new file mode 100644 index 0000000..a98d17b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/mut_array_get_element_address_err.vv:3:20: error: cannot take the address of mutable array elements outside unsafe blocks + 1 | fn main() { + 2 | mut arr_int := [int(23), 45, 7, 8] + 3 | ele := &arr_int[1] + | ~~~ + 4 | println(ele) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.vv b/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.vv new file mode 100644 index 0000000..1afbcb3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_array_get_element_address_err.vv @@ -0,0 +1,5 @@ +fn main() { + mut arr_int := [int(23), 45, 7, 8] + ele := &arr_int[1] + println(ele) +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_int.out b/v_windows/v/vlib/v/checker/tests/mut_int.out new file mode 100644 index 0000000..20a092c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_int.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/mut_int.vv:1:14: error: mutable arguments are only allowed for arrays, interfaces, maps, pointers, structs or their aliases +return values instead: `fn foo(mut n int) {` => `fn foo(n int) int {` + 1 | fn foo(mut x int) { + | ~~~ + 2 | } + 3 | diff --git a/v_windows/v/vlib/v/checker/tests/mut_int.vv b/v_windows/v/vlib/v/checker/tests/mut_int.vv new file mode 100644 index 0000000..a268c5a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_int.vv @@ -0,0 +1,5 @@ +fn foo(mut x int) { +} + +fn main() { +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.out b/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.out new file mode 100644 index 0000000..1e58a50 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/mut_map_get_value_address_err.vv:3:12: error: cannot take the address of map values + 1 | fn main() { + 2 | mut m := {'key' : 3} + 3 | a := &m['key'] + | ~~~~~~~ + 4 | println(a) + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.vv b/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.vv new file mode 100644 index 0000000..1f3cc58 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_map_get_value_address_err.vv @@ -0,0 +1,5 @@ +fn main() { + mut m := {'key' : 3} + a := &m['key'] + println(a) +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_receiver.out b/v_windows/v/vlib/v/checker/tests/mut_receiver.out new file mode 100644 index 0000000..fb160a4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_receiver.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/mut_receiver.vv:5:4: warning: use `(mut f Foo)` instead of `(f mut Foo)` + 3 | name string + 4 | } + 5 | fn (f mut Foo) info() { + | ~~~~~~~~~~~ + 6 | f.name = 'foo' + 7 | } +vlib/v/checker/tests/mut_receiver.vv:8:4: error: use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)` + 6 | f.name = 'foo' + 7 | } + 8 | fn (mut f &Foo) info2() { + | ~~~~~~~~~~~~ + 9 | f.name = 'foo' + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/mut_receiver.vv b/v_windows/v/vlib/v/checker/tests/mut_receiver.vv new file mode 100644 index 0000000..fae05df --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_receiver.vv @@ -0,0 +1,13 @@ +struct Foo{ +mut: + name string +} +fn (f mut Foo) info() { + f.name = 'foo' +} +fn (mut f &Foo) info2() { + f.name = 'foo' +} +fn main() { + println('hello, world') +} diff --git a/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.out b/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.out new file mode 100644 index 0000000..e0cd490 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/mut_receiver_lit.vv:10:1: error: cannot pass expression as `mut` + 8 | } + 9 | + 10 | Box{}.set(0) + | ~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.vv b/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.vv new file mode 100644 index 0000000..e015e71 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/mut_receiver_lit.vv @@ -0,0 +1,10 @@ +struct Box { +mut: + value int +} + +fn (mut box Box) set(value int) { + box.value = value +} + +Box{}.set(0) diff --git a/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.out b/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.out new file mode 100644 index 0000000..1927447 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/negative_assign_to_unsigned.vv:3:9: error: Cannot assign negative value to unsigned integer type + 1 | fn main() { + 2 | mut u := u32(10) + 3 | u = -10 + | ~~~ + 4 | eprintln(u) + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.vv b/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.vv new file mode 100644 index 0000000..da7646d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/negative_assign_to_unsigned.vv @@ -0,0 +1,5 @@ +fn main() { + mut u := u32(10) + u = -10 + eprintln(u) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/nested_aliases.out b/v_windows/v/vlib/v/checker/tests/nested_aliases.out new file mode 100644 index 0000000..da93210 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/nested_aliases.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/nested_aliases.vv:2:16: error: type `MyInt` is an alias, use the original alias type `int` instead + 1 | type MyInt = int + 2 | type MyMyInt = MyInt + | ~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/nested_aliases.vv b/v_windows/v/vlib/v/checker/tests/nested_aliases.vv new file mode 100644 index 0000000..a21d0d3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/nested_aliases.vv @@ -0,0 +1,2 @@ +type MyInt = int +type MyMyInt = MyInt diff --git a/v_windows/v/vlib/v/checker/tests/no_heap_struct.out b/v_windows/v/vlib/v/checker/tests/no_heap_struct.out new file mode 100644 index 0000000..8a996a1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_heap_struct.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/no_heap_struct.vv:13:6: error: `x` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider declaring `Abc` as `[heap]`. + 11 | fn f(x &Abc) St { + 12 | s := St{ + 13 | a: x + | ^ + 14 | } + 15 | return s +vlib/v/checker/tests/no_heap_struct.vv:19:9: error: `x` cannot be returned outside `unsafe` blocks as it might refer to an object stored on stack. Consider declaring `Abc` as `[heap]`. + 17 | + 18 | fn g(mut x Abc) &Abc { + 19 | return x + | ^ + 20 | } + 21 | +vlib/v/checker/tests/no_heap_struct.vv:23:7: error: `x` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider declaring `Abc` as `[heap]`. + 21 | + 22 | fn h(x &Abc) &Abc { + 23 | y := x + | ^ + 24 | return y + 25 | } diff --git a/v_windows/v/vlib/v/checker/tests/no_heap_struct.vv b/v_windows/v/vlib/v/checker/tests/no_heap_struct.vv new file mode 100644 index 0000000..ce1d474 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_heap_struct.vv @@ -0,0 +1,25 @@ +struct Abc { +mut: + n int +} + +struct St { +mut: + a &Abc +} + +fn f(x &Abc) St { + s := St{ + a: x + } + return s +} + +fn g(mut x Abc) &Abc { + return x +} + +fn h(x &Abc) &Abc { + y := x + return y +} diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.out b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.out new file mode 100644 index 0000000..e01c3a7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/no_interface_instantiation_a.vv:4:9: error: cannot instantiate interface `Speaker` + 2 | + 3 | fn main() { + 4 | _ = Speaker{} + | ~~~~~~~~~ + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.vv b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.vv new file mode 100644 index 0000000..1d98644 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_a.vv @@ -0,0 +1,5 @@ +interface Speaker {} + +fn main() { + _ = Speaker{} +} diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.out b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.out new file mode 100644 index 0000000..0a8f1b9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/no_interface_instantiation_b.vv:6:5: error: expected 1 arguments, but got 0 + 4 | + 5 | fn main() { + 6 | my_fn() + | ~~~~~~~ + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.vv b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.vv new file mode 100644 index 0000000..4c73538 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_b.vv @@ -0,0 +1,7 @@ +interface Speaker {} + +fn my_fn(s Speaker) {} + +fn main() { + my_fn() +} diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.out b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.out new file mode 100644 index 0000000..11f0568 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/no_interface_instantiation_c.vv:9:9: error: cannot instantiate interface `Speaker` + 7 | fn main() { + 8 | my_fn( + 9 | speak: 1 + | ~~~~~~~~ + 10 | ) + 11 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.vv b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.vv new file mode 100644 index 0000000..0eb6eec --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_instantiation_c.vv @@ -0,0 +1,11 @@ +interface Speaker { + speak() +} + +fn my_fn(s Speaker) {} + +fn main() { + my_fn( + speak: 1 + ) +} diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_str.out b/v_windows/v/vlib/v/checker/tests/no_interface_str.out new file mode 100644 index 0000000..37b33f2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_str.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/no_interface_str.vv:18:12: error: interface `Animal` does not have a .str() method. Use typeof() instead + 16 | fn moin() { + 17 | a := get_animal() + 18 | println(a.str()) + | ~~~~~ + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/no_interface_str.vv b/v_windows/v/vlib/v/checker/tests/no_interface_str.vv new file mode 100644 index 0000000..b0da84a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_interface_str.vv @@ -0,0 +1,19 @@ +interface Animal { + speak() +} + +struct Cow { +} + +fn (c Cow)speak() { + println('moo') +} + +fn get_animal() Animal { + return Cow{} +} + +fn moin() { + a := get_animal() + println(a.str()) +} diff --git a/v_windows/v/vlib/v/checker/tests/no_main_mod.out b/v_windows/v/vlib/v/checker/tests/no_main_mod.out new file mode 100644 index 0000000..549575d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_main_mod.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/no_main_mod.vv:1:1: error: project must include a `main` module or be a shared library (compile with `v -shared`) + 1 | module a + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/no_main_mod.vv b/v_windows/v/vlib/v/checker/tests/no_main_mod.vv new file mode 100644 index 0000000..a32281d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_main_mod.vv @@ -0,0 +1 @@ +module a diff --git a/v_windows/v/vlib/v/checker/tests/no_main_println_err.out b/v_windows/v/vlib/v/checker/tests/no_main_println_err.out new file mode 100644 index 0000000..5b0d71e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_main_println_err.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/no_main_println_err.vv:1:5: error: expected 1 arguments, but got 0 + 1 | println() + | ~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/no_main_println_err.vv b/v_windows/v/vlib/v/checker/tests/no_main_println_err.vv new file mode 100644 index 0000000..2ac13fb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_main_println_err.vv @@ -0,0 +1 @@ + println() diff --git a/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.out b/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.out new file mode 100644 index 0000000..b55e1d4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/no_method_on_interface_propagation.vv:18:5: error: unknown method or field: `Cat.foo` + 16 | a := new_animal('persian') + 17 | if a is Cat { + 18 | a.foo() + | ~~~~~ + 19 | } + 20 | } diff --git a/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.vv b/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.vv new file mode 100644 index 0000000..a9673df --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_method_on_interface_propagation.vv @@ -0,0 +1,20 @@ +struct Cat { + breed string +} + +interface Animal { + breed string +} + +fn (a Animal) foo() {} + +fn new_animal(breed string) Animal { + return &Cat{breed} +} + +fn test_methods_on_interfaces_dont_exist_on_implementers() { + a := new_animal('persian') + if a is Cat { + a.foo() + } +} diff --git a/v_windows/v/vlib/v/checker/tests/no_pub_in_main.out b/v_windows/v/vlib/v/checker/tests/no_pub_in_main.out new file mode 100644 index 0000000..e1da080 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_pub_in_main.out @@ -0,0 +1,49 @@ +vlib/v/checker/tests/no_pub_in_main.vv:3:1: error: type alias `Integer` in module main cannot be declared public + 1 | module main + 2 | + 3 | pub type Integer = int + | ~~~~~~~~~~~~~~~~ + 4 | + 5 | pub type Float = f32 | f64 +vlib/v/checker/tests/no_pub_in_main.vv:5:1: error: sum type `Float` in module main cannot be declared public + 3 | pub type Integer = int + 4 | + 5 | pub type Float = f32 | f64 + | ~~~~~~~~~~~~~~ + 6 | + 7 | // Buggy ATM +vlib/v/checker/tests/no_pub_in_main.vv:10:1: error: enum `Color` in module main cannot be declared public + 8 | // pub type Fn = fn () int + 9 | + 10 | pub enum Color { + | ~~~~~~~~~~~~~~ + 11 | red + 12 | green +vlib/v/checker/tests/no_pub_in_main.vv:16:1: error: const in module main cannot be declared public + 14 | } + 15 | + 16 | pub const ( + | ~~~~~~~~~ + 17 | w = 'world' + 18 | ) +vlib/v/checker/tests/no_pub_in_main.vv:20:1: error: function `my_fn` in module main cannot be declared public + 18 | ) + 19 | + 20 | pub fn my_fn() int { + | ~~~~~~~~~~~~~~~~~~ + 21 | return 1 + 22 | } +vlib/v/checker/tests/no_pub_in_main.vv:24:1: error: function `main` cannot be declared public + 22 | } + 23 | + 24 | pub fn main() { + | ~~~~~~~~~~~~~ + 25 | println('main') + 26 | } +vlib/v/checker/tests/no_pub_in_main.vv:28:1: error: struct `MyStruct` in module main cannot be declared public + 26 | } + 27 | + 28 | pub struct MyStruct { + | ~~~~~~~~~~~~~~~~~~~ + 29 | field int + 30 | } diff --git a/v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.out b/v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.vv b/v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.vv new file mode 100644 index 0000000..3b66ce2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/no_warning_for_in_mut_var_unused.vv @@ -0,0 +1,7 @@ +fn main() { + mut arr := [1, 2, 3] + for mut v in arr { + v = 2 + } + println(arr) +} diff --git a/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.out b/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.out new file mode 100644 index 0000000..a885eb7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/non_lvalue_as_voidptr.vv:5:13: error: expression cannot be passed as `voidptr` + 3 | } + 4 | + 5 | println(add(5, 10)) + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.vv b/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.vv new file mode 100644 index 0000000..55de9f3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/non_lvalue_as_voidptr.vv @@ -0,0 +1,5 @@ +fn add(a voidptr, b voidptr) int { + return int(a) + int(b) +} + +println(add(5, 10)) diff --git a/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.out b/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.out new file mode 100644 index 0000000..7d155d0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.out @@ -0,0 +1,16 @@ +vlib/v/checker/tests/non_matching_functional_args.vv:27:6: error: cannot use `fn (mut Table)` as `fn (Table)` in argument 1 to `sum` + 25 | + 26 | fn main() { + 27 | sum(fn (mut t Table) { + | ~~~~~~~~~~~~~~~~~~ + 28 | t.rename() + 29 | println(t.name) +Details: `main.MyFn`'s expected fn argument: `zzzz` is NOT a pointer, but the passed fn argument: `t` is a pointer +vlib/v/checker/tests/non_matching_functional_args.vv:31:6: error: cannot use `fn (mut Table)` as `fn (Table)` in argument 1 to `sum` + 29 | println(t.name) + 30 | }) + 31 | sum(xxx) + | ~~~ + 32 | sum(yyy) + 33 | } +Details: `main.MyFn`'s expected fn argument: `zzzz` is NOT a pointer, but the passed fn argument: `mytable` is a pointer diff --git a/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.vv b/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.vv new file mode 100644 index 0000000..a0e90ff --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/non_matching_functional_args.vv @@ -0,0 +1,33 @@ +struct Table { +pub mut: + name string +} + +type MyFn = fn (zzzz Table) + +fn (mut t Table) rename() { + t.name = 'abc' +} + +fn yyy(t Table) { + println(t.name) +} + +fn xxx(mut mytable Table) { + mytable.rename() + println(mytable.name) +} + +fn sum(myfn MyFn) { + mut t := Table{} + myfn(t) +} + +fn main() { + sum(fn (mut t Table) { + t.rename() + println(t.name) + }) + sum(xxx) + sum(yyy) +} diff --git a/v_windows/v/vlib/v/checker/tests/none_type_cast_err.out b/v_windows/v/vlib/v/checker/tests/none_type_cast_err.out new file mode 100644 index 0000000..0d0ba86 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/none_type_cast_err.out @@ -0,0 +1,75 @@ +vlib/v/checker/tests/none_type_cast_err.vv:2:7: error: cannot cast `none` to `string` + 1 | fn main() { + 2 | _ := string(none) + | ~~~~~~~~~~~~ + 3 | _ := int(none) + 4 | _ := i8(none) +vlib/v/checker/tests/none_type_cast_err.vv:3:7: error: cannot cast `none` to `int` + 1 | fn main() { + 2 | _ := string(none) + 3 | _ := int(none) + | ~~~~~~~~~ + 4 | _ := i8(none) + 5 | _ := i16(none) +vlib/v/checker/tests/none_type_cast_err.vv:4:7: error: cannot cast `none` to `i8` + 2 | _ := string(none) + 3 | _ := int(none) + 4 | _ := i8(none) + | ~~~~~~~~ + 5 | _ := i16(none) + 6 | _ := i64(none) +vlib/v/checker/tests/none_type_cast_err.vv:5:7: error: cannot cast `none` to `i16` + 3 | _ := int(none) + 4 | _ := i8(none) + 5 | _ := i16(none) + | ~~~~~~~~~ + 6 | _ := i64(none) + 7 | _ := u16(none) +vlib/v/checker/tests/none_type_cast_err.vv:6:7: error: cannot cast `none` to `i64` + 4 | _ := i8(none) + 5 | _ := i16(none) + 6 | _ := i64(none) + | ~~~~~~~~~ + 7 | _ := u16(none) + 8 | _ := u32(none) +vlib/v/checker/tests/none_type_cast_err.vv:7:7: error: cannot cast `none` to `u16` + 5 | _ := i16(none) + 6 | _ := i64(none) + 7 | _ := u16(none) + | ~~~~~~~~~ + 8 | _ := u32(none) + 9 | _ := u64(none) +vlib/v/checker/tests/none_type_cast_err.vv:8:7: error: cannot cast `none` to `u32` + 6 | _ := i64(none) + 7 | _ := u16(none) + 8 | _ := u32(none) + | ~~~~~~~~~ + 9 | _ := u64(none) + 10 | _ := rune(none) +vlib/v/checker/tests/none_type_cast_err.vv:9:7: error: cannot cast `none` to `u64` + 7 | _ := u16(none) + 8 | _ := u32(none) + 9 | _ := u64(none) + | ~~~~~~~~~ + 10 | _ := rune(none) + 11 | _ := f32(none) +vlib/v/checker/tests/none_type_cast_err.vv:10:7: error: cannot cast `none` to `rune` + 8 | _ := u32(none) + 9 | _ := u64(none) + 10 | _ := rune(none) + | ~~~~~~~~~~ + 11 | _ := f32(none) + 12 | _ := f64(none) +vlib/v/checker/tests/none_type_cast_err.vv:11:7: error: cannot cast `none` to `f32` + 9 | _ := u64(none) + 10 | _ := rune(none) + 11 | _ := f32(none) + | ~~~~~~~~~ + 12 | _ := f64(none) + 13 | } +vlib/v/checker/tests/none_type_cast_err.vv:12:7: error: cannot cast `none` to `f64` + 10 | _ := rune(none) + 11 | _ := f32(none) + 12 | _ := f64(none) + | ~~~~~~~~~ + 13 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/none_type_cast_err.vv b/v_windows/v/vlib/v/checker/tests/none_type_cast_err.vv new file mode 100644 index 0000000..953b369 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/none_type_cast_err.vv @@ -0,0 +1,13 @@ +fn main() { + _ := string(none) + _ := int(none) + _ := i8(none) + _ := i16(none) + _ := i64(none) + _ := u16(none) + _ := u32(none) + _ := u64(none) + _ := rune(none) + _ := f32(none) + _ := f64(none) +} diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.out b/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.out new file mode 100644 index 0000000..02fbccc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv:4:6: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop + 2 | fn another() { + 3 | eprintln(@FN) + 4 | for { + | ^ + 5 | break + 6 | } +vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv:18:2: error: unreachable code after a [noreturn] call + 16 | eprintln('start') + 17 | abc() + 18 | eprintln('done') + | ~~~~~~~~~~~~~~~~ + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv b/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv new file mode 100644 index 0000000..84f413c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv @@ -0,0 +1,19 @@ +[noreturn] +fn another() { + eprintln(@FN) + for { + break + } +} + +[noreturn] +fn abc() { + eprintln(@FN) + another() +} + +fn main() { + eprintln('start') + abc() + eprintln('done') +} diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_with_return.out b/v_windows/v/vlib/v/checker/tests/noreturn_with_return.out new file mode 100644 index 0000000..d8c3279 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_with_return.out @@ -0,0 +1,19 @@ +vlib/v/checker/tests/noreturn_with_return.vv:2:1: error: [noreturn] functions cannot use return statements + 1 | [noreturn] + 2 | fn another() { + | ~~~~~~~~~~~~ + 3 | eprintln(@FN) + 4 | // for{} +vlib/v/checker/tests/noreturn_with_return.vv:6:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop + 4 | // for{} + 5 | // exit(0) + 6 | return + | ~~~~~~ + 7 | } + 8 | +vlib/v/checker/tests/noreturn_with_return.vv:18:2: error: unreachable code after a [noreturn] call + 16 | eprintln('start') + 17 | abc() + 18 | eprintln('done') + | ~~~~~~~~~~~~~~~~ + 19 | } diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_with_return.vv b/v_windows/v/vlib/v/checker/tests/noreturn_with_return.vv new file mode 100644 index 0000000..c5fdaa7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_with_return.vv @@ -0,0 +1,19 @@ +[noreturn] +fn another() { + eprintln(@FN) + // for{} + // exit(0) + return +} + +[noreturn] +fn abc() { + eprintln(@FN) + another() +} + +fn main() { + eprintln('start') + abc() + eprintln('done') +} diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.out b/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.out new file mode 100644 index 0000000..6215215 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv:3:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop + 1 | [noreturn] + 2 | fn another() { + 3 | eprintln(@FN) + | ~~~~~~~~~~~~~ + 4 | } + 5 | +vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv:15:2: error: unreachable code after a [noreturn] call + 13 | eprintln('start') + 14 | abc() + 15 | eprintln('done') + | ~~~~~~~~~~~~~~~~ + 16 | } diff --git a/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv b/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv new file mode 100644 index 0000000..d764d7d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv @@ -0,0 +1,16 @@ +[noreturn] +fn another() { + eprintln(@FN) +} + +[noreturn] +fn abc() { + eprintln(@FN) + another() +} + +fn main() { + eprintln('start') + abc() + eprintln('done') +} diff --git a/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.out b/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.out new file mode 100644 index 0000000..29eac2d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/oct_lit_without_digit_err.vv:2:14: error: number part of this octal is not provided + 1 | fn main() { + 2 | println(0o) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.vv b/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.vv new file mode 100644 index 0000000..195a010 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oct_lit_without_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0o) +} diff --git a/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.out b/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.out new file mode 100644 index 0000000..0dbccf9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/oct_lit_wrong_digit_err.vv:2:18: error: this octal number has unsuitable digit `8` + 1 | fn main() { + 2 | println(0o1118) + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.vv b/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.vv new file mode 100644 index 0000000..235e1a9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oct_lit_wrong_digit_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0o1118) +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_fn_err.out b/v_windows/v/vlib/v/checker/tests/optional_fn_err.out new file mode 100644 index 0000000..3e47b49 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_fn_err.out @@ -0,0 +1,168 @@ +vlib/v/checker/tests/optional_fn_err.vv:13:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 11 | + 12 | const ( + 13 | const_value = bar(0) + | ~~~~~~ + 14 | ) + 15 | +vlib/v/checker/tests/optional_fn_err.vv:19:14: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 17 | f fn (int) + 18 | mut: + 19 | value int = bar(0) + | ~~~~~~ + 20 | opt ?int = bar(0) + 21 | } +vlib/v/checker/tests/optional_fn_err.vv:33:2: error: foo() returns an option, so it should have either an `or {}` block, or `?` at the end + 31 | fn main() { + 32 | // call fn + 33 | foo() + | ~~~~~ + 34 | _ := bar(0) + 35 | println(twice(bar(0))) +vlib/v/checker/tests/optional_fn_err.vv:34:7: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 32 | // call fn + 33 | foo() + 34 | _ := bar(0) + | ~~~~~~ + 35 | println(twice(bar(0))) + 36 | +vlib/v/checker/tests/optional_fn_err.vv:35:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 33 | foo() + 34 | _ := bar(0) + 35 | println(twice(bar(0))) + | ~~~~~~ + 36 | + 37 | // anon fn +vlib/v/checker/tests/optional_fn_err.vv:38:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 36 | + 37 | // anon fn + 38 | fn (_ int) {}(bar(0)) + | ~~~~~~ + 39 | + 40 | // assert +vlib/v/checker/tests/optional_fn_err.vv:41:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 39 | + 40 | // assert + 41 | assert bar(true) + | ~~~~~~~~~ + 42 | + 43 | // struct +vlib/v/checker/tests/optional_fn_err.vv:46:10: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 44 | mut v := Data{ + 45 | f: fn (_ int) {}, + 46 | value: bar(0), + | ~~~~~~ + 47 | opt: bar(0), + 48 | } +vlib/v/checker/tests/optional_fn_err.vv:49:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 47 | opt: bar(0), + 48 | } + 49 | v.add(bar(0)) // call method + | ~~~~~~ + 50 | v.f(bar(0)) // call fn field + 51 | +vlib/v/checker/tests/optional_fn_err.vv:50:6: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 48 | } + 49 | v.add(bar(0)) // call method + 50 | v.f(bar(0)) // call fn field + | ~~~~~~ + 51 | + 52 | // array +vlib/v/checker/tests/optional_fn_err.vv:54:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 52 | // array + 53 | mut arr := [1, 2] + 54 | arr << bar(0) + | ~~~~~~ + 55 | // init + 56 | _ := [bar(0)] +vlib/v/checker/tests/optional_fn_err.vv:56:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 54 | arr << bar(0) + 55 | // init + 56 | _ := [bar(0)] + | ~~~~~~ + 57 | _ := []int{init: bar(0)} + 58 | _ := [bar(0)]! +vlib/v/checker/tests/optional_fn_err.vv:57:19: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 55 | // init + 56 | _ := [bar(0)] + 57 | _ := []int{init: bar(0)} + | ~~~~~~ + 58 | _ := [bar(0)]! + 59 | _ := [1]int{init: bar(0)} +vlib/v/checker/tests/optional_fn_err.vv:58:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 56 | _ := [bar(0)] + 57 | _ := []int{init: bar(0)} + 58 | _ := [bar(0)]! + | ~~~~~~ + 59 | _ := [1]int{init: bar(0)} + 60 | // index +vlib/v/checker/tests/optional_fn_err.vv:61:13: error: cannot use optional as index (array type `[]int`) + 59 | _ := [1]int{init: bar(0)} + 60 | // index + 61 | println(arr[bar(0)]) + | ~~~~~~~~ + 62 | // array builtin methods + 63 | arr.insert(0, bar(0)) +vlib/v/checker/tests/optional_fn_err.vv:63:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 61 | println(arr[bar(0)]) + 62 | // array builtin methods + 63 | arr.insert(0, bar(0)) + | ~~~~~~ + 64 | arr.prepend(bar(0)) + 65 | arr.contains(bar(0)) +vlib/v/checker/tests/optional_fn_err.vv:64:14: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 62 | // array builtin methods + 63 | arr.insert(0, bar(0)) + 64 | arr.prepend(bar(0)) + | ~~~~~~ + 65 | arr.contains(bar(0)) + 66 | arr.index(bar(0)) +vlib/v/checker/tests/optional_fn_err.vv:65:15: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 63 | arr.insert(0, bar(0)) + 64 | arr.prepend(bar(0)) + 65 | arr.contains(bar(0)) + | ~~~~~~ + 66 | arr.index(bar(0)) + 67 | println(arr.map(bar(0))) +vlib/v/checker/tests/optional_fn_err.vv:66:12: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 64 | arr.prepend(bar(0)) + 65 | arr.contains(bar(0)) + 66 | arr.index(bar(0)) + | ~~~~~~ + 67 | println(arr.map(bar(0))) + 68 | println(arr.filter(bar(true))) +vlib/v/checker/tests/optional_fn_err.vv:67:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 65 | arr.contains(bar(0)) + 66 | arr.index(bar(0)) + 67 | println(arr.map(bar(0))) + | ~~~~~~ + 68 | println(arr.filter(bar(true))) + 69 | println(arr.any(bar(true))) +vlib/v/checker/tests/optional_fn_err.vv:68:21: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 66 | arr.index(bar(0)) + 67 | println(arr.map(bar(0))) + 68 | println(arr.filter(bar(true))) + | ~~~~~~~~~ + 69 | println(arr.any(bar(true))) + 70 | println(arr.all(bar(true))) +vlib/v/checker/tests/optional_fn_err.vv:69:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 67 | println(arr.map(bar(0))) + 68 | println(arr.filter(bar(true))) + 69 | println(arr.any(bar(true))) + | ~~~~~~~~~ + 70 | println(arr.all(bar(true))) + 71 | +vlib/v/checker/tests/optional_fn_err.vv:70:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 68 | println(arr.filter(bar(true))) + 69 | println(arr.any(bar(true))) + 70 | println(arr.all(bar(true))) + | ~~~~~~~~~ + 71 | + 72 | match bar(0) { +vlib/v/checker/tests/optional_fn_err.vv:72:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end + 70 | println(arr.all(bar(true))) + 71 | + 72 | match bar(0) { + | ~~~~~~ + 73 | 0 { } + 74 | else { } diff --git a/v_windows/v/vlib/v/checker/tests/optional_fn_err.vv b/v_windows/v/vlib/v/checker/tests/optional_fn_err.vv new file mode 100644 index 0000000..b3d82d1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_fn_err.vv @@ -0,0 +1,76 @@ + +// use optional without ? or an or block in places where it is not allowed + +fn foo() ? { + println('foo is called') +} + +fn bar(v T) ?T { + return none +} + +const ( + const_value = bar(0) +) + +struct Data { + f fn (int) +mut: + value int = bar(0) + opt ?int = bar(0) +} + +fn (mut v Data) add(n int) { + v.value += n +} + +fn twice(n int) int { + return n * 2 +} + +fn main() { + // call fn + foo() + _ := bar(0) + println(twice(bar(0))) + + // anon fn + fn (_ int) {}(bar(0)) + + // assert + assert bar(true) + + // struct + mut v := Data{ + f: fn (_ int) {}, + value: bar(0), + opt: bar(0), + } + v.add(bar(0)) // call method + v.f(bar(0)) // call fn field + + // array + mut arr := [1, 2] + arr << bar(0) + // init + _ := [bar(0)] + _ := []int{init: bar(0)} + _ := [bar(0)]! + _ := [1]int{init: bar(0)} + // index + println(arr[bar(0)]) + // array builtin methods + arr.insert(0, bar(0)) + arr.prepend(bar(0)) + arr.contains(bar(0)) + arr.index(bar(0)) + println(arr.map(bar(0))) + println(arr.filter(bar(true))) + println(arr.any(bar(true))) + println(arr.all(bar(true))) + + match bar(0) { + 0 { } + else { } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.out b/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.out new file mode 100644 index 0000000..8f13586 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/optional_in_println_mismatch.vv:6:23: error: wrong return type `string` in the `or {}` block, expected `int` + 4 | + 5 | fn main() { + 6 | println(funcy() or { '' }) + | ~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.vv b/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.vv new file mode 100644 index 0000000..fd3d067 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_in_println_mismatch.vv @@ -0,0 +1,7 @@ +fn funcy() ?int { + return none +} + +fn main() { + println(funcy() or { '' }) +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.out b/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.out new file mode 100644 index 0000000..64fe585 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/optional_interface_mismatch.vv:11:9: error: mismatched types `?MObject` and `string` + 9 | + 10 | fn give_string(line string) ?MObject { + 11 | return if true { 'string' } else { 'string' } + | ~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.vv b/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.vv new file mode 100644 index 0000000..1138fa3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_interface_mismatch.vv @@ -0,0 +1,12 @@ +fn main() { + le_string := give_string('string') or { return } + le_string.unimplemented() +} + +interface MObject { + unimplemented() string +} + +fn give_string(line string) ?MObject { + return if true { 'string' } else { 'string' } +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.out b/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.out new file mode 100644 index 0000000..d48fd8f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/optional_or_block_mismatch.vv:10:18: error: wrong return type `Bar` in the `or {}` block, expected `&Bar` + 8 | + 9 | fn main() { + 10 | x := foo() or { Bar{} } + | ~~~~~ + 11 | println(x) + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.vv b/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.vv new file mode 100644 index 0000000..849a7aa --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_mismatch.vv @@ -0,0 +1,12 @@ +module main + +struct Bar {} + +fn foo() ?&Bar { + return none +} + +fn main() { + x := foo() or { Bar{} } + println(x) +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.out b/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.out new file mode 100644 index 0000000..ed608c3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/optional_or_block_none_err.vv:18:3: error: wrong return type `none` in the `or {}` block, expected `Animal` + 16 | fn main() { + 17 | mut dog := new_animal(9) or { + 18 | none + | ~~~~ + 19 | } + 20 | diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.vv b/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.vv new file mode 100644 index 0000000..c1970a3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_none_err.vv @@ -0,0 +1,22 @@ +module main + +struct Animal { + mut: + height byte +} + +fn new_animal(height byte) ?Animal { + if height < 10 { + return error('Too small to be an animal!') + } + + return Animal{ height: height } +} + +fn main() { + mut dog := new_animal(9) or { + none + } + + println(dog) +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.out b/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.out new file mode 100644 index 0000000..0cd1b38 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.vv:13:3: error: the default expression type in the `or` block should be `string`, instead you gave a value of type `int literal` + 11 | // must be of the same type of the return + 12 | // type of the `test_optional` function + 13 | 123 + | ~~~ + 14 | // 'I break things' + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.vv b/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.vv new file mode 100644 index 0000000..9935bb7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_or_block_returns_value_of_incompatible_type.vv @@ -0,0 +1,16 @@ +fn test_optional(fail bool) ?string { + if fail { + return error('false') + } + return 'fff' +} + +fn main() { + // a := test_optional(false) or { println(err) } + test_optional(true) or { + // must be of the same type of the return + // type of the `test_optional` function + 123 + // 'I break things' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.out b/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.out new file mode 100644 index 0000000..f113a89 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/optional_propagate_nested.vv:10:19: error: to propagate the optional call, `xx_prop` must return an optional + 8 | + 9 | fn xx_prop() string { + 10 | s := ret(raise() ?) + | ^ + 11 | return s + 12 | } +vlib/v/checker/tests/optional_propagate_nested.vv:28:22: error: to propagate the optional call, `aa_propagate` must return an optional + 26 | + 27 | fn (mut s St) aa_propagate() { + 28 | f := retf(s.raise() ?) + | ^ + 29 | s.z = 7.5 + 30 | println(f) diff --git a/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.vv b/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.vv new file mode 100644 index 0000000..d00ef53 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_propagate_nested.vv @@ -0,0 +1,31 @@ +fn ret(s string) string { + return s +} + +fn raise() ?string { + return none +} + +fn xx_prop() string { + s := ret(raise() ?) + return s +} + +struct St { +mut: + z f64 +} + +fn (mut s St) raise() ?f64 { + return error('some error') +} + +fn retf(f f64) f64 { + return f +} + +fn (mut s St) aa_propagate() { + f := retf(s.raise() ?) + s.z = 7.5 + println(f) +} diff --git a/v_windows/v/vlib/v/checker/tests/optional_type_call_err.out b/v_windows/v/vlib/v/checker/tests/optional_type_call_err.out new file mode 100644 index 0000000..6550765 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_type_call_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/optional_type_call_err.vv:4:5: error: optional type cannot be called directly + 2 | + 3 | fn main() { + 4 | os.ls('.').filter(it.ends_with('.v')) or { return } + | ~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/optional_type_call_err.vv b/v_windows/v/vlib/v/checker/tests/optional_type_call_err.vv new file mode 100644 index 0000000..1b0cfa0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/optional_type_call_err.vv @@ -0,0 +1,5 @@ +import os + +fn main() { + os.ls('.').filter(it.ends_with('.v')) or { return } +} diff --git a/v_windows/v/vlib/v/checker/tests/or_err.out b/v_windows/v/vlib/v/checker/tests/or_err.out new file mode 100644 index 0000000..5aaf5f2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/or_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/or_err.vv:4:10: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope + 2 | return none + 3 | } + 4 | a := f() or { + | ~~~~ + 5 | {} + 6 | } +vlib/v/checker/tests/or_err.vv:11:2: error: wrong return type `rune` in the `or {}` block, expected `&int` + 9 | } + 10 | _ = f() or { + 11 | `.` + | ~~~ + 12 | } + 13 | diff --git a/v_windows/v/vlib/v/checker/tests/or_err.vv b/v_windows/v/vlib/v/checker/tests/or_err.vv new file mode 100644 index 0000000..035b87e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/or_err.vv @@ -0,0 +1,13 @@ +fn f() ?&int { + return none +} +a := f() or { + {} +} +_ = f() or { + a +} +_ = f() or { + `.` +} + diff --git a/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.out b/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.out new file mode 100644 index 0000000..352b56b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/or_expr_types_mismatch.vv:3:19: error: wrong return type `none` in the `or {}` block, expected `string` + 1 | fn get_map() ?string { + 2 | m := {1: 'a', 2: 'b'} + 3 | return m[1] or { none } + | ~~~~ + 4 | } + 5 | +vlib/v/checker/tests/or_expr_types_mismatch.vv:8:19: error: wrong return type `none` in the `or {}` block, expected `int` + 6 | fn get_array() ?int { + 7 | a := [1, 2, 3] + 8 | return a[4] or { none } + | ~~~~ + 9 | } + 10 | diff --git a/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.vv b/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.vv new file mode 100644 index 0000000..ab5f1c8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/or_expr_types_mismatch.vv @@ -0,0 +1,17 @@ +fn get_map() ?string { + m := {1: 'a', 2: 'b'} + return m[1] or { none } +} + +fn get_array() ?int { + a := [1, 2, 3] + return a[4] or { none } +} + +fn main() { + map_result := get_map() or { return } + println(map_result) + + array_result := get_array() or { return } + println(array_result) +} diff --git a/v_windows/v/vlib/v/checker/tests/orm_empty_struct.out b/v_windows/v/vlib/v/checker/tests/orm_empty_struct.out new file mode 100644 index 0000000..26e5131 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/orm_empty_struct.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/orm_empty_struct.vv:9:15: error: V orm: select: empty fields in `Person` + 7 | db := sqlite.connect(':memory:')? + 8 | _ := sql db { + 9 | select from Person + | ~~~~~~ + 10 | } + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/orm_empty_struct.vv b/v_windows/v/vlib/v/checker/tests/orm_empty_struct.vv new file mode 100644 index 0000000..ba7b4e7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/orm_empty_struct.vv @@ -0,0 +1,11 @@ +import sqlite + +struct Person { +} + +fn main() { + db := sqlite.connect(':memory:')? + _ := sql db { + select from Person + } +} diff --git a/v_windows/v/vlib/v/checker/tests/os_prefix.out b/v_windows/v/vlib/v/checker/tests/os_prefix.out new file mode 100644 index 0000000..4b87a6f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/os_prefix.out @@ -0,0 +1,18 @@ +vlib/v/checker/tests/os_prefix.vv:1:8: warning: module 'os' is imported but never used + 1 | import os + | ~~ + 2 | + 3 | fn main() { +vlib/v/checker/tests/os_prefix.vv:5:12: error: unknown function: execute + 3 | fn main() { + 4 | cmd := "ls" + 5 | result := execute(cmd) + | ~~~~~~~~~~~~ + 6 | println(result) + 7 | } +vlib/v/checker/tests/os_prefix.vv:6:2: error: `println` can not print void expressions + 4 | cmd := "ls" + 5 | result := execute(cmd) + 6 | println(result) + | ~~~~~~~~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/os_prefix.vv b/v_windows/v/vlib/v/checker/tests/os_prefix.vv new file mode 100644 index 0000000..b9342d3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/os_prefix.vv @@ -0,0 +1,7 @@ +import os + +fn main() { + cmd := "ls" + result := execute(cmd) + println(result) +} diff --git a/v_windows/v/vlib/v/checker/tests/overflow_int_err.out b/v_windows/v/vlib/v/checker/tests/overflow_int_err.out new file mode 100644 index 0000000..b26b097 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/overflow_int_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/overflow_int_err.vv:4:7: error: overflow in implicit type `int`, use explicit type casting instead + 2 | a := -2147483648 + 3 | b := 2147483647 + 4 | c := -2147483649 + | ~~~~~~~~~~~ + 5 | d := 2147483648 + 6 | println(a) +vlib/v/checker/tests/overflow_int_err.vv:5:7: error: overflow in implicit type `int`, use explicit type casting instead + 3 | b := 2147483647 + 4 | c := -2147483649 + 5 | d := 2147483648 + | ~~~~~~~~~~ + 6 | println(a) + 7 | println(b) diff --git a/v_windows/v/vlib/v/checker/tests/overflow_int_err.vv b/v_windows/v/vlib/v/checker/tests/overflow_int_err.vv new file mode 100644 index 0000000..0537ae3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/overflow_int_err.vv @@ -0,0 +1,10 @@ +fn main() { + a := -2147483648 + b := 2147483647 + c := -2147483649 + d := 2147483648 + println(a) + println(b) + println(c) + println(d) +} diff --git a/v_windows/v/vlib/v/checker/tests/overload_return_type.out b/v_windows/v/vlib/v/checker/tests/overload_return_type.out new file mode 100644 index 0000000..547519c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/overload_return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/overload_return_type.vv:14:11: error: cannot assign to `two`: expected `Point`, not `int` + 12 | mut one := Point {x:1, y:2} + 13 | mut two := Point {x:5, y:1} + 14 | two = one + two + | ~~~~~~~~~ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/overload_return_type.vv b/v_windows/v/vlib/v/checker/tests/overload_return_type.vv new file mode 100644 index 0000000..f9ae2f1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/overload_return_type.vv @@ -0,0 +1,15 @@ +struct Point { + mut: + x int + y int +} + +fn (a Point) +(b Point) int { + return a.x + b.x +} + +fn main() { + mut one := Point {x:1, y:2} + mut two := Point {x:5, y:1} + two = one + two +} diff --git a/v_windows/v/vlib/v/checker/tests/oversized_int_lit.out b/v_windows/v/vlib/v/checker/tests/oversized_int_lit.out new file mode 100644 index 0000000..30a9622 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oversized_int_lit.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/oversized_int_lit.vv:1:8: error: integer literal 18446744073709551616 overflows int + 1 | assert 18446744073709551616 > 0 + | ~~~~~~~~~~~~~~~~~~~~ + 2 | assert -9223372036854775809 < 0 +vlib/v/checker/tests/oversized_int_lit.vv:2:8: error: integer literal -9223372036854775809 overflows int + 1 | assert 18446744073709551616 > 0 + 2 | assert -9223372036854775809 < 0 + | ~~~~~~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/oversized_int_lit.vv b/v_windows/v/vlib/v/checker/tests/oversized_int_lit.vv new file mode 100644 index 0000000..d6e3a27 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/oversized_int_lit.vv @@ -0,0 +1,2 @@ +assert 18446744073709551616 > 0 +assert -9223372036854775809 < 0 diff --git a/v_windows/v/vlib/v/checker/tests/pass_mut_lit.out b/v_windows/v/vlib/v/checker/tests/pass_mut_lit.out new file mode 100644 index 0000000..09a0343 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/pass_mut_lit.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/pass_mut_lit.vv:10:12: error: cannot pass expression as `mut` + 8 | } + 9 | + 10 | modify(mut Box{}, 10) + | ~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/pass_mut_lit.vv b/v_windows/v/vlib/v/checker/tests/pass_mut_lit.vv new file mode 100644 index 0000000..7d358f9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/pass_mut_lit.vv @@ -0,0 +1,10 @@ +struct Box { +mut: + value int +} + +fn modify(mut box Box, value int) { + box.value = value +} + +modify(mut Box{}, 10) diff --git a/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.out b/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.out new file mode 100644 index 0000000..1acbdb5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.vv:3:31: error: expression cannot be passed as `voidptr` + 1 | import strconv + 2 | + 3 | strconv.v_printf('%02.02f\n', 1.1) + | ~~~ diff --git a/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.vv b/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.vv new file mode 100644 index 0000000..1b3bc39 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/passing_expr_to_fn_expecting_voidptr.vv @@ -0,0 +1,3 @@ +import strconv + +strconv.v_printf('%02.02f\n', 1.1) diff --git a/v_windows/v/vlib/v/checker/tests/pointer_ops.out b/v_windows/v/vlib/v/checker/tests/pointer_ops.out new file mode 100644 index 0000000..0221116 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/pointer_ops.out @@ -0,0 +1,49 @@ +vlib/v/checker/tests/pointer_ops.vv:5:7: error: `+` cannot be used with `voidptr` + 3 | unsafe { + 4 | mut p := voidptr(0) + 5 | _ = p + 1 + | ^ + 6 | p++ + 7 | p += 3 +vlib/v/checker/tests/pointer_ops.vv:6:4: error: invalid operation: ++ (non-numeric type `voidptr`) + 4 | mut p := voidptr(0) + 5 | _ = p + 1 + 6 | p++ + | ~~ + 7 | p += 3 + 8 | _ = p - 1 +vlib/v/checker/tests/pointer_ops.vv:7:3: error: operator `+=` not defined on left operand type `voidptr` + 5 | _ = p + 1 + 6 | p++ + 7 | p += 3 + | ^ + 8 | _ = p - 1 + 9 | p-- +vlib/v/checker/tests/pointer_ops.vv:8:7: error: `-` cannot be used with `voidptr` + 6 | p++ + 7 | p += 3 + 8 | _ = p - 1 + | ^ + 9 | p-- + 10 | p -= 3 +vlib/v/checker/tests/pointer_ops.vv:9:4: error: invalid operation: -- (non-numeric type `voidptr`) + 7 | p += 3 + 8 | _ = p - 1 + 9 | p-- + | ~~ + 10 | p -= 3 + 11 | _ = p[3] +vlib/v/checker/tests/pointer_ops.vv:10:3: error: operator `-=` not defined on left operand type `voidptr` + 8 | _ = p - 1 + 9 | p-- + 10 | p -= 3 + | ^ + 11 | _ = p[3] + 12 | } +vlib/v/checker/tests/pointer_ops.vv:11:8: error: type `voidptr` does not support indexing + 9 | p-- + 10 | p -= 3 + 11 | _ = p[3] + | ~~~ + 12 | } + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/pointer_ops.vv b/v_windows/v/vlib/v/checker/tests/pointer_ops.vv new file mode 100644 index 0000000..e93161b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/pointer_ops.vv @@ -0,0 +1,13 @@ +// void* arithmetic is not allowed in MSVC, so ban it +fn test_voidptr() { + unsafe { + mut p := voidptr(0) + _ = p + 1 + p++ + p += 3 + _ = p - 1 + p-- + p -= 3 + _ = p[3] + } +} diff --git a/v_windows/v/vlib/v/checker/tests/prefix_err.out b/v_windows/v/vlib/v/checker/tests/prefix_err.out new file mode 100644 index 0000000..528ddd0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/prefix_err.out @@ -0,0 +1,95 @@ +vlib/v/checker/tests/prefix_err.vv:5:6: error: cannot take the address of true + 3 | } + 4 | + 5 | b := &true + | ^ + 6 | _ := &get() + 7 | _ := &(get()) +vlib/v/checker/tests/prefix_err.vv:6:6: error: cannot take the address of get() + 4 | + 5 | b := &true + 6 | _ := &get() + | ^ + 7 | _ := &(get()) + 8 | _ := &(get() + 1) +vlib/v/checker/tests/prefix_err.vv:7:6: error: cannot take the address of get() + 5 | b := &true + 6 | _ := &get() + 7 | _ := &(get()) + | ^ + 8 | _ := &(get() + 1) + 9 | _ := &10 +vlib/v/checker/tests/prefix_err.vv:8:6: error: cannot take the address of get() + 1 + 6 | _ := &get() + 7 | _ := &(get()) + 8 | _ := &(get() + 1) + | ^ + 9 | _ := &10 + 10 | _ := &"Hi" +vlib/v/checker/tests/prefix_err.vv:9:6: error: cannot take the address of 10 + 7 | _ := &(get()) + 8 | _ := &(get() + 1) + 9 | _ := &10 + | ^ + 10 | _ := &"Hi" + 11 | _ := &"${b}" +vlib/v/checker/tests/prefix_err.vv:10:6: error: cannot take the address of 'Hi' + 8 | _ := &(get() + 1) + 9 | _ := &10 + 10 | _ := &"Hi" + | ^ + 11 | _ := &"${b}" + 12 | _ := &`c` +vlib/v/checker/tests/prefix_err.vv:11:6: error: cannot take the address of '$b' + 9 | _ := &10 + 10 | _ := &"Hi" + 11 | _ := &"${b}" + | ^ + 12 | _ := &`c` + 13 | _ := &1.2 +vlib/v/checker/tests/prefix_err.vv:12:6: error: cannot take the address of `c` + 10 | _ := &"Hi" + 11 | _ := &"${b}" + 12 | _ := &`c` + | ^ + 13 | _ := &1.2 + 14 | _ := &(1 + 2) +vlib/v/checker/tests/prefix_err.vv:13:6: error: cannot take the address of 1.2 + 11 | _ := &"${b}" + 12 | _ := &`c` + 13 | _ := &1.2 + | ^ + 14 | _ := &(1 + 2) + 15 | _ := 12.3 +vlib/v/checker/tests/prefix_err.vv:14:6: error: cannot take the address of 1 + 2 + 12 | _ := &`c` + 13 | _ := &1.2 + 14 | _ := &(1 + 2) + | ^ + 15 | _ := 12.3 + 16 | +vlib/v/checker/tests/prefix_err.vv:18:5: error: invalid indirect of `int` + 16 | + 17 | a := 1 + 18 | _ = *a + | ^ + 19 | a <- 4 + 20 | +vlib/v/checker/tests/prefix_err.vv:19:1: error: cannot push on non-channel `int` + 17 | a := 1 + 18 | _ = *a + 19 | a <- 4 + | ^ + 20 | + 21 | _ = ~true +vlib/v/checker/tests/prefix_err.vv:21:5: error: operator ~ only defined on int types + 19 | a <- 4 + 20 | + 21 | _ = ~true + | ^ + 22 | _ = !4 +vlib/v/checker/tests/prefix_err.vv:22:5: error: ! operator can only be used with bool types + 20 | + 21 | _ = ~true + 22 | _ = !4 + | ^ diff --git a/v_windows/v/vlib/v/checker/tests/prefix_err.vv b/v_windows/v/vlib/v/checker/tests/prefix_err.vv new file mode 100644 index 0000000..6472a0b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/prefix_err.vv @@ -0,0 +1,22 @@ +fn get() int { + return 1 +} + +b := &true +_ := &get() +_ := &(get()) +_ := &(get() + 1) +_ := &10 +_ := &"Hi" +_ := &"${b}" +_ := &`c` +_ := &1.2 +_ := &(1 + 2) +_ := 12.3 + +a := 1 +_ = *a +a <- 4 + +_ = ~true +_ = !4 diff --git a/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.out b/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.out new file mode 100644 index 0000000..575fc0f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/prefix_expr_decl_assign_err.vv:2:5: error: non-name on the left side of `:=` + 1 | fn main() { + 2 | &a := 12 + | ^ + 3 | (*d) := 14 + 4 | } +vlib/v/checker/tests/prefix_expr_decl_assign_err.vv:3:5: error: non-name `(*d)` on left side of `:=` + 1 | fn main() { + 2 | &a := 12 + 3 | (*d) := 14 + | ~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.vv b/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.vv new file mode 100644 index 0000000..3cfff48 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/prefix_expr_decl_assign_err.vv @@ -0,0 +1,4 @@ +fn main() { + &a := 12 + (*d) := 14 +} diff --git a/v_windows/v/vlib/v/checker/tests/print_char.out b/v_windows/v/vlib/v/checker/tests/print_char.out new file mode 100644 index 0000000..055ec11 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/print_char.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/print_char.vv:1:1: error: `println` cannot print type `char` directly, print its address or cast it to an integer instead + 1 | println(char(91)) + | ~~~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/print_char.vv b/v_windows/v/vlib/v/checker/tests/print_char.vv new file mode 100644 index 0000000..9ddba8a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/print_char.vv @@ -0,0 +1 @@ +println(char(91)) diff --git a/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.out b/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.out new file mode 100644 index 0000000..674f2a5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/println_can_not_print_void_expressions.vv:3:2: error: `print` can not print void expressions + 1 | fn blabla() {} + 2 | fn main() { + 3 | print(blabla()) + | ~~~~~~~~~~~~~~~ + 4 | println(blabla()) + 5 | eprint(blabla()) +vlib/v/checker/tests/println_can_not_print_void_expressions.vv:4:2: error: `println` can not print void expressions + 2 | fn main() { + 3 | print(blabla()) + 4 | println(blabla()) + | ~~~~~~~~~~~~~~~~~ + 5 | eprint(blabla()) + 6 | eprintln(blabla()) +vlib/v/checker/tests/println_can_not_print_void_expressions.vv:5:2: error: `eprint` can not print void expressions + 3 | print(blabla()) + 4 | println(blabla()) + 5 | eprint(blabla()) + | ~~~~~~~~~~~~~~~~ + 6 | eprintln(blabla()) + 7 | } +vlib/v/checker/tests/println_can_not_print_void_expressions.vv:6:2: error: `eprintln` can not print void expressions + 4 | println(blabla()) + 5 | eprint(blabla()) + 6 | eprintln(blabla()) + | ~~~~~~~~~~~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.vv b/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.vv new file mode 100644 index 0000000..e412697 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/println_can_not_print_void_expressions.vv @@ -0,0 +1,7 @@ +fn blabla() {} +fn main() { + print(blabla()) + println(blabla()) + eprint(blabla()) + eprintln(blabla()) +} diff --git a/v_windows/v/vlib/v/checker/tests/ptr_assign.out b/v_windows/v/vlib/v/checker/tests/ptr_assign.out new file mode 100644 index 0000000..5afa1d2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ptr_assign.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/ptr_assign.vv:3:5: error: cannot assign to `p`: expected `&int`, not `int literal` + 1 | mut v := 43 + 2 | mut p := &v + 3 | p = 4 + | ^ + 4 | _ = p diff --git a/v_windows/v/vlib/v/checker/tests/ptr_assign.vv b/v_windows/v/vlib/v/checker/tests/ptr_assign.vv new file mode 100644 index 0000000..562f196 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/ptr_assign.vv @@ -0,0 +1,4 @@ +mut v := 43 +mut p := &v +p = 4 +_ = p diff --git a/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.out b/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.out new file mode 100644 index 0000000..667a0d2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.out @@ -0,0 +1,4 @@ +vlib/v/checker/tests/receiver_unknown_type_single_letter.vv:1:7: error: unknown type `Abc` + 1 | fn (p Abc) foo() {} + | ~~~ + 2 | diff --git a/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.vv b/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.vv new file mode 100644 index 0000000..703cf29 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/receiver_unknown_type_single_letter.vv @@ -0,0 +1,2 @@ +fn (p Abc) foo() {} + diff --git a/v_windows/v/vlib/v/checker/tests/recursive_interface_err.out b/v_windows/v/vlib/v/checker/tests/recursive_interface_err.out new file mode 100644 index 0000000..f3ec2a7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/recursive_interface_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/recursive_interface_err.vv:2:6: error: recursive interface fields are not allowed because they cannot be initialised + 1 | interface Foo { + 2 | foo Foo + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/recursive_interface_err.vv b/v_windows/v/vlib/v/checker/tests/recursive_interface_err.vv new file mode 100644 index 0000000..74a3dd9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/recursive_interface_err.vv @@ -0,0 +1,3 @@ +interface Foo { + foo Foo +} diff --git a/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.out b/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.out new file mode 100644 index 0000000..a9465b6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/redundant_parentheses_warning.vv:3:7: error: redundant parentheses are used + 1 | fn main() { + 2 | a := 2 + 3 | b := ((a + 2)) + | ~~~~~~~~~ + 4 | println(b) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.vv b/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.vv new file mode 100644 index 0000000..df64e04 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/redundant_parentheses_warning.vv @@ -0,0 +1,5 @@ +fn main() { + a := 2 + b := ((a + 2)) + println(b) +} diff --git a/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.out b/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.out new file mode 100644 index 0000000..846e5b7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/reference_field_must_be_initialized.vv:8:7: error: reference field `Node.next` must be initialized + 6 | + 7 | fn main(){ + 8 | n := Node{ data: 123 } + | ~~~~~~~~~~~~~~~~~ + 9 | eprintln('n.data: $n.data') + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.vv b/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.vv new file mode 100644 index 0000000..a308173 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/reference_field_must_be_initialized.vv @@ -0,0 +1,10 @@ +module main +struct Node { + data int + next &Node +} + +fn main(){ + n := Node{ data: 123 } + eprintln('n.data: $n.data') +} diff --git a/v_windows/v/vlib/v/checker/tests/reference_return.out b/v_windows/v/vlib/v/checker/tests/reference_return.out new file mode 100644 index 0000000..4c9b3c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/reference_return.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/reference_return.vv:6:9: error: fn `f` expects you to return a reference type `&Aa`, but you are returning `Aa` instead + 4 | + 5 | fn f() &Aa { + 6 | return Aa{ + | ~~~ + 7 | a: 1 + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/reference_return.vv b/v_windows/v/vlib/v/checker/tests/reference_return.vv new file mode 100644 index 0000000..42c1e5a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/reference_return.vv @@ -0,0 +1,14 @@ +struct Aa { + a int +} + +fn f() &Aa { + return Aa{ + a: 1 + } +} + +fn main() { + x := f() + println(x.a) +} diff --git a/v_windows/v/vlib/v/checker/tests/return_count_mismatch.out b/v_windows/v/vlib/v/checker/tests/return_count_mismatch.out new file mode 100644 index 0000000..8182870 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_count_mismatch.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/return_count_mismatch.vv:2:9: error: unexpected argument, current function does not return anything + 1 | fn v() { + 2 | return 3 + | ^ + 3 | } + 4 | +vlib/v/checker/tests/return_count_mismatch.vv:7:2: error: expected `int` argument + 5 | fn f() int + 6 | { + 7 | return + | ~~~~~~ + 8 | } + 9 | +vlib/v/checker/tests/return_count_mismatch.vv:12:2: error: expected `(int, string)` arguments + 10 | fn g() (int, string) + 11 | { + 12 | return + | ~~~~~~ + 13 | } + 14 | +vlib/v/checker/tests/return_count_mismatch.vv:17:2: error: expected 1 argument, but got 2 + 15 | fn ff() int + 16 | { + 17 | return 2, '' + | ~~~~~~~~~~~~ + 18 | } + 19 | +vlib/v/checker/tests/return_count_mismatch.vv:22:2: error: expected 2 arguments, but got 1 + 20 | fn gg() (int, string) + 21 | { + 22 | return 3 + | ~~~~~~~~ + 23 | } + 24 | diff --git a/v_windows/v/vlib/v/checker/tests/return_count_mismatch.vv b/v_windows/v/vlib/v/checker/tests/return_count_mismatch.vv new file mode 100644 index 0000000..15f4116 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_count_mismatch.vv @@ -0,0 +1,24 @@ +fn v() { + return 3 +} + +fn f() int +{ + return +} + +fn g() (int, string) +{ + return +} + +fn ff() int +{ + return 2, '' +} + +fn gg() (int, string) +{ + return 3 +} + diff --git a/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.out b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.out new file mode 100644 index 0000000..ea41c8b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_duplicate_with_none_err_a.vv:3:2: error: unreachable code + 1 | fn f() ?bool { + 2 | return true + 3 | return none + | ~~~~~~~~~~~ + 4 | } + 5 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.vv b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.vv new file mode 100644 index 0000000..c356eb8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_a.vv @@ -0,0 +1,9 @@ +fn f() ?bool { + return true + return none +} +fn main() { + f() or { + println('fail') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.out b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.out new file mode 100644 index 0000000..7bc37bb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_duplicate_with_none_err_b.vv:3:2: error: unreachable code + 1 | fn f() ?bool { + 2 | return none + 3 | return true + | ~~~~~~~~~~~ + 4 | } + 5 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.vv b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.vv new file mode 100644 index 0000000..aaf39a1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_duplicate_with_none_err_b.vv @@ -0,0 +1,9 @@ +fn f() ?bool { + return none + return true +} +fn main() { + f() or { + println('fail') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_fixed_array.out b/v_windows/v/vlib/v/checker/tests/return_fixed_array.out new file mode 100644 index 0000000..3ac7df0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_fixed_array.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/return_fixed_array.vv:1:25: error: fixed array cannot be returned by function + 1 | fn return_fixed_array() [3]int { + | ~~~~~~ + 2 | return [1, 2, 3]! + 3 | } +vlib/v/checker/tests/return_fixed_array.vv:5:41: error: fixed array cannot be used in multi-return + 3 | } + 4 | + 5 | fn return_fixed_array_in_multi_return() ([3]int, [3]int) { + | ~~~~~~~~~~~~~~~~ + 6 | return [1, 2, 3]!, [4, 5, 6]! + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/return_fixed_array.vv b/v_windows/v/vlib/v/checker/tests/return_fixed_array.vv new file mode 100644 index 0000000..dc24da1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_fixed_array.vv @@ -0,0 +1,7 @@ +fn return_fixed_array() [3]int { + return [1, 2, 3]! +} + +fn return_fixed_array_in_multi_return() ([3]int, [3]int) { + return [1, 2, 3]!, [4, 5, 6]! +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.out b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.out new file mode 100644 index 0000000..f8315ac --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_comp_if.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | $if windows { + 5 | \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.vv b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.vv new file mode 100644 index 0000000..e6e6d79 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if.vv @@ -0,0 +1,9 @@ +fn main() {} + +fn foo() string { + $if windows { + + } $else { + + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.out b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.out new file mode 100644 index 0000000..4c24e85 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_comp_if_nested.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | $if windows { + 5 | if true { \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.vv b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.vv new file mode 100644 index 0000000..08fada9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_comp_if_nested.vv @@ -0,0 +1,17 @@ +fn main() {} + +fn foo() string { + $if windows { + if true { + + } else { + return '' + } + } $else { + if true { + + } else { + return '' + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.out b/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.out new file mode 100644 index 0000000..41eb21b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/return_missing_if_else_simple.vv:1:1: error: missing return at end of function `if_no_returns` + 1 | fn if_no_returns() int { + | ~~~~~~~~~~~~~~~~~~~~~~ + 2 | if true { + 3 | println('no return in then') +vlib/v/checker/tests/return_missing_if_else_simple.vv:10:1: error: missing return at end of function `if_no_return_in_else` + 8 | } + 9 | + 10 | fn if_no_return_in_else() int { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | if true { + 12 | return 123 diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.vv b/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.vv new file mode 100644 index 0000000..802a7d9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_if_else_simple.vv @@ -0,0 +1,35 @@ +fn if_no_returns() int { + if true { + println('no return in then') + } else { + println('no return in else') + } + println('hello') +} + +fn if_no_return_in_else() int { + if true { + return 123 + } else { + println('no return in else') + } + println('hello') +} + +fn if_returns_in_both_branches() int { + // The if here always returns, so the function + // returns too: + if true { + return 123 + } else { + return 456 + } + // TODO: should produce a warning/error about a statement after a return. + println('hello') +} + +fn main() { + println(if_no_returns()) + println(if_no_return_in_else()) + println(if_returns_in_both_branches()) +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_if_match.out b/v_windows/v/vlib/v/checker/tests/return_missing_if_match.out new file mode 100644 index 0000000..d4877d8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_if_match.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_if_match.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | if true { + 5 | match 1 { \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_if_match.vv b/v_windows/v/vlib/v/checker/tests/return_missing_if_match.vv new file mode 100644 index 0000000..88ab70c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_if_match.vv @@ -0,0 +1,13 @@ +fn main() {} + +fn foo() string { + if true { + match 1 { + 1 { return '' } + 2 { } + else { return '' } + } + } else { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_match_if.out b/v_windows/v/vlib/v/checker/tests/return_missing_match_if.out new file mode 100644 index 0000000..196b321 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_match_if.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_match_if.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | match 1 { + 5 | 1 { return '' } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_match_if.vv b/v_windows/v/vlib/v/checker/tests/return_missing_match_if.vv new file mode 100644 index 0000000..4ff9345 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_match_if.vv @@ -0,0 +1,15 @@ +fn main() {} + +fn foo() string { + match 1 { + 1 { return '' } + 2 { + if true { + + } else { + return '' + } + } + else { return '' } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.out b/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.out new file mode 100644 index 0000000..6e8306b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.out @@ -0,0 +1,12 @@ +vlib/v/checker/tests/return_missing_match_simple.vv:4:3: warning: `match` must have at least one non `else` branch + 2 | a := 1 + 3 | match a { + 4 | else { println('abc') } + | ~~~~ + 5 | } + 6 | println('hello') +vlib/v/checker/tests/return_missing_match_simple.vv:1:1: error: missing return at end of function `h` + 1 | fn h() int { + | ~~~~~~~~~~ + 2 | a := 1 + 3 | match a { diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.vv b/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.vv new file mode 100644 index 0000000..463c2b2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_match_simple.vv @@ -0,0 +1,12 @@ +fn h() int { + a := 1 + match a { + else { println('abc') } + } + println('hello') +} + +fn main() { + d := h() + println('h returned: $d') +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_nested.out b/v_windows/v/vlib/v/checker/tests/return_missing_nested.out new file mode 100644 index 0000000..fce3245 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_nested.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_nested.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | if true { + 5 | return '' \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_nested.vv b/v_windows/v/vlib/v/checker/tests/return_missing_nested.vv new file mode 100644 index 0000000..41b4c7f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_nested.vv @@ -0,0 +1,11 @@ +fn main() {} + +fn foo() string { + if true { + return '' + } else { + if true { + return '' + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_simple.out b/v_windows/v/vlib/v/checker/tests/return_missing_simple.out new file mode 100644 index 0000000..8016345 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_simple.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_missing_simple.vv:3:1: error: missing return at end of function `foo` + 1 | fn main() {} + 2 | + 3 | fn foo() string { + | ~~~~~~~~~~~~~~~ + 4 | if true { + 5 | return '' \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/return_missing_simple.vv b/v_windows/v/vlib/v/checker/tests/return_missing_simple.vv new file mode 100644 index 0000000..ed200fe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_missing_simple.vv @@ -0,0 +1,7 @@ +fn main() {} + +fn foo() string { + if true { + return '' + } else {} +} diff --git a/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.out b/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.out new file mode 100644 index 0000000..01b9990 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/return_ref_as_no_ref_bug.vv:11:9: error: fn `return_not_reference` expects you to return a non reference type `BugStruct`, but you are returning `&BugStruct` instead + 9 | + 10 | fn return_not_reference() BugStruct{ + 11 | return &BugStruct { + | ^ + 12 | id: 1 + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.vv b/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.vv new file mode 100644 index 0000000..8d04dc9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_ref_as_no_ref_bug.vv @@ -0,0 +1,14 @@ +struct BugStruct { + id int +} + +fn main() { + x := return_not_reference() + println(x.id) +} + +fn return_not_reference() BugStruct{ + return &BugStruct { + id: 1 + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_type.out b/v_windows/v/vlib/v/checker/tests/return_type.out new file mode 100644 index 0000000..971347c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/return_type.vv:2:9: error: cannot use `int literal` as type `bool` in return argument + 1 | fn test() bool { + 2 | return 100 + | ~~~ + 3 | } + 4 | diff --git a/v_windows/v/vlib/v/checker/tests/return_type.vv b/v_windows/v/vlib/v/checker/tests/return_type.vv new file mode 100644 index 0000000..333ec9f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_type.vv @@ -0,0 +1,5 @@ +fn test() bool { + return 100 +} + +fn main() {} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_comp_if.out b/v_windows/v/vlib/v/checker/tests/return_working_comp_if.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_comp_if.vv b/v_windows/v/vlib/v/checker/tests/return_working_comp_if.vv new file mode 100644 index 0000000..d6fffe5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_comp_if.vv @@ -0,0 +1,9 @@ +fn main() {} + +fn foo() string { + $if windows { + return '' + } $else { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.out b/v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.vv b/v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.vv new file mode 100644 index 0000000..c76e981 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_comp_if_nested.vv @@ -0,0 +1,17 @@ +fn main() {} + +fn foo() string { + $if windows { + if true { + return '' + } else { + return '' + } + } $else { + if true { + return '' + } else { + return '' + } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_if_match.out b/v_windows/v/vlib/v/checker/tests/return_working_if_match.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_if_match.vv b/v_windows/v/vlib/v/checker/tests/return_working_if_match.vv new file mode 100644 index 0000000..5e8d1bf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_if_match.vv @@ -0,0 +1,13 @@ +fn main() {} + +fn foo() string { + if true { + match 1 { + 1 { return '' } + 2 { return '' } + else { return '' } + } + } else { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_match_if.out b/v_windows/v/vlib/v/checker/tests/return_working_match_if.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_match_if.vv b/v_windows/v/vlib/v/checker/tests/return_working_match_if.vv new file mode 100644 index 0000000..2fbbcdf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_match_if.vv @@ -0,0 +1,15 @@ +fn main() {} + +fn foo() string { + match 1 { + 1 { return '' } + 2 { + if true { + return '' + } else { + return '' + } + } + else { return '' } + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_nested.out b/v_windows/v/vlib/v/checker/tests/return_working_nested.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_nested.vv b/v_windows/v/vlib/v/checker/tests/return_working_nested.vv new file mode 100644 index 0000000..1d72a02 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_nested.vv @@ -0,0 +1,12 @@ +fn main() {} + +fn foo() string { + if true { + return '' + } else { + if true { + return '' + } + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_simple.out b/v_windows/v/vlib/v/checker/tests/return_working_simple.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_simple.vv b/v_windows/v/vlib/v/checker/tests/return_working_simple.vv new file mode 100644 index 0000000..271e3f6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_simple.vv @@ -0,0 +1,9 @@ +fn main() {} + +fn foo() string { + if true { + return '' + } else { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_two_if.out b/v_windows/v/vlib/v/checker/tests/return_working_two_if.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_two_if.vv b/v_windows/v/vlib/v/checker/tests/return_working_two_if.vv new file mode 100644 index 0000000..a95825f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_two_if.vv @@ -0,0 +1,17 @@ +fn main() {} + +fn foo() string { + if true { + if true { + return '' + } + + if true { + return '' + } else { + return '' + } + } else { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/return_working_unsafe.out b/v_windows/v/vlib/v/checker/tests/return_working_unsafe.out new file mode 100644 index 0000000..e69de29 diff --git a/v_windows/v/vlib/v/checker/tests/return_working_unsafe.vv b/v_windows/v/vlib/v/checker/tests/return_working_unsafe.vv new file mode 100644 index 0000000..777b297 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/return_working_unsafe.vv @@ -0,0 +1,7 @@ +fn main() {} + +fn foo() string { + unsafe { + return '' + } +} diff --git a/v_windows/v/vlib/v/checker/tests/returns/return_missing_simple.vv b/v_windows/v/vlib/v/checker/tests/returns/return_missing_simple.vv new file mode 100644 index 0000000..ad62212 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/returns/return_missing_simple.vv @@ -0,0 +1,7 @@ +fn foo() string { + if true { + return '' + } else { + + } +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.out b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.out new file mode 100644 index 0000000..60866d5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/rshift_op_wrong_left_type_err.vv:2:10: error: invalid operation: shift on type `float literal` + 1 | fn main() { + 2 | println(0.5 >> 1) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.vv b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.vv new file mode 100644 index 0000000..5ea5841 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_left_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0.5 >> 1) +} diff --git a/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.out b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.out new file mode 100644 index 0000000..7a0c5d9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/rshift_op_wrong_right_type_err.vv:2:15: error: cannot shift non-integer type `float literal` into type `int literal` + 1 | fn main() { + 2 | println(1 >> 0.5) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.vv b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.vv new file mode 100644 index 0000000..cd55344 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/rshift_op_wrong_right_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(1 >> 0.5) +} diff --git a/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.run.out b/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.run.out new file mode 100644 index 0000000..f114c07 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.run.out @@ -0,0 +1 @@ +log_and_die: error: oh no diff --git a/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.vv b/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.vv new file mode 100644 index 0000000..10cf22c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/run/noreturn_fn_can_be_used_instead_of_panic.vv @@ -0,0 +1,14 @@ +fn abc() ?int { + return error('oh no') +} + +[noreturn] +fn log_and_die(e IError) { + eprintln('${@FN}: error: $e') + exit(77) +} + +fn main() { + x := abc() or { log_and_die(err) } + println(x) +} diff --git a/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.run.out b/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.run.out new file mode 100644 index 0000000..9fa8e3c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.run.out @@ -0,0 +1,15 @@ +vlib/v/checker/tests/run/unused_variable_warning.vv:3:2: warning: unused variable: `a` + 1 | // NB: this test should compile and run, but it also should produce a compiler warning. + 2 | fn main() { + 3 | a := 1 + | ^ + 4 | b := 2 + 5 | println('hello') +vlib/v/checker/tests/run/unused_variable_warning.vv:4:2: warning: unused variable: `b` + 2 | fn main() { + 3 | a := 1 + 4 | b := 2 + | ^ + 5 | println('hello') + 6 | } +hello diff --git a/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.vv b/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.vv new file mode 100644 index 0000000..1611e54 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/run/unused_variable_warning.vv @@ -0,0 +1,6 @@ +// NB: this test should compile and run, but it also should produce a compiler warning. +fn main() { + a := 1 + b := 2 + println('hello') +} diff --git a/v_windows/v/vlib/v/checker/tests/selective_const_import.out b/v_windows/v/vlib/v/checker/tests/selective_const_import.out new file mode 100644 index 0000000..690fc3c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selective_const_import.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/selective_const_import.vv:1:15: error: cannot selectively import constant `second` from `time`, import `time` and use `time.second` instead + 1 | import time { second } + | ~~~~~~ diff --git a/v_windows/v/vlib/v/checker/tests/selective_const_import.vv b/v_windows/v/vlib/v/checker/tests/selective_const_import.vv new file mode 100644 index 0000000..93e7b2f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selective_const_import.vv @@ -0,0 +1 @@ +import time { second } diff --git a/v_windows/v/vlib/v/checker/tests/selector_expr_assign.out b/v_windows/v/vlib/v/checker/tests/selector_expr_assign.out new file mode 100644 index 0000000..9e98006 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selector_expr_assign.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/selector_expr_assign.vv:7:6: error: struct fields can only be declared during the initialization + 5 | fn main() { + 6 | abc := Abc{} + 7 | abc.a := 2 + | ^ + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/selector_expr_assign.vv b/v_windows/v/vlib/v/checker/tests/selector_expr_assign.vv new file mode 100644 index 0000000..024c869 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selector_expr_assign.vv @@ -0,0 +1,8 @@ +struct Abc { + a int +} + +fn main() { + abc := Abc{} + abc.a := 2 +} diff --git a/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.out b/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.out new file mode 100644 index 0000000..51065b7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/selector_expr_optional_err.vv:4:46: error: cannot access fields of an optional, handle the error with `or {...}` or propagate it with `?` + 2 | + 3 | fn main() { + 4 | println(http.get('https://httpbin.org/').status_code) + | ~~~~~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.vv b/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.vv new file mode 100644 index 0000000..d277787 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/selector_expr_optional_err.vv @@ -0,0 +1,5 @@ +import net.http + +fn main() { + println(http.get('https://httpbin.org/').status_code) +} diff --git a/v_windows/v/vlib/v/checker/tests/shared_bad_args.out b/v_windows/v/vlib/v/checker/tests/shared_bad_args.out new file mode 100644 index 0000000..e41873c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_bad_args.out @@ -0,0 +1,83 @@ +vlib/v/checker/tests/shared_bad_args.vv:43:8: error: `r` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut receiver + 41 | shared r := Qr{ a: 7 } + 42 | lock s { + 43 | u := r.s_val(s) + | ^ + 44 | println(u) + 45 | } +vlib/v/checker/tests/shared_bad_args.vv:47:16: error: `s` is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument + 45 | } + 46 | lock r { + 47 | v := r.s_val(s) + | ^ + 48 | println(v) + 49 | } +vlib/v/checker/tests/shared_bad_args.vv:50:13: error: `m` is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument + 48 | println(v) + 49 | } + 50 | w := m_val(m) + | ^ + 51 | x := a_val(a) + 52 | println('$w $x') +vlib/v/checker/tests/shared_bad_args.vv:51:13: error: `a` is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument + 49 | } + 50 | w := m_val(m) + 51 | x := a_val(a) + | ^ + 52 | println('$w $x') + 53 | } +vlib/v/checker/tests/shared_bad_args.vv:61:3: error: r must be added to the `lock` list above + 59 | shared r := Qr{ a: 7 } + 60 | lock s { + 61 | r.s_mut(mut s) + | ^ + 62 | } + 63 | lock r { +vlib/v/checker/tests/shared_bad_args.vv:64:15: error: s must be added to the `lock` list above + 62 | } + 63 | lock r { + 64 | r.s_mut(mut s) + | ^ + 65 | } + 66 | m_mut(mut m) +vlib/v/checker/tests/shared_bad_args.vv:66:12: error: m is `shared` and must be `lock`ed to be passed as `mut` + 64 | r.s_mut(mut s) + 65 | } + 66 | m_mut(mut m) + | ^ + 67 | a_mut(mut a) + 68 | } +vlib/v/checker/tests/shared_bad_args.vv:67:12: error: a is `shared` and must be `lock`ed to be passed as `mut` + 65 | } + 66 | m_mut(mut m) + 67 | a_mut(mut a) + | ^ + 68 | } + 69 | +vlib/v/checker/tests/shared_bad_args.vv:76:10: error: `y` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 74 | fn main() { + 75 | shared y := St{ a: 5 } + 76 | println(y) + | ^ + 77 | println('$y') + 78 | a := Ab{ s: St{ a: 3 } } +vlib/v/checker/tests/shared_bad_args.vv:77:12: error: `y` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut interpolation object + 75 | shared y := St{ a: 5 } + 76 | println(y) + 77 | println('$y') + | ^ + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) +vlib/v/checker/tests/shared_bad_args.vv:79:12: error: `a.s` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 77 | println('$y') + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) + | ^ + 80 | println('$a.s') + 81 | } +vlib/v/checker/tests/shared_bad_args.vv:80:14: error: `a.s` is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut interpolation object + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) + 80 | println('$a.s') + | ^ + 81 | } diff --git a/v_windows/v/vlib/v/checker/tests/shared_bad_args.vv b/v_windows/v/vlib/v/checker/tests/shared_bad_args.vv new file mode 100644 index 0000000..08e5d8a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_bad_args.vv @@ -0,0 +1,81 @@ +struct St { +mut: + a int +} + +struct Qr { +mut: + a int +} + +fn (mut r Qr) s_mut(mut s St) { + r.a = 5 + s.a = 7 +} + +fn (r Qr) s_val(s St) int { + return r.a * s.a +} + +fn m_mut(mut a map[string]f64) { + a['yxcv'] = -2.25 +} + +fn m_val(a map[string]f64) f64 { + x := a['yxcv'] + return x +} + +fn a_mut(mut a []int) { + a[2] = 42 +} + +fn a_val(a []int) int { + return a[1] +} + +fn test_shared_as_value() { + shared s := St{ a: 5 } + shared a := [3, 4, 6, 13, -23] + shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625} + shared r := Qr{ a: 7 } + lock s { + u := r.s_val(s) + println(u) + } + lock r { + v := r.s_val(s) + println(v) + } + w := m_val(m) + x := a_val(a) + println('$w $x') +} + +fn test_shared_as_mut() { + shared s := St{ a: 5 } + shared a := [3, 4, 6, 13, -23] + shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625} + shared r := Qr{ a: 7 } + lock s { + r.s_mut(mut s) + } + lock r { + r.s_mut(mut s) + } + m_mut(mut m) + a_mut(mut a) +} + +struct Ab { + s shared St +} + +fn main() { + shared y := St{ a: 5 } + println(y) + println('$y') + a := Ab{ s: St{ a: 3 } } + println(a.s) + println('$a.s') +} diff --git a/v_windows/v/vlib/v/checker/tests/shared_element_lock.out b/v_windows/v/vlib/v/checker/tests/shared_element_lock.out new file mode 100644 index 0000000..f392a1f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_element_lock.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/shared_element_lock.vv:36:5: error: `pr.pe` is `shared` and needs explicit lock for `v.ast.SelectorExpr` + 34 | } + 35 | } + 36 | pr.pe.color = 3 + | ~~ + 37 | shared y := pr.pe + 38 | rlock y { +vlib/v/checker/tests/shared_element_lock.vv:42:2: error: `g` is `shared` and needs explicit lock for `v.ast.SelectorExpr` + 40 | } + 41 | shared g := Pro{} + 42 | g.pers.age = 42 + | ^ + 43 | mut h := []shared Pro{len: 3} + 44 | h[2].pers.age = 42 +vlib/v/checker/tests/shared_element_lock.vv:44:2: error: you have to create a handle and `lock` it to modify `shared` array element + 42 | g.pers.age = 42 + 43 | mut h := []shared Pro{len: 3} + 44 | h[2].pers.age = 42 + | ~~~~ + 45 | println(h[2].pers.age) + 46 | } +vlib/v/checker/tests/shared_element_lock.vv:45:10: error: you have to create a handle and `rlock` it to use a `shared` element as non-mut argument to print + 43 | mut h := []shared Pro{len: 3} + 44 | h[2].pers.age = 42 + 45 | println(h[2].pers.age) + | ~~~~ + 46 | } diff --git a/v_windows/v/vlib/v/checker/tests/shared_element_lock.vv b/v_windows/v/vlib/v/checker/tests/shared_element_lock.vv new file mode 100644 index 0000000..de61ad9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_element_lock.vv @@ -0,0 +1,46 @@ +struct Person { +mut: + name string + age int +} + +struct Pet { +mut: + name string + color int +} + +struct Programmer { +mut: + pers Person + pe shared Pet +} + +struct Pro { +mut: + pers Person + pe Pet +} + +fn main() { + mut pr := Programmer{ + pers: Person{ + name: 'Qwe' + age: 44 + } + pe: Pet{ + name: 'Ghj' + color: 7 + } + } + pr.pe.color = 3 + shared y := pr.pe + rlock y { + println(y.color) + } + shared g := Pro{} + g.pers.age = 42 + mut h := []shared Pro{len: 3} + h[2].pers.age = 42 + println(h[2].pers.age) +} diff --git a/v_windows/v/vlib/v/checker/tests/shared_lock.out b/v_windows/v/vlib/v/checker/tests/shared_lock.out new file mode 100644 index 0000000..62d7130 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_lock.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/shared_lock.vv:19:5: error: method with `shared` receiver cannot be called inside `lock`/`rlock` block + 17 | } + 18 | lock x { + 19 | x.r(x) + | ~~~~ + 20 | x.m(x) + 21 | f(0, x) +vlib/v/checker/tests/shared_lock.vv:20:7: error: method with `shared` arguments cannot be called inside `lock`/`rlock` block + 18 | lock x { + 19 | x.r(x) + 20 | x.m(x) + | ^ + 21 | f(0, x) + 22 | } +vlib/v/checker/tests/shared_lock.vv:21:8: error: function with `shared` arguments cannot be called inside `lock`/`rlock` block + 19 | x.r(x) + 20 | x.m(x) + 21 | f(0, x) + | ^ + 22 | } + 23 | } diff --git a/v_windows/v/vlib/v/checker/tests/shared_lock.vv b/v_windows/v/vlib/v/checker/tests/shared_lock.vv new file mode 100644 index 0000000..9af8111 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_lock.vv @@ -0,0 +1,23 @@ +struct St { +mut: + a int +} +fn (shared s St) r(x St) { +} +fn (s St) m(shared x St) { +} + + +fn f(w int, shared x St) { +} + +fn g() { + shared x := St{ + a: 5 + } + lock x { + x.r(x) + x.m(x) + f(0, x) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.out b/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.out new file mode 100644 index 0000000..32e16d9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/shared_type_mismatch.vv:13:3: error: wrong return type `St` in the `or {}` block, expected `shared St` + 11 | fn test_shared_opt_bad() { + 12 | shared yy := f() or { + 13 | St{ x: 37.5 } + | ~~~~~~~~~~~~~ + 14 | } + 15 | rlock yy { diff --git a/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.vv b/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.vv new file mode 100644 index 0000000..9d85636 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shared_type_mismatch.vv @@ -0,0 +1,18 @@ +struct St { +mut: + x f64 +} + +fn f() ?shared St { + shared x := St{ x: 12.75 } + return x +} + +fn test_shared_opt_bad() { + shared yy := f() or { + St{ x: 37.5 } + } + rlock yy { + println(yy.x) + } +} diff --git a/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.out b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.out new file mode 100644 index 0000000..1bdeb5e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/shift_op_wrong_left_type_err.vv:2:10: error: invalid operation: shift on type `float literal` + 1 | fn main() { + 2 | println(0.5 << 1) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.vv b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.vv new file mode 100644 index 0000000..87409eb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_left_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(0.5 << 1) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.out b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.out new file mode 100644 index 0000000..87d0a44 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/shift_op_wrong_right_type_err.vv:2:15: error: cannot shift non-integer type `float literal` into type `int literal` + 1 | fn main() { + 2 | println(1 << 0.5) + | ~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.vv b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.vv new file mode 100644 index 0000000..0eb1328 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/shift_op_wrong_right_type_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(1 << 0.5) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.out b/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.out new file mode 100644 index 0000000..fa6044b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/short_struct_wrong_number.vv:7:6: error: too few fields in `Test` literal (expecting 2, got 1) + 5 | + 6 | fn main() { + 7 | _ = Test{true} + | ~~~~~~~~~~ + 8 | _ = Test{true, false, true} + 9 | } +vlib/v/checker/tests/short_struct_wrong_number.vv:8:6: error: too many fields in `Test` literal (expecting 2, got 3) + 6 | fn main() { + 7 | _ = Test{true} + 8 | _ = Test{true, false, true} + | ~~~~~~~~~~~~~~~~~~~~~~~ + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.vv b/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.vv new file mode 100644 index 0000000..393ca5e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/short_struct_wrong_number.vv @@ -0,0 +1,9 @@ +struct Test { + foo bool + bar bool +} + +fn main() { + _ = Test{true} + _ = Test{true, false, true} +} diff --git a/v_windows/v/vlib/v/checker/tests/slice_reassignment.out b/v_windows/v/vlib/v/checker/tests/slice_reassignment.out new file mode 100644 index 0000000..7ab478f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/slice_reassignment.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/slice_reassignment.vv:3:5: error: cannot reassign using range expression on the left side of an assignment + 1 | fn main() { + 2 | mut arr := [1, 2, 3, 4, 5] + 3 | arr[..2] = [0, 0] + | ~~~~~ + 4 | println(arr) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/slice_reassignment.vv b/v_windows/v/vlib/v/checker/tests/slice_reassignment.vv new file mode 100644 index 0000000..602bd64 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/slice_reassignment.vv @@ -0,0 +1,5 @@ +fn main() { + mut arr := [1, 2, 3, 4, 5] + arr[..2] = [0, 0] + println(arr) +} diff --git a/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.out b/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.out new file mode 100644 index 0000000..d588267 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv:2:2: error: `a` is immutable, declare it with `mut` to make it mutable + 1 | fn abc (a []int) { + 2 | a.sort() + | ^ + 3 | } + 4 | +vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv:7:2: error: `a` is immutable, declare it with `mut` to make it mutable + 5 | fn main() { + 6 | a := [2,30,10,20,1] + 7 | a.sort(a>b) + | ^ + 8 | eprintln(' a: $a') + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv b/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv new file mode 100644 index 0000000..b2dafa5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sort_method_called_on_immutable_receiver.vv @@ -0,0 +1,9 @@ +fn abc (a []int) { + a.sort() +} + +fn main() { + a := [2,30,10,20,1] + a.sort(a>b) + eprintln(' a: $a') +} diff --git a/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.out b/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.out new file mode 100644 index 0000000..18c297a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/static_vars_in_translated_mode.vv:2:13: error: static variables are supported only in -translated mode or in [unsafe] fn + 1 | fn counter() int { + 2 | mut static icounter := 0 + | ~~~~~~~~ + 3 | icounter++ + 4 | return icounter diff --git a/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.vv b/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.vv new file mode 100644 index 0000000..0f153c8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/static_vars_in_translated_mode.vv @@ -0,0 +1,12 @@ +fn counter() int { + mut static icounter := 0 + icounter++ + return icounter +} + +fn main() { + println(unsafe { counter() }) + println(unsafe { counter() }) + println(unsafe { counter() }) + println(unsafe { counter() }) +} diff --git a/v_windows/v/vlib/v/checker/tests/store_string_err.out b/v_windows/v/vlib/v/checker/tests/store_string_err.out new file mode 100644 index 0000000..b0a44bf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/store_string_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/store_string_err.vv:5:26: error: wrong return type `IError` in the `or {}` block, expected `int` + 3 | } + 4 | + 5 | err := return_err() or { err } + | ~~~ + 6 | eprintln(err) diff --git a/v_windows/v/vlib/v/checker/tests/store_string_err.vv b/v_windows/v/vlib/v/checker/tests/store_string_err.vv new file mode 100644 index 0000000..7f8b3ff --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/store_string_err.vv @@ -0,0 +1,6 @@ +fn return_err() ?int { + return error('') +} + +err := return_err() or { err } +eprintln(err) diff --git a/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.out b/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.out new file mode 100644 index 0000000..9b8f1df --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/str_method_0_arguments.vv:5:1: error: .str() methods should have 0 arguments + 3 | } + 4 | + 5 | fn (z Zzz) str(x int) string { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | return 'z: $z.x' + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.vv b/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.vv new file mode 100644 index 0000000..28ef0ef --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/str_method_0_arguments.vv @@ -0,0 +1,11 @@ +struct Zzz { + x int +} + +fn (z Zzz) str(x int) string { + return 'z: $z.x' +} + +fn main() { + println(Zzz{123}) +} diff --git a/v_windows/v/vlib/v/checker/tests/str_method_return_string.out b/v_windows/v/vlib/v/checker/tests/str_method_return_string.out new file mode 100644 index 0000000..43bb36a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/str_method_return_string.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/str_method_return_string.vv:5:1: error: .str() methods should return `string` + 3 | } + 4 | + 5 | fn (z Zzz) str(x int) int { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | return z.x + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/str_method_return_string.vv b/v_windows/v/vlib/v/checker/tests/str_method_return_string.vv new file mode 100644 index 0000000..a3f6952 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/str_method_return_string.vv @@ -0,0 +1,11 @@ +struct Zzz { + x int +} + +fn (z Zzz) str(x int) int { + return z.x +} + +fn main() { + println(Zzz{123}) +} diff --git a/v_windows/v/vlib/v/checker/tests/string_char_null_err.out b/v_windows/v/vlib/v/checker/tests/string_char_null_err.out new file mode 100644 index 0000000..60e1f7a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_char_null_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/string_char_null_err.vv:2:31: error: cannot use `\0` (NULL character) in the string literal + 1 | fn main() { + 2 | println('Null character: \0') + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_char_null_err.vv b/v_windows/v/vlib/v/checker/tests/string_char_null_err.vv new file mode 100644 index 0000000..d60854f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_char_null_err.vv @@ -0,0 +1,3 @@ +fn main() { + println('Null character: \0') +} diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.out b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.out new file mode 100644 index 0000000..607c240 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/string_escape_u_err_a.vv:2:15: error: `\u` incomplete unicode character value + 1 | fn main() { + 2 | println('\u') + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.vv b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.vv new file mode 100644 index 0000000..ad7aee7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_a.vv @@ -0,0 +1,3 @@ +fn main() { + println('\u') +} diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.out b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.out new file mode 100644 index 0000000..d81d688 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/string_escape_u_err_b.vv:2:15: error: `\u` incomplete unicode character value + 1 | fn main() { + 2 | println('\u345') + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.vv b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.vv new file mode 100644 index 0000000..650f8d6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_u_err_b.vv @@ -0,0 +1,3 @@ +fn main() { + println('\u345') +} diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.out b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.out new file mode 100644 index 0000000..f441565 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/string_escape_x_err_a.vv:2:15: error: `\x` used with no following hex digits + 1 | fn main() { + 2 | println('\x') + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.vv b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.vv new file mode 100644 index 0000000..fdbd44b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_a.vv @@ -0,0 +1,3 @@ +fn main() { + println('\x') +} diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.out b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.out new file mode 100644 index 0000000..da90fd1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/string_escape_x_err_b.vv:2:15: error: `\x` used with no following hex digits + 1 | fn main() { + 2 | println('\xhh') + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.vv b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.vv new file mode 100644 index 0000000..deabe28 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_escape_x_err_b.vv @@ -0,0 +1,3 @@ +fn main() { + println('\xhh') +} diff --git a/v_windows/v/vlib/v/checker/tests/string_index_assign_error.out b/v_windows/v/vlib/v/checker/tests/string_index_assign_error.out new file mode 100644 index 0000000..be9369e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_index_assign_error.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/string_index_assign_error.vv:3:6: error: cannot assign to s[i] since V strings are immutable +(note, that variables may be mutable but string values are always immutable, like in Go and Java) + 1 | fn main() { + 2 | mut a := 'V is great!!' + 3 | a[0] = `R` + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_index_assign_error.vv b/v_windows/v/vlib/v/checker/tests/string_index_assign_error.vv new file mode 100644 index 0000000..161b215 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_index_assign_error.vv @@ -0,0 +1,4 @@ +fn main() { + mut a := 'V is great!!' + a[0] = `R` +} diff --git a/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.out b/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.out new file mode 100644 index 0000000..81e574a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/string_index_non_int_err.vv:3:14: error: non-integer string index `string` + 1 | fn main() { + 2 | v := 'foo' + 3 | println(v['f']) + | ~~~~~ + 4 | println(v[true]) + 5 | println(v[[23]]) +vlib/v/checker/tests/string_index_non_int_err.vv:4:14: error: non-integer string index `bool` + 2 | v := 'foo' + 3 | println(v['f']) + 4 | println(v[true]) + | ~~~~~~ + 5 | println(v[[23]]) + 6 | } +vlib/v/checker/tests/string_index_non_int_err.vv:5:14: error: non-integer string index `[]int` + 3 | println(v['f']) + 4 | println(v[true]) + 5 | println(v[[23]]) + | ~~~~~~ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.vv b/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.vv new file mode 100644 index 0000000..6619e47 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_index_non_int_err.vv @@ -0,0 +1,6 @@ +fn main() { + v := 'foo' + println(v['f']) + println(v[true]) + println(v[[23]]) +} diff --git a/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.out b/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.out new file mode 100644 index 0000000..6ac3efe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/string_interpolation_invalid_fmt.vv:3:12: error: format specifier may only be one letter + 1 | fn interpolate_wrong() string { + 2 | a := 5 + 3 | x := '${a:xy}' + | ~~ + 4 | return x + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.vv b/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.vv new file mode 100644 index 0000000..bfc8db4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_interpolation_invalid_fmt.vv @@ -0,0 +1,5 @@ +fn interpolate_wrong() string { + a := 5 + x := '${a:xy}' + return x +} diff --git a/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.out b/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.out new file mode 100644 index 0000000..4c9d7ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.out @@ -0,0 +1,63 @@ +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:3:16: error: precision specification only valid for float types + 1 | fn interpolate_str() string { + 2 | a := 'hallo' + 3 | x := '>${a:8.3s}<' + | ^ + 4 | y := '${a:G}' + 5 | z := '${a:d}' +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:4:12: error: illegal format specifier `G` for type `string` + 2 | a := 'hallo' + 3 | x := '>${a:8.3s}<' + 4 | y := '${a:G}' + | ^ + 5 | z := '${a:d}' + 6 | return x + y + z +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:5:12: error: illegal format specifier `d` for type `string` + 3 | x := '>${a:8.3s}<' + 4 | y := '${a:G}' + 5 | z := '${a:d}' + | ^ + 6 | return x + y + z + 7 | } +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:11:15: error: illegal format specifier `s` for type `f64` + 9 | fn interpolate_f64() string { + 10 | b := 1367.57 + 11 | x := '>${b:20s}<' + | ^ + 12 | y := '${b:d}' + 13 | return x + y +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:12:12: error: illegal format specifier `d` for type `f64` + 10 | b := 1367.57 + 11 | x := '>${b:20s}<' + 12 | y := '${b:d}' + | ^ + 13 | return x + y + 14 | } +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:19:14: error: illegal format specifier `d` for type `u32` + 17 | u := u32(15) + 18 | s := -12 + 19 | x := '${u:13d}' + | ^ + 20 | y := '${s:04u}' + 21 | z := '${s:f}' +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:20:14: error: illegal format specifier `u` for type `int` + 18 | s := -12 + 19 | x := '${u:13d}' + 20 | y := '${s:04u}' + | ^ + 21 | z := '${s:f}' + 22 | q := '${u:v}' +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:21:12: error: illegal format specifier `f` for type `int` + 19 | x := '${u:13d}' + 20 | y := '${s:04u}' + 21 | z := '${s:f}' + | ^ + 22 | q := '${u:v}' + 23 | return x + y + z + q +vlib/v/checker/tests/string_interpolation_wrong_fmt.vv:22:12: error: unknown format specifier `v` + 20 | y := '${s:04u}' + 21 | z := '${s:f}' + 22 | q := '${u:v}' + | ^ + 23 | return x + y + z + q + 24 | } diff --git a/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.vv b/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.vv new file mode 100644 index 0000000..38aa5f3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/string_interpolation_wrong_fmt.vv @@ -0,0 +1,24 @@ +fn interpolate_str() string { + a := 'hallo' + x := '>${a:8.3s}<' + y := '${a:G}' + z := '${a:d}' + return x + y + z +} + +fn interpolate_f64() string { + b := 1367.57 + x := '>${b:20s}<' + y := '${b:d}' + return x + y +} + +fn interpolate_int() string { + u := u32(15) + s := -12 + x := '${u:13d}' + y := '${s:04u}' + z := '${s:f}' + q := '${u:v}' + return x + y + z + q +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.out b/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.out new file mode 100644 index 0000000..54b047e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.vv:5:4: error: mismatched types `&Foo` and `Foo` + 3 | fn main() { + 4 | mut f := &Foo{ 10 } + 5 | f = Foo{ x: 20 } + | ^ + 6 | _ = f + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.vv b/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.vv new file mode 100644 index 0000000..5e96349 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_assigned_to_pointer_to_struct.vv @@ -0,0 +1,7 @@ +struct Foo { x int } + +fn main() { + mut f := &Foo{ 10 } + f = Foo{ x: 20 } + _ = f +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.out b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.out new file mode 100644 index 0000000..62cb81a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv:11:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 9 | fn main() { + 10 | abc := Abc{} + 11 | _ := Xyz(abc) + | ~~~~~~~~ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv new file mode 100644 index 0000000..ce81ba5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_generic_err.vv @@ -0,0 +1,12 @@ +struct Abc { + name T +} + +struct Xyz { + name string +} + +fn main() { + abc := Abc{} + _ := Xyz(abc) +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.out b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.out new file mode 100644 index 0000000..baafe70 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 10 | fn main() { + 11 | abc := Abc{} + 12 | _ := Xyz(abc) + | ~~~~~~~~ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv new file mode 100644 index 0000000..7a5e4a3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_a.vv @@ -0,0 +1,13 @@ +struct Abc { +mut: + name string +} + +struct Xyz { + name string +} + +fn main() { + abc := Abc{} + _ := Xyz(abc) +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.out b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.out new file mode 100644 index 0000000..aa6f232 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 10 | fn main() { + 11 | abc := Abc{} + 12 | _ := Xyz(abc) + | ~~~~~~~~ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv new file mode 100644 index 0000000..ff34dca --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_mut_err_b.vv @@ -0,0 +1,13 @@ +struct Abc { + name string +} + +struct Xyz { +mut: + name string +} + +fn main() { + abc := Abc{} + _ := Xyz(abc) +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.out b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.out new file mode 100644 index 0000000..0a2778b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 10 | fn main() { + 11 | abc := Abc{} + 12 | _ := Xyz(abc) + | ~~~~~~~~ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv new file mode 100644 index 0000000..7c559e4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_a.vv @@ -0,0 +1,13 @@ +struct Abc { +pub: + name string +} + +struct Xyz { + name string +} + +fn main() { + abc := Abc{} + _ := Xyz(abc) +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.out b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.out new file mode 100644 index 0000000..bbc7e33 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv:12:7: error: casting to struct is deprecated, use e.g. `Struct{...expr}` instead + 10 | fn main() { + 11 | abc := Abc{} + 12 | _ := Xyz(abc) + | ~~~~~~~~ + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv new file mode 100644 index 0000000..aa92278 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_cast_to_struct_pub_err_b.vv @@ -0,0 +1,13 @@ +struct Abc { + name string +} + +struct Xyz { +pub: + name string +} + +fn main() { + abc := Abc{} + _ := Xyz(abc) +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.out b/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.out new file mode 100644 index 0000000..d85190f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_embed_invalid_type.vv:4:2: error: `Foo` is not a struct + 2 | + 3 | struct Bar { + 4 | Foo + | ~~~ + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.vv b/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.vv new file mode 100644 index 0000000..13f3c52 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_embed_invalid_type.vv @@ -0,0 +1,5 @@ +type Foo = int + +struct Bar { + Foo +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.out b/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.out new file mode 100644 index 0000000..691268c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_field_name_duplicate_err.vv:3:2: error: field name `a` duplicate + 1 | struct Aaa { + 2 | a int + 3 | a string + | ~~~~~~~~ + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.vv b/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.vv new file mode 100644 index 0000000..487b0a7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_field_name_duplicate_err.vv @@ -0,0 +1,4 @@ +struct Aaa { + a int + a string +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_field_type_err.out b/v_windows/v/vlib/v/checker/tests/struct_field_type_err.out new file mode 100644 index 0000000..6c8e6df --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_field_type_err.out @@ -0,0 +1,43 @@ +vlib/v/checker/tests/struct_field_type_err.vv:12:3: error: cannot assign to field `n`: expected `int`, not `bool` + 10 | fn main() { + 11 | mut data := Data{ + 12 | n: true + | ~~~~~~~ + 13 | b: 0 + 14 | f1: fn (v ...voidptr) {} +vlib/v/checker/tests/struct_field_type_err.vv:13:3: error: cannot assign to field `b`: expected `bool`, not `int literal` + 11 | mut data := Data{ + 12 | n: true + 13 | b: 0 + | ~~~~ + 14 | f1: fn (v ...voidptr) {} + 15 | f2: fn (v voidptr) {} +vlib/v/checker/tests/struct_field_type_err.vv:14:3: error: cannot assign to field `f1`: expected `fn (voidptr)`, not `fn (...voidptr)` + 12 | n: true + 13 | b: 0 + 14 | f1: fn (v ...voidptr) {} + | ~~~~~~~~~~~~~~~~~~~~~~~~ + 15 | f2: fn (v voidptr) {} + 16 | data: true +Details: ``'s expected fn argument: `` is a pointer, but the passed fn argument: `v` is NOT a pointer +vlib/v/checker/tests/struct_field_type_err.vv:15:3: error: cannot assign to field `f2`: expected `fn (...voidptr)`, not `fn (voidptr)` + 13 | b: 0 + 14 | f1: fn (v ...voidptr) {} + 15 | f2: fn (v voidptr) {} + | ~~~~~~~~~~~~~~~~~~~~~ + 16 | data: true + 17 | } +Details: ``'s expected fn argument: `` is NOT a pointer, but the passed fn argument: `v` is a pointer +vlib/v/checker/tests/struct_field_type_err.vv:16:3: error: cannot assign to field `data`: expected `&Data`, not `bool` + 14 | f1: fn (v ...voidptr) {} + 15 | f2: fn (v voidptr) {} + 16 | data: true + | ~~~~~~~~~~ + 17 | } + 18 | +vlib/v/checker/tests/struct_field_type_err.vv:19:11: error: cannot assign to `data.n`: expected `int`, not `bool` + 17 | } + 18 | + 19 | data.n = true + | ~~~~ + 20 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_field_type_err.vv b/v_windows/v/vlib/v/checker/tests/struct_field_type_err.vv new file mode 100644 index 0000000..44850c2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_field_type_err.vv @@ -0,0 +1,20 @@ +struct Data { +mut: + n int + b bool + f1 fn (voidptr) + f2 fn (...voidptr) + data &Data +} + +fn main() { + mut data := Data{ + n: true + b: 0 + f1: fn (v ...voidptr) {} + f2: fn (v voidptr) {} + data: true + } + + data.n = true +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.out b/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.out new file mode 100644 index 0000000..c0eab9e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.out @@ -0,0 +1,35 @@ +vlib/v/checker/tests/struct_init_update_type_err.vv:11:6: error: expected struct, found `int` + 9 | i := 2 + 10 | _ := Foo{ + 11 | ...i + | ^ + 12 | name: 'f2' + 13 | } +vlib/v/checker/tests/struct_init_update_type_err.vv:16:6: error: expected struct, found `&int` + 14 | p := &i + 15 | _ = Foo{ + 16 | ...p + | ^ + 17 | } + 18 | f2 := Foo2{} +vlib/v/checker/tests/struct_init_update_type_err.vv:20:6: error: struct `Foo2` is not compatible with struct `Foo` + 18 | f2 := Foo2{} + 19 | _ = Foo{ + 20 | ...f2 + | ~~ + 21 | } + 22 | _ = Foo{ +vlib/v/checker/tests/struct_init_update_type_err.vv:23:6: error: expression is not an lvalue + 21 | } + 22 | _ = Foo{ + 23 | ...Foo{} + | ~~~~~ + 24 | } + 25 | } +vlib/v/checker/tests/struct_init_update_type_err.vv:32:6: error: struct `Empty` is not compatible with struct `Foo` + 30 | e := Empty{} + 31 | _ = Foo{ + 32 | ...e + | ^ + 33 | } + 34 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.vv b/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.vv new file mode 100644 index 0000000..6f8deeb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_init_update_type_err.vv @@ -0,0 +1,35 @@ +struct Foo { + name string + age int +} + +struct Foo2 {b bool} + +fn main() { + i := 2 + _ := Foo{ + ...i + name: 'f2' + } + p := &i + _ = Foo{ + ...p + } + f2 := Foo2{} + _ = Foo{ + ...f2 + } + _ = Foo{ + ...Foo{} + } +} + +struct Empty {} + +fn empty() { + e := Empty{} + _ = Foo{ + ...e + } +} + diff --git a/v_windows/v/vlib/v/checker/tests/struct_pub_field.out b/v_windows/v/vlib/v/checker/tests/struct_pub_field.out new file mode 100644 index 0000000..c0c34c7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_pub_field.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_pub_field.vv:9:4: error: field `i` of struct `Foo` is immutable + 7 | i: 1 + 8 | } + 9 | a.i = 2 + | ^ + 10 | } diff --git a/v_windows/v/vlib/v/checker/tests/struct_pub_field.vv b/v_windows/v/vlib/v/checker/tests/struct_pub_field.vv new file mode 100644 index 0000000..1f104ca --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_pub_field.vv @@ -0,0 +1,10 @@ +struct Foo { + i int +} + +fn main() { + a := Foo{ + i: 1 + } + a.i = 2 +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_required_field.out b/v_windows/v/vlib/v/checker/tests/struct_required_field.out new file mode 100644 index 0000000..212ca63 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_required_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_required_field.vv:12:6: error: field `Abc.f3` must be initialized + 10 | f3: 789 + 11 | } + 12 | _ = Abc{ + | ~~~~ + 13 | f1: 123 + 14 | f2: 789 diff --git a/v_windows/v/vlib/v/checker/tests/struct_required_field.vv b/v_windows/v/vlib/v/checker/tests/struct_required_field.vv new file mode 100644 index 0000000..85b2a4d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_required_field.vv @@ -0,0 +1,16 @@ +struct Abc { + f1 int [required] + f2 int + f3 int [required] +} + +fn main() { + _ = Abc{ + f1: 123 + f3: 789 + } + _ = Abc{ + f1: 123 + f2: 789 + } +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.out b/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.out new file mode 100644 index 0000000..25d6ee8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_required_fn_field.vv:12:6: error: field `Abc.f3` must be initialized + 10 | f3: fn () {} + 11 | } + 12 | _ = Abc{ + | ~~~~ + 13 | f1: 123 + 14 | f2: 789 diff --git a/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.vv b/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.vv new file mode 100644 index 0000000..e72cfe6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_required_fn_field.vv @@ -0,0 +1,16 @@ +struct Abc { + f1 int [required] + f2 int + f3 fn () [attr1; required; attr2] +} + +fn main() { + _ = Abc{ + f1: 123 + f3: fn () {} + } + _ = Abc{ + f1: 123 + f2: 789 + } +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.out b/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.out new file mode 100644 index 0000000..4648a27 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.out @@ -0,0 +1,63 @@ +vlib/v/checker/tests/struct_type_cast_err.vv:5:10: error: cannot cast struct to `string` + 3 | fn main() { + 4 | foo := Foo{} + 5 | _ := string(foo) + | ~~~~~~~~~~~ + 6 | _ := int(foo) + 7 | _ := u64(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:6:10: error: cannot cast struct to `int` + 4 | foo := Foo{} + 5 | _ := string(foo) + 6 | _ := int(foo) + | ~~~~~~~~ + 7 | _ := u64(foo) + 8 | _ := u32(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:7:10: error: cannot cast struct to `u64` + 5 | _ := string(foo) + 6 | _ := int(foo) + 7 | _ := u64(foo) + | ~~~~~~~~ + 8 | _ := u32(foo) + 9 | _ := rune(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:8:10: error: cannot cast struct to `u32` + 6 | _ := int(foo) + 7 | _ := u64(foo) + 8 | _ := u32(foo) + | ~~~~~~~~ + 9 | _ := rune(foo) + 10 | _ := byte(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:9:10: error: cannot cast struct to `rune` + 7 | _ := u64(foo) + 8 | _ := u32(foo) + 9 | _ := rune(foo) + | ~~~~~~~~~ + 10 | _ := byte(foo) + 11 | _ := i8(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:10:10: error: cannot cast type `Foo` to `byte` + 8 | _ := u32(foo) + 9 | _ := rune(foo) + 10 | _ := byte(foo) + | ~~~~~~~~~ + 11 | _ := i8(foo) + 12 | _ := i64(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:11:10: error: cannot cast struct to `i8` + 9 | _ := rune(foo) + 10 | _ := byte(foo) + 11 | _ := i8(foo) + | ~~~~~~~ + 12 | _ := i64(foo) + 13 | _ := int(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:12:10: error: cannot cast struct to `i64` + 10 | _ := byte(foo) + 11 | _ := i8(foo) + 12 | _ := i64(foo) + | ~~~~~~~~ + 13 | _ := int(foo) + 14 | _ = &I1(foo) +vlib/v/checker/tests/struct_type_cast_err.vv:13:10: error: cannot cast struct to `int` + 11 | _ := i8(foo) + 12 | _ := i64(foo) + 13 | _ := int(foo) + | ~~~~~~~~ + 14 | _ = &I1(foo) + 15 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.vv b/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.vv new file mode 100644 index 0000000..a27eefd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_type_cast_err.vv @@ -0,0 +1,17 @@ +struct Foo{} + +fn main() { + foo := Foo{} + _ := string(foo) + _ := int(foo) + _ := u64(foo) + _ := u32(foo) + _ := rune(foo) + _ := byte(foo) + _ := i8(foo) + _ := i64(foo) + _ := int(foo) + _ = &I1(foo) +} + +interface I1{} diff --git a/v_windows/v/vlib/v/checker/tests/struct_unknown_field.out b/v_windows/v/vlib/v/checker/tests/struct_unknown_field.out new file mode 100644 index 0000000..2c544b0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_unknown_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_unknown_field.vv:8:3: error: unknown field `bar` in struct literal of type `Test` + 6 | t := Test{ + 7 | foo: true + 8 | bar: false + | ~~~~~~~~~~ + 9 | } + 10 | _ = t diff --git a/v_windows/v/vlib/v/checker/tests/struct_unknown_field.vv b/v_windows/v/vlib/v/checker/tests/struct_unknown_field.vv new file mode 100644 index 0000000..018bcef --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_unknown_field.vv @@ -0,0 +1,11 @@ +struct Test { + foo bool +} + +fn main() { + t := Test{ + foo: true + bar: false + } + _ = t +} diff --git a/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.out b/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.out new file mode 100644 index 0000000..029f9fe --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/struct_unneeded_default.vv:2:10: error: unnecessary default value of `0`: struct fields are zeroed by default + 1 | struct Test { + 2 | n int = 0 + | ^ + 3 | s string = '' + 4 | b bool = false +vlib/v/checker/tests/struct_unneeded_default.vv:3:13: error: unnecessary default value of '': struct fields are zeroed by default + 1 | struct Test { + 2 | n int = 0 + 3 | s string = '' + | ~~ + 4 | b bool = false + 5 | } +vlib/v/checker/tests/struct_unneeded_default.vv:4:11: error: unnecessary default value `false`: struct fields are zeroed by default + 2 | n int = 0 + 3 | s string = '' + 4 | b bool = false + | ~~~~~ + 5 | } + 6 | \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.vv b/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.vv new file mode 100644 index 0000000..7ddc066 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/struct_unneeded_default.vv @@ -0,0 +1,8 @@ +struct Test { + n int = 0 + s string = '' + b bool = false +} + +fn main() { +} diff --git a/v_windows/v/vlib/v/checker/tests/sum.out b/v_windows/v/vlib/v/checker/tests/sum.out new file mode 100644 index 0000000..5c92692 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/sum.vv:5:8: error: cannot cast non-sum type `int` using `as` + 3 | fn non_sum() { + 4 | v := 4 + 5 | _ = v as rune + | ~~ + 6 | _ = v as Var + 7 | } +vlib/v/checker/tests/sum.vv:6:8: error: cannot cast non-sum type `int` using `as` - use e.g. `Var(some_expr)` instead. + 4 | v := 4 + 5 | _ = v as rune + 6 | _ = v as Var + | ~~ + 7 | } + 8 | +vlib/v/checker/tests/sum.vv:10:7: error: cannot cast `rune` to `Var` + 8 | + 9 | fn sum() { + 10 | _ := Var(`J`) + | ~~~~~~~~ + 11 | mut s2 := Var('') + 12 | s2 = true +vlib/v/checker/tests/sum.vv:12:7: error: cannot assign to `s2`: expected `Var`, not `bool` + 10 | _ := Var(`J`) + 11 | mut s2 := Var('') + 12 | s2 = true + | ~~~~ + 13 | _ = s2 + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum.vv b/v_windows/v/vlib/v/checker/tests/sum.vv new file mode 100644 index 0000000..6796a2e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum.vv @@ -0,0 +1,14 @@ +type Var = int | string + +fn non_sum() { + v := 4 + _ = v as rune + _ = v as Var +} + +fn sum() { + _ := Var(`J`) + mut s2 := Var('') + s2 = true + _ = s2 +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.out b/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.out new file mode 100644 index 0000000..1a9ea99 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/sum_type_assign_non_variant_err.vv:11:6: error: cannot assign to `w`: expected `Stmt`, not `IfExpr` + 9 | fn main() { + 10 | mut w := Stmt(AnotherThing{}) + 11 | w = IfExpr{} + | ~~~~~~~~ + 12 | _ = w + 13 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.vv b/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.vv new file mode 100644 index 0000000..1ab9a2c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_assign_non_variant_err.vv @@ -0,0 +1,13 @@ +type Expr = IfExpr | CallExpr | MatchExpr +struct MatchExpr {} +struct IfExpr {} +struct CallExpr {} + +type Stmt = Expr | AnotherThing +struct AnotherThing {} + +fn main() { + mut w := Stmt(AnotherThing{}) + w = IfExpr{} + _ = w +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.out b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.out new file mode 100644 index 0000000..20c3b00 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/sum_type_common_fields_alias_error.vv:35:14: error: field `name` does not exist or have the same type in all sumtype variants + 33 | } + 34 | println(m) + 35 | assert m[0].name == 'abc' + | ~~~~ + 36 | assert m[1].name == 'def' + 37 | assert m[2].name == 'xyz' +vlib/v/checker/tests/sum_type_common_fields_alias_error.vv:36:14: error: field `name` does not exist or have the same type in all sumtype variants + 34 | println(m) + 35 | assert m[0].name == 'abc' + 36 | assert m[1].name == 'def' + | ~~~~ + 37 | assert m[2].name == 'xyz' + 38 | } +vlib/v/checker/tests/sum_type_common_fields_alias_error.vv:37:14: error: field `name` does not exist or have the same type in all sumtype variants + 35 | assert m[0].name == 'abc' + 36 | assert m[1].name == 'def' + 37 | assert m[2].name == 'xyz' + | ~~~~ + 38 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.vv b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.vv new file mode 100644 index 0000000..9a296b8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_alias_error.vv @@ -0,0 +1,38 @@ +type Main = Sub1 | Sub2 | Sub3 + +// NB: the subtypes will have a common `name` field, of the same `string` +// type, except Sub3, which has `name` of type AliasedString. + +type AliasedString = string + +struct Sub1 { +mut: + name string +} + +struct Sub2 { +mut: + name string +} + +struct Sub3 { +mut: + name AliasedString +} + +fn main() { + mut m := []Main{} + m << Sub1{ + name: 'abc' + } + m << Sub2{ + name: 'def' + } + m << Sub3{ + name: 'xyz' + } + println(m) + assert m[0].name == 'abc' + assert m[1].name == 'def' + assert m[2].name == 'xyz' +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.out b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.out new file mode 100644 index 0000000..9dce91a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/sum_type_common_fields_error.vv:53:14: error: field `val` does not exist or have the same type in all sumtype variants + 51 | assert m[2].name == '64bit integer' + 52 | assert m[3].name == 'string' + 53 | assert m[0].val == 123 + | ~~~ + 54 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.vv b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.vv new file mode 100644 index 0000000..2da560a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_common_fields_error.vv @@ -0,0 +1,54 @@ +type Main = Sub1 | Sub2 | Sub3 | Sub4 + +// NB: all subtypes have a common name field, of the same `string` type +// but they also have a field `val` that is of a different type in the +// different subtypes => accessing `m[0].name` is fine, but *not* `m[0].val` +struct Sub1 { +mut: + val int + name string +} + +struct Sub2 { +mut: + val f32 + name string +} + +struct Sub3 { +mut: + val i64 + name string +} + +struct Sub4 { +mut: + val string + name string +} + +fn main() { + mut m := []Main{} + m << Sub1{ + val: 123 + name: 'integer' + } + m << Sub2{ + val: 3.14 + name: 'float' + } + m << Sub3{ + val: 9_876_543_210 + name: '64bit integer' + } + m << Sub4{ + val: 'abcd' + name: 'string' + } + println(m) + assert m[0].name == 'integer' + assert m[1].name == 'float' + assert m[2].name == '64bit integer' + assert m[3].name == 'string' + assert m[0].val == 123 +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_exists.out b/v_windows/v/vlib/v/checker/tests/sum_type_exists.out new file mode 100644 index 0000000..a580ba0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_exists.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/sum_type_exists.vv:1:22: error: unknown type `Inexistant` + 1 | type Miscellaneous = Inexistant | Nope | int + | ~~~~~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_exists.vv b/v_windows/v/vlib/v/checker/tests/sum_type_exists.vv new file mode 100644 index 0000000..28a4475 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_exists.vv @@ -0,0 +1 @@ +type Miscellaneous = Inexistant | Nope | int diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.out b/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.out new file mode 100644 index 0000000..6a06f1b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/sum_type_infix_err.vv:5:9: error: cannot use operator `+` with `Abc` + 3 | fn main() { + 4 | x := Abc(0) + 5 | _ := x + Abc(5) + | ^ + 6 | _ := 123 + x + 7 | _ = unsafe{&x + 5} +vlib/v/checker/tests/sum_type_infix_err.vv:6:11: error: cannot use operator `+` with `Abc` + 4 | x := Abc(0) + 5 | _ := x + Abc(5) + 6 | _ := 123 + x + | ^ + 7 | _ = unsafe{&x + 5} + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.vv b/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.vv new file mode 100644 index 0000000..938dd6f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_infix_err.vv @@ -0,0 +1,8 @@ +type Abc = int | string + +fn main() { + x := Abc(0) + _ := x + Abc(5) + _ := 123 + x + _ = unsafe{&x + 5} +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.out b/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.out new file mode 100644 index 0000000..7052f3b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/sum_type_multiple_type_define.vv:3:22: error: sum type FooType cannot hold the type `Foo` more than once + 1 | struct Foo {} + 2 | + 3 | type FooType = Foo | Foo + | ~~~ diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.vv b/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.vv new file mode 100644 index 0000000..7ec43d0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_multiple_type_define.vv @@ -0,0 +1,3 @@ +struct Foo {} + +type FooType = Foo | Foo diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.out b/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.out new file mode 100644 index 0000000..a880221 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/sum_type_mutable_cast_err.vv:15:10: error: cannot use operator `+` with `Abc` + 13 | mut x := Abc(0) + 14 | if x is int { + 15 | _ := x + 5 + | ^ + 16 | } + 17 | mut f := Foo{Bar{Abc(0)}} +vlib/v/checker/tests/sum_type_mutable_cast_err.vv:19:14: error: cannot use operator `+` with `Abc` + 17 | mut f := Foo{Bar{Abc(0)}} + 18 | if f.b.a is int { + 19 | _ := f.b.a + 5 + | ^ + 20 | } + 21 | } diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.vv b/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.vv new file mode 100644 index 0000000..d122a12 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_mutable_cast_err.vv @@ -0,0 +1,21 @@ +type Abc = int | string + +struct Bar { +mut: + a Abc +} + +struct Foo { + b Bar +} + +fn main() { + mut x := Abc(0) + if x is int { + _ := x + 5 + } + mut f := Foo{Bar{Abc(0)}} + if f.b.a is int { + _ := f.b.a + 5 + } +} diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.out b/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.out new file mode 100644 index 0000000..d893a2b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.out @@ -0,0 +1,18 @@ +vlib/v/checker/tests/sum_type_ref_variant_err.vv:7:33: error: sum type cannot hold a reference type + 5 | foo string + 6 | } + 7 | type Alphabet1 = Abc | string | &Xyz + | ~~~~ + 8 | type Alphabet2 = Abc | &Xyz | string + 9 | type Alphabet3 = &Xyz | Abc | string +vlib/v/checker/tests/sum_type_ref_variant_err.vv:8:24: error: sum type cannot hold a reference type + 6 | } + 7 | type Alphabet1 = Abc | string | &Xyz + 8 | type Alphabet2 = Abc | &Xyz | string + | ~~~~ + 9 | type Alphabet3 = &Xyz | Abc | string +vlib/v/checker/tests/sum_type_ref_variant_err.vv:9:18: error: sum type cannot hold a reference type + 7 | type Alphabet1 = Abc | string | &Xyz + 8 | type Alphabet2 = Abc | &Xyz | string + 9 | type Alphabet3 = &Xyz | Abc | string + | ~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.vv b/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.vv new file mode 100644 index 0000000..def2a54 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sum_type_ref_variant_err.vv @@ -0,0 +1,9 @@ +struct Abc { + val string +} +struct Xyz { + foo string +} +type Alphabet1 = Abc | string | &Xyz +type Alphabet2 = Abc | &Xyz | string +type Alphabet3 = &Xyz | Abc | string \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.out b/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.out new file mode 100644 index 0000000..f491079 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.out @@ -0,0 +1,3 @@ +vlib/v/checker/tests/sumtype_in_sumtype_err.vv:1:11: error: sum type cannot hold itself + 1 | type AA = AA | int + | ~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.vv b/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.vv new file mode 100644 index 0000000..7e53df8 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_in_sumtype_err.vv @@ -0,0 +1 @@ +type AA = AA | int diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.out b/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.out new file mode 100644 index 0000000..14cfcb5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.vv:7:11: error: cannot use `(i8 | i16 | int | i64)` as type `SimpleInt` in return argument + 5 | match s { + 6 | i8, i16, int, i64 { + 7 | return s + | ^ + 8 | } + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.vv b/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.vv new file mode 100644 index 0000000..0aea56c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_mismatch_of_aggregate_err.vv @@ -0,0 +1,13 @@ +type SimpleInt = i64 | int +type SuperInt = i16 | i64 | i8 | int + +fn ret_super(s SuperInt) SimpleInt { + match s { + i8, i16, int, i64 { + return s + } + } +} + +fn main() { +} diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.out b/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.out new file mode 100644 index 0000000..813f472 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/sumtype_mismatched_type.vv:4:8: error: infix expr: cannot use `int literal` (right expression) as `AA` + 2 | + 3 | a := AA(3) + 4 | assert a == 3 + | ~~~~~~ \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.vv b/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.vv new file mode 100644 index 0000000..d6a998b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/sumtype_mismatched_type.vv @@ -0,0 +1,4 @@ +type AA = int | string + +a := AA(3) +assert a == 3 diff --git a/v_windows/v/vlib/v/checker/tests/templates/index.html b/v_windows/v/vlib/v/checker/tests/templates/index.html new file mode 100644 index 0000000..7624de1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/templates/index.html @@ -0,0 +1 @@ +@test diff --git a/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.out b/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.out new file mode 100644 index 0000000..0972921 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/test_functions_should_not_return_test.vv:9:1: error: test functions should either return nothing at all, or be marked to return `?` + 7 | + 8 | // should be disallowed: + 9 | fn test_returning_int() int { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.vv b/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.vv new file mode 100644 index 0000000..3bebcea --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/test_functions_should_not_return_test.vv @@ -0,0 +1,17 @@ +// ordinary functions can return whatever they like, +// since they are not called by V's testing system +// in the generated main(): +fn abc() int { + return 1 +} + +// should be disallowed: +fn test_returning_int() int { + +} + +// NB: this is allowed explicitly now, to allow for shorter tests +// of functions returning optionals. +fn test_returning_opt() ? { + assert true +} diff --git a/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.out b/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.out new file mode 100644 index 0000000..eed55bd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/trailing_comma_struct_attr.vv:3:31: error: unexpected token `]`, expecting name + 1 | struct User { + 2 | name string + 3 | jobs []string [json:jobss;] + | ^ + 4 | } diff --git a/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.vv b/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.vv new file mode 100644 index 0000000..d20a3d4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/trailing_comma_struct_attr.vv @@ -0,0 +1,4 @@ +struct User { + name string + jobs []string [json:jobss;] +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.out b/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.out new file mode 100644 index 0000000..bb52bad --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/type_cast_optional_err.vv:2:13: error: cannot type cast an optional + 1 | fn main() { + 2 | println(int('hi'.last_index('i'))) + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.vv b/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.vv new file mode 100644 index 0000000..982559c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/type_cast_optional_err.vv @@ -0,0 +1,3 @@ +fn main() { + println(int('hi'.last_index('i'))) +} diff --git a/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.out b/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.out new file mode 100644 index 0000000..0253fba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/typedef_attr_v_struct_err.vv:2:1: error: `typedef` attribute can only be used with C structs + 1 | [typedef] + 2 | struct Point{} + | ~~~~~~~~~~~~ + 3 | + 4 | fn main() { diff --git a/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.vv b/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.vv new file mode 100644 index 0000000..421b635 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/typedef_attr_v_struct_err.vv @@ -0,0 +1,7 @@ +[typedef] +struct Point{} + +fn main() { + p := Point{} + println(p) +} diff --git a/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.out b/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.out new file mode 100644 index 0000000..35e0a6c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/undefined_ident_of_struct.vv:4:2: error: undefined ident: `f` + 2 | + 3 | fn get() { + 4 | f.a = 'test' + | ^ + 5 | } + 6 | diff --git a/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.vv b/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.vv new file mode 100644 index 0000000..a426195 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/undefined_ident_of_struct.vv @@ -0,0 +1,9 @@ +module main + +fn get() { + f.a = 'test' +} + +fn main() { + println('hello') +} diff --git a/v_windows/v/vlib/v/checker/tests/unexpected_or.out b/v_windows/v/vlib/v/checker/tests/unexpected_or.out new file mode 100644 index 0000000..ab1e077 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unexpected_or.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unexpected_or.vv:6:17: error: unexpected `or` block, the function `ret_zero` does not return an optional + 4 | + 5 | fn main() { + 6 | _ = ret_zero() or { 1 } + | ~~~~~~~~ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/unexpected_or.vv b/v_windows/v/vlib/v/checker/tests/unexpected_or.vv new file mode 100644 index 0000000..48a8696 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unexpected_or.vv @@ -0,0 +1,7 @@ +fn ret_zero() int { + return 0 +} + +fn main() { + _ = ret_zero() or { 1 } +} diff --git a/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.out b/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.out new file mode 100644 index 0000000..411748d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unexpected_or_propagate.vv:6:17: error: unexpected `?`, the function `ret_zero` does not return an optional + 4 | + 5 | fn opt_fn() ?int { + 6 | a := ret_zero()? + | ^ + 7 | return a + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.vv b/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.vv new file mode 100644 index 0000000..16ef7b0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unexpected_or_propagate.vv @@ -0,0 +1,12 @@ +fn ret_zero() int { + return 0 +} + +fn opt_fn() ?int { + a := ret_zero()? + return a +} + +fn main() { + opt_fn() or {} +} diff --git a/v_windows/v/vlib/v/checker/tests/unfinished_string.out b/v_windows/v/vlib/v/checker/tests/unfinished_string.out new file mode 100644 index 0000000..c42f70a --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unfinished_string.out @@ -0,0 +1,2 @@ +vlib/v/checker/tests/unfinished_string.vv:2:1: error: unfinished string literal + 1 | a := ' diff --git a/v_windows/v/vlib/v/checker/tests/unfinished_string.vv b/v_windows/v/vlib/v/checker/tests/unfinished_string.vv new file mode 100644 index 0000000..e6374fb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unfinished_string.vv @@ -0,0 +1 @@ +a := ' diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.out new file mode 100644 index 0000000..225ccd0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_a.vv:10:6: error: `Cat` doesn't implement method `name` of interface `Animal` + 8 | + 9 | fn main() { + 10 | foo(Cat{}) + | ~~~~~ + 11 | } diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.vv new file mode 100644 index 0000000..fad1194 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_a.vv @@ -0,0 +1,11 @@ +interface Animal { + name() string +} + +struct Cat {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.out new file mode 100644 index 0000000..edb00d0 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unimplemented_interface_b.vv:13:6: error: `Cat` incorrectly implements method `name` of interface `Animal`: expected return type `string` + 11 | fn main() { + 12 | c := Cat{} + 13 | foo(c) + | ^ + 14 | } +Details: main.Animal has `name() string` diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.vv new file mode 100644 index 0000000..0b07a78 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_b.vv @@ -0,0 +1,14 @@ +interface Animal { + name() string +} + +struct Cat {} + +fn (c Cat) name() {} + +fn foo(a Animal) {} + +fn main() { + c := Cat{} + foo(c) +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.out new file mode 100644 index 0000000..43060d4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unimplemented_interface_c.vv:12:6: error: `Cat` incorrectly implements method `name` of interface `Animal`: expected 1 parameter(s), not 2 + 10 | + 11 | fn main() { + 12 | foo(Cat{}) + | ~~~~~ + 13 | } +Details: main.Animal has `name()` diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.vv new file mode 100644 index 0000000..46960da --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_c.vv @@ -0,0 +1,13 @@ +interface Animal { + name() +} + +struct Cat {} + +fn (c Cat) name(s string) {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.out new file mode 100644 index 0000000..5e39c5e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unimplemented_interface_d.vv:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected 2 parameter(s), not 1 + 10 | + 11 | fn main() { + 12 | foo(Cat{}) + | ~~~~~ + 13 | } +Details: main.Animal has `speak(s string)` diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.vv new file mode 100644 index 0000000..51b3724 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_d.vv @@ -0,0 +1,13 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak() {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.out new file mode 100644 index 0000000..f388cb2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.out @@ -0,0 +1,15 @@ +vlib/v/checker/tests/unimplemented_interface_e.vv:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected `string`, not `&string` for parameter 1 + 10 | + 11 | fn main() { + 12 | foo(Cat{}) + | ~~~~~ + 13 | _ = Animal(Cat{}) + 14 | } +Details: main.Animal has `speak(s string)` +vlib/v/checker/tests/unimplemented_interface_e.vv:13:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected `string`, not `&string` for parameter 1 + 11 | fn main() { + 12 | foo(Cat{}) + 13 | _ = Animal(Cat{}) + | ~~~~~~~~~~~~~ + 14 | } +Details: main.Animal has `speak(s string)` diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.vv new file mode 100644 index 0000000..85cee42 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_e.vv @@ -0,0 +1,14 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak(s &string) {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) + _ = Animal(Cat{}) +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.out new file mode 100644 index 0000000..3bdc2ac --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unimplemented_interface_f.vv:11:13: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected 2 parameter(s), not 1 + 9 | fn main() { + 10 | mut animals := []Animal{} + 11 | animals << Cat{} + | ~~~~~ + 12 | } +Details: main.Animal has `speak(s string)` diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.vv new file mode 100644 index 0000000..3fd767d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_f.vv @@ -0,0 +1,12 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak() {} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.out new file mode 100644 index 0000000..61c2cd3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unimplemented_interface_g.vv:12:13: error: `Cat` incorrectly implements method `speak` of interface `Animal`, expected `speak(s string)` + 10 | mut animals := []Animal{} + 11 | mut cats := []Cat{} + 12 | animals << cats + | ~~~~ + 13 | } + 14 | diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.vv.disabled b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.vv.disabled new file mode 100644 index 0000000..fca4beb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_g.vv.disabled @@ -0,0 +1,14 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak() {} + +fn main() { + mut animals := []Animal{} + mut cats := []Cat{} + animals << cats +} + diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.out new file mode 100644 index 0000000..0731050 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_h.vv:9:13: error: `Cat` doesn't implement field `name` of interface `Animal` + 7 | fn main() { + 8 | mut animals := []Animal{} + 9 | animals << Cat{} + | ~~~~~ + 10 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.vv new file mode 100644 index 0000000..cd00881 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_h.vv @@ -0,0 +1,10 @@ +interface Animal { + name string +} + +struct Cat {} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.out new file mode 100644 index 0000000..073e248 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_i.vv:11:13: error: `Cat` incorrectly implements field `name` of interface `Animal`, expected `string`, got `int` + 9 | fn main() { + 10 | mut animals := []Animal{} + 11 | animals << Cat{} + | ~~~~~ + 12 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.vv new file mode 100644 index 0000000..4c50f0e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_i.vv @@ -0,0 +1,12 @@ +interface Animal { + name string +} + +struct Cat { + name int +} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.out b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.out new file mode 100644 index 0000000..4c85238 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_j.vv:12:13: error: `Cat` incorrectly implements interface `Animal`, field `name` must be mutable + 10 | fn main() { + 11 | mut animals := []Animal{} + 12 | animals << Cat{} + | ~~~~~ + 13 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.vv b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.vv new file mode 100644 index 0000000..f54dab3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unimplemented_interface_j.vv @@ -0,0 +1,13 @@ +interface Animal { +mut: + name string +} + +struct Cat { + name string +} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.out b/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.out new file mode 100644 index 0000000..be40d1b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/union_unsafe_fields.vv:10:9: error: reading a union field (or its address) requires `unsafe` + 8 | mut u := Uf32{u: 3} + 9 | u.f = 3.3 // ok + 10 | _ := u.u + | ^ + 11 | return &u.f + 12 | } +vlib/v/checker/tests/union_unsafe_fields.vv:11:12: error: reading a union field (or its address) requires `unsafe` + 9 | u.f = 3.3 // ok + 10 | _ := u.u + 11 | return &u.f + | ^ + 12 | } diff --git a/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.vv b/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.vv new file mode 100644 index 0000000..6a1bc0f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/union_unsafe_fields.vv @@ -0,0 +1,12 @@ +union Uf32 { +mut: + f f32 + u u32 +} + +fn f() f32 { + mut u := Uf32{u: 3} + u.f = 3.3 // ok + _ := u.u + return &u.f +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.out b/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.out new file mode 100644 index 0000000..2efc456 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unknown_array_element_type_b.vv:2:6: error: unknown type `abc`. +Did you mean `Aaa`? + 1 | struct Aaa { + 2 | a []abc + | ~~~ + 3 | } + 4 | diff --git a/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.vv b/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.vv new file mode 100644 index 0000000..0fc059f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_array_element_type_b.vv @@ -0,0 +1,9 @@ +struct Aaa { + a []abc +} + +fn main() { + s := Aaa{} + println(s) +} + diff --git a/v_windows/v/vlib/v/checker/tests/unknown_as_type.out b/v_windows/v/vlib/v/checker/tests/unknown_as_type.out new file mode 100644 index 0000000..e520655 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_as_type.out @@ -0,0 +1,8 @@ +vlib/v/checker/tests/unknown_as_type.vv:7:9: error: unknown type `Stringg`. +Did you mean `String`? + 5 | + 6 | fn foo(e Expr) { + 7 | x := e as Stringg + | ~~ + 8 | println(x) + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_as_type.vv b/v_windows/v/vlib/v/checker/tests/unknown_as_type.vv new file mode 100644 index 0000000..66c2a57 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_as_type.vv @@ -0,0 +1,13 @@ +type Expr = Int | String + +struct Int {} +struct String {} + +fn foo(e Expr) { + x := e as Stringg + println(x) +} + +fn main() { + +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.out b/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.out new file mode 100644 index 0000000..b97e13d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.out @@ -0,0 +1,42 @@ +vlib/v/checker/tests/unknown_comptime_expr.vv:5:6: error: `foo` is mut and may have changed since its definition + 3 | fn main() { + 4 | mut foo := 0 + 5 | $if foo == 0 {} + | ~~~ + 6 | + 7 | bar := unknown_at_ct() +vlib/v/checker/tests/unknown_comptime_expr.vv:8:6: error: definition of `bar` is unknown at compile time + 6 | + 7 | bar := unknown_at_ct() + 8 | $if bar == 0 {} + | ~~~ + 9 | } + 10 | +vlib/v/checker/tests/unknown_comptime_expr.vv:13:6: error: undefined ident: `huh` + 11 | fn if_is() { + 12 | s := S1{} + 13 | $if huh.typ is T {} + | ~~~ + 14 | $if s is int {} + 15 | $if s.i is 5 {} +vlib/v/checker/tests/unknown_comptime_expr.vv:14:6: error: invalid `$if` condition: expected a type or a selector expression or an interface check + 12 | s := S1{} + 13 | $if huh.typ is T {} + 14 | $if s is int {} + | ^ + 15 | $if s.i is 5 {} + 16 | $if s.i is T {} +vlib/v/checker/tests/unknown_comptime_expr.vv:15:13: error: invalid `$if` condition: expected a type + 13 | $if huh.typ is T {} + 14 | $if s is int {} + 15 | $if s.i is 5 {} + | ^ + 16 | $if s.i is T {} + 17 | } +vlib/v/checker/tests/unknown_comptime_expr.vv:16:13: error: unknown type `T` + 14 | $if s is int {} + 15 | $if s.i is 5 {} + 16 | $if s.i is T {} + | ^ + 17 | } + 18 | diff --git a/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.vv b/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.vv new file mode 100644 index 0000000..c3734ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_comptime_expr.vv @@ -0,0 +1,22 @@ +fn unknown_at_ct() int { return 0 } + +fn main() { + mut foo := 0 + $if foo == 0 {} + + bar := unknown_at_ct() + $if bar == 0 {} +} + +fn if_is() { + s := S1{} + $if huh.typ is T {} + $if s is int {} + $if s.i is 5 {} + $if s.i is T {} +} + +struct S1 { + i int +} + diff --git a/v_windows/v/vlib/v/checker/tests/unknown_field.out b/v_windows/v/vlib/v/checker/tests/unknown_field.out new file mode 100644 index 0000000..395eedf --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_field.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unknown_field.vv:7:12: error: type `Test` has no field named `sdd` + 5 | fn main() { + 6 | t := Test{} + 7 | println(t.sdd) + | ~~~ + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_field.vv b/v_windows/v/vlib/v/checker/tests/unknown_field.vv new file mode 100644 index 0000000..8cf14a9 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_field.vv @@ -0,0 +1,8 @@ +module main + +struct Test {} + +fn main() { + t := Test{} + println(t.sdd) +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_generic_type.out b/v_windows/v/vlib/v/checker/tests/unknown_generic_type.out new file mode 100644 index 0000000..ea09b5e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_generic_type.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unknown_generic_type.vv:6:13: error: unknown type `Foo` + 4 | + 5 | fn main() { + 6 | x := decode('{"name": "test"}')? + | ~~~~~ + 7 | println(x) + 8 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/checker/tests/unknown_generic_type.vv b/v_windows/v/vlib/v/checker/tests/unknown_generic_type.vv new file mode 100644 index 0000000..1b88eb2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_generic_type.vv @@ -0,0 +1,8 @@ +fn decode(raw_data string) ?T { + return none +} + +fn main() { + x := decode('{"name": "test"}')? + println(x) +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_method.out b/v_windows/v/vlib/v/checker/tests/unknown_method.out new file mode 100644 index 0000000..037d2f3 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_method.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unknown_method.vv:7:12: error: unknown method or field: `Test.sdd` + 5 | fn main() { + 6 | t := Test{} + 7 | println(t.sdd()) + | ~~~~~ + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_method.vv b/v_windows/v/vlib/v/checker/tests/unknown_method.vv new file mode 100644 index 0000000..879b372 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_method.vv @@ -0,0 +1,8 @@ +module main + +struct Test {} + +fn main() { + t := Test{} + println(t.sdd()) +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.out b/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.out new file mode 100644 index 0000000..f80de09 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.out @@ -0,0 +1,22 @@ +vlib/v/checker/tests/unknown_method_suggest_name.vv:13:12: error: unknown type `hash.crc32.Crc33`. +Did you mean `crc32.Crc32`? + 11 | y int + 12 | z int + 13 | ccc crc32.Crc33 + | ~~~~~ + 14 | } + 15 | +vlib/v/checker/tests/unknown_method_suggest_name.vv:27:9: error: unknown method or field: `Point.tranzlate`. +Did you mean `translate`? + 25 | p := Point{1, 2, 3} + 26 | v := Vector{x: 5, y: 5, z: 10} + 27 | z := p.tranzlate(v) + | ~~~~~~~~~~~~ + 28 | println('p: $p') + 29 | println('v: $v') +vlib/v/checker/tests/unknown_method_suggest_name.vv:30:15: error: expression does not return a value + 28 | println('p: $p') + 29 | println('v: $v') + 30 | println('z: $z') + | ^ + 31 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.vv b/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.vv new file mode 100644 index 0000000..52af44c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_method_suggest_name.vv @@ -0,0 +1,31 @@ +import hash.crc32 + +struct Point { + x int + y int + z int +} + +struct Vector { + x int + y int + z int + ccc crc32.Crc33 +} + +fn (p Point) translate(v Vector) Point { + return Point{p.x + v.x, p.y + v.y, p.z + v.z} +} + +fn (p Point) identity() Point { + return Point{1, 1, 1} +} + +fn main() { + p := Point{1, 2, 3} + v := Vector{x: 5, y: 5, z: 10} + z := p.tranzlate(v) + println('p: $p') + println('v: $v') + println('z: $z') +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.out b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.out new file mode 100644 index 0000000..5c6e6cb --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unknown_sizeof_type_err_a.vv:14:34: cgen error: unknown type `T` + 12 | println("size of Abc: ${sizeof(Abc)}") + 13 | println("size of Xyz: ${sizeof(Xyz)}") + 14 | println("size of Test: ${sizeof(T)}") + | ^ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.vv b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.vv new file mode 100644 index 0000000..dd15843 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_a.vv @@ -0,0 +1,15 @@ +struct Abc { + i int +} + +struct Xyz { + f f64 +} + +type Test = Abc | Xyz + +fn main() { + println("size of Abc: ${sizeof(Abc)}") + println("size of Xyz: ${sizeof(Xyz)}") + println("size of Test: ${sizeof(T)}") +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.out b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.out new file mode 100644 index 0000000..7daa009 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unknown_sizeof_type_err_b.vv:14:34: cgen error: unknown type `Zabc` + 12 | println("size of Abc: ${sizeof(Abc)}") + 13 | println("size of Xyz: ${sizeof(Xyz)}") + 14 | println("size of Test: ${sizeof(Zabc)}") + | ~~~~ + 15 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.vv b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.vv new file mode 100644 index 0000000..154f406 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_sizeof_type_err_b.vv @@ -0,0 +1,15 @@ +struct Abc { + i int +} + +struct Xyz { + f f64 +} + +type Test = Abc | Xyz + +fn main() { + println("size of Abc: ${sizeof(Abc)}") + println("size of Xyz: ${sizeof(Xyz)}") + println("size of Test: ${sizeof(Zabc)}") +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.out b/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.out new file mode 100644 index 0000000..98347d6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.out @@ -0,0 +1,15 @@ +vlib/v/checker/tests/unknown_struct_field_suggest_name.vv:11:16: error: type `Points` has no field named `xxxa`. +Did you mean `xxxx`? + 9 | p := Points{[1], [2], [3]} + 10 | println('p: $p') + 11 | for x in p.xxxa { + | ~~~~ + 12 | println('x: $x') + 13 | } +vlib/v/checker/tests/unknown_struct_field_suggest_name.vv:12:19: error: expression does not return a value + 10 | println('p: $p') + 11 | for x in p.xxxa { + 12 | println('x: $x') + | ^ + 13 | } + 14 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.vv b/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.vv new file mode 100644 index 0000000..aebdcea --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_struct_field_suggest_name.vv @@ -0,0 +1,14 @@ + +struct Points { + xxxx []int + yyyy []int + zzzz []int +} + +fn main() { + p := Points{[1], [2], [3]} + println('p: $p') + for x in p.xxxa { + println('x: $x') + } +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_struct_name.out b/v_windows/v/vlib/v/checker/tests/unknown_struct_name.out new file mode 100644 index 0000000..d21f02d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_struct_name.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unknown_struct_name.vv:4:7: error: unknown struct `F` + 2 | + 3 | fn main() { + 4 | _ := F{} + | ~~~ + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_struct_name.vv b/v_windows/v/vlib/v/checker/tests/unknown_struct_name.vv new file mode 100644 index 0000000..52a4330 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_struct_name.vv @@ -0,0 +1,5 @@ +module main + +fn main() { + _ := F{} +} diff --git a/v_windows/v/vlib/v/checker/tests/unknown_var_assign.out b/v_windows/v/vlib/v/checker/tests/unknown_var_assign.out new file mode 100644 index 0000000..6a12a8c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_var_assign.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/unknown_var_assign.vv:2:5: error: undefined ident: `x` (use `:=` to declare a variable) + 1 | fn main() { + 2 | x = 'hello v' + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/unknown_var_assign.vv b/v_windows/v/vlib/v/checker/tests/unknown_var_assign.vv new file mode 100644 index 0000000..d7ed530 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unknown_var_assign.vv @@ -0,0 +1,3 @@ +fn main() { + x = 'hello v' +} diff --git a/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.out b/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.out new file mode 100644 index 0000000..eee14f7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/unnecessary_parenthesis.vv:2:2: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. + 1 | fn main() { + 2 | if (1 == 1) { + | ~~~~~~~~~~~ + 3 | println('yeay') + 4 | } else if (1 == 2) { +vlib/v/checker/tests/unnecessary_parenthesis.vv:4:4: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. + 2 | if (1 == 1) { + 3 | println('yeay') + 4 | } else if (1 == 2) { + | ~~~~~~~~~~~~~~~~ + 5 | println("oh no :'(") + 6 | } else if (1 == 3) { +vlib/v/checker/tests/unnecessary_parenthesis.vv:6:4: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. + 4 | } else if (1 == 2) { + 5 | println("oh no :'(") + 6 | } else if (1 == 3) { + | ~~~~~~~~~~~~~~~~ + 7 | println("what's wrong with physics ????") + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.vv b/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.vv new file mode 100644 index 0000000..c1eb1ae --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unnecessary_parenthesis.vv @@ -0,0 +1,9 @@ +fn main() { + if (1 == 1) { + println('yeay') + } else if (1 == 2) { + println("oh no :'(") + } else if (1 == 3) { + println("what's wrong with physics ????") + } +} diff --git a/v_windows/v/vlib/v/checker/tests/unreachable_code.out b/v_windows/v/vlib/v/checker/tests/unreachable_code.out new file mode 100644 index 0000000..ae0dc92 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unreachable_code.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unreachable_code.vv:3:4: error: unreachable code + 1 | fn foo() int { + 2 | return if 1 == 1 { 1 } else { 2 } + 3 | a := 1 + | ~~ + 4 | println(a) + 5 | } diff --git a/v_windows/v/vlib/v/checker/tests/unreachable_code.vv b/v_windows/v/vlib/v/checker/tests/unreachable_code.vv new file mode 100644 index 0000000..7da480f --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unreachable_code.vv @@ -0,0 +1,8 @@ +fn foo() int { + return if 1 == 1 { 1 } else { 2 } + a := 1 + println(a) +} +fn main() { + foo() +} diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.out b/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.out new file mode 100644 index 0000000..3d791c4 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:3:16: error: function `C.malloc` must be called from an `unsafe` block + 1 | + 2 | fn test_c() { + 3 | mut p := C.malloc(4) + | ~~~~~~~~~ + 4 | s := 'hope' + 5 | C.memcpy(p, s.str, 4) +vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv:5:7: error: function `C.memcpy` must be called from an `unsafe` block + 3 | mut p := C.malloc(4) + 4 | s := 'hope' + 5 | C.memcpy(p, s.str, 4) + | ~~~~~~~~~~~~~~~~~~~ + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv b/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv new file mode 100644 index 0000000..092fe2c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_c_calls_should_be_checked.vv @@ -0,0 +1,6 @@ + +fn test_c() { + mut p := C.malloc(4) + s := 'hope' + C.memcpy(p, s.str, 4) +} diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.out b/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.out new file mode 100644 index 0000000..fae8c95 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unsafe_fixed_array_assign.vv:8:7: error: assignment from one fixed array to another with a pointer element type is prohibited outside of `unsafe` + 6 | mut box := Box { num: 10 } + 7 | a := [&box]! + 8 | mut b := a + | ~~ + 9 | b[0].num = 0 + 10 | println(a) diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.vv b/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.vv new file mode 100644 index 0000000..38defbd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_fixed_array_assign.vv @@ -0,0 +1,10 @@ +struct Box { +mut: + num int +} + +mut box := Box { num: 10 } +a := [&box]! +mut b := a +b[0].num = 0 +println(a) diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.out b/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.out new file mode 100644 index 0000000..9ac0225 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:4:6: error: pointer arithmetic is only allowed in `unsafe` blocks + 2 | mut v := 5 + 3 | mut p := &v + 4 | p++ + | ~~ + 5 | p += 2 + 6 | _ := v +vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:5:7: error: pointer arithmetic is only allowed in `unsafe` blocks + 3 | mut p := &v + 4 | p++ + 5 | p += 2 + | ~~ + 6 | _ := v + 7 | } +vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:11:14: error: pointer arithmetic is only allowed in `unsafe` blocks + 9 | fn test_ptr_infix() { + 10 | v := 4 + 11 | mut q := &v - 1 + | ~~~~~~ + 12 | q = q + 3 + 13 | _ := q +vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv:12:9: error: pointer arithmetic is only allowed in `unsafe` blocks + 10 | v := 4 + 11 | mut q := &v - 1 + 12 | q = q + 3 + | ~~~~~ + 13 | _ := q + 14 | _ := v diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv b/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv new file mode 100644 index 0000000..b83630b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_pointer_arithmetic_should_be_checked.vv @@ -0,0 +1,15 @@ +fn test_ptr_assign() { + mut v := 5 + mut p := &v + p++ + p += 2 + _ := v +} + +fn test_ptr_infix() { + v := 4 + mut q := &v - 1 + q = q + 3 + _ := q + _ := v +} diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_required.out b/v_windows/v/vlib/v/checker/tests/unsafe_required.out new file mode 100644 index 0000000..463a6a2 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_required.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/unsafe_required.vv:8:7: error: method `S1.f` must be called from an `unsafe` block + 6 | fn test_funcs() { + 7 | s := S1{} + 8 | s.f() + | ~~~ + 9 | } + 10 | +vlib/v/checker/tests/unsafe_required.vv:16:7: error: pointer indexing is only allowed in `unsafe` blocks + 14 | _ = b[0] // OK + 15 | c := &b + 16 | _ = c[0] + | ~~~ + 17 | + 18 | v := 4 +vlib/v/checker/tests/unsafe_required.vv:20:10: error: pointer indexing is only allowed in `unsafe` blocks + 18 | v := 4 + 19 | p := &v + 20 | _ = p[0] + | ~~~ + 21 | } diff --git a/v_windows/v/vlib/v/checker/tests/unsafe_required.vv b/v_windows/v/vlib/v/checker/tests/unsafe_required.vv new file mode 100644 index 0000000..e47a90e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unsafe_required.vv @@ -0,0 +1,21 @@ +struct S1 {} + +[unsafe] +fn (s S1) f(){} + +fn test_funcs() { + s := S1{} + s.f() +} + +fn test_ptr_index(mut a []string) { + _ = a[0] // OK + b := ['jo'] + _ = b[0] // OK + c := &b + _ = c[0] + + v := 4 + p := &v + _ = p[0] +} diff --git a/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.out b/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.out new file mode 100644 index 0000000..146e7e6 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unwrapped_optional_infix.vv:5:9: error: unwrapped optional cannot be used in an infix expression + 3 | } + 4 | + 5 | println(test() == "") + | ~~~~~~~~~~~~ + diff --git a/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.vv b/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.vv new file mode 100644 index 0000000..432b0cc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/unwrapped_optional_infix.vv @@ -0,0 +1,5 @@ +fn test() ?string { + return "" +} + +println(test() == "") diff --git a/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.out b/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.out new file mode 100644 index 0000000..9cffcba --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/use_deprecated_function_warning.vv:12:2: error: function `xyz` has been deprecated + 10 | + 11 | fn main() { + 12 | xyz() + | ~~~~~ + 13 | abc() + 14 | } +vlib/v/checker/tests/use_deprecated_function_warning.vv:13:2: error: function `abc` has been deprecated; use foo2 instead + 11 | fn main() { + 12 | xyz() + 13 | abc() + | ~~~~~ + 14 | } + 15 | +vlib/v/checker/tests/use_deprecated_function_warning.vv:23:4: error: method `S1.m` has been deprecated; use bar instead + 21 | fn method() { + 22 | s := S1{} + 23 | s.m() + | ~~~ + 24 | } diff --git a/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.vv b/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.vv new file mode 100644 index 0000000..d5f95ef --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/use_deprecated_function_warning.vv @@ -0,0 +1,24 @@ +[deprecated] +fn xyz() { + println('hi') +} + +[deprecated: 'use foo2 instead'] +fn abc() { + println('hi') +} + +fn main() { + xyz() + abc() +} + +struct S1 {} + +[deprecated: 'use bar instead'] +fn (s S1) m() {} + +fn method() { + s := S1{} + s.m() +} diff --git a/v_windows/v/vlib/v/checker/tests/var_duplicate_const.out b/v_windows/v/vlib/v/checker/tests/var_duplicate_const.out new file mode 100644 index 0000000..b7b4633 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_duplicate_const.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/var_duplicate_const.vv:4:5: error: duplicate of a const name `size` + 2 | + 3 | fn main() { + 4 | size := 11 + | ~~~~ + 5 | println(main.size) + 6 | } diff --git a/v_windows/v/vlib/v/checker/tests/var_duplicate_const.vv b/v_windows/v/vlib/v/checker/tests/var_duplicate_const.vv new file mode 100644 index 0000000..4275e7b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_duplicate_const.vv @@ -0,0 +1,6 @@ +const size = 22 + +fn main() { + size := 11 + println(main.size) +} diff --git a/v_windows/v/vlib/v/checker/tests/var_eval_not_used.out b/v_windows/v/vlib/v/checker/tests/var_eval_not_used.out new file mode 100644 index 0000000..2cb3593 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_eval_not_used.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/var_eval_not_used.vv:6:2: error: `c` evaluated but not used + 4 | + 5 | fn main() { + 6 | c + | ^ + 7 | } diff --git a/v_windows/v/vlib/v/checker/tests/var_eval_not_used.vv b/v_windows/v/vlib/v/checker/tests/var_eval_not_used.vv new file mode 100644 index 0000000..57642a1 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_eval_not_used.vv @@ -0,0 +1,7 @@ +const ( + c = 1 +) + +fn main() { + c +} diff --git a/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.out b/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.out new file mode 100644 index 0000000..89c4afd --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/var_eval_not_used_scope.vv:7:3: error: `c` evaluated but not used + 5 | fn main() { + 6 | { + 7 | c + | ^ + 8 | } + 9 | } diff --git a/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.vv b/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.vv new file mode 100644 index 0000000..298e35b --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_eval_not_used_scope.vv @@ -0,0 +1,9 @@ +const ( + c = 1 +) + +fn main() { + { + c + } +} diff --git a/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.out b/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.out new file mode 100644 index 0000000..b3ea361 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/var_used_before_declaration.vv:2:13: error: undefined variable `x` (used before declaration) + 1 | fn main() { + 2 | println(x) + | ^ + 3 | x := 'hello v' + 4 | _ = x diff --git a/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.vv b/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.vv new file mode 100644 index 0000000..1a2b63d --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/var_used_before_declaration.vv @@ -0,0 +1,5 @@ +fn main() { + println(x) + x := 'hello v' + _ = x +} diff --git a/v_windows/v/vlib/v/checker/tests/void_fn_as_value.out b/v_windows/v/vlib/v/checker/tests/void_fn_as_value.out new file mode 100644 index 0000000..9898c19 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_fn_as_value.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/void_fn_as_value.vv:5:7: error: unknown function: x + 3 | fn main() { + 4 | mut a := 'aa' + 5 | a += x('a','b') + | ~~~~~~~~~~ + 6 | mut b := 'abcdef' + 7 | _ = b diff --git a/v_windows/v/vlib/v/checker/tests/void_fn_as_value.vv b/v_windows/v/vlib/v/checker/tests/void_fn_as_value.vv new file mode 100644 index 0000000..012019e --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_fn_as_value.vv @@ -0,0 +1,9 @@ +module main + +fn main() { + mut a := 'aa' + a += x('a','b') + mut b := 'abcdef' + _ = b + _ = a +} diff --git a/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.out b/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.out new file mode 100644 index 0000000..b04d39c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/void_function_assign_to_string.vv:6:4: error: assignment mismatch: 1 variable(s) but `x()` returns 0 value(s) + 4 | fn main(){ + 5 | mut a := '' + 6 | a = x(1,2) // hello + | ^ + 7 | eprintln('a: $a') + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.vv b/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.vv new file mode 100644 index 0000000..fddd5fc --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_function_assign_to_string.vv @@ -0,0 +1,8 @@ +fn x(x int,y int) { + +} +fn main(){ + mut a := '' + a = x(1,2) // hello + eprintln('a: $a') +} diff --git a/v_windows/v/vlib/v/checker/tests/void_optional_err.out b/v_windows/v/vlib/v/checker/tests/void_optional_err.out new file mode 100644 index 0000000..c9efcb7 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_optional_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/void_optional_err.vv:1:16: error: use `?` instead of `?void` + 1 | fn ret_void() ?void { + | ~~~~ + 2 | return error('error') + 3 | } diff --git a/v_windows/v/vlib/v/checker/tests/void_optional_err.vv b/v_windows/v/vlib/v/checker/tests/void_optional_err.vv new file mode 100644 index 0000000..136e6e5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/void_optional_err.vv @@ -0,0 +1,7 @@ +fn ret_void() ?void { + return error('error') +} + +fn main() { + _ = ret_void() or { panic('$err') } +} diff --git a/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.out b/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.out new file mode 100644 index 0000000..141d7b5 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/vweb_routing_checks.vv:20:1: error: mismatched parameters count between vweb method `App.bar` (1) and route attribute ['/bar'] (0) + 18 | // segfault because path taks 0 vars and fcn takes 1 arg + 19 | ['/bar'] + 20 | pub fn (mut app App) bar(a string) vweb.Result { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 21 | return app.html('works') + 22 | } +vlib/v/checker/tests/vweb_routing_checks.vv:26:1: error: mismatched parameters count between vweb method `App.cow` (0) and route attribute ['/cow/:low'] (1) + 24 | // no segfault, but it shouldnt compile + 25 | ['/cow/:low'] + 26 | pub fn (mut app App) cow() vweb.Result { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 27 | return app.html('works') + 28 | } diff --git a/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.vv b/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.vv new file mode 100644 index 0000000..6954e83 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/vweb_routing_checks.vv @@ -0,0 +1,44 @@ +import vweb + +struct App { + vweb.Context +} + +pub fn (mut app App) no_attributes(a string) vweb.Result { + return app.text('ok') +} + +// works fine, as long as fcn gets 1 arg and route takes 1 var +['/foo/:bar'] +pub fn (mut app App) foo(a string) vweb.Result { + eprintln('foo') + return app.html('works') +} + +// segfault because path taks 0 vars and fcn takes 1 arg +['/bar'] +pub fn (mut app App) bar(a string) vweb.Result { + return app.html('works') +} + +// no segfault, but it shouldnt compile +['/cow/:low'] +pub fn (mut app App) cow() vweb.Result { + return app.html('works') +} + +/* + +pub fn (app App) before_request() { + // +} +*/ + +pub fn (mut app App) index() { + app.html('hello') +} + +fn main() { + port := 8181 + vweb.run(&App{}, port) +} diff --git a/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.out b/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.out new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.out @@ -0,0 +1 @@ + diff --git a/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.vv b/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.vv new file mode 100644 index 0000000..13cdbec --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/vweb_tmpl_used_var.vv @@ -0,0 +1,14 @@ +import vweb + +struct App { + vweb.Context +} + +pub fn (mut app App) index() vweb.Result { + test := 'test' + return $vweb.html() +} + +fn main() { + vweb.run(&App{}, 8181) +} diff --git a/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.out b/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.out new file mode 100644 index 0000000..f570847 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/warnings_for_string_c2v_calls.vv:8:7: error: to convert a C string buffer pointer to a V string, use x.vstring() instead of string(x) + 6 | p[2] = `z` + 7 | } + 8 | x := string(p) + | ~~~~~~~~~ + 9 | eprintln('x: $x') + 10 | eprintln('x.len: $x.len') diff --git a/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.vv b/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.vv new file mode 100644 index 0000000..7f4dade --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/warnings_for_string_c2v_calls.vv @@ -0,0 +1,11 @@ +fn main() { + mut p := vcalloc(20) + unsafe { + p[0] = `A` + p[1] = `B` + p[2] = `z` + } + x := string(p) + eprintln('x: $x') + eprintln('x.len: $x.len') +} diff --git a/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.out b/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.out new file mode 100644 index 0000000..af8d737 --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/wrong_propagate_ret_type.vv:6:17: error: to propagate the optional call, `opt_call` must return an optional + 4 | + 5 | fn opt_call() int { + 6 | a := ret_none()? + | ^ + 7 | return a + 8 | } diff --git a/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.vv b/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.vv new file mode 100644 index 0000000..82ef06c --- /dev/null +++ b/v_windows/v/vlib/v/checker/tests/wrong_propagate_ret_type.vv @@ -0,0 +1,8 @@ +fn ret_none() ?int { + return none +} + +fn opt_call() int { + a := ret_none()? + return a +} diff --git a/v_windows/v/vlib/v/compiler_errors_test.v b/v_windows/v/vlib/v/compiler_errors_test.v new file mode 100644 index 0000000..1c502b2 --- /dev/null +++ b/v_windows/v/vlib/v/compiler_errors_test.v @@ -0,0 +1,337 @@ +import os +import rand +import term +import v.util.diff +import v.util.vtest +import time +import sync +import runtime +import benchmark + +const skip_files = [ + 'non_existing.vv', // minimize commit diff churn, do not remove +] + +const skip_on_ubuntu_musl = [ + 'vlib/v/checker/tests/vweb_tmpl_used_var.vv', +] + +const turn_off_vcolors = os.setenv('VCOLORS', 'never', true) + +const should_autofix = os.getenv('VAUTOFIX') != '' + +const github_job = os.getenv('GITHUB_JOB') + +struct TaskDescription { + vexe string + dir string + voptions string + result_extension string + path string +mut: + is_error bool + is_skipped bool + is_module bool + expected string + expected_out_path string + found___ string + took time.Duration + cli_cmd string +} + +struct Tasks { + vexe string + parallel_jobs int // 0 is using VJOBS, anything else is an override + label string +mut: + show_cmd bool + all []TaskDescription +} + +fn test_all() { + vexe := os.getenv('VEXE') + vroot := os.dir(vexe) + os.chdir(vroot) or {} + checker_dir := 'vlib/v/checker/tests' + parser_dir := 'vlib/v/parser/tests' + scanner_dir := 'vlib/v/scanner/tests' + module_dir := '$checker_dir/modules' + global_dir := '$checker_dir/globals' + global_run_dir := '$checker_dir/globals_run' + run_dir := '$checker_dir/run' + skip_unused_dir := 'vlib/v/tests/skip_unused' + // + checker_tests := get_tests_in_dir(checker_dir, false) + parser_tests := get_tests_in_dir(parser_dir, false) + scanner_tests := get_tests_in_dir(scanner_dir, false) + global_tests := get_tests_in_dir(global_dir, false) + global_run_tests := get_tests_in_dir(global_run_dir, false) + module_tests := get_tests_in_dir(module_dir, true) + run_tests := get_tests_in_dir(run_dir, false) + skip_unused_dir_tests := get_tests_in_dir(skip_unused_dir, false) + // -prod is used for the parser and checker tests, so that warns are errors + mut tasks := Tasks{ + vexe: vexe + label: 'all tests' + } + tasks.add('', parser_dir, '-prod', '.out', parser_tests, false) + tasks.add('', checker_dir, '-prod', '.out', checker_tests, false) + tasks.add('', scanner_dir, '-prod', '.out', scanner_tests, false) + tasks.add('', checker_dir, '-enable-globals run', '.run.out', ['globals_error.vv'], + false) + tasks.add('', global_run_dir, '-enable-globals run', '.run.out', global_run_tests, + false) + tasks.add('', global_dir, '-enable-globals', '.out', global_tests, false) + tasks.add('', module_dir, '-prod run', '.out', module_tests, true) + tasks.add('', run_dir, 'run', '.run.out', run_tests, false) + tasks.run() + // + if os.user_os() == 'linux' { + mut skip_unused_tasks := Tasks{ + vexe: vexe + parallel_jobs: 1 + label: '-skip-unused tests' + } + skip_unused_tasks.add('', skip_unused_dir, 'run', '.run.out', skip_unused_dir_tests, + false) + skip_unused_tasks.add('', skip_unused_dir, '-d no_backtrace -skip-unused run', + '.skip_unused.run.out', skip_unused_dir_tests, false) + skip_unused_tasks.run() + } + // + if github_job == 'ubuntu-tcc' { + // This is done with tcc only, because the error output is compiler specific. + // NB: the tasks should be run serially, since they depend on + // setting and using environment variables. + mut cte_tasks := Tasks{ + vexe: vexe + parallel_jobs: 1 + label: 'comptime env tests' + } + cte_dir := '$checker_dir/comptime_env' + files := get_tests_in_dir(cte_dir, false) + cte_tasks.add('', cte_dir, '-no-retry-compilation run', '.run.out', files, false) + cte_tasks.add('VAR=/usr/include $vexe', cte_dir, '-no-retry-compilation run', + '.var.run.out', ['using_comptime_env.vv'], false) + cte_tasks.add('VAR=/opt/invalid/path $vexe', cte_dir, '-no-retry-compilation run', + '.var_invalid.run.out', ['using_comptime_env.vv'], false) + cte_tasks.run() + } + mut ct_tasks := Tasks{ + vexe: vexe + parallel_jobs: 1 + label: 'comptime define tests' + } + ct_tasks.add_checked_run('-d mysymbol run', '.mysymbol.run.out', [ + 'custom_comptime_define_error.vv', + ]) + ct_tasks.add_checked_run('-d mydebug run', '.mydebug.run.out', [ + 'custom_comptime_define_if_flag.vv', + ]) + ct_tasks.add_checked_run('-d nodebug run', '.nodebug.run.out', [ + 'custom_comptime_define_if_flag.vv', + ]) + ct_tasks.add_checked_run('run', '.run.out', ['custom_comptime_define_if_debug.vv']) + ct_tasks.add_checked_run('-g run', '.g.run.out', ['custom_comptime_define_if_debug.vv']) + ct_tasks.add_checked_run('-cg run', '.cg.run.out', ['custom_comptime_define_if_debug.vv']) + ct_tasks.add_checked_run('-d debug run', '.debug.run.out', ['custom_comptime_define_if_debug.vv']) + ct_tasks.add_checked_run('-d debug -d bar run', '.debug.bar.run.out', [ + 'custom_comptime_define_if_debug.vv', + ]) + ct_tasks.run() +} + +fn (mut tasks Tasks) add_checked_run(voptions string, result_extension string, tests []string) { + checker_dir := 'vlib/v/checker/tests' + tasks.add('', checker_dir, voptions, result_extension, tests, false) +} + +fn (mut tasks Tasks) add(custom_vexe string, dir string, voptions string, result_extension string, tests []string, is_module bool) { + mut vexe := tasks.vexe + if custom_vexe != '' { + vexe = custom_vexe + } + paths := vtest.filter_vtest_only(tests, basepath: dir) + for path in paths { + tasks.all << TaskDescription{ + vexe: vexe + dir: dir + voptions: voptions + result_extension: result_extension + path: path + is_module: is_module + } + } +} + +fn bstep_message(mut bench benchmark.Benchmark, label string, msg string, sduration time.Duration) string { + return bench.step_message_with_label_and_duration(label, msg, sduration) +} + +// process an array of tasks in parallel, using no more than vjobs worker threads +fn (mut tasks Tasks) run() { + tasks.show_cmd = os.getenv('VTEST_SHOW_CMD') != '' + vjobs := if tasks.parallel_jobs > 0 { tasks.parallel_jobs } else { runtime.nr_jobs() } + mut bench := benchmark.new_benchmark() + bench.set_total_expected_steps(tasks.all.len) + mut work := sync.new_channel(u32(tasks.all.len)) + mut results := sync.new_channel(u32(tasks.all.len)) + mut m_skip_files := skip_files.clone() + if os.getenv('V_CI_UBUNTU_MUSL').len > 0 { + m_skip_files << skip_on_ubuntu_musl + } + $if noskip ? { + m_skip_files = [] + } + $if tinyc { + // NB: tcc does not support __has_include, so the detection mechanism + // used for the other compilers does not work. It still provides a + // cleaner error message, than a generic C error, but without the explanation. + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + } + $if msvc { + m_skip_files << 'vlib/v/checker/tests/asm_alias_does_not_exist.vv' + m_skip_files << 'vlib/v/checker/tests/asm_immutable_err.vv' + // TODO: investigate why MSVC regressed + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' + m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + } + for i in 0 .. tasks.all.len { + if tasks.all[i].path in m_skip_files { + tasks.all[i].is_skipped = true + } + unsafe { work.push(&tasks.all[i]) } + } + work.close() + for _ in 0 .. vjobs { + go work_processor(mut work, mut results) + } + if github_job == '' { + println('') + } + mut line_can_be_erased := true + mut total_errors := 0 + for _ in 0 .. tasks.all.len { + mut task := TaskDescription{} + results.pop(&task) + bench.step() + if task.is_skipped { + bench.skip() + eprintln(bstep_message(mut bench, benchmark.b_skip, task.path, task.took)) + line_can_be_erased = false + continue + } + if task.is_error { + total_errors++ + bench.fail() + eprintln(bstep_message(mut bench, benchmark.b_fail, task.path, task.took)) + println('============') + println('failed cmd: $task.cli_cmd') + println('expected_out_path: $task.expected_out_path') + println('============') + println('expected:') + println(task.expected) + println('============') + println('found:') + println(task.found___) + println('============\n') + diff_content(task.expected, task.found___) + line_can_be_erased = false + } else { + bench.ok() + if tasks.show_cmd { + eprintln(bstep_message(mut bench, benchmark.b_ok, '$task.cli_cmd $task.path', + task.took)) + } else { + if github_job == '' { + // local mode: + if line_can_be_erased { + term.clear_previous_line() + } + println(bstep_message(mut bench, benchmark.b_ok, task.path, task.took)) + } + } + line_can_be_erased = true + } + } + bench.stop() + eprintln(term.h_divider('-')) + eprintln(bench.total_message(tasks.label)) + if total_errors != 0 { + exit(1) + } +} + +// a single worker thread spends its time getting work from the `work` channel, +// processing the task, and then putting the task in the `results` channel +fn work_processor(mut work sync.Channel, mut results sync.Channel) { + for { + mut task := TaskDescription{} + if !work.pop(&task) { + break + } + sw := time.new_stopwatch() + task.execute() + task.took = sw.elapsed() + results.push(&task) + } +} + +// actual processing; NB: no output is done here at all +fn (mut task TaskDescription) execute() { + if task.is_skipped { + return + } + program := task.path + cli_cmd := '$task.vexe $task.voptions $program' + res := os.execute(cli_cmd) + expected_out_path := program.replace('.vv', '') + task.result_extension + task.expected_out_path = expected_out_path + task.cli_cmd = cli_cmd + if should_autofix && !os.exists(expected_out_path) { + os.write_file(expected_out_path, '') or { panic(err) } + } + mut expected := os.read_file(expected_out_path) or { panic(err) } + task.expected = clean_line_endings(expected) + task.found___ = clean_line_endings(res.output) + $if windows { + if task.is_module { + task.found___ = task.found___.replace_once('\\', '/') + } + } + if task.expected != task.found___ { + task.is_error = true + if should_autofix { + os.write_file(expected_out_path, res.output) or { panic(err) } + } + } +} + +fn clean_line_endings(s string) string { + mut res := s.trim_space() + res = res.replace(' \n', '\n') + res = res.replace(' \r\n', '\n') + res = res.replace('\r\n', '\n') + res = res.trim('\n') + return res +} + +fn diff_content(s1 string, s2 string) { + diff_cmd := diff.find_working_diff_command() or { return } + println(term.bold(term.yellow('diff: '))) + println(diff.color_compare_strings(diff_cmd, rand.ulid(), s1, s2)) + println('============\n') +} + +fn get_tests_in_dir(dir string, is_module bool) []string { + files := os.ls(dir) or { panic(err) } + mut tests := files.clone() + if !is_module { + tests = files.filter(it.ends_with('.vv')) + } else { + tests = files.filter(!it.ends_with('.out')) + } + tests.sort() + return tests +} diff --git a/v_windows/v/vlib/v/depgraph/depgraph.v b/v_windows/v/vlib/v/depgraph/depgraph.v new file mode 100644 index 0000000..cc6a8eb --- /dev/null +++ b/v_windows/v/vlib/v/depgraph/depgraph.v @@ -0,0 +1,218 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// Directed acyclic graph +// this implementation is specifically suited to ordering dependencies +module depgraph + +import v.dotgraph + +struct DepGraphNode { +pub mut: + name string + deps []string +} + +struct DepGraph { +pub mut: + acyclic bool + nodes []DepGraphNode +} + +struct OrderedDepMap { +mut: + keys []string + data map[string][]string +} + +pub fn (mut o OrderedDepMap) set(name string, deps []string) { + if name !in o.data { + o.keys << name + } + o.data[name] = deps +} + +pub fn (mut o OrderedDepMap) add(name string, deps []string) { + mut d := o.get(name) + for dep in deps { + if dep !in d { + d << dep + } else { + } + } + o.set(name, d) +} + +pub fn (o &OrderedDepMap) get(name string) []string { + res := o.data[name] or { []string{} } + return res +} + +pub fn (mut o OrderedDepMap) delete(name string) { + if name !in o.data { + panic('delete: no such key: $name') + } + for i, _ in o.keys { + if o.keys[i] == name { + o.keys.delete(i) + break + } + } + o.data.delete(name) +} + +pub fn (mut o OrderedDepMap) apply_diff(name string, deps []string) { + mut diff := []string{} + deps_of_name := o.get(name) + for dep in deps_of_name { + if dep !in deps { + diff << dep + } + } + o.set(name, diff) +} + +pub fn (o &OrderedDepMap) size() int { + return o.data.len +} + +pub fn new_dep_graph() &DepGraph { + return &DepGraph{ + acyclic: true + nodes: []DepGraphNode{cap: 1024} + } +} + +pub fn (mut graph DepGraph) add(mod string, deps []string) { + new_node := DepGraphNode{ + name: mod + deps: deps.clone() + } + graph.nodes << new_node +} + +pub fn (graph &DepGraph) resolve() &DepGraph { + mut node_names := OrderedDepMap{} + mut node_deps := OrderedDepMap{} + for node in graph.nodes { + node_names.add(node.name, node.deps) + node_deps.add(node.name, node.deps) + } + mut iterations := 0 + mut resolved := new_dep_graph() + for node_deps.size() != 0 { + iterations++ + mut ready_set := []string{} + for name in node_deps.keys { + deps := node_deps.get(name) + if deps.len == 0 { + ready_set << name + } + } + if ready_set.len == 0 { + mut g := new_dep_graph() + g.acyclic = false + for name in node_deps.keys { + g.add(name, node_names.get(name)) + } + return g + } + for name in ready_set { + node_deps.delete(name) + resolved_deps := node_names.get(name) + resolved.add(name, resolved_deps) + } + for name in node_deps.keys { + node_deps.apply_diff(name, ready_set) + } + } + return resolved +} + +pub fn (graph &DepGraph) last_node() DepGraphNode { + return graph.nodes[graph.nodes.len - 1] +} + +pub fn (graph &DepGraph) display() string { + mut out := []string{} + for node in graph.nodes { + for dep in node.deps { + out << ' * $node.name -> $dep' + } + } + return out.join('\n') +} + +struct NodeNames { +mut: + is_cycle map[string]bool + names map[string][]string +} + +pub fn (graph &DepGraph) display_cycles() string { + mut seen := false + mut out := []string{} + mut nn := NodeNames{} + for node in graph.nodes { + nn.names[node.name] = node.deps + } + for k, _ in nn.names { + mut cycle_names := []string{} + if k in nn.is_cycle { + continue + } + seen, cycle_names = nn.is_part_of_cycle(k, cycle_names) + if seen { + out << ' * ' + cycle_names.join(' -> ') + nn.is_cycle = map[string]bool{} + } + } + return out.join('\n') +} + +fn (mut nn NodeNames) is_part_of_cycle(name string, already_seen []string) (bool, []string) { + mut seen := false + mut new_already_seen := already_seen.clone() + if name in nn.is_cycle { + return nn.is_cycle[name], new_already_seen + } + if name in already_seen { + new_already_seen << name + nn.is_cycle[name] = true + return true, new_already_seen + } + new_already_seen << name + deps := nn.names[name] + if deps.len == 0 { + nn.is_cycle[name] = false + return false, new_already_seen + } + for d in deps { + mut d_already_seen := new_already_seen.clone() + seen, d_already_seen = nn.is_part_of_cycle(d, d_already_seen) + if seen { + new_already_seen = d_already_seen.clone() + nn.is_cycle[name] = true + return true, new_already_seen + } + } + nn.is_cycle[name] = false + return false, new_already_seen +} + +pub fn show(graph &DepGraph, path string) { + mut dg := dotgraph.new('ModGraph', 'ModGraph for $path', 'blue') + mbuiltin := 'builtin' + for node in graph.nodes { + is_main := node.name == 'main' + dg.new_node(node.name, should_highlight: is_main) + mut deps := node.deps.clone() + if node.name != mbuiltin && mbuiltin !in deps { + deps << mbuiltin + } + for dep in deps { + dg.new_edge(node.name, dep, should_highlight: is_main) + } + } + dg.finish() +} diff --git a/v_windows/v/vlib/v/doc/comment.v b/v_windows/v/vlib/v/doc/comment.v new file mode 100644 index 0000000..468f46e --- /dev/null +++ b/v_windows/v/vlib/v/doc/comment.v @@ -0,0 +1,25 @@ +module doc + +import v.token + +const ( + example_pattern = '\x01 Example: ' +) + +pub struct DocComment { +pub mut: + text string // Raw text content of the comment, excluding the comment token chars ('//, /*, */') + is_multi bool // Is a block / multi-line comment + pos token.Position +} + +// is_example returns true if the contents of this comment is a doc example. +// The current convention is '// Example: ' +pub fn (dc DocComment) is_example() bool { + return dc.text.starts_with(doc.example_pattern) +} + +// example returns the content of the example body +pub fn (dc DocComment) example() string { + return dc.text.all_after(doc.example_pattern) +} diff --git a/v_windows/v/vlib/v/doc/doc.v b/v_windows/v/vlib/v/doc/doc.v new file mode 100644 index 0000000..3149439 --- /dev/null +++ b/v_windows/v/vlib/v/doc/doc.v @@ -0,0 +1,525 @@ +module doc + +import os +import time +import v.ast +import v.checker +import v.fmt +import v.parser +import v.pref +import v.scanner +import v.token + +// SymbolKind categorizes the symbols it documents. +// The names are intentionally not in order as a guide when sorting the nodes. +pub enum SymbolKind { + none_ + const_group + constant + variable + function + method + interface_ + typedef + enum_ + enum_field + struct_ + struct_field +} + +pub enum Platform { + auto + ios + macos + linux + windows + freebsd + openbsd + netbsd + dragonfly + js // for interoperability in prefs.OS + android + solaris + serenity + vinix + haiku + raw + cross // TODO: add functionality for v doc -os cross whenever possible +} + +// copy of pref.os_from_string +pub fn platform_from_string(platform_str string) ?Platform { + match platform_str { + 'all', 'cross' { return .cross } + 'linux' { return .linux } + 'windows' { return .windows } + 'ios' { return .ios } + 'macos' { return .macos } + 'freebsd' { return .freebsd } + 'openbsd' { return .openbsd } + 'netbsd' { return .netbsd } + 'dragonfly' { return .dragonfly } + 'js' { return .js } + 'solaris' { return .solaris } + 'serenity' { return .serenity } + 'vinix' { return .vinix } + 'android' { return .android } + 'haiku' { return .haiku } + 'nix' { return .linux } + '' { return .auto } + else { return error('vdoc: invalid platform `$platform_str`') } + } +} + +pub fn platform_from_filename(filename string) Platform { + suffix := filename.all_after_last('_').all_before('.c.v') + mut platform := platform_from_string(suffix) or { Platform.cross } + if platform == .auto { + platform = .cross + } + return platform +} + +pub fn (sk SymbolKind) str() string { + return match sk { + .const_group { 'Constants' } + .function, .method { 'fn' } + .interface_ { 'interface' } + .typedef { 'type' } + .enum_ { 'enum' } + .struct_ { 'struct' } + else { '' } + } +} + +pub struct Doc { +pub mut: + prefs &pref.Preferences = new_vdoc_preferences() + base_path string + table &ast.Table = ast.new_table() + checker checker.Checker = checker.Checker{ + table: 0 + pref: 0 + } + fmt fmt.Fmt + filename string + pos int + pub_only bool = true + with_comments bool = true + with_pos bool + with_head bool = true + is_vlib bool + time_generated time.Time + head DocNode + contents map[string]DocNode + scoped_contents map[string]DocNode + parent_mod_name string + orig_mod_name string + extract_vars bool + filter_symbol_names []string + common_symbols []string + platform Platform +} + +pub struct DocNode { +pub mut: + name string + content string + comments []DocComment + pos token.Position + file_path string + kind SymbolKind + tags []string + parent_name string + return_type string + children []DocNode + attrs map[string]string [json: attributes] + from_scope bool + is_pub bool [json: public] + platform Platform +} + +// new_vdoc_preferences creates a new instance of pref.Preferences tailored for v.doc. +pub fn new_vdoc_preferences() &pref.Preferences { + // vdoc should be able to parse as much user code as possible + // so its preferences should be permissive: + mut pref := &pref.Preferences{ + enable_globals: true + is_fmt: true + } + pref.fill_with_defaults() + return pref +} + +// new creates a new instance of a `Doc` struct. +pub fn new(input_path string) Doc { + mut d := Doc{ + base_path: os.real_path(input_path) + table: ast.new_table() + head: DocNode{} + contents: map[string]DocNode{} + time_generated: time.now() + } + d.fmt = fmt.Fmt{ + pref: d.prefs + indent: 0 + is_debug: false + table: d.table + } + d.checker = checker.new_checker(d.table, d.prefs) + return d +} + +// stmt reads the data of an `ast.Stmt` node and returns a `DocNode`. +// An option error is thrown if the symbol is not exposed to the public +// (when `pub_only` is enabled) or the content's of the AST node is empty. +pub fn (mut d Doc) stmt(stmt ast.Stmt, filename string) ?DocNode { + mut name := d.stmt_name(stmt) + if name in d.common_symbols { + return error('already documented') + } + if name.starts_with(d.orig_mod_name + '.') { + name = name.all_after(d.orig_mod_name + '.') + } + mut node := DocNode{ + name: name + content: d.stmt_signature(stmt) + pos: stmt.pos + file_path: os.join_path(d.base_path, filename) + is_pub: d.stmt_pub(stmt) + platform: platform_from_filename(filename) + } + if (!node.is_pub && d.pub_only) || stmt is ast.GlobalDecl { + return error('symbol $node.name not public') + } + if node.name.starts_with(d.orig_mod_name + '.') { + node.name = node.name.all_after(d.orig_mod_name + '.') + } + if node.name.len == 0 && node.comments.len == 0 && node.content.len == 0 { + return error('empty stmt') + } + match stmt { + ast.ConstDecl { + node.kind = .const_group + node.parent_name = 'Constants' + if d.extract_vars { + for field in stmt.fields { + ret_type := if field.typ == 0 { + d.expr_typ_to_string(field.expr) + } else { + d.type_to_str(field.typ) + } + node.children << DocNode{ + name: field.name.all_after(d.orig_mod_name + '.') + kind: .constant + pos: field.pos + return_type: ret_type + } + } + } + } + ast.EnumDecl { + node.kind = .enum_ + if d.extract_vars { + for field in stmt.fields { + ret_type := if field.has_expr { d.expr_typ_to_string(field.expr) } else { 'int' } + node.children << DocNode{ + name: field.name + kind: .enum_field + parent_name: node.name + pos: field.pos + return_type: ret_type + } + } + } + } + ast.InterfaceDecl { + node.kind = .interface_ + } + ast.StructDecl { + node.kind = .struct_ + if d.extract_vars { + for field in stmt.fields { + ret_type := if field.typ == 0 && field.has_default_expr { + d.expr_typ_to_string(field.default_expr) + } else { + d.type_to_str(field.typ) + } + node.children << DocNode{ + name: field.name + kind: .struct_field + parent_name: node.name + pos: field.pos + return_type: ret_type + } + } + } + } + ast.TypeDecl { + node.kind = .typedef + } + ast.FnDecl { + if stmt.is_deprecated { + node.tags << 'deprecated' + } + if stmt.is_unsafe { + node.tags << 'unsafe' + } + node.kind = .function + node.return_type = d.type_to_str(stmt.return_type) + if stmt.receiver.typ !in [0, 1] { + method_parent := d.type_to_str(stmt.receiver.typ) + node.kind = .method + node.parent_name = method_parent + } + if d.extract_vars { + for param in stmt.params { + node.children << DocNode{ + name: param.name + kind: .variable + parent_name: node.name + pos: param.pos + attrs: { + 'mut': param.is_mut.str() + } + return_type: d.type_to_str(param.typ) + } + } + } + } + else { + return error('invalid stmt type to document') + } + } + included := node.name in d.filter_symbol_names || node.parent_name in d.filter_symbol_names + if d.filter_symbol_names.len != 0 && !included { + return error('not included in the list of symbol names') + } + if d.prefs.os == .all { + d.common_symbols << node.name + } + return node +} + +// file_ast reads the contents of `ast.File` and returns a map of `DocNode`s. +pub fn (mut d Doc) file_ast(file_ast ast.File) map[string]DocNode { + mut contents := map[string]DocNode{} + stmts := file_ast.stmts + d.fmt.file = file_ast + d.fmt.set_current_module_name(d.orig_mod_name) + d.fmt.process_file_imports(file_ast) + mut last_import_stmt_idx := 0 + for sidx, stmt in stmts { + if stmt is ast.Import { + last_import_stmt_idx = sidx + } + } + mut preceeding_comments := []DocComment{} + // mut imports_section := true + for sidx, stmt in stmts { + if stmt is ast.ExprStmt { + // Collect comments + if stmt.expr is ast.Comment { + preceeding_comments << ast_comment_to_doc_comment(stmt.expr) + continue + } + } + // TODO: Fetch head comment once + if stmt is ast.Module { + if !d.with_head { + continue + } + // the previous comments were probably a copyright/license one + module_comment := merge_doc_comments(preceeding_comments) + preceeding_comments = [] + if !d.is_vlib && !module_comment.starts_with('Copyright (c)') { + if module_comment == '' { + continue + } + d.head.comments << preceeding_comments + } + continue + } + if last_import_stmt_idx > 0 && sidx == last_import_stmt_idx { + // the accumulated comments were interspersed before/between the imports; + // just add them all to the module comments: + if d.with_head { + d.head.comments << preceeding_comments + } + preceeding_comments = [] + // imports_section = false + } + if stmt is ast.Import { + continue + } + mut node := d.stmt(stmt, os.base(file_ast.path)) or { + preceeding_comments = [] + continue + } + if node.parent_name !in contents { + parent_node_kind := if node.parent_name == 'Constants' { + SymbolKind.const_group + } else { + SymbolKind.typedef + } + contents[node.parent_name] = DocNode{ + name: node.parent_name + kind: parent_node_kind + } + } + if d.with_comments && (preceeding_comments.len > 0) { + node.comments << preceeding_comments + } + preceeding_comments = [] + if node.parent_name.len > 0 { + parent_name := node.parent_name + if node.parent_name == 'Constants' { + node.parent_name = '' + } + contents[parent_name].children << node + } else { + contents[node.name] = node + } + } + d.fmt.mod2alias = map[string]string{} + if contents[''].kind != .const_group { + contents.delete('') + } + return contents +} + +// file_ast_with_pos has the same function as the `file_ast` but +// instead returns a list of variables in a given offset-based position. +pub fn (mut d Doc) file_ast_with_pos(file_ast ast.File, pos int) map[string]DocNode { + lscope := file_ast.scope.innermost(pos) + mut contents := map[string]DocNode{} + for name, val in lscope.objects { + if val !is ast.Var { + continue + } + vr_data := val as ast.Var + l_node := DocNode{ + name: name + pos: vr_data.pos + file_path: file_ast.path + from_scope: true + kind: .variable + return_type: d.expr_typ_to_string(vr_data.expr) + } + contents[l_node.name] = l_node + } + return contents +} + +// generate is a `Doc` method that will start documentation +// process based on a file path provided. +pub fn (mut d Doc) generate() ? { + // get all files + d.base_path = if os.is_dir(d.base_path) { + d.base_path + } else { + os.real_path(os.dir(d.base_path)) + } + d.is_vlib = !d.base_path.contains('vlib') + project_files := os.ls(d.base_path) or { return err } + v_files := d.prefs.should_compile_filtered_files(d.base_path, project_files) + if v_files.len == 0 { + return error_with_code('vdoc: No valid V files were found.', 1) + } + // parse files + mut comments_mode := scanner.CommentsMode.skip_comments + if d.with_comments { + comments_mode = .toplevel_comments + } + mut file_asts := []ast.File{} + for i, file_path in v_files { + if i == 0 { + d.parent_mod_name = get_parent_mod(d.base_path) or { '' } + } + file_asts << parser.parse_file(file_path, d.table, comments_mode, d.prefs) + } + return d.file_asts(file_asts) +} + +// file_asts has the same function as the `file_ast` function but +// accepts an array of `ast.File` and throws an error if necessary. +pub fn (mut d Doc) file_asts(file_asts []ast.File) ? { + mut fname_has_set := false + d.orig_mod_name = file_asts[0].mod.name + for i, file_ast in file_asts { + if d.filename.len > 0 && file_ast.path.contains(d.filename) && !fname_has_set { + d.filename = file_ast.path + fname_has_set = true + } + if d.with_head && i == 0 { + mut module_name := file_ast.mod.name + // if module_name != 'main' && d.parent_mod_name.len > 0 { + // module_name = d.parent_mod_name + '.' + module_name + // } + d.head = DocNode{ + name: module_name + content: 'module $module_name' + kind: .none_ + } + } else if file_ast.mod.name != d.orig_mod_name { + continue + } + if file_ast.path == d.filename { + d.checker.check(file_ast) + d.scoped_contents = d.file_ast_with_pos(file_ast, d.pos) + } + contents := d.file_ast(file_ast) + for name, node in contents { + if name !in d.contents { + d.contents[name] = node + continue + } + if d.contents[name].kind == .typedef && node.kind !in [.typedef, .none_] { + old_children := d.contents[name].children.clone() + d.contents[name] = node + d.contents[name].children = old_children + } + if d.contents[name].kind != .none_ || node.kind == .none_ { + d.contents[name].children << node.children + d.contents[name].children.sort_by_name() + d.contents[name].children.sort_by_kind() + } + } + } + if d.filter_symbol_names.len != 0 && d.contents.len != 0 { + for filter_name in d.filter_symbol_names { + if filter_name !in d.contents { + return error('vdoc: `$filter_name` symbol in module `$d.orig_mod_name` not found') + } + } + } + d.time_generated = time.now() +} + +// generate documents a certain file directory and returns an +// instance of `Doc` if it is successful. Otherwise, it will throw an error. +pub fn generate(input_path string, pub_only bool, with_comments bool, platform Platform, filter_symbol_names ...string) ?Doc { + if platform == .js { + return error('vdoc: Platform `$platform` is not supported.') + } + mut doc := new(input_path) + doc.pub_only = pub_only + doc.with_comments = with_comments + doc.filter_symbol_names = filter_symbol_names.filter(it.len != 0) + doc.prefs.os = if platform == .auto { pref.get_host_os() } else { pref.OS(int(platform)) } + doc.generate() ? + return doc +} + +// generate_with_pos has the same function as the `generate` function but +// accepts an offset-based position and enables the comments by default. +pub fn generate_with_pos(input_path string, filename string, pos int) ?Doc { + mut doc := new(input_path) + doc.pub_only = false + doc.with_comments = true + doc.with_pos = true + doc.filename = filename + doc.pos = pos + doc.generate() ? + return doc +} diff --git a/v_windows/v/vlib/v/doc/doc_private_fn_test.v b/v_windows/v/vlib/v/doc/doc_private_fn_test.v new file mode 100644 index 0000000..107d76f --- /dev/null +++ b/v_windows/v/vlib/v/doc/doc_private_fn_test.v @@ -0,0 +1,47 @@ +module doc + +import os + +fn testsuite_begin() { + os.chdir(@VMODROOT) or {} + eprintln('>> @VMODROOT: ' + @VMODROOT) +} + +fn test_get_parent_mod_on_root_folder() { + // TODO: add an equivalent windows check for c:\ + $if !windows { + assert '---' == get_parent_mod('/') or { + assert err.msg == 'root folder reached' + '---' + } + } +} + +fn test_get_parent_mod_current_folder() { + // TODO: this should may be return '' reliably on windows too: + // assert '' == get_parent_mod('.') or { + // assert err.msg == 'No V files found.' + // '---' + // } +} + +fn test_get_parent_mod_on_temp_dir() ? { + // TODO: fix this on windows + $if !windows { + assert get_parent_mod(os.temp_dir()) ? == '' + } +} + +fn test_get_parent_mod_normal_cases() ? { + assert '---' == get_parent_mod(os.join_path(@VMODROOT, 'vlib/v')) or { + assert err.msg == 'No V files found.' + '---' + } + // TODO: WTF? + // assert get_parent_mod(os.join_path(@VMODROOT, 'vlib', 'v', 'doc', 'doc.v')) ? == 'v.v.doc' + assert get_parent_mod(os.join_path(@VMODROOT, 'vlib', 'v', 'doc')) ? == 'v' + assert get_parent_mod(os.join_path(@VMODROOT, 'vlib', 'os', 'os.v')) ? == 'os' + assert get_parent_mod(os.join_path(@VMODROOT, 'cmd')) ? == '' + assert get_parent_mod(os.join_path(@VMODROOT, 'cmd', 'tools', 'modules', 'testing', + 'common.v')) ? == 'testing' +} diff --git a/v_windows/v/vlib/v/doc/doc_test.v b/v_windows/v/vlib/v/doc/doc_test.v new file mode 100644 index 0000000..cdb0ef8 --- /dev/null +++ b/v_windows/v/vlib/v/doc/doc_test.v @@ -0,0 +1,18 @@ +// import v.ast +import v.doc + +// fn test_generate_with_pos() {} +// fn test_generate() {} +// fn test_generate_from_ast() {} +fn test_generate_from_mod() { + nested_mod_name := 'net.http.chunked' + nested_mod_doc := doc.generate_from_mod(nested_mod_name, false, true) or { + eprintln(err) + assert false + doc.Doc{} + } + assert nested_mod_doc.head.name == nested_mod_name + assert nested_mod_doc.head.content == 'module $nested_mod_name' + assert nested_mod_doc.contents.len == 3 + assert nested_mod_doc.contents['ChunkScanner'].children.len == 3 +} diff --git a/v_windows/v/vlib/v/doc/module.v b/v_windows/v/vlib/v/doc/module.v new file mode 100644 index 0000000..5ac87db --- /dev/null +++ b/v_windows/v/vlib/v/doc/module.v @@ -0,0 +1,90 @@ +module doc + +import os +import v.ast +import v.parser +import v.pref + +// get_parent_mod returns the parent mod name, in dot format. +// It works by climbing up the folder hierarchy, until a folder, +// that either contains main .v files, or a v.mod file is reached. +// For example, given something like /languages/v/vlib/x/websocket/tests/autobahn +// it returns `x.websocket.tests`, because /languages/v/ has v.mod file in it. +// NB: calling this is expensive, so keep the result, instead of recomputing it. +// TODO: turn this to a Doc method, so that the new_vdoc_preferences call here can +// be removed. +fn get_parent_mod(input_dir string) ?string { + // windows root path is C: or D: + if input_dir.len == 2 && input_dir[1] == `:` { + return error('root folder reached') + } + // unix systems have / at the top: + if input_dir == '/' { + return error('root folder reached') + } + if input_dir == '' { + return error('no input folder') + } + base_dir := os.dir(input_dir) + input_dir_name := os.file_name(base_dir) + prefs := new_vdoc_preferences() + fentries := os.ls(base_dir) or { []string{} } + files := fentries.filter(!os.is_dir(it)) + if 'v.mod' in files { + // the top level is reached, no point in climbing up further + return '' + } + v_files := prefs.should_compile_filtered_files(base_dir, files) + if v_files.len == 0 { + parent_mod := get_parent_mod(base_dir) or { return input_dir_name } + if parent_mod.len > 0 { + return parent_mod + '.' + input_dir_name + } + return error('No V files found.') + } + tbl := ast.new_table() + file_ast := parser.parse_file(v_files[0], tbl, .skip_comments, prefs) + if file_ast.mod.name == 'main' { + return '' + } + parent_mod := get_parent_mod(base_dir) or { return input_dir_name } + if parent_mod.len > 0 { + return '${parent_mod}.$file_ast.mod.name' + } + return file_ast.mod.name +} + +// lookup_module_with_path looks up the path of a given module name. +// Throws an error if the module was not found. +pub fn lookup_module_with_path(mod string, base_path string) ?string { + vexe := pref.vexe_path() + vroot := os.dir(vexe) + mod_path := mod.replace('.', os.path_separator) + compile_dir := os.real_path(base_path) + modules_dir := os.join_path(compile_dir, 'modules', mod_path) + vlib_path := os.join_path(vroot, 'vlib', mod_path) + mut paths := [modules_dir, vlib_path] + vmodules_paths := os.vmodules_paths() + for vmpath in vmodules_paths { + paths << os.join_path(vmpath, mod_path) + } + for path in paths { + if !os.exists(path) || os.is_dir_empty(path) { + continue + } + return path + } + return error('module "$mod" not found.') +} + +// lookup_module returns the result of the `lookup_module_with_path` +// but with the current directory as the provided base lookup path. +pub fn lookup_module(mod string) ?string { + return lookup_module_with_path(mod, os.dir('.')) +} + +// generate_from_mod generates a documentation from a specific module. +pub fn generate_from_mod(module_name string, pub_only bool, with_comments bool) ?Doc { + mod_path := lookup_module(module_name) ? + return generate(mod_path, pub_only, with_comments, .auto) +} diff --git a/v_windows/v/vlib/v/doc/node.v b/v_windows/v/vlib/v/doc/node.v new file mode 100644 index 0000000..736067f --- /dev/null +++ b/v_windows/v/vlib/v/doc/node.v @@ -0,0 +1,70 @@ +module doc + +pub fn (nodes []DocNode) find(symname string) ?DocNode { + for node in nodes { + if node.name != symname { + continue + } + return node + } + return error('symbol not found') +} + +// sort_by_name sorts the array based on the symbol names. +pub fn (mut nodes []DocNode) sort_by_name() { + nodes.sort_with_compare(compare_nodes_by_name) +} + +// sort_by_kind sorts the array based on the symbol kind. +pub fn (mut nodes []DocNode) sort_by_kind() { + nodes.sort_with_compare(compare_nodes_by_kind) +} + +fn compare_nodes_by_kind(a &DocNode, b &DocNode) int { + ak := int((*a).kind) + bk := int((*b).kind) + if ak < bk { + return -1 + } + if ak > bk { + return 1 + } + return 0 +} + +fn compare_nodes_by_name(a &DocNode, b &DocNode) int { + al := a.name.to_lower() + bl := b.name.to_lower() + return compare_strings(al, bl) +} + +// arr() converts the map into an array of `DocNode`. +pub fn (cnts map[string]DocNode) arr() []DocNode { + mut contents := cnts.keys().map(cnts[it]) + contents.sort_by_name() + contents.sort_by_kind() + return contents +} + +// merge_comments returns a `string` with the combined contents of `DocNode.comments`. +pub fn (dc DocNode) merge_comments() string { + return merge_doc_comments(dc.comments) +} + +// merge_comments_without_examples returns a `string` with the +// combined contents of `DocNode.comments` - excluding any examples. +pub fn (dc DocNode) merge_comments_without_examples() string { + sans_examples := dc.comments.filter(!it.is_example()) + return merge_doc_comments(sans_examples) +} + +// examples returns a `[]string` containing examples parsed from `DocNode.comments`. +pub fn (dn DocNode) examples() []string { + mut output := []string{} + for comment in dn.comments { + if comment.is_example() { + output << comment.example() + } + } + return output +} diff --git a/v_windows/v/vlib/v/doc/utils.v b/v_windows/v/vlib/v/doc/utils.v new file mode 100644 index 0000000..3fb3973 --- /dev/null +++ b/v_windows/v/vlib/v/doc/utils.v @@ -0,0 +1,183 @@ +module doc + +import strings +import v.ast +import v.token + +// merge_comments merges all the comment contents into a single text. +pub fn merge_comments(comments []ast.Comment) string { + mut res := []string{} + for comment in comments { + res << comment.text.trim_left('\x01') + } + return res.join('\n') +} + +// ast_comment_to_doc_comment converts an `ast.Comment` node type to a `DocComment` +pub fn ast_comment_to_doc_comment(ast_node ast.Comment) DocComment { + text := ast_node.text // TODO .trim_left('\x01') // BUG why are this byte here in the first place? + return DocComment{ + text: text + is_multi: ast_node.is_multi + pos: token.Position{ + line_nr: ast_node.pos.line_nr + col: 0 // ast_node.pos.pos - ast_node.text.len + len: text.len + } + } +} + +// ast_comments_to_doc_comments converts an array of `ast.Comment` nodes to +// an array of `DocComment` nodes +pub fn ast_comments_to_doc_comments(ast_nodes []ast.Comment) []DocComment { + mut doc_comments := []DocComment{len: ast_nodes.len} + for ast_comment in ast_nodes { + doc_comments << ast_comment_to_doc_comment(ast_comment) + } + return doc_comments +} + +// merge_doc_comments merges all the comments starting from +// the last up to the first item of the array. +pub fn merge_doc_comments(comments []DocComment) string { + if comments.len == 0 { + return '' + } + mut commentlines := []string{} + mut last_comment_line_nr := 0 + for i := comments.len - 1; i >= 0; i-- { + cmt := comments[i] + if (!cmt.is_multi && last_comment_line_nr != 0 + && cmt.pos.line_nr + 1 < last_comment_line_nr - 1) || (cmt.is_multi + && cmt.pos.line_nr + cmt.text.count('\n') + 1 < last_comment_line_nr - 1) { + // skip comments that are not part of a continuous block, + // located right above the top level statement. + break + } + mut cmt_content := cmt.text.trim_left('\x01') + if cmt.is_multi { + // /**/ comments are deliberately NOT supported as vdoc comments, + // so just ignore them: + continue + } else { + if cmt_content.starts_with(' ') { + cmt_content = cmt_content[1..] + } + commentlines << cmt_content + } + last_comment_line_nr = cmt.pos.line_nr + 1 + } + commentlines = commentlines.reverse() + mut is_codeblock := false + mut previously_newline := true + mut comment := '' + for line in commentlines { + if line.starts_with('```') { + is_codeblock = !is_codeblock + comment = comment + '\n' + line + continue + } else if is_codeblock { + comment = comment + '\n' + line + continue + } + + line_trimmed := line.trim(' ') + + mut is_horizontalrule := false + line_no_spaces := line_trimmed.replace(' ', '') + for char in ['-', '=', '*', '_', '~'] { + if line_no_spaces.starts_with(char.repeat(3)) + && line_no_spaces.count(char) == line_no_spaces.len { + is_horizontalrule = true + break + } + } + + if line_trimmed == '' || is_horizontalrule + || (line.starts_with('#') && line.before(' ').count('#') == line.before(' ').len) + || (line_trimmed.starts_with('|') && line_trimmed.ends_with('|')) + || line_trimmed.starts_with('- ') { + comment = comment + '\n' + line + previously_newline = true + } else if line.ends_with('.') { + comment = comment + '\n' + line + ' ' + previously_newline = true + } else { + sep := if previously_newline { '\n' } else { ' ' } + comment = comment + sep + line + previously_newline = false + } + } + return comment +} + +// stmt_signature returns the signature of a given `ast.Stmt` node. +pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string { + match stmt { + ast.Module { + return 'module $stmt.name' + } + ast.FnDecl { + return stmt.stringify(d.table, d.fmt.cur_mod, d.fmt.mod2alias) + } + else { + d.fmt.out = strings.new_builder(1000) + d.fmt.stmt(stmt) + return d.fmt.out.str().trim_space() + } + } +} + +// stmt_name returns the name of a given `ast.Stmt` node. +pub fn (d Doc) stmt_name(stmt ast.Stmt) string { + match stmt { + ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl { + return stmt.name + } + ast.TypeDecl { + match stmt { + ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.name } + } + } + ast.ConstDecl { + return '' + } // leave it blank + else { + return '' + } + } +} + +// stmt_pub returns a boolean if a given `ast.Stmt` node +// is exposed to the public. +pub fn (d Doc) stmt_pub(stmt ast.Stmt) bool { + match stmt { + ast.FnDecl, ast.StructDecl, ast.EnumDecl, ast.InterfaceDecl, ast.ConstDecl { + return stmt.is_pub + } + ast.TypeDecl { + match stmt { + ast.FnTypeDecl, ast.AliasTypeDecl, ast.SumTypeDecl { return stmt.is_pub } + } + } + else { + return false + } + } +} + +// type_to_str is a wrapper function around `fmt.ast.type_to_str`. +pub fn (mut d Doc) type_to_str(typ ast.Type) string { + // why is it the default behaviour of ast.type_to_str + // to convert math.bits.Type to bits.Type? + d.table.cmod_prefix = d.orig_mod_name + '.' + return d.fmt.table.type_to_str(typ).all_after('&') +} + +// expr_typ_to_string has the same function as `Doc.typ_to_str` +// but for `ast.Expr` nodes. The checker will check first the +// node and it executes the `type_to_str` method. +pub fn (mut d Doc) expr_typ_to_string(ex ast.Expr) string { + expr_typ := d.checker.expr(ex) + return d.type_to_str(expr_typ) +} diff --git a/v_windows/v/vlib/v/dotgraph/dotgraph.c.v b/v_windows/v/vlib/v/dotgraph/dotgraph.c.v new file mode 100644 index 0000000..a98f7d8 --- /dev/null +++ b/v_windows/v/vlib/v/dotgraph/dotgraph.c.v @@ -0,0 +1,8 @@ +module dotgraph + +pub fn start_digraph() { + println('digraph G {') + C.atexit(fn () { + println('}') + }) +} diff --git a/v_windows/v/vlib/v/dotgraph/dotgraph.v b/v_windows/v/vlib/v/dotgraph/dotgraph.v new file mode 100644 index 0000000..087aef8 --- /dev/null +++ b/v_windows/v/vlib/v/dotgraph/dotgraph.v @@ -0,0 +1,81 @@ +module dotgraph + +import strings + +[heap] +struct DotGraph { +mut: + sb strings.Builder +} + +pub fn new(name string, label string, color string) &DotGraph { + mut res := &DotGraph{ + sb: strings.new_builder(1024) + } + res.writeln(' subgraph cluster_$name {') + res.writeln('\tedge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10",style="solid",color="black"];') + res.writeln('\tnode [fontname="Helvetica",fontsize="10",style="filled",fontcolor="black",fillcolor="white",color="black",shape="box"];') + res.writeln('\trankdir="LR";') + res.writeln('\tcolor="$color";') + res.writeln('\tlabel="$label";') + // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"]; + // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; + return res +} + +pub fn (mut d DotGraph) writeln(line string) { + d.sb.writeln(line) +} + +pub fn (mut d DotGraph) finish() { + d.sb.writeln(' }') + println(d.sb.str()) +} + +// + +pub struct NewNodeConfig { + node_name string + should_highlight bool + tooltip string + ctx voidptr = voidptr(0) + name2node_fn FnLabel2NodeName = node_name +} + +pub fn (mut d DotGraph) new_node(nlabel string, cfg NewNodeConfig) { + mut nname := cfg.name2node_fn(nlabel, cfg.ctx) + if cfg.node_name != '' { + nname = cfg.node_name + } + if cfg.should_highlight { + d.writeln('\t$nname [label="$nlabel",color="blue",height=0.2,width=0.4,fillcolor="#00FF00",tooltip="$cfg.tooltip",shape=oval];') + } else { + d.writeln('\t$nname [shape="box",label="$nlabel"];') + } +} + +// + +pub struct NewEdgeConfig { + should_highlight bool + ctx voidptr = voidptr(0) + name2node_fn FnLabel2NodeName = node_name +} + +pub fn (mut d DotGraph) new_edge(source string, target string, cfg NewEdgeConfig) { + nsource := cfg.name2node_fn(source, cfg.ctx) + ntarget := cfg.name2node_fn(target, cfg.ctx) + if cfg.should_highlight { + d.writeln('\t$nsource -> $ntarget [color="blue"];') + } else { + d.writeln('\t$nsource -> $ntarget;') + } +} + +// + +pub type FnLabel2NodeName = fn (string, voidptr) string + +pub fn node_name(name string, context voidptr) string { + return name.replace('.', '_') +} diff --git a/v_windows/v/vlib/v/embed_file/embed_file.v b/v_windows/v/vlib/v/embed_file/embed_file.v new file mode 100644 index 0000000..3c556d3 --- /dev/null +++ b/v_windows/v/vlib/v/embed_file/embed_file.v @@ -0,0 +1,105 @@ +module embed_file + +import os + +// https://github.com/vlang/rfcs/blob/master/embedding_resources.md +// EmbedFileData encapsulates functionality for the `$embed_file()` compile time call. +pub struct EmbedFileData { + path string + apath string +mut: + compressed &byte + uncompressed &byte + free_compressed bool + free_uncompressed bool +pub: + len int +} + +pub fn (ed EmbedFileData) str() string { + return 'embed_file.EmbedFileData{ len: $ed.len, path: "$ed.path", path: "$ed.apath", uncompressed: ${ptr_str(ed.uncompressed)} }' +} + +[unsafe] +pub fn (mut ed EmbedFileData) free() { + unsafe { + ed.path.free() + ed.apath.free() + if ed.free_compressed { + free(ed.compressed) + ed.compressed = &byte(0) + } + if ed.free_uncompressed { + free(ed.uncompressed) + ed.uncompressed = &byte(0) + } + } +} + +pub fn (original &EmbedFileData) to_string() string { + unsafe { + mut ed := &EmbedFileData(original) + the_copy := &byte(memdup(ed.data(), ed.len)) + return the_copy.vstring_with_len(ed.len) + } +} + +pub fn (original &EmbedFileData) to_bytes() []byte { + unsafe { + mut ed := &EmbedFileData(original) + the_copy := memdup(ed.data(), ed.len) + return the_copy.vbytes(ed.len) + } +} + +pub fn (mut ed EmbedFileData) data() &byte { + if !isnil(ed.uncompressed) { + return ed.uncompressed + } else { + if isnil(ed.uncompressed) && !isnil(ed.compressed) { + // TODO implement uncompression + // See also C Gen.gen_embedded_data() where the compression should occur. + ed.uncompressed = ed.compressed + } else { + mut path := os.resource_abs_path(ed.path) + if !os.is_file(path) { + path = ed.apath + if !os.is_file(path) { + panic('EmbedFileData error: files "$ed.path" and "$ed.apath" do not exist') + } + } + bytes := os.read_bytes(path) or { + panic('EmbedFileData error: "$path" could not be read: $err') + } + ed.uncompressed = bytes.data + ed.free_uncompressed = true + } + } + return ed.uncompressed +} + +////////////////////////////////////////////////////////////////////////////// +// EmbedFileIndexEntry is used internally by the V compiler when you compile a +// program that uses $embed_file('file.bin') in -prod mode. +// V will generate a static index of all embedded files, and will call the +// find_index_entry_by_path over the index and the relative paths of the embeds. +// NB: these are public on purpose, to help -usecache. +pub struct EmbedFileIndexEntry { + id int + path string + data &byte +} + +// find_index_entry_by_path is used internally by the V compiler: +pub fn find_index_entry_by_path(start voidptr, path string) &EmbedFileIndexEntry { + mut x := &EmbedFileIndexEntry(start) + for !(x.path == path || isnil(x.data)) { + unsafe { + x++ + } + } + $if debug_embed_file_in_prod ? { + eprintln('>> v.embed_file find_index_entry_by_path ${ptr_str(start)}, path: "$path" => ${ptr_str(x)}') + } + return x +} diff --git a/v_windows/v/vlib/v/embed_file/embed_file_test.v b/v_windows/v/vlib/v/embed_file/embed_file_test.v new file mode 100644 index 0000000..2af4459 --- /dev/null +++ b/v_windows/v/vlib/v/embed_file/embed_file_test.v @@ -0,0 +1,25 @@ +const const_file = $embed_file('v.png') + +fn test_const_embed_file() { + mut file := const_file + eprintln('file: $file') + assert file.len == 603 + fdata := file.data() + eprintln('file after .data() call: $file') + assert file.len == 603 + unsafe { + assert fdata.vbytes(4) == [byte(0x89), `P`, `N`, `G`] + } +} + +fn test_embed_file() { + mut file := $embed_file('v.png') + eprintln('file: $file') + assert file.len == 603 + fdata := file.data() + eprintln('file after .data() call: $file') + assert file.len == 603 + unsafe { + assert fdata.vbytes(4) == [byte(0x89), `P`, `N`, `G`] + } +} diff --git a/v_windows/v/vlib/v/embed_file/v.png b/v_windows/v/vlib/v/embed_file/v.png new file mode 100644 index 0000000..8739e46 Binary files /dev/null and b/v_windows/v/vlib/v/embed_file/v.png differ diff --git a/v_windows/v/vlib/v/errors/errors.v b/v_windows/v/vlib/v/errors/errors.v new file mode 100644 index 0000000..16f31ed --- /dev/null +++ b/v_windows/v/vlib/v/errors/errors.v @@ -0,0 +1,38 @@ +module errors + +import v.token + +pub enum Reporter { + scanner + parser + checker + gen +} + +pub struct Error { +pub: + message string + details string + file_path string + pos token.Position + backtrace string + reporter Reporter +} + +pub struct Warning { +pub: + message string + details string + file_path string + pos token.Position + reporter Reporter +} + +pub struct Notice { +pub: + message string + details string + file_path string + pos token.Position + reporter Reporter +} diff --git a/v_windows/v/vlib/v/eval/eval.v b/v_windows/v/vlib/v/eval/eval.v new file mode 100644 index 0000000..45bb687 --- /dev/null +++ b/v_windows/v/vlib/v/eval/eval.v @@ -0,0 +1,99 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module eval + +import v.ast +import v.checker +import v.pref + +pub type Object = int | string + +pub struct Eval { +mut: + checker checker.Checker + vars map[string]Var + table &ast.Table +} + +pub struct Var { + value Object +} + +pub fn (mut e Eval) eval(file ast.File, table &ast.Table) string { + vpref := &pref.Preferences{} + e.table = table + mut res := '' + e.checker = checker.new_checker(table, vpref) + for stmt in file.stmts { + res += e.stmt(stmt) + '\n' + } + return res.trim_space() +} + +fn print_object(o Object) { + match o { + int { println(o) } + else { println('unknown object') } + } +} + +pub fn (o Object) str() string { + match o { + int { return o.str() } + else { println('unknown object') } + } + return '' +} + +fn (mut e Eval) stmt(node ast.Stmt) string { + match node { + ast.AssignStmt { + // TODO; replaced VarDecl + } + ast.ExprStmt { + o := e.expr(node.expr) + print('out: ') + print_object(o) + return o.str() + } + // ast.StructDecl { + // println('s decl') + // } + // ast.VarDecl { + // e.vars[it.name] = Var{ + // value: e.expr(it.expr) + // } + // } + else {} + } + return '>>' +} + +fn (mut e Eval) expr(node ast.Expr) Object { + match node { + ast.IntegerLiteral { + return node.val + } + ast.Ident { + print_object(node.value) + // Find the variable + v := e.vars[node.name] + return v.value + } + ast.InfixExpr { + e.checker.infix_expr(mut node) + // println('bin $it.op') + left := e.expr(node.left) as int + right := e.expr(node.right) as int + match node.op { + .plus { return left + right } + .mul { return left * right } + else {} + } + } + else {} + } + return 0 + // return Object{} +} diff --git a/v_windows/v/vlib/v/fmt/align.v b/v_windows/v/vlib/v/fmt/align.v new file mode 100644 index 0000000..f767abd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/align.v @@ -0,0 +1,56 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import math.mathutil + +const struct_field_align_threshold = 8 + +struct AlignInfo { +mut: + line_nr int + max_len int + max_type_len int +} + +struct AddInfoConfig { + use_threshold bool +} + +fn (mut infos []AlignInfo) add_new_info(len int, type_len int, line int) { + infos << AlignInfo{ + line_nr: line + max_len: len + max_type_len: type_len + } +} + +[direct_array_access] +fn (mut infos []AlignInfo) add_info(len int, type_len int, line int, cfg AddInfoConfig) { + if infos.len == 0 { + infos.add_new_info(len, type_len, line) + return + } + i := infos.len - 1 + if line - infos[i].line_nr > 1 { + infos.add_new_info(len, type_len, line) + return + } + if cfg.use_threshold { + len_diff := mathutil.abs(infos[i].max_len - len) + + mathutil.abs(infos[i].max_type_len - type_len) + + if len_diff >= fmt.struct_field_align_threshold { + infos.add_new_info(len, type_len, line) + return + } + } + infos[i].line_nr = line + if len > infos[i].max_len { + infos[i].max_len = len + } + if type_len > infos[i].max_type_len { + infos[i].max_type_len = type_len + } +} diff --git a/v_windows/v/vlib/v/fmt/asm.v b/v_windows/v/vlib/v/fmt/asm.v new file mode 100644 index 0000000..9de83dd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/asm.v @@ -0,0 +1,185 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import v.ast + +fn (mut f Fmt) asm_stmt(stmt ast.AsmStmt) { + f.write('asm ') + if stmt.is_volatile { + f.write('volatile ') + } else if stmt.is_goto { + f.write('goto ') + } + f.writeln('$stmt.arch {') + f.indent++ + + f.asm_templates(stmt.templates) + + if stmt.output.len != 0 || stmt.input.len != 0 || stmt.clobbered.len != 0 { + f.write('; ') + } + f.asm_ios(stmt.output) + + if stmt.input.len != 0 || stmt.clobbered.len != 0 { + f.write('; ') + } + f.asm_ios(stmt.input) + + if stmt.clobbered.len != 0 { + f.write('; ') + } + f.asm_clobbered(stmt.clobbered) + + f.indent-- + f.writeln('}') + if f.indent == 0 { + f.writeln('') + } +} + +fn (mut f Fmt) asm_arg(arg ast.AsmArg) { + match arg { + ast.AsmRegister { + f.asm_reg(arg) + } + ast.AsmAlias { + f.write('$arg.name') + } + ast.IntegerLiteral, ast.FloatLiteral, ast.CharLiteral { + f.write(arg.val) + } + ast.BoolLiteral { + f.write(arg.val.str()) + } + string { + f.write(arg) + } + ast.AsmAddressing { + f.write('[') + base := arg.base + index := arg.index + displacement := arg.displacement + scale := arg.scale + match arg.mode { + .base { + f.asm_arg(base) + } + .displacement { + f.asm_arg(displacement) + } + .base_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(displacement) + } + .index_times_scale_plus_displacement { + f.asm_arg(index) + f.write(' * $scale + ') + f.asm_arg(displacement) + } + .base_plus_index_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(index) + f.write(' + ') + f.asm_arg(displacement) + } + .base_plus_index_times_scale_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(index) + f.write(' * $scale + ') + f.asm_arg(displacement) + } + .rip_plus_displacement { + f.asm_arg(base) + f.write(' + ') + f.asm_arg(displacement) + } + .invalid { + panic('fmt: invalid addressing mode') + } + } + f.write(']') + } + ast.AsmDisp { + if arg.val.len >= 2 && arg.val[arg.val.len - 1] in [`b`, `f`] + && arg.val[..arg.val.len - 1].bytes().all(it.is_digit()) { + f.write(arg.val[arg.val.len - 1].ascii_str()) + f.write(arg.val[..arg.val.len - 1]) + } else { + f.write(arg.val) + } + } + } +} + +fn (mut f Fmt) asm_reg(reg ast.AsmRegister) { + f.write(reg.name) +} + +fn (mut f Fmt) asm_templates(templates []ast.AsmTemplate) { + for template in templates { + if template.is_directive { + f.write('.') + } + f.write('$template.name') + if template.is_label { + f.write(':') + } else if template.args.len > 0 { + f.write(' ') + } + for i, arg in template.args { + f.asm_arg(arg) + if i + 1 < template.args.len { + f.write(', ') + } + } + if template.comments.len == 0 { + f.writeln('') + } else { + f.comments(template.comments, inline: false) + } + } +} + +fn (mut f Fmt) asm_clobbered(clobbered []ast.AsmClobbered) { + for i, clob in clobbered { + if i != 0 { + f.write(' ') + } + f.write(clob.reg.name) + + if clob.comments.len == 0 { + f.writeln('') + } else { + f.comments(clob.comments, inline: false) + } + } +} + +fn (mut f Fmt) asm_ios(ios []ast.AsmIO) { + for i, io in ios { + if i != 0 { + f.write(' ') + } + + f.write('$io.constraint ($io.expr)') + mut as_block := true + if io.expr is ast.Ident { + if io.expr.name == io.alias { + as_block = false + } + } + if as_block && io.alias != '' { + f.write(' as $io.alias') + } + if io.comments.len == 0 { + f.writeln('') + } else { + f.comments(io.comments, inline: false) + } + } +} diff --git a/v_windows/v/vlib/v/fmt/attrs.v b/v_windows/v/vlib/v/fmt/attrs.v new file mode 100644 index 0000000..798cbb2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/attrs.v @@ -0,0 +1,60 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import v.ast + +pub fn (mut f Fmt) attrs(attrs []ast.Attr) { + mut sorted_attrs := attrs.clone() + // Sort the attributes. The ones with arguments come first. + sorted_attrs.sort(a.arg.len > b.arg.len) + for i, attr in sorted_attrs { + if attr.arg.len == 0 { + f.single_line_attrs(sorted_attrs[i..]) + break + } + f.writeln('[$attr]') + } +} + +pub struct AttrsOptions { + inline bool +} + +pub fn (mut f Fmt) single_line_attrs(attrs []ast.Attr, options AttrsOptions) { + if attrs.len == 0 { + return + } + mut sorted_attrs := attrs.clone() + sorted_attrs.sort(a.name < b.name) + if options.inline { + f.write(' ') + } + f.write('[') + for i, attr in sorted_attrs { + if i > 0 { + f.write('; ') + } + f.write('$attr') + } + f.write(']') + if !options.inline { + f.writeln('') + } +} + +fn inline_attrs_len(attrs []ast.Attr) int { + if attrs.len == 0 { + return 0 + } + mut n := 2 // ' ['.len + for i, attr in attrs { + if i > 0 { + n += 2 // '; '.len + } + n += '$attr'.len + } + n++ // ']'.len + return n +} diff --git a/v_windows/v/vlib/v/fmt/comments.v b/v_windows/v/vlib/v/fmt/comments.v new file mode 100644 index 0000000..8c55794 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/comments.v @@ -0,0 +1,141 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import v.ast + +pub enum CommentsLevel { + keep + indent +} + +// CommentsOptions defines the way comments are going to be written +// - has_nl: adds an newline at the end of a list of comments +// - inline: line comments will be on the same line as the last statement +// - level: either .keep (don't indent), or .indent (increment indentation) +// - iembed: a /* ... */ block comment used inside expressions; // comments the whole line +// - prev_line: the line number of the previous token to save linebreaks +pub struct CommentsOptions { + has_nl bool = true + inline bool + level CommentsLevel + iembed bool + prev_line int = -1 +} + +pub fn (mut f Fmt) comment(node ast.Comment, options CommentsOptions) { + // Shebang in .vsh files + if node.text.starts_with('#!') { + f.writeln(node.text) + return + } + if options.level == .indent { + f.indent++ + } + if options.iembed { + x := node.text.trim_left('\x01').trim_space() + if x.contains('\n') { + f.writeln('/*') + f.writeln(x) + f.write('*/') + } else { + f.write('/* $x */') + } + } else if !node.text.contains('\n') { + is_separate_line := !options.inline || node.text.starts_with('\x01') + mut s := node.text.trim_left('\x01').trim_right(' ') + mut out_s := '//' + if s != '' { + if is_char_alphanumeric(s[0]) { + out_s += ' ' + } + out_s += s + } + if !is_separate_line && f.indent > 0 { + f.remove_new_line() // delete the generated \n + f.write(' ') + } + f.write(out_s) + } else { + lines := node.text.trim_space().split_into_lines() + start_break := is_char_alphanumeric(node.text[0]) || node.text[0].is_space() + end_break := is_char_alphanumeric(node.text.trim('\t').bytes().last()) + || node.text.bytes().last().is_space() + f.write('/*') + if start_break { + f.writeln('') + } + for line in lines { + f.writeln(line.trim_right(' ')) + f.empty_line = false + } + if end_break { + f.empty_line = true + } else { + f.remove_new_line() + } + f.write('*/') + } + if options.level == .indent { + f.indent-- + } +} + +pub fn (mut f Fmt) comments(comments []ast.Comment, options CommentsOptions) { + mut prev_line := options.prev_line + for i, c in comments { + if options.prev_line > -1 && ((c.pos.line_nr > prev_line && f.out.last_n(1) != '\n') + || (c.pos.line_nr > prev_line + 1 && f.out.last_n(2) != '\n\n')) { + f.writeln('') + } + if !f.out.last_n(1)[0].is_space() { + f.write(' ') + } + f.comment(c, options) + if !options.iembed && (i < comments.len - 1 || options.has_nl) { + f.writeln('') + } + prev_line = c.pos.last_line + } +} + +pub fn (mut f Fmt) comments_before_field(comments []ast.Comment) { + // They behave the same as comments after the last field. This alias is just for clarity. + f.comments_after_last_field(comments) +} + +pub fn (mut f Fmt) comments_after_last_field(comments []ast.Comment) { + for comment in comments { + f.indent++ + f.empty_line = true + f.comment(comment, inline: true) + f.writeln('') + f.indent-- + } +} + +pub fn (mut f Fmt) import_comments(comments []ast.Comment, options CommentsOptions) { + if comments.len == 0 { + return + } + if options.inline { + f.remove_new_line(imports_buffer: true) + } + for c in comments { + ctext := c.text.trim_left('\x01') + if ctext == '' { + continue + } + mut out_s := if options.inline { ' ' } else { '' } + '//' + if is_char_alphanumeric(ctext[0]) { + out_s += ' ' + } + out_s += ctext + f.out_imports.writeln(out_s) + } +} + +fn is_char_alphanumeric(c byte) bool { + return c.is_letter() || c.is_digit() +} diff --git a/v_windows/v/vlib/v/fmt/fmt.v b/v_windows/v/vlib/v/fmt/fmt.v new file mode 100644 index 0000000..df98c76 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/fmt.v @@ -0,0 +1,2454 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import math.mathutil +import v.ast +import strings +import v.util +import v.pref + +const ( + bs = '\\' + // when to break a line dependant on penalty + max_len = [0, 35, 60, 85, 93, 100] +) + +pub struct Fmt { +pub mut: + file ast.File + table &ast.Table + pref &pref.Preferences + is_debug bool + out strings.Builder + out_imports strings.Builder + indent int + empty_line bool + line_len int // the current line length, NB: it counts \t as 4 spaces, and starts at 0 after f.writeln + buffering bool // disables line wrapping for exprs that will be analyzed later + par_level int // how many parentheses are put around the current expression + array_init_break []bool // line breaks after elements in hierarchy level of multi dimensional array + array_init_depth int // current level of hierarchy in array init + single_line_if bool + cur_mod string + did_imports bool + is_assign bool + is_struct_init bool + auto_imports []string // automatically inserted imports that the user forgot to specify + import_pos int // position of the imports in the resulting string for later autoimports insertion + used_imports []string // to remove unused imports + import_syms_used map[string]bool // to remove unused import symbols. + mod2alias map[string]string // for `import time as t`, will contain: 'time'=>'t' + use_short_fn_args bool + single_line_fields bool // should struct fields be on a single line + it_name string // the name to replace `it` with + inside_lambda bool + inside_const bool + is_mbranch_expr bool // match a { x...y { } } + fn_scope &ast.Scope = voidptr(0) +} + +pub fn fmt(file ast.File, table &ast.Table, pref &pref.Preferences, is_debug bool) string { + mut f := Fmt{ + file: file + table: table + pref: pref + is_debug: is_debug + out: strings.new_builder(1000) + out_imports: strings.new_builder(200) + } + f.process_file_imports(file) + f.set_current_module_name('main') + // As these are toplevel stmts, the indent increase done in f.stmts() has to be compensated + f.indent-- + f.stmts(file.stmts) + f.indent++ + f.imports(f.file.imports) // now that we have all autoimports, handle them + res := f.out.str().trim_space() + '\n' + if res.len == 1 { + return f.out_imports.str().trim_space() + '\n' + } + bounded_import_pos := mathutil.min(res.len, f.import_pos) + return res[..bounded_import_pos] + f.out_imports.str() + res[bounded_import_pos..] +} + +pub fn (mut f Fmt) process_file_imports(file &ast.File) { + for imp in file.imports { + f.mod2alias[imp.mod] = imp.alias + for sym in imp.syms { + f.mod2alias['${imp.mod}.$sym.name'] = sym.name + f.mod2alias['${imp.mod.all_after_last('.')}.$sym.name'] = sym.name + f.mod2alias[sym.name] = sym.name + f.import_syms_used[sym.name] = false + } + } + f.auto_imports = file.auto_imports +} + +//=== Basic buffer write operations ===// + +pub fn (mut f Fmt) write(s string) { + if f.indent > 0 && f.empty_line { + f.write_indent() + } + f.out.write_string(s) + f.line_len += s.len + f.empty_line = false +} + +pub fn (mut f Fmt) writeln(s string) { + if f.indent > 0 && f.empty_line && s.len > 0 { + f.write_indent() + } + f.out.writeln(s) + f.empty_line = true + f.line_len = 0 +} + +fn (mut f Fmt) write_indent() { + f.out.write_string(util.tabs(f.indent)) + f.line_len += f.indent * 4 +} + +pub fn (mut f Fmt) wrap_long_line(penalty_idx int, add_indent bool) bool { + if f.buffering { + return false + } + if penalty_idx > 0 && f.line_len <= fmt.max_len[penalty_idx] { + return false + } + if f.out[f.out.len - 1] == ` ` { + f.out.go_back(1) + } + f.write('\n') + f.line_len = 0 + if add_indent { + f.indent++ + } + f.write_indent() + if add_indent { + f.indent-- + } + return true +} + +pub struct RemoveNewLineConfig { + imports_buffer bool // Work on f.out_imports instead of f.out +} + +pub fn (mut f Fmt) remove_new_line(cfg RemoveNewLineConfig) { + mut buffer := if cfg.imports_buffer { unsafe { &f.out_imports } } else { unsafe { &f.out } } + mut i := 0 + for i = buffer.len - 1; i >= 0; i-- { + if !buffer.byte_at(i).is_space() { // != `\n` { + break + } + } + buffer.go_back(buffer.len - i - 1) + f.empty_line = false +} + +//=== Specialized write methods ===// + +fn (mut f Fmt) write_language_prefix(lang ast.Language) { + match lang { + .c { f.write('C.') } + .js { f.write('JS.') } + else {} + } +} + +fn (mut f Fmt) write_generic_types(gtypes []ast.Type) { + if gtypes.len > 0 { + f.write('<') + gtypes_string := gtypes.map(f.table.type_to_str(it)).join(', ') + f.write(gtypes_string) + f.write('>') + } +} + +//=== Module handling helper methods ===// + +pub fn (mut f Fmt) set_current_module_name(cmodname string) { + f.cur_mod = cmodname + f.table.cmod_prefix = cmodname + '.' +} + +fn (f Fmt) get_modname_prefix(mname string) (string, string) { + // ./tests/proto_module_importing_vproto_keep.vv to know, why here is checked for ']' and '&' + if !mname.contains(']') && !mname.contains('&') { + return mname, '' + } + after_rbc := mname.all_after_last(']') + after_ref := mname.all_after_last('&') + modname := if after_rbc.len < after_ref.len { after_rbc } else { after_ref } + return modname, mname.trim_suffix(modname) +} + +fn (mut f Fmt) is_external_name(name string) bool { + if name.len > 2 && name[0] == `C` && name[1] == `.` { + return true + } + if name.len > 3 && name[0] == `J` && name[1] == `S` && name[2] == `.` { + return true + } + return false +} + +pub fn (mut f Fmt) no_cur_mod(typename string) string { + return util.no_cur_mod(typename, f.cur_mod) +} + +// foo.bar.fn() => bar.fn() +pub fn (mut f Fmt) short_module(name string) string { + if !name.contains('.') { + return name + } + if name in f.mod2alias { + return f.mod2alias[name] + } + if name.ends_with('>') { + x := name.trim_suffix('>').split('<') + if x.len == 2 { + main := f.short_module(x[0]) + genlist := x[1].split(',') + genshorts := genlist.map(f.short_module(it)).join(',') + return '$main<$genshorts>' + } + } + vals := name.split('.') + if vals.len < 2 { + return name + } + idx := vals.len - 1 + mname, tprefix := f.get_modname_prefix(vals[..idx].join('.')) + symname := vals[vals.len - 1] + aname := f.mod2alias[mname] + if aname == '' { + return symname + } + return '$tprefix${aname}.$symname' +} + +//=== Import-related methods ===// + +pub fn (mut f Fmt) mark_types_import_as_used(typ ast.Type) { + sym := f.table.get_type_symbol(typ) + f.mark_import_as_used(sym.name) +} + +// `name` is a function (`foo.bar()`) or type (`foo.Bar{}`) +pub fn (mut f Fmt) mark_import_as_used(name string) { + parts := name.split('.') + last := parts.last() + if last in f.import_syms_used { + f.import_syms_used[last] = true + } + if parts.len == 1 { + return + } + mod := parts[0..parts.len - 1].join('.') + if mod in f.used_imports { + return + } + f.used_imports << mod +} + +pub fn (mut f Fmt) imports(imports []ast.Import) { + if f.did_imports || imports.len == 0 { + return + } + f.did_imports = true + mut num_imports := 0 + mut already_imported := map[string]bool{} + + for imp in imports { + if imp.mod !in f.used_imports { + // TODO bring back once only unused imports are removed + // continue + } + if imp.mod in f.auto_imports && imp.mod !in f.used_imports { + continue + } + import_text := 'import ${f.imp_stmt_str(imp)}' + if already_imported[import_text] { + continue + } + already_imported[import_text] = true + f.out_imports.writeln(import_text) + f.import_comments(imp.comments, inline: true) + f.import_comments(imp.next_comments) + num_imports++ + } + if num_imports > 0 { + f.out_imports.writeln('') + } +} + +pub fn (f Fmt) imp_stmt_str(imp ast.Import) string { + mod := if imp.mod.len == 0 { imp.alias } else { imp.mod } + is_diff := imp.alias != mod && !mod.ends_with('.' + imp.alias) + mut imp_alias_suffix := if is_diff { ' as $imp.alias' } else { '' } + mut syms := imp.syms.map(it.name).filter(f.import_syms_used[it]) + syms.sort() + if syms.len > 0 { + imp_alias_suffix += if imp.syms[0].pos.line_nr == imp.pos.line_nr { + ' { ' + syms.join(', ') + ' }' + } else { + ' {\n\t' + syms.join(',\n\t') + ',\n}' + } + } + return '$mod$imp_alias_suffix' +} + +//=== Node helpers ===// + +fn (f Fmt) should_insert_newline_before_node(node ast.Node, prev_node ast.Node) bool { + // No need to insert a newline if there is already one + if f.out.last_n(2) == '\n\n' { + return false + } + prev_line_nr := prev_node.position().last_line + // The nodes are Stmts + if node is ast.Stmt && prev_node is ast.Stmt { + stmt := node + prev_stmt := prev_node + // Force a newline after a block of HashStmts + if prev_stmt is ast.HashStmt && stmt !is ast.HashStmt && stmt !is ast.ExprStmt { + return true + } + // Force a newline after function declarations + // The only exception is inside an block of no_body functions + if prev_stmt is ast.FnDecl { + if stmt !is ast.FnDecl || !prev_stmt.no_body { + return true + } + } + // Force a newline after struct declarations + if prev_stmt is ast.StructDecl { + return true + } + // Empty line after an block of type declarations + if prev_stmt is ast.TypeDecl && stmt !is ast.TypeDecl { + return true + } + // Imports are handled special hence they are ignored here + if stmt is ast.Import || prev_stmt is ast.Import { + return false + } + // Attributes are not respected in the stmts position, so this requires manual checking + if stmt is ast.StructDecl { + if stmt.attrs.len > 0 && stmt.attrs[0].pos.line_nr - prev_line_nr <= 1 { + return false + } + } + if stmt is ast.FnDecl { + if stmt.attrs.len > 0 && stmt.attrs[0].pos.line_nr - prev_line_nr <= 1 { + return false + } + } + } + // The node shouldn't have a newline before + if node.position().line_nr - prev_line_nr <= 1 { + return false + } + return true +} + +pub fn (mut f Fmt) node_str(node ast.Node) string { + was_empty_line := f.empty_line + prev_line_len := f.line_len + pos := f.out.len + match node { + ast.Stmt { f.stmt(node) } + ast.Expr { f.expr(node) } + else { panic('´f.node_str()´ is not implemented for ${node}.') } + } + str := f.out.after(pos) + f.out.go_back_to(pos) + f.empty_line = was_empty_line + f.line_len = prev_line_len + return str +} + +//=== General Stmt-related methods and helpers ===// + +pub fn (mut f Fmt) stmts(stmts []ast.Stmt) { + mut prev_stmt := if stmts.len > 0 { stmts[0] } else { ast.empty_stmt() } + f.indent++ + for stmt in stmts { + if !f.pref.building_v && f.should_insert_newline_before_node(stmt, prev_stmt) { + f.out.writeln('') + } + f.stmt(stmt) + prev_stmt = stmt + } + f.indent-- +} + +pub fn (mut f Fmt) stmt(node ast.Stmt) { + if f.is_debug { + eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}') + } + match node { + ast.EmptyStmt, ast.NodeError {} + ast.AsmStmt { + f.asm_stmt(node) + } + ast.AssertStmt { + f.assert_stmt(node) + } + ast.AssignStmt { + f.assign_stmt(node) + } + ast.Block { + f.block(node) + } + ast.BranchStmt { + f.branch_stmt(node) + } + ast.CompFor { + f.comp_for(node) + } + ast.ConstDecl { + f.const_decl(node) + } + ast.DeferStmt { + f.defer_stmt(node) + } + ast.EnumDecl { + f.enum_decl(node) + } + ast.ExprStmt { + f.expr_stmt(node) + } + ast.FnDecl { + f.fn_decl(node) + } + ast.ForCStmt { + f.for_c_stmt(node) + } + ast.ForInStmt { + f.for_in_stmt(node) + } + ast.ForStmt { + f.for_stmt(node) + } + ast.GlobalDecl { + f.global_decl(node) + } + ast.GotoLabel { + f.goto_label(node) + } + ast.GotoStmt { + f.goto_stmt(node) + } + ast.HashStmt { + f.hash_stmt(node) + } + ast.Import { + // Imports are handled after the file is formatted, to automatically add necessary modules + // Just remember the position of the imports for now + f.import_pos = f.out.len + } + ast.InterfaceDecl { + f.interface_decl(node) + } + ast.Module { + f.mod(node) + } + ast.Return { + f.return_stmt(node) + } + ast.SqlStmt { + f.sql_stmt(node) + } + ast.StructDecl { + f.struct_decl(node) + } + ast.TypeDecl { + f.type_decl(node) + } + } +} + +fn stmt_is_single_line(stmt ast.Stmt) bool { + return match stmt { + ast.ExprStmt, ast.AssertStmt { expr_is_single_line(stmt.expr) } + ast.Return, ast.AssignStmt, ast.BranchStmt { true } + else { false } + } +} + +//=== General Expr-related methods and helpers ===// + +pub fn (mut f Fmt) expr(node ast.Expr) { + if f.is_debug { + eprintln('expr: ${node.position():-42} | node: ${node.type_name():-20} | $node.str()') + } + match mut node { + ast.NodeError {} + ast.EmptyExpr {} + ast.AnonFn { + f.anon_fn(node) + } + ast.ArrayDecompose { + f.array_decompose(node) + } + ast.ArrayInit { + f.array_init(node) + } + ast.AsCast { + f.as_cast(node) + } + ast.Assoc { + f.assoc(node) + } + ast.AtExpr { + f.at_expr(node) + } + ast.BoolLiteral { + f.write(node.val.str()) + } + ast.CallExpr { + f.call_expr(node) + } + ast.CastExpr { + f.cast_expr(node) + } + ast.ChanInit { + f.chan_init(mut node) + } + ast.CharLiteral { + if node.val == r"\'" { + f.write("`'`") + } else { + f.write('`$node.val`') + } + } + ast.Comment { + f.comment(node, inline: true) + } + ast.ComptimeCall { + f.comptime_call(node) + } + ast.ComptimeSelector { + f.comptime_selector(node) + } + ast.ConcatExpr { + f.concat_expr(node) + } + ast.CTempVar { + eprintln('ast.CTempVar of $node.orig.str() should be generated/used only in cgen') + } + ast.DumpExpr { + f.dump_expr(node) + } + ast.EnumVal { + f.enum_val(node) + } + ast.FloatLiteral { + f.write(node.val) + if node.val.ends_with('.') { + f.write('0') + } + } + ast.GoExpr { + f.go_expr(node) + } + ast.Ident { + f.ident(node) + } + ast.IfExpr { + f.if_expr(node) + } + ast.IfGuardExpr { + f.if_guard_expr(node) + } + ast.IndexExpr { + f.index_expr(node) + } + ast.InfixExpr { + f.infix_expr(node) + } + ast.IntegerLiteral { + f.write(node.val) + } + ast.Likely { + f.likely(node) + } + ast.LockExpr { + f.lock_expr(node) + } + ast.MapInit { + f.map_init(node) + } + ast.MatchExpr { + f.match_expr(node) + } + ast.None { + f.write('none') + } + ast.OffsetOf { + f.offset_of(node) + } + ast.OrExpr { + // shouldn't happen, an or expression is always linked to a call expr or index expr + panic('fmt: OrExpr should be linked to ast.CallExpr or ast.IndexExpr') + } + ast.ParExpr { + f.par_expr(node) + } + ast.PostfixExpr { + f.postfix_expr(node) + } + ast.PrefixExpr { + f.prefix_expr(node) + } + ast.RangeExpr { + f.range_expr(node) + } + ast.SelectExpr { + f.select_expr(node) + } + ast.SelectorExpr { + f.selector_expr(node) + } + ast.SizeOf { + f.size_of(node) + } + ast.IsRefType { + f.is_ref_type(node) + } + ast.SqlExpr { + f.sql_expr(node) + } + ast.StringLiteral { + f.string_literal(node) + } + ast.StringInterLiteral { + f.string_inter_literal(node) + } + ast.StructInit { + f.struct_init(node) + } + ast.TypeNode { + f.type_expr(node) + } + ast.TypeOf { + f.type_of(node) + } + ast.UnsafeExpr { + f.unsafe_expr(node) + } + } +} + +fn expr_is_single_line(expr ast.Expr) bool { + match expr { + ast.Comment, ast.IfExpr, ast.MapInit, ast.MatchExpr { + return false + } + ast.AnonFn { + if !expr.decl.no_body { + return false + } + } + ast.StructInit { + if !expr.is_short && (expr.fields.len > 0 || expr.pre_comments.len > 0) { + return false + } + } + ast.CallExpr { + if expr.or_block.stmts.len > 1 { + return false + } + } + ast.ArrayInit { + if expr.exprs.len > 0 { + return expr_is_single_line(expr.exprs[0]) + } + } + ast.ConcatExpr { + for e in expr.vals { + if !expr_is_single_line(e) { + return false + } + } + } + ast.StringLiteral { + return expr.pos.line_nr == expr.pos.last_line + } + else {} + } + return true +} + +//=== Specific Stmt methods ===// + +pub fn (mut f Fmt) assert_stmt(node ast.AssertStmt) { + f.write('assert ') + if node.expr is ast.ParExpr { + if node.expr.expr is ast.InfixExpr { + infix := node.expr.expr + f.expr(infix) + f.writeln('') + return + } + } + f.expr(node.expr) + f.writeln('') +} + +pub fn (mut f Fmt) assign_stmt(node ast.AssignStmt) { + f.comments(node.comments) + for i, left in node.left { + f.expr(left) + if i < node.left.len - 1 { + f.write(', ') + } + } + f.is_assign = true + f.write(' $node.op.str() ') + for i, val in node.right { + f.prefix_expr_cast_expr(val) + if i < node.right.len - 1 { + f.write(', ') + } + } + f.comments(node.end_comments, has_nl: false, inline: true, level: .keep) + if !f.single_line_if { + f.writeln('') + } + f.is_assign = false +} + +pub fn (mut f Fmt) block(node ast.Block) { + if node.is_unsafe { + f.write('unsafe ') + } + f.write('{') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Fmt) branch_stmt(node ast.BranchStmt) { + f.writeln(node.str()) +} + +pub fn (mut f Fmt) comp_for(node ast.CompFor) { + typ := f.no_cur_mod(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write('\$for $node.val_var in ${typ}.$node.kind.str() {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +struct ConstAlignInfo { +mut: + max int + last_idx int +} + +pub fn (mut f Fmt) const_decl(node ast.ConstDecl) { + if node.is_pub { + f.write('pub ') + } + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('const ()\n') + return + } + f.inside_const = true + defer { + f.inside_const = false + } + f.write('const ') + mut align_infos := []ConstAlignInfo{} + if node.is_block { + f.writeln('(') + mut info := ConstAlignInfo{} + for i, field in node.fields { + if field.name.len > info.max { + info.max = field.name.len + } + if !expr_is_single_line(field.expr) { + info.last_idx = i + align_infos << info + info = ConstAlignInfo{} + } + } + info.last_idx = node.fields.len + align_infos << info + f.indent++ + } else { + align_infos << ConstAlignInfo{0, 1} + } + mut prev_field := if node.fields.len > 0 { + ast.Node(node.fields[0]) + } else { + ast.Node(ast.NodeError{}) + } + mut align_idx := 0 + for i, field in node.fields { + if i > align_infos[align_idx].last_idx { + align_idx++ + } + if field.comments.len > 0 { + if f.should_insert_newline_before_node(ast.Expr(field.comments[0]), prev_field) { + f.writeln('') + } + f.comments(field.comments, inline: true) + prev_field = ast.Expr(field.comments.last()) + } + if node.is_block && f.should_insert_newline_before_node(field, prev_field) { + f.writeln('') + } + name := field.name.after('.') + f.write('$name ') + f.write(strings.repeat(` `, align_infos[align_idx].max - field.name.len)) + f.write('= ') + f.expr(field.expr) + f.writeln('') + prev_field = field + } + f.comments_after_last_field(node.end_comments) + if node.is_block { + f.indent-- + f.writeln(')\n') + } else { + f.writeln('') + } +} + +pub fn (mut f Fmt) defer_stmt(node ast.DeferStmt) { + f.write('defer {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Fmt) expr_stmt(node ast.ExprStmt) { + f.comments(node.comments) + f.expr(node.expr) + if !f.single_line_if { + f.writeln('') + } +} + +pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + name := node.name.after('.') + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('enum $name {}\n') + return + } + f.writeln('enum $name {') + f.comments(node.comments, inline: true, level: .indent) + for field in node.fields { + f.write('\t$field.name') + if field.has_expr { + f.write(' = ') + f.expr(field.expr) + } + f.comments(field.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + f.comments(field.next_comments, inline: false, has_nl: true, level: .indent) + } + f.writeln('}\n') +} + +pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { + f.attrs(node.attrs) + f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.fn_body(node) +} + +pub fn (mut f Fmt) anon_fn(node ast.AnonFn) { + f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.fn_body(node.decl) +} + +fn (mut f Fmt) fn_body(node ast.FnDecl) { + prev_fn_scope := f.fn_scope + f.fn_scope = node.scope + defer { + f.fn_scope = prev_fn_scope + } + if node.language == .v { + if !node.no_body { + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.write('}') + } + if !node.is_anon { + f.writeln('') + } + } else { + f.writeln('') + } + // Mark all function's used type so that they are not removed from imports + for arg in node.params { + f.mark_types_import_as_used(arg.typ) + } + f.mark_types_import_as_used(node.return_type) +} + +pub fn (mut f Fmt) for_c_stmt(node ast.ForCStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + if node.has_init { + f.single_line_if = true // to keep all for ;; exprs on the same line + f.stmt(node.init) + f.single_line_if = false + } + f.write('; ') + f.expr(node.cond) + f.write('; ') + f.stmt(node.inc) + f.remove_new_line() + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Fmt) for_in_stmt(node ast.ForInStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + if node.key_var != '' { + f.write(node.key_var) + } + if node.val_var != '' { + if node.key_var != '' { + f.write(', ') + } + if node.val_is_mut { + f.write('mut ') + } + f.write(node.val_var) + } + f.write(' in ') + f.expr(node.cond) + if node.is_range { + f.write(' .. ') + f.expr(node.high) + } + f.write(' {') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Fmt) for_stmt(node ast.ForStmt) { + if node.label.len > 0 { + f.write('$node.label: ') + } + f.write('for ') + f.expr(node.cond) + if !node.is_inf { + f.write(' ') + } + f.write('{') + if node.stmts.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + f.stmts(node.stmts) + } + f.writeln('}') +} + +pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) { + if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln('__global ()') + return + } + f.write('__global ') + mut max := 0 + // mut has_assign := false + if node.is_block { + f.writeln('(') + f.indent++ + for field in node.fields { + if field.name.len > max { + max = field.name.len + } + // if field.has_expr { + // has_assign = true + //} + } + } + for field in node.fields { + f.comments(field.comments, inline: true) + f.write('$field.name ') + f.write(strings.repeat(` `, max - field.name.len)) + if field.has_expr { + f.write('= ') + f.expr(field.expr) + } else { + f.write('${f.table.type_to_str_using_aliases(field.typ, f.mod2alias)}') + } + if node.is_block { + f.writeln('') + } + f.mark_types_import_as_used(field.typ) + } + f.comments_after_last_field(node.end_comments) + if node.is_block { + f.indent-- + f.writeln(')') + } else { + f.writeln('') + } +} + +pub fn (mut f Fmt) go_expr(node ast.GoExpr) { + f.write('go ') + f.call_expr(node.call_expr) +} + +pub fn (mut f Fmt) goto_label(node ast.GotoLabel) { + f.writeln('$node.name:') +} + +pub fn (mut f Fmt) goto_stmt(node ast.GotoStmt) { + f.writeln('goto $node.name') +} + +pub fn (mut f Fmt) hash_stmt(node ast.HashStmt) { + f.writeln('#$node.val') +} + +pub fn (mut f Fmt) interface_decl(node ast.InterfaceDecl) { + if node.is_pub { + f.write('pub ') + } + f.write('interface ') + f.write_language_prefix(node.language) + name := node.name.after('.') // strip prepended module + f.write(name) + f.write_generic_types(node.generic_types) + f.write(' {') + if node.fields.len > 0 || node.methods.len > 0 || node.pos.line_nr < node.pos.last_line { + f.writeln('') + } + f.comments_before_field(node.pre_comments) + for iface in node.ifaces { + f.write('\t$iface.name') + f.comments(iface.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + } + immut_fields := if node.mut_pos < 0 { node.fields } else { node.fields[..node.mut_pos] } + mut_fields := if node.mut_pos < 0 { []ast.StructField{} } else { node.fields[node.mut_pos..] } + + mut immut_methods := node.methods + mut mut_methods := []ast.FnDecl{} + for i, method in node.methods { + if method.params[0].is_mut { + immut_methods = node.methods[..i] + mut_methods = node.methods[i..] + break + } + } + + // TODO: alignment, comments, etc. + for field in immut_fields { + mut ft := f.no_cur_mod(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) + f.writeln('\t$field.name $ft') + f.mark_types_import_as_used(field.typ) + } + for method in immut_methods { + f.write('\t') + f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn ')) + f.comments(method.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + f.comments(method.next_comments, inline: false, has_nl: true, level: .indent) + for param in method.params { + f.mark_types_import_as_used(param.typ) + } + f.mark_types_import_as_used(method.return_type) + } + if mut_fields.len + mut_methods.len > 0 { + f.writeln('mut:') + for field in mut_fields { + mut ft := f.no_cur_mod(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) + f.writeln('\t$field.name $ft') + f.mark_types_import_as_used(field.typ) + } + for method in mut_methods { + f.write('\t') + f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn ')) + f.comments(method.comments, inline: true, has_nl: false, level: .indent) + f.writeln('') + f.comments(method.next_comments, inline: false, has_nl: true, level: .indent) + for param in method.params { + f.mark_types_import_as_used(param.typ) + } + f.mark_types_import_as_used(method.return_type) + } + } + f.writeln('}\n') +} + +pub fn (mut f Fmt) mod(mod ast.Module) { + f.set_current_module_name(mod.name) + if mod.is_skipped { + return + } + f.attrs(mod.attrs) + f.writeln('module $mod.short_name\n') + if f.import_pos == 0 { + f.import_pos = f.out.len + } +} + +pub fn (mut f Fmt) return_stmt(node ast.Return) { + f.comments(node.comments) + f.write('return') + if node.exprs.len > 0 { + f.write(' ') + // Loop over all return values. In normal returns this will only run once. + for i, expr in node.exprs { + f.expr(expr) + if i < node.exprs.len - 1 { + f.write(', ') + } + } + } + f.writeln('') +} + +pub fn (mut f Fmt) sql_stmt(node ast.SqlStmt) { + f.write('sql ') + f.expr(node.db_expr) + f.writeln(' {') + + for line in node.lines { + f.sql_stmt_line(line) + } + + f.writeln('}') +} + +pub fn (mut f Fmt) sql_stmt_line(node ast.SqlStmtLine) { + table_name := util.strip_mod_name(f.table.get_type_symbol(node.table_expr.typ).name) + f.mark_types_import_as_used(node.table_expr.typ) + f.write('\t') + match node.kind { + .insert { + f.writeln('insert $node.object_var_name into $table_name') + } + .update { + f.write('update $table_name set ') + for i, col in node.updated_columns { + f.write('$col = ') + f.expr(node.update_exprs[i]) + if i < node.updated_columns.len - 1 { + f.write(', ') + } else { + f.write(' ') + } + f.wrap_long_line(3, true) + } + f.write('where ') + f.expr(node.where_expr) + f.writeln('') + } + .delete { + f.write('delete from $table_name where ') + f.expr(node.where_expr) + f.writeln('') + } + .create { + f.writeln('create table $table_name') + } + .drop { + f.writeln('drop table $table_name') + } + } +} + +pub fn (mut f Fmt) type_decl(node ast.TypeDecl) { + match node { + ast.AliasTypeDecl { f.alias_type_decl(node) } + ast.FnTypeDecl { f.fn_type_decl(node) } + ast.SumTypeDecl { f.sum_type_decl(node) } + } + f.writeln('') +} + +pub fn (mut f Fmt) alias_type_decl(node ast.AliasTypeDecl) { + if node.is_pub { + f.write('pub ') + } + ptype := f.table.type_to_str_using_aliases(node.parent_type, f.mod2alias) + f.write('type $node.name = $ptype') + + f.comments(node.comments, has_nl: false) +} + +pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) { + if node.is_pub { + f.write('pub ') + } + typ_sym := f.table.get_type_symbol(node.typ) + fn_typ_info := typ_sym.info as ast.FnType + fn_info := fn_typ_info.func + fn_name := f.no_cur_mod(node.name) + f.write('type $fn_name = fn (') + for i, arg in fn_info.params { + if arg.is_mut { + f.write(arg.typ.share().str() + ' ') + } + f.write(arg.name) + mut s := f.no_cur_mod(f.table.type_to_str_using_aliases(arg.typ, f.mod2alias)) + if arg.is_mut { + if s.starts_with('&') { + s = s[1..] + } + } + is_last_arg := i == fn_info.params.len - 1 + should_add_type := true || is_last_arg + || fn_info.params[i + 1].typ != arg.typ + || (fn_info.is_variadic && i == fn_info.params.len - 2) + if should_add_type { + ns := if arg.name == '' { '' } else { ' ' } + if fn_info.is_variadic && is_last_arg { + f.write(ns + '...' + s) + } else { + f.write(ns + s) + } + } + if !is_last_arg { + f.write(', ') + } + } + f.write(')') + if fn_info.return_type.idx() != ast.void_type_idx { + ret_str := f.no_cur_mod(f.table.type_to_str_using_aliases(fn_info.return_type, + f.mod2alias)) + f.write(' $ret_str') + } else if fn_info.return_type.has_flag(.optional) { + f.write(' ?') + } + + f.comments(node.comments, has_nl: false) + f.writeln('') +} + +pub fn (mut f Fmt) sum_type_decl(node ast.SumTypeDecl) { + if node.is_pub { + f.write('pub ') + } + f.write('type $node.name') + f.write_generic_types(node.generic_types) + f.write(' = ') + + mut sum_type_names := []string{} + for t in node.variants { + sum_type_names << f.table.type_to_str_using_aliases(t.typ, f.mod2alias) + } + sum_type_names.sort() + for i, name in sum_type_names { + f.write(name) + if i < sum_type_names.len - 1 { + f.write(' | ') + } + if i < sum_type_names.len - 1 { + f.wrap_long_line(3, true) + } + } + + f.comments(node.comments, has_nl: false) +} + +//=== Specific Expr methods ===// + +pub fn (mut f Fmt) array_decompose(node ast.ArrayDecompose) { + f.write('...') + f.expr(node.expr) +} + +pub fn (mut f Fmt) array_init(node ast.ArrayInit) { + if node.exprs.len == 0 && node.typ != 0 && node.typ != ast.void_type { + // `x := []string{}` + f.mark_types_import_as_used(node.typ) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write('{') + if node.has_len { + f.write('len: ') + f.expr(node.len_expr) + if node.has_cap || node.has_default { + f.write(', ') + } + } + if node.has_cap { + f.write('cap: ') + f.expr(node.cap_expr) + if node.has_default { + f.write(', ') + } + } + if node.has_default { + f.write('init: ') + f.expr(node.default_expr) + } + f.write('}') + return + } + // `[1,2,3]` + f.write('[') + mut inc_indent := false + mut last_line_nr := node.pos.line_nr // to have the same newlines between array elements + f.array_init_depth++ + if node.pre_cmnts.len > 0 { + if node.pre_cmnts[0].pos.line_nr > last_line_nr { + f.writeln('') + } + } + for i, c in node.pre_cmnts { + if i < node.pre_cmnts.len - 1 { + if c.pos.last_line < node.pre_cmnts[i + 1].pos.line_nr { + f.comment(c, level: .indent) + f.writeln('') + } else { + f.comment(c, level: .indent, iembed: true) + f.write(' ') + } + } else { + next_line := if node.exprs.len > 0 { + node.exprs[0].position().line_nr + } else { + node.pos.last_line + } + if c.pos.last_line < next_line { + f.comment(c, level: .indent) + if node.exprs.len == 0 { + f.writeln('') + } + } else { + f.comment(c, level: .indent, iembed: true) + if node.exprs.len > 0 { + f.write(' ') + } + } + } + last_line_nr = c.pos.last_line + } + mut set_comma := false + for i, expr in node.exprs { + pos := expr.position() + if i == 0 { + if f.array_init_depth > f.array_init_break.len { + f.array_init_break << pos.line_nr > last_line_nr + } + } + line_break := f.array_init_break[f.array_init_depth - 1] + mut penalty := if line_break { 0 } else { 4 } + if penalty > 0 { + if i == 0 || should_decrease_arr_penalty(node.exprs[i - 1]) { + penalty-- + } + if should_decrease_arr_penalty(expr) { + penalty-- + } + } + mut is_new_line := f.wrap_long_line(penalty, !inc_indent) + if is_new_line && !inc_indent { + f.indent++ + inc_indent = true + } + single_line_expr := expr_is_single_line(expr) + if single_line_expr { + estr := f.node_str(expr) + if !is_new_line && !f.buffering && f.line_len + estr.len > fmt.max_len.last() { + f.writeln('') + is_new_line = true + if !inc_indent { + f.indent++ + inc_indent = true + } + } + if !is_new_line && i > 0 { + f.write(' ') + } + f.write(estr) + } else { + if !is_new_line && i > 0 { + f.write(' ') + } + f.expr(expr) + } + mut last_comment_was_inline := false + mut has_comments := node.ecmnts[i].len > 0 + if i < node.ecmnts.len && has_comments { + expr_pos := expr.position() + for icmt, cmt in node.ecmnts[i] { + if !set_comma && cmt.pos.pos > expr_pos.pos + expr_pos.len + 2 { + if icmt > 0 { + if last_comment_was_inline { + f.write(',') + set_comma = true + } + } else { + f.write(',') // first comment needs a comma + set_comma = true + } + } + mut next_pos := expr_pos + if i + 1 < node.exprs.len { + next_pos = node.exprs[i + 1].position() + } + if cmt.pos.line_nr > expr_pos.last_line { + embed := i + 1 < node.exprs.len && next_pos.line_nr == cmt.pos.last_line + f.writeln('') + f.comment(cmt, iembed: embed) + } else { + if cmt.is_inline { + f.write(' ') + f.comment(cmt, iembed: true) + if cmt.pos.line_nr == expr_pos.last_line && cmt.pos.pos < expr_pos.pos { + f.write(',') + set_comma = true + } else { + if !cmt.is_inline { + // a // comment, transformed to a /**/ one, needs a comma too + f.write(',') + set_comma = true + } + } + } else { + f.write(', ') + f.comment(cmt, iembed: false) + set_comma = true + } + } + last_comment_was_inline = cmt.is_inline + } + } + mut put_comma := !set_comma + if has_comments && !last_comment_was_inline { + put_comma = false + } + if i == node.exprs.len - 1 { + if is_new_line { + if put_comma { + f.write(',') + } + f.writeln('') + } + } else if put_comma { + f.write(',') + } + last_line_nr = pos.last_line + set_comma = false + } + f.array_init_depth-- + if f.array_init_depth == 0 { + f.array_init_break = [] + } + if inc_indent { + f.indent-- + } + f.write(']') + // `[100]byte` + if node.is_fixed { + if node.has_val { + f.write('!') + return + } + f.write(f.table.type_to_str_using_aliases(node.elem_type, f.mod2alias)) + if node.has_default { + f.write('{init: ') + f.expr(node.default_expr) + f.write('}') + } else { + f.write('{}') + } + } +} + +fn should_decrease_arr_penalty(e ast.Expr) bool { + if e is ast.ArrayInit || e is ast.StructInit || e is ast.MapInit || e is ast.CallExpr { + return true + } + return false +} + +pub fn (mut f Fmt) as_cast(node ast.AsCast) { + f.mark_types_import_as_used(node.typ) + type_str := f.table.type_to_str_using_aliases(node.typ, f.mod2alias) + f.expr(node.expr) + f.write(' as $type_str') +} + +pub fn (mut f Fmt) assoc(node ast.Assoc) { + f.writeln('{') + f.indent++ + f.writeln('...$node.var_name') + for i, field in node.fields { + f.write('$field: ') + f.expr(node.exprs[i]) + f.writeln('') + } + f.indent-- + f.write('}') +} + +pub fn (mut f Fmt) at_expr(node ast.AtExpr) { + f.write(node.name) +} + +pub fn (mut f Fmt) call_expr(node ast.CallExpr) { + for arg in node.args { + f.comments(arg.comments) + } + if node.is_method { + if node.name in ['map', 'filter'] { + f.inside_lambda = true + defer { + f.inside_lambda = false + } + } + if node.left is ast.Ident { + // `time.now()` without `time imported` is processed as a method call with `time` being + // a `node.left` expression. Import `time` automatically. + // TODO fetch all available modules + if node.left.name in ['time', 'os', 'strings', 'math', 'json', 'base64'] { + f.file.imports << ast.Import{ + mod: node.left.name + alias: node.left.name + } + } + } + f.expr(node.left) + f.write('.' + node.name) + } else { + f.write_language_prefix(node.language) + if node.left is ast.AnonFn { + f.anon_fn(node.left) + } else if node.language != .v { + f.write('${node.name.after_char(`.`)}') + } else { + mut name := f.short_module(node.name) + f.mark_import_as_used(name) + f.write('$name') + } + } + if node.mod == '' && node.name == '' { + f.write(node.left.str()) + } + f.write_generic_call_if_require(node) + f.write('(') + f.call_args(node.args) + f.write(')') + f.or_expr(node.or_block) + f.comments(node.comments, has_nl: false) +} + +fn (mut f Fmt) write_generic_call_if_require(node ast.CallExpr) { + if node.concrete_types.len > 0 { + f.write('<') + for i, concrete_type in node.concrete_types { + f.write(f.table.type_to_str_using_aliases(concrete_type, f.mod2alias)) + if i != node.concrete_types.len - 1 { + f.write(', ') + } + } + // avoid `>` => ` >` + if f.out.last_n(1) == '>' { + f.write(' ') + } + f.write('>') + } +} + +pub fn (mut f Fmt) call_args(args []ast.CallArg) { + f.single_line_fields = true + old_short_arg_state := f.use_short_fn_args + f.use_short_fn_args = false + defer { + f.single_line_fields = false + f.use_short_fn_args = old_short_arg_state + } + for i, arg in args { + if i == args.len - 1 && arg.expr is ast.StructInit { + if arg.expr.typ == ast.void_type { + f.use_short_fn_args = true + } + } + if arg.is_mut { + f.write(arg.share.str() + ' ') + } + if i > 0 && !f.single_line_if { + f.wrap_long_line(3, true) + } + f.expr(arg.expr) + if i < args.len - 1 { + f.write(', ') + } + } +} + +pub fn (mut f Fmt) cast_expr(node ast.CastExpr) { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias) + '(') + f.mark_types_import_as_used(node.typ) + f.expr(node.expr) + if node.has_arg { + f.write(', ') + f.expr(node.arg) + } + f.write(')') +} + +pub fn (mut f Fmt) chan_init(mut node ast.ChanInit) { + info := f.table.get_type_symbol(node.typ).chan_info() + if node.elem_type == 0 && node.typ > 0 { + node.elem_type = info.elem_type + } + is_mut := info.is_mut + el_typ := if is_mut { + node.elem_type.set_nr_muls(node.elem_type.nr_muls() - 1) + } else { + node.elem_type + } + f.write('chan ') + if is_mut { + f.write('mut ') + } + f.write(f.table.type_to_str_using_aliases(el_typ, f.mod2alias)) + f.write('{') + if node.has_cap { + f.write('cap: ') + f.expr(node.cap_expr) + } + f.write('}') +} + +pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) { + if node.is_vweb { + if node.method_name == 'html' { + f.write('\$vweb.html()') + } else { + f.write("\$tmpl('$node.args_var')") + } + } else { + if node.is_embed { + f.write("\$embed_file('$node.embed_file.rpath')") + } else if node.is_env { + f.write("\$env('$node.args_var')") + } else if node.is_pkgconfig { + f.write("\$pkgconfig('$node.args_var')") + } else { + inner_args := if node.args_var != '' { + node.args_var + } else { + node.args.map(it.str()).join(', ') + } + method_expr := if node.has_parens { + '(${node.method_name}($inner_args))' + } else { + '${node.method_name}($inner_args)' + } + f.write('${node.left}.$$method_expr') + } + } +} + +pub fn (mut f Fmt) comptime_selector(node ast.ComptimeSelector) { + f.write('${node.left}.\$($node.field_expr)') +} + +pub fn (mut f Fmt) concat_expr(node ast.ConcatExpr) { + for i, val in node.vals { + if i != 0 { + f.write(', ') + } + f.expr(val) + } +} + +pub fn (mut f Fmt) dump_expr(node ast.DumpExpr) { + f.write('dump(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Fmt) enum_val(node ast.EnumVal) { + name := f.short_module(node.enum_name) + f.write(name + '.' + node.val) + f.mark_import_as_used(name) +} + +pub fn (mut f Fmt) ident(node ast.Ident) { + if mut node.info is ast.IdentVar { + if node.info.is_mut { + f.write(node.info.share.str() + ' ') + } + var_info := node.var_info() + if var_info.is_static { + f.write('static ') + } + } + f.write_language_prefix(node.language) + if node.name == 'it' && f.it_name != '' && !f.inside_lambda { // allow `it` in lambdas + f.write(f.it_name) + } else if node.kind == .blank_ident { + f.write('_') + } else { + mut is_local := false + if !isnil(f.fn_scope) { + if _ := f.fn_scope.find_var(node.name) { + is_local = true + } + } + if !is_local && !node.name.contains('.') && !f.inside_const { + // Force usage of full path to const in the same module: + // `println(minute)` => `println(time.minute)` + // This makes it clear that a module const is being used + // (since V's consts are no longer ALL_CAP). + // ^^^ except for `main`, where consts are allowed to not have a `main.` prefix. + mod := f.cur_mod + full_name := mod + '.' + node.name + if obj := f.file.global_scope.find(full_name) { + if obj is ast.ConstField { + // "v.fmt.foo" => "fmt.foo" + vals := full_name.split('.') + mod_prefix := vals[vals.len - 2] + const_name := vals[vals.len - 1] + if mod_prefix == 'main' { + f.write(const_name) + return + } else { + short := mod_prefix + '.' + const_name + f.write(short) + f.mark_import_as_used(short) + return + } + } + } + } + name := f.short_module(node.name) + f.write(name) + f.mark_import_as_used(name) + } +} + +pub fn (mut f Fmt) if_expr(node ast.IfExpr) { + dollar := if node.is_comptime { '$' } else { '' } + mut is_ternary := node.branches.len == 2 && node.has_else + && branch_is_single_line(node.branches[0]) && branch_is_single_line(node.branches[1]) + && (node.is_expr || f.is_assign || f.is_struct_init || f.single_line_fields) + f.single_line_if = is_ternary + start_pos := f.out.len + start_len := f.line_len + for { + for i, branch in node.branches { + if i == 0 { + // first `if` + f.comments(branch.comments) + } else { + // `else`, close previous branch + if branch.comments.len > 0 { + f.writeln('}') + f.comments(branch.comments) + } else { + f.write('} ') + } + f.write('${dollar}else ') + } + if i < node.branches.len - 1 || !node.has_else { + f.write('${dollar}if ') + cur_pos := f.out.len + f.expr(branch.cond) + cond_len := f.out.len - cur_pos + is_cond_wrapped := cond_len > 0 + && (branch.cond is ast.IfGuardExpr || branch.cond is ast.CallExpr) + && f.out.last_n(cond_len).contains('\n') + if is_cond_wrapped { + f.writeln('') + } else { + f.write(' ') + } + } + f.write('{') + if is_ternary { + f.write(' ') + } else { + f.writeln('') + } + f.stmts(branch.stmts) + if is_ternary { + f.write(' ') + } + } + // When a single line if is really long, write it again as multiline, + // except it is part of an InfixExpr. + if is_ternary && f.line_len > fmt.max_len.last() && !f.buffering { + is_ternary = false + f.single_line_if = false + f.out.go_back_to(start_pos) + f.line_len = start_len + f.empty_line = start_len == 0 + continue + } + break + } + f.write('}') + f.single_line_if = false + if node.post_comments.len > 0 { + f.writeln('') + f.comments(node.post_comments, + has_nl: false + prev_line: node.branches.last().body_pos.last_line + ) + } +} + +fn branch_is_single_line(b ast.IfBranch) bool { + if b.stmts.len == 1 && b.comments.len == 0 && stmt_is_single_line(b.stmts[0]) { + return true + } + return false +} + +pub fn (mut f Fmt) if_guard_expr(node ast.IfGuardExpr) { + f.write(node.var_name + ' := ') + f.expr(node.expr) +} + +pub fn (mut f Fmt) index_expr(node ast.IndexExpr) { + f.expr(node.left) + f.write('[') + f.expr(node.index) + f.write(']') + if node.or_expr.kind != .absent { + f.or_expr(node.or_expr) + } +} + +pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) { + buffering_save := f.buffering + if !f.buffering && node.op in [.logical_or, .and, .plus] { + f.buffering = true + } + is_assign_save := f.is_assign + if node.op == .left_shift { + f.is_assign = true // To write ternary if on a single line + } + start_pos := f.out.len + start_len := f.line_len + f.expr(node.left) + is_one_val_array_init := node.op in [.key_in, .not_in] && node.right is ast.ArrayInit + && (node.right as ast.ArrayInit).exprs.len == 1 + if is_one_val_array_init { + // `var in [val]` => `var == val` + op := if node.op == .key_in { ' == ' } else { ' != ' } + f.write(op) + } else { + f.write(' $node.op.str() ') + } + if is_one_val_array_init { + // `var in [val]` => `var == val` + f.expr((node.right as ast.ArrayInit).exprs[0]) + } else { + f.expr(node.right) + } + if !buffering_save && f.buffering { + f.buffering = false + if !f.single_line_if && f.line_len > fmt.max_len.last() { + is_cond := node.op in [.and, .logical_or] + f.wrap_infix(start_pos, start_len, is_cond) + } + } + f.is_assign = is_assign_save + f.or_expr(node.or_block) +} + +pub fn (mut f Fmt) wrap_infix(start_pos int, start_len int, is_cond bool) { + cut_span := f.out.len - start_pos + infix_str := f.out.cut_last(cut_span) + if !infix_str.contains_any_substr(['&&', '||', '+']) { + f.write(infix_str) + return + } + f.line_len = start_len + if start_len == 0 { + f.empty_line = true + } + conditions, penalties := split_up_infix(infix_str, false, is_cond) + f.write_splitted_infix(conditions, penalties, false, is_cond) +} + +fn split_up_infix(infix_str string, ignore_paren bool, is_cond_infix bool) ([]string, []int) { + mut conditions := [''] + mut penalties := [5] + or_pen := if infix_str.contains('&&') { 3 } else { 5 } + parts := infix_str.split(' ') + mut inside_paren := false + mut ind := 0 + for p in parts { + if is_cond_infix && p in ['&&', '||'] { + if inside_paren { + conditions[ind] += '$p ' + } else { + pen := if p == '||' { or_pen } else { 5 } + penalties << pen + conditions << '$p ' + ind++ + } + } else if !is_cond_infix && p == '+' { + penalties << 5 + conditions[ind] += '$p ' + conditions << '' + ind++ + } else { + conditions[ind] += '$p ' + if ignore_paren { + continue + } + if p.starts_with('(') { + inside_paren = true + } else if p.ends_with(')') { + inside_paren = false + } + } + } + return conditions, penalties +} + +fn (mut f Fmt) write_splitted_infix(conditions []string, penalties []int, ignore_paren bool, is_cond bool) { + for i, cnd in conditions { + c := cnd.trim_space() + if f.line_len + c.len < fmt.max_len[penalties[i]] { + if (i > 0 && i < conditions.len) || (ignore_paren && i == 0 && c.len > 5 && c[3] == `(`) { + f.write(' ') + } + f.write(c) + } else { + is_paren_expr := (c[0] == `(` || (c.len > 5 && c[3] == `(`)) && c.ends_with(')') + final_len := ((f.indent + 1) * 4) + c.len + if final_len > fmt.max_len.last() && is_paren_expr { + conds, pens := split_up_infix(c, true, is_cond) + f.write_splitted_infix(conds, pens, true, is_cond) + continue + } + if i == 0 { + f.remove_new_line() + } + f.writeln('') + f.indent++ + f.write(c) + f.indent-- + } + } +} + +pub fn (mut f Fmt) likely(node ast.Likely) { + if node.is_likely { + f.write('_likely_') + } else { + f.write('_unlikely_') + } + f.write('(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Fmt) lock_expr(node ast.LockExpr) { + mut num_locked := 0 + mut num_rlocked := 0 + for is_rlock in node.is_rlock { + if is_rlock { + num_rlocked++ + } else { + num_locked++ + } + } + if num_locked > 0 || num_rlocked == 0 { + f.write('lock ') + mut n := 0 + for i, v in node.lockeds { + if !node.is_rlock[i] { + if n > 0 { + f.write(', ') + } + f.expr(v) + n++ + } + } + } + if num_rlocked > 0 { + if num_locked > 0 { + f.write('; ') + } + f.write('rlock ') + mut n := 0 + for i, v in node.lockeds { + if node.is_rlock[i] { + if n > 0 { + f.write(', ') + } + f.expr(v) + n++ + } + } + } + f.writeln(' {') + f.stmts(node.stmts) + f.write('}') +} + +pub fn (mut f Fmt) map_init(node ast.MapInit) { + if node.keys.len == 0 { + if node.typ > ast.void_type { + sym := f.table.get_type_symbol(node.typ) + info := sym.info as ast.Map + f.mark_types_import_as_used(info.key_type) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } + f.write('{}') + return + } + f.writeln('{') + f.indent++ + f.comments(node.pre_cmnts) + mut max_field_len := 0 + for key in node.keys { + if key.str().len > max_field_len { + max_field_len = key.str().len + } + } + for i, key in node.keys { + f.expr(key) + f.write(': ') + f.write(strings.repeat(` `, max_field_len - key.str().len)) + f.expr(node.vals[i]) + f.comments(node.comments[i], prev_line: node.vals[i].position().last_line, has_nl: false) + f.writeln('') + } + f.indent-- + f.write('}') +} + +fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool) { + if !branch.is_else { + // normal branch + f.is_mbranch_expr = true + for j, expr in branch.exprs { + estr := f.node_str(expr).trim_space() + if f.line_len + estr.len + 2 > fmt.max_len[5] { + f.remove_new_line() + f.writeln('') + } + f.write(estr) + if j < branch.ecmnts.len && branch.ecmnts[j].len > 0 { + f.write(' ') + f.comments(branch.ecmnts[j], iembed: true) + } + if j < branch.exprs.len - 1 { + f.write(', ') + } + } + f.is_mbranch_expr = false + } else { + // else branch + f.write('else') + } + if branch.stmts.len == 0 { + f.writeln(' {}') + } else { + if single_line { + f.write(' { ') + } else { + f.writeln(' {') + } + f.stmts(branch.stmts) + if single_line { + f.remove_new_line() + f.writeln(' }') + } else { + f.writeln('}') + } + } + f.comments(branch.post_comments, inline: true) +} + +pub fn (mut f Fmt) match_expr(node ast.MatchExpr) { + f.write('match ') + f.expr(node.cond) + if node.cond is ast.Ident { + f.it_name = node.cond.name + } + f.writeln(' {') + f.indent++ + f.comments(node.comments) + mut single_line := true + for branch in node.branches { + if branch.stmts.len > 1 || branch.pos.line_nr < branch.pos.last_line { + single_line = false + break + } + if branch.stmts.len == 0 { + continue + } + if !stmt_is_single_line(branch.stmts[0]) { + single_line = false + break + } + } + mut else_idx := -1 + for i, branch in node.branches { + if branch.is_else { + else_idx = i + continue + } + f.match_branch(branch, single_line) + } + if else_idx >= 0 { + f.match_branch(node.branches[else_idx], single_line) + } + f.indent-- + f.write('}') + f.it_name = '' +} + +pub fn (mut f Fmt) offset_of(node ast.OffsetOf) { + f.write('__offsetof(${f.table.type_to_str_using_aliases(node.struct_type, f.mod2alias)}, $node.field)') + f.mark_types_import_as_used(node.struct_type) +} + +pub fn (mut f Fmt) or_expr(node ast.OrExpr) { + match node.kind { + .absent {} + .block { + if node.stmts.len == 0 { + f.write(' or {') + if node.pos.line_nr != node.pos.last_line { + f.writeln('') + } + f.write('}') + return + } else if node.stmts.len == 1 && stmt_is_single_line(node.stmts[0]) { + // the control stmts (return/break/continue...) print a newline inside them, + // so, since this'll all be on one line, trim any possible whitespace + str := f.node_str(node.stmts[0]).trim_space() + single_line := ' or { $str }' + if single_line.len + f.line_len <= fmt.max_len.last() { + f.write(single_line) + return + } + } + // Make it multiline if the blocks has at least two stmts + // or a single line would be too long + f.writeln(' or {') + f.stmts(node.stmts) + f.write('}') + } + .propagate { + f.write(' ?') + } + } +} + +pub fn (mut f Fmt) par_expr(node ast.ParExpr) { + requires_paren := node.expr !is ast.Ident + if requires_paren { + f.par_level++ + f.write('(') + } + f.expr(node.expr) + if requires_paren { + f.par_level-- + f.write(')') + } +} + +pub fn (mut f Fmt) postfix_expr(node ast.PostfixExpr) { + f.expr(node.expr) + // `$if foo ?` + if node.op == .question { + f.write(' ?') + } else { + f.write('$node.op') + } +} + +pub fn (mut f Fmt) prefix_expr(node ast.PrefixExpr) { + // !(a in b) => a !in b, !(a is b) => a !is b + if node.op == .not && node.right is ast.ParExpr { + if node.right.expr is ast.InfixExpr { + if node.right.expr.op in [.key_in, .not_in, .key_is, .not_is] + && node.right.expr.right !is ast.InfixExpr { + f.expr(node.right.expr.left) + if node.right.expr.op == .key_in { + f.write(' !in ') + } else if node.right.expr.op == .not_in { + f.write(' in ') + } else if node.right.expr.op == .key_is { + f.write(' !is ') + } else if node.right.expr.op == .not_is { + f.write(' is ') + } + f.expr(node.right.expr.right) + return + } + } + } + f.write(node.op.str()) + f.prefix_expr_cast_expr(node.right) + f.or_expr(node.or_block) +} + +pub fn (mut f Fmt) range_expr(node ast.RangeExpr) { + f.expr(node.low) + if f.is_mbranch_expr { + f.write('...') + } else { + f.write('..') + } + f.expr(node.high) +} + +pub fn (mut f Fmt) select_expr(node ast.SelectExpr) { + f.writeln('select {') + f.indent++ + for branch in node.branches { + if branch.comment.text != '' { + f.comment(branch.comment, inline: true) + f.writeln('') + } + if branch.is_else { + f.write('else {') + } else { + f.single_line_if = true + match branch.stmt { + ast.ExprStmt { f.expr(branch.stmt.expr) } + else { f.stmt(branch.stmt) } + } + f.single_line_if = false + f.write(' {') + } + if branch.stmts.len > 0 { + f.writeln('') + f.stmts(branch.stmts) + } + f.writeln('}') + if branch.post_comments.len > 0 { + f.comments(branch.post_comments, inline: true) + } + } + f.indent-- + f.write('}') +} + +pub fn (mut f Fmt) selector_expr(node ast.SelectorExpr) { + f.expr(node.expr) + f.write('.') + f.write(node.field_name) +} + +pub fn (mut f Fmt) size_of(node ast.SizeOf) { + f.write('sizeof(') + if node.is_type { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } else { + f.expr(node.expr) + } + f.write(')') +} + +pub fn (mut f Fmt) is_ref_type(node ast.IsRefType) { + f.write('isreftype(') + if node.is_type { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } else { + f.expr(node.expr) + } + f.write(')') +} + +pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) { + // sql app.db { select from Contributor where repo == id && user == 0 } + f.write('sql ') + f.expr(node.db_expr) + f.writeln(' {') + f.write('\tselect ') + table_name := util.strip_mod_name(f.table.get_type_symbol(node.table_expr.typ).name) + if node.is_count { + f.write('count ') + } else { + for i, fd in node.fields { + f.write(fd.name) + if i < node.fields.len - 1 { + f.write(', ') + } + } + } + f.write('from $table_name') + if node.has_where { + f.write(' where ') + f.expr(node.where_expr) + } + if node.has_order { + f.write(' order by ') + f.expr(node.order_expr) + if node.has_desc { + f.write(' desc') + } + } + if node.has_limit { + f.write(' limit ') + f.expr(node.limit_expr) + } + if node.has_offset { + f.write(' offset ') + f.expr(node.offset_expr) + } + f.writeln('') + f.write('}') +} + +pub fn (mut f Fmt) string_literal(node ast.StringLiteral) { + use_double_quote := node.val.contains("'") && !node.val.contains('"') + if node.is_raw { + f.write('r') + } else if node.language == ast.Language.c { + f.write('c') + } + if node.is_raw { + if use_double_quote { + f.write('"$node.val"') + } else { + f.write("'$node.val'") + } + } else { + unescaped_val := node.val.replace('$fmt.bs$fmt.bs', '\x01').replace_each(["$fmt.bs'", "'", + '$fmt.bs"', '"']) + if use_double_quote { + s := unescaped_val.replace_each(['\x01', '$fmt.bs$fmt.bs', '"', '$fmt.bs"']) + f.write('"$s"') + } else { + s := unescaped_val.replace_each(['\x01', '$fmt.bs$fmt.bs', "'", "$fmt.bs'"]) + f.write("'$s'") + } + } +} + +pub fn (mut f Fmt) string_inter_literal(node ast.StringInterLiteral) { + mut quote := "'" + for val in node.vals { + if val.contains('\\"') { + quote = '"' + break + } + if val.contains("\\'") { + quote = "'" + break + } + if val.contains('"') { + quote = "'" + } + if val.contains("'") { + quote = '"' + } + } + // TODO: this code is very similar to ast.Expr.str() + // serkonda7: it can not fully be replaced tho as ´f.expr()´ and `ast.Expr.str()` + // work too different for the various exprs that are interpolated + f.write(quote) + for i, val in node.vals { + f.write(val) + if i >= node.exprs.len { + break + } + f.write('$') + fspec_str, needs_braces := node.get_fspec_braces(i) + if needs_braces { + f.write('{') + f.expr(node.exprs[i]) + f.write(fspec_str) + f.write('}') + } else { + f.expr(node.exprs[i]) + } + } + f.write(quote) +} + +pub fn (mut f Fmt) type_expr(node ast.TypeNode) { + f.mark_types_import_as_used(node.typ) + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) +} + +pub fn (mut f Fmt) type_of(node ast.TypeOf) { + f.write('typeof(') + f.expr(node.expr) + f.write(')') +} + +pub fn (mut f Fmt) unsafe_expr(node ast.UnsafeExpr) { + single_line := node.pos.line_nr >= node.pos.last_line + f.write('unsafe {') + if single_line { + f.write(' ') + } else { + f.writeln('') + f.indent++ + f.empty_line = true + } + f.expr(node.expr) + if single_line { + f.write(' ') + } else { + f.writeln('') + f.indent-- + } + f.write('}') +} + +pub fn (mut f Fmt) prefix_expr_cast_expr(node ast.Expr) { + mut is_pe_amp_ce := false + if node is ast.PrefixExpr { + if node.right is ast.CastExpr && node.op == .amp { + mut ce := node.right + ce.typname = f.table.get_type_symbol(ce.typ).name + is_pe_amp_ce = true + f.expr(ce) + } + } else if node is ast.CastExpr { + last := f.out.cut_last(1) + if last != '&' { + f.out.write_string(last) + } + } + if !is_pe_amp_ce { + f.expr(node) + } +} + +fn (mut f Fmt) trace(fbase string, message string) { + if f.file.path_base == fbase { + println('> f.trace | ${fbase:-10s} | $message') + } +} diff --git a/v_windows/v/vlib/v/fmt/fmt_keep_test.v b/v_windows/v/vlib/v/fmt/fmt_keep_test.v new file mode 100644 index 0000000..be00fa9 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/fmt_keep_test.v @@ -0,0 +1,115 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import os +import term +import benchmark +import v.fmt +import v.parser +import v.ast +import v.pref +import v.util.diff +import v.util.vtest + +const ( + error_missing_vexe = 1 + error_failed_tests = 2 + b2v_keep_path = os.join_path('vlib', 'v', 'fmt', 'tests', 'bin2v_keep.vv') + fpref = &pref.Preferences{ + is_fmt: true + } + vexe = os.getenv('VEXE') +) + +fn test_fmt() { + fmt_message := 'checking that v fmt keeps already formatted files *unchanged*' + eprintln(term.header(fmt_message, '-')) + if vexe.len == 0 || !os.exists(vexe) { + eprintln('VEXE must be set') + exit(error_missing_vexe) + } + vroot := os.dir(vexe) + os.chdir(vroot) or {} + basepath := os.join_path(vroot, '') + tmpfolder := os.temp_dir() + diff_cmd := diff.find_working_diff_command() or { '' } + mut fmt_bench := benchmark.new_benchmark() + keep_input_files := os.walk_ext('vlib/v/fmt/tests', '_keep.vv') + expected_input_files := os.walk_ext('vlib/v/fmt/tests', '_expected.vv') + mut input_files := []string{} + input_files << keep_input_files + input_files << expected_input_files + input_files = vtest.filter_vtest_only(input_files, basepath: vroot) + fmt_bench.set_total_expected_steps(input_files.len + 1) + prepare_bin2v_file(mut fmt_bench) + for istep, ipath in input_files { + fmt_bench.cstep = istep + 1 + fmt_bench.step() + ifilename := os.file_name(ipath) + vrelpath := ipath.replace(basepath, '') + opath := ipath + expected_ocontent := os.read_file(opath) or { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('cannot read from $vrelpath')) + continue + } + table := ast.new_table() + file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) + result_ocontent := fmt.fmt(file_ast, table, fpref, false) + if expected_ocontent != result_ocontent { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('file $vrelpath after formatting, does not look as expected.')) + if ipath.ends_with(b2v_keep_path) { + continue + } + if diff_cmd == '' { + eprintln('>> sorry, but no working "diff" CLI command can be found') + continue + } + vfmt_result_file := os.join_path(tmpfolder, 'vfmt_run_over_$ifilename') + os.write_file(vfmt_result_file, result_ocontent) or { panic(err.msg) } + eprintln(diff.color_compare_files(diff_cmd, opath, vfmt_result_file)) + continue + } + fmt_bench.ok() + eprintln(fmt_bench.step_message_ok(vrelpath)) + } + restore_bin2v_placeholder() or { + eprintln('failed restoring vbin2v_keep.vv placeholder: $err.msg') + } + fmt_bench.stop() + eprintln(term.h_divider('-')) + eprintln(fmt_bench.total_message(fmt_message)) + if fmt_bench.nfail > 0 { + exit(error_failed_tests) + } +} + +fn prepare_bin2v_file(mut fmt_bench benchmark.Benchmark) { + fmt_bench.cstep = 0 + fmt_bench.step() + write_bin2v_keep_content() or { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('Failed preparing bin2v_keep.vv: $err.msg')) + return + } + fmt_bench.ok() + eprintln(fmt_bench.step_message_ok('Prepared bin2v_keep.vv')) +} + +fn write_bin2v_keep_content() ? { + img0 := os.join_path('vlib', 'v', 'embed_file', 'v.png') + img1 := os.join_path('tutorials', 'building_a_simple_web_blog_with_vweb', 'img', 'time.png') + os.rm(b2v_keep_path) ? + res := os.execute('$vexe bin2v -w $b2v_keep_path $img0 $img1') + if res.exit_code != 0 { + restore_bin2v_placeholder() or {} + return error_with_code(res.output.trim_space(), res.exit_code) + } +} + +fn restore_bin2v_placeholder() ? { + text := '// This is a placeholder file which will be filled with bin2v output before the test. +// HINT: do NOT delete, move or rename this file!\n' + os.write_file(b2v_keep_path, text) ? +} diff --git a/v_windows/v/vlib/v/fmt/fmt_test.v b/v_windows/v/vlib/v/fmt/fmt_test.v new file mode 100644 index 0000000..32d7445 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/fmt_test.v @@ -0,0 +1,75 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import os +import term +import benchmark +import v.ast +import v.fmt +import v.parser +import v.pref +import v.util.diff + +const ( + error_missing_vexe = 1 + error_failed_tests = 2 + fpref = &pref.Preferences{ + is_fmt: true + } +) + +fn test_fmt() { + fmt_message := 'vfmt tests' + eprintln(term.header(fmt_message, '-')) + vexe := os.getenv('VEXE') + if vexe.len == 0 || !os.exists(vexe) { + eprintln('VEXE must be set') + exit(error_missing_vexe) + } + vroot := os.dir(vexe) + tmpfolder := os.temp_dir() + diff_cmd := diff.find_working_diff_command() or { '' } + mut fmt_bench := benchmark.new_benchmark() + // Lookup the existing test _input.vv files: + input_files := os.walk_ext('$vroot/vlib/v/fmt/tests', '_input.vv') + fmt_bench.set_total_expected_steps(input_files.len) + for istep, ipath in input_files { + fmt_bench.cstep = istep + fmt_bench.step() + ifilename := os.file_name(ipath) + opath := ipath.replace('_input.vv', '_expected.vv') + if !os.exists(opath) { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('missing file $opath')) + continue + } + expected_ocontent := os.read_file(opath) or { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('cannot read from $opath')) + continue + } + table := ast.new_table() + file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) + result_ocontent := fmt.fmt(file_ast, table, fpref, false) + if expected_ocontent != result_ocontent { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('file $ipath after formatting, does not look as expected.')) + if diff_cmd == '' { + eprintln('>> sorry, but no working "diff" CLI command can be found') + continue + } + vfmt_result_file := os.join_path(tmpfolder, 'vfmt_run_over_$ifilename') + os.write_file(vfmt_result_file, result_ocontent) or { panic(err) } + eprintln(diff.color_compare_files(diff_cmd, opath, vfmt_result_file)) + continue + } + fmt_bench.ok() + eprintln(fmt_bench.step_message_ok('$ipath')) + } + fmt_bench.stop() + eprintln(term.h_divider('-')) + eprintln(fmt_bench.total_message(fmt_message)) + if fmt_bench.nfail > 0 { + exit(error_failed_tests) + } +} diff --git a/v_windows/v/vlib/v/fmt/fmt_vlib_test.v b/v_windows/v/vlib/v/fmt/fmt_vlib_test.v new file mode 100644 index 0000000..0715d5f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/fmt_vlib_test.v @@ -0,0 +1,73 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import os +import term +import benchmark +import v.ast +import v.fmt +import v.parser +import v.pref +import v.util.diff + +const ( + error_missing_vexe = 1 + error_failed_tests = 2 + fpref = &pref.Preferences{ + is_fmt: true + } +) + +fn test_vlib_fmt() { + $if !vfmt_everything ? { + return + } + fmt_message := "checking that all V source files are vfmt'ed" + eprintln(term.header(fmt_message, '-')) + vexe := os.getenv('VEXE') + if vexe.len == 0 || !os.exists(vexe) { + eprintln('VEXE must be set') + exit(error_missing_vexe) + } + vroot := os.dir(vexe) + tmpfolder := os.temp_dir() + diff_cmd := diff.find_working_diff_command() or { '' } + mut fmt_bench := benchmark.new_benchmark() + os.chdir(vroot) or {} + input_files := os.walk_ext('vlib/v/', '.v').filter(!it.contains('/tests/')) + fmt_bench.set_total_expected_steps(input_files.len) + for istep, ipath in input_files { + fmt_bench.cstep = istep + fmt_bench.step() + ifilename := os.file_name(ipath) + opath := ipath + expected_ocontent := os.read_file(opath) or { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('cannot read from $opath')) + continue + } + table := ast.new_table() + file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) + result_ocontent := fmt.fmt(file_ast, table, fpref, false) + if expected_ocontent != result_ocontent { + fmt_bench.fail() + eprintln(fmt_bench.step_message_fail('file $ipath after formatting, does not look as expected.')) + if diff_cmd == '' { + eprintln('>> sorry, but no working "diff" CLI command can be found') + continue + } + vfmt_result_file := os.join_path(tmpfolder, 'vfmt_run_over_$ifilename') + os.write_file(vfmt_result_file, result_ocontent) or { panic(err) } + eprintln(diff.color_compare_files(diff_cmd, opath, vfmt_result_file)) + continue + } + fmt_bench.ok() + eprintln(fmt_bench.step_message_ok('$ipath')) + } + fmt_bench.stop() + eprintln(term.h_divider('-')) + eprintln(fmt_bench.total_message(fmt_message)) + if fmt_bench.nfail > 0 { + exit(error_failed_tests) + } +} diff --git a/v_windows/v/vlib/v/fmt/struct.v b/v_windows/v/vlib/v/fmt/struct.v new file mode 100644 index 0000000..69c2c01 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/struct.v @@ -0,0 +1,288 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module fmt + +import math.mathutil as mu +import strings +import v.ast + +pub fn (mut f Fmt) struct_decl(node ast.StructDecl) { + f.attrs(node.attrs) + if node.is_pub { + f.write('pub ') + } + if node.is_union { + f.write('union ') + } else { + f.write('struct ') + } + f.write_language_prefix(node.language) + name := node.name.after('.') // strip prepended module + f.write(name) + f.write_generic_types(node.generic_types) + if node.fields.len == 0 && node.embeds.len == 0 && node.pos.line_nr == node.pos.last_line { + f.writeln(' {}') + return + } + mut field_aligns := []AlignInfo{} + mut comment_aligns := []AlignInfo{} + mut default_expr_aligns := []AlignInfo{} + mut field_types := []string{cap: node.fields.len} + for i, field in node.fields { + ft := f.no_cur_mod(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) + field_types << ft + attrs_len := inline_attrs_len(field.attrs) + end_pos := field.pos.pos + field.pos.len + mut comments_len := 0 // Length of comments between field name and type + for comment in field.comments { + if comment.pos.pos >= end_pos { + if comment.pos.line_nr == field.pos.line_nr { + comment_aligns.add_info(attrs_len, field_types[i].len, comment.pos.line_nr, + use_threshold: true) + } + continue + } + if comment.pos.pos > field.pos.pos { + comments_len += '/* ${comment.text.trim_left('\x01')} */ '.len + } + } + field_aligns.add_info(comments_len + field.name.len, ft.len, field.pos.line_nr) + if field.has_default_expr { + default_expr_aligns.add_info(attrs_len, field_types[i].len, field.pos.line_nr, + use_threshold: true) + } + } + f.writeln(' {') + for embed in node.embeds { + f.mark_types_import_as_used(embed.typ) + styp := f.table.type_to_str_using_aliases(embed.typ, f.mod2alias) + if embed.comments.len == 0 { + f.writeln('\t$styp') + } else { + f.write('\t$styp') + f.comments(embed.comments, level: .indent) + } + } + mut field_align_i := 0 + mut comment_align_i := 0 + mut default_expr_align_i := 0 + mut inc_indent := false // for correct indents with multi line default exprs + for i, field in node.fields { + if i == node.mut_pos { + f.writeln('mut:') + } else if i == node.pub_pos { + f.writeln('pub:') + } else if i == node.pub_mut_pos { + f.writeln('pub mut:') + } else if i == node.global_pos { + f.writeln('__global:') + } else if i == node.module_pos { + f.writeln('module:') + } else if i > 0 { + // keep one empty line between fields (exclude one after mut:, pub:, ...) + mut before_last_line := node.fields[i - 1].pos.line_nr + if node.fields[i - 1].comments.len > 0 { + before_last_line = mu.max(before_last_line, node.fields[i - 1].comments.last().pos.last_line) + } + if node.fields[i - 1].has_default_expr { + before_last_line = mu.max(before_last_line, node.fields[i - 1].default_expr.position().last_line) + } + + mut next_first_line := field.pos.line_nr + if field.comments.len > 0 { + next_first_line = mu.min(next_first_line, field.comments[0].pos.line_nr) + } + if next_first_line - before_last_line > 1 { + f.writeln('') + } + } + end_pos := field.pos.pos + field.pos.len + before_comments := field.comments.filter(it.pos.pos < field.pos.pos) + between_comments := field.comments[before_comments.len..].filter(it.pos.pos < end_pos) + after_type_comments := field.comments[(before_comments.len + between_comments.len)..] + // Handle comments before the field + f.comments_before_field(before_comments) + f.write('\t$field.name ') + // Handle comments between field name and type + before_len := f.line_len + f.comments(between_comments, iembed: true, has_nl: false) + comments_len := f.line_len - before_len + mut field_align := field_aligns[field_align_i] + if field_align.line_nr < field.pos.line_nr { + field_align_i++ + field_align = field_aligns[field_align_i] + } + f.write(strings.repeat(` `, field_align.max_len - field.name.len - comments_len)) + f.write(field_types[i]) + f.mark_types_import_as_used(field.typ) + attrs_len := inline_attrs_len(field.attrs) + has_attrs := field.attrs.len > 0 + if has_attrs { + f.write(strings.repeat(` `, field_align.max_type_len - field_types[i].len)) + f.single_line_attrs(field.attrs, inline: true) + } + if field.has_default_expr { + mut align := default_expr_aligns[default_expr_align_i] + if align.line_nr < field.pos.line_nr { + default_expr_align_i++ + align = default_expr_aligns[default_expr_align_i] + } + pad_len := align.max_len - attrs_len + align.max_type_len - field_types[i].len + f.write(strings.repeat(` `, pad_len)) + f.write(' = ') + if !expr_is_single_line(field.default_expr) { + f.indent++ + inc_indent = true + } + f.prefix_expr_cast_expr(field.default_expr) + if inc_indent { + f.indent-- + inc_indent = false + } + } + // Handle comments after field type + if after_type_comments.len > 0 { + if after_type_comments[0].pos.line_nr > field.pos.line_nr { + f.writeln('') + } else { + if !field.has_default_expr { + mut align := comment_aligns[comment_align_i] + if align.line_nr < field.pos.line_nr { + comment_align_i++ + align = comment_aligns[comment_align_i] + } + pad_len := align.max_len - attrs_len + align.max_type_len - field_types[i].len + f.write(strings.repeat(` `, pad_len)) + } + f.write(' ') + } + f.comments(after_type_comments, level: .indent) + } else { + f.writeln('') + } + } + f.comments_after_last_field(node.end_comments) + f.writeln('}\n') +} + +pub fn (mut f Fmt) struct_init(node ast.StructInit) { + struct_init_save := f.is_struct_init + f.is_struct_init = true + defer { + f.is_struct_init = struct_init_save + } + + type_sym := f.table.get_type_symbol(node.typ) + // f.write('') + mut name := type_sym.name + if !name.starts_with('C.') && !name.starts_with('JS.') { + name = f.no_cur_mod(f.short_module(type_sym.name)) // TODO f.type_to_str? + } + if name == 'void' { + name = '' + } + if node.fields.len == 0 && !node.has_update_expr { + // `Foo{}` on one line if there are no fields or comments + if node.pre_comments.len == 0 { + f.write('$name{}') + } else { + f.writeln('$name{') + f.comments(node.pre_comments, inline: true, has_nl: true, level: .indent) + f.write('}') + } + f.mark_import_as_used(name) + } else if node.is_short { + // `Foo{1,2,3}` (short syntax ) + f.write('$name{') + f.mark_import_as_used(name) + if node.has_update_expr { + f.write('...') + f.expr(node.update_expr) + f.write(', ') + } + for i, field in node.fields { + f.prefix_expr_cast_expr(field.expr) + if i < node.fields.len - 1 { + f.write(', ') + } + } + f.write('}') + } else { + use_short_args := f.use_short_fn_args && !node.has_update_expr + f.use_short_fn_args = false + mut single_line_fields := f.single_line_fields + f.single_line_fields = false + if node.pos.line_nr < node.pos.last_line || node.pre_comments.len > 0 { + single_line_fields = false + } + if !use_short_args { + f.write('$name{') + f.mark_import_as_used(name) + if single_line_fields { + f.write(' ') + } + } + fields_start := f.out.len + fields_loop: for { + if !single_line_fields { + if use_short_args && f.out[f.out.len - 1] == ` ` { + // v Remove space at tail of line + // f(a, b, c, \n + // f1: 0\n + // f2: 1\n + // ) + f.out.go_back(1) + } + f.writeln('') + f.indent++ + } + f.comments(node.pre_comments, inline: true, has_nl: true, level: .keep) + if node.has_update_expr { + f.write('...') + f.expr(node.update_expr) + if single_line_fields { + if node.fields.len > 0 { + f.write(', ') + } + } else { + f.writeln('') + } + f.comments(node.update_expr_comments, inline: true, has_nl: true, level: .keep) + } + for i, field in node.fields { + f.write('$field.name: ') + f.prefix_expr_cast_expr(field.expr) + f.comments(field.comments, inline: true, has_nl: false, level: .indent) + if single_line_fields { + if i < node.fields.len - 1 { + f.write(', ') + } + } else { + f.writeln('') + } + f.comments(field.next_comments, inline: false, has_nl: true, level: .keep) + if single_line_fields && (field.comments.len > 0 + || field.next_comments.len > 0 + || !expr_is_single_line(field.expr) + || f.line_len > max_len.last()) { + single_line_fields = false + f.out.go_back_to(fields_start) + f.line_len = fields_start + f.remove_new_line() + continue fields_loop + } + } + break + } + if !single_line_fields { + f.indent-- + } + if !use_short_args { + if single_line_fields { + f.write(' ') + } + f.write('}') + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/anon_fn_as_param_keep.vv b/v_windows/v/vlib/v/fmt/tests/anon_fn_as_param_keep.vv new file mode 100644 index 0000000..4ed7d1a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/anon_fn_as_param_keep.vv @@ -0,0 +1,20 @@ +import v.ast + +struct Data { + a fn (stmt ast.Stmt, vp voidptr) bool +} + +pub fn (a []Data) reduce(iter fn (int, int) int, accum_start int) int { + iter(accum_start) +} + +pub fn test_anon_fn_void(func fn ()) int { + return 0 +} + +fn C.HasAnonFnWithNamedParams(cb fn (c C.bar, d voidptr)) + +// NB: the signature of both anonymus functions should only differs in the param name +fn anon_fn_param_has_no_name(f fn (int) string) {} + +fn anon_fn_with_named_param(func fn (a int) string) {} diff --git a/v_windows/v/vlib/v/fmt/tests/anon_fn_call_keep.vv b/v_windows/v/vlib/v/fmt/tests/anon_fn_call_keep.vv new file mode 100644 index 0000000..e2661ba --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/anon_fn_call_keep.vv @@ -0,0 +1,7 @@ +fn main() { + fn () { + for { + println('Hello V!') + } + }() +} diff --git a/v_windows/v/vlib/v/fmt/tests/anon_fn_expected.vv b/v_windows/v/vlib/v/fmt/tests/anon_fn_expected.vv new file mode 100644 index 0000000..35641ee --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/anon_fn_expected.vv @@ -0,0 +1,24 @@ +fn has_anon_fn() { + an_fn := fn () { + println('Hello there !') + } + an_fn_w_param := fn (s string) { + println('I received $s') + } + an_fn_w_multi_params := fn (s1 string, s2 string, s3 string) { + println('I received $s1, $s2, $s3') + } + an_fn_w_multi_params2 := fn (s string, i int) { + println('I received $s, $i') + } + fn_w_var_args := fn (ss ...string) { + for s in ss { + println('yo $s') + } + } + an_fn() + an_fn_w_param('a gift') + an_fn_w_multi_params('one', 'two', 'three') + an_fn_w_multi_params2('one', 1) + fn_w_var_args('one arg', 'two args', 'three args') +} diff --git a/v_windows/v/vlib/v/fmt/tests/anon_fn_input.vv b/v_windows/v/vlib/v/fmt/tests/anon_fn_input.vv new file mode 100644 index 0000000..8884991 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/anon_fn_input.vv @@ -0,0 +1,23 @@ +fn has_anon_fn() { + an_fn := fn() { + println('Hello there !') + } + an_fn_w_param := fn ( s string ) + { + println('I received $s') + } + an_fn_w_multi_params := fn (s1 string, s2 string, s3 string) { + println('I received $s1, $s2, $s3') + } + an_fn_w_multi_params2 :=fn (s string, i int) { + println('I received $s, $i') + } + fn_w_var_args := fn (ss ...string) { + for s in ss { + println('yo $s') + } + } an_fn() + an_fn_w_param('a gift') an_fn_w_multi_params('one', 'two', 'three') + an_fn_w_multi_params2( 'one', 1) + fn_w_var_args('one arg', 'two args', 'three args') +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_decomposition_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_decomposition_keep.vv new file mode 100644 index 0000000..387997f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_decomposition_keep.vv @@ -0,0 +1,8 @@ +fn varargs(a ...int) { + println(a) +} + +fn main() { + a := [1, 2, 3] + varargs(...a) +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_comment_ending_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_init_comment_ending_keep.vv new file mode 100644 index 0000000..fdbf675 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_comment_ending_keep.vv @@ -0,0 +1,23 @@ +import net.http + +const ( + write_set_cookie_tests = [ + ReadSetCookiesTestCase{ + header: { + 'Set-Cookie': ['special-7=","'] + } + cookies: [ + &http.Cookie{ + name: 'special-7' + value: ',' + raw: 'special-8=","' + }, + ] + } + // (bradfitz): users have reported seeing this in the + // wild, but do browsers handle it? RFC 6265 just says "don't + // do that" (section 3) and then never mentions header folding + // again. + // Header{"Set-Cookie": ["ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"]}, + ] +) diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_eol_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_init_eol_comments_keep.vv new file mode 100644 index 0000000..a9e4570 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_eol_comments_keep.vv @@ -0,0 +1,104 @@ +fn abc() { + test_cases_f32 := [ + f32_from_bits1(0x0000_0000), // +0 + f32_from_bits1(0x8000_0000), // -0 + f32_from_bits1(0xFFC0_0001), // sNan + f32_from_bits1(0xFF80_0001), // qNan + f32_from_bits1(0x7F80_0000), // +inf + f32_from_bits1(0xFF80_0000), // -inf + 1, + -1, + 10, + -10, + 0.3, + -0.3, + 1000000, + 123456.7, + 123e35, + -123.45, + 1e23, + f32_from_bits1(0x0080_0000), // smallest float32 + math.max_f32, + 383260575764816448.0, + ] + + exp_result_f32 := [ + '0e+00', + '-0e+00', + 'nan', + 'nan', + '+inf', + '-inf', + '1.e+00', + '-1.e+00', + '1.e+01', + '-1.e+01', + '3.e-01', + '-3.e-01', + '1.e+06', + '1.234567e+05', + '1.23e+37', + '-1.2345e+02', + '1.e+23', + '1.1754944e-38', // aprox from 1.1754943508 × 10−38, + '3.4028235e+38', + '3.8326058e+17', + ] + + test_cases_f64 := [ + f64_from_bits1(0x0000_0000_0000_0000), // +0 + f64_from_bits1(0x8000_0000_0000_0000), // -0 + f64_from_bits1(0x7FF0_0000_0000_0001), // sNan + f64_from_bits1(0x7FF8_0000_0000_0001), // qNan + f64_from_bits1(0x7FF0_0000_0000_0000), // +inf + f64_from_bits1(0xFFF0_0000_0000_0000), // -inf + 1, + -1, + 10, + -10, + 0.3, + -0.3, + 1000000, + 123456.7, + 123e45, + -123.45, + 1e23, + f64_from_bits1(0x0010_0000_0000_0000), // smallest float64 + math.max_f32, + 383260575764816448, + 383260575764816448, + // C failing cases + 123e300, + 123e-300, + 5.e-324, + -5.e-324, + ] + + exp_result_f64 := [ + '0e+00', + '-0e+00', + 'nan', + 'nan', + '+inf', + '-inf', + '1.e+00', + '-1.e+00', + '1.e+01', + '-1.e+01', + '3.e-01', + '-3.e-01', + '1.e+06', + '1.234567e+05', + '1.23e+47', + '-1.2345e+02', + '1.e+23', + '2.2250738585072014e-308', + '3.4028234663852886e+38', + '3.8326057576481645e+17', + '3.8326057576481645e+17', + '1.23e+302', // this test is failed from C sprintf!! + '1.23e-298', + '5.e-324', + '-5.e-324', + ] +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_expected.vv b/v_windows/v/vlib/v/fmt/tests/array_init_expected.vv new file mode 100644 index 0000000..44cf30f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_expected.vv @@ -0,0 +1,35 @@ +fn main() { + if 1 == 1 { + println('yeah !') + } + if 1 != 1 { + println("oh no :'(") + } +} + +fn wrapping_tests() { + my_arr := ['Lorem ipsum dolor sit amet, consectetur adipiscing', + 'elit. Donec varius purus leo, vel maximus diam', + 'finibus sed. Etiam eu urna ante. Nunc quis vehicula', + 'velit. Sed at mauris et quam ornare tristique.', + ] +} + +fn array_init_without_commas() { + a := [3, -4, 5, -2] + b := [5 - 2, 7, 2 * 3, -4, -7 * 8, 2 * -4] + c := [4, 6 - 7, 8, 3 * 4] + d := [&a, &b, &c] + e := u16(434) + f := u16(23) + g := u16(4324) + h := [e & f, g, g & g] + k := 323 + l := [k, -k, 5] + x := &e + y := &f + z := &g + q := [f, *y, f * g, e] + ch := chan u16{} + w := [f, 12 <- ch] +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_inline_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_init_inline_comments_keep.vv new file mode 100644 index 0000000..d1c18e9 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_inline_comments_keep.vv @@ -0,0 +1,2 @@ +a := [1 /* y */, /* x */ 2, 3] +println(a) diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_input.vv b/v_windows/v/vlib/v/fmt/tests/array_init_input.vv new file mode 100644 index 0000000..4b465d7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_input.vv @@ -0,0 +1,31 @@ +fn main() { + if 1 in [1] { + println('yeah !') + } + if 1 !in [1] { + println("oh no :'(") + } +} + +fn wrapping_tests() { + my_arr := ['Lorem ipsum dolor sit amet, consectetur adipiscing', 'elit. Donec varius purus leo, vel maximus diam', 'finibus sed. Etiam eu urna ante. Nunc quis vehicula', 'velit. Sed at mauris et quam ornare tristique.'] +} + +fn array_init_without_commas() { + a := [3 -4 5 -2] + b := [5-2,7,2*3,-4,-7*8,2*-4] + c := [4 6 - 7 8 3 * 4] + d := [&a &b &c] + e := u16(434) + f := u16(23) + g := u16(4324) + h := [e&f g g & g] + k := 323 + l := [k -k 5] + x := &e + y := &f + z := &g + q := [f *y f * g e] + ch := chan u16{} + w := [f 12 <-ch] +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_init_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_init_keep.vv new file mode 100644 index 0000000..8321e2e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_init_keep.vv @@ -0,0 +1,9 @@ +fn main() { + _ := []int{len: 10, cap: 10, init: 7} + _ := []map[string]string{len: 5, cap: 50, init: { + 'a': 'a' + }} + _ := []map[string][]int{len: 7, cap: 100, init: { + 'a': [1, 2] + }} +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_newlines_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_newlines_keep.vv new file mode 100644 index 0000000..11fded1 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_newlines_keep.vv @@ -0,0 +1,17 @@ +fn make_flag(a string, b string, c string) string { + return '' +} + +fn main() { + // Set up flags + expected_flags := [ + make_flag('solaris', '-L', '/opt/local/lib'), + make_flag('macos', '-framework', 'Cocoa'), + make_flag('windows', '-l', 'gdi32'), + ] + x := []int{len: 10, cap: 100, init: 1} + _ := expected_flags + buf := [100]byte{} + println(x) + println(buf) +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_slices_expected.vv b/v_windows/v/vlib/v/fmt/tests/array_slices_expected.vv new file mode 100644 index 0000000..b0bcee7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_slices_expected.vv @@ -0,0 +1,12 @@ +fn fn_contains_index_expr() { + arr := [1, 2, 3, 4, 5] + a := 1 in arr[0..] + _ := a + _ := 1 in arr[..2] + _ := 1 in arr[1..3] + d := arr[2] + _ := d + _ := arr[2..] + _ := arr[..2] + _ := arr[1..3] +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_slices_input.vv b/v_windows/v/vlib/v/fmt/tests/array_slices_input.vv new file mode 100644 index 0000000..d70add5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_slices_input.vv @@ -0,0 +1,14 @@ + + +fn fn_contains_index_expr() { + arr := [1, 2, 3, 4, 5] + a := 1 in arr[ 0.. ] +_ := a + _ := 1 in arr[ ..2 ] + _ := 1 in arr[1..3] + d := arr[2] + _ := d + _ := arr[2 ..] + _ := arr[.. 2 ] + _ := arr[ 1 .. 3] +} diff --git a/v_windows/v/vlib/v/fmt/tests/array_static_keep.vv b/v_windows/v/vlib/v/fmt/tests/array_static_keep.vv new file mode 100644 index 0000000..1555780 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/array_static_keep.vv @@ -0,0 +1,14 @@ +struct Board { +mut: + field [10][5]int + points int + shifts int +} + +struct TileLine { + ypos int +mut: + field [5]int + points int + shifts int +} diff --git a/v_windows/v/vlib/v/fmt/tests/assembly/asm_keep.vv b/v_windows/v/vlib/v/fmt/tests/assembly/asm_keep.vv new file mode 100644 index 0000000..a6f9e56 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/assembly/asm_keep.vv @@ -0,0 +1,10 @@ +fn main() { + asm volatile amd64 { + mov ecx, 5 + loop_start: + add j, 3 + loop loop_start + ; +r (j) + ; ; ecx + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/asserts_expected.vv b/v_windows/v/vlib/v/fmt/tests/asserts_expected.vv new file mode 100644 index 0000000..08dd71a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/asserts_expected.vv @@ -0,0 +1,5 @@ +fn f() { + assert 0 == 0 + assert 0 < 1 + assert (1 + 2) == 3 +} diff --git a/v_windows/v/vlib/v/fmt/tests/asserts_input.vv b/v_windows/v/vlib/v/fmt/tests/asserts_input.vv new file mode 100644 index 0000000..ae2a01a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/asserts_input.vv @@ -0,0 +1,5 @@ +fn f() { + assert (0 == 0) + assert (0 < 1) + assert ((1 + 2) == 3) +} diff --git a/v_windows/v/vlib/v/fmt/tests/asserts_keep.vv b/v_windows/v/vlib/v/fmt/tests/asserts_keep.vv new file mode 100644 index 0000000..999c1fe --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/asserts_keep.vv @@ -0,0 +1,6 @@ +fn test_abc() { + assert 2 < 3 + assert 2 == 2 + assert 'abc' == 'abc' + assert 'abc'.len == 3 +} diff --git a/v_windows/v/vlib/v/fmt/tests/attrs_expected.vv b/v_windows/v/vlib/v/fmt/tests/attrs_expected.vv new file mode 100644 index 0000000..1660ee5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/attrs_expected.vv @@ -0,0 +1,9 @@ +[export: 'JNICALL Java_io_vlang_V_callStaticMethods'] +[tom: 'jerry'] +[direct_array_access; inline; unsafe] +fn heavily_tagged() {} + +// console attribute for easier diagnostics on windows +// also it's not safe to use +[console; unsafe] +fn dangerous_console() {} diff --git a/v_windows/v/vlib/v/fmt/tests/attrs_input.vv b/v_windows/v/vlib/v/fmt/tests/attrs_input.vv new file mode 100644 index 0000000..bfffc55 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/attrs_input.vv @@ -0,0 +1,10 @@ +[inline] +[export: 'JNICALL Java_io_vlang_V_callStaticMethods'] +[direct_array_access] +[unsafe] +[tom: 'jerry'] +fn heavily_tagged() {} + +[console] // console attribute for easier diagnostics on windows +[unsafe] // also it's not safe to use +fn dangerous_console() {} diff --git a/v_windows/v/vlib/v/fmt/tests/attrs_keep.vv b/v_windows/v/vlib/v/fmt/tests/attrs_keep.vv new file mode 100644 index 0000000..cad488c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/attrs_keep.vv @@ -0,0 +1,32 @@ +struct AttrsWithEscpaedStringArgs { + dollar string [foo: '\$var'] + double_bs string [bar: '\\baz'] +} + +[deprecated: 'use bar() instead'] +[foo: bar] +[if debug; inline] +fn keep_attributes() { + println('hi !') +} + +[bar: 'foo'] +fn attr_with_arg() {} + +['a_string_name'] +fn name_only_attr() {} + +struct User { + age int + nums []int + last_name string [json: lastName] + is_registered bool [json: IsRegistered] + typ int [json: 'type'] + pets string [json: 'pet_animals'; raw] +} + +[_allow_multiple_values] +enum Example { + value1 = 1 + value1_again = 1 +} diff --git a/v_windows/v/vlib/v/fmt/tests/bin2v_keep.vv b/v_windows/v/vlib/v/fmt/tests/bin2v_keep.vv new file mode 100644 index 0000000..5c6f861 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/bin2v_keep.vv @@ -0,0 +1,2 @@ +// This is a placeholder file which will be filled with bin2v output before the test. +// HINT: do NOT delete, move or rename this file! diff --git a/v_windows/v/vlib/v/fmt/tests/blocks_expected.vv b/v_windows/v/vlib/v/fmt/tests/blocks_expected.vv new file mode 100644 index 0000000..988f8a8 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/blocks_expected.vv @@ -0,0 +1,11 @@ +fn unsafe_fn() { + unsafe { + _ = malloc(2) + } +} + +fn fn_with_defer() { + defer { + voidfn() + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/blocks_input.vv b/v_windows/v/vlib/v/fmt/tests/blocks_input.vv new file mode 100644 index 0000000..6efacaa --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/blocks_input.vv @@ -0,0 +1,7 @@ +fn unsafe_fn() { +unsafe { _ = malloc(2) } +} + +fn fn_with_defer() { +defer { voidfn() } +} diff --git a/v_windows/v/vlib/v/fmt/tests/c_struct_init_keep.vv b/v_windows/v/vlib/v/fmt/tests/c_struct_init_keep.vv new file mode 100644 index 0000000..f5c93e1 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/c_struct_init_keep.vv @@ -0,0 +1,30 @@ +module main + +fn abc() { + desc := C.sapp_desc{ + width: a.width + height: a.height + user_data: &a + init_userdata_cb: init + frame_userdata_cb: frame + event_userdata_cb: event + window_title: title.str + html5_canvas_name: title.str + cleanup_userdata_cb: cleanup + } +} + +fn init(user_data voidptr) { + desc := C.sg_desc{ + mtl_device: sapp.metal_get_device() + mtl_renderpass_descriptor_cb: sapp.metal_get_renderpass_descriptor + mtl_drawable_cb: sapp.metal_get_drawable + d3d11_device: sapp.d3d11_get_device() + d3d11_device_context: sapp.d3d11_get_device_context() + d3d11_render_target_view_cb: sapp.d3d11_get_render_target_view + d3d11_depth_stencil_view_cb: sapp.d3d11_get_depth_stencil_view + } + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/cast_expected.vv b/v_windows/v/vlib/v/fmt/tests/cast_expected.vv new file mode 100644 index 0000000..169f07c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/cast_expected.vv @@ -0,0 +1,13 @@ +import v.ast { InfixExpr } +import v.table { Type } + +fn test_as() { + _ := sum_expr() as InfixExpr + _ := sum_expr() as ast.PrefixExpr +} + +fn test_cast() { + _ := f32(0) + _ := Type(0) + _ := ast.Expr(sum_expr()) +} diff --git a/v_windows/v/vlib/v/fmt/tests/cast_input.vv b/v_windows/v/vlib/v/fmt/tests/cast_input.vv new file mode 100644 index 0000000..72b254c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/cast_input.vv @@ -0,0 +1,14 @@ +import v.ast { InfixExpr } +import v.table { Type } + +fn test_as() { + _ := sum_expr() as ast.InfixExpr + _ := sum_expr() as ast.PrefixExpr +} + +fn test_cast() { + _ := f32(0) + _ := table.Type(0) + _ := ast.Expr(sum_expr()) +} + diff --git a/v_windows/v/vlib/v/fmt/tests/chan_init_keep.vv b/v_windows/v/vlib/v/fmt/tests/chan_init_keep.vv new file mode 100644 index 0000000..47eaac6 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/chan_init_keep.vv @@ -0,0 +1,27 @@ +import some_mod + +struct ChanFieldType { + c chan some_mod.Type +} + +struct FSMEvent { + x int +} + +fn abc(n FSMEvent) { +} + +fn (e FSMEvent) abc(n FSMEvent) { +} + +fn (e FSMEvent) x(ch chan FSMEvent) { +} + +fn produce_events(ch chan FSMEvent) { +} + +fn main() { + ch_fsm_events := chan FSMEvent{cap: 1000} + eprintln('ch_fsm_events.len: $ch_fsm_events.len') + eprintln('ch_fsm_events.cap: $ch_fsm_events.cap') +} diff --git a/v_windows/v/vlib/v/fmt/tests/chan_ops_keep.vv b/v_windows/v/vlib/v/fmt/tests/chan_ops_keep.vv new file mode 100644 index 0000000..57ae616 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/chan_ops_keep.vv @@ -0,0 +1,65 @@ +import sync + +const ( + num_iterations = 10000 +) + +struct St { + a int +} + +fn get_val_from_chan(ch chan i64) ?i64 { + r := <-ch ? + return r +} + +fn get_val_from_chan2(ch chan i64) ?i64 { + r := <-ch or { + println('error') + return err + } + return r +} + +// this function gets an array of channels for `i64` +fn do_rec_calc_send(chs []chan i64, sem sync.Semaphore) { + mut msg := '' + for { + mut s := get_val_from_chan(chs[0]) or { + msg = err.str() + break + } + s++ + chs[1] <- s + } + assert msg == 'channel closed' + sem.post() +} + +fn test_channel_array_mut() { + mut chs := [chan i64{}, chan i64{cap: 10}] + sem := sync.new_semaphore() + go do_rec_calc_send(chs, sem) + mut t := i64(100) + for _ in 0 .. num_iterations { + chs[0] <- t + t = <-chs[1] + } + (&sync.Channel(chs[0])).close() + orr := &sync.Channel(chs[0]) + chs[1].close() + ch := chan int{} + ch.close() + a := ch.cap + b := ch.len + c := ch[1].cap + d := ch[o].len + sem.wait() + assert t == 100 + num_iterations + ch2 := chan mut St{cap: 10} + go g(ch2) +} + +fn g(ch chan mut St) { + return +} diff --git a/v_windows/v/vlib/v/fmt/tests/chan_or_keep.vv b/v_windows/v/vlib/v/fmt/tests/chan_or_keep.vv new file mode 100644 index 0000000..a6e7a90 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/chan_or_keep.vv @@ -0,0 +1,33 @@ +const n = 1000 + +fn f(ch chan int) { + mut s := 0 + for _ in 0 .. n { + s += <-ch or { + println('Something went wrong:') + println('got $err') + } + } + assert s == n * (n + 1) / 2 + ch.close() +} + +fn do_send(ch chan int, val int) ?int { + ch <- val ? + return val + 1 +} + +fn do_send_2(ch chan int, val int) ?int { + ch <- val or { return error('could not send') } + return val + 1 +} + +fn main() { + ch := chan int{} + go f(ch) + mut s := 1 + for { + s = do_send(ch, s) or { break } + } + assert s == n + 1 +} diff --git a/v_windows/v/vlib/v/fmt/tests/char_literal_keep.vv b/v_windows/v/vlib/v/fmt/tests/char_literal_keep.vv new file mode 100644 index 0000000..e00c5e4 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/char_literal_keep.vv @@ -0,0 +1,3 @@ +println(`"`) +printrn(`'`) +println(`\``) diff --git a/v_windows/v/vlib/v/fmt/tests/closure_keep.vv b/v_windows/v/vlib/v/fmt/tests/closure_keep.vv new file mode 100644 index 0000000..ec05f85 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/closure_keep.vv @@ -0,0 +1,10 @@ +my_var := 1 +my_simple_closure := fn [my_var] () { + println(my_var) +} +my_closure_returns := fn [my_var] () int { + return my_var +} +my_closure_has_params := fn [my_var, my_closure_returns] (add int) { + println(my_var + my_closure_returns() + add) +} diff --git a/v_windows/v/vlib/v/fmt/tests/comments_array_keep.vv b/v_windows/v/vlib/v/fmt/tests/comments_array_keep.vv new file mode 100644 index 0000000..74632ed --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comments_array_keep.vv @@ -0,0 +1,79 @@ +fn main() { + _ := [ + // pre comment + 6, + // and after + ] + _ := [ + 7, + // below expr + ] + _ := [ + 8, /* I don't know why this still is a block comment */ + 9, + ] + arr := [ + // test 0 + 1, + // test 1 + 2, + // test 2 + 3, /* 3 */ + 4, /* 4-1 */ /* 4-2 */ + ] +} + +fn only_comments_array() { + _ := [/* on a single line */ /* too */] + _ := [ + // 1, + // 2, + // 3, + ] + _ := [ + /* whatever */ /* this is */ // 3, + // 4, + ] +} + +fn single_line_array_pre_comments() { + _ := [/* 2, */ 3] + _ := [/* 4, */ /* 5, */ 6] + _ := [/* cmt */ -4] +} + +fn single_line_array_iembed_comments() { + _ := [1, /* betw single line */ 2] + // This caused a bug where the ´-´ was parsed as InfixExpr and not as part of an IntegerLiteral + _ := [1, /* cmt */ -4] +} + +fn mixed_comments() { + _ := [ + 3 /* iembed */, + // keep line comment here + // and here + 5, + ] +} + +fn keep_real_block_comment() { + _ := [ + 'foo', + /* + 'bar', + 'baz', + 'spam', + */ + 'eggs', + ] +} + +fn comment_at_line_start_with_expressions_after() { + arr := [123456789012345, 234567890123456, 678901234567890, 789012345678901, /* at the end */ + 345678901234567, /* in between */ 456789012345678, + /* line start */ 567890123456789, 890123456789012] + arr2 := [/* same line pre comment */ Foo{ + a: 123 + }] +} diff --git a/v_windows/v/vlib/v/fmt/tests/comments_expected.vv b/v_windows/v/vlib/v/fmt/tests/comments_expected.vv new file mode 100644 index 0000000..859908f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comments_expected.vv @@ -0,0 +1,105 @@ +import time // foo + +/* +block +comment +*/ + +fn fun() int { + // comment zero + return 0 // another comment +} + +fn mr_fun() (int, int) { + // one comment + // another comment + return 1, 2 +} + +fn single_line_blocks() { + // 1 + println('') + // 2 + println('') + // 3 + // 4 + println('') + // 5 + // 6 +} + +fn main() { + /* + block1 + */ + /* + block2 + */ + /* + block3 + */ + // this is a comment + a := 1 + // and another comment + // just to make it worse + b, c := a, 2 + d := c // and an extra one + e := c + // more comments = more good + arr := [ + // block foo bar + // inline foo bar + 0, + ] + // before arg comment + // after arg comment + println('this is a test') + // before if expr + // after if expr + if true { + println('if') + } + // before else if + // between else if + else if false { + println('else if') + } + // before else + // after else + else { + println('else') + } + // empty return + return +} + +fn insert_space() { + // abc +} + +fn linebreaks_in_block_comments() { + /* + foo + comment goes here! + bar + */ + /* + spam + spaces make no difference there + eggs + */ +} + +fn between_if_branches() { + if spam { + } + // remove the empty line above + else if eggs { + } + + if spam2 { + } + // remove the empty line below + else { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/comments_input.vv b/v_windows/v/vlib/v/fmt/tests/comments_input.vv new file mode 100644 index 0000000..e67d070 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comments_input.vv @@ -0,0 +1,89 @@ +import time // foo +/* +block +comment +*/ + +fn fun() int { + return /* comment zero */ 0 // another comment +} + +fn mr_fun() (int, int) { + return /* one comment */ 1, /* another comment */ 2 +} + +fn single_line_blocks() { + /* 1 */ + println('') + /* 2 */ + println('') + /* 3 */ + /* 4 */ + println('') + // 5 + /* 6 */ +} + +fn main() { + /* block1 + */ + /* + block2 */ + /* + + block3 + + */ + a := /* this is a comment */ 1 + b, c := /* and another comment */ a, /* just to make it worse */ 2 + d := c // and an extra one + e := c + // more comments = more good + arr := [ + /* block foo bar */ + // inline foo bar + 0, + ] + println(/* before arg comment */ 'this is a test' /* after arg comment */) + if /* before if expr */ true /* after if expr */ { + println('if') + } + // before else if + else /* between else if */ if false { + println('else if') + } + // before else + else /* after else */ { + println('else') + } + return // empty return +} + +fn insert_space() { + //abc +} + +fn linebreaks_in_block_comments() { + /*foo + comment goes here! + bar*/ + /* spam + spaces make no difference there + eggs */ +} + +fn between_if_branches() { + if spam { + } + + // remove the empty line above + else if eggs { + } + + if spam2 { + } + // remove the empty line below + + else { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/comments_keep.vv new file mode 100644 index 0000000..5af7280 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comments_keep.vv @@ -0,0 +1,101 @@ +import semver // as sv + +enum Abc { + a + b // after a value + c + // between values + d +} + +struct User { + name string // name + // middle comment + age int + // last comment + // last comment2 +} + +fn main() { + u := User{ + name: 'Peter' + } + n := sizeof(User) + // else + // else { + // } + _ := User{ + name: 'Henry' // comment after name + // on the next line + age: 42 + // after age line + // after line2 + } + _ := User{ + // Just a comment + } + ////// + // / + // 123 + match 0 { + 0 { + 0 // comment after an expression inside match + } + else {} + } +} + +fn assign_comments() { + a := 123 // comment after assign + b := 'foo' // also comment after assign + c := true + // Between two assigns + d := false + // at the end +} + +fn linebreaks_in_ascii_art_block_comments() { + /* + +++ + */ + /***** + +++ + *****/ + /**** + +++ + */ + /* + +++ + ****/ +} + +fn map_comments() { + mymap := { + // pre + `:`: 1 + `!`: 2 // after + // and between + `%`: 3 + // between + // between second + `$`: 4 + `&`: 5 + // post + } +} + +fn ifs_comments_and_empty_lines() { + if true { + } + // some comment direct after an if without else + if false { + } else { + } + // some comment direct after an else + if false { + } + + // this is parsed as post_comment of the if but does not really belong there + // thereore keep the empty line + something_else() +} diff --git a/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_keep.vv b/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_keep.vv new file mode 100644 index 0000000..e9752de --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_keep.vv @@ -0,0 +1,23 @@ +struct Foo { +mut: + test string + name string +} + +fn (f Foo) print() { + println('test') +} + +fn test() { + mut t := T{} + t.name = '2' + $for f in T.fields { + $if f.typ is string { + println(t.$(f.name)) + } + } +} + +fn main() { + test() +} diff --git a/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv b/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv new file mode 100644 index 0000000..e9752de --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv @@ -0,0 +1,23 @@ +struct Foo { +mut: + test string + name string +} + +fn (f Foo) print() { + println('test') +} + +fn test() { + mut t := T{} + t.name = '2' + $for f in T.fields { + $if f.typ is string { + println(t.$(f.name)) + } + } +} + +fn main() { + test() +} diff --git a/v_windows/v/vlib/v/fmt/tests/comptime_keep.vv b/v_windows/v/vlib/v/fmt/tests/comptime_keep.vv new file mode 100644 index 0000000..987e513 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/comptime_keep.vv @@ -0,0 +1,91 @@ +import vweb + +struct App { + a string + b string +mut: + c int + d f32 +pub: + e f32 + f u64 +pub mut: + g string + h byte +} + +fn comptime_for() { + println(@FN) + $for method in App.methods { + println(' method: $method.name | $method') + } +} + +fn comptime_for_with_if() { + println(@FN) + $for method in App.methods { + println(' method: $method') + $if method.typ is fn () { + assert method.name in ['run', 'method2'] + } + $if method.return_type is int { + assert method.name in ['int_method1', 'int_method2'] + } + $if method.args[0].typ is string { + assert method.name == 'my_method' + } + } +} + +fn comptime_for_fields() { + println(@FN) + $for field in App.fields { + println(' field: $field.name | $field') + $if field.typ is string { + assert field.name in ['a', 'b', 'g'] + } + $if field.typ is f32 { + assert field.name in ['d', 'e'] + } + if field.is_mut { + assert field.name in ['c', 'd', 'g', 'h'] + } + if field.is_pub { + assert field.name in ['e', 'f', 'g', 'h'] + } + if field.is_pub && field.is_mut { + assert field.name in ['g', 'h'] + } + } +} + +struct Result {} + +fn (mut a App) my_method(p string) Result { + println('>>>> ${@FN} | p: $p') + return Result{} +} + +fn handle_conn(mut app T) { + $for method in T.methods { + $if method.return_type is Result { + app.$method('abc', 'def') + } + } +} + +fn comptime_call_dollar_method() { + mut app := App{} + handle_conn(mut app) +} + +fn (mut app App) create() vweb.Result { + return $vweb.html() +} + +fn main() { + comptime_for() + comptime_for_with_if() + comptime_for_fields() + comptime_call_dollar_method() +} diff --git a/v_windows/v/vlib/v/fmt/tests/concat_expr_expected.vv b/v_windows/v/vlib/v/fmt/tests/concat_expr_expected.vv new file mode 100644 index 0000000..47d5872 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/concat_expr_expected.vv @@ -0,0 +1,24 @@ +fn concatenation_of_strings() { + _ := 'Simple' + 'Concat' + _ := 'Hello' + ' ' + 'World' + '!' + _ := 'There' + ' ' + 'so' + ' ' + 'many' + ' ' + 'words' + ' ' + 'they' + ' ' + "don't" + ' ' + + 'fit' + ' ' + 'in' + ' ' + 'one' + ' ' + 'line' +} + +fn concat_inside_ternary() { + { // This block is needed to force line wrapping + cline := if iline == pos.line_nr { + sline[..start_column] + color(kind, sline[start_column..end_column]) + + sline[end_column..] + } else { + sline + } + } +} + +fn concat_two_or_more_ternaries() { + x := if some_condition { 'very long string part' } else { 'def' } + + if y == 'asd' { 'qwe' } else { 'something else' } + var := if other_condition { 'concat three long ternary ifs' } else { 'def' } + + if true { 'shorter' } else { 'quite short' } + if true { 'small' } else { 'tiny' } +} diff --git a/v_windows/v/vlib/v/fmt/tests/concat_expr_input.vv b/v_windows/v/vlib/v/fmt/tests/concat_expr_input.vv new file mode 100644 index 0000000..383c52e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/concat_expr_input.vv @@ -0,0 +1,22 @@ +fn concatenation_of_strings() { +_ := 'Simple' + 'Concat' + _ := 'Hello' + + ' ' + + 'World' + '!' + _ := 'There' + ' ' + 'so' + ' ' + 'many' + ' ' + 'words' + ' ' + 'they' + ' ' + "don't" + ' ' + 'fit' + ' ' + 'in' + ' ' + 'one' + ' ' + 'line' +} + +fn concat_inside_ternary() { + { // This block is needed to force line wrapping + cline := if iline == pos.line_nr { + sline[..start_column] + color(kind, sline[start_column..end_column]) + sline[end_column..] + } else { + sline + } + } +} + +fn concat_two_or_more_ternaries() { + x := if some_condition { 'very long string part' } else { 'def' } + if y == 'asd' { 'qwe' } else {'something else'} + var := if other_condition { 'concat three long ternary ifs' } else { 'def' } + if true {'shorter'} else {'quite short'} + if true { 'small' } else {'tiny'} +} diff --git a/v_windows/v/vlib/v/fmt/tests/conditional_compilation_keep.vv b/v_windows/v/vlib/v/fmt/tests/conditional_compilation_keep.vv new file mode 100644 index 0000000..3d7e1d2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/conditional_compilation_keep.vv @@ -0,0 +1,19 @@ +// __global g_my_global_variable int +fn main() { + $if tinyc { + println('This will be compiled only when the compiler is tcc') + } + $if !windows { + println('This will get compiled on non-windows platforms.') + } + // + $if !linux { + println('Only non linux platforms will get this') + } $else { + println('This part is for linux only.') + } + // + $if network ? { + println('This will be run, only when the program is compiled with `-d network`') + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/conditions_expected.vv b/v_windows/v/vlib/v/fmt/tests/conditions_expected.vv new file mode 100644 index 0000000..cee2823 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/conditions_expected.vv @@ -0,0 +1,12 @@ +fn fn_with_if_else() { + if true { + a = 10 + a++ + } else { + println('false') + } +} + +fn fn_with_if_else_oneline() { + _ := if true { 1 } else { 2 } +} diff --git a/v_windows/v/vlib/v/fmt/tests/conditions_input.vv b/v_windows/v/vlib/v/fmt/tests/conditions_input.vv new file mode 100644 index 0000000..e38b836 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/conditions_input.vv @@ -0,0 +1,10 @@ +fn fn_with_if_else() { + if true { + a = 10 + a++ + } else { println('false') } +} + +fn fn_with_if_else_oneline() { +_ := if true { 1 } else { 2 } +} diff --git a/v_windows/v/vlib/v/fmt/tests/consts_expected.vv b/v_windows/v/vlib/v/fmt/tests/consts_expected.vv new file mode 100644 index 0000000..b97e49d --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/consts_expected.vv @@ -0,0 +1,60 @@ +const ( + // pi + // pi + pi = 3.14 + // phi + // phi + // phi + phi = 1.618 + // Euler's constant + eulers = 2.7182 + supported_platforms = ['windows', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', + 'android', 'js', 'solaris', 'haiku'] + one_line_supported = ['windows', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', + 'android', 'js', 'solaris', 'haiku'] + another_const = ['a', 'b', 'c', 'd', 'e', 'f'] + multiline_const = [ + 'first line', + 'second line', + 'third line', + 'fourth line', + ] +) + +const ( + i_am_a_very_long_constant_name_so_i_stand_alone_and_my_length_is_over_90_characters = [ + 'testforit', + ] +) + +pub const ( + i_am_pub_const = true +) + +fn main() { + a := [ + [3, 5, 6], + [7, 9, 2], + ] + b := [[ + [2, 5, 8], + [5, 1, 3], + [2, 6, 0], + ], [ + [9, 4, 5], + [7, 2, 3], + [1, 2, 3], + ]] + c := [ + [ + [2, 5, 8], + [5, 1, 3], + [2, 6, 0], + ], + [ + [9, 4, 5], + [7, 2, 3], + [1, 2, 3], + ], + ] +} diff --git a/v_windows/v/vlib/v/fmt/tests/consts_input.vv b/v_windows/v/vlib/v/fmt/tests/consts_input.vv new file mode 100644 index 0000000..e16942a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/consts_input.vv @@ -0,0 +1,54 @@ +const ( +// pi +// pi +pi=3.14 +// phi +// phi +// phi +phi=1.618 + //Euler's constant +eulers=2.7182 +supported_platforms = ['windows', 'macos', 'linux', 'freebsd', 'openbsd', + 'netbsd', 'dragonfly', 'android', 'js', 'solaris', 'haiku'] +one_line_supported = ['windows', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'android', 'js', 'solaris', 'haiku'] +another_const = ['a', 'b' + 'c', 'd', 'e' + 'f' + ] +multiline_const = [ + 'first line', 'second line','third line', + 'fourth line'] +) + +const ( + i_am_a_very_long_constant_name_so_i_stand_alone_and_my_length_is_over_90_characters = ['testforit'] +) + +pub const ( +i_am_pub_const=true +) + +fn main() { + a := [ + [3, +5, + 6],[7, 9, 2]] +b := [[ +[2, +5,8],[ 5, 1, +3],[ 2, 6, 0]],[ +[9, +4,5],[ +7,2,3], +[1, +2,3]]] +c := [ +[ +[2, +5,8],[ 5, 1, +3],[ 2, 6, 0]],[[ +9, +4,5],[7,2,3], +[1, +2,3]]] +} diff --git a/v_windows/v/vlib/v/fmt/tests/consts_keep.vv b/v_windows/v/vlib/v/fmt/tests/consts_keep.vv new file mode 100644 index 0000000..c505ad7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/consts_keep.vv @@ -0,0 +1,27 @@ +const font = $embed_file('../assets/fonts/RobotoMono-Regular.ttf') + +const ( + test_alignment = 123 + foo = Foo{ + f: 'foo' + } + spam = 456 + egg = 'lorem ipsum' + spameggs = true +) + +const ( + bar = 'A string +spanning multiple +lines' + baz = 'short' + some_long_name = MyStruct{ + x: 42 + } +) + +const ( + a = 123 + abc = 123 + b = 123 +) diff --git a/v_windows/v/vlib/v/fmt/tests/consts_with_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/consts_with_comments_keep.vv new file mode 100644 index 0000000..2f8a221 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/consts_with_comments_keep.vv @@ -0,0 +1,4 @@ +const ( + fsm_state_array = ['init', 'state_a', 'state_b', 'state_c', 'exit'] // use as a first half key for map see fsm_state_ev_fn, the same order as in enum FSM_state + fsm_event_array = ['ev1', 'ev2', 'ev3', 'ev4', 'ev5'] // use as a second half key for map see fsm_state_ev_fn, the same order as in enum FSM_event +) diff --git a/v_windows/v/vlib/v/fmt/tests/embed_file_keep.vv b/v_windows/v/vlib/v/fmt/tests/embed_file_keep.vv new file mode 100644 index 0000000..2234bd7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/embed_file_keep.vv @@ -0,0 +1,8 @@ +fn main() { + mut the_png := $embed_file('v.png') + println(the_png) + content := the_png.data() + eprintln('content: ${ptr_str(content)}') + eprintln(unsafe { the_png.data().vbytes(the_png.len) }.hex()) + println(the_png) +} diff --git a/v_windows/v/vlib/v/fmt/tests/empty_curlies_and_parens_keep.vv b/v_windows/v/vlib/v/fmt/tests/empty_curlies_and_parens_keep.vv new file mode 100644 index 0000000..30e4420 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/empty_curlies_and_parens_keep.vv @@ -0,0 +1,44 @@ +const () + +const ( +) + +struct Bar {} + +struct Bar2 { +} + +interface Spam {} + +interface Spam2 { +} + +enum Baz {} + +enum Baz2 { +} + +fn foo() {} + +fn foo2() { +} + +fn main() { + arr := []int{} + x := 1 + for s in arr {} + for s in arr { + } + for i := 0; i < 5; i++ {} + for j := 0; j < 5; j++ { + } + for false {} + for false { + } + defer {} + defer { + } + unsafe {} + unsafe { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/empty_lines_expected.vv b/v_windows/v/vlib/v/fmt/tests/empty_lines_expected.vv new file mode 100644 index 0000000..605c552 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/empty_lines_expected.vv @@ -0,0 +1,28 @@ +type MyInt = int +type EmptyLineAfterLastType = fn () int + +struct EmptyLineAfterStructs {} + +fn empty_line_after_functions() {} + +fn squash_multiple_empty_lines() { + println('a') + + println('b') + + c := 0 + + d := 0 +} + +fn remove_leading_and_trailing_empty_lines() { + println('a') + + println('b') + + if test { + c := 0 + } else { + d := 0 + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/empty_lines_input.vv b/v_windows/v/vlib/v/fmt/tests/empty_lines_input.vv new file mode 100644 index 0000000..f8eee66 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/empty_lines_input.vv @@ -0,0 +1,34 @@ +type MyInt = int +type EmptyLineAfterLastType = fn() int +struct EmptyLineAfterStructs {} +fn empty_line_after_functions() {} +fn squash_multiple_empty_lines() { + println('a') + + + println('b') + + + c := 0 + + + d := 0 +} + +fn remove_leading_and_trailing_empty_lines() { + + println('a') + + println('b') + + if test { + + c := 0 + + } else { + + d := 0 + + } + +} diff --git a/v_windows/v/vlib/v/fmt/tests/empty_lines_keep.vv b/v_windows/v/vlib/v/fmt/tests/empty_lines_keep.vv new file mode 100644 index 0000000..7e22486 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/empty_lines_keep.vv @@ -0,0 +1,98 @@ +// Keep empty lines between types +type Expr = IfExpr | IntegerLiteral + +type Node2 = Expr | string +type MyInt = int + +// Keep empty lines in const blocks +const ( + _ = SomeStruct{ + val: 'Multiline field exprs should cause no problems' + } + _ = 1 + + _ = 'Keep this empty line before' + // Comment before a field + _ = 2 + + // The empty line above should stay too + _ = 3 + + // This comment doesn't really belong anywhere + + _ = 'A multiline string with a StringInterLiteral... + $foo + ...and the ending brace on a newline had a wrong pos.last_line. + ' + _ = 4 +) + +fn keep_single_empty_line() { + println('a') + println('b') + + println('c') + + d := 0 + + if true { + println('e') + } + + f := 0 + arr << MyStruct{} + + arr2 := [1, 2] +} + +fn prevent_empty_line_after_multi_line_statements() { + // line1 + /* + block1 + */ + /* + block2 + */ + if test { + println('a') + } + println('b') + for test { + println('c') + } + c := fn (s string) { + println('s') + } +} + +fn between_orm_blocks() { + sql db { + insert upper_1 into Upper + } + + upper_s := sql db { + select from Upper where id == 1 + } +} + +fn no_empty_lines() { + _ := $embed_file('testy.v') + mut files := map[string][]FileData{} + code := 'foo +bar' + params[0] = IfExpr{ + ...params[0] + typ: params[0].typ.set_nr_muls(1) + } + env_value = environ()[env_lit] or { + return error('the environment variable "$env_lit" does not exist.') + } + assert '$mr_one_two()' == "(One{ + value: 'one' +}, Two{ + value: 'two' +})" + r := m[key] ? + compile_time_env := $env('ENV_VAR') + func() +} diff --git a/v_windows/v/vlib/v/fmt/tests/enum_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/enum_comments_keep.vv new file mode 100644 index 0000000..f46208b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/enum_comments_keep.vv @@ -0,0 +1,17 @@ +module main + +// Top level comment +enum EnumType { + // comment for the enum + abc + xyz // comment for the enum field +} + +// Another top level comment +enum PacketType { + // Regular data packet + data // abc def + // xyz + // another comment + abcd // jklmn +} diff --git a/v_windows/v/vlib/v/fmt/tests/enums_expected.vv b/v_windows/v/vlib/v/fmt/tests/enums_expected.vv new file mode 100644 index 0000000..8c30ec2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/enums_expected.vv @@ -0,0 +1,9 @@ +pub enum PubEnum { + foo + bar +} + +enum PrivateEnum { + foo + bar +} diff --git a/v_windows/v/vlib/v/fmt/tests/enums_input.vv b/v_windows/v/vlib/v/fmt/tests/enums_input.vv new file mode 100644 index 0000000..c7af1d6 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/enums_input.vv @@ -0,0 +1,5 @@ +pub enum PubEnum{ + foo + bar +} +enum PrivateEnum { foo bar } diff --git a/v_windows/v/vlib/v/fmt/tests/expressions_expected.vv b/v_windows/v/vlib/v/fmt/tests/expressions_expected.vv new file mode 100644 index 0000000..1498e2e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/expressions_expected.vv @@ -0,0 +1,103 @@ +import v.checker +import v.ast +import v.table +import v.gen +import v.token + +fn string_inter_lit(mut c checker.Checker, mut node ast.StringInterLiteral) table.Type { + for i, expr in node.exprs { + ftyp := c.expr(expr) + node.expr_types << ftyp + typ := c.table.unalias_num_type(ftyp) + mut fmt := node.fmts[i] + // analyze and validate format specifier + if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `p`, `_`] { + c.error('unknown format specifier `${fmt:c}`', node.fmt_poss[i]) + } + if node.precisions[i] != 987698 && !typ.is_float() { + c.error('precision specification only valid for float types', node.fmt_poss[i]) + } + if node.pluss[i] && !typ.is_number() { + c.error('plus prefix only allowd for numbers', node.fmt_poss[i]) + } + if (typ.is_unsigned() && fmt !in [`u`, `x`, `X`, `o`, `c`]) + || (typ.is_signed() && fmt !in [`d`, `x`, `X`, `o`, `c`]) + || (typ.is_int_literal() && fmt !in [`d`, `c`, `x`, `X`, `o`, `u`, `x`, `X`, `o`]) + || (typ.is_float() && fmt !in [`E`, `F`, `G`, `e`, `f`, `g`]) + || (typ.is_pointer() && fmt !in [`p`, `x`, `X`]) + || (typ.is_string() && fmt != `s`) + || (typ.idx() in [table.i64_type_idx, table.f64_type_idx] && fmt == `c`) { + c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`', + node.fmt_poss[i]) + } + node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ) + } + + return table.string_type +} + +fn get_some_val(a_test f64, b_test f64, c_test f64, d_test f64, e_test f64, f_test f64) f64 { + return a_test * b_test * c_test * d_test + e_test * f_test * a_test * d_test + + a_test * b_test * c_test +} + +fn main() { + a, b, r, d := 5.3, 7.5, 4.4, 6.6 + if a + b + r * d + a + b + r * d > a + b + r * d + a * b + r { + println('ok') + } + v_str := 'v' + s := []string{} + s << ' `$v_str`' + println(s) + println('this is quite a long string' + + ' that is followd by an even longer part that should go to another line') + if (a == b && b > r) || (d > r) || (a < b) || (b < d && a + b > r) + || (a + b + d >= 0 && r < 0) || (a > b && d - r < b) { + println('ok') + } +} + +fn gen_str_for_multi_return(mut g gen.Gen, info table.MultiReturn, styp string, str_fn_name string) { + for i, _ in info.types { + println('\tstrings__Builder_write(&sb, _STR("\'%.*s\\000\'", 2, a.arg$i));') + } +} + +struct Parser { + peek_tok token.Token + peek_tok2 token.Token + peek_tok3 token.Token +} + +fn (mut p Parser) name_expr() { + if p.peek_tok.kind == .lpar + || (p.peek_tok.kind == .lt && p.peek_tok2.kind == .name && p.peek_tok3.kind == .gt) { + println(p.peek_tok.lit) + } +} + +fn set_nr_muls(t table.Type, nr_muls int) table.Type { + return int(t) & 0xff00ffff | (nr_muls << 16) +} + +// Test what exprs are treated as multiline. The ternary if only functions as a wrapper. +// When one expr in a branch doesn't fit a single line, the whole if will be unwrapped. +fn multiline_exprs() { + // StructInit with at least one field + _ := if true { + Foo{} + } else { + Foo{ + val: 123 + } + } + // ConcatExpr with a multiline child expr + _, _ := if true { + 1, Foo{ + val: 123 + } + } else { + 2, Foo{} + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/expressions_input.vv b/v_windows/v/vlib/v/fmt/tests/expressions_input.vv new file mode 100644 index 0000000..dec30ce --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/expressions_input.vv @@ -0,0 +1,102 @@ +import v.checker +import v.ast +import v.table +import v.gen +import v.token + +fn string_inter_lit(mut c checker.Checker, mut node ast.StringInterLiteral) table.Type { + for i, expr in node.exprs { + ftyp := c.expr(expr) + node.expr_types << ftyp + typ := c.table.unalias_num_type(ftyp) + mut fmt := node.fmts[i] + // analyze and validate format specifier + if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, + `d`, `u`, `x`, `X`, `o`, `c`, `s`, `p`, `_`] { + c.error('unknown format specifier `${fmt:c}`', + node.fmt_poss[i]) + } + if node.precisions[i] != 987698 && + !typ.is_float() { + c.error('precision specification only valid for float types', + node.fmt_poss[i]) + } + if node.pluss[i] && !typ.is_number() { + c.error('plus prefix only allowd for numbers', node.fmt_poss[i]) + } + if (typ.is_unsigned() && fmt !in [`u`, `x`, `X`, `o`, `c`]) || (typ.is_signed() && + fmt !in [`d`, `x`, `X`, `o`, `c`]) || (typ.is_int_literal() + && fmt !in [`d`, `c`, `x`, `X`, `o`, + `u`, `x`, `X`, `o`]) || (typ.is_float() && fmt !in [`E`, `F`, + `G`, `e`, `f`, `g`]) || (typ.is_pointer() && + fmt !in [`p`, `x`, `X`]) || (typ.is_string() && fmt != `s`) + || (typ.idx() in [table.i64_type_idx, + table.f64_type_idx + ] && fmt == `c`) { + c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`', + node.fmt_poss[i]) + } + node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ) + } + + return table.string_type +} + +fn get_some_val(a_test, b_test, c_test, d_test, e_test, f_test f64) f64 { + return a_test*b_test*c_test* + d_test+e_test*f_test*a_test*d_test+a_test* + b_test*c_test +} + +fn main() { + a, b, r, d := 5.3, 7.5, 4.4, 6.6 + if a+b+ + r*d+a+b+r* + d > a+b+r*d+ + a*b+r { + println('ok') + } + v_str := 'v' + s := []string{} + s << ' `$v_str`' + println(s) + println('this is quite a long string' + ' that is followd by an even longer part that should go to another line') + if (a == b && b > r) || (d > r) || (a < b) || (b< d && a+b > r) || (a+b+d >= 0 && r < 0) || (a > b && d-r < b) { + println('ok') + } +} + +fn gen_str_for_multi_return(mut g gen.Gen, + info table.MultiReturn, styp, str_fn_name string) { +for i, _ in info.types { + println('\tstrings__Builder_write(&sb, _STR("\'%.*s\\000\'", 2, a.arg$i));') + } +} + +struct Parser { + peek_tok token.Token + peek_tok2 token.Token + peek_tok3 token.Token +} + +fn (mut p Parser) name_expr() { + if p.peek_tok.kind == + .lpar || (p.peek_tok.kind == .lt && p.peek_tok2.kind == .name && + p.peek_tok3.kind == .gt) { + println(p.peek_tok.lit) + } +} + +fn set_nr_muls(t table.Type, nr_muls int) table.Type { + return int(t) & + 0xff00ffff | (nr_muls << 16) +} + +// Test what exprs are treated as multiline. The ternary if only functions as a wrapper. +// When one expr in a branch doesn't fit a single line, the whole if will be unwrapped. +fn multiline_exprs() { + // StructInit with at least one field + _ := if true { Foo{} } else { Foo{ val: 123} } + // ConcatExpr with a multiline child expr + _, _ := if true { 1, Foo{val: 123} } else { 2, Foo{} } +} diff --git a/v_windows/v/vlib/v/fmt/tests/file_with_just_imports_keep.vv b/v_windows/v/vlib/v/fmt/tests/file_with_just_imports_keep.vv new file mode 100644 index 0000000..122510a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/file_with_just_imports_keep.vv @@ -0,0 +1,3 @@ +module proto +import emily33901.vproto + diff --git a/v_windows/v/vlib/v/fmt/tests/fixed_size_array_type_keep.vv b/v_windows/v/vlib/v/fmt/tests/fixed_size_array_type_keep.vv new file mode 100644 index 0000000..fbe0001 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fixed_size_array_type_keep.vv @@ -0,0 +1,28 @@ +const size = 5 + +struct Foo { + bar [size]int + baz [5]int +} + +fn foo() [1]f32 { + return [f32(0.0)]! +} + +fn main() { + _ := [5]string{init: 'abc'} +} + +// NB: secret_key_size is missing here on purpose +// vfmt should leave it as is, assuming it is comming +// from another .v file + +struct VerifyKey { + public_key [public_key_size]byte +} + +struct SigningKey { + secret_key [secret_key_size]byte +pub: + verify_key VerifyKey +} diff --git a/v_windows/v/vlib/v/fmt/tests/float_literals_expected.vv b/v_windows/v/vlib/v/fmt/tests/float_literals_expected.vv new file mode 100644 index 0000000..ec535d0 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/float_literals_expected.vv @@ -0,0 +1,4 @@ +fn main() { + a := 1.0 + println(a) +} diff --git a/v_windows/v/vlib/v/fmt/tests/float_literals_input.vv b/v_windows/v/vlib/v/fmt/tests/float_literals_input.vv new file mode 100644 index 0000000..e1f2d51 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/float_literals_input.vv @@ -0,0 +1,4 @@ +fn main() { + a := 1. + println(a) +} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_headers_with_no_bodies_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_headers_with_no_bodies_keep.vv new file mode 100644 index 0000000..98d0476 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_headers_with_no_bodies_keep.vv @@ -0,0 +1,5 @@ +fn proc_pidpath(int, voidptr, int) int + +fn C.realpath(&char, &char) &char + +fn C.chmod(&byte, int) int diff --git a/v_windows/v/vlib/v/fmt/tests/fn_multi_return_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_multi_return_keep.vv new file mode 100644 index 0000000..c7f343a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_multi_return_keep.vv @@ -0,0 +1,13 @@ +import v.ast + +fn return_multiple_values() (int, int) { + return 0, 1 +} + +fn return_multiple_values_opt() ?(int, int) { + return none +} + +fn return_multiple_values_with_type_from_other_module() (ast.File, int) { + return ast.File{}, 0 +} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_parameter_the_same_as_a_module_const_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_parameter_the_same_as_a_module_const_keep.vv new file mode 100644 index 0000000..fb725a9 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_parameter_the_same_as_a_module_const_keep.vv @@ -0,0 +1,12 @@ +module xyz + +const params = []string{} + +pub fn abc(params []string) { + if params.len > 5 { + println('more') + } + for x in params { + println(x) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_return_generic_struct_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_return_generic_struct_keep.vv new file mode 100644 index 0000000..abbc861 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_return_generic_struct_keep.vv @@ -0,0 +1,39 @@ +pub struct Optional { +mut: + value T + some bool +} + +pub struct Foo { + foo int +} + +pub fn (f Foo) new_some(value T) Optional { + return Optional{ + value: value + some: true + } +} + +pub fn (f Foo) some(opt Optional) bool { + return opt.some +} + +pub fn (f Foo) get(opt Optional) T { + return opt.value +} + +pub fn (f Foo) set(mut opt Optional, value T) { + opt.value = value + opt.some = true +} + +fn main() { + foo := Foo{} + mut o := foo.new_some(23) + println(foo.some(o)) + assert foo.some(o) == true + foo.set(mut o, 42) + println(foo.get(o)) + assert foo.get(o) == 42 +} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_expected.vv b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_expected.vv new file mode 100644 index 0000000..f082c13 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_expected.vv @@ -0,0 +1,50 @@ +struct Bar { + x string + y int + b Baz +} + +struct Baz { + x int + y int +} + +fn main() { + bar_func(x: 'this line is short enough', y: 13) + bar_func( + x: 'a very long content should cause vfmt to use multiple lines instead of one.' + y: 123456789 + ) + bar_func( + x: 'some string' + b: Baz{ + x: 0 + y: 0 + } + ) + bar2_func() + bar2_func(Bar{ x: 's' }, + x: 's' + ) + baz_func('foo', 'bar', + x: 0 + y: 0 + ) + ui.row( + // stretch: true + margin: Margin{ + top: 10 + left: 10 + right: 10 + bottom: 10 + } + ) +} + +fn bar_func(bar Bar) { +} + +fn bar2_func(bar1 Bar, bar2 Bar) { +} + +fn baz_func(a string, b string, baz Baz) {} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_input.vv b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_input.vv new file mode 100644 index 0000000..a42259f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_input.vv @@ -0,0 +1,36 @@ +struct Bar { + x string + y int + b Baz +} + +struct Baz { + x int + y int +} + +fn main() { + bar_func(x: 'this line is short enough', y: 13) + bar_func(x: 'a very long content should cause vfmt to use multiple lines instead of one.', y: 123456789) + bar_func(x: 'some string', b: Baz{ + x: 0 + y: 0 + }) + bar2_func() + bar2_func(Bar{x: 's'}, x: 's') + baz_func('foo', 'bar', x: 0 + y: 0 + ) + ui.row( + //stretch: true + margin: Margin{top:10,left:10,right:10,bottom:10} + ) +} + +fn bar_func(bar Bar) { +} + +fn bar2_func(bar1 Bar, bar2 Bar) { +} + +fn baz_func(a string, b string, baz Baz) {} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_keep.vv new file mode 100644 index 0000000..dc76db4 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_trailing_arg_syntax_keep.vv @@ -0,0 +1,56 @@ +type Foo = Bar | Baz + +struct Bar { + x string + y int + z int + a int +} + +struct Baz { + x string +} + +fn bar_func(bar Bar) {} + +fn foo_func(f Foo) {} + +fn main() { + bar_func(x: 'bar', y: 13, z: 42) + bar_func( + x: 'bar' + y: 13 + z: 42 + ) + foo_func(Baz{ + x: 'Baz as Foo sumtype' + }) + func_from_other_file(val: 'something') + bar_func( + // pre comment + x: 'struct has a pre comment' + ) + bar_func( + x: 'first field' + // comment between fields + y: 100 + ) + bar_func( + x: 'Look! A comment to my right.' // comment after field + ) + func_from_other_file( + xyz: AnotherStruct{ + f: 'here' + } + ) + ui.button( + width: 70 + onclick: fn (a voidptr, b voidptr) { + webview.new_window(url: 'https://vlang.io', title: 'The V programming language') + } + ) +} + +fn trailing_struct_with_update_expr() { + c.error('duplicate const `$field.name`', Position{ ...field.pos, len: name_len }) +} diff --git a/v_windows/v/vlib/v/fmt/tests/fn_with_anon_params_keep.vv b/v_windows/v/vlib/v/fmt/tests/fn_with_anon_params_keep.vv new file mode 100644 index 0000000..ab6a13a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fn_with_anon_params_keep.vv @@ -0,0 +1 @@ +fn C.PQgetvalue(voidptr, int, int) &byte diff --git a/v_windows/v/vlib/v/fmt/tests/fntype_alias_array_keep.vv b/v_windows/v/vlib/v/fmt/tests/fntype_alias_array_keep.vv new file mode 100644 index 0000000..080fe9a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fntype_alias_array_keep.vv @@ -0,0 +1,4 @@ +type MyFn = fn (int) int + +mut arr := []MyFn{} +arr << MyFn(test) diff --git a/v_windows/v/vlib/v/fmt/tests/fntype_alias_keep.vv b/v_windows/v/vlib/v/fmt/tests/fntype_alias_keep.vv new file mode 100644 index 0000000..8e530a7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fntype_alias_keep.vv @@ -0,0 +1,65 @@ +module ui + +import gg +import gx +import eventbus + +pub type DrawFn = fn (ctx &gg.Context, state voidptr) + +pub type ClickFn = fn (e MouseEvent, func voidptr) + +pub type KeyFn = fn (e KeyEvent, func voidptr) + +pub type ScrollFn = fn (e ScrollEvent, func voidptr) + +pub type MouseMoveFn = fn (e MouseMoveEvent, func voidptr) + +[heap] +pub struct Window { +pub mut: + ui &UI = voidptr(0) + children []Widget + child_window &Window = voidptr(0) + parent_window &Window = voidptr(0) + has_textbox bool // for initial focus + tab_index int + just_tabbed bool + state voidptr + draw_fn DrawFn + title string + mx f64 + my f64 + width int + height int + bg_color gx.Color + click_fn ClickFn + mouse_down_fn ClickFn + mouse_up_fn ClickFn + scroll_fn ScrollFn + key_down_fn KeyFn + char_fn KeyFn + mouse_move_fn MouseMoveFn + eventbus &eventbus.EventBus = eventbus.new() +} + +pub struct WindowConfig { +pub: + width int + height int + resizable bool + title string + always_on_top bool + state voidptr + draw_fn DrawFn + bg_color gx.Color = default_window_color + on_click ClickFn + on_mouse_down ClickFn + on_mouse_up ClickFn + on_key_down KeyFn + on_scroll ScrollFn + on_mouse_move MouseMoveFn + children []Widget + font_path string + // pub mut: + // parent_window &Window +} diff --git a/v_windows/v/vlib/v/fmt/tests/fntype_mut_args_with_optional_keep.vv b/v_windows/v/vlib/v/fmt/tests/fntype_mut_args_with_optional_keep.vv new file mode 100644 index 0000000..efb2685 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fntype_mut_args_with_optional_keep.vv @@ -0,0 +1 @@ +pub type SocketMessageFn = fn (mut c Client, msg &Message) ? diff --git a/v_windows/v/vlib/v/fmt/tests/fntype_return_optional_keep.vv b/v_windows/v/vlib/v/fmt/tests/fntype_return_optional_keep.vv new file mode 100644 index 0000000..35c829e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/fntype_return_optional_keep.vv @@ -0,0 +1,3 @@ +type Foo = fn (a int) ? + +type Foo2 = fn (num int) ?int diff --git a/v_windows/v/vlib/v/fmt/tests/functions_expected.vv b/v_windows/v/vlib/v/fmt/tests/functions_expected.vv new file mode 100644 index 0000000..1d88e63 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/functions_expected.vv @@ -0,0 +1,68 @@ +fn C.func(arg int) int + +fn fn_variadic(arg int, args ...string) { + println('Do nothing') +} + +fn fn_with_assign_stmts() { + _, _ := fn_with_multi_return() +} + +fn fn_with_multi_return() (int, string) { + return 0, 'test' +} + +fn voidfn() { + println('this is a function that does not return anything') +} + +fn fn_with_1_arg(arg int) int { + return 0 +} + +fn fn_with_2a_args(arg1 int, arg2 int) int { + return 0 +} + +fn fn_with_2_args_to_be_shorten(arg1 int, arg2 int) int { + return 0 +} + +fn fn_with_2b_args(arg1 string, arg2 int) int { + return 0 +} + +fn fn_with_3_args(arg1 string, arg2 int, arg3 User) int { + return 0 +} + +fn (this User) fn_with_receiver() { + println('') +} + +fn fn_with_optional() ?int { + if true { + return error('true') + } + return 30 +} + +fn (f Foo) fn_with_optional() ?int { + if true { + return error('true') + } + return 40 +} + +fn mut_array(mut a []int) { + println(1) +} + +fn fn_with_ref_return() &Foo { + return &Foo{} +} + +[inline] +fn fn_with_flag() { + println('flag') +} diff --git a/v_windows/v/vlib/v/fmt/tests/functions_input.vv b/v_windows/v/vlib/v/fmt/tests/functions_input.vv new file mode 100644 index 0000000..cbd9865 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/functions_input.vv @@ -0,0 +1,63 @@ +fn C.func(arg int) int + +fn fn_variadic(arg int, args... string) { + println('Do nothing') +} + +fn fn_with_assign_stmts() { + _,_ := fn_with_multi_return() +} + +fn fn_with_multi_return() (int,string) { + return 0,'test' +} + +fn voidfn(){ + println('this is a function that does not return anything') + } + +fn fn_with_1_arg(arg int) int { +return 0 +} + +fn fn_with_2a_args(arg1 int, arg2 int) int { +return 0 +} + +fn fn_with_2_args_to_be_shorten(arg1 int, arg2 int) int { +return 0 +} + +fn fn_with_2b_args(arg1 string, arg2 int) int { +return 0 +} + +fn fn_with_3_args(arg1 string, arg2 int, arg3 User) int { +return 0 +} + +fn (this User) fn_with_receiver() { +println('') +} + +fn fn_with_optional() ?int { + if true { return error('true') } + return 30 +} + +fn (f Foo) fn_with_optional() ?int { + if true { return error('true') } + return 40 +} + +fn mut_array(mut a []int) { + println(1) +} + +fn fn_with_ref_return() &Foo { + return &Foo{} +} + +[inline] fn fn_with_flag() { +println('flag') +} diff --git a/v_windows/v/vlib/v/fmt/tests/generic_recursive_structs_keep.vv b/v_windows/v/vlib/v/fmt/tests/generic_recursive_structs_keep.vv new file mode 100644 index 0000000..a8704c5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/generic_recursive_structs_keep.vv @@ -0,0 +1,28 @@ +pub struct Node { + value T + points_to []&Node +} + +fn main() { + mid := &Node{ + value: 'Middle' + } + finish := &Node{ + value: 'Finish' + } + + graph := &Node{ + value: 'Start' + points_to: [ + &Node{ + value: 'TopLeft' + points_to: [ + finish, + mid, + ] + }, + ] + } + + println(graph.points_to[0].value) // 'TopLeft' +} diff --git a/v_windows/v/vlib/v/fmt/tests/generic_structs_keep.vv b/v_windows/v/vlib/v/fmt/tests/generic_structs_keep.vv new file mode 100644 index 0000000..c021c7c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/generic_structs_keep.vv @@ -0,0 +1,45 @@ +struct Foo { +pub: + data T +} + +fn (f Foo) value() string { + return f.data.str() +} + +type DB = string + +struct Repo { + db DB +pub mut: + model T + permission U +} + +struct User { +mut: + name string +} + +struct Permission { +pub mut: + name string +} + +fn main() { + foo_int := Foo{2} + assert foo_int.value() == '2' + println(foo_int) + // + x := Repo{'abc', 3, 1.5} + println(x.db) + println(x.model) + println(x.permission) + // + mut a := Repo{ + model: User{ + name: 'joe' + } + } + println(a.model.name) +} diff --git a/v_windows/v/vlib/v/fmt/tests/generics_cascade_types_keep.vv b/v_windows/v/vlib/v/fmt/tests/generics_cascade_types_keep.vv new file mode 100644 index 0000000..82fd8dd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/generics_cascade_types_keep.vv @@ -0,0 +1,27 @@ +struct Foo { +pub: + data T +} + +struct Foo1 {} + +struct Foo2 {} + +fn multi_generic_args(t T, v V) bool { + return true +} + +fn main() { + v1, v2 := -1, 1 + + // not generic + a1, a2 := v1 < v2, v2 > v1 + assert a1 && a2 + b1, b2 := v1 < simplemodule.zero, v2 > v1 + assert b1 && b2 + + // generic + assert multi_generic_args(0, 's') + assert multi_generic_args(Foo1{}, Foo2{}) + assert multi_generic_args, Foo >(Foo{}, Foo{}) +} diff --git a/v_windows/v/vlib/v/fmt/tests/generics_keep.vv b/v_windows/v/vlib/v/fmt/tests/generics_keep.vv new file mode 100644 index 0000000..4fe55e7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/generics_keep.vv @@ -0,0 +1,32 @@ +import mymod { ImpNode } + +fn foobar_mymod(inode ImpNode) ImpNode { + return ImpNode{} +} + +fn simple() T { + return T{} +} + +struct Foo {} + +fn (_ Foo) simple() T { + return T{} +} + +struct NonGenericStruct {} + +fn use_as_generic(ngs NonGenericStruct) NonGenericStruct { + return NonGenericStruct{} +} + +struct GenericStruct {} + +fn proper_generics(gs GenericStruct) GenericStruct { + return gs +} + +fn main() { + simple() + Foo{}.simple() +} diff --git a/v_windows/v/vlib/v/fmt/tests/global_keep.vv b/v_windows/v/vlib/v/fmt/tests/global_keep.vv new file mode 100644 index 0000000..e809f3d --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/global_keep.vv @@ -0,0 +1,10 @@ +__global x = bool(true) +__global y f32 +__global () +__global ( + spam string + foo = int(5) +) +__global ( + a int +) diff --git a/v_windows/v/vlib/v/fmt/tests/go_stmt_expected.vv b/v_windows/v/vlib/v/fmt/tests/go_stmt_expected.vv new file mode 100644 index 0000000..5e26722 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/go_stmt_expected.vv @@ -0,0 +1,14 @@ +fn my_thread() { + println('yo') +} + +fn my_thread_with_params(s string) { + println(s) +} + +fn my_fn_calling_threads() { + go my_thread() + go my_thread_with_params('yay') + + go my_thread_with_params('nono') +} diff --git a/v_windows/v/vlib/v/fmt/tests/go_stmt_input.vv b/v_windows/v/vlib/v/fmt/tests/go_stmt_input.vv new file mode 100644 index 0000000..3ccaf8f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/go_stmt_input.vv @@ -0,0 +1,16 @@ +fn my_thread() { + println('yo') +} + +fn my_thread_with_params(s string) +{ + println(s) +} + +fn my_fn_calling_threads () { +go my_thread() + go my_thread_with_params('yay') + + go + my_thread_with_params('nono') +} diff --git a/v_windows/v/vlib/v/fmt/tests/go_stmt_keep.vv b/v_windows/v/vlib/v/fmt/tests/go_stmt_keep.vv new file mode 100644 index 0000000..e404156 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/go_stmt_keep.vv @@ -0,0 +1,9 @@ +import sync + +fn go_with_anon_fn() { + wg.add(1) + go fn (mut wg sync.WaitGroup) { + wg.done() + }(mut wg) + wg.wait() +} diff --git a/v_windows/v/vlib/v/fmt/tests/goto_expected.vv b/v_windows/v/vlib/v/fmt/tests/goto_expected.vv new file mode 100644 index 0000000..144a3ad --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/goto_expected.vv @@ -0,0 +1,4 @@ +fn test_goto() { + goto label + label: +} diff --git a/v_windows/v/vlib/v/fmt/tests/goto_input.vv b/v_windows/v/vlib/v/fmt/tests/goto_input.vv new file mode 100644 index 0000000..c0740dd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/goto_input.vv @@ -0,0 +1,4 @@ +fn test_goto() { + goto label +label: +} diff --git a/v_windows/v/vlib/v/fmt/tests/hashstmt_expected.vv b/v_windows/v/vlib/v/fmt/tests/hashstmt_expected.vv new file mode 100644 index 0000000..057d14a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/hashstmt_expected.vv @@ -0,0 +1,3 @@ +#include "header.h" + +struct Foo {} diff --git a/v_windows/v/vlib/v/fmt/tests/hashstmt_input.vv b/v_windows/v/vlib/v/fmt/tests/hashstmt_input.vv new file mode 100644 index 0000000..406f30a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/hashstmt_input.vv @@ -0,0 +1,2 @@ +#include "header.h" +struct Foo {} diff --git a/v_windows/v/vlib/v/fmt/tests/hashstmt_keep.vv b/v_windows/v/vlib/v/fmt/tests/hashstmt_keep.vv new file mode 100644 index 0000000..dd994f5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/hashstmt_keep.vv @@ -0,0 +1,12 @@ +// comment above HashStmt +#flag -I @VROOT/c +#flag @VROOT/c/implementation.o + +#include "@VROOT/c/implementation.h" + +// comment between with newlines around + +#include "header.h" +// comment between without newlines +#include "sqlite3.h" +// comment directly after the HsahStmt diff --git a/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_expected.vv b/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_expected.vv new file mode 100644 index 0000000..6613108 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_expected.vv @@ -0,0 +1,12 @@ +fn get_typ() Type { + { + { + // The opening brace should be put on a new line here for readability + if typ := c.resolve_generic_type(method.return_type, method.generic_names, + call_expr.generic_types) + { + return typ + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_input.vv b/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_input.vv new file mode 100644 index 0000000..52b9b2a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_brace_on_newline_input.vv @@ -0,0 +1,10 @@ +fn get_typ() Type { + { + { + // The opening brace should be put on a new line here for readability + if typ := c.resolve_generic_type(method.return_type, method.generic_names, call_expr.generic_types) { + return typ + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_expected.vv b/v_windows/v/vlib/v/fmt/tests/if_expected.vv new file mode 100644 index 0000000..e691055 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_expected.vv @@ -0,0 +1,9 @@ +fn non_ternary_return() string { + return if some_cond { + 'foo' + } else if false { + 'bar' + } else { + 'baz' + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_input.vv b/v_windows/v/vlib/v/fmt/tests/if_input.vv new file mode 100644 index 0000000..5853876 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_input.vv @@ -0,0 +1,3 @@ +fn non_ternary_return() string { + return if some_cond { 'foo' } else if false { 'bar' } else { 'baz' } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_keep.vv b/v_windows/v/vlib/v/fmt/tests/if_keep.vv new file mode 100644 index 0000000..7e8166a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_keep.vv @@ -0,0 +1,11 @@ +fn void_type_array_cond() { + if fg == [] { + stack.ctx.reset_color() + } +} + +fn multi_dimensional_array_cond() { + if t.data == [][]string{} { + return error('Table.data should not be empty.') + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_not_in_is_expected.vv b/v_windows/v/vlib/v/fmt/tests/if_not_in_is_expected.vv new file mode 100644 index 0000000..fefa1e1 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_not_in_is_expected.vv @@ -0,0 +1,17 @@ +struct Foo1 {} + +struct Foo2 {} + +type Foo = Foo1 | Foo2 + +fn main() { + a := [1, 2, 3] + if 4 !in a { + println('4 not in a') + } + + foo := Foo(Foo1{}) + if foo !is Foo2 { + println('foo is not Foo2') + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_not_in_is_input.vv b/v_windows/v/vlib/v/fmt/tests/if_not_in_is_input.vv new file mode 100644 index 0000000..5527b6b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_not_in_is_input.vv @@ -0,0 +1,17 @@ +struct Foo1{} + +struct Foo2{} + +type Foo = Foo1 | Foo2 + +fn main() { + a := [1, 2, 3] + if !(4 in a) { + println('4 not in a') + } + + foo := Foo(Foo1{}) + if !(foo is Foo2) { + println('foo is not Foo2') + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_ternary_expected.vv b/v_windows/v/vlib/v/fmt/tests/if_ternary_expected.vv new file mode 100644 index 0000000..e100a4a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_ternary_expected.vv @@ -0,0 +1,48 @@ +fn main() { + // This line is too long + sprogress := if b.no_cstep { + 'TMP1/${b.nexpected_steps:1d}' + } else { + '${b.cstep:1d}/${b.nexpected_steps:1d}' + } + // Normal struct inits + _ := if true { + Foo{} + } else { + Foo{ + x: 5 + } + } + _ := if some_cond { + Bar{ + a: 'bar' + b: 'also bar' + } + } else { + Bar{} + } +} + +fn condition_is_very_long_infix() { + val := if the_first_condition && this_is_required_too + && (another_cond || foobar_to_exceed_the_max_len) { + 'true' + } else { + 'false' + } +} + +fn branches_are_long_fn_calls() { + _ := if nr_dims == 1 { + t.find_or_register_array(elem_type) + } else { + t.find_or_register_arra(t.find_or_register_array_with_dims(elem_type, nr_dims - 1)) + } + // With another arg to make fn call exceed the max_len after if unwrapping + _ := if nr_dims == 1 { + t.find_or_register_array(elem_type) + } else { + t.find_or_register_arra(t.find_or_register_array_with_dims(elem_type, nr_dims - 1, + 'some string')) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_ternary_input.vv b/v_windows/v/vlib/v/fmt/tests/if_ternary_input.vv new file mode 100644 index 0000000..c76a8ff --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_ternary_input.vv @@ -0,0 +1,19 @@ +fn main() { + // This line is too long + sprogress := if b.no_cstep { 'TMP1/${b.nexpected_steps:1d}' } else { '${b.cstep:1d}/${b.nexpected_steps:1d}' } + // Normal struct inits + _ := if true { Foo{} } else { Foo{ + x: 5 + } } + _ := if some_cond { Bar{ a: 'bar', b: 'also bar'} } else { Bar{} } +} + +fn condition_is_very_long_infix() { + val := if the_first_condition && this_is_required_too && (another_cond || foobar_to_exceed_the_max_len) { 'true' } else { 'false' } +} + +fn branches_are_long_fn_calls() { + _ := if nr_dims == 1 { t.find_or_register_array(elem_type) } else { t.find_or_register_arra(t.find_or_register_array_with_dims(elem_type, nr_dims - 1)) } + // With another arg to make fn call exceed the max_len after if unwrapping + _ := if nr_dims == 1 { t.find_or_register_array(elem_type) } else { t.find_or_register_arra(t.find_or_register_array_with_dims(elem_type, nr_dims - 1, 'some string')) } +} diff --git a/v_windows/v/vlib/v/fmt/tests/if_ternary_keep.vv b/v_windows/v/vlib/v/fmt/tests/if_ternary_keep.vv new file mode 100644 index 0000000..8352cc5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/if_ternary_keep.vv @@ -0,0 +1,44 @@ +struct Foo { + n int + s string +} + +fn valid_single_line() { + // Variable initialization + a, b := if true { 'a', 'b' } else { 'b', 'a' } + // Variable assignment + mut x := 'abc' + x = if x == 'def' { 'ghi' } else { 'def' } + // Array pushes + [0, 1] << if true { 2 } else { 3 } + // Empty or literal syntax struct inits + _ := if false { Foo{} } else { Foo{5, 6} } + // As argument for a function call + some_func(if cond { 'param1' } else { 'param2' }) + // struct init + foo := Foo{ + n: if true { 1 } else { 0 } + s: if false { 'false' } else { 'true' } + } +} + +fn requires_multiple_lines() { + b := if bar { + // with comments inside + 'some str' + } else { + 'other str' + } +} + +fn return_ternary(cond bool) int { + return if cond { 5 } else { 12 } +} + +fn long_return_ternary() string { + return if false { + 'spam and eggs' + } else { + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_auto_added_expected.vv b/v_windows/v/vlib/v/fmt/tests/import_auto_added_expected.vv new file mode 100644 index 0000000..2b9dc52 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_auto_added_expected.vv @@ -0,0 +1,7 @@ +module main + +import time + +fn main() { + curr_time := time.now() +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_auto_added_input.vv b/v_windows/v/vlib/v/fmt/tests/import_auto_added_input.vv new file mode 100644 index 0000000..4c6a559 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_auto_added_input.vv @@ -0,0 +1,5 @@ +module main + +fn main() { + curr_time := time.now() +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/import_comments_keep.vv new file mode 100644 index 0000000..1f64b0b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_comments_keep.vv @@ -0,0 +1,11 @@ +// import time This should be commented out +import os +// between +import rand +// first between +// second between +import semver +// after the last import +// also after the last import + +// this comment does not belong to the imports anymore diff --git a/v_windows/v/vlib/v/fmt/tests/import_duplicate_expected.vv b/v_windows/v/vlib/v/fmt/tests/import_duplicate_expected.vv new file mode 100644 index 0000000..5769710 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_duplicate_expected.vv @@ -0,0 +1,10 @@ +import math +import os + +const ( + mypi = math.pi +) + +fn main() { + println(os.path_separator) +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_duplicate_input.vv b/v_windows/v/vlib/v/fmt/tests/import_duplicate_input.vv new file mode 100644 index 0000000..0c11afe --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_duplicate_input.vv @@ -0,0 +1,11 @@ +import math +import os +import math + +const ( + mypi = math.pi +) + +fn main() { + println(os.path_separator) +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_expected.vv b/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_expected.vv new file mode 100644 index 0000000..e2ee322 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_expected.vv @@ -0,0 +1,11 @@ +import os +import math + +fn main() { + // println(m.pi) + println(os.path_separator) + println(math.pi) + // math as m + // import math.complex as c + // num := c.Complex{} TODO +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_input.vv b/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_input.vv new file mode 100644 index 0000000..e2ee322 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_multiple_with_alias_input.vv @@ -0,0 +1,11 @@ +import os +import math + +fn main() { + // println(m.pi) + println(os.path_separator) + println(math.pi) + // math as m + // import math.complex as c + // num := c.Complex{} TODO +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_selective_expected.vv b/v_windows/v/vlib/v/fmt/tests/import_selective_expected.vv new file mode 100644 index 0000000..fa72af7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_selective_expected.vv @@ -0,0 +1,67 @@ +import math { max, min } +import cli { Command } +import math.complex { Complex, complex } +import os { + file_ext, + user_os, +} +import mod { + Enum, + FnArg, + FnRet, + InterfaceField, + InterfaceMethodArg, + InterfaceMethodRet, + RightOfAs, + RightOfIs, + StructEmbed, + StructField, + StructMethodArg, + StructMethodRet, + StructRefField, +} + +struct Struct { + StructEmbed + v StructField + ref &StructRefField +} + +fn (s Struct) method(v StructMethodArg) StructMethodRet { + return StructMethodRet{} +} + +interface Interface { + v InterfaceField + f(InterfaceMethodArg) InterfaceMethodRet +} + +fn f(v FnArg) FnRet { + if v is RightOfIs { + } + _ = v as RightOfAs + + println(Enum.val) + + return FnRet{} +} + +struct App { + command &Command +} + +struct MyCommand { + Command +} + +fn imaginary(im f64) Complex { + return complex(0, im) +} + +fn main() { + println(max(0.1, 0.2)) + println(min(0.1, 0.2)) + println(user_os()) + println(file_ext('main.v')) + println(imaginary(1)) +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_selective_input.vv b/v_windows/v/vlib/v/fmt/tests/import_selective_input.vv new file mode 100644 index 0000000..534b72e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_selective_input.vv @@ -0,0 +1,71 @@ +import math { max, + min, +} + +import cli { Command } +import math.complex { complex, Complex } +import os { + input, user_os, file_ext } + +import mod { + Unused, + StructEmbed, StructField, StructRefField + StructMethodArg, + StructMethodRet + + InterfaceField, + InterfaceMethodArg, + InterfaceMethodRet, + + FnArg, + FnRet, + + RightOfIs, + RightOfAs, + + Enum +} + +struct Struct { + StructEmbed + v StructField + ref &StructRefField +} + +fn (s Struct) method(v StructMethodArg) StructMethodRet { + return StructMethodRet{} +} + +interface Interface { + v InterfaceField + f(InterfaceMethodArg) InterfaceMethodRet +} + +fn f(v FnArg) FnRet { + if v is RightOfIs {} + _ = v as RightOfAs + + println(Enum.val) + + return FnRet{} +} + +struct App { + command &Command +} + +struct MyCommand { + Command +} + +fn imaginary(im f64) Complex { + return complex(0, im) +} + +fn main() { + println(max(0.1, 0.2)) + println(min(0.1, 0.2)) + println(user_os()) + println(file_ext('main.v')) + println(imaginary(1)) +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_selective_keep.vv b/v_windows/v/vlib/v/fmt/tests/import_selective_keep.vv new file mode 100644 index 0000000..592a635 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_selective_keep.vv @@ -0,0 +1,12 @@ +import math.complex { Complex } +import gg { MouseButton } +import time { Duration } + +fn keep_imported_enum_map_key() { + bm := map[MouseButton]string{} +} + +fn main() { + _ := Duration(10) // keep cast type + assert *(&f64(&byte(&num) + __offsetof(Complex, re))) == 1.0 +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_single_keep.vv b/v_windows/v/vlib/v/fmt/tests/import_single_keep.vv new file mode 100644 index 0000000..29d4d36 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_single_keep.vv @@ -0,0 +1,5 @@ +import os + +fn main() { + os.system('echo hi') +} diff --git a/v_windows/v/vlib/v/fmt/tests/import_with_alias_keep.vv b/v_windows/v/vlib/v/fmt/tests/import_with_alias_keep.vv new file mode 100644 index 0000000..f883f0e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/import_with_alias_keep.vv @@ -0,0 +1,25 @@ +import os +import time as t +import some.library as slib +import crypto.sha256 +import mymod.sha256 as mysha256 + +type my_alias = fn (t slib.MyType) + +struct Foo { + bar t.Time + bars []t.Time + barref &t.Time + barrefs []&t.Time + c_type C.some_struct + js_type JS.other_struct +} + +fn main() { + println('start') + t.sleep_ms(500) + println('end') + os.system('date') + v_hash := sha256.sum('hi'.bytes()).hex() + my_hash := mysha256.sum('hi'.bytes()).hex() +} diff --git a/v_windows/v/vlib/v/fmt/tests/infix_expr_expected.vv b/v_windows/v/vlib/v/fmt/tests/infix_expr_expected.vv new file mode 100644 index 0000000..4dfa012 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/infix_expr_expected.vv @@ -0,0 +1,33 @@ +fn grouped_cond_single_line() { + // fmt tries to keep grouped conditions together... + _ := one_condition_before && another_condition + && (inside_paren || is_kept_together || if_possible) && end_cond +} + +fn unwrap_grouped_conds() { + // ...but sometimes they have to be splitted + _ := one_condition && before_condition && (conds_inside_paren + || are_kept_together || if_possible || but_this_is_really_too_much + || for_one_line) + _ := (also_inside_parens || just_as_above || but_this_is_also_more + || than_a_single_line_could_fit) && end_cond + fields = fields.filter((it.typ in [string_type, int_type, bool_type] + || c.table.types[int(it.typ)].kind == .struct_) && !it.attrs.contains('skip')) +} + +fn main() { + clean_struct_v_type_name = + clean_struct_v_type_name.replace('_Array', '_array').replace('_T_', '<').replace('_', ', ') + + '>' + { + { + { + // Indent this much to force a break after the assign (`:=`). + // Check that the trailing space is removed + should_cast := + (g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables) + && (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last()) + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/infix_expr_input.vv b/v_windows/v/vlib/v/fmt/tests/infix_expr_input.vv new file mode 100644 index 0000000..7bfec62 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/infix_expr_input.vv @@ -0,0 +1,24 @@ +fn grouped_cond_single_line() { + // fmt tries to keep grouped conditions together... + _ := one_condition_before && another_condition && (inside_paren || is_kept_together || if_possible) && end_cond +} + +fn unwrap_grouped_conds() { + // ...but sometimes they have to be splitted + _ := one_condition && before_condition && (conds_inside_paren || are_kept_together || if_possible || but_this_is_really_too_much || for_one_line) + _ := (also_inside_parens || just_as_above || but_this_is_also_more || than_a_single_line_could_fit) && end_cond + fields = fields.filter((it.typ in [string_type, int_type, bool_type] || c.table.types[int(it.typ)].kind == .struct_) && !it.attrs.contains('skip')) +} + +fn main() { + clean_struct_v_type_name = clean_struct_v_type_name.replace('_Array', '_array').replace('_T_', '<').replace('_', ', ') + '>' + { + { + { + // Indent this much to force a break after the assign (`:=`). + // Check that the trailing space is removed + should_cast := (g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables) && (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last()) + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/infix_expr_keep.vv b/v_windows/v/vlib/v/fmt/tests/infix_expr_keep.vv new file mode 100644 index 0000000..5eecc2c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/infix_expr_keep.vv @@ -0,0 +1,15 @@ +fn infix_in_multi_assign() { + child_width, child_height = child.adj_width + child.margin(.left) + child.margin(.right), + child.adj_height + child.margin(.top) + child.margin(.bottom) +} + +fn impostor_infix() { + /* + String concatiation is an InfixExpr. The second part is so long + that it overflows a single line. Therefore the wrapping logic is run. + The problem was that `&&` and `||` inside the string were detected as infix operators. + Thus causing a totally wrong wrapping and sometimes runtime panics. + */ + x := 'foo' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa || (eeeeeeeeeeeeeeeeee && ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) && bbbbbbbbbbbbbbbbbbbbbbbbbbbb' +} diff --git a/v_windows/v/vlib/v/fmt/tests/integer_literal_keep.vv b/v_windows/v/vlib/v/fmt/tests/integer_literal_keep.vv new file mode 100644 index 0000000..f26d917 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/integer_literal_keep.vv @@ -0,0 +1,19 @@ +struct IfExpr { +} + +struct MatchExpr { +} + +type Expr = IfExpr | MatchExpr + +fn sum_types(a []Expr) { +} + +fn main() { + x := 0xdead_beef + u := 9_978_654_321 + o := 0o66_4 + eprintln(' hex constant in decimal: $x') + eprintln(' u constant in decimal: $u') + eprintln('octal constant in decimal: $o') +} diff --git a/v_windows/v/vlib/v/fmt/tests/interface_declaration_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/interface_declaration_comments_keep.vv new file mode 100644 index 0000000..60c8312 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/interface_declaration_comments_keep.vv @@ -0,0 +1,31 @@ +pub interface ReaderWriter { + read(mut buf []byte) ?int // from Reader + write(buf []byte) ?int // from Writer +} + +interface Speaker { + // first + speak() string + // between + foo() string + foo2() string + // last +} + +interface Baz { + // first + speak() string + // comment + // more between + foo() string + foo2() string + // last +} + +interface Bar { + speak() string // after + foo() string + speak2() string // also after + // and between + foo2() string +} diff --git a/v_windows/v/vlib/v/fmt/tests/interface_variadic_keep.vv b/v_windows/v/vlib/v/fmt/tests/interface_variadic_keep.vv new file mode 100644 index 0000000..ed288d3 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/interface_variadic_keep.vv @@ -0,0 +1,4 @@ +interface Element { + unnamed_method(...f64) + named_method(params ...f64) +} diff --git a/v_windows/v/vlib/v/fmt/tests/interface_with_mut_fields_keep.vv b/v_windows/v/vlib/v/fmt/tests/interface_with_mut_fields_keep.vv new file mode 100644 index 0000000..868b09e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/interface_with_mut_fields_keep.vv @@ -0,0 +1,7 @@ +interface Toto { + a int + d() +mut: + b int + f() +} diff --git a/v_windows/v/vlib/v/fmt/tests/labelled_break_continue_keep.vv b/v_windows/v/vlib/v/fmt/tests/labelled_break_continue_keep.vv new file mode 100644 index 0000000..89fb1d2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/labelled_break_continue_keep.vv @@ -0,0 +1,38 @@ +fn test_labelled_for() { + mut i := 4 + goto L1 + L1: for { + i++ + for { + if i < 7 { + continue L1 + } else { + break L1 + } + } + } + assert i == 7 + goto L2 + L2: for ; true; i++ { + for { + if i < 17 { + continue L2 + } else { + break L2 + } + } + } + assert i == 17 + goto L3 + L3: for e in [1, 2, 3, 4] { + i = e + for { + if i < 3 { + continue L3 + } else { + break L3 + } + } + } + assert i == 3 +} diff --git a/v_windows/v/vlib/v/fmt/tests/language_prefixes_keep.vv b/v_windows/v/vlib/v/fmt/tests/language_prefixes_keep.vv new file mode 100644 index 0000000..a0a25c5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/language_prefixes_keep.vv @@ -0,0 +1,13 @@ +fn (v JS.String) toString() JS.String + +fn (v JS.String) toMultiRet() (JS.String, int) + +fn JS.Math.abs(f64) f64 + +fn main() { + JS.Math.abs(0) +} + +fn object_ref_optional() ?&C.File { + return error('') +} diff --git a/v_windows/v/vlib/v/fmt/tests/loops_expected.vv b/v_windows/v/vlib/v/fmt/tests/loops_expected.vv new file mode 100644 index 0000000..17d3f66 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/loops_expected.vv @@ -0,0 +1,18 @@ +fn for_in_loop() { + for item in arr { + println(item) + } +} + +fn for_in_loop_with_counter() { + for i, item in arr { + println(i) + println(item) + } +} + +fn for_in_loop_with_index_expr() { + for i in 0 .. 10 { + println(i) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/loops_input.vv b/v_windows/v/vlib/v/fmt/tests/loops_input.vv new file mode 100644 index 0000000..1b271a3 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/loops_input.vv @@ -0,0 +1,18 @@ +fn for_in_loop() { + for item in arr { + println(item) + } +} + +fn for_in_loop_with_counter() { + for i, item in arr { + println(i) + println(item) + } +} + +fn for_in_loop_with_index_expr() { + for i in 0..10 { + println(i) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/manualfree_keep.v b/v_windows/v/vlib/v/fmt/tests/manualfree_keep.v new file mode 100644 index 0000000..98de3f1 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/manualfree_keep.v @@ -0,0 +1,18 @@ +[manualfree] +module main + +fn abc() { + x := 'abc should be autofreed' + println(x) +} + +[manualfree] +fn xyz() { + x := 'xyz should do its own memory management' + println(x) +} + +fn main() { + abc() + xyz() +} diff --git a/v_windows/v/vlib/v/fmt/tests/maps_expected.vv b/v_windows/v/vlib/v/fmt/tests/maps_expected.vv new file mode 100644 index 0000000..122114a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/maps_expected.vv @@ -0,0 +1,16 @@ +const ( + reserved_types = { + 'i8': true + 'i16': true + 'int': true + 'i64': true + 'i128': true + } +) + +numbers := { + 'one': 1 + 'two': 2 + 'sevenhundredseventyseven': 777 + 'fivethousandthreehundredtwentyseven': 5327 +} diff --git a/v_windows/v/vlib/v/fmt/tests/maps_in_fn_args__keep.vv b/v_windows/v/vlib/v/fmt/tests/maps_in_fn_args__keep.vv new file mode 100644 index 0000000..5968438 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/maps_in_fn_args__keep.vv @@ -0,0 +1,15 @@ +import v.ast + +fn abc_xxx(xobj ast.ScopeObject) { +} + +fn abc_map(xmap map[string]ast.ScopeObject) { +} + +fn (t Tree) objects(so map[string]ast.ScopeObject) &C.cJSON { + obj := create_object() + for key, val in so { + to_object(obj, key, t.scope_object(val)) + } + return obj +} diff --git a/v_windows/v/vlib/v/fmt/tests/maps_input.vv b/v_windows/v/vlib/v/fmt/tests/maps_input.vv new file mode 100644 index 0000000..6eb783e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/maps_input.vv @@ -0,0 +1,16 @@ +const ( +reserved_types = { + 'i8': true + 'i16': true + 'int': true + 'i64': true + 'i128': true +} +) + +numbers := { + 'one': 1 + 'two': 2 + 'sevenhundredseventyseven': 777 +'fivethousandthreehundredtwentyseven': 5327 +} diff --git a/v_windows/v/vlib/v/fmt/tests/maps_keep.vv b/v_windows/v/vlib/v/fmt/tests/maps_keep.vv new file mode 100644 index 0000000..b5737f7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/maps_keep.vv @@ -0,0 +1,17 @@ +fn workaround() { + a := map[string]string{} + println(a) +} + +fn main() { + mut ams := []map[string]string{} + ams << { + 'a': 'b' + 'c': 'd' + } + ams << { + 'e': 'f' + 'g': 'h' + } + println(ams) +} diff --git a/v_windows/v/vlib/v/fmt/tests/maps_of_fns_with_string_keys_keep.vv b/v_windows/v/vlib/v/fmt/tests/maps_of_fns_with_string_keys_keep.vv new file mode 100644 index 0000000..30829de --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/maps_of_fns_with_string_keys_keep.vv @@ -0,0 +1,13 @@ +fn sqr(n int) int { + return n * n +} + +fn main() { + fns := [sqr] + println(fns[0](10)) + fns_map := { + 'sqr': sqr + } + println(fns_map['sqr']) + println(fns_map['sqr'](2)) +} diff --git a/v_windows/v/vlib/v/fmt/tests/match_expected.vv b/v_windows/v/vlib/v/fmt/tests/match_expected.vv new file mode 100644 index 0000000..556768f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/match_expected.vv @@ -0,0 +1,38 @@ +fn match_expr_assignment() { + a := 20 + _ := match a { + 10 { 10 } + 5 { 5 } + else { 2 } + } +} + +fn match_branch_comment() { + a := 1 + match a { + 1 { + println('1') + } + 2 { + println('2') + } + else { + // do nothing + } + } +} + +fn really_long_branch_exprs() { + match x { + NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, + MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, + SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit { + return expr.pos + } + InfixExpr { + Foo{ + x: 3 + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/match_input.vv b/v_windows/v/vlib/v/fmt/tests/match_input.vv new file mode 100644 index 0000000..3f7cbae --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/match_input.vv @@ -0,0 +1,36 @@ +fn match_expr_assignment() { + a := 20 + _ := match a { + 10 { 10 } + else { 2 } + 5 { 5 } + } +} + +fn match_branch_comment() { + a := 1 + match a { + 1 { println('1') } + 2 { + println('2') + } + else { + + +// do nothing + } + } +} + +fn really_long_branch_exprs() { + match x { + NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit { + return expr.pos + } + InfixExpr { + Foo{ + x: 3 + } + } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/match_keep.vv b/v_windows/v/vlib/v/fmt/tests/match_keep.vv new file mode 100644 index 0000000..73326e4 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/match_keep.vv @@ -0,0 +1,76 @@ +fn nested_match() { + match 100 { + 0...1 { + println('0 to 1') + } + else { + match 200 { + 0...1 { println('0 to 1') } + else { println('unknown value') } + } + } + } +} + +fn branches_are_struct_inits() { + match 'a' { + 'b' { SpamStruct{} } + } + match 'a' { + 'b' { + SpamStruct{ + x: 42 + } + } + } + match 'a' { + 'b' { + SpamStruct{ + // comment inside init + } + } + } +} + +fn branches_are_call_exprs_with_or_blocks() { + match 'a' { + 'b' { foo() or { panic(err.msg) } } + } + match 'a' { + 'b' { + foo() or { + // do stuff + panic(err.msg) + } + } + } + match 'a' { + 'b' { + foo() or { + another_stmt() + panic(err.msg) + } + } + } +} + +fn keep_branch_linebreaks() { + a := 10 + match a { + // first comment + 10 { + println('10') + } + 20 { + println('20') + } + else {} + } + match a { + // first comment + 10 { println('10') } + // post_comment of the first branch + 20 { println('20') } + else {} + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/match_range_expression_branches_keep.vv b/v_windows/v/vlib/v/fmt/tests/match_range_expression_branches_keep.vv new file mode 100644 index 0000000..ae99b0f --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/match_range_expression_branches_keep.vv @@ -0,0 +1,15 @@ +pub fn str_escaped(b byte) string { + str := match b { + 0 { '`\\' + '0`' } // Bug is preventing \\0 in a literal + 7 { '`\\a`' } + 8 { '`\\b`' } + 9 { '`\\t`' } + 10 { '`\\n`' } + 11 { '`\\v`' } + 12 { '`\\f`' } + 13 { '`\\r`' } + 32...126 { b.str() } + else { '0x' + b.hex() } + } + return str +} diff --git a/v_windows/v/vlib/v/fmt/tests/match_with_commented_branches_keep.vv b/v_windows/v/vlib/v/fmt/tests/match_with_commented_branches_keep.vv new file mode 100644 index 0000000..3240eb5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/match_with_commented_branches_keep.vv @@ -0,0 +1,57 @@ +module ast + +pub fn (stmt Stmt) position() Position { + match stmt { + AssertStmt { return stmt.pos } + AssignStmt { return stmt.pos } + /* + // Attr { + // } + // Block { + // } + // BranchStmt { + // } + */ + Comment { return stmt.pos } + ConstDecl { return stmt.pos } + /* + // DeferStmt { + // } + */ + EnumDecl { return stmt.pos } + ExprStmt { return stmt.pos } + FnDecl { return stmt.pos } + ForCStmt { return stmt.pos } + ForInStmt { return stmt.pos } + ForStmt { return stmt.pos } + /* + // GlobalDecl { + // } + // GoStmt { + // } + // GotoLabel { + // } + // GotoStmt { + // } + // HashStmt { + // } + */ + Import { return stmt.pos } + /* + // InterfaceDecl { + // } + // Module { + // } + */ + Return { return stmt.pos } + StructDecl { return stmt.pos } + /* + // TypeDecl { + // } + // UnsafeStmt { + // } + */ + // + else { return Position{} } + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/missing_import_expected.vv b/v_windows/v/vlib/v/fmt/tests/missing_import_expected.vv new file mode 100644 index 0000000..c463392 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/missing_import_expected.vv @@ -0,0 +1,5 @@ +import time + +fn main() { + println(time.now()) +} diff --git a/v_windows/v/vlib/v/fmt/tests/missing_import_input.vv b/v_windows/v/vlib/v/fmt/tests/missing_import_input.vv new file mode 100644 index 0000000..d173cb2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/missing_import_input.vv @@ -0,0 +1,3 @@ +fn main() { + println(time.now()) +} diff --git a/v_windows/v/vlib/v/fmt/tests/module_alias_keep.vv b/v_windows/v/vlib/v/fmt/tests/module_alias_keep.vv new file mode 100644 index 0000000..f6f77c0 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/module_alias_keep.vv @@ -0,0 +1,36 @@ +import time +import semver as sv +import term.ui as tui +import v.ast + +interface Inter { + code tui.KeyCode +} + +struct TuiStruct { + code tui.KeyCode +} + +fn foo(f time.Time) time.Time { + f2 := time.Time{} + return f +} + +fn bar(b sv.Version) sv.Version { + b2 := sv.Version{} + return b +} + +fn bar_multi_return(b sv.Version) (sv.Version, int) { + b2 := sv.Version{} + return b, 0 +} + +struct SomeStruct { + a fn (ast.Stmt, voidptr) bool +} + +fn main() { + if x is ast.FnDecl { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/module_interface_keep.vv b/v_windows/v/vlib/v/fmt/tests/module_interface_keep.vv new file mode 100644 index 0000000..f07e5bf --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/module_interface_keep.vv @@ -0,0 +1,5 @@ +module module_fmt + +pub interface MyInterface { + fun() +} diff --git a/v_windows/v/vlib/v/fmt/tests/module_struct_keep.vv b/v_windows/v/vlib/v/fmt/tests/module_struct_keep.vv new file mode 100644 index 0000000..87c6ff8 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/module_struct_keep.vv @@ -0,0 +1,11 @@ +module module_fmt + +pub struct MyStruct { +mut: + value int + foo mod.Foo +} + +pub fn (m MyStruct) foo() bool { + return true +} diff --git a/v_windows/v/vlib/v/fmt/tests/multi_generic_test_keep.vv b/v_windows/v/vlib/v/fmt/tests/multi_generic_test_keep.vv new file mode 100644 index 0000000..f8da457 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/multi_generic_test_keep.vv @@ -0,0 +1,2 @@ +fn test() { +} diff --git a/v_windows/v/vlib/v/fmt/tests/multiline_comment_keep.vv b/v_windows/v/vlib/v/fmt/tests/multiline_comment_keep.vv new file mode 100644 index 0000000..d3d1536 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/multiline_comment_keep.vv @@ -0,0 +1,22 @@ +/* +this is a very long comment +that is on multiple lines + and has some formatting in it + that should be + preserved. +*/ +fn main() { + println('hello') + /* + this comment also + has mutliple lines + but it's difference + is that it is indented ! + */ + if true { + /* + this one is even more + indented ! + */ + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/nested_map_type_keep.vv b/v_windows/v/vlib/v/fmt/tests/nested_map_type_keep.vv new file mode 100644 index 0000000..37efebc --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/nested_map_type_keep.vv @@ -0,0 +1,5 @@ +import v.ast + +fn foo(my_map map[string]map[string]int) int { + return 0 +} diff --git a/v_windows/v/vlib/v/fmt/tests/newlines_keep.vv b/v_windows/v/vlib/v/fmt/tests/newlines_keep.vv new file mode 100644 index 0000000..7d70d56 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/newlines_keep.vv @@ -0,0 +1,32 @@ +// Module with attribute +[manualfree] +module websocket + +fn C.no_body_function() +fn C.another_one(x int) + +fn C.separated_from_my_body_and_the_above() + +fn main() {} + +// This should stay between both functions + +fn x() {} + +// doc comment above an attributed function +[inline] +fn y_with_attr() { +} + +// doc comment above an attributed struct +[typedef] +struct FooWithAttr { +} + +fn between_assembly_blocks() { + asm amd64 { + } + + asm i386 { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/no_main_expected.vv b/v_windows/v/vlib/v/fmt/tests/no_main_expected.vv new file mode 100644 index 0000000..2a082f9 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/no_main_expected.vv @@ -0,0 +1 @@ +println('hello world') diff --git a/v_windows/v/vlib/v/fmt/tests/no_main_input.vv b/v_windows/v/vlib/v/fmt/tests/no_main_input.vv new file mode 100644 index 0000000..d774314 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/no_main_input.vv @@ -0,0 +1 @@ +println( "hello world" ) diff --git a/v_windows/v/vlib/v/fmt/tests/offset_keep.vv b/v_windows/v/vlib/v/fmt/tests/offset_keep.vv new file mode 100644 index 0000000..df391b9 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/offset_keep.vv @@ -0,0 +1,8 @@ +struct Animal { + breed string + age u64 +} + +fn main() { + println(__offsetof(Animal, breed) + __offsetof(Animal, age)) +} diff --git a/v_windows/v/vlib/v/fmt/tests/operator_overload_keep.vv b/v_windows/v/vlib/v/fmt/tests/operator_overload_keep.vv new file mode 100644 index 0000000..381224a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/operator_overload_keep.vv @@ -0,0 +1,11 @@ +struct Foo { + x int +} + +fn (a Foo) + (b Foo) Foo { + return Foo{a.x + b.x} +} + +fn (a Foo) % (b Foo) Foo { + return Foo{a.x % b.x} +} diff --git a/v_windows/v/vlib/v/fmt/tests/optional_keep.vv b/v_windows/v/vlib/v/fmt/tests/optional_keep.vv new file mode 100644 index 0000000..407db36 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/optional_keep.vv @@ -0,0 +1,5 @@ +pub fn test() ?&SomeType { +} + +struct SomeType { +} diff --git a/v_windows/v/vlib/v/fmt/tests/optional_propagate_keep.vv b/v_windows/v/vlib/v/fmt/tests/optional_propagate_keep.vv new file mode 100644 index 0000000..0cd4524 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/optional_propagate_keep.vv @@ -0,0 +1,3 @@ +fn opt_propagate() ?int { + eventual_wrong_int() ? +} diff --git a/v_windows/v/vlib/v/fmt/tests/or_keep.vv b/v_windows/v/vlib/v/fmt/tests/or_keep.vv new file mode 100644 index 0000000..a1d1f65 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/or_keep.vv @@ -0,0 +1,40 @@ +fn main() { + empty_or_block() or {} + empty_or_block() or { + } +} + +fn fn_with_or() int { + fn_with_optional() or { return 10 } + return 20 +} + +fn (f Foo) method_with_or() int { + f.fn_with_optional() or { return 10 } + return 20 +} + +fn unwrapped_single_line_if() { + namefound := publisher.name_fix_check(name_to_find, state.site.id, ispage) or { + if err.contains('Could not find') { + state.error('cannot find link: $name_to_find') + } else { + state.error('cannot find link: $name_to_find\n$err') + } + println('Another stmt') + } +} + +fn or_with_one_multi_line_stmt() { + b := or_func() or { + MyStruct{ + val: 'xyz' + } + } +} + +fn channel_pop() { + var_init := <-ch or { -1.25 } + var_assign = <-ch or { -2.5 } + arr_push << <-ch or { -3.75 } +} diff --git a/v_windows/v/vlib/v/fmt/tests/orm_keep.vv b/v_windows/v/vlib/v/fmt/tests/orm_keep.vv new file mode 100644 index 0000000..26513bd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/orm_keep.vv @@ -0,0 +1,67 @@ +import sqlite +import mymodule { ModDbStruct } + +struct Customer { + id int + name string + nr_orders int + country string +} + +fn find_all_customers(db sqlite.DB) []Customer { + return sql db { + select from Customer + } +} + +fn main() { + db := sqlite.connect('customers.db') ? + // select count(*) from Customer + nr_customers := sql db { + select count from Customer + } + println('number of all customers: $nr_customers') + // V syntax can be used to build queries + // db.select returns an array + uk_customers := sql db { + select from Customer where country == 'uk' && nr_orders > 0 + } + println(uk_customers.len) + for customer in uk_customers { + println('$customer.id - $customer.name') + } + // by adding `limit 1` we tell V that there will be only one object + customer := sql db { + select from Customer where id == 1 limit 1 + } + best_customer := sql db { + select from Customer order by nr_orders desc limit 1 + } + second_best := sql db { + select from UCustomerser order by nr_orders desc limit 1 offset 1 + } + println('$customer.id - $customer.name') + // insert a new customer + new_customer := Customer{ + name: 'Bob' + nr_orders: 10 + } + sql db { + insert new_customer into Customer + } + // delete a row + sql db { + delete from Customer where nr_orders == 10 && name == 'Bob' + } + sql db { + update Customer set name = 'Queen Elizabeth II', age = 150, nr_orders = 42, country = 'Great Britain' + where id == 5 + } + // DB is a selective import + sql db { + delete from ModDbStruct where id == 1 + } + _ := sql db { + select from ModDbStruct + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/par_expr_expected.vv b/v_windows/v/vlib/v/fmt/tests/par_expr_expected.vv new file mode 100644 index 0000000..360c528 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/par_expr_expected.vv @@ -0,0 +1,3 @@ +fn main() { + _ := (cond1 && cond2) || single_ident +} diff --git a/v_windows/v/vlib/v/fmt/tests/par_expr_input.vv b/v_windows/v/vlib/v/fmt/tests/par_expr_input.vv new file mode 100644 index 0000000..d3c8653 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/par_expr_input.vv @@ -0,0 +1,3 @@ +fn main() { + _ := (cond1 && cond2) || (single_ident) +} diff --git a/v_windows/v/vlib/v/fmt/tests/pointer_casts_keep.vv b/v_windows/v/vlib/v/fmt/tests/pointer_casts_keep.vv new file mode 100644 index 0000000..57b5905 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/pointer_casts_keep.vv @@ -0,0 +1,50 @@ +struct Struct { + name string + x int +} + +fn main() { + unsafe { + pb := &byte(0) + ppb := &&byte(0) + pppb := &&&byte(0) + ppppb := &&&&byte(0) + dump(voidptr(pb)) + dump(voidptr(ppb)) + dump(voidptr(pppb)) + dump(voidptr(ppppb)) + pc := &char(0) + ppc := &&char(0) + pppc := &&&char(0) + ppppc := &&&&char(0) + dump(voidptr(pc)) + dump(voidptr(ppc)) + dump(voidptr(pppc)) + dump(voidptr(ppppc)) + ps := &Struct(0) + pps := &&Struct(0) + ppps := &&&Struct(0) + pppps := &&&&Struct(0) + dump(voidptr(ps)) + dump(voidptr(pps)) + dump(voidptr(ppps)) + dump(voidptr(pppps)) + } + ss := &Struct{ + name: 'abc' + x: 123 + } + dump(ss) + pss := voidptr(ss) + if &Struct(pss).name == 'abc' { + println('ok') + } + if &Struct(pss).x == 123 { + // &Struct cast and selecting .x + println('ok') + } + if &&Struct(pss) != 0 { + // &&Struct + println('ok') + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/proto_module_importing_vproto_keep.vv b/v_windows/v/vlib/v/fmt/tests/proto_module_importing_vproto_keep.vv new file mode 100644 index 0000000..2bca234 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/proto_module_importing_vproto_keep.vv @@ -0,0 +1,35 @@ +module proto + +import vproto + +struct Xyz { + x int +} + +struct Abcde { + f1 vproto.Xyz + f2 &vproto.Xyz + f3 []vproto.Xyz + f4 []&vproto.Xyz + f5 map[string]vproto.Xyz + f6 map[string]&vproto.Xyz + // + p1 Xyz + p2 &Xyz + p3 []Xyz + p4 []&Xyz + p5 map[string]Xyz + p6 map[string]&Xyz + p7 map[string]map[string]map[string]&Xyz + // + p8 map[string]map[string]map[string]map[string]&Xyz + p9 map[string]map[string]map[string]map[string]&vproto.Xyz +} + +fn abc() { + x := vproto.Xyz{2} + mut a := []vproto.Xyz{} + a << x + a << vproto.Xyz{3} + z := map[string]map[string]map[string]map[string]&vproto.Xyz{} +} diff --git a/v_windows/v/vlib/v/fmt/tests/ref_type_cast_keep.vv b/v_windows/v/vlib/v/fmt/tests/ref_type_cast_keep.vv new file mode 100644 index 0000000..aba9e54 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/ref_type_cast_keep.vv @@ -0,0 +1,9 @@ +module main + +fn cleanup(user_data voidptr) { + abc := App(user_data) + xyz := &App(user_data) + // + mut app := App(user_data) + mut ref := &App(user_data) +} diff --git a/v_windows/v/vlib/v/fmt/tests/select_keep.vv b/v_windows/v/vlib/v/fmt/tests/select_keep.vv new file mode 100644 index 0000000..4a5abf7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/select_keep.vv @@ -0,0 +1,128 @@ +import time +import sync + +struct St { + a int +} + +fn getint() int { + return 8 +} + +fn f1(ch1 chan int, ch2 chan St, ch3 chan int, ch4 chan int, ch5 chan int, sem sync.Semaphore) { + mut a := 5 + select { + // pre comment + a = <-ch3 { + a = 0 + } + b := <-ch2 { + a = b.a + } + ch3 <- 5 { + a = 1 + } + ch2 <- St{ + a: 37 + } { + a = 2 + } + // another comment + ch4 <- (6 + 7 * 9) { + a = 8 + } + ch5 <- getint() { + a = 9 + } + 300 * time.millisecond { + a = 3 + } + // post comment + } + assert a == 3 + sem.post() +} + +fn f2(ch1 chan St, ch2 chan int, sem sync.Semaphore) { + mut r := 23 + for i in 0 .. 2 { + select { + b := <-ch1 { + r = b.a + } + ch2 <- r { + r = 17 + } + } + if i == 0 { + assert r == 17 + } else { + assert r == 13 + } + } + sem.post() +} + +fn test_select_blocks() { + ch1 := chan int{cap: 1} + ch2 := chan St{} + ch3 := chan int{} + ch4 := chan int{} + ch5 := chan int{} + sem := sync.new_semaphore() + mut r := false + t := select { + b := <-ch1 { + println(b) + } + else { + // no channel ready + r = true + } + } + assert r == true + assert t == true + go f2(ch2, ch3, sem) + n := <-ch3 + assert n == 23 + ch2 <- St{ + a: 13 + } + sem.wait() + stopwatch := time.new_stopwatch() + go f1(ch1, ch2, ch3, ch4, ch5, sem) + sem.wait() + elapsed_ms := f64(stopwatch.elapsed()) / time.millisecond + assert elapsed_ms >= 295.0 + ch1.close() + ch2.close() + mut h := 7 + mut is_open := true + if select { + b := <-ch2 { + h = 0 + } + ch1 <- h { + h = 1 + } + else { + h = 2 + } + } { + panic('channel is still open') + } else { + is_open = false + } + // no branch should have run + for select { + b := <-ch2 { + h = 0 + } + } { + println('ch2 open') + } + println('ch2 closed') + assert h == 7 + // since all channels are closed `select` should return `false` + assert is_open == false +} diff --git a/v_windows/v/vlib/v/fmt/tests/shared_expected.vv b/v_windows/v/vlib/v/fmt/tests/shared_expected.vv new file mode 100644 index 0000000..d077f88 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/shared_expected.vv @@ -0,0 +1,85 @@ +import time + +struct St { +mut: + a int +} + +fn (shared x St) f(shared z St) { + for _ in 0 .. reads_per_thread { + rlock x { // other instances may read at the same time + time.sleep(time.millisecond) + assert x.a == 7 || x.a == 5 + } + } + lock z { + z.a-- + } +} + +fn g() shared St { + shared x := St{ + a: 12 + } + return x +} + +fn h() ?shared St { + return error('no value') +} + +fn k() { + shared x := g() + shared y := h() or { + shared f := St{} + f + } + shared z := h() ? + shared p := v + v := rlock z { + z.a + } + lock y, z; rlock x, p { + z.a = x.a + y.a + } + println(v) +} + +const ( + reads_per_thread = 30 + read_threads = 10 + writes = 5 +) + +fn test_shared_lock() { + // object with separate read/write lock + shared x := &St{ + a: 5 + } + shared z := &St{ + a: read_threads + } + for _ in 0 .. read_threads { + go x.f(shared z) + } + for i in 0 .. writes { + lock x { // wait for ongoing reads to finish, don't start new ones + x.a = 17 // this value should never be read + time.sleep(50 * time.millisecond) + x.a = if (i & 1) == 0 { 7 } else { 5 } + } // now new reads are possible again + time.sleep(20 * time.millisecond) + } + // wait until all read threads are finished + for finished := false; true; { + mut rr := 0 + rlock z { + rr = z.a + finished = z.a == 0 + } + if finished { + break + } + time.sleep(100 * time.millisecond) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/shared_input.vv b/v_windows/v/vlib/v/fmt/tests/shared_input.vv new file mode 100644 index 0000000..dccb34a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/shared_input.vv @@ -0,0 +1,84 @@ +import sync +import time + +struct St { +mut: + a int +} + +fn (shared x St) f(shared z St) { + for _ in 0 .. reads_per_thread { + rlock x { // other instances may read at the same time + time.sleep(time.millisecond) + assert x.a == 7 || x.a == 5 + } + } + lock z { + z.a-- + } +} + +fn g() shared St { +shared x := St{a: 12 } + return x +} + +fn h() ?shared St { + return error('no value') +} + +fn k() { + shared x := g() + shared y := h() or { + shared f := St{} + f + } + shared z := h() ? + shared p := v + v := rlock z { z.a } + rlock x; lock y, z; rlock p { + z.a = x.a + y.a + } + println(v) +} + +const ( + reads_per_thread = 30 + read_threads = 10 + writes = 5 +) + +fn test_shared_lock() { + // object with separate read/write lock +shared x := &St { +a: 5 +} + shared z := +&St{ + a: read_threads + } + for _ in 0.. read_threads { + go x.f(shared z) + } + for i in 0..writes { + lock x { // wait for ongoing reads to finish, don't start new ones + x.a = 17 // this value should never be read + time.sleep(50* time.millisecond) + x.a = if (i & 1) == 0 { +7} else {5} + } // now new reads are possible again +time.sleep(20*time.millisecond) + } + // wait until all read threads are finished +for finished:=false;; { + mut rr := 0 + rlock z { + rr = z.a + finished = z.a == 0 +} +if finished { + break + } + time.sleep(100*time.millisecond) + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/star__amp_int__cast_keep.vv b/v_windows/v/vlib/v/fmt/tests/star__amp_int__cast_keep.vv new file mode 100644 index 0000000..73312cd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/star__amp_int__cast_keep.vv @@ -0,0 +1,5 @@ +fn main() { + body := [1, 2, 3] + size := *&int(body.data) + eprintln('size: $size') +} diff --git a/v_windows/v/vlib/v/fmt/tests/static_mut_keep.vv b/v_windows/v/vlib/v/fmt/tests/static_mut_keep.vv new file mode 100644 index 0000000..4d4d61b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/static_mut_keep.vv @@ -0,0 +1,12 @@ +[unsafe] +fn foo() int { + mut static x := 42 + x++ + return x +} + +[unsafe] +fn foo() int { + static x := 42 // a immutable static is not very useful, but vfmt should support that too + return x +} diff --git a/v_windows/v/vlib/v/fmt/tests/stmt_keep.vv b/v_windows/v/vlib/v/fmt/tests/stmt_keep.vv new file mode 100644 index 0000000..1dcb23d --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/stmt_keep.vv @@ -0,0 +1,8 @@ +fn single_line_stmts() { + // Wouldn't be the or-block's stmt be single line, the block would be written as multi line + foo() or { assert false } + for { + foo() or { break } + } + foo() or { return } +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_interpolation_complex_keep.vv b/v_windows/v/vlib/v/fmt/tests/string_interpolation_complex_keep.vv new file mode 100644 index 0000000..470ee54 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_interpolation_complex_keep.vv @@ -0,0 +1,9 @@ +struct Container { + id string +} + +container := Container{} +docker_pubkey := '1234657890' + +cmd := "docker exec $container.id sh -c 'echo \"$docker_pubkey\" >> ~/.ssh/authorized_keys'" +println(cmd) diff --git a/v_windows/v/vlib/v/fmt/tests/string_interpolation_expected.vv b/v_windows/v/vlib/v/fmt/tests/string_interpolation_expected.vv new file mode 100644 index 0000000..4b0944b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_interpolation_expected.vv @@ -0,0 +1,32 @@ +struct Aa { + xy int +} + +struct Bb { + a Aa +} + +struct Cc { + a []Aa +} + +fn (c &Cc) f() int { + return c.a[0].xy +} + +fn (c &Cc) g(k int, l int) int { + return c.a[k].xy + l +} + +fn main() { + st := Bb{Aa{5}} + ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} + aa := Aa{-13} + z := -14.75 + println('$st.a.xy ${ar.a[2].xy} $aa.xy $z') + println('$st.a.xy${ar.a[2].xy}$aa.xy$z') + println('${st.a.xy}ya ${ar.a[2].xy}X2 ${aa.xy}.b ${z}3') + println('${z:-5} ${z:+5.3} ${z:+09.3f} ${z:-7.2} ${z:+09} ${z:08.3f}') + println('$ar.f() ${ar.g(1, 2)} ${ar.a}() ${z}(') + println('${z > 12.3 * z - 3} ${@VEXE} ${4 * 5}') +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_interpolation_input.vv b/v_windows/v/vlib/v/fmt/tests/string_interpolation_input.vv new file mode 100644 index 0000000..891e011 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_interpolation_input.vv @@ -0,0 +1,32 @@ +struct Aa { + xy int +} + +struct Bb { + a Aa +} + +struct Cc { + a []Aa +} + +fn (c &Cc) f() int { + return c.a[0].xy +} + +fn (c &Cc) g(k int, l int) int { + return c.a[k].xy+l +} + +fn main() { + st := Bb{Aa{5}} + ar := Cc{[Aa{3}, Aa{-4}, Aa{12}]} + aa := Aa{-13} + z := -14.75 + println('${st.a.xy} ${ar.a[2].xy} ${aa.xy} ${z}') + println('${st.a.xy}${ar.a[2].xy}${aa.xy}${z}') + println('${st.a.xy}ya ${ar.a[2].xy}X2 ${aa.xy}.b ${z}3') + println('${z:-5} ${z:+5.3} ${z:+09.3f} ${z:-07.2} ${z:+009} ${z:008.3f}') + println('${ar.f()} ${ar.g(1, 2)} ${ar.a}() ${z}(') + println('${z > 12.3 * z - 3} ${@VEXE} ${4 * 5}') +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_interpolation_keep.vv b/v_windows/v/vlib/v/fmt/tests/string_interpolation_keep.vv new file mode 100644 index 0000000..97a1ff5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_interpolation_keep.vv @@ -0,0 +1,17 @@ +import os + +fn main() { + println('Hello world, args: $os.args') + i := 123 + a := 'abc' + b := 'xyz' + e := 'a: $a b: $b i: $i' + d := 'a: ${a:5s} b: ${b:-5s} i: ${i:20d}' + f := 'a byte string'.bytes() + println('a: $a $b xxx') + eprintln('e: $e') + _ = ' ${foo.method(bar).str()} ' + println('(${some_struct.@type}, $some_struct.y)') + _ := 'CastExpr ${int(d.e).str()}' + println('${f[0..4].bytestr()}') +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_quotes_expected.vv b/v_windows/v/vlib/v/fmt/tests/string_quotes_expected.vv new file mode 100644 index 0000000..5f61490 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_quotes_expected.vv @@ -0,0 +1,10 @@ +fn main() { + println('Hello world !') + println('This is correct !') + println("It's okay") + println('This is "too"') + println("I'm correctly formatted") + println('"Everything on the internet is true" - Albert Einstein, 1965') + println('I\'m out of idea "_"') + println('Definitely out ":\'("') +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_quotes_input.vv b/v_windows/v/vlib/v/fmt/tests/string_quotes_input.vv new file mode 100644 index 0000000..c8cedb5 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_quotes_input.vv @@ -0,0 +1,10 @@ +fn main() { + println("Hello world !") + println('This is correct !') + println("It's okay") + println('This is "too"') + println('I\'m correctly formatted') + println("\"Everything on the internet is true\" - Albert Einstein, 1965") + println('I\'m out of idea "_"') + println("Definitely out \":'(\"") +} diff --git a/v_windows/v/vlib/v/fmt/tests/string_raw_and_cstr_keep.vv b/v_windows/v/vlib/v/fmt/tests/string_raw_and_cstr_keep.vv new file mode 100644 index 0000000..89e3010 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/string_raw_and_cstr_keep.vv @@ -0,0 +1,6 @@ +fn main() { + raw := r'\x00' + cstr := c'foo' + println(raw) + println(cstr) +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_decl_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_decl_keep.vv new file mode 100644 index 0000000..cb2de8c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_decl_keep.vv @@ -0,0 +1,5 @@ +struct Foo { + a int +__global: + g string +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_default_field_expressions_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_default_field_expressions_keep.vv new file mode 100644 index 0000000..0b8239e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_default_field_expressions_keep.vv @@ -0,0 +1,14 @@ +struct Foo { + i int = 1 // A comment +} + +struct Bar { + f Foo = &Foo(0) + z int [skip] = -1 +} + +struct Baz { + x int = 1 // It's one + y string = 'one' // It's one written out + z bool = true // Also one +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_embed_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_embed_keep.vv new file mode 100644 index 0000000..9213bf7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_embed_keep.vv @@ -0,0 +1,18 @@ +struct Foo { + x int +} + +struct Test {} + +struct Bar { + Foo // comment for Foo + Test // comment for Test + // another comment for Test + y int + z string +} + +struct Baz { + Foo // Another comment for Foo + Test +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_init_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_init_keep.vv new file mode 100644 index 0000000..d4ec06a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_init_keep.vv @@ -0,0 +1,14 @@ +struct User { + age int + name string +} + +fn main() { + u := User{ + age: 54 + } + _ = User{ + ...u + name: 'hi' + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_init_with_comments_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_init_with_comments_keep.vv new file mode 100644 index 0000000..60576b2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_init_with_comments_keep.vv @@ -0,0 +1,20 @@ +module abcde + +pub struct Builder { +pub mut: + // inline before field + buf []byte + str_calls int + len int + initial_size int = 1 +} + +pub fn new_builder(initial_size int) Builder { + return Builder{ + // buf: make(0, initial_size) + buf: []byte{cap: initial_size} + str_calls: 0 // after str_calls + len: 0 // after len + initial_size: initial_size // final + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_init_with_custom_len_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_init_with_custom_len_keep.vv new file mode 100644 index 0000000..57d916d --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_init_with_custom_len_keep.vv @@ -0,0 +1,17 @@ +struct Foo { + i int + a []int +} + +struct Bar { + f &Foo = &Foo(0) + d Foo = Foo{0} +} + +fn main() { + size := 5 + f := &Foo{ + a: []int{len: int(size)} + } + println('f.a: $f.a') +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_init_with_ref_cast_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_init_with_ref_cast_keep.vv new file mode 100644 index 0000000..006e4f7 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_init_with_ref_cast_keep.vv @@ -0,0 +1,22 @@ +struct Foo { + f int = 123 +} + +struct Bar { + f &Foo = &Foo(0) +} + +struct Zar { + f Foo +} + +fn main() { + b := &Bar{ + f: &Foo(32) + } + c := &Zar{ + f: Foo{456} + } + assert ptr_str(b.f) == '20' + assert c.f.f == 456 +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_keep.vv new file mode 100644 index 0000000..68ed58b --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_keep.vv @@ -0,0 +1,46 @@ +import os + +struct KeepAnyLanguagePrefixVariation { + x C.bar + y &C.bar + z []C.bar + z2 []&C.bar +} + +fn foo(a []os.File) { +} + +struct User { + age int + name string +} + +fn handle_users(users []User) { + println(users.len) +} + +fn (u &User) foo(u2 &User) { +} + +type Expr = IfExpr | IntegerLiteral + +fn exprs(e []Expr) { + println(e.len) +} + +struct KeepStructEmbed { + User +pub: + a int + b int +} + +struct KeepMultiLineDefaultExprsIndent { + buttons []PeriodButton = [PeriodButton{ + period: pr.Period.m1 + text: 'M1' + }, PeriodButton{ + period: pr.Period.m5 + text: 'M5' + }] +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_no_extra_attr_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_no_extra_attr_keep.vv new file mode 100644 index 0000000..8278308 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_no_extra_attr_keep.vv @@ -0,0 +1,27 @@ +[typedef] +struct Foo { +} + +[typedef] +struct Bar { + x string + y int +} + +[heap] +struct Baz { + x string + y int +} + +[inline] +struct Spam { + x string + y int +} + +[deprecated] +struct Eggs { + y_y int [json: yY] + x string [deprecated] +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_update_comment_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_update_comment_keep.vv new file mode 100644 index 0000000..1a257d8 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_update_comment_keep.vv @@ -0,0 +1,18 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f := Foo{ + name: 'test' + age: 18 + } + f2 := Foo{ + // before + ...f // after + name: 'f2' + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_update_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_update_keep.vv new file mode 100644 index 0000000..e550ac1 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_update_keep.vv @@ -0,0 +1,17 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f := Foo{ + name: 'test' + age: 18 + } + f2 := Foo{ + ...f + name: 'f2' + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/struct_with_fn_fields_keep.vv b/v_windows/v/vlib/v/fmt/tests/struct_with_fn_fields_keep.vv new file mode 100644 index 0000000..2f1bd98 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/struct_with_fn_fields_keep.vv @@ -0,0 +1,4 @@ +struct FieldsWithOptionalVoidReturnType { + f fn () ? + g fn () ? +} diff --git a/v_windows/v/vlib/v/fmt/tests/structs_expected.vv b/v_windows/v/vlib/v/fmt/tests/structs_expected.vv new file mode 100644 index 0000000..ca3fb0c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/structs_expected.vv @@ -0,0 +1,62 @@ +struct User { + name string // name + name2 []rune // name2 + very_long_field bool + age int // age + very_long_type_field1 very_looooog_type // long + very_long_type_field2 very_loooooooong_type // long +} + +struct FamousUser { + User +pub: + aka string +} + +struct Foo { + field1 int // f1 + field2 string // f2 +pub: + public_field1 int // f1 + public_field2 f64 // f2 +mut: + mut_field string +pub mut: + pub_mut_field string +} + +struct Bar { + Foo +} + +fn new_user() User { + return User{ + name: 'Serious Sam' + age: 19 + } +} + +struct SomeStruct { +mut: + // 1 + // 2 + // 3 + somefield /* 4 */ /* 5 */ int // 6 + // 7 + // 8 + /* + 9 +10 + */ + somefield2 /* 11 */ int // 12 +pub: + somefield3 int + + somefield4 int + /* + 13 +14 + */ +} + +struct C.Foo {} diff --git a/v_windows/v/vlib/v/fmt/tests/structs_input.vv b/v_windows/v/vlib/v/fmt/tests/structs_input.vv new file mode 100644 index 0000000..c17259a --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/structs_input.vv @@ -0,0 +1,65 @@ +struct User { + name string // name + name2 []rune // name2 + very_long_field bool + age int // age + very_long_type_field1 very_looooog_type // long + very_long_type_field2 very_loooooooong_type // long +} + +struct FamousUser { +pub: + User + aka string +} + +struct Foo { + field1 int // f1 + field2 string // f2 + pub: + public_field1 int // f1 + public_field2 f64 // f2 + mut: + mut_field string + pub mut: + pub_mut_field string +} + +struct Bar { Foo } + +fn new_user() +User +{ + return User{ + name: 'Serious Sam' + age: 19 + } +} + +struct SomeStruct { +// 1 +mut: +// 2 +// 3 +somefield /*4*/ /*5*/ int /*6*/ /*7*/ /*8*/ /* +9 +10 +*/ +somefield2 /*11*/ int // 12 +pub: + + + + +somefield3 int + + + +somefield4 int +/* +13 +14 +*/ +} + +struct C.Foo diff --git a/v_windows/v/vlib/v/fmt/tests/sum_smartcast_keep.vv b/v_windows/v/vlib/v/fmt/tests/sum_smartcast_keep.vv new file mode 100644 index 0000000..eacdcbd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/sum_smartcast_keep.vv @@ -0,0 +1,20 @@ +struct S1 { +mut: + i int +} + +struct S2 { +} + +type Sum = S1 | S2 + +fn f(sum Sum) { + if mut sum is S1 { + sum.i++ + } + if sum is S1 { + } + a := [sum] + if a[0] is S2 { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/thread_in_a_module_keep.vv b/v_windows/v/vlib/v/fmt/tests/thread_in_a_module_keep.vv new file mode 100644 index 0000000..41e7ea6 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/thread_in_a_module_keep.vv @@ -0,0 +1,17 @@ +module my_module + +import another +import other as ooo + +pub fn do_something() { + mut simples := []MyStruct{} + // + mut threads := []thread MyStruct{} + threads.wait() + // + mut another_threads := []thread another.MyStruct{} + another_threads.wait() + // + mut other_threads := []thread ooo.MyStruct{} + other_threads.wait() +} diff --git a/v_windows/v/vlib/v/fmt/tests/to_string_2_forms_keep.vv b/v_windows/v/vlib/v/fmt/tests/to_string_2_forms_keep.vv new file mode 100644 index 0000000..adb49bd --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/to_string_2_forms_keep.vv @@ -0,0 +1,33 @@ +fn abc() string { + unsafe { + mut fullpath := vcalloc(4) + fullpath[0] = `a` + fullpath[1] = `b` + fullpath[2] = `c` + fullpath[3] = 0 + return fullpath.vstring() + } + return '' +} + +fn def() string { + unsafe { + mut fullpath := vcalloc(4) + fullpath[0] = `a` + fullpath[1] = `b` + fullpath[2] = `c` + fullpath[3] = 0 + return fullpath.vstring_with_len(3) + } + return '' +} + +fn main() { + assert 'abc' == abc() + assert 'abc' == def() + abc_str1 := ptr_str(abc().str) + abc_str2 := ptr_str(abc().str) + println('abc_str1: $abc_str1') + println('abc_str2: $abc_str2') + assert abc_str1 != abc_str2 +} diff --git a/v_windows/v/vlib/v/fmt/tests/trailing_space_expected.vv b/v_windows/v/vlib/v/fmt/tests/trailing_space_expected.vv new file mode 100644 index 0000000..690540c --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/trailing_space_expected.vv @@ -0,0 +1,10 @@ +// NB: The input file has and *should* have trailing spaces. +// When making changes, please ensure these spaces are not removed. + +fn comments_with_trailing_space() { + // two spaces on the right + /* + spaces after here + and everywhere :) + */ +} diff --git a/v_windows/v/vlib/v/fmt/tests/trailing_space_input.vv b/v_windows/v/vlib/v/fmt/tests/trailing_space_input.vv new file mode 100644 index 0000000..2b5b448 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/trailing_space_input.vv @@ -0,0 +1,10 @@ +// NB: The input file has and *should* have trailing spaces. +// When making changes, please ensure these spaces are not removed. + +fn comments_with_trailing_space() { + // two spaces on the right + /* + spaces after here + and everywhere :) + */ +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/fmt/tests/type_ptr_keep.vv b/v_windows/v/vlib/v/fmt/tests/type_ptr_keep.vv new file mode 100644 index 0000000..338b54e --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/type_ptr_keep.vv @@ -0,0 +1,16 @@ +const ( + x = &Test{} + y = []&Test{} + xx = &&Test{} + yy = []&&Test{} +) + +fn test_type_ptr() { + _ := &Test{} + _ := []&Test{} + _ := &&Test{} + _ := []&&Test{} +} + +struct Test { +} diff --git a/v_windows/v/vlib/v/fmt/tests/typeof_keep.vv b/v_windows/v/vlib/v/fmt/tests/typeof_keep.vv new file mode 100644 index 0000000..93d6cdc --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/typeof_keep.vv @@ -0,0 +1,3 @@ +fn test_typeof() { + println(typeof(x).name) +} diff --git a/v_windows/v/vlib/v/fmt/tests/types_expected.vv b/v_windows/v/vlib/v/fmt/tests/types_expected.vv new file mode 100644 index 0000000..1eed436 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/types_expected.vv @@ -0,0 +1,27 @@ +// Sumtype +type FooBar = Bar | Foo +pub type PublicBar = Bar | Foo | FooBar + +type Uint = byte | u16 | u32 | u64 // This should stay on the same line +type Float = f32 | f64 + +// Alias type +type MyInt = int + +pub type Abc = f32 + +// Fn type decl + +type EmptyFn = fn () + +type OneArgFn = fn (i int) + +type TwoDiffArgs = fn (i int, s string) bool + +type TwoSameArgs = fn (i int, j int) string // And a comment + +type VarArgs = fn (s ...string) int + +type NOVarArgs = fn (i int, s ...string) f64 + +type NoNameArgs = fn (int, string, ...string) diff --git a/v_windows/v/vlib/v/fmt/tests/types_input.vv b/v_windows/v/vlib/v/fmt/tests/types_input.vv new file mode 100644 index 0000000..6a5f947 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/types_input.vv @@ -0,0 +1,37 @@ + + + // Sumtype + type FooBar= Foo | Bar + pub type PublicBar = Foo | Bar | FooBar + +type Uint = u16 | u64 + | u32 + | byte // This should stay on the same line +type +Float = + f32 | + f64 + + // Alias type + type MyInt = int + + pub type Abc = f32 + + +// Fn type decl + + type EmptyFn = fn() +type OneArgFn = + fn (i int) +type TwoDiffArgs += fn (i int, s string) bool + + + type TwoSameArgs = fn(i int, j int) string // And a comment + +type VarArgs = fn +(s ...string) int + +type NOVarArgs = fn(i int, s ...string) f64 + +type NoNameArgs = fn( int, string , ...string) diff --git a/v_windows/v/vlib/v/fmt/tests/union_keep.vv b/v_windows/v/vlib/v/fmt/tests/union_keep.vv new file mode 100644 index 0000000..5872d03 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/union_keep.vv @@ -0,0 +1,4 @@ +union Un { + i int + b byte +} diff --git a/v_windows/v/vlib/v/fmt/tests/unsafe_keep.vv b/v_windows/v/vlib/v/fmt/tests/unsafe_keep.vv new file mode 100644 index 0000000..d1d6702 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/unsafe_keep.vv @@ -0,0 +1,17 @@ +fn main() { + unsafe { + println('hi') + println('hi2') + } + unsafe { + println('qwer') + } + unsafe { 6 } + x := unsafe { + 5 + } + y := unsafe { 7 } + unsafe {} + unsafe { + } +} diff --git a/v_windows/v/vlib/v/fmt/tests/vargs_reference_param_keep.vv b/v_windows/v/vlib/v/fmt/tests/vargs_reference_param_keep.vv new file mode 100644 index 0000000..cef84b3 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/vargs_reference_param_keep.vv @@ -0,0 +1,27 @@ +[heap] +struct Foo { + name string +} + +fn agg_stuff(stuffs ...&Foo) []&Foo { + stuffs2 := stuffs.clone() + return stuffs2 +} + +fn arr_stuff(stuffs []&Foo) []&Foo { + stuffs2 := stuffs.clone() + return stuffs2 +} + +fn main() { + foo1 := &Foo{'foo'} + foo2 := &Foo{'bar'} + + foo11 := agg_stuff(foo1, foo2) + println(foo11) + + foo22 := arr_stuff([foo1, foo2]) + println(foo22) + + assert '$foo11' == '$foo22' +} diff --git a/v_windows/v/vlib/v/fmt/tests/void_optional_keep.vv b/v_windows/v/vlib/v/fmt/tests/void_optional_keep.vv new file mode 100644 index 0000000..97ed7db --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/void_optional_keep.vv @@ -0,0 +1,7 @@ +fn tt() ? { + return error('error') +} + +fn main() { + tt() or { panic('$err') } +} diff --git a/v_windows/v/vlib/v/fmt/tests/vscript_keep.vv b/v_windows/v/vlib/v/fmt/tests/vscript_keep.vv new file mode 100644 index 0000000..3f4dfe2 --- /dev/null +++ b/v_windows/v/vlib/v/fmt/tests/vscript_keep.vv @@ -0,0 +1,9 @@ +#!/usr/local/bin/v run + +x := 5 +println(x) // comment after +println('b') +// comment between +println('c') +mut numbers := [1, 3, 2] +numbers.sort() // 1, 2, 3 diff --git a/v_windows/v/vlib/v/gen/c/array.v b/v_windows/v/vlib/v/gen/c/array.v new file mode 100644 index 0000000..74dc9aa --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/array.v @@ -0,0 +1,723 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module c + +import strings +import v.ast + +fn (mut g Gen) array_init(node ast.ArrayInit) { + array_type := g.unwrap(node.typ) + mut array_styp := '' + elem_type := g.unwrap(node.elem_type) + mut shared_styp := '' // only needed for shared &[]{...} + is_amp := g.is_amp + g.is_amp = false + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared { + shared_styp = g.typ(array_type.typ.set_flag(.shared_f)) + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if is_amp { + array_styp = g.typ(array_type.typ) + g.write('HEAP($array_styp, ') + } + if array_type.unaliased_sym.kind == .array_fixed { + g.write('{') + if node.has_val { + for i, expr in node.exprs { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + if i != node.exprs.len - 1 { + g.write(', ') + } + } + } else if node.has_default { + g.expr(node.default_expr) + info := array_type.unaliased_sym.info as ast.ArrayFixed + for _ in 1 .. info.size { + g.write(', ') + g.expr(node.default_expr) + } + } else { + g.write('0') + } + g.write('}') + return + } + elem_styp := g.typ(elem_type.typ) + noscan := g.check_noscan(elem_type.typ) + if node.exprs.len == 0 { + is_default_array := elem_type.unaliased_sym.kind == .array && node.has_default + if is_default_array { + g.write('__new_array_with_array_default${noscan}(') + } else { + g.write('__new_array_with_default${noscan}(') + } + if node.has_len { + g.expr(node.len_expr) + g.write(', ') + } else { + g.write('0, ') + } + if node.has_cap { + g.expr(node.cap_expr) + g.write(', ') + } else { + g.write('0, ') + } + if elem_type.unaliased_sym.kind == .function { + g.write('sizeof(voidptr), ') + } else { + g.write('sizeof($elem_styp), ') + } + if is_default_array { + g.write('($elem_styp[]){') + g.expr(node.default_expr) + g.write('}[0])') + } else if node.has_default { + g.write('&($elem_styp[]){') + g.expr(node.default_expr) + g.write('})') + } else if node.has_len && node.elem_type == ast.string_type { + g.write('&($elem_styp[]){') + g.write('_SLIT("")') + g.write('})') + } else if node.has_len && elem_type.unaliased_sym.kind in [.array, .map] { + g.write('(voidptr)&($elem_styp[]){') + g.write(g.type_default(node.elem_type)) + g.write('}[0])') + } else { + g.write('0)') + } + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write(')') + } + return + } + len := node.exprs.len + if elem_type.unaliased_sym.kind == .function { + g.write('new_array_from_c_array($len, $len, sizeof(voidptr), _MOV((voidptr[$len]){') + } else { + g.write('new_array_from_c_array${noscan}($len, $len, sizeof($elem_styp), _MOV(($elem_styp[$len]){') + } + if len > 8 { + g.writeln('') + g.write('\t\t') + } + for i, expr in node.exprs { + if node.expr_types[i] == ast.string_type && expr !is ast.StringLiteral + && expr !is ast.StringInterLiteral { + g.write('string_clone(') + g.expr(expr) + g.write(')') + } else { + g.expr_with_cast(expr, node.expr_types[i], node.elem_type) + } + if i != len - 1 { + if i > 0 && i & 7 == 0 { // i > 0 && i % 8 == 0 + g.writeln(',') + g.write('\t\t') + } else { + g.write(', ') + } + } + } + g.write('}))') + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write('), sizeof($array_styp))') + } +} + +// `nums.map(it % 2 == 0)` +fn (mut g Gen) gen_array_map(node ast.CallExpr) { + g.inside_lambda = true + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + // println('filter s="$s"') + ret_typ := g.typ(node.return_type) + // inp_typ := g.typ(node.receiver_type) + ret_sym := g.table.get_type_symbol(node.return_type) + inp_sym := g.table.get_type_symbol(node.receiver_type) + ret_info := ret_sym.info as ast.Array + ret_elem_type := g.typ(ret_info.elem_type) + inp_info := inp_sym.info as ast.Array + inp_elem_type := g.typ(inp_info.elem_type) + if inp_sym.kind != .array { + verror('map() requires an array') + } + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + noscan := g.check_noscan(ret_info.elem_type) + g.writeln('$ret_typ $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($ret_elem_type));\n') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$inp_elem_type it = (($inp_elem_type*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\t$ret_elem_type ti = ') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\t$ret_elem_type ti = ') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym := g.table.get_type_symbol(var_info.typ) + if sym.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\t$ret_elem_type ti = ') + g.expr(node.args[0].expr) + } + else { + g.write('\t$ret_elem_type ti = ') + g.expr(node.args[0].expr) + } + } + g.writeln(';') + g.writeln('\tarray_push${noscan}((array*)&$tmp, &ti);') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) + g.inside_lambda = false +} + +// `users.sort(a.age < b.age)` +fn (mut g Gen) gen_array_sort(node ast.CallExpr) { + // println('filter s="$s"') + rec_sym := g.table.get_type_symbol(node.receiver_type) + if rec_sym.kind != .array { + println(node.name) + println(g.typ(node.receiver_type)) + // println(rec_sym.kind) + verror('.sort() is an array method') + } + if g.pref.is_bare { + g.writeln('bare_panic(_SLIT("sort does not work with -freestanding"))') + return + } + info := rec_sym.info as ast.Array + // `users.sort(a.age > b.age)` + // Generate a comparison function for a custom type + elem_stype := g.typ(info.elem_type) + mut compare_fn := 'compare_${elem_stype.replace('*', '_ptr')}' + mut comparison_type := g.unwrap(ast.void_type) + mut left_expr, mut right_expr := '', '' + // the only argument can only be an infix expression like `a < b` or `b.field > a.field` + if node.args.len == 0 { + comparison_type = g.unwrap(info.elem_type.set_nr_muls(0)) + if compare_fn in g.array_sort_fn { + g.gen_array_sort_call(node, compare_fn) + return + } + left_expr = '*a' + right_expr = '*b' + } else { + infix_expr := node.args[0].expr as ast.InfixExpr + comparison_type = g.unwrap(infix_expr.left_type.set_nr_muls(0)) + left_name := infix_expr.left.str() + if left_name.len > 1 { + compare_fn += '_by' + left_name[1..].replace_each(['.', '_', '[', '_', ']', '_']) + } + // is_reverse is `true` for `.sort(a > b)` and `.sort(b < a)` + is_reverse := (left_name.starts_with('a') && infix_expr.op == .gt) + || (left_name.starts_with('b') && infix_expr.op == .lt) + if is_reverse { + compare_fn += '_reverse' + } + if compare_fn in g.array_sort_fn { + g.gen_array_sort_call(node, compare_fn) + return + } + if left_name.starts_with('a') != is_reverse { + left_expr = g.expr_string(infix_expr.left) + right_expr = g.expr_string(infix_expr.right) + if infix_expr.left is ast.Ident { + left_expr = '*' + left_expr + } + if infix_expr.right is ast.Ident { + right_expr = '*' + right_expr + } + } else { + left_expr = g.expr_string(infix_expr.right) + right_expr = g.expr_string(infix_expr.left) + if infix_expr.left is ast.Ident { + right_expr = '*' + right_expr + } + if infix_expr.right is ast.Ident { + left_expr = '*' + left_expr + } + } + } + + // Register a new custom `compare_xxx` function for qsort() + // TODO: move to checker + g.table.register_fn(name: compare_fn, return_type: ast.int_type) + g.array_sort_fn[compare_fn] = true + + stype_arg := g.typ(info.elem_type) + g.definitions.writeln('int ${compare_fn}($stype_arg* a, $stype_arg* b) {') + c_condition := if comparison_type.sym.has_method('<') { + '${g.typ(comparison_type.typ)}__lt($left_expr, $right_expr)' + } else if comparison_type.unaliased_sym.has_method('<') { + '${g.typ(comparison_type.unaliased)}__lt($left_expr, $right_expr)' + } else { + '$left_expr < $right_expr' + } + g.definitions.writeln('\tif ($c_condition) return -1;') + g.definitions.writeln('\telse return 1;') + g.definitions.writeln('}\n') + + // write call to the generated function + g.gen_array_sort_call(node, compare_fn) +} + +fn (mut g Gen) gen_array_sort_call(node ast.CallExpr, compare_fn string) { + deref_field := if node.left_type.is_ptr() || node.left_type.is_pointer() { '->' } else { '.' } + // eprintln('> qsort: pointer $node.left_type | deref: `$deref`') + g.empty_line = true + g.write('qsort(') + g.expr(node.left) + g.write('${deref_field}data, ') + g.expr(node.left) + g.write('${deref_field}len, ') + g.expr(node.left) + g.write('${deref_field}element_size, (int (*)(const void *, const void *))&$compare_fn)') +} + +// `nums.filter(it % 2 == 0)` +fn (mut g Gen) gen_array_filter(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + // println('filter s="$s"') + sym := g.table.get_type_symbol(node.return_type) + if sym.kind != .array { + verror('filter() requires an array') + } + info := sym.info as ast.Array + styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + noscan := g.check_noscan(info.elem_type) + g.writeln('$styp $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($elem_type_str));\n') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (') + g.expr(node.args[0].expr) + } + } + g.writeln(') {') + g.writeln('\t\tarray_push${noscan}((array*)&$tmp, &it); \n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} + +// `nums.insert(0, 2)` `nums.insert(0, [2,3,4])` +fn (mut g Gen) gen_array_insert(node ast.CallExpr) { + left_sym := g.table.get_type_symbol(node.left_type) + left_info := left_sym.info as ast.Array + elem_type_str := g.typ(left_info.elem_type) + arg2_sym := g.table.get_type_symbol(node.args[1].typ) + is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) + if is_arg2_array { + g.write('array_insert_many${noscan}(&') + } else { + g.write('array_insert${noscan}(&') + } + g.expr(node.left) + g.write(', ') + g.expr(node.args[0].expr) + if is_arg2_array { + g.write(', ') + g.expr(node.args[1].expr) + g.write('.data, ') + g.expr(node.args[1].expr) + g.write('.len)') + } else { + g.write(', &($elem_type_str[]){') + if left_info.elem_type == ast.string_type { + g.write('string_clone(') + } + g.expr_with_cast(node.args[1].expr, node.args[1].typ, left_info.elem_type) + if left_info.elem_type == ast.string_type { + g.write(')') + } + g.write('})') + } +} + +// `nums.prepend(2)` `nums.prepend([2,3,4])` +fn (mut g Gen) gen_array_prepend(node ast.CallExpr) { + left_sym := g.table.get_type_symbol(node.left_type) + left_info := left_sym.info as ast.Array + elem_type_str := g.typ(left_info.elem_type) + arg_sym := g.table.get_type_symbol(node.args[0].typ) + is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) + if is_arg_array { + g.write('array_prepend_many${noscan}(&') + } else { + g.write('array_prepend${noscan}(&') + } + g.expr(node.left) + if is_arg_array { + g.write(', ') + g.expr(node.args[0].expr) + g.write('.data, ') + g.expr(node.args[0].expr) + g.write('.len)') + } else { + g.write(', &($elem_type_str[]){') + g.expr_with_cast(node.args[0].expr, node.args[0].typ, left_info.elem_type) + g.write('})') + } +} + +fn (mut g Gen) gen_array_contains_method(left_type ast.Type) string { + mut unwrap_left_type := g.unwrap_generic(left_type) + if unwrap_left_type.share() == .shared_t { + unwrap_left_type = unwrap_left_type.clear_flag(.shared_f) + } + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + left_final_sym := g.table.get_final_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).replace('*', '') + fn_name := '${left_type_str}_contains' + if !left_sym.has_method('contains') { + left_info := left_final_sym.info as ast.Array + mut elem_type_str := g.typ(left_info.elem_type) + elem_sym := g.table.get_type_symbol(left_info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + elem_type_str = 'voidptr' + } + g.type_definitions.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v) {') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (fast_string_eq(((string*)a.data)[i], v)) {') + } else if elem_sym.kind == .array && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_array_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq((($elem_type_str*)a.data)[i], v)) {') + } else if elem_sym.kind == .function { + fn_builder.writeln('\t\tif (((voidptr*)a.data)[i] == v) {') + } else if elem_sym.kind == .map && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_map_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_map_eq((($elem_type_str*)a.data)[i], v)) {') + } else if elem_sym.kind == .struct_ && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq((($elem_type_str*)a.data)[i], v)) {') + } else { + fn_builder.writeln('\t\tif ((($elem_type_str*)a.data)[i] == v) {') + } + fn_builder.writeln('\t\t\treturn true;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn false;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + left_sym.register_method(&ast.Fn{ + name: 'contains' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: left_info.elem_type + }] + }) + } + return fn_name +} + +// `nums.contains(2)` +fn (mut g Gen) gen_array_contains(node ast.CallExpr) { + fn_name := g.gen_array_contains_method(node.left_type) + g.write('${fn_name}(') + if node.left_type.is_ptr() && node.left_type.share() != .shared_t { + g.write('*') + } + g.expr(node.left) + if node.left_type.share() == .shared_t { + g.write('->val') + } + g.write(', ') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g Gen) gen_array_index_method(left_type ast.Type) string { + unwrap_left_type := g.unwrap_generic(left_type) + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).trim('*') + fn_name := '${left_type_str}_index' + if !left_sym.has_method('index') { + info := left_sym.info as ast.Array + mut elem_type_str := g.typ(info.elem_type) + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + elem_type_str = 'voidptr' + } + g.type_definitions.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v) {') + fn_builder.writeln('\t$elem_type_str* pelem = a.data;') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i, ++pelem) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (fast_string_eq(*pelem, v)) {') + } else if elem_sym.kind == .array && !info.elem_type.is_ptr() { + ptr_typ := g.gen_array_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq( *pelem, v)) {') + } else if elem_sym.kind == .function && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif ( pelem == v) {') + } else if elem_sym.kind == .map && !info.elem_type.is_ptr() { + ptr_typ := g.gen_map_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(( *pelem, v))) {') + } else if elem_sym.kind == .struct_ && !info.elem_type.is_ptr() { + ptr_typ := g.gen_struct_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq( *pelem, v)) {') + } else { + fn_builder.writeln('\t\tif (*pelem == v) {') + } + fn_builder.writeln('\t\t\treturn i;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn -1;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + left_sym.register_method(&ast.Fn{ + name: 'index' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: info.elem_type + }] + }) + } + return fn_name +} + +// `nums.index(2)` +fn (mut g Gen) gen_array_index(node ast.CallExpr) { + fn_name := g.gen_array_index_method(node.left_type) + g.write('${fn_name}(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g Gen) gen_array_wait(node ast.CallExpr) { + arr := g.table.get_type_symbol(node.receiver_type) + thread_type := arr.array_info().elem_type + thread_sym := g.table.get_type_symbol(thread_type) + thread_ret_type := thread_sym.thread_info().return_type + eltyp := g.table.get_type_symbol(thread_ret_type).cname + fn_name := g.register_thread_array_wait_call(eltyp) + g.write('${fn_name}(') + g.expr(node.left) + g.write(')') +} + +fn (mut g Gen) gen_array_any(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + sym := g.table.get_type_symbol(node.left_type) + info := sym.info as ast.Array + // styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + g.writeln('bool $tmp = false;') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (') + g.expr(node.args[0].expr) + } + } + g.writeln(') {') + g.writeln('\t\t$tmp = true;\n\t\t\tbreak;\n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} + +fn (mut g Gen) gen_array_all(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + sym := g.table.get_type_symbol(node.left_type) + info := sym.info as ast.Array + // styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + g.writeln('bool $tmp = true;') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (!(') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (!(') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (!(') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (!(') + g.expr(node.args[0].expr) + } + } + g.writeln(')) {') + g.writeln('\t\t$tmp = false;\n\t\t\tbreak;\n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} diff --git a/v_windows/v/vlib/v/gen/c/assert.v b/v_windows/v/vlib/v/gen/c/assert.v new file mode 100644 index 0000000..46b48f7 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/assert.v @@ -0,0 +1,159 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast + +fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) { + if !original_assert_statement.is_used { + return + } + mut node := original_assert_statement + g.writeln('// assert') + if mut node.expr is ast.InfixExpr { + if mut node.expr.left is ast.CallExpr { + node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type) + } else if mut node.expr.left is ast.ParExpr { + if node.expr.left.expr is ast.CallExpr { + node.expr.left = g.new_ctemp_var_then_gen(node.expr.left.expr, node.expr.left_type) + } + } + if mut node.expr.right is ast.CallExpr { + node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type) + } else if mut node.expr.right is ast.ParExpr { + if node.expr.right.expr is ast.CallExpr { + node.expr.right = g.new_ctemp_var_then_gen(node.expr.right.expr, node.expr.right_type) + } + } + } + g.inside_ternary++ + if g.is_test { + g.write('if (') + g.expr(node.expr) + g.write(')') + g.decrement_inside_ternary() + g.writeln(' {') + g.writeln('\tg_test_oks++;') + metaname_ok := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);') + g.writeln('} else {') + g.writeln('\tg_test_fails++;') + metaname_fail := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);') + g.gen_assert_postfailure_mode(node) + g.writeln('\tlongjmp(g_jump_buffer, 1);') + g.writeln('\t// TODO') + g.writeln('\t// Maybe print all vars in a test function if it fails?') + g.writeln('}') + } else { + g.write('if (!(') + g.expr(node.expr) + g.write('))') + g.decrement_inside_ternary() + g.writeln(' {') + metaname_panic := g.gen_assert_metainfo(node) + g.writeln('\t__print_assert_failure(&$metaname_panic);') + g.gen_assert_postfailure_mode(node) + g.writeln('\t_v_panic(_SLIT("Assertion failed..."));') + g.writeln('}') + } +} + +fn (mut g Gen) gen_assert_postfailure_mode(node ast.AssertStmt) { + g.write_v_source_line_info(node.pos) + match g.pref.assert_failure_mode { + .default {} + .aborts { + g.writeln('\tabort();') + } + .backtraces { + g.writeln('\tprint_backtrace();') + } + } +} + +fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string { + mod_path := cestring(g.file.path) + fn_name := g.fn_decl.name + line_nr := node.pos.line_nr + src := cestring(node.expr.str()) + metaname := 'v_assert_meta_info_$g.new_tmp_var()' + g.writeln('\tVAssertMetaInfo $metaname = {0};') + g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};') + g.writeln('\t${metaname}.line_nr = $line_nr;') + g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};') + metasrc := cnewlines(ctoslit(src)) + g.writeln('\t${metaname}.src = $metasrc;') + match mut node.expr { + ast.InfixExpr { + expr_op_str := ctoslit(node.expr.op.str()) + expr_left_str := cnewlines(ctoslit(node.expr.left.str())) + expr_right_str := cnewlines(ctoslit(node.expr.right.str())) + g.writeln('\t${metaname}.op = $expr_op_str;') + g.writeln('\t${metaname}.llabel = $expr_left_str;') + g.writeln('\t${metaname}.rlabel = $expr_right_str;') + g.write('\t${metaname}.lvalue = ') + g.gen_assert_single_expr(node.expr.left, node.expr.left_type) + g.writeln(';') + g.write('\t${metaname}.rvalue = ') + g.gen_assert_single_expr(node.expr.right, node.expr.right_type) + g.writeln(';') + } + ast.CallExpr { + g.writeln('\t${metaname}.op = _SLIT("call");') + } + else {} + } + return metaname +} + +fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { + // eprintln('> gen_assert_single_expr typ: $typ | expr: $expr | typeof(expr): ${typeof(expr)}') + unknown_value := '*unknown value*' + match expr { + ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr { + g.write(ctoslit(unknown_value)) + } + ast.PrefixExpr { + if expr.right is ast.CastExpr { + // TODO: remove this check; + // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails + // without special casing ast.CastExpr here + g.write(ctoslit(unknown_value)) + } else { + g.gen_expr_to_string(expr, typ) + } + } + ast.TypeNode { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + g.write(ctoslit('$sym.name')) + } + else { + mut should_clone := true + if typ == ast.string_type && expr is ast.StringLiteral { + should_clone = false + } + if expr is ast.CTempVar { + if expr.orig is ast.CallExpr { + should_clone = false + if expr.orig.or_block.kind == .propagate { + should_clone = true + } + if expr.orig.is_method && expr.orig.args.len == 0 + && expr.orig.name == 'type_name' { + should_clone = true + } + } + } + if should_clone { + g.write('string_clone(') + } + g.gen_expr_to_string(expr, typ) + if should_clone { + g.write(')') + } + } + } + g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') +} diff --git a/v_windows/v/vlib/v/gen/c/auto_eq_methods.v b/v_windows/v/vlib/v/gen/c/auto_eq_methods.v new file mode 100644 index 0000000..16dd339 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/auto_eq_methods.v @@ -0,0 +1,343 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module c + +import strings +import v.ast + +fn (mut g Gen) gen_sumtype_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.sumtype_fn_definitions { + return ptr_styp + } + g.sumtype_fn_definitions << ptr_styp + info := left.sym.sumtype_info() + g.type_definitions.writeln('static bool ${ptr_styp}_sumtype_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_sumtype_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a._typ != b._typ) { return false; }') + for typ in info.variants { + variant := g.unwrap(typ) + fn_builder.writeln('\tif (a._typ == $variant.typ) {') + name := '_$variant.sym.cname' + if variant.sym.kind == .string { + fn_builder.writeln('\t\treturn string__eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .sum_type && !typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_sumtype_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .struct_ && !typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_struct_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .array && !typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .array_fixed && !typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .map && !typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_map_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .alias && !typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_alias_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .function { + fn_builder.writeln('\t\treturn *((voidptr*)(*a.$name)) == *((voidptr*)(*b.$name));') + } else { + fn_builder.writeln('\t\treturn *a.$name == *b.$name;') + } + fn_builder.writeln('\t}') + } + fn_builder.writeln('\treturn false;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_struct_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.struct_fn_definitions { + return ptr_styp + } + g.struct_fn_definitions << ptr_styp + info := left.sym.struct_info() + g.type_definitions.writeln('static bool ${ptr_styp}_struct_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + defer { + g.auto_fn_definitions << fn_builder.str() + } + fn_builder.writeln('static bool ${ptr_styp}_struct_eq($ptr_styp a, $ptr_styp b) {') + + // overloaded + if left.sym.has_method('==') { + fn_builder.writeln('\treturn ${ptr_styp}__eq(a, b);') + fn_builder.writeln('}') + return ptr_styp + } + + fn_builder.write_string('\treturn ') + if info.fields.len > 0 { + for i, field in info.fields { + if i > 0 { + fn_builder.write_string('\n\t\t&& ') + } + field_type := g.unwrap(field.typ) + if field_type.sym.kind == .string { + fn_builder.write_string('string__eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .sum_type && !field.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_sumtype_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .struct_ && !field.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_struct_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .array && !field.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .array_fixed && !field.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .map && !field.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_map_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .alias && !field.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_alias_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .function { + fn_builder.write_string('*((voidptr*)(a.$field.name)) == *((voidptr*)(b.$field.name))') + } else { + fn_builder.write_string('a.$field.name == b.$field.name') + } + } + } else { + fn_builder.write_string('true') + } + fn_builder.writeln(';') + fn_builder.writeln('}') + return ptr_styp +} + +fn (mut g Gen) gen_alias_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.alias_fn_definitions { + return ptr_styp + } + g.alias_fn_definitions << ptr_styp + info := left.sym.info as ast.Alias + g.type_definitions.writeln('static bool ${ptr_styp}_alias_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_alias_eq($ptr_styp a, $ptr_styp b) {') + sym := g.table.get_type_symbol(info.parent_type) + if sym.kind == .string { + fn_builder.writeln('\treturn string__eq(a, b);') + } else if sym.kind == .sum_type && !left.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_sumtype_eq(a, b);') + } else if sym.kind == .struct_ && !left.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_struct_eq(a, b);') + } else if sym.kind == .array && !left.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .array_fixed && !left.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .map && !left.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_map_eq(a, b);') + } else if sym.kind == .function { + fn_builder.writeln('\treturn *((voidptr*)(a)) == *((voidptr*)(b));') + } else { + fn_builder.writeln('\treturn a == b;') + } + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem := g.unwrap(left.sym.array_info().elem_type) + ptr_elem_styp := g.typ(elem.typ) + g.type_definitions.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a.len != b.len) {') + fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') + // compare every pair of elements of the two arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (!string__eq(*(($ptr_elem_styp*)((byte*)a.data+(i*a.element_size))), *(($ptr_elem_styp*)((byte*)b.data+(i*b.element_size))))) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (*((voidptr*)((byte*)a.data+(i*a.element_size))) != *((voidptr*)((byte*)b.data+(i*b.element_size)))) {') + } else { + fn_builder.writeln('\t\tif (*(($ptr_elem_styp*)((byte*)a.data+(i*a.element_size))) != *(($ptr_elem_styp*)((byte*)b.data+(i*b.element_size)))) {') + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_fixed_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem_info := left.sym.array_fixed_info() + elem := g.unwrap(elem_info.elem_type) + size := elem_info.size + g.type_definitions.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tfor (int i = 0; i < $size; ++i) {') + // compare every pair of elements of the two fixed arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (!string__eq(a[i], b[i])) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(a[i], b[i])) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(a[i], b[i])) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a[i], b[i])) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a[i], b[i])) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(a[i], b[i])) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(a[i], b[i])) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (a[i] != b[i]) {') + } else { + fn_builder.writeln('\t\tif (a[i] != b[i]) {') + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_map_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.map_fn_definitions { + return ptr_styp + } + g.map_fn_definitions << ptr_styp + value := g.unwrap(left.sym.map_info().value_type) + ptr_value_styp := g.typ(value.typ) + g.type_definitions.writeln('static bool ${ptr_styp}_map_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_map_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a.len != b.len) {') + fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') + fn_builder.writeln('\t\tif (!DenseArray_has_index(&a.key_values, i)) continue;') + fn_builder.writeln('\t\tvoidptr k = DenseArray_key(&a.key_values, i);') + fn_builder.writeln('\t\tif (!map_exists(&b, k)) return false;') + kind := g.table.type_kind(value.typ) + if kind == .function { + func := value.sym.info as ast.FnType + ret_styp := g.typ(func.func.return_type) + fn_builder.write_string('\t\t$ret_styp (*v) (') + arg_len := func.func.params.len + for j, arg in func.func.params { + arg_styp := g.typ(arg.typ) + fn_builder.write_string('$arg_styp $arg.name') + if j < arg_len - 1 { + fn_builder.write_string(', ') + } + } + fn_builder.writeln(') = *(voidptr*)map_get(&a, k, &(voidptr[]){ 0 });') + } else { + fn_builder.writeln('\t\t$ptr_value_styp v = *($ptr_value_styp*)map_get(&a, k, &($ptr_value_styp[]){ 0 });') + } + match kind { + .string { + fn_builder.writeln('\t\tif (!fast_string_eq(*(string*)map_get(&b, k, &(string[]){_SLIT("")}), v)) {') + } + .sum_type { + eq_fn := g.gen_sumtype_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .struct_ { + eq_fn := g.gen_struct_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .array { + eq_fn := g.gen_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .array_fixed { + eq_fn := g.gen_fixed_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .map { + eq_fn := g.gen_map_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .alias { + eq_fn := g.gen_alias_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .function { + fn_builder.writeln('\t\tif (*(voidptr*)map_get(&b, k, &(voidptr[]){ 0 }) != v) {') + } + else { + fn_builder.writeln('\t\tif (*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }) != v) {') + } + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} diff --git a/v_windows/v/vlib/v/gen/c/auto_str_methods.v b/v_windows/v/vlib/v/gen/c/auto_str_methods.v new file mode 100644 index 0000000..ffec50c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/auto_str_methods.v @@ -0,0 +1,900 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module c + +import v.ast +import v.util +import strings + +pub enum StrIntpType { + si_no_str = 0 // no parameter to print only fix string + si_c + si_u8 + si_i8 + si_u16 + si_i16 + si_u32 + si_i32 + si_u64 + si_i64 + si_e32 + si_e64 + si_f32 + si_f64 + si_g32 + si_g64 + si_s + si_p + si_vp +} + +pub fn type_to_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'c' } + .si_u8 { return 'u8' } + .si_i8 { return 'i8' } + .si_u16 { return 'u16' } + .si_i16 { return 'i16' } + .si_u32 { return 'u32' } + .si_i32 { return 'i32' } + .si_u64 { return 'u64' } + .si_i64 { return 'i64' } + .si_f32 { return 'f32' } + .si_f64 { return 'f64' } + .si_g32 { return 'f32' } // g32 format use f32 data + .si_g64 { return 'f64' } // g64 format use f64 data + .si_e32 { return 'f32' } // e32 format use f32 data + .si_e64 { return 'f64' } // e64 format use f64 data + .si_s { return 's' } + .si_p { return 'p' } + .si_vp { return 'vp' } + } +} + +pub fn data_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'd_c' } + .si_u8 { return 'd_u8' } + .si_i8 { return 'd_i8' } + .si_u16 { return 'd_u16' } + .si_i16 { return 'd_i16' } + .si_u32 { return 'd_u32' } + .si_i32 { return 'd_i32' } + .si_u64 { return 'd_u64' } + .si_i64 { return 'd_i64' } + .si_f32 { return 'd_f32' } + .si_f64 { return 'd_f64' } + .si_g32 { return 'd_f32' } // g32 format use f32 data + .si_g64 { return 'd_f64' } // g64 format use f64 data + .si_e32 { return 'd_f32' } // e32 format use f32 data + .si_e64 { return 'd_f64' } // e64 format use f64 data + .si_s { return 'd_s' } + .si_p { return 'd_p' } + .si_vp { return 'd_vp' } + } +} + +const ( + // BUG: this const is not released from the memory! use a const for now + // si_s_code = "0x" + int(StrIntpType.si_s).hex() // code for a simple string + si_s_code = '0xfe10' +) + +fn should_use_indent_func(kind ast.Kind) bool { + return kind in [.struct_, .alias, .array, .array_fixed, .map, .sum_type, .interface_] +} + +fn (mut g Gen) gen_str_default(sym ast.TypeSymbol, styp string, str_fn_name string) { + mut convertor := '' + mut typename_ := '' + if sym.parent_idx in ast.integer_type_idxs { + convertor = 'int' + typename_ = 'int' + } else if sym.parent_idx == ast.f32_type_idx { + convertor = 'float' + typename_ = 'f32' + } else if sym.parent_idx == ast.f64_type_idx { + convertor = 'double' + typename_ = 'f64' + } else if sym.parent_idx == ast.bool_type_idx { + convertor = 'bool' + typename_ = 'bool' + } else { + verror("could not generate string method for type '$styp'") + } + g.type_definitions.writeln('string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('string ${str_fn_name}($styp it) {') + if convertor == 'bool' { + g.auto_str_funcs.writeln('\tstring tmp1 = string__plus(_SLIT("${styp}("), ($convertor)it ? _SLIT("true") : _SLIT("false"));') + } else { + g.auto_str_funcs.writeln('\tstring tmp1 = string__plus(_SLIT("${styp}("), tos3(${typename_}_str(($convertor)it).str));') + } + g.auto_str_funcs.writeln('\tstring tmp2 = string__plus(tmp1, _SLIT(")"));') + g.auto_str_funcs.writeln('\tstring_free(&tmp1);') + g.auto_str_funcs.writeln('\treturn tmp2;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_type(typ ast.Type) string { + styp := g.typ(typ).replace('*', '') + mut sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut str_fn_name := styp_to_str_fn_name(styp) + if mut sym.info is ast.Alias { + if sym.info.is_import { + sym = g.table.get_type_symbol(sym.info.parent_type) + str_fn_name = styp_to_str_fn_name(sym.name) + } + } + sym_has_str_method, str_method_expects_ptr, str_nr_args := sym.str_method_info() + already_generated_key := '$styp:$str_fn_name' + if !sym_has_str_method && already_generated_key !in g.str_types && !typ.has_flag(.optional) { + $if debugautostr ? { + eprintln('> gen_str_for_type: |typ: ${typ:5}, ${sym.name:20}|has_str: ${sym_has_str_method:5}|expects_ptr: ${str_method_expects_ptr:5}|nr_args: ${str_nr_args:1}|fn_name: ${str_fn_name:20}') + } + g.str_types << already_generated_key + match mut sym.info { + ast.Alias { + if sym.info.is_import { + g.gen_str_default(sym, styp, str_fn_name) + } else { + g.gen_str_for_alias(sym.info, styp, str_fn_name) + } + } + ast.Array { + g.gen_str_for_array(sym.info, styp, str_fn_name) + } + ast.ArrayFixed { + g.gen_str_for_array_fixed(sym.info, styp, str_fn_name) + } + ast.Enum { + g.gen_str_for_enum(sym.info, styp, str_fn_name) + } + ast.FnType { + g.gen_str_for_fn_type(sym.info, styp, str_fn_name) + } + ast.Struct { + g.gen_str_for_struct(sym.info, styp, str_fn_name) + } + ast.Map { + g.gen_str_for_map(sym.info, styp, str_fn_name) + } + ast.MultiReturn { + g.gen_str_for_multi_return(sym.info, styp, str_fn_name) + } + ast.SumType { + g.gen_str_for_union_sum_type(sym.info, styp, str_fn_name) + } + ast.Interface { + g.gen_str_for_interface(sym.info, styp, str_fn_name) + } + ast.Chan { + g.gen_str_for_chan(sym.info, styp, str_fn_name) + } + ast.Thread { + g.gen_str_for_thread(sym.info, styp, str_fn_name) + } + else { + verror("could not generate string method $str_fn_name for type '$styp'") + } + } + } + if typ.has_flag(.optional) { + option_already_generated_key := 'option_$already_generated_key' + if option_already_generated_key !in g.str_types { + g.gen_str_for_option(typ, styp, str_fn_name) + g.str_types << option_already_generated_key + } + return str_fn_name + } + return str_fn_name +} + +fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) { + parent_type := typ.clear_flag(.optional) + sym := g.table.get_type_symbol(parent_type) + sym_has_str_method, _, _ := sym.str_method_info() + parent_str_fn_name := g.gen_str_for_type(parent_type) + + g.type_definitions.writeln('string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0); }') + g.type_definitions.writeln('string indent_${str_fn_name}($styp it, int indent_count); // auto') + g.auto_str_funcs.writeln('string indent_${str_fn_name}($styp it, int indent_count) {') + g.auto_str_funcs.writeln('\tstring res;') + g.auto_str_funcs.writeln('\tif (it.state == 0) {') + if sym.kind == .string { + tmp_res := '${parent_str_fn_name}(*($sym.cname*)it.data)' + g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};') + } else if should_use_indent_func(sym.kind) && !sym_has_str_method { + g.auto_str_funcs.writeln('\t\tres = indent_${parent_str_fn_name}(*($sym.cname*)it.data, indent_count);') + } else { + g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(*($sym.cname*)it.data);') + } + g.auto_str_funcs.writeln('\t} else {') + + tmp_str := str_intp_sub('error: %%', 'IError_str(it.err)') + g.auto_str_funcs.writeln('\t\tres = $tmp_str;') + g.auto_str_funcs.writeln('\t}') + + g.auto_str_funcs.writeln('\treturn ${str_intp_sub('Option(%%)', 'res')};') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) { + parent_str_fn_name := g.gen_str_for_type(info.parent_type) + mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.')) + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp it, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp it, int indent_count) {') + g.auto_str_funcs.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);') + g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(it);') + g.auto_str_funcs.writeln('\tstring res = str_intp(3, _MOV((StrIntpData[]){ + {_SLIT0, $c.si_s_code, {.d_s = indents }}, + {_SLIT("${clean_type_v_type_name}("), $c.si_s_code, {.d_s = tmp_ds }}, + {_SLIT(")"), 0, {.d_c = 0 }} + }));') + g.auto_str_funcs.writeln('\tstring_free(&indents);') + g.auto_str_funcs.writeln('\tstring_free(&tmp_ds);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_fn_name string) { + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static string ${str_fn_name}($styp a) {') + fn_builder.writeln('\tstrings__Builder sb = strings__new_builder($info.types.len * 10);') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT("("));') + for i, typ in info.types { + sym := g.table.get_type_symbol(typ) + is_arg_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + arg_str_fn_name := g.gen_str_for_type(typ) + + if should_use_indent_func(sym.kind) && !sym_has_str_method { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}(a.arg$i));') + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + tmp_val := str_intp_g32('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } else { + tmp_val := str_intp_g64('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } + } else if sym.kind == .string { + tmp_str := str_intp_sq('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_str);') + } else if sym.kind == .function { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}());') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_arg_ptr, typ) + fn_builder.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}( $deref a.arg$i));') + } + if i != info.types.len - 1 { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT(", "));') + } + } + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT(")"));') + fn_builder.writeln('\tstring res = strings__Builder_str(&sb);') + fn_builder.writeln('\tstrings__Builder_free(&sb);') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) gen_str_for_enum(info ast.Enum, styp string, str_fn_name string) { + s := util.no_dots(styp) + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { /* gen_str_for_enum */') + // Enums tagged with `[flag]` are special in that they can be a combination of enum values + if info.is_flag { + clean_name := util.strip_main_name(styp.replace('__', '.')) + g.auto_str_funcs.writeln('\tstring ret = _SLIT("$clean_name{");') + g.auto_str_funcs.writeln('\tint first = 1;') + for i, val in info.vals { + g.auto_str_funcs.writeln('\tif (it & (1 << $i)) {if (!first) {ret = string__plus(ret, _SLIT(" | "));} ret = string__plus(ret, _SLIT(".$val")); first = 0;}') + } + g.auto_str_funcs.writeln('\tret = string__plus(ret, _SLIT("}"));') + g.auto_str_funcs.writeln('\treturn ret;') + } else { + g.auto_str_funcs.writeln('\tswitch(it) {') + // Only use the first multi value on the lookup + mut seen := []string{len: info.vals.len} + for val in info.vals { + if info.is_multi_allowed && val in seen { + continue + } else if info.is_multi_allowed { + seen << val + } + g.auto_str_funcs.writeln('\t\tcase ${s}__$val: return _SLIT("$val");') + } + g.auto_str_funcs.writeln('\t\tdefault: return _SLIT("unknown enum value");') + g.auto_str_funcs.writeln('\t}') + } + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return indent_${str_fn_name}(x, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + mut clean_interface_v_type_name := styp.replace('__', '.') + if styp.ends_with('*') { + clean_interface_v_type_name = '&' + clean_interface_v_type_name.replace('*', '') + } + if clean_interface_v_type_name.contains('_T_') { + clean_interface_v_type_name = + clean_interface_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_interface_v_type_name = util.strip_main_name(clean_interface_v_type_name) + fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) { /* gen_str_for_interface */') + for typ in info.types { + subtype := g.table.get_type_symbol(typ) + mut func_name := g.gen_str_for_type(typ) + sym_has_str_method, str_method_expects_ptr, _ := subtype.str_method_info() + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + func_name = 'indent_$func_name' + } + + // str_intp + deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } + if typ == ast.string_type { + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_interface_v_type_name}(\'"), $c.si_s_code, {.d_s = $val}}, + {_SLIT("\')"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;') + } else { + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_interface_v_type_name}("), $c.si_s_code, {.d_s = $val}}, + {_SLIT(")"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;\n') + } + } + fn_builder.writeln('\treturn _SLIT("unknown interface value");') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return indent_${str_fn_name}(x, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) {') + mut clean_sum_type_v_type_name := styp.replace('__', '.') + if styp.ends_with('*') { + clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '') + } + if clean_sum_type_v_type_name.contains('_T_') { + clean_sum_type_v_type_name = + clean_sum_type_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name) + fn_builder.writeln('\tswitch(x._typ) {') + for typ in info.variants { + typ_str := g.typ(typ) + mut func_name := g.gen_str_for_type(typ) + sym := g.table.get_type_symbol(typ) + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } + if should_use_indent_func(sym.kind) && !sym_has_str_method { + func_name = 'indent_$func_name' + } + + // str_intp + if typ == ast.string_type { + mut val := '${func_name}(${deref}($typ_str*)x._$sym.cname' + if should_use_indent_func(sym.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_sum_type_v_type_name}(\'"), $c.si_s_code, {.d_s = $val}}, + {_SLIT("\')"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\t\tcase $typ: return $res;') + } else { + mut val := '${func_name}(${deref}($typ_str*)x._$sym.cname' + if should_use_indent_func(sym.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_sum_type_v_type_name}("), $c.si_s_code, {.d_s = $val}}, + {_SLIT(")"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\t\tcase $typ: return $res;') + } + } + fn_builder.writeln('\t\tdefault: return _SLIT("unknown sum type value");') + fn_builder.writeln('\t}') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) fn_decl_str(info ast.FnType) string { + mut fn_str := 'fn (' + for i, arg in info.func.params { + if arg.is_mut { + fn_str += 'mut ' + } + if i > 0 { + fn_str += ', ' + } + fn_str += util.strip_main_name(g.table.get_type_name(g.unwrap_generic(arg.typ))) + } + fn_str += ')' + if info.func.return_type == ast.ovoid_type { + fn_str += ' ?' + } else if info.func.return_type != ast.void_type { + x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) + if info.func.return_type.has_flag(.optional) { + fn_str += ' ?$x' + } else { + fn_str += ' $x' + } + } + return fn_str +} + +fn (mut g Gen) gen_str_for_fn_type(info ast.FnType, styp string, str_fn_name string) { + g.type_definitions.writeln('static string ${str_fn_name}(); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}() { return _SLIT("${g.fn_decl_str(info)}");}') +} + +fn (mut g Gen) gen_str_for_chan(info ast.Chan, styp string, str_fn_name string) { + elem_type_name := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.elem_type))) + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return sync__Channel_auto_str(x, _SLIT("$elem_type_name")); }') +} + +fn (mut g Gen) gen_str_for_thread(info ast.Thread, styp string, str_fn_name string) { + ret_type_name := util.strip_main_name(g.table.get_type_name(info.return_type)) + g.type_definitions.writeln('static string ${str_fn_name}($styp _); // auto}') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp _) { return _SLIT("thread($ret_type_name)");}') +} + +[inline] +fn styp_to_str_fn_name(styp string) string { + return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_str' +} + +fn deref_kind(str_method_expects_ptr bool, is_elem_ptr bool, typ ast.Type) (string, string) { + if str_method_expects_ptr != is_elem_ptr { + if is_elem_ptr { + return '*'.repeat(typ.nr_muls()), '&'.repeat(typ.nr_muls()) + } else { + return '&', '' + } + } + return '', '' +} + +fn (mut g Gen) gen_str_for_array(info ast.Array, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + field_styp := g.typ(typ) + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + mut elem_str_fn_name := g.gen_str_for_type(typ) + if sym.kind == .byte { + elem_str_fn_name = elem_str_fn_name + '_escaped' + } + + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp a) { return indent_${str_fn_name}(a, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp a, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp a, int indent_count) {') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(a.len * 10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("["));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < a.len; ++i) {') + if sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') + } else { + if sym.kind == .array_fixed { + g.auto_str_funcs.writeln('\t\t$field_styp it;') + g.auto_str_funcs.writeln('\t\tmemcpy(*($field_styp*)it, (byte*)array_get(a, i), sizeof($field_styp));') + } else { + g.auto_str_funcs.writeln('\t\t$field_styp it = *($field_styp*)array_get(a, i);') + } + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = indent_${elem_str_fn_name}(*it, indent_count);') + } else { + g.auto_str_funcs.writeln('\t\tstring x = indent_${elem_str_fn_name}(it, indent_count);') + } + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g32('it')};') + } else { + g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g64('it')};') + } + } else if sym.kind == .rune { + // Rune are managed at this level as strings + g.auto_str_funcs.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{_SLIT("\`"), $c.si_s_code, {.d_s = ${elem_str_fn_name}(it) }}, {_SLIT("\`"), 0, {.d_c = 0 }}}));\n') + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{_SLIT("\'"), $c.si_s_code, {.d_s = it }}, {_SLIT("\'"), 0, {.d_c = 0 }}}));\n') + } else { + // There is a custom .str() method, so use it. + // NB: we need to take account of whether the user has defined + // `fn (x T) str() {` or `fn (x &T) str() {`, and convert accordingly + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}( $deref it);') + } + } + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);') + if g.is_autofree && typ != ast.bool_type { + // no need to free "true"/"false" literals + g.auto_str_funcs.writeln('\t\tstring_free(&x);') + } + g.auto_str_funcs.writeln('\t\tif (i < a.len-1) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("]"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_array_fixed(info ast.ArrayFixed, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + elem_str_fn_name := g.gen_str_for_type(typ) + + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp a) { return indent_${str_fn_name}(a, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp a, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp a, int indent_count) {') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder($info.size * 10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("["));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {') + if sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + g.auto_str_funcs.writeln('\t\tif ( 0 == a[i] ) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT("0"));') + g.auto_str_funcs.writeln('\t\t}else{') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]) );') + g.auto_str_funcs.writeln('\t\t}') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]) );') + } + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32('a[i]')} );') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64('a[i]')} );') + } + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('a[i]')});') + } else if sym.kind == .rune { + tmp_str := str_intp_rune('${elem_str_fn_name}( $deref a[i])') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]));') + } + } + g.auto_str_funcs.writeln('\t\tif (i < ${info.size - 1}) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("]"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) { + mut key_typ := info.key_type + mut key_sym := g.table.get_type_symbol(key_typ) + if mut key_sym.info is ast.Alias { + key_typ = key_sym.info.parent_type + key_sym = g.table.get_type_symbol(key_typ) + } + key_styp := g.typ(key_typ) + key_str_fn_name := key_styp.replace('*', '') + '_str' + if !key_sym.has_method('str') { + g.gen_str_for_type(key_typ) + } + + mut val_typ := info.value_type + mut val_sym := g.table.get_type_symbol(val_typ) + if mut val_sym.info is ast.Alias { + val_typ = val_sym.info.parent_type + val_sym = g.table.get_type_symbol(val_typ) + } + val_styp := g.typ(val_typ) + elem_str_fn_name := val_styp.replace('*', '') + '_str' + if !val_sym.has_method('str') { + g.gen_str_for_type(val_typ) + } + + g.type_definitions.writeln('static string ${str_fn_name}($styp m); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp m) { return indent_${str_fn_name}(m, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp m, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp m, int indent_count) { /* gen_str_for_map */') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(m.key_values.len*10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("{"));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {') + g.auto_str_funcs.writeln('\t\tif (!DenseArray_has_index(&m.key_values, i)) { continue; }') + + if key_sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstring key = *(string*)DenseArray_key(&m.key_values, i);') + } else { + g.auto_str_funcs.writeln('\t\t$key_styp key = *($key_styp*)DenseArray_key(&m.key_values, i);') + } + if key_sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('key')});') + } else if key_sym.kind == .rune { + tmp_str := str_intp_rune('${key_str_fn_name}(key)') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${key_str_fn_name}(key));') + } + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT(": "));') + if val_sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());') + } else if val_sym.kind == .string { + tmp_str := str_intp_sq('*($val_styp*)DenseArray_value(&m.key_values, i)') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { + ptr_str := '*'.repeat(val_typ.nr_muls()) + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*${ptr_str}($val_styp*)DenseArray_value(&m.key_values, i), indent_count));') + } else if val_sym.kind in [.f32, .f64] { + tmp_val := '*($val_styp*)DenseArray_value(&m.key_values, i)' + if val_sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64(tmp_val)});') + } + } else if val_sym.kind == .rune { + tmp_str := str_intp_rune('${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i))') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i)));') + } + g.auto_str_funcs.writeln('\t\tif (i != m.key_values.len-1) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("}"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (g &Gen) type_to_fmt1(typ ast.Type) StrIntpType { + if typ == ast.byte_type_idx { + return .si_u8 + } + if typ == ast.char_type_idx { + return .si_c + } + if typ in ast.voidptr_types || typ in ast.byteptr_types { + return .si_p + } + if typ in ast.charptr_types { + // return '%C\\000' // a C string + return .si_s + } + sym := g.table.get_type_symbol(typ) + if typ.is_ptr() && (typ.is_int_valptr() || typ.is_float_valptr()) { + return .si_s + } else if sym.kind in [.struct_, .array, .array_fixed, .map, .bool, .enum_, .interface_, + .sum_type, .function, .alias, .chan] { + return .si_s + } else if sym.kind == .string { + return .si_s + // return "'%.*s\\000'" + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + return .si_g32 + } + return .si_g64 + } else if sym.kind == .int { + return .si_i32 + } else if sym.kind == .u32 { + return .si_u32 + } else if sym.kind == .u64 { + return .si_u64 + } else if sym.kind == .i64 { + return .si_i64 + } + return .si_i32 +} + +fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp it, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + defer { + g.auto_fn_definitions << fn_builder.str() + } + fn_builder.writeln('static string indent_${str_fn_name}($styp it, int indent_count) {') + mut clean_struct_v_type_name := styp.replace('__', '.') + if clean_struct_v_type_name.contains('_T_') { + // TODO: this is a bit hacky. styp shouldn't be even parsed with _T_ + // use something different than g.typ for styp + clean_struct_v_type_name = + clean_struct_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_struct_v_type_name = util.strip_main_name(clean_struct_v_type_name) + // generate ident / indent length = 4 spaces + if info.fields.len == 0 { + fn_builder.writeln('\treturn _SLIT("$clean_struct_v_type_name{}");') + fn_builder.writeln('}') + return + } + + fn_builder.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);') + fn_builder.writeln('\tstring res = str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){') + fn_builder.writeln('\t\t{_SLIT("$clean_struct_v_type_name{\\n"), 0, {.d_c=0}},') + + for i, field in info.fields { + mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' } + base_fmt := g.type_to_fmt1(g.unwrap_generic(field.typ)) + + // manage prefix and quote symbol for the filed + mut quote_str := '' + mut prefix := '' + sym := g.table.get_type_symbol(g.unwrap_generic(field.typ)) + if sym.kind == .string { + quote_str = "'" + } else if field.typ in ast.charptr_types { + quote_str = '\\"' + prefix = 'C' + } + + // first fields doesn't need \n + if i == 0 { + fn_builder.write_string('\t\t{_SLIT0, $c.si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') + } else { + fn_builder.write_string('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') + } + + // custom methods management + has_custom_str := sym.has_method('str') + mut field_styp := g.typ(field.typ).replace('*', '') + field_styp_fn_name := if has_custom_str { + '${field_styp}_str' + } else { + g.gen_str_for_type(field.typ) + } + + // manage the fact hat with float we use always the g representation + if sym.kind !in [.f32, .f64] { + fn_builder.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=') + } else { + g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex() + fn_builder.write_string('{_SLIT("$quote_str"), $g_fmt, {.${data_str(base_fmt)}=') + } + + mut func := struct_auto_str_func1(sym, field.typ, field_styp_fn_name, field.name) + if field.typ in ast.cptr_types { + func = '(voidptr) it.$field.name' + } else if field.typ.is_ptr() { + // reference types can be "nil" + fn_builder.write_string('isnil(it.${c_name(field.name)})') + fn_builder.write_string(' ? _SLIT("nil") : ') + // struct, floats and ints have a special case through the _str function + if sym.kind != .struct_ && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() { + fn_builder.write_string('*') + } + } + // handle circular ref type of struct to the struct itself + if styp == field_styp { + fn_builder.write_string('_SLIT("")') + } else { + // manage C charptr + if field.typ in ast.charptr_types { + fn_builder.write_string('tos2((byteptr)$func)') + } else { + if field.typ.is_ptr() && sym.kind == .struct_ { + fn_builder.write_string('(indent_count > 25) ? _SLIT("") : ') + } + fn_builder.write_string(func) + } + } + + fn_builder.writeln('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},') + } + fn_builder.writeln('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},') + fn_builder.writeln('\t}));') + fn_builder.writeln('\tstring_free(&indents);') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') +} + +fn struct_auto_str_func1(sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string) string { + has_custom_str, expects_ptr, _ := sym.str_method_info() + if sym.kind == .enum_ { + return '${fn_name}(it.${c_name(field_name)})' + } else if should_use_indent_func(sym.kind) { + mut obj := 'it.${c_name(field_name)}' + if field_type.is_ptr() && !expects_ptr { + obj = '*$obj' + } + if has_custom_str { + return '${fn_name}($obj)' + } + return 'indent_${fn_name}($obj, indent_count + 1)' + } else if sym.kind in [.array, .array_fixed, .map, .sum_type] { + if has_custom_str { + return '${fn_name}(it.${c_name(field_name)})' + } + return 'indent_${fn_name}(it.${c_name(field_name)}, indent_count + 1)' + } else if sym.kind == .function { + return '${fn_name}()' + } else { + if sym.kind == .chan { + return '${fn_name}(it.${c_name(field_name)})' + } + mut method_str := 'it.${c_name(field_name)}' + if sym.kind == .bool { + method_str += ' ? _SLIT("true") : _SLIT("false")' + } else if (field_type.is_int_valptr() || field_type.is_float_valptr()) + && field_type.is_ptr() && !expects_ptr { + // ptr int can be "nil", so this needs to be casted to a string + if sym.kind == .f32 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g32_code, {.d_f32 = *$method_str }} + }))' + } else if sym.kind == .f64 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g64_code, {.d_f64 = *$method_str }} + }))' + } else if sym.kind == .u64 { + fmt_type := StrIntpType.si_u64 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *$method_str }}}))' + } + fmt_type := StrIntpType.si_i32 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))' + } + return method_str + } +} diff --git a/v_windows/v/vlib/v/gen/c/cgen.v b/v_windows/v/vlib/v/gen/c/cgen.v new file mode 100644 index 0000000..152d84d --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cgen.v @@ -0,0 +1,6878 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import os +import strings +import v.ast +import v.pref +import v.token +import v.util +import v.util.version +import v.depgraph + +const ( + // NB: some of the words in c_reserved, are not reserved in C, + // but are in C++, or have special meaning in V, thus need escaping too. + c_reserved = ['auto', 'break', 'calloc', 'case', 'char', 'class', 'const', 'continue', + 'default', 'delete', 'do', 'double', 'else', 'enum', 'error', 'exit', 'export', 'extern', + 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', 'long', 'malloc', 'namespace', + 'new', 'panic', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', + 'struct', 'switch', 'typedef', 'typename', 'union', 'unix', 'unsigned', 'void', 'volatile', + 'while', 'template', 'stdout', 'stdin', 'stderr'] + c_reserved_map = string_array_to_map(c_reserved) + // same order as in token.Kind + cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le'] + // when operands are switched + cmp_rev = ['eq', 'ne', 'lt', 'gt', 'le', 'ge'] +) + +fn string_array_to_map(a []string) map[string]bool { + mut res := map[string]bool{} + for x in a { + res[x] = true + } + return res +} + +struct Gen { + pref &pref.Preferences + module_built string + field_data_type ast.Type // cache her to avoid map lookups +mut: + table &ast.Table + out strings.Builder + cheaders strings.Builder + includes strings.Builder // all C #includes required by V modules + typedefs strings.Builder + typedefs2 strings.Builder + type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + global_inits map[string]strings.Builder // default initializers for globals (goes in _vinit()) + inits map[string]strings.Builder // contents of `void _vinit/2{}` + cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` + gowrappers strings.Builder // all go callsite wrappers + stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined + auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs + comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI + pcs_declarations strings.Builder // -prof profile counter declarations for each function + hotcode_definitions strings.Builder // -live declarations & functions + embedded_data strings.Builder // data to embed in the executable/binary + shared_types strings.Builder // shared/lock types + shared_functions strings.Builder // shared constructors + channel_definitions strings.Builder // channel related code + options_typedefs strings.Builder // Option typedefs + options strings.Builder // `Option_xxxx` types + json_forward_decls strings.Builder // json type forward decls + enum_typedefs strings.Builder // enum types + sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc + file &ast.File + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + last_fn_c_name string + tmp_count int // counter for unique tmp vars (_tmp1, _tmp2 etc); resets at the start of each fn. + tmp_count2 int // a separate tmp var counter for autofree fn calls + tmp_count_declarations int // counter for unique tmp names (_d1, _d2 etc); does NOT reset, used for C declarations + global_tmp_count int // like tmp_count but global and not resetted in each function + is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) + discard_or_result bool // do not safe last ExprStmt of `or` block in tmp variable to defer ongoing expr usage + is_void_expr_stmt bool // ExprStmt whos result is discarded + is_arraymap_set bool // map or array set value state + is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc + is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) + is_shared bool // for initialization of hidden mutex in `[rw]shared` literals + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + inside_cast_in_heap int // inside cast to interface type in heap (resolve recursive calls) + arraymap_set_pos int // map or array set value position + vlines_path string // set to the proper path for generating #line directives + optionals []string // to avoid duplicates TODO perf, use map + chan_pop_optionals []string // types for `x := <-ch or {...}` + chan_push_optionals []string // types for `ch <- x or {...}` + cur_lock ast.LockExpr + mtxs string // array of mutexes if the `lock` has multiple variables + labeled_loops map[string]&ast.Stmt + inner_loop &ast.Stmt + shareds []int // types with hidden mutex for which decl has been emitted + inside_ternary int // ?: comma separated statements on a single line + inside_map_postfix bool // inside map++/-- postfix expr + inside_map_infix bool // inside map< fn counter name + is_builtin_mod bool + hotcode_fn_names []string + embedded_files []ast.EmbeddedFile + sql_i int + sql_stmt_name string + sql_bind_name string + sql_idents []string + sql_idents_types []ast.Type + sql_left_type ast.Type + sql_table_name string + sql_fkey string + sql_parent_id string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` + inside_vweb_tmpl bool + inside_return bool + inside_or_block bool + strs_to_free0 []string // strings.Builder + // strs_to_free []string // strings.Builder + inside_call bool + has_main bool + inside_const bool + comp_for_method string // $for method in T.methods {} + comp_for_field_var string // $for field in T.fields {}; the variable name + comp_for_field_value ast.StructField // value of the field variable + comp_for_field_type ast.Type // type of the field variable inferred from `$if field.typ is T {}` + comptime_var_type_map map[string]ast.Type + // tmp_arg_vars_to_free []string + // autofree_pregen map[string]string + // autofree_pregen_buf strings.Builder + // autofree_tmp_vars []string // to avoid redefining the same tmp vars in a single function + called_fn_name string + cur_mod ast.Module + is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` + is_fn_index_call bool + // nr_vars_to_free int + // doing_autofree_tmp bool + inside_lambda bool + prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type + // used in match multi branch + // TypeOne, TypeTwo {} + // where an aggregate (at least two types) is generated + // sum type deref needs to know which index to deref because unions take care of the correct field + aggregate_type_idx int + returned_var_name string // to detect that a var doesn't need to be freed since it's being returned + branch_parent_pos int // used in BranchStmt (continue/break) for autofree stop position + infix_left_var_name string // a && if expr + timers &util.Timers = util.new_timers(false) + force_main_console bool // true when [console] used on fn main() + as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast) + obf_table map[string]string + // main_fn_decl_node ast.FnDecl + expected_cast_type ast.Type // for match expr of sumtypes + defer_vars []string + anon_fn bool + array_sort_fn map[string]bool + nr_closures int +} + +pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { + // println('start cgen2') + mut module_built := '' + if pref.build_mode == .build_module { + for file in files { + if file.path.contains(pref.path) + && file.mod.short_name == pref.path.all_after_last(os.path_separator).trim_right(os.path_separator) { + module_built = file.mod.name + break + } + } + } + mut timers_should_print := false + $if time_cgening ? { + timers_should_print = true + } + mut g := Gen{ + file: 0 + out: strings.new_builder(512000) + cheaders: strings.new_builder(15000) + includes: strings.new_builder(100) + typedefs: strings.new_builder(100) + typedefs2: strings.new_builder(100) + type_definitions: strings.new_builder(100) + definitions: strings.new_builder(100) + gowrappers: strings.new_builder(100) + stringliterals: strings.new_builder(100) + auto_str_funcs: strings.new_builder(100) + comptime_defines: strings.new_builder(100) + pcs_declarations: strings.new_builder(100) + hotcode_definitions: strings.new_builder(100) + embedded_data: strings.new_builder(1000) + options_typedefs: strings.new_builder(100) + options: strings.new_builder(100) + shared_types: strings.new_builder(100) + shared_functions: strings.new_builder(100) + channel_definitions: strings.new_builder(100) + json_forward_decls: strings.new_builder(100) + enum_typedefs: strings.new_builder(100) + sql_buf: strings.new_builder(100) + table: table + pref: pref + fn_decl: 0 + is_autofree: true + indent: -1 + module_built: module_built + timers: util.new_timers(timers_should_print) + inner_loop: &ast.EmptyStmt{} + field_data_type: ast.Type(table.find_type_idx('FieldData')) + } + g.timers.start('cgen init') + for mod in g.table.modules { + g.inits[mod] = strings.new_builder(100) + g.global_inits[mod] = strings.new_builder(100) + g.cleanups[mod] = strings.new_builder(100) + } + g.init() + g.timers.show('cgen init') + mut tests_inited := false + mut autofree_used := false + for file in files { + g.timers.start('cgen_file $file.path') + g.file = file + if g.pref.is_vlines { + g.vlines_path = util.vlines_escape_path(file.path, g.pref.ccompiler) + } + // println('\ncgen "$g.file.path" nr_stmts=$file.stmts.len') + // building_v := true && (g.file.path.contains('/vlib/') || g.file.path.contains('cmd/v')) + g.is_test = g.pref.is_test + if g.file.path == '' || !g.pref.autofree { + // cgen test or building V + // println('autofree=false') + g.is_autofree = false + } else { + g.is_autofree = true + autofree_used = true + } + // anon fn may include assert and thus this needs + // to be included before any test contents are written + if g.is_test && !tests_inited { + g.write_tests_definitions() + tests_inited = true + } + g.stmts(file.stmts) + // Transfer embedded files + if file.embedded_files.len > 0 { + for path in file.embedded_files { + if path !in g.embedded_files { + g.embedded_files << path + } + } + } + g.timers.show('cgen_file $file.path') + } + g.timers.start('cgen common') + if autofree_used { + g.is_autofree = true // so that void _vcleanup is generated + } + // to make sure type idx's are the same in cached mods + if g.pref.build_mode == .build_module { + for idx, typ in g.table.type_symbols { + if idx == 0 { + continue + } + g.definitions.writeln('int _v_type_idx_${typ.cname}();') + } + } else if g.pref.use_cache { + for idx, typ in g.table.type_symbols { + if idx == 0 { + continue + } + g.definitions.writeln('int _v_type_idx_${typ.cname}() { return $idx; };') + } + } + // + g.dump_expr_definitions() + // v files are finished, what remains is pure C code + g.gen_vlines_reset() + if g.pref.build_mode != .build_module { + // no init in builtin.o + g.write_init_function() + } + + g.finish() + + mut b := strings.new_builder(640000) + b.write_string(g.hashes()) + b.writeln('\n// V comptime_defines:') + b.write_string(g.comptime_defines.str()) + b.writeln('\n// V typedefs:') + b.write_string(g.typedefs.str()) + b.writeln('\n// V typedefs2:') + b.write_string(g.typedefs2.str()) + b.writeln('\n// V cheaders:') + b.write_string(g.cheaders.str()) + if g.pcs_declarations.len > 0 { + b.writeln('\n// V profile counters:') + b.write_string(g.pcs_declarations.str()) + } + b.writeln('\n// V includes:') + b.write_string(g.includes.str()) + b.writeln('\n// Enum definitions:') + b.write_string(g.enum_typedefs.str()) + b.writeln('\n// V type definitions:') + b.write_string(g.type_definitions.str()) + b.writeln('\n// V shared types:') + b.write_string(g.shared_types.str()) + b.writeln('\n// V Option_xxx definitions:') + b.write_string(g.options.str()) + b.writeln('\n// V json forward decls:') + b.write_string(g.json_forward_decls.str()) + b.writeln('\n// V definitions:') + b.write_string(g.definitions.str()) + interface_table := g.interface_table() + if interface_table.len > 0 { + b.writeln('\n// V interface table:') + b.write_string(interface_table) + } + if g.gowrappers.len > 0 { + b.writeln('\n// V gowrappers:') + b.write_string(g.gowrappers.str()) + } + if g.hotcode_definitions.len > 0 { + b.writeln('\n// V hotcode definitions:') + b.write_string(g.hotcode_definitions.str()) + } + if g.embedded_data.len > 0 { + b.writeln('\n// V embedded data:') + b.write_string(g.embedded_data.str()) + } + if g.options_typedefs.len > 0 { + b.writeln('\n// V option typedefs:') + b.write_string(g.options_typedefs.str()) + } + if g.shared_functions.len > 0 { + b.writeln('\n// V shared type functions:') + b.write_string(g.shared_functions.str()) + b.write_string(c_concurrency_helpers) + } + if g.channel_definitions.len > 0 { + b.writeln('\n// V channel code:') + b.write_string(g.channel_definitions.str()) + } + if g.stringliterals.len > 0 { + b.writeln('\n// V stringliterals:') + b.write_string(g.stringliterals.str()) + } + if g.auto_str_funcs.len > 0 { + // if g.pref.build_mode != .build_module { + b.writeln('\n// V auto str functions:') + b.write_string(g.auto_str_funcs.str()) + // } + } + if g.auto_fn_definitions.len > 0 { + for fn_def in g.auto_fn_definitions { + b.writeln(fn_def) + } + } + if g.anon_fn_definitions.len > 0 { + if g.nr_closures > 0 { + b.writeln('\n// V closure helpers') + b.writeln(c_closure_helpers(g.pref)) + } + for fn_def in g.anon_fn_definitions { + b.writeln(fn_def) + } + } + b.writeln('\n// V out') + b.write_string(g.out.str()) + b.writeln('\n// THE END.') + g.timers.show('cgen common') + return b.str() +} + +pub fn (g &Gen) hashes() string { + mut res := c_commit_hash_default.replace('@@@', version.vhash()) + res += c_current_commit_hash_default.replace('@@@', version.githash(g.pref.building_v)) + return res +} + +pub fn (mut g Gen) init() { + if g.pref.custom_prelude != '' { + g.cheaders.writeln(g.pref.custom_prelude) + } else if !g.pref.no_preludes { + g.cheaders.writeln('// Generated by the V compiler') + tcc_undef_has_include := ' +#if defined(__TINYC__) && defined(__has_include) +// tcc does not support has_include properly yet, turn it off completely +#undef __has_include +#endif' + g.cheaders.writeln(tcc_undef_has_include) + g.includes.writeln(tcc_undef_has_include) + g.cheaders.writeln(get_guarded_include_text('', 'The C compiler can not find . Please install build-essentials')) // int64_t etc + g.cheaders.writeln(c_builtin_types) + if g.pref.is_bare { + g.cheaders.writeln(c_bare_headers) + } else { + g.cheaders.writeln(c_headers) + } + if !g.pref.skip_unused || g.table.used_maps > 0 { + g.cheaders.writeln(c_wyhash_headers) + } + } + if g.pref.os == .ios { + g.cheaders.writeln('#define __TARGET_IOS__ 1') + g.cheaders.writeln('#include ') + } + g.write_builtin_types() + g.write_typedef_types() + g.write_typeof_functions() + g.write_sorted_types() + g.write_multi_return_types() + g.definitions.writeln('// end of definitions #endif') + // + g.stringliterals.writeln('') + g.stringliterals.writeln('// >> string literal consts') + if g.pref.build_mode != .build_module { + g.stringliterals.writeln('void vinit_string_literals(){') + } + if g.pref.compile_defines_all.len > 0 { + g.comptime_defines.writeln('// V compile time defines by -d or -define flags:') + g.comptime_defines.writeln('// All custom defines : ' + + g.pref.compile_defines_all.join(',')) + g.comptime_defines.writeln('// Turned ON custom defines: ' + + g.pref.compile_defines.join(',')) + for cdefine in g.pref.compile_defines { + g.comptime_defines.writeln('#define CUSTOM_DEFINE_$cdefine') + } + g.comptime_defines.writeln('') + } + if g.table.gostmts > 0 { + g.comptime_defines.writeln('#define __VTHREADS__ (1)') + } + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.comptime_defines.writeln('#define _VGCBOEHM (1)') + } + if g.pref.is_debug || 'debug' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VDEBUG (1)') + } + if g.pref.is_prod || 'prod' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VPROD (1)') + } + if g.pref.is_test || 'test' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VTEST (1)') + } + if g.pref.autofree { + g.comptime_defines.writeln('#define _VAUTOFREE (1)') + // g.comptime_defines.writeln('unsigned char* g_cur_str;') + } + if g.pref.prealloc { + g.comptime_defines.writeln('#define _VPREALLOC (1)') + } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloading_declarations() + } + // Obfuscate only functions in the main module for now. + // Generate the obf_ast. + if g.pref.obfuscate { + mut i := 0 + // fns + for key, f in g.table.fns { + if f.mod != 'main' && key != 'main' { // !key.starts_with('main.') { + continue + } + g.obf_table[key] = '_f$i' + i++ + } + // methods + for type_sym in g.table.type_symbols { + if type_sym.mod != 'main' { + continue + } + for method in type_sym.methods { + g.obf_table[type_sym.name + '.' + method.name] = '_f$i' + i++ + } + } + } +} + +pub fn (mut g Gen) finish() { + if g.pref.build_mode != .build_module { + g.stringliterals.writeln('}') + } + g.stringliterals.writeln('// << string literal consts') + g.stringliterals.writeln('') + if g.pref.is_prof && g.pref.build_mode != .build_module { + g.gen_vprint_profile_stats() + } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloader_code() + } + if g.embed_file_is_prod_mode() && g.embedded_files.len > 0 { + g.gen_embedded_data() + } + if g.pref.is_test { + g.gen_c_main_for_tests() + } else { + g.gen_c_main() + } +} + +pub fn (mut g Gen) write_typeof_functions() { + g.writeln('') + g.writeln('// >> typeof() support for sum types / interfaces') + for ityp, typ in g.table.type_symbols { + if typ.kind == .sum_type { + sum_info := typ.info as ast.SumType + if sum_info.is_generic { + continue + } + g.writeln('static char * v_typeof_sumtype_${typ.cname}(int sidx) { /* $typ.name */ ') + if g.pref.build_mode == .build_module { + g.writeln('\t\tif( sidx == _v_type_idx_${typ.cname}() ) return "${util.strip_main_name(typ.name)}";') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\treturn "unknown ${util.strip_main_name(typ.name)}";') + } else { + tidx := g.table.find_type_idx(typ.name) + g.writeln('\tswitch(sidx) {') + g.writeln('\t\tcase $tidx: return "${util.strip_main_name(typ.name)}";') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\t\tcase $v: return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\t\tdefault: return "unknown ${util.strip_main_name(typ.name)}";') + g.writeln('\t}') + } + g.writeln('}') + g.writeln('') + g.writeln('int v_typeof_sumtype_idx_${typ.cname}(int sidx) { /* $typ.name */ ') + if g.pref.build_mode == .build_module { + g.writeln('\t\tif( sidx == _v_type_idx_${typ.cname}() ) return ${int(ityp)};') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return ${int(v)};') + } + g.writeln('\treturn ${int(ityp)};') + } else { + tidx := g.table.find_type_idx(typ.name) + g.writeln('\tswitch(sidx) {') + g.writeln('\t\tcase $tidx: return ${int(ityp)};') + for v in sum_info.variants { + g.writeln('\t\tcase $v: return ${int(v)};') + } + g.writeln('\t\tdefault: return ${int(ityp)};') + g.writeln('\t}') + } + g.writeln('}') + } else if typ.kind == .interface_ { + inter_info := typ.info as ast.Interface + if inter_info.is_generic { + continue + } + g.definitions.writeln('static char * v_typeof_interface_${typ.cname}(int sidx);') + g.writeln('static char * v_typeof_interface_${typ.cname}(int sidx) { /* $typ.name */ ') + for t in inter_info.types { + subtype := g.table.get_type_symbol(t) + g.writeln('\tif (sidx == _${typ.cname}_${subtype.cname}_index) return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\treturn "unknown ${util.strip_main_name(typ.name)}";') + g.writeln('}') + g.writeln('') + g.writeln('static int v_typeof_interface_idx_${typ.cname}(int sidx) { /* $typ.name */ ') + for t in inter_info.types { + subtype := g.table.get_type_symbol(t) + g.writeln('\tif (sidx == _${typ.cname}_${subtype.cname}_index) return ${int(t)};') + } + g.writeln('\treturn ${int(ityp)};') + g.writeln('}') + } + } + g.writeln('// << typeof() support for sum types') + g.writeln('') +} + +// V type to C typecc +fn (mut g Gen) typ(t ast.Type) string { + styp := g.base_type(t) + if t.has_flag(.optional) { + // Register an optional if it's not registered yet + return g.register_optional(t) + } + /* + if styp.starts_with('C__') { + return styp[3..] + } + */ + return styp +} + +fn (mut g Gen) base_type(t ast.Type) string { + share := t.share() + mut styp := if share == .atomic_t { t.atomic_typename() } else { g.cc_type(t, true) } + if t.has_flag(.shared_f) { + styp = g.find_or_register_shared(t, styp) + } + if !t.has_flag(.variadic) { + nr_muls := g.unwrap_generic(t).nr_muls() + if nr_muls > 0 { + styp += strings.repeat(`*`, nr_muls) + } + } + return styp +} + +fn (mut g Gen) generic_fn_name(types []ast.Type, before string, is_decl bool) string { + if types.len == 0 { + return before + } + // Using _T_ to differentiate between get and get_string + // `foo()` => `foo_T_int()` + mut name := before + '_T' + for typ in types { + name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.typ(typ.set_nr_muls(0)) + } + return name +} + +fn (mut g Gen) expr_string(expr ast.Expr) string { + pos := g.out.len + g.expr(expr) + return g.out.cut_to(pos).trim_space() +} + +// Surround a potentially multi-statement expression safely with `prepend` and `append`. +// (and create a statement) +fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string { + pos := g.out.len + g.stmt_path_pos << pos + defer { + g.stmt_path_pos.delete_last() + } + g.write(prepend) + g.expr(expr) + g.write(append) + return g.out.cut_to(pos) +} + +// TODO this really shouldnt be seperate from typ +// but I(emily) would rather have this generation +// all unified in one place so that it doesnt break +// if one location changes +fn (mut g Gen) optional_type_name(t ast.Type) (string, string) { + base := g.base_type(t) + mut styp := 'Option_$base' + if t.is_ptr() { + styp = styp.replace('*', '_ptr') + } + return styp, base +} + +fn (g &Gen) optional_type_text(styp string, base string) string { + // replace void with something else + size := if base == 'void' { 'byte' } else { base } + ret := 'struct $styp { + byte state; + IError err; + byte data[sizeof($size)]; +}' + return ret +} + +fn (mut g Gen) register_optional(t ast.Type) string { + styp, base := g.optional_type_name(t) + if styp !in g.optionals { + g.typedefs2.writeln('typedef struct $styp $styp;') + g.options.write_string(g.optional_type_text(styp, base)) + g.options.writeln(';\n') + g.optionals << styp.clone() + } + return styp +} + +fn (mut g Gen) find_or_register_shared(t ast.Type, base string) string { + sh_typ := '__shared__$base' + t_idx := t.idx() + if t_idx in g.shareds { + return sh_typ + } + mtx_typ := 'sync__RwMutex' + g.shared_types.writeln('struct $sh_typ { $base val; $mtx_typ mtx; };') + g.shared_functions.writeln('static inline voidptr __dup${sh_typ}(voidptr src, int sz) {') + g.shared_functions.writeln('\t$sh_typ* dest = memdup(src, sz);') + g.shared_functions.writeln('\tsync__RwMutex_init(&dest->mtx);') + g.shared_functions.writeln('\treturn dest;') + g.shared_functions.writeln('}') + g.typedefs2.writeln('typedef struct $sh_typ $sh_typ;') + // println('registered shared type $sh_typ') + g.shareds << t_idx + return sh_typ +} + +fn (mut g Gen) register_thread_array_wait_call(eltyp string) string { + is_void := eltyp == 'void' + thread_typ := if is_void { '__v_thread' } else { '__v_thread_$eltyp' } + ret_typ := if is_void { 'void' } else { 'Array_$eltyp' } + thread_arr_typ := 'Array_$thread_typ' + fn_name := '${thread_arr_typ}_wait' + if fn_name !in g.waiter_fns { + g.waiter_fns << fn_name + if is_void { + g.gowrappers.writeln(' +void ${fn_name}($thread_arr_typ a) { + for (int i = 0; i < a.len; ++i) { + $thread_typ t = (($thread_typ*)a.data)[i]; + __v_thread_wait(t); + } +}') + } else { + g.gowrappers.writeln(' +$ret_typ ${fn_name}($thread_arr_typ a) { + $ret_typ res = __new_array_with_default(a.len, a.len, sizeof($eltyp), 0); + for (int i = 0; i < a.len; ++i) { + $thread_typ t = (($thread_typ*)a.data)[i]; + (($eltyp*)res.data)[i] = __v_thread_${eltyp}_wait(t); + } + return res; +}') + } + } + return fn_name +} + +fn (mut g Gen) register_chan_pop_optional_call(opt_el_type string, styp string) { + if opt_el_type !in g.chan_pop_optionals { + g.chan_pop_optionals << opt_el_type + g.channel_definitions.writeln(' +static inline $opt_el_type __Option_${styp}_popval($styp ch) { + $opt_el_type _tmp = {0}; + if (sync__Channel_try_pop_priv(ch, _tmp.data, false)) { + return ($opt_el_type){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; + } + return _tmp; +}') + } +} + +fn (mut g Gen) register_chan_push_optional_call(el_type string, styp string) { + if styp !in g.chan_push_optionals { + g.chan_push_optionals << styp + g.register_optional(ast.void_type.set_flag(.optional)) + g.channel_definitions.writeln(' +static inline Option_void __Option_${styp}_pushval($styp ch, $el_type e) { + if (sync__Channel_try_push_priv(ch, &e, false)) { + return (Option_void){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; + } + return (Option_void){0}; +}') + } +} + +// cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo) +fn (mut g Gen) cc_type(typ ast.Type, is_prefix_struct bool) string { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut styp := sym.cname + // TODO: this needs to be removed; cgen shouldn't resolve generic types (job of checker) + match mut sym.info { + ast.Struct, ast.Interface, ast.SumType { + if sym.info.is_generic { + mut sgtyps := '_T' + for gt in sym.info.generic_types { + gts := g.table.get_type_symbol(g.unwrap_generic(gt)) + sgtyps += '_$gts.cname' + } + styp += sgtyps + } + } + ast.MultiReturn { + // TODO: this doesn't belong here, but makes it working for now + mut cname := 'multi_return' + for mr_typ in sym.info.types { + mr_type_sym := g.table.get_type_symbol(g.unwrap_generic(mr_typ)) + cname += '_$mr_type_sym.cname' + } + return cname + } + else {} + } + if is_prefix_struct && styp.starts_with('C__') { + styp = styp[3..] + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if !info.is_typedef { + styp = 'struct $styp' + } + } + } + return styp +} + +[inline] +fn (g &Gen) type_sidx(t ast.Type) string { + if g.pref.build_mode == .build_module { + sym := g.table.get_type_symbol(t) + return '_v_type_idx_${sym.cname}()' + } + return '$t.idx()' +} + +// +pub fn (mut g Gen) write_typedef_types() { + for typ in g.table.type_symbols { + if typ.name in c.builtins { + continue + } + match typ.kind { + .array { + info := typ.info as ast.Array + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.kind != .placeholder { + g.type_definitions.writeln('typedef array $typ.cname;') + } + } + .array_fixed { + info := typ.info as ast.ArrayFixed + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.is_builtin() { + // .array_fixed { + styp := typ.cname + // array_fixed_char_300 => char x[300] + mut fixed := styp[12..] + len := styp.after('_') + fixed = fixed[..fixed.len - len.len - 1] + if fixed.starts_with('C__') { + fixed = fixed[3..] + } + if elem_sym.info is ast.FnType { + pos := g.out.len + g.write_fn_ptr_decl(&elem_sym.info, '') + fixed = g.out.cut_to(pos) + mut def_str := 'typedef $fixed;' + def_str = def_str.replace_once('(*)', '(*$styp[$len])') + g.type_definitions.writeln(def_str) + } else { + g.type_definitions.writeln('typedef $fixed $styp [$len];') + } + } + } + .chan { + if typ.name != 'chan' { + g.type_definitions.writeln('typedef chan $typ.cname;') + chan_inf := typ.chan_info() + chan_elem_type := chan_inf.elem_type + if !chan_elem_type.has_flag(.generic) { + el_stype := g.typ(chan_elem_type) + g.channel_definitions.writeln(' +static inline $el_stype __${typ.cname}_popval($typ.cname ch) { + $el_stype val; + sync__Channel_try_pop_priv(ch, &val, false); + return val; +}') + g.channel_definitions.writeln(' +static inline void __${typ.cname}_pushval($typ.cname ch, $el_stype val) { + sync__Channel_try_push_priv(ch, &val, false); +}') + } + } + } + .map { + g.type_definitions.writeln('typedef map $typ.cname;') + } + else { + continue + } + } + } + for typ in g.table.type_symbols { + if typ.kind == .alias && typ.name !in c.builtins { + g.write_alias_typesymbol_declaration(typ) + } + } + for typ in g.table.type_symbols { + if typ.kind == .function && typ.name !in c.builtins { + g.write_fn_typesymbol_declaration(typ) + } + } + // Generating interfaces after all the common types have been defined + // to prevent generating interface struct before definition of field types + for typ in g.table.type_symbols { + if typ.kind == .interface_ && typ.name !in c.builtins { + g.write_interface_typedef(typ) + } + } + for typ in g.table.type_symbols { + if typ.kind == .interface_ && typ.name !in c.builtins { + g.write_interface_typesymbol_declaration(typ) + } + } +} + +pub fn (mut g Gen) write_alias_typesymbol_declaration(sym ast.TypeSymbol) { + parent := unsafe { &g.table.type_symbols[sym.parent_idx] } + is_c_parent := parent.name.len > 2 && parent.name[0] == `C` && parent.name[1] == `.` + mut is_typedef := false + if parent.info is ast.Struct { + is_typedef = parent.info.is_typedef + } + mut parent_styp := parent.cname + if is_c_parent { + if !is_typedef { + parent_styp = 'struct ' + parent.cname[3..] + } else { + parent_styp = parent.cname[3..] + } + } else { + if sym.info is ast.Alias { + parent_styp = g.typ(sym.info.parent_type) + } + } + if parent_styp == 'byte' && sym.cname == 'u8' { + // TODO: remove this check; it is here just to fix V rebuilding in -cstrict mode with clang-12 + return + } + g.type_definitions.writeln('typedef $parent_styp $sym.cname;') +} + +pub fn (mut g Gen) write_interface_typedef(sym ast.TypeSymbol) { + struct_name := c_name(sym.cname) + g.typedefs.writeln('typedef struct $struct_name $struct_name;') +} + +pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { + info := sym.info as ast.Interface + if info.is_generic { + return + } + struct_name := c_name(sym.cname) + g.type_definitions.writeln('struct $struct_name {') + g.type_definitions.writeln('\tunion {') + g.type_definitions.writeln('\t\tvoid* _object;') + for variant in info.types { + vcname := g.table.get_type_symbol(variant).cname + g.type_definitions.writeln('\t\t$vcname* _$vcname;') + } + g.type_definitions.writeln('\t};') + g.type_definitions.writeln('\tint _typ;') + for field in info.fields { + styp := g.typ(field.typ) + cname := c_name(field.name) + g.type_definitions.writeln('\t$styp* $cname;') + } + g.type_definitions.writeln('};') +} + +pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { + info := sym.info as ast.FnType + func := info.func + is_fn_sig := func.name == '' + not_anon := !info.is_anon + mut has_generic_arg := false + for param in func.params { + if param.typ.has_flag(.generic) { + has_generic_arg = true + break + } + } + if !info.has_decl && (not_anon || is_fn_sig) && !func.return_type.has_flag(.generic) + && !has_generic_arg { + fn_name := sym.cname + g.type_definitions.write_string('typedef ${g.typ(func.return_type)} (*$fn_name)(') + for i, param in func.params { + g.type_definitions.write_string(g.typ(param.typ)) + if i < func.params.len - 1 { + g.type_definitions.write_string(',') + } + } + g.type_definitions.writeln(');') + } +} + +pub fn (mut g Gen) write_multi_return_types() { + g.typedefs.writeln('\n// BEGIN_multi_return_typedefs') + g.type_definitions.writeln('\n// BEGIN_multi_return_structs') + for sym in g.table.type_symbols { + if sym.kind != .multi_return { + continue + } + info := sym.mr_info() + if info.types.filter(it.has_flag(.generic)).len > 0 { + continue + } + g.typedefs.writeln('typedef struct $sym.cname $sym.cname;') + g.type_definitions.writeln('struct $sym.cname {') + for i, mr_typ in info.types { + type_name := g.typ(mr_typ) + g.type_definitions.writeln('\t$type_name arg$i;') + } + g.type_definitions.writeln('};\n') + } + g.typedefs.writeln('// END_multi_return_typedefs\n') + g.type_definitions.writeln('// END_multi_return_structs\n') +} + +pub fn (mut g Gen) write(s string) { + $if trace_gen ? { + eprintln('gen file: ${g.file.path:-30} | last_fn_c_name: ${g.last_fn_c_name:-45} | write: $s') + } + if g.indent > 0 && g.empty_line { + g.out.write_string(util.tabs(g.indent)) + } + g.out.write_string(s) + g.empty_line = false +} + +pub fn (mut g Gen) writeln(s string) { + $if trace_gen ? { + eprintln('gen file: ${g.file.path:-30} | last_fn_c_name: ${g.last_fn_c_name:-45} | writeln: $s') + } + if g.indent > 0 && g.empty_line { + g.out.write_string(util.tabs(g.indent)) + } + g.out.writeln(s) + g.empty_line = true +} + +pub fn (mut g Gen) new_tmp_var() string { + g.tmp_count++ + return '_t$g.tmp_count' +} + +pub fn (mut g Gen) new_global_tmp_var() string { + g.global_tmp_count++ + return '_t$g.global_tmp_count' +} + +pub fn (mut g Gen) new_tmp_declaration_name() string { + g.tmp_count_declarations++ + return '_d$g.tmp_count_declarations' +} + +pub fn (mut g Gen) current_tmp_var() string { + return '_t$g.tmp_count' +} + +/* +pub fn (mut g Gen) new_tmp_var2() string { + g.tmp_count2++ + return '_tt$g.tmp_count2' +} +*/ +pub fn (mut g Gen) reset_tmp_count() { + g.tmp_count = 0 +} + +fn (mut g Gen) decrement_inside_ternary() { + key := g.inside_ternary.str() + for name in g.ternary_level_names[key] { + g.ternary_names.delete(name) + } + g.ternary_level_names.delete(key) + g.inside_ternary-- +} + +fn (mut g Gen) stmts(stmts []ast.Stmt) { + g.stmts_with_tmp_var(stmts, '') +} + +fn is_noreturn_callexpr(expr ast.Expr) bool { + if expr is ast.CallExpr { + return expr.is_noreturn + } + return false +} + +// tmp_var is used in `if` expressions only +fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) { + g.indent++ + if g.inside_ternary > 0 { + g.write('(') + } + for i, stmt in stmts { + if i == stmts.len - 1 && tmp_var != '' { + // Handle if expressions, set the value of the last expression to the temp var. + if g.inside_if_optional { + g.stmt_path_pos << g.out.len + g.skip_stmt_pos = true + if stmt is ast.ExprStmt { + if stmt.typ == ast.error_type_idx || stmt.expr is ast.None { + g.writeln('${tmp_var}.state = 2;') + g.write('${tmp_var}.err = ') + g.expr(stmt.expr) + g.writeln(';') + } else { + mut styp := g.base_type(stmt.typ) + $if tinyc && x32 && windows { + if stmt.typ == ast.int_literal_type { + styp = 'int' + } else if stmt.typ == ast.float_literal_type { + styp = 'f64' + } + } + g.write('opt_ok(&($styp[]) { ') + g.stmt(stmt) + g.writeln(' }, (Option*)(&$tmp_var), sizeof($styp));') + } + } + } else { + g.stmt_path_pos << g.out.len + g.skip_stmt_pos = true + mut is_noreturn := false + if stmt is ast.ExprStmt { + is_noreturn = is_noreturn_callexpr(stmt.expr) + } + if !is_noreturn { + g.write('$tmp_var = ') + } + g.stmt(stmt) + if !g.out.last_n(2).contains(';') { + g.writeln(';') + } + } + } else { + g.stmt(stmt) + if g.inside_if_optional && stmt is ast.ExprStmt { + g.writeln(';') + } + } + g.skip_stmt_pos = false + if g.inside_ternary > 0 && i < stmts.len - 1 { + g.write(',') + } + } + g.indent-- + if g.inside_ternary > 0 { + g.write('') + g.write(')') + } + if g.is_autofree && !g.inside_vweb_tmpl && stmts.len > 0 { + // use the first stmt to get the scope + stmt := stmts[0] + // stmt := stmts[stmts.len-1] + if stmt !is ast.FnDecl && g.inside_ternary == 0 { + // g.trace_autofree('// autofree scope') + // g.trace_autofree('// autofree_scope_vars($stmt.pos.pos) | ${typeof(stmt)}') + // go back 1 position is important so we dont get the + // internal scope of for loops and possibly other nodes + // g.autofree_scope_vars(stmt.pos.pos - 1) + mut stmt_pos := stmt.pos + if stmt_pos.pos == 0 { + // Do not autofree if the position is 0, since the correct scope won't be found. + // Report a bug, since position shouldn't be 0 for most nodes. + if stmt is ast.Module { + return + } + if stmt is ast.ExprStmt { + // For some reason ExprStmt.pos is 0 when ExprStmt.expr is comp if expr + // Extract the pos. TODO figure out why and fix. + stmt_pos = stmt.expr.position() + } + if stmt_pos.pos == 0 { + $if trace_autofree ? { + println('autofree: first stmt pos = 0. $stmt.type_name()') + } + return + } + } + g.autofree_scope_vars(stmt_pos.pos - 1, stmt_pos.line_nr, false) + } + } +} + +[inline] +fn (mut g Gen) write_v_source_line_info(pos token.Position) { + if g.inside_ternary == 0 && g.pref.is_vlines && g.is_vlines_enabled { + nline := pos.line_nr + 1 + lineinfo := '\n#line $nline "$g.vlines_path"' + g.writeln(lineinfo) + } +} + +fn (mut g Gen) stmt(node ast.Stmt) { + if !g.skip_stmt_pos { + g.stmt_path_pos << g.out.len + } + defer { + } + // println('g.stmt()') + // g.writeln('//// stmt start') + match node { + ast.EmptyStmt {} + ast.AsmStmt { + g.write_v_source_line_info(node.pos) + g.gen_asm_stmt(node) + } + ast.AssertStmt { + g.write_v_source_line_info(node.pos) + g.gen_assert_stmt(node) + } + ast.AssignStmt { + g.write_v_source_line_info(node.pos) + g.gen_assign_stmt(node) + } + ast.Block { + g.write_v_source_line_info(node.pos) + if node.is_unsafe { + g.writeln('{ // Unsafe block') + } else { + g.writeln('{') + } + g.stmts(node.stmts) + g.writeln('}') + } + ast.BranchStmt { + g.write_v_source_line_info(node.pos) + + if node.label != '' { + x := g.labeled_loops[node.label] or { + panic('$node.label doesn\'t exist $g.file.path, $node.pos') + } + match x { + ast.ForCStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForInStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + else {} + } + + if node.kind == .key_break { + g.writeln('goto ${node.label}__break;') + } else { + // assert node.kind == .key_continue + g.writeln('goto ${node.label}__continue;') + } + } else { + inner_loop := g.inner_loop + match inner_loop { + ast.ForCStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForInStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + else {} + } + // continue or break + if g.is_autofree && !g.is_builtin_mod { + g.trace_autofree('// free before continue/break') + g.autofree_scope_vars_stop(node.pos.pos - 1, node.pos.line_nr, true, + g.branch_parent_pos) + } + g.writeln('$node.kind;') + } + } + ast.ConstDecl { + g.write_v_source_line_info(node.pos) + // if g.pref.build_mode != .build_module { + g.const_decl(node) + // } + } + ast.CompFor { + g.comp_for(node) + } + ast.DeferStmt { + mut defer_stmt := node + defer_stmt.ifdef = g.defer_ifdef + g.writeln('${g.defer_flag_var(defer_stmt)} = true;') + g.defer_stmts << defer_stmt + } + ast.EnumDecl { + enum_name := util.no_dots(node.name) + is_flag := node.is_flag + g.enum_typedefs.writeln('typedef enum {') + mut cur_enum_expr := '' + mut cur_enum_offset := 0 + for i, field in node.fields { + g.enum_typedefs.write_string('\t${enum_name}__$field.name') + if field.has_expr { + g.enum_typedefs.write_string(' = ') + expr_str := g.expr_string(field.expr) + g.enum_typedefs.write_string(expr_str) + cur_enum_expr = expr_str + cur_enum_offset = 0 + } else if is_flag { + g.enum_typedefs.write_string(' = ') + cur_enum_expr = '1 << $i' + g.enum_typedefs.write_string((1 << i).str()) + cur_enum_offset = 0 + } + cur_value := if cur_enum_offset > 0 { + '$cur_enum_expr+$cur_enum_offset' + } else { + cur_enum_expr + } + g.enum_typedefs.writeln(', // $cur_value') + cur_enum_offset++ + } + g.enum_typedefs.writeln('} $enum_name;\n') + } + ast.ExprStmt { + g.write_v_source_line_info(node.pos) + // af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod + // if af { + // g.autofree_call_pregen(node.expr as ast.CallExpr) + // } + old_is_void_expr_stmt := g.is_void_expr_stmt + g.is_void_expr_stmt = !node.is_expr + if node.typ != ast.void_type && g.expected_cast_type != 0 { + g.expr_with_cast(node.expr, node.typ, g.expected_cast_type) + } else { + g.expr(node.expr) + } + g.is_void_expr_stmt = old_is_void_expr_stmt + // if af { + // g.autofree_call_postgen() + // } + if g.inside_ternary == 0 && !g.inside_if_optional && !node.is_expr + && node.expr !is ast.IfExpr { + g.writeln(';') + } + } + ast.FnDecl { + g.process_fn_decl(node) + } + ast.ForCStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_c_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.ForInStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_in_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.ForStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.GlobalDecl { + g.global_decl(node) + } + ast.GotoLabel { + g.writeln('$node.name: {}') + } + ast.GotoStmt { + g.write_v_source_line_info(node.pos) + g.writeln('goto $node.name;') + } + ast.HashStmt { + mut ct_condition := '' + if node.ct_conds.len > 0 { + ct_condition_start := g.out.len + for idx, ct_expr in node.ct_conds { + g.comp_if_cond(ct_expr, false) + if idx < node.ct_conds.len - 1 { + g.write(' && ') + } + } + ct_condition = g.out.cut_to(ct_condition_start).trim_space() + // dump(node) + // dump(ct_condition) + } + // #include etc + if node.kind == 'include' { + mut missing_message := 'Header file $node.main, needed for module `$node.mod` was not found.' + if node.msg != '' { + missing_message += ' ${node.msg}.' + } else { + missing_message += ' Please install the corresponding development headers.' + } + mut guarded_include := get_guarded_include_text(node.main, missing_message) + if node.main == '' { + // fails with musl-gcc and msvc; but an unguarded include works: + guarded_include = '#include $node.main' + } + if node.main.contains('.m') { + g.definitions.writeln('\n') + if ct_condition.len > 0 { + g.definitions.writeln('#if $ct_condition') + } + // Objective C code import, include it after V types, so that e.g. `string` is + // available there + g.definitions.writeln('// added by module `$node.mod`') + g.definitions.writeln(guarded_include) + if ct_condition.len > 0 { + g.definitions.writeln('#endif // \$if $ct_condition') + } + g.definitions.writeln('\n') + } else { + g.includes.writeln('\n') + if ct_condition.len > 0 { + g.includes.writeln('#if $ct_condition') + } + g.includes.writeln('// added by module `$node.mod`') + g.includes.writeln(guarded_include) + if ct_condition.len > 0 { + g.includes.writeln('#endif // \$if $ct_condition') + } + g.includes.writeln('\n') + } + } else if node.kind == 'define' { + if ct_condition.len > 0 { + g.includes.writeln('#if $ct_condition') + } + g.includes.writeln('// defined by module `$node.mod`') + g.includes.writeln('#define $node.main') + if ct_condition.len > 0 { + g.includes.writeln('#endif // \$if $ct_condition') + } + } + } + ast.Import {} + ast.InterfaceDecl { + // definitions are sorted and added in write_types + ts := g.table.get_type_symbol(node.typ) + if !(ts.info as ast.Interface).is_generic { + for method in node.methods { + if method.return_type.has_flag(.optional) { + // Register an optional if it's not registered yet + g.register_optional(method.return_type) + } + } + } + } + ast.Module { + // g.is_builtin_mod = node.name == 'builtin' + g.is_builtin_mod = node.name in ['builtin', 'os', 'strconv', 'strings', 'gg'] + // g.cur_mod = node.name + g.cur_mod = node + } + ast.NodeError {} + ast.Return { + g.return_stmt(node) + } + ast.SqlStmt { + g.sql_stmt(node) + } + ast.StructDecl { + name := if node.language == .c { util.no_dots(node.name) } else { c_name(node.name) } + // TODO For some reason, build fails with autofree with this line + // as it's only informative, comment it for now + // g.gen_attrs(node.attrs) + // g.writeln('typedef struct {') + // for field in it.fields { + // field_type_sym := g.table.get_type_symbol(field.typ) + // g.writeln('\t$field_type_sym.name $field.name;') + // } + // g.writeln('} $name;') + if node.language == .c { + return + } + if node.is_union { + g.typedefs.writeln('typedef union $name $name;') + } else { + /* + attrs := if node.attrs.contains('packed') { + '__attribute__((__packed__))' + } else { + '' + } + */ + g.typedefs.writeln('typedef struct $name $name;') + } + } + ast.TypeDecl { + if !g.pref.skip_unused { + g.writeln('// TypeDecl') + } + } + } + if !g.skip_stmt_pos { // && g.stmt_path_pos.len > 0 { + g.stmt_path_pos.delete_last() + } + // If we have temporary string exprs to free after this statement, do it. e.g.: + // `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);` + if g.is_autofree { + // if node is ast.ExprStmt {&& node.expr is ast.CallExpr { + if node !is ast.FnDecl { + // p := node.position() + // g.autofree_call_postgen(p.pos) + } + } +} + +fn (mut g Gen) write_defer_stmts() { + for i := g.defer_stmts.len - 1; i >= 0; i-- { + defer_stmt := g.defer_stmts[i] + g.writeln('// Defer begin') + g.writeln('if (${g.defer_flag_var(defer_stmt)}) {') + g.indent++ + if defer_stmt.ifdef.len > 0 { + g.writeln(defer_stmt.ifdef) + g.stmts(defer_stmt.stmts) + g.writeln('') + g.writeln('#endif') + } else { + g.indent-- + g.stmts(defer_stmt.stmts) + g.indent++ + } + g.indent-- + g.writeln('}') + g.writeln('// Defer end') + } +} + +fn (mut g Gen) for_c_stmt(node ast.ForCStmt) { + if node.is_multi { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.writeln('{') + g.indent++ + if node.has_init { + g.stmt(node.init) + } + g.writeln('bool _is_first = true;') + g.writeln('while (true) {') + g.writeln('\tif (_is_first) {') + g.writeln('\t\t_is_first = false;') + g.writeln('\t} else {') + if node.has_inc { + g.indent++ + g.stmt(node.inc) + g.writeln(';') + g.indent-- + } + g.writeln('}') + if node.has_cond { + g.write('if (!(') + g.expr(node.cond) + g.writeln(')) break;') + } + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('${node.label}__continue: {}') + } + g.writeln('}') + g.indent-- + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } + } else { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.write('for (') + if !node.has_init { + g.write('; ') + } else { + g.stmt(node.init) + // Remove excess return and add space + if g.out.last_n(1) == '\n' { + g.out.go_back(1) + g.empty_line = false + g.write(' ') + } + } + if node.has_cond { + g.expr(node.cond) + } + g.write('; ') + if node.has_inc { + g.stmt(node.inc) + } + g.writeln(') {') + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('${node.label}__continue: {}') + } + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } + } +} + +fn (mut g Gen) for_stmt(node ast.ForStmt) { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.writeln('for (;;) {') + if !node.is_inf { + g.indent++ + g.stmt_path_pos << g.out.len + g.write('if (!(') + g.expr(node.cond) + g.writeln(')) break;') + g.indent-- + } + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('\t${node.label}__continue: {}') + } + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } +} + +fn (mut g Gen) for_in_stmt(node ast.ForInStmt) { + if node.label.len > 0 { + g.writeln('\t$node.label: {}') + } + if node.is_range { + // `for x in 1..10 {` + i := if node.val_var == '_' { g.new_tmp_var() } else { c_name(node.val_var) } + val_typ := g.table.mktyp(node.val_type) + g.write('for (${g.typ(val_typ)} $i = ') + g.expr(node.cond) + g.write('; $i < ') + g.expr(node.high) + g.writeln('; ++$i) {') + } else if node.kind == .array { + // `for num in nums {` + // g.writeln('// FOR IN array') + styp := g.typ(node.val_type) + val_sym := g.table.get_type_symbol(node.val_type) + mut cond_var := '' + if node.cond is ast.Ident || node.cond is ast.SelectorExpr { + cond_var = g.expr_string(node.cond) + } else { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } + i := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + field_accessor := if node.cond_type.is_ptr() { '->' } else { '.' } + share_accessor := if node.cond_type.share() == .shared_t { 'val.' } else { '' } + op_field := field_accessor + share_accessor + g.empty_line = true + g.writeln('for (int $i = 0; $i < $cond_var${op_field}len; ++$i) {') + if node.val_var != '_' { + if val_sym.kind == .function { + g.write('\t') + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + g.writeln(' = ((voidptr*)$cond_var${op_field}data)[$i];') + } else if val_sym.kind == .array_fixed && !node.val_is_mut { + right := '(($styp*)$cond_var${op_field}data)[$i]' + g.writeln('\t$styp ${c_name(node.val_var)};') + g.writeln('\tmemcpy(*($styp*)${c_name(node.val_var)}, (byte*)$right, sizeof($styp));') + } else { + // If val is mutable (pointer behind the scenes), we need to generate + // `int* val = ((int*)arr.data) + i;` + // instead of + // `int* val = ((int**)arr.data)[i];` + // right := if node.val_is_mut { styp } else { styp + '*' } + right := if node.val_is_mut { + '(($styp)$cond_var${op_field}data) + $i' + } else { + '(($styp*)$cond_var${op_field}data)[$i]' + } + g.writeln('\t$styp ${c_name(node.val_var)} = $right;') + } + } + } else if node.kind == .array_fixed { + mut cond_var := '' + cond_type_is_ptr := node.cond_type.is_ptr() + cond_is_literal := node.cond is ast.ArrayInit + if cond_is_literal { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } else if cond_type_is_ptr { + cond_var = g.new_tmp_var() + cond_var_type := g.typ(node.cond_type).trim('*') + if !node.cond.is_lvalue() { + g.write('$cond_var_type *$cond_var = (($cond_var_type)') + } else { + g.write('$cond_var_type *$cond_var = (') + } + g.expr(node.cond) + g.writeln(');') + } else { + cond_var = g.expr_string(node.cond) + } + idx := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + cond_sym := g.table.get_type_symbol(node.cond_type) + info := cond_sym.info as ast.ArrayFixed + g.writeln('for (int $idx = 0; $idx != $info.size; ++$idx) {') + if node.val_var != '_' { + val_sym := g.table.get_type_symbol(node.val_type) + is_fixed_array := val_sym.kind == .array_fixed && !node.val_is_mut + if val_sym.kind == .function { + g.write('\t') + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + } else if is_fixed_array { + styp := g.typ(node.val_type) + g.writeln('\t$styp ${c_name(node.val_var)};') + g.writeln('\tmemcpy(*($styp*)${c_name(node.val_var)}, (byte*)$cond_var[$idx], sizeof($styp));') + } else { + styp := g.typ(node.val_type) + g.write('\t$styp ${c_name(node.val_var)}') + } + if !is_fixed_array { + addr := if node.val_is_mut { '&' } else { '' } + if cond_type_is_ptr { + g.writeln(' = ${addr}(*$cond_var)[$idx];') + } else if cond_is_literal { + g.writeln(' = $addr$cond_var[$idx];') + } else { + g.write(' = $addr') + g.expr(node.cond) + g.writeln('[$idx];') + } + } + } + } else if node.kind == .map { + // `for key, val in map { + // g.writeln('// FOR IN map') + mut cond_var := '' + if node.cond is ast.Ident { + cond_var = g.expr_string(node.cond) + } else { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } + mut arw_or_pt := if node.cond_type.is_ptr() { '->' } else { '.' } + if node.cond_type.has_flag(.shared_f) { + arw_or_pt = '->val.' + } + idx := g.new_tmp_var() + map_len := g.new_tmp_var() + g.empty_line = true + g.writeln('int $map_len = $cond_var${arw_or_pt}key_values.len;') + g.writeln('for (int $idx = 0; $idx < $map_len; ++$idx ) {') + // TODO: don't have this check when the map has no deleted elements + g.indent++ + diff := g.new_tmp_var() + g.writeln('int $diff = $cond_var${arw_or_pt}key_values.len - $map_len;') + g.writeln('$map_len = $cond_var${arw_or_pt}key_values.len;') + // TODO: optimize this + g.writeln('if ($diff < 0) {') + g.writeln('\t$idx = -1;') + g.writeln('\tcontinue;') + g.writeln('}') + g.writeln('if (!DenseArray_has_index(&$cond_var${arw_or_pt}key_values, $idx)) {continue;}') + if node.key_var != '_' { + key_styp := g.typ(node.key_type) + key := c_name(node.key_var) + g.writeln('$key_styp $key = /*key*/ *($key_styp*)DenseArray_key(&$cond_var${arw_or_pt}key_values, $idx);') + // TODO: analyze whether node.key_type has a .clone() method and call .clone() for all types: + if node.key_type == ast.string_type { + g.writeln('$key = string_clone($key);') + } + } + if node.val_var != '_' { + val_sym := g.table.get_type_symbol(node.val_type) + if val_sym.kind == .function { + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + g.write(' = (*(voidptr*)') + g.writeln('DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx));') + } else if val_sym.kind == .array_fixed && !node.val_is_mut { + val_styp := g.typ(node.val_type) + g.writeln('$val_styp ${c_name(node.val_var)};') + g.writeln('memcpy(*($val_styp*)${c_name(node.val_var)}, (byte*)DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx), sizeof($val_styp));') + } else { + val_styp := g.typ(node.val_type) + if node.val_type.is_ptr() { + g.write('$val_styp ${c_name(node.val_var)} = &(*($val_styp)') + } else { + g.write('$val_styp ${c_name(node.val_var)} = (*($val_styp*)') + } + g.writeln('DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx));') + } + } + g.indent-- + } else if node.kind == .string { + cond := if node.cond is ast.StringLiteral || node.cond is ast.StringInterLiteral { + ast.Expr(g.new_ctemp_var_then_gen(node.cond, ast.string_type)) + } else { + node.cond + } + i := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + g.write('for (int $i = 0; $i < ') + g.expr(cond) + g.writeln('.len; ++$i) {') + if node.val_var != '_' { + g.write('\tbyte ${c_name(node.val_var)} = ') + g.expr(cond) + g.writeln('.str[$i];') + } + } else if node.kind == .struct_ { + cond_type_sym := g.table.get_type_symbol(node.cond_type) + next_fn := cond_type_sym.find_method('next') or { + verror('`next` method not found') + return + } + ret_typ := next_fn.return_type + t_expr := g.new_tmp_var() + g.write('${g.typ(node.cond_type)} $t_expr = ') + g.expr(node.cond) + g.writeln(';') + g.writeln('while (1) {') + t_var := g.new_tmp_var() + receiver_typ := next_fn.params[0].typ + receiver_styp := g.typ(receiver_typ) + fn_name := receiver_styp.replace_each(['*', '', '.', '__']) + '_next' + g.write('\t${g.typ(ret_typ)} $t_var = ${fn_name}(') + if !node.cond_type.is_ptr() && receiver_typ.is_ptr() { + g.write('&') + } + g.writeln('$t_expr);') + g.writeln('\tif (${t_var}.state != 0) break;') + val := if node.val_var in ['', '_'] { g.new_tmp_var() } else { node.val_var } + val_styp := g.typ(node.val_type) + g.writeln('\t$val_styp $val = *($val_styp*)${t_var}.data;') + } else { + typ_str := g.table.type_to_str(node.cond_type) + g.error('for in: unhandled symbol `$node.cond` of type `$typ_str`', node.pos) + } + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('\t${node.label}__continue: {}') + } + + if node.kind == .map { + // diff := g.new_tmp_var() + // g.writeln('int $diff = $cond_var${arw_or_pt}key_values.len - $map_len;') + // g.writeln('if ($diff < 0) {') + // g.writeln('\t$idx = -1;') + // g.writeln('\t$map_len = $cond_var${arw_or_pt}key_values.len;') + // g.writeln('}') + } + + g.writeln('}') + if node.label.len > 0 { + g.writeln('\t${node.label}__break: {}') + } +} + +fn (mut g Gen) write_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) { + got, exp := got_.idx(), exp_.idx() + i := got | (exp << 16) + if got == exp || g.sumtype_definitions[i] { + return + } + g.sumtype_definitions[i] = true + got_sym := g.table.get_type_symbol(got) + exp_sym := g.table.get_type_symbol(exp) + mut sb := strings.new_builder(128) + got_cname, exp_cname := got_sym.cname, exp_sym.cname + sb.writeln('static inline $exp_cname ${got_cname}_to_sumtype_${exp_cname}($got_cname* x) {') + sb.writeln('\t$got_cname* ptr = memdup(x, sizeof($got_cname));') + for embed_hierarchy in g.table.get_embeds(got_sym) { + // last embed in the hierarchy + mut embed_cname := '' + mut embed_name := '' + mut accessor := '&x->' + for j, embed in embed_hierarchy { + embed_sym := g.table.get_type_symbol(embed) + embed_cname = embed_sym.cname + embed_name = embed_sym.embed_name() + if j > 0 { + accessor += '.' + } + accessor += embed_name + } + // if the variable is not used, the C compiler will optimize it away + sb.writeln('\t$embed_cname* ${embed_name}_ptr = memdup($accessor, sizeof($embed_cname));') + } + sb.write_string('\treturn ($exp_cname){ ._$got_cname = ptr, ._typ = ${g.type_sidx(got)}') + for field in (exp_sym.info as ast.SumType).fields { + mut ptr := 'ptr' + mut type_cname := got_cname + _, embed_types := g.table.find_field_from_embeds_recursive(got_sym, field.name) or { + ast.StructField{}, []ast.Type{} + } + if embed_types.len > 0 { + embed_sym := g.table.get_type_symbol(embed_types.last()) + ptr = '${embed_sym.embed_name()}_ptr' + type_cname = embed_sym.cname + } + field_styp := g.typ(field.typ) + if got_sym.kind in [.sum_type, .interface_] { + // the field is already a wrapped pointer; we shouldn't wrap it once again + sb.write_string(', .$field.name = ptr->$field.name') + } else { + sb.write_string(', .$field.name = ($field_styp*)((char*)$ptr + __offsetof_ptr($ptr, $type_cname, $field.name))') + } + } + sb.writeln('};\n}') + g.auto_fn_definitions << sb.str() +} + +fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp_is_ptr bool, exp_styp string, got_is_ptr bool, got_styp string) { + mut rparen_n := 1 + if exp_is_ptr { + g.write('HEAP($exp_styp, ') + rparen_n++ + } + g.write('${fname}(') + if !got_is_ptr { + if !expr.is_lvalue() + || (expr is ast.Ident && (expr as ast.Ident).obj.is_simple_define_const()) { + g.write('ADDR($got_styp, (') + rparen_n += 2 + } else { + g.write('&') + } + } + g.expr(expr) + g.write(')'.repeat(rparen_n)) +} + +// use instead of expr() when you need to cast to a different type +fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) { + got_type := g.table.mktyp(got_type_raw) + exp_sym := g.table.get_type_symbol(expected_type) + expected_is_ptr := expected_type.is_ptr() + got_is_ptr := got_type.is_ptr() + got_sym := g.table.get_type_symbol(got_type) + // allow using the new Error struct as a string, to avoid a breaking change + // TODO: temporary to allow people to migrate their code; remove soon + if got_type == ast.error_type_idx && expected_type == ast.string_type_idx { + g.write('(*(') + g.expr(expr) + g.write('.msg))') + return + } + if got_sym.kind == .none_ && exp_sym.name == 'IError' { + g.expr(expr) + return + } + if exp_sym.info is ast.Interface && got_type_raw.idx() != expected_type.idx() + && !expected_type.has_flag(.optional) { + if expr is ast.StructInit && !got_type.is_ptr() { + g.inside_cast_in_heap++ + got_styp := g.cc_type(got_type.to_ptr(), true) + // TODO: why does cc_type even add this in the first place? + exp_styp := exp_sym.cname + mut fname := 'I_${got_styp}_to_Interface_$exp_styp' + if exp_sym.info.is_generic { + fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false) + } + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, true, + got_styp) + g.inside_cast_in_heap-- + } else { + got_styp := g.cc_type(got_type, true) + exp_styp := exp_sym.cname + mut fname := '/*$exp_sym*/I_${got_styp}_to_Interface_$exp_styp' + if exp_sym.info.is_generic { + fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false) + } + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, got_is_ptr, + got_styp) + } + return + } + // cast to sum type + exp_styp := g.typ(expected_type) + got_styp := g.typ(got_type) + if expected_type != ast.void_type { + expected_deref_type := if expected_is_ptr { expected_type.deref() } else { expected_type } + got_deref_type := if got_is_ptr { got_type.deref() } else { got_type } + if g.table.sumtype_has_variant(expected_deref_type, got_deref_type) { + mut is_already_sum_type := false + scope := g.file.scope.innermost(expr.position().pos) + if expr is ast.Ident { + if v := scope.find_var(expr.name) { + if v.smartcasts.len > 0 { + is_already_sum_type = true + } + } + } else if expr is ast.SelectorExpr { + if _ := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) { + is_already_sum_type = true + } + } + if is_already_sum_type { + // Don't create a new sum type wrapper if there is already one + g.prevent_sum_type_unwrapping_once = true + g.expr(expr) + } else { + g.write_sumtype_casting_fn(got_type, expected_type) + fname := '${got_sym.cname}_to_sumtype_$exp_sym.cname' + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_sym.cname, + got_is_ptr, got_styp) + } + return + } + } + // Generic dereferencing logic + neither_void := ast.voidptr_type !in [got_type, expected_type] + to_shared := expected_type.has_flag(.shared_f) && !got_type_raw.has_flag(.shared_f) + && !expected_type.has_flag(.optional) + // from_shared := got_type_raw.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) + if to_shared { + shared_styp := exp_styp[0..exp_styp.len - 1] // `shared` implies ptr, so eat one `*` + if got_type_raw.is_ptr() { + g.error('cannot convert reference to `shared`', expr.position()) + } + if exp_sym.kind == .array { + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if exp_sym.kind == .map { + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else { + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =') + } + old_is_shared := g.is_shared + g.is_shared = false + g.expr(expr) + g.is_shared = old_is_shared + g.writeln('}, sizeof($shared_styp))') + return + } + if got_is_ptr && !expected_is_ptr && neither_void + && exp_sym.kind !in [.interface_, .placeholder] && expr !is ast.InfixExpr { + got_deref_type := got_type.deref() + deref_sym := g.table.get_type_symbol(got_deref_type) + deref_will_match := expected_type in [got_type, got_deref_type, deref_sym.parent_idx] + got_is_opt := got_type.has_flag(.optional) + if deref_will_match || got_is_opt { + g.write('*') + } + } + if expected_type.has_flag(.optional) && expr is ast.None { + g.gen_optional_error(expected_type, expr) + return + } + if expr is ast.IntegerLiteral { + if expected_type in [ast.u64_type, ast.u32_type, ast.u16_type] && expr.val[0] != `-` { + g.expr(expr) + g.write('U') + return + } + } + if exp_sym.kind == .function { + g.write('(voidptr)') + } + // no cast + g.expr(expr) +} + +// cestring returns a V string, properly escaped for embeddeding in a C string literal. +fn cestring(s string) string { + return s.replace('\\', '\\\\').replace('"', "'") +} + +// ctoslit returns a '_SLIT("$s")' call, where s is properly escaped. +fn ctoslit(s string) string { + return '_SLIT("' + cestring(s) + '")' +} + +fn (mut g Gen) gen_attrs(attrs []ast.Attr) { + if g.pref.skip_unused { + return + } + for attr in attrs { + g.writeln('// Attr: [$attr.name]') + } +} + +fn (mut g Gen) gen_asm_stmt(stmt ast.AsmStmt) { + g.write('__asm__') + if stmt.is_volatile { + g.write(' volatile') + } + if stmt.is_goto { + g.write(' goto') + } + g.writeln(' (') + g.indent++ + for template_tmp in stmt.templates { + mut template := template_tmp + g.write('"') + if template.is_directive { + g.write('.') + } + g.write(template.name) + if template.is_label { + g.write(':') + } else { + g.write(' ') + } + // swap destionation and operands for att syntax + if template.args.len != 0 && !template.is_directive { + template.args.prepend(template.args[template.args.len - 1]) + template.args.delete(template.args.len - 1) + } + + for i, arg in template.args { + if stmt.arch == .amd64 && (template.name == 'call' || template.name[0] == `j`) + && arg is ast.AsmRegister { + g.write('*') // indirect branching + } + + g.asm_arg(arg, stmt) + if i + 1 < template.args.len { + g.write(', ') + } + } + + if !template.is_label { + g.write(';') + } + g.writeln('"') + } + + if stmt.output.len != 0 || stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + g.gen_asm_ios(stmt.output) + if stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + g.gen_asm_ios(stmt.input) + if stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + for i, clob in stmt.clobbered { + g.write('"') + g.write(clob.reg.name) + g.write('"') + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + if stmt.is_goto { + g.write(': ') + } + for i, label in stmt.global_labels { + g.write(label) + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + g.indent-- + g.writeln(');') +} + +fn (mut g Gen) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt) { + match arg { + ast.AsmAlias { + name := arg.name + if name in stmt.local_labels || name in stmt.global_labels + || name in g.file.global_labels || stmt.is_basic + || (name !in stmt.input.map(it.alias) && name !in stmt.output.map(it.alias)) { + asm_formatted_name := if name in stmt.global_labels { '%l[$name]' } else { name } + g.write(asm_formatted_name) + } else { + g.write('%[$name]') + } + } + ast.CharLiteral { + g.write("'$arg.val'") + } + ast.IntegerLiteral, ast.FloatLiteral { + g.write('\$$arg.val') + } + ast.BoolLiteral { + g.write('\$$arg.val.str()') + } + ast.AsmRegister { + if !stmt.is_basic { + g.write('%') // escape percent with percent in extended assembly + } + g.write('%$arg.name') + } + ast.AsmAddressing { + base := arg.base + index := arg.index + displacement := arg.displacement + scale := arg.scale + match arg.mode { + .base { + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .displacement { + g.asm_arg(displacement, stmt) + g.write('()') + } + .base_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .index_times_scale_plus_displacement { + if displacement is ast.AsmDisp { + g.asm_arg(displacement, stmt) + g.write('(, ') + } else if displacement is ast.AsmRegister { + g.write('(') + g.asm_arg(displacement, stmt) + g.write(',') + } else { + panic('unexpected $displacement.type_name()') + } + g.asm_arg(index, stmt) + g.write(',$scale)') + } + .base_plus_index_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',1)') + } + .base_plus_index_times_scale_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',$scale)') + } + .rip_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .invalid { + g.error('invalid addressing mode', arg.pos) + } + } + } + ast.AsmDisp { + g.write(arg.val) + } + string { + g.write(arg) + } + } +} + +fn (mut g Gen) gen_asm_ios(ios []ast.AsmIO) { + for i, io in ios { + if io.alias != '' { + g.write('[$io.alias] ') + } + g.write('"$io.constraint" (') + g.expr(io.expr) + g.write(')') + if i + 1 < ios.len { + g.writeln(',') + } else { + g.writeln('') + } + } +} + +fn cnewlines(s string) string { + return s.replace('\n', r'\n') +} + +fn (mut g Gen) write_fn_ptr_decl(func &ast.FnType, ptr_name string) { + ret_styp := g.typ(func.func.return_type) + g.write('$ret_styp (*$ptr_name) (') + arg_len := func.func.params.len + for i, arg in func.func.params { + arg_styp := g.typ(arg.typ) + g.write('$arg_styp $arg.name') + if i < arg_len - 1 { + g.write(', ') + } + } + g.write(')') +} + +// TODO this function is scary. Simplify/split up. +fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { + if assign_stmt.is_static { + g.write('static ') + } + mut return_type := ast.void_type + is_decl := assign_stmt.op == .decl_assign + g.assign_op = assign_stmt.op + op := if is_decl { token.Kind.assign } else { assign_stmt.op } + right_expr := assign_stmt.right[0] + match right_expr { + ast.CallExpr { return_type = right_expr.return_type } + ast.LockExpr { return_type = right_expr.typ } + ast.MatchExpr { return_type = right_expr.return_type } + ast.IfExpr { return_type = right_expr.typ } + else {} + } + // Free the old value assigned to this string var (only if it's `str = [new value]` + // or `x.str = [new value]` ) + mut af := g.is_autofree && !g.is_builtin_mod && assign_stmt.op == .assign + && assign_stmt.left_types.len == 1 + && (assign_stmt.left[0] is ast.Ident || assign_stmt.left[0] is ast.SelectorExpr) + // assign_stmt.left_types[0] in [ast.string_type, ast.array_type] && + mut sref_name := '' + mut type_to_free := '' + if af { + first_left_type := assign_stmt.left_types[0] + first_left_sym := g.table.get_type_symbol(assign_stmt.left_types[0]) + if first_left_type == ast.string_type || first_left_sym.kind == .array { + type_to_free = if first_left_type == ast.string_type { 'string' } else { 'array' } + mut ok := true + left0 := assign_stmt.left[0] + if left0 is ast.Ident { + if left0.name == '_' { + ok = false + } + } + if ok { + sref_name = '_sref$assign_stmt.pos.pos' + g.write('$type_to_free $sref_name = (') // TODO we are copying the entire string here, optimize + // we can't just do `.str` since we need the extra data from the string struct + // doing `&string` is also not an option since the stack memory with the data will be overwritten + g.expr(left0) // assign_stmt.left[0]) + g.writeln('); // free $type_to_free on re-assignment2') + defer { + if af { + g.writeln('${type_to_free}_free(&$sref_name);') + } + } + } else { + af = false + } + } else { + af = false + } + } + // Autofree tmp arg vars + // first_right := assign_stmt.right[0] + // af := g.autofree && first_right is ast.CallExpr && !g.is_builtin_mod + // if af { + // g.autofree_call_pregen(first_right as ast.CallExpr) + // } + // + // + // Handle optionals. We need to declare a temp variable for them, that's why they are handled + // here, not in call_expr(). + // `pos := s.index('x') or { return }` + // ==========> + // Option_int _t190 = string_index(s, _STR("x")); // _STR() no more used!! + // if (_t190.state != 2) { + // Error err = _t190.err; + // return; + // } + // int pos = *(int*)_t190.data; + // mut tmp_opt := '' + /* + is_optional := false && g.is_autofree && (assign_stmt.op in [.decl_assign, .assign]) + && assign_stmt.left_types.len == 1 && assign_stmt.right[0] is ast.CallExpr + if is_optional { + // g.write('/* optional assignment */') + call_expr := assign_stmt.right[0] as ast.CallExpr + if call_expr.or_block.kind != .absent { + styp := g.typ(call_expr.return_type.set_flag(.optional)) + tmp_opt = g.new_tmp_var() + g.write('/*AF opt*/$styp $tmp_opt = ') + g.expr(assign_stmt.right[0]) + g.or_block(tmp_opt, call_expr.or_block, call_expr.return_type) + g.writeln('/*=============ret*/') + // if af && is_optional { + // g.autofree_call_postgen() + // } + // return + } + } + */ + // json_test failed w/o this check + if return_type != ast.void_type && return_type != 0 { + sym := g.table.get_type_symbol(return_type) + if sym.kind == .multi_return { + // multi return + // TODO Handle in if_expr + is_opt := return_type.has_flag(.optional) + mr_var_name := 'mr_$assign_stmt.pos.pos' + mr_styp := g.typ(return_type) + g.write('$mr_styp $mr_var_name = ') + g.expr(assign_stmt.right[0]) + g.writeln(';') + for i, lx in assign_stmt.left { + mut is_auto_heap := false + mut ident := ast.Ident{ + scope: 0 + } + if lx is ast.Ident { + ident = lx + if lx.kind == .blank_ident { + continue + } + if lx.obj is ast.Var { + is_auto_heap = lx.obj.is_auto_heap + } + } + styp := if ident.name in g.defer_vars { + '' + } else { + g.typ(assign_stmt.left_types[i]) + } + if assign_stmt.op == .decl_assign { + g.write('$styp ') + if is_auto_heap { + g.write('*') + } + } + if lx.is_auto_deref_var() { + g.write('*') + } + g.expr(lx) + noscan := if is_auto_heap { g.check_noscan(return_type) } else { '' } + if g.is_arraymap_set { + if is_opt { + mr_base_styp := g.base_type(return_type) + if is_auto_heap { + g.writeln('HEAP${noscan}($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i) });') + } else { + g.writeln('(*($mr_base_styp*)${mr_var_name}.data).arg$i });') + } + } else { + if is_auto_heap { + g.writeln('HEAP${noscan}($styp, ${mr_var_name}.arg$i) });') + } else { + g.writeln('${mr_var_name}.arg$i });') + } + } + } else { + if is_opt { + mr_base_styp := g.base_type(return_type) + if is_auto_heap { + g.writeln(' = HEAP${noscan}($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i);') + } else { + g.writeln(' = (*($mr_base_styp*)${mr_var_name}.data).arg$i;') + } + } else { + if is_auto_heap { + g.writeln(' = HEAP${noscan}($styp, ${mr_var_name}.arg$i);') + } else { + g.writeln(' = ${mr_var_name}.arg$i;') + } + } + } + } + if g.is_arraymap_set { + g.is_arraymap_set = false + } + return + } + } + // TODO: non idents on left (exprs) + if assign_stmt.has_cross_var { + for i, left in assign_stmt.left { + match left { + ast.Ident { + left_typ := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_typ) + if left_sym.kind == .function { + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.writeln(' = $left.name;') + } else { + styp := g.typ(left_typ) + g.writeln('$styp _var_$left.pos.pos = $left.name;') + } + } + ast.IndexExpr { + sym := g.table.get_type_symbol(left.left_type) + if sym.kind == .array { + info := sym.info as ast.Array + elem_typ := g.table.get_type_symbol(info.elem_type) + if elem_typ.kind == .function { + left_typ := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_typ) + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.write(' = *(voidptr*)array_get(') + } else { + styp := g.typ(info.elem_type) + g.write('$styp _var_$left.pos.pos = *($styp*)array_get(') + } + if left.left_type.is_ptr() { + g.write('*') + } + needs_clone := info.elem_type == ast.string_type && g.is_autofree + if needs_clone { + g.write('/*1*/string_clone(') + } + g.expr(left.left) + if needs_clone { + g.write(')') + } + g.write(', ') + g.expr(left.index) + g.writeln(');') + } else if sym.kind == .map { + info := sym.info as ast.Map + skeytyp := g.typ(info.key_type) + styp := g.typ(info.value_type) + zero := g.type_default(info.value_type) + val_typ := g.table.get_type_symbol(info.value_type) + if val_typ.kind == .function { + left_type := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_type) + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.write(' = *(voidptr*)map_get(') + } else { + g.write('$styp _var_$left.pos.pos = *($styp*)map_get(') + } + if !left.left_type.is_ptr() { + g.write('ADDR(map, ') + g.expr(left.left) + g.write(')') + } else { + g.expr(left.left) + } + g.write(', &($skeytyp[]){') + g.expr(left.index) + g.write('}') + if val_typ.kind == .function { + g.writeln(', &(voidptr[]){ $zero });') + } else { + g.writeln(', &($styp[]){ $zero });') + } + } + } + ast.SelectorExpr { + styp := g.typ(left.typ) + g.write('$styp _var_$left.pos.pos = ') + g.expr(left.expr) + mut sel := '.' + if left.expr_type.is_ptr() { + if left.expr_type.has_flag(.shared_f) { + sel = '->val.' + } else { + sel = '->' + } + } + g.writeln('$sel$left.field_name;') + } + else {} + } + } + } + // `a := 1` | `a,b := 1,2` + for i, left in assign_stmt.left { + mut is_auto_heap := false + mut var_type := assign_stmt.left_types[i] + mut val_type := assign_stmt.right_types[i] + val := assign_stmt.right[i] + mut is_call := false + mut blank_assign := false + mut ident := ast.Ident{ + scope: 0 + } + left_sym := g.table.get_type_symbol(var_type) + if left is ast.Ident { + ident = left + // id_info := ident.var_info() + // var_type = id_info.typ + blank_assign = left.kind == .blank_ident + // TODO: temporary, remove this + left_info := left.info + if left_info is ast.IdentVar { + share := left_info.share + if share == .shared_t { + var_type = var_type.set_flag(.shared_f) + } + if share == .atomic_t { + var_type = var_type.set_flag(.atomic_f) + } + } + if left.obj is ast.Var { + is_auto_heap = left.obj.is_auto_heap + } + } + styp := g.typ(var_type) + mut is_fixed_array_init := false + mut has_val := false + match val { + ast.ArrayInit { + is_fixed_array_init = val.is_fixed + has_val = val.has_val + } + ast.CallExpr { + is_call = true + return_type = val.return_type + } + // TODO: no buffer fiddling + ast.AnonFn { + if blank_assign { + g.write('{') + } + // if it's a decl assign (`:=`) or a blank assignment `_ =`/`_ :=` then generate `void (*ident) (args) =` + if (is_decl || blank_assign) && left is ast.Ident { + ret_styp := g.typ(val.decl.return_type) + g.write('$ret_styp (*$ident.name) (') + def_pos := g.definitions.len + g.fn_args(val.decl.params, voidptr(0)) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(') = ') + } else { + g.is_assign_lhs = true + g.assign_op = assign_stmt.op + g.expr(left) + g.is_assign_lhs = false + g.is_arraymap_set = false + if left is ast.IndexExpr { + sym := g.table.get_type_symbol(left.left_type) + if sym.kind in [.map, .array] { + g.expr(val) + g.writeln('});') + continue + } + } + g.write(' = ') + } + g.expr(val) + g.writeln(';') + if blank_assign { + g.write('}') + } + continue + } + else {} + } + unwrapped_val_type := g.unwrap_generic(val_type) + right_sym := g.table.get_type_symbol(unwrapped_val_type) + unaliased_right_sym := g.table.get_final_type_symbol(unwrapped_val_type) + is_fixed_array_var := unaliased_right_sym.kind == .array_fixed && val !is ast.ArrayInit + && (val is ast.Ident || val is ast.IndexExpr || val is ast.CallExpr + || (val is ast.CastExpr && (val as ast.CastExpr).expr !is ast.ArrayInit) + || val is ast.SelectorExpr) + g.is_assign_lhs = true + g.assign_op = assign_stmt.op + if val_type.has_flag(.optional) { + g.right_is_opt = true + } + if blank_assign { + if is_call { + old_is_void_expr_stmt := g.is_void_expr_stmt + g.is_void_expr_stmt = true + g.expr(val) + g.is_void_expr_stmt = old_is_void_expr_stmt + } else { + g.write('{$styp _ = ') + g.expr(val) + g.writeln(';}') + } + } else if assign_stmt.op == .assign + && (is_fixed_array_init || (right_sym.kind == .array_fixed && val is ast.Ident)) { + mut v_var := '' + arr_typ := styp.trim('*') + if is_fixed_array_init { + right := val as ast.ArrayInit + v_var = g.new_tmp_var() + g.write('$arr_typ $v_var = ') + g.expr(right) + g.writeln(';') + } else { + right := val as ast.Ident + v_var = right.name + } + pos := g.out.len + g.expr(left) + + if g.is_arraymap_set && g.arraymap_set_pos > 0 { + g.out.go_back_to(g.arraymap_set_pos) + g.write(', &$v_var)') + g.is_arraymap_set = false + g.arraymap_set_pos = 0 + } else { + g.out.go_back_to(pos) + is_var_mut := !is_decl && left.is_auto_deref_var() + addr := if is_var_mut { '' } else { '&' } + g.writeln('') + g.write('memcpy($addr') + g.expr(left) + g.writeln(', &$v_var, sizeof($arr_typ));') + } + g.is_assign_lhs = false + } else { + is_inside_ternary := g.inside_ternary != 0 + cur_line := if is_inside_ternary && is_decl { + g.register_ternary_name(ident.name) + g.empty_line = false + g.go_before_ternary() + } else { + '' + } + mut str_add := false + mut op_overloaded := false + mut op_expected_left := ast.Type(0) + mut op_expected_right := ast.Type(0) + if var_type == ast.string_type_idx && assign_stmt.op == .plus_assign { + if left is ast.IndexExpr { + // a[0] += str => `array_set(&a, 0, &(string[]) {string__plus(...))})` + g.expr(left) + g.write('string__plus(') + } else { + // str += str2 => `str = string__plus(str, str2)` + g.expr(left) + g.write(' = /*f*/string__plus(') + } + g.is_assign_lhs = false + str_add = true + } + // Assignment Operator Overloading + if ((left_sym.kind == .struct_ && right_sym.kind == .struct_) + || (left_sym.kind == .alias && right_sym.kind == .alias)) + && assign_stmt.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { + extracted_op := match assign_stmt.op { + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .mod_assign { '%' } + .mult_assign { '*' } + else { 'unknown op' } + } + g.expr(left) + g.write(' = ${styp}_${util.replace_op(extracted_op)}(') + method := g.table.type_find_method(left_sym, extracted_op) or { + // the checker will most likely have found this, already... + g.error('assignemnt operator `$extracted_op=` used but no `$extracted_op` method defined', + assign_stmt.pos) + ast.Fn{} + } + op_expected_left = method.params[0].typ + op_expected_right = method.params[1].typ + op_overloaded = true + } + if right_sym.kind == .function && is_decl { + if is_inside_ternary && is_decl { + g.out.write_string(util.tabs(g.indent - g.inside_ternary)) + } + func := right_sym.info as ast.FnType + ret_styp := g.typ(func.func.return_type) + g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (') + def_pos := g.definitions.len + g.fn_args(func.func.params, voidptr(0)) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(')') + } else { + if is_decl { + if is_inside_ternary { + g.out.write_string(util.tabs(g.indent - g.inside_ternary)) + } + mut is_used_var_styp := false + if ident.name !in g.defer_vars { + val_sym := g.table.get_type_symbol(val_type) + if val_sym.info is ast.Struct { + if val_sym.info.generic_types.len > 0 { + if val is ast.StructInit { + var_styp := g.typ(val.typ) + g.write('$var_styp ') + is_used_var_styp = true + } else if val is ast.PrefixExpr { + if val.op == .amp && val.right is ast.StructInit { + var_styp := g.typ(val.right.typ.to_ptr()) + g.write('$var_styp ') + is_used_var_styp = true + } + } + } + } + if !is_used_var_styp { + g.write('$styp ') + } + if is_auto_heap { + g.write('*') + } + } + } + if left is ast.Ident || left is ast.SelectorExpr { + g.prevent_sum_type_unwrapping_once = true + } + if !is_fixed_array_var || is_decl { + if op_overloaded { + g.op_arg(left, op_expected_left, var_type) + } else { + if !is_decl && left.is_auto_deref_var() { + g.write('*') + } + g.expr(left) + } + } + } + if is_inside_ternary && is_decl { + g.write(';\n$cur_line') + g.out.write_string(util.tabs(g.indent)) + g.expr(left) + } + g.is_assign_lhs = false + if is_fixed_array_var { + if is_decl { + g.writeln(';') + } + } else if !g.is_arraymap_set && !str_add && !op_overloaded { + g.write(' $op ') + } else if str_add || op_overloaded { + g.write(', ') + } + mut cloned := false + if g.is_autofree && right_sym.kind in [.array, .string] { + if g.gen_clone_assignment(val, right_sym, false) { + cloned = true + } + } + unwrap_optional := !var_type.has_flag(.optional) && val_type.has_flag(.optional) + if unwrap_optional { + // Unwrap the optional now that the testing code has been prepended. + // `pos := s.index(... + // `int pos = *(int)_t10.data;` + // if g.is_autofree { + /* + if is_optional { + g.write('*($styp*)') + g.write(tmp_opt + '.data/*FFz*/') + g.right_is_opt = false + if g.inside_ternary == 0 && !assign_stmt.is_simple { + g.writeln(';') + } + return + } + */ + } + g.is_shared = var_type.has_flag(.shared_f) + if !cloned { + if is_fixed_array_var { + typ_str := g.typ(val_type).trim('*') + ref_str := if val_type.is_ptr() { '' } else { '&' } + g.write('memcpy(($typ_str*)') + g.expr(left) + g.write(', (byte*)$ref_str') + g.expr(val) + g.write(', sizeof($typ_str))') + } else if is_decl { + if is_fixed_array_init && !has_val { + if val is ast.ArrayInit { + if val.has_default { + g.write('{') + g.expr(val.default_expr) + info := right_sym.info as ast.ArrayFixed + for _ in 1 .. info.size { + g.write(', ') + g.expr(val.default_expr) + } + g.write('}') + } else { + g.write('{0}') + } + } else { + g.write('{0}') + } + } else { + if is_auto_heap { + g.write('HEAP($styp, (') + } + if val.is_auto_deref_var() { + g.write('*') + } + g.expr(val) + if is_auto_heap { + g.write('))') + } + } + } else { + if assign_stmt.has_cross_var { + g.gen_cross_tmp_variable(assign_stmt.left, val) + } else { + if op_overloaded { + g.op_arg(val, op_expected_right, val_type) + } else { + g.expr_with_cast(val, val_type, var_type) + } + } + } + } + if str_add || op_overloaded { + g.write(')') + } + if g.is_arraymap_set { + g.write(' })') + g.is_arraymap_set = false + } + g.is_shared = false + } + g.right_is_opt = false + if g.inside_ternary == 0 && (assign_stmt.left.len > 1 || !assign_stmt.is_simple) { + g.writeln(';') + } + } +} + +fn (mut g Gen) gen_cross_tmp_variable(left []ast.Expr, val ast.Expr) { + val_ := val + match val { + ast.Ident { + mut has_var := false + for lx in left { + if lx is ast.Ident { + if val.name == lx.name { + g.write('_var_') + g.write(lx.pos.pos.str()) + has_var = true + break + } + } + } + if !has_var { + g.expr(val_) + } + } + ast.IndexExpr { + mut has_var := false + for lx in left { + if val_.str() == lx.str() { + g.write('_var_') + g.write(lx.position().pos.str()) + has_var = true + break + } + } + if !has_var { + g.expr(val_) + } + } + ast.InfixExpr { + g.gen_cross_tmp_variable(left, val.left) + g.write(val.op.str()) + g.gen_cross_tmp_variable(left, val.right) + } + ast.PrefixExpr { + g.write(val.op.str()) + g.gen_cross_tmp_variable(left, val.right) + } + ast.PostfixExpr { + g.gen_cross_tmp_variable(left, val.expr) + g.write(val.op.str()) + } + ast.SelectorExpr { + mut has_var := false + for lx in left { + if val_.str() == lx.str() { + g.write('_var_') + g.write(lx.position().pos.str()) + has_var = true + break + } + } + if !has_var { + g.expr(val_) + } + } + else { + g.expr(val_) + } + } +} + +fn (mut g Gen) register_ternary_name(name string) { + level_key := g.inside_ternary.str() + if level_key !in g.ternary_level_names { + g.ternary_level_names[level_key] = []string{} + } + new_name := g.new_tmp_var() + g.ternary_names[name] = new_name + g.ternary_level_names[level_key] << name +} + +fn (mut g Gen) get_ternary_name(name string) string { + if g.inside_ternary == 0 { + return name + } + if name !in g.ternary_names { + return name + } + return g.ternary_names[name] +} + +fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym ast.TypeSymbol, add_eq bool) bool { + if val !is ast.Ident && val !is ast.SelectorExpr { + return false + } + if g.is_autofree && right_sym.kind == .array { + // `arr1 = arr2` => `arr1 = arr2.clone()` + if add_eq { + g.write('=') + } + g.write(' array_clone_static_to_depth(') + g.expr(val) + elem_type := (right_sym.info as ast.Array).elem_type + array_depth := g.get_array_depth(elem_type) + g.write(', $array_depth)') + } else if g.is_autofree && right_sym.kind == .string { + if add_eq { + g.write('=') + } + // `str1 = str2` => `str1 = str2.clone()` + g.write(' string_clone_static(') + g.expr(val) + g.write(')') + } + return true +} + +fn (mut g Gen) autofree_scope_vars(pos int, line_nr int, free_parent_scopes bool) { + g.autofree_scope_vars_stop(pos, line_nr, free_parent_scopes, -1) +} + +fn (mut g Gen) autofree_scope_vars_stop(pos int, line_nr int, free_parent_scopes bool, stop_pos int) { + if g.is_builtin_mod { + // In `builtin` everything is freed manually. + return + } + if pos == -1 { + // TODO why can pos be -1? + return + } + // eprintln('> free_scope_vars($pos)') + scope := g.file.scope.innermost(pos) + if scope.start_pos == 0 { + // TODO why can scope.pos be 0? (only outside fns?) + return + } + g.trace_autofree('// autofree_scope_vars(pos=$pos line_nr=$line_nr scope.pos=$scope.start_pos scope.end_pos=$scope.end_pos)') + g.autofree_scope_vars2(scope, scope.start_pos, scope.end_pos, line_nr, free_parent_scopes, + stop_pos) +} + +[if trace_autofree ?] +fn (mut g Gen) trace_autofree(line string) { + g.writeln(line) +} + +// fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, end_pos int) { +fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int, line_nr int, free_parent_scopes bool, stop_pos int) { + if isnil(scope) { + return + } + for _, obj in scope.objects { + match obj { + ast.Var { + g.trace_autofree('// var "$obj.name" var.pos=$obj.pos.pos var.line_nr=$obj.pos.line_nr') + if obj.name == g.returned_var_name { + g.trace_autofree('// skipping returned var') + continue + } + if obj.is_or { + // Skip vars inited with the `or {}`, since they are generated + // after the or block in C. + g.trace_autofree('// skipping `or{}` var "$obj.name"') + continue + } + if obj.is_tmp { + // Skip for loop vars + g.trace_autofree('// skipping tmp var "$obj.name"') + continue + } + if obj.is_inherited { + g.trace_autofree('// skipping inherited var "$obj.name"') + continue + } + // if var.typ == 0 { + // // TODO why 0? + // continue + // } + // if v.pos.pos > end_pos { + if obj.pos.pos > end_pos || (obj.pos.pos < start_pos && obj.pos.line_nr == line_nr) { + // Do not free vars that were declared after this scope + continue + } + is_optional := obj.typ.has_flag(.optional) + if is_optional { + // TODO: free optionals + continue + } + g.autofree_variable(obj) + } + else {} + } + } + // Free all vars in parent scopes as well: + // ``` + // s := ... + // if ... { + // s.free() + // return + // } + // ``` + // if !isnil(scope.parent) && line_nr > 0 { + if free_parent_scopes && !isnil(scope.parent) + && (stop_pos == -1 || scope.parent.start_pos >= stop_pos) { + g.trace_autofree('// af parent scope:') + g.autofree_scope_vars2(scope.parent, start_pos, end_pos, line_nr, true, stop_pos) + } +} + +fn (mut g Gen) autofree_variable(v ast.Var) { + sym := g.table.get_type_symbol(v.typ) + // if v.name.contains('output2') { + // eprintln(' > var name: ${v.name:-20s} | is_arg: ${v.is_arg.str():6} | var type: ${int(v.typ):8} | type_name: ${sym.name:-33s}') + // } + if sym.kind == .array { + if sym.has_method('free') { + free_method_name := g.typ(v.typ) + '_free' + g.autofree_var_call(free_method_name, v) + return + } + g.autofree_var_call('array_free', v) + return + } + if sym.kind == .string { + // Don't free simple string literals. + match v.expr { + ast.StringLiteral { + g.trace_autofree('// str literal') + } + else { + // NOTE/TODO: assign_stmt multi returns variables have no expr + // since the type comes from the called fns return type + /* + f := v.name[0] + if + //!(f >= `a` && f <= `d`) { + //f != `c` { + v.name!='cvar_name' { + t := typeof(v.expr) + return '// other ' + t + '\n' + } + */ + } + } + g.autofree_var_call('string_free', v) + return + } + if sym.has_method('free') { + g.autofree_var_call(c_name(sym.name) + '_free', v) + } +} + +fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { + if v.is_arg { + // fn args should not be autofreed + return + } + if v.is_used && v.is_autofree_tmp { + // tmp expr vars do not need to be freed again here + return + } + if g.is_builtin_mod { + return + } + if !g.is_autofree { + return + } + // if v.is_autofree_tmp && !g.doing_autofree_tmp { + // return + // } + if v.name.contains('expr_write_string_1_') { + // TODO remove this temporary hack + return + } + if v.typ.is_ptr() { + g.writeln('\t${free_fn_name}(${c_name(v.name)}); // autofreed ptr var') + } else { + if v.typ == ast.error_type && !v.is_autofree_tmp { + return + } + g.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var $g.cur_mod.name $g.is_builtin_mod') + } +} + +fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) { + mut hash_fn := '' + mut key_eq_fn := '' + mut clone_fn := '' + mut free_fn := '&map_free_nop' + match key_typ.kind { + .byte, .i8, .char { + hash_fn = '&map_hash_int_1' + key_eq_fn = '&map_eq_int_1' + clone_fn = '&map_clone_int_1' + } + .i16, .u16 { + hash_fn = '&map_hash_int_2' + key_eq_fn = '&map_eq_int_2' + clone_fn = '&map_clone_int_2' + } + .int, .u32, .rune, .f32, .enum_ { + hash_fn = '&map_hash_int_4' + key_eq_fn = '&map_eq_int_4' + clone_fn = '&map_clone_int_4' + } + .voidptr { + ts := if g.pref.m64 { + unsafe { &g.table.type_symbols[ast.u64_type_idx] } + } else { + unsafe { &g.table.type_symbols[ast.u32_type_idx] } + } + return g.map_fn_ptrs(ts) + } + .u64, .i64, .f64 { + hash_fn = '&map_hash_int_8' + key_eq_fn = '&map_eq_int_8' + clone_fn = '&map_clone_int_8' + } + .string { + hash_fn = '&map_hash_string' + key_eq_fn = '&map_eq_string' + clone_fn = '&map_clone_string' + free_fn = '&map_free_string' + } + else { + verror('map key type not supported') + } + } + return hash_fn, key_eq_fn, clone_fn, free_fn +} + +fn (mut g Gen) expr(node ast.Expr) { + // println('cgen expr() line_nr=$node.pos.line_nr') + old_discard_or_result := g.discard_or_result + old_is_void_expr_stmt := g.is_void_expr_stmt + if g.is_void_expr_stmt { + g.discard_or_result = true + g.is_void_expr_stmt = false + } else { + g.discard_or_result = false + } + // NB: please keep the type names in the match here in alphabetical order: + match mut node { + ast.EmptyExpr { + g.error('g.expr(): unhandled EmptyExpr', token.Position{}) + } + ast.AnonFn { + g.gen_anon_fn(mut node) + } + ast.ArrayDecompose { + g.expr(node.expr) + } + ast.ArrayInit { + g.array_init(node) + } + ast.AsCast { + g.as_cast(node) + } + ast.Assoc { + g.assoc(node) + } + ast.BoolLiteral { + g.write(node.val.str()) + } + ast.CallExpr { + // if g.fileis('1.strings') { + // println('\ncall_expr()()') + // } + ret_type := if node.or_block.kind == .absent { + node.return_type + } else { + node.return_type.clear_flag(.optional) + } + mut shared_styp := '' + if g.is_shared && !ret_type.has_flag(.shared_f) { + ret_sym := g.table.get_type_symbol(ret_type) + shared_typ := ret_type.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + if ret_sym.kind == .array { + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if ret_sym.kind == .map { + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else { + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =') + } + } + last_stmt_pos := g.stmt_path_pos.last() + g.call_expr(node) + // if g.fileis('1.strings') { + // println('before:' + node.autofree_pregen) + // } + if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 + && !g.inside_lambda { // && g.inside_ternary == + // if len != 0, that means we are handling call expr inside call expr (arg) + // and it'll get messed up here, since it's handled recursively in autofree_call_pregen() + // so just skip it + g.autofree_call_pregen(node) + if g.strs_to_free0.len > 0 { + g.insert_at(last_stmt_pos, g.strs_to_free0.join('\n') + '/* inserted before */') + } + g.strs_to_free0 = [] + // println('pos=$node.pos.pos') + } + if g.is_shared && !ret_type.has_flag(.shared_f) { + g.writeln('}, sizeof($shared_styp))') + } + // if g.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 { + /* + if g.autofree { + s := g.autofree_pregen[node.pos.pos.str()] + if s != '' { + // g.insert_before_stmt('/*START2*/' + g.strs_to_free0.join('\n') + '/*END*/') + // g.insert_before_stmt('/*START3*/' + node.autofree_pregen + '/*END*/') + g.insert_before_stmt('/*START3*/' + s + '/*END*/') + // for s in g.strs_to_free0 { + } + // //g.writeln(s) + // } + g.strs_to_free0 = [] + } + */ + } + ast.CastExpr { + g.cast_expr(node) + } + ast.ChanInit { + elem_typ_str := g.typ(node.elem_type) + noscan := g.check_noscan(node.elem_type) + g.write('sync__new_channel_st${noscan}(') + if node.has_cap { + g.expr(node.cap_expr) + } else { + g.write('0') + } + g.write(', sizeof(') + g.write(elem_typ_str) + g.write('))') + } + ast.CharLiteral { + if node.val == r'\`' { + g.write("'`'") + } else { + // TODO: optimize use L-char instead of u32 when possible + if utf8_str_len(node.val) < node.val.len { + g.write('((rune)0x$node.val.utf32_code().hex() /* `$node.val` */)') + } else { + g.write("'$node.val'") + } + } + } + ast.DumpExpr { + g.dump_expr(node) + } + ast.AtExpr { + g.comp_at(node) + } + ast.ComptimeCall { + g.comptime_call(node) + } + ast.ComptimeSelector { + g.comptime_selector(node) + } + ast.Comment {} + ast.ConcatExpr { + g.concat_expr(node) + } + ast.CTempVar { + // g.write('/*ctmp .orig: $node.orig.str() , ._typ: $node.typ, .is_ptr: $node.is_ptr */ ') + g.write(node.name) + } + ast.EnumVal { + // g.write('${it.mod}${it.enum_name}_$it.val') + // g.enum_expr(node) + styp := g.typ(node.typ) + g.write('${styp}__$node.val') + } + ast.FloatLiteral { + g.write(node.val) + } + ast.GoExpr { + g.go_expr(node) + } + ast.Ident { + g.ident(node) + } + ast.IfExpr { + g.if_expr(node) + } + ast.IfGuardExpr { + g.write('/* guard */') + } + ast.IndexExpr { + g.index_expr(node) + } + ast.InfixExpr { + if node.op in [.left_shift, .plus_assign, .minus_assign] { + g.inside_map_infix = true + g.infix_expr(node) + g.inside_map_infix = false + } else { + g.infix_expr(node) + } + } + ast.IntegerLiteral { + if node.val.starts_with('0o') { + g.write('0') + g.write(node.val[2..]) + } else if node.val.starts_with('-0o') { + g.write('-0') + g.write(node.val[3..]) + } else { + g.write(node.val) // .int().str()) + } + } + ast.LockExpr { + g.lock_expr(node) + } + ast.MatchExpr { + g.match_expr(node) + } + ast.MapInit { + g.map_init(node) + } + ast.NodeError {} + ast.None { + g.write('_const_none__') + } + ast.OrExpr { + // this should never appear here + } + ast.ParExpr { + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.PostfixExpr { + if node.auto_locked != '' { + g.writeln('sync__RwMutex_lock(&$node.auto_locked->mtx);') + } + g.inside_map_postfix = true + if node.expr.is_auto_deref_var() { + g.write('(*') + g.expr(node.expr) + g.write(')') + } else { + g.expr(node.expr) + } + g.inside_map_postfix = false + g.write(node.op.str()) + if node.auto_locked != '' { + g.writeln(';') + g.write('sync__RwMutex_unlock(&$node.auto_locked->mtx)') + } + } + ast.PrefixExpr { + gen_or := node.op == .arrow && (node.or_block.kind != .absent || node.is_option) + if node.op == .amp { + g.is_amp = true + } + if node.op == .arrow { + styp := g.typ(node.right_type) + right_sym := g.table.get_type_symbol(node.right_type) + mut right_inf := right_sym.info as ast.Chan + elem_type := right_inf.elem_type + is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result + cur_line := if is_gen_or_and_assign_rhs { + line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + line + } else { + '' + } + tmp_opt := if gen_or { g.new_tmp_var() } else { '' } + if gen_or { + opt_elem_type := g.typ(elem_type.set_flag(.optional)) + g.register_chan_pop_optional_call(opt_elem_type, styp) + g.write('$opt_elem_type $tmp_opt = __Option_${styp}_popval(') + } else { + g.write('__${styp}_popval(') + } + g.expr(node.right) + g.write(')') + if gen_or { + if !node.is_option { + g.or_block(tmp_opt, node.or_block, elem_type) + } + if is_gen_or_and_assign_rhs { + elem_styp := g.typ(elem_type) + g.write(';\n$cur_line*($elem_styp*)${tmp_opt}.data') + } + } + } else { + // g.write('/*pref*/') + if !(g.is_amp && node.right.is_auto_deref_var()) { + g.write(node.op.str()) + } + // g.write('(') + g.expr(node.right) + } + g.is_amp = false + } + ast.RangeExpr { + // Only used in IndexExpr + } + ast.SelectExpr { + g.select_expr(node) + } + ast.SizeOf { + typ := if node.typ == g.field_data_type { g.comp_for_field_value.typ } else { node.typ } + node_typ := g.unwrap_generic(typ) + sym := g.table.get_type_symbol(node_typ) + if sym.language == .v && sym.kind in [.placeholder, .any] { + g.error('unknown type `$sym.name`', node.pos) + } + styp := g.typ(node_typ) + g.write('/*SizeOf*/ sizeof(${util.no_dots(styp)})') + } + ast.IsRefType { + typ := if node.typ == g.field_data_type { g.comp_for_field_value.typ } else { node.typ } + node_typ := g.unwrap_generic(typ) + sym := g.table.get_type_symbol(node_typ) + if sym.language == .v && sym.kind in [.placeholder, .any] { + g.error('unknown type `$sym.name`', node.pos) + } + is_ref_type := g.contains_ptr(node_typ) + g.write('/*IsRefType*/ $is_ref_type') + } + ast.OffsetOf { + styp := g.typ(node.struct_type) + g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))') + } + ast.SqlExpr { + g.sql_select_expr(node) + } + ast.StringLiteral { + g.string_literal(node) + } + ast.StringInterLiteral { + g.string_inter_literal(node) + } + ast.StructInit { + if node.unresolved { + g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) + } else { + // `user := User{name: 'Bob'}` + g.struct_init(node) + } + } + ast.SelectorExpr { + g.selector_expr(node) + } + ast.TypeNode { + // match sum Type + // g.write('/* Type */') + // type_idx := node.typ.idx() + typ := g.unwrap_generic(node.typ) + sym := g.table.get_type_symbol(typ) + sidx := g.type_sidx(typ) + // g.write('$type_idx /* $sym.name */') + g.write('$sidx /* $sym.name */') + } + ast.TypeOf { + g.typeof_expr(node) + } + ast.Likely { + if node.is_likely { + g.write('_likely_') + } else { + g.write('_unlikely_') + } + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.UnsafeExpr { + g.expr(node.expr) + } + } + g.discard_or_result = old_discard_or_result + g.is_void_expr_stmt = old_is_void_expr_stmt +} + +// T.name, typeof(expr).name +fn (mut g Gen) type_name(raw_type ast.Type) { + typ := if raw_type == g.field_data_type { g.comp_for_field_value.typ } else { raw_type } + sym := g.table.get_type_symbol(typ) + mut s := '' + if sym.kind == .function { + if typ.is_ptr() { + s = '&' + g.fn_decl_str(sym.info as ast.FnType) + } else { + s = g.fn_decl_str(sym.info as ast.FnType) + } + } else { + s = g.table.type_to_str(g.unwrap_generic(typ)) + } + g.write('_SLIT("${util.strip_main_name(s)}")') +} + +fn (mut g Gen) typeof_expr(node ast.TypeOf) { + typ := if node.expr_type == g.field_data_type { + g.comp_for_field_value.typ + } else { + node.expr_type + } + sym := g.table.get_type_symbol(typ) + if sym.kind == .sum_type { + // When encountering a .sum_type, typeof() should be done at runtime, + // because the subtype of the expression may change: + g.write('charptr_vstring_literal( /* $sym.name */ v_typeof_sumtype_${sym.cname}( (') + g.expr(node.expr) + g.write(')._typ ))') + } else if sym.kind == .array_fixed { + fixed_info := sym.info as ast.ArrayFixed + typ_name := g.table.get_type_name(fixed_info.elem_type) + g.write('_SLIT("[$fixed_info.size]${util.strip_main_name(typ_name)}")') + } else if sym.kind == .function { + info := sym.info as ast.FnType + g.write('_SLIT("${g.fn_decl_str(info)}")') + } else if typ.has_flag(.variadic) { + varg_elem_type_sym := g.table.get_type_symbol(g.table.value_type(typ)) + g.write('_SLIT("...${util.strip_main_name(varg_elem_type_sym.name)}")') + } else { + x := g.table.type_to_str(typ) + y := util.strip_main_name(x) + g.write('_SLIT("$y")') + } +} + +fn (mut g Gen) selector_expr(node ast.SelectorExpr) { + prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once + g.prevent_sum_type_unwrapping_once = false + if node.name_type > 0 { + match node.gkind_field { + .name { + g.type_name(node.name_type) + return + } + .typ { + g.write(int(g.unwrap_generic(node.name_type)).str()) + return + } + .unknown { + if node.field_name == 'name' { + // typeof(expr).name + g.type_name(node.name_type) + return + } else if node.field_name == 'idx' { + // typeof(expr).idx + g.write(int(g.unwrap_generic(node.name_type)).str()) + return + } + g.error('unknown generic field', node.pos) + } + } + } + if node.expr_type == 0 { + g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) + } + sym := g.table.get_type_symbol(g.unwrap_generic(node.expr_type)) + // if node expr is a root ident and an optional + mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional) + if is_optional { + opt_base_typ := g.base_type(node.expr_type) + g.writeln('(*($opt_base_typ*)') + } + if sym.kind in [.interface_, .sum_type] { + g.write('(*(') + } + if sym.kind == .array_fixed { + if node.field_name != 'len' { + g.error('field_name should be `len`', node.pos) + } + info := sym.info as ast.ArrayFixed + g.write('$info.size') + return + } + if sym.kind == .chan && (node.field_name == 'len' || node.field_name == 'closed') { + g.write('sync__Channel_${node.field_name}(') + g.expr(node.expr) + g.write(')') + return + } + mut sum_type_deref_field := '' + mut sum_type_dot := '.' + if f := g.table.find_field(sym, node.field_name) { + field_sym := g.table.get_type_symbol(f.typ) + if field_sym.kind in [.sum_type, .interface_] { + if !prevent_sum_type_unwrapping_once { + // check first if field is sum type because scope searching is expensive + scope := g.file.scope.innermost(node.pos.pos) + if field := scope.find_struct_field(node.expr.str(), node.expr_type, node.field_name) { + if field.orig_type.is_ptr() { + sum_type_dot = '->' + } + for i, typ in field.smartcasts { + g.write('(') + if field_sym.kind == .sum_type { + g.write('*') + } + cast_sym := g.table.get_type_symbol(typ) + if i != 0 { + dot := if field.typ.is_ptr() { '->' } else { '.' } + sum_type_deref_field += ')$dot' + } + if mut cast_sym.info is ast.Aggregate { + agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) + sum_type_deref_field += '_$agg_sym.cname' + } else { + sum_type_deref_field += '_$cast_sym.cname' + } + } + } + } + } + } + n_ptr := node.expr_type.nr_muls() - 1 + if n_ptr > 0 { + g.write('(') + g.write('*'.repeat(n_ptr)) + g.expr(node.expr) + g.write(')') + } else { + g.expr(node.expr) + } + if is_optional { + g.write('.data)') + } + // struct embedding + if sym.info is ast.Struct || sym.info is ast.Aggregate { + if node.from_embed_type != 0 { + embed_sym := g.table.get_type_symbol(node.from_embed_type) + embed_name := embed_sym.embed_name() + if node.expr_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(embed_name) + } + } + if (node.expr_type.is_ptr() || sym.kind == .chan) && node.from_embed_type == 0 { + g.write('->') + } else { + // g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /') + g.write('.') + } + if node.expr_type.has_flag(.shared_f) { + g.write('val.') + } + if node.expr_type == 0 { + verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr') + } + field_name := if sym.language == .v { c_name(node.field_name) } else { node.field_name } + g.write(field_name) + if sum_type_deref_field != '' { + g.write('$sum_type_dot$sum_type_deref_field)') + } + if sym.kind in [.interface_, .sum_type] { + g.write('))') + } +} + +fn (mut g Gen) enum_expr(node ast.Expr) { + match node { + ast.EnumVal { + g.write(node.val) + } + else { + g.expr(node) + } + } +} + +fn (mut g Gen) lock_expr(node ast.LockExpr) { + g.cur_lock = unsafe { node } // is ok because it is discarded at end of fn + defer { + g.cur_lock = ast.LockExpr{ + scope: 0 + } + } + tmp_result := if node.is_expr { g.new_tmp_var() } else { '' } + mut cur_line := '' + if node.is_expr { + styp := g.typ(node.typ) + cur_line = g.go_before_stmt(0) + g.writeln('$styp $tmp_result;') + } + mut mtxs := '' + if node.lockeds.len == 0 { + // this should not happen + } else if node.lockeds.len == 1 { + lock_prefix := if node.is_rlock[0] { 'r' } else { '' } + g.write('sync__RwMutex_${lock_prefix}lock(&') + g.expr(node.lockeds[0]) + g.writeln('->mtx);') + } else { + mtxs = g.new_tmp_var() + g.writeln('uintptr_t _arr_$mtxs[$node.lockeds.len];') + g.writeln('bool _isrlck_$mtxs[$node.lockeds.len];') + mut j := 0 + for i, is_rlock in node.is_rlock { + if !is_rlock { + g.write('_arr_$mtxs[$j] = (uintptr_t)&') + g.expr(node.lockeds[i]) + g.writeln('->mtx;') + g.writeln('_isrlck_$mtxs[$j] = false;') + j++ + } + } + for i, is_rlock in node.is_rlock { + if is_rlock { + g.write('_arr_$mtxs[$j] = (uintptr_t)&') + g.expr(node.lockeds[i]) + g.writeln('->mtx;') + g.writeln('_isrlck_$mtxs[$j] = true;') + j++ + } + } + if node.lockeds.len == 2 { + g.writeln('if (_arr_$mtxs[0] > _arr_$mtxs[1]) {') + g.writeln('\tuintptr_t _ptr_$mtxs = _arr_$mtxs[0];') + g.writeln('\t_arr_$mtxs[0] = _arr_$mtxs[1];') + g.writeln('\t_arr_$mtxs[1] = _ptr_$mtxs;') + g.writeln('\tbool _bool_$mtxs = _isrlck_$mtxs[0];') + g.writeln('\t_isrlck_$mtxs[0] = _isrlck_$mtxs[1];') + g.writeln('\t_isrlck_$mtxs[1] = _bool_$mtxs;') + g.writeln('}') + } else { + g.writeln('__sort_ptr(_arr_$mtxs, _isrlck_$mtxs, $node.lockeds.len);') + } + g.writeln('for (int $mtxs=0; $mtxs<$node.lockeds.len; $mtxs++) {') + g.writeln('\tif ($mtxs && _arr_$mtxs[$mtxs] == _arr_$mtxs[$mtxs-1]) continue;') + g.writeln('\tif (_isrlck_$mtxs[$mtxs])') + g.writeln('\t\tsync__RwMutex_rlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') + g.writeln('\telse') + g.writeln('\t\tsync__RwMutex_lock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') + g.writeln('}') + } + g.mtxs = mtxs + defer { + g.mtxs = '' + } + g.writeln('/*lock*/ {') + g.stmts_with_tmp_var(node.stmts, tmp_result) + if node.is_expr { + g.writeln(';') + } + g.writeln('}') + g.unlock_locks() + if node.is_expr { + g.writeln('') + g.write(cur_line) + g.write('$tmp_result') + } +} + +fn (mut g Gen) unlock_locks() { + if g.cur_lock.lockeds.len == 0 { + } else if g.cur_lock.lockeds.len == 1 { + lock_prefix := if g.cur_lock.is_rlock[0] { 'r' } else { '' } + g.write('sync__RwMutex_${lock_prefix}unlock(&') + g.expr(g.cur_lock.lockeds[0]) + g.write('->mtx);') + } else { + g.writeln('for (int $g.mtxs=${g.cur_lock.lockeds.len - 1}; $g.mtxs>=0; $g.mtxs--) {') + g.writeln('\tif ($g.mtxs && _arr_$g.mtxs[$g.mtxs] == _arr_$g.mtxs[$g.mtxs-1]) continue;') + g.writeln('\tif (_isrlck_$g.mtxs[$g.mtxs])') + g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);') + g.writeln('\telse') + g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);') + g.write('}') + } +} + +fn (mut g Gen) need_tmp_var_in_match(node ast.MatchExpr) bool { + if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 { + sym := g.table.get_type_symbol(node.return_type) + if sym.kind == .multi_return { + return false + } + for branch in node.branches { + if branch.stmts.len > 1 { + return true + } + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr || stmt.expr is ast.IfExpr + || stmt.expr is ast.MatchExpr || (stmt.expr is ast.IndexExpr + && (stmt.expr as ast.IndexExpr).or_expr.kind != .absent) { + return true + } + } + } + } + } + return false +} + +fn (mut g Gen) match_expr(node ast.MatchExpr) { + // println('match expr typ=$it.expr_type') + // TODO + if node.cond_type == 0 { + g.writeln('// match 0') + return + } + need_tmp_var := g.need_tmp_var_in_match(node) + is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary > 0 + mut cond_var := '' + mut tmp_var := '' + mut cur_line := '' + if is_expr && !need_tmp_var { + g.inside_ternary++ + } + if node.cond is ast.Ident || node.cond is ast.SelectorExpr || node.cond is ast.IntegerLiteral + || node.cond is ast.StringLiteral || node.cond is ast.FloatLiteral { + cond_var = g.expr_string(node.cond) + } else { + line := if is_expr { + g.empty_line = true + g.go_before_stmt(0) + } else { + '' + } + cond_var = g.new_tmp_var() + g.write('${g.typ(node.cond_type)} $cond_var = ') + g.expr(node.cond) + g.writeln(';') + g.stmt_path_pos << g.out.len + g.write(line) + } + if need_tmp_var { + g.empty_line = true + cur_line = g.go_before_stmt(0).trim_left(' \t') + tmp_var = g.new_tmp_var() + g.writeln('${g.typ(node.return_type)} $tmp_var = ${g.type_default(node.return_type)};') + } + + if is_expr && !need_tmp_var { + // brackets needed otherwise '?' will apply to everything on the left + g.write('(') + } + if node.is_sum_type { + g.match_expr_sumtype(node, is_expr, cond_var, tmp_var) + } else { + g.match_expr_classic(node, is_expr, cond_var, tmp_var) + } + g.write(cur_line) + if need_tmp_var { + g.write('$tmp_var') + } + if is_expr && !need_tmp_var { + g.write(')') + g.decrement_inside_ternary() + } +} + +fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { + for j, branch in node.branches { + mut sumtype_index := 0 + // iterates through all types in sumtype branches + for { + g.aggregate_type_idx = sumtype_index + is_last := j == node.branches.len - 1 + sym := g.table.get_type_symbol(node.cond_type) + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if is_expr && tmp_var.len == 0 { + // TODO too many branches. maybe separate ?: matches + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.writeln('else {') + } + } else { + if j > 0 || sumtype_index > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.write_v_source_line_info(branch.pos) + g.write('else ') + } + } + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + if j == 0 && sumtype_index == 0 { + g.empty_line = true + } + g.write_v_source_line_info(branch.pos) + g.write('if (') + } + g.write(cond_var) + dot_or_ptr := if node.cond_type.is_ptr() { '->' } else { '.' } + if sym.kind == .sum_type { + g.write('${dot_or_ptr}_typ == ') + g.expr(branch.exprs[sumtype_index]) + } else if sym.kind == .interface_ { + if branch.exprs[sumtype_index] is ast.TypeNode { + typ := branch.exprs[sumtype_index] as ast.TypeNode + branch_sym := g.table.get_type_symbol(g.unwrap_generic(typ.typ)) + g.write('${dot_or_ptr}_typ == _${sym.cname}_${branch_sym.cname}_index') + } else if branch.exprs[sumtype_index] is ast.None && sym.name == 'IError' { + g.write('${dot_or_ptr}_typ == _IError_None___index') + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + if is_expr && tmp_var.len > 0 + && g.table.get_type_symbol(node.return_type).kind == .sum_type { + g.expected_cast_type = node.return_type + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + g.expected_cast_type = 0 + if g.inside_ternary == 0 { + g.writeln('}') + g.stmt_path_pos << g.out.len + } + sumtype_index++ + if branch.exprs.len == 0 || sumtype_index == branch.exprs.len { + break + } + } + // reset global field for next use + g.aggregate_type_idx = 0 + } +} + +fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { + type_sym := g.table.get_type_symbol(node.cond_type) + for j, branch in node.branches { + is_last := j == node.branches.len - 1 + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if node.branches.len > 1 { + if is_expr && tmp_var.len == 0 { + // TODO too many branches. maybe separate ?: matches + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.writeln('else {') + } + } + } else { + if j > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.write('else ') + } + } + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + if j == 0 { + g.writeln('') + } + g.write_v_source_line_info(branch.pos) + g.write('if (') + } + for i, expr in branch.exprs { + if i > 0 { + g.write(' || ') + } + match type_sym.kind { + .array { + ptr_typ := g.gen_array_equality_fn(node.cond_type) + g.write('${ptr_typ}_arr_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .array_fixed { + ptr_typ := g.gen_fixed_array_equality_fn(node.cond_type) + g.write('${ptr_typ}_arr_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .map { + ptr_typ := g.gen_map_equality_fn(node.cond_type) + g.write('${ptr_typ}_map_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .string { + g.write('string__eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .struct_ { + ptr_typ := g.gen_struct_equality_fn(node.cond_type) + g.write('${ptr_typ}_struct_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + else { + if expr is ast.RangeExpr { + // if type is unsigned and low is 0, check is unneeded + mut skip_low := false + if expr.low is ast.IntegerLiteral { + if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] + && expr.low.val == '0' { + skip_low = true + } + } + g.write('(') + if !skip_low { + g.write('$cond_var >= ') + g.expr(expr.low) + g.write(' && ') + } + g.write('$cond_var <= ') + g.expr(expr.high) + g.write(')') + } else { + g.write('$cond_var == (') + g.expr(expr) + g.write(')') + } + } + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + if g.inside_ternary == 0 && node.branches.len >= 1 { + g.write('}') + } + } +} + +fn (mut g Gen) map_init(node ast.MapInit) { + key_typ_str := g.typ(node.key_type) + value_typ_str := g.typ(node.value_type) + value_typ := g.table.get_type_symbol(node.value_type) + key_typ := g.table.get_final_type_symbol(node.key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) + size := node.vals.len + mut shared_styp := '' // only needed for shared &[]{...} + mut styp := '' + is_amp := g.is_amp + g.is_amp = false + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared { + mut shared_typ := node.typ.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else if is_amp { + styp = g.typ(node.typ) + g.write('($styp*)memdup(ADDR($styp, ') + } + noscan_key := g.check_noscan(node.key_type) + noscan_value := g.check_noscan(node.value_type) + mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } + if noscan.len != 0 { + if noscan_key.len != 0 { + noscan += '_key' + } + if noscan_value.len != 0 { + noscan += '_value' + } + } + if size > 0 { + if value_typ.kind == .function { + g.write('new_map_init${noscan}($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') + } else { + g.write('new_map_init${noscan}($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') + } + for expr in node.keys { + g.expr(expr) + g.write(', ') + } + if value_typ.kind == .function { + g.write('}), _MOV((voidptr[$size]){') + } else { + g.write('}), _MOV(($value_typ_str[$size]){') + } + for expr in node.vals { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(', ') + } + g.write('}))') + } else { + g.write('new_map${noscan}(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') + } + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write('), sizeof($styp))') + } +} + +fn (mut g Gen) select_expr(node ast.SelectExpr) { + is_expr := node.is_expr || g.inside_ternary > 0 + cur_line := if is_expr { + g.empty_line = true + g.go_before_stmt(0) + } else { + '' + } + n_channels := if node.has_exception { node.branches.len - 1 } else { node.branches.len } + mut channels := []ast.Expr{cap: n_channels} + mut objs := []ast.Expr{cap: n_channels} + mut tmp_objs := []string{cap: n_channels} + mut elem_types := []string{cap: n_channels} + mut is_push := []bool{cap: n_channels} + mut has_else := false + mut has_timeout := false + mut timeout_expr := ast.empty_expr() + mut exception_branch := -1 + for j, branch in node.branches { + if branch.is_else { + has_else = true + exception_branch = j + } else if branch.is_timeout { + has_timeout = true + exception_branch = j + timeout_expr = (branch.stmt as ast.ExprStmt).expr + } else { + match branch.stmt { + ast.ExprStmt { + // send expression + expr := branch.stmt.expr as ast.InfixExpr + channels << expr.left + if expr.right is ast.Ident || expr.right is ast.IndexExpr + || expr.right is ast.SelectorExpr || expr.right is ast.StructInit { + // addressable objects in the `C` output + objs << expr.right + tmp_objs << '' + elem_types << '' + } else { + // must be evaluated to tmp var before real `select` is performed + objs << ast.empty_expr() + tmp_obj := g.new_tmp_var() + tmp_objs << tmp_obj + el_stype := g.typ(g.table.mktyp(expr.right_type)) + g.writeln('$el_stype $tmp_obj;') + } + is_push << true + } + ast.AssignStmt { + rec_expr := branch.stmt.right[0] as ast.PrefixExpr + channels << rec_expr.right + is_push << false + // create tmp unless the object with *exactly* the type we need exists already + if branch.stmt.op == .decl_assign + || branch.stmt.right_types[0] != branch.stmt.left_types[0] { + tmp_obj := g.new_tmp_var() + tmp_objs << tmp_obj + el_stype := g.typ(branch.stmt.right_types[0]) + elem_types << if branch.stmt.op == .decl_assign { + el_stype + ' ' + } else { + '' + } + g.writeln('$el_stype $tmp_obj;') + } else { + tmp_objs << '' + elem_types << '' + } + objs << branch.stmt.left[0] + } + else {} + } + } + } + chan_array := g.new_tmp_var() + g.write('Array_sync__Channel_ptr $chan_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Channel*), _MOV((sync__Channel*[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', ') + } + g.write('(sync__Channel*)(') + g.expr(channels[i]) + g.write(')') + } + g.writeln('}));\n') + directions_array := g.new_tmp_var() + g.write('Array_sync__Direction $directions_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Direction), _MOV((sync__Direction[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', ') + } + if is_push[i] { + g.write('sync__Direction__push') + } else { + g.write('sync__Direction__pop') + } + } + g.writeln('}));\n') + objs_array := g.new_tmp_var() + g.write('Array_voidptr $objs_array = new_array_from_c_array($n_channels, $n_channels, sizeof(voidptr), _MOV((voidptr[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', &') + } else { + g.write('&') + } + if tmp_objs[i] == '' { + g.expr(objs[i]) + } else { + g.write(tmp_objs[i]) + } + } + g.writeln('}));\n') + select_result := g.new_tmp_var() + g.write('int $select_result = sync__channel_select(&/*arr*/$chan_array, $directions_array, &/*arr*/$objs_array, ') + if has_timeout { + g.expr(timeout_expr) + } else if has_else { + g.write('0') + } else { + g.write('_const_time__infinite') + } + g.writeln(');') + // free the temps that were created + g.writeln('array_free(&$objs_array);') + g.writeln('array_free(&$directions_array);') + g.writeln('array_free(&$chan_array);') + mut i := 0 + for j in 0 .. node.branches.len { + if j > 0 { + g.write('} else ') + } + g.write('if ($select_result == ') + if j == exception_branch { + g.writeln('-1) {') + } else { + g.writeln('$i) {') + if !is_push[i] && tmp_objs[i] != '' { + g.write('\t${elem_types[i]}') + g.expr(objs[i]) + g.writeln(' = ${tmp_objs[i]};') + } + i++ + } + g.stmts(node.branches[j].stmts) + } + g.writeln('}') + if is_expr { + g.empty_line = false + g.write(cur_line) + g.write('($select_result != -2)') + } +} + +fn (mut g Gen) ident(node ast.Ident) { + prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once + g.prevent_sum_type_unwrapping_once = false + if node.name == 'lld' { + return + } + if node.name.starts_with('C.') { + g.write(util.no_dots(node.name[2..])) + return + } + if node.kind == .constant { // && !node.name.starts_with('g_') { + // TODO globals hack + g.write('_const_') + } + mut name := c_name(node.name) + // TODO: temporary, remove this + node_info := node.info + mut is_auto_heap := false + if node_info is ast.IdentVar { + // x ?int + // `x = 10` => `x.data = 10` (g.right_is_opt == false) + // `x = new_opt()` => `x = new_opt()` (g.right_is_opt == true) + // `println(x)` => `println(*(int*)x.data)` + if node_info.is_optional && !(g.is_assign_lhs && g.right_is_opt) { + g.write('/*opt*/') + styp := g.base_type(node_info.typ) + g.write('(*($styp*)${name}.data)') + return + } + if !g.is_assign_lhs && node_info.share == .shared_t { + g.write('${name}.val') + return + } + scope := g.file.scope.innermost(node.pos.pos) + if v := scope.find_var(node.name) { + is_auto_heap = v.is_auto_heap && (!g.is_assign_lhs || g.assign_op != .decl_assign) + if is_auto_heap { + g.write('(*(') + } + if v.smartcasts.len > 0 { + v_sym := g.table.get_type_symbol(v.typ) + if !prevent_sum_type_unwrapping_once { + for _ in v.smartcasts { + g.write('(') + if v_sym.kind == .sum_type && !is_auto_heap { + g.write('*') + } + } + for i, typ in v.smartcasts { + cast_sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut is_ptr := false + if i == 0 { + g.write(name) + if v.orig_type.is_ptr() { + is_ptr = true + } + } + dot := if is_ptr || is_auto_heap { '->' } else { '.' } + if mut cast_sym.info is ast.Aggregate { + sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) + g.write('${dot}_$sym.cname') + } else { + g.write('${dot}_$cast_sym.cname') + } + g.write(')') + } + if is_auto_heap { + g.write('))') + } + return + } + } + if v.is_inherited { + g.write(closure_ctx + '->') + } + } + } else if node_info is ast.IdentFn { + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + key := node.name + g.write('/* obf identfn: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + } + g.write(g.get_ternary_name(name)) + if is_auto_heap { + g.write('))') + } +} + +fn (mut g Gen) cast_expr(node ast.CastExpr) { + if g.is_amp { + // &Foo(0) => ((Foo*)0) + g.out.go_back(1) + } + g.is_amp = false + sym := g.table.get_type_symbol(node.typ) + if sym.kind == .string && !node.typ.is_ptr() { + // `string(x)` needs `tos()`, but not `&string(x) + // `tos(str, len)`, `tos2(str)` + if node.has_arg { + g.write('tos((byteptr)') + } else { + g.write('tos2((byteptr)') + } + g.expr(node.expr) + expr_sym := g.table.get_type_symbol(node.expr_type) + if expr_sym.kind == .array { + // if we are casting an array, we need to add `.data` + g.write('.data') + } + if node.has_arg { + // len argument + g.write(', ') + g.expr(node.arg) + } + g.write(')') + } else if sym.kind in [.sum_type, .interface_] { + g.expr_with_cast(node.expr, node.expr_type, node.typ) + } else if sym.kind == .struct_ && !node.typ.is_ptr() && !(sym.info as ast.Struct).is_typedef { + // deprecated, replaced by Struct{...exr} + styp := g.typ(node.typ) + g.write('*(($styp *)(&') + g.expr(node.expr) + g.write('))') + } else if sym.kind == .alias && g.table.get_final_type_symbol(node.typ).kind == .array_fixed { + g.expr(node.expr) + } else { + styp := g.typ(node.typ) + mut cast_label := '' + // `ast.string_type` is done for MSVC's bug + if sym.kind != .alias + || (sym.info as ast.Alias).parent_type !in [node.expr_type, ast.string_type] { + cast_label = '($styp)' + } + if node.typ.has_flag(.optional) && node.expr is ast.None { + g.gen_optional_error(node.typ, node.expr) + } else { + g.write('(${cast_label}(') + g.expr(node.expr) + if node.expr is ast.IntegerLiteral { + if node.typ in [ast.u64_type, ast.u32_type, ast.u16_type] { + if !node.expr.val.starts_with('-') { + g.write('U') + } + } + } + g.write('))') + } + } +} + +fn (mut g Gen) concat_expr(node ast.ConcatExpr) { + styp := g.typ(node.return_type) + sym := g.table.get_type_symbol(node.return_type) + is_multi := sym.kind == .multi_return + if !is_multi { + g.expr(node.vals[0]) + } else { + g.write('($styp){') + for i, expr in node.vals { + g.write('.arg$i=') + g.expr(expr) + if i < node.vals.len - 1 { + g.write(',') + } + } + g.write('}') + } +} + +fn (mut g Gen) need_tmp_var_in_if(node ast.IfExpr) bool { + if node.is_expr && g.inside_ternary == 0 { + if g.is_autofree || node.typ.has_flag(.optional) { + return true + } + for branch in node.branches { + if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 { + return true + } + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr { + if stmt.expr.is_method { + left_sym := g.table.get_type_symbol(stmt.expr.receiver_type) + if left_sym.kind in [.array, .array_fixed, .map] { + return true + } + } + } + } + } + } + } + return false +} + +fn (mut g Gen) if_expr(node ast.IfExpr) { + if node.is_comptime { + g.comp_if(node) + return + } + // For simpe if expressions we can use C's `?:` + // `if x > 0 { 1 } else { 2 }` => `(x > 0) ? (1) : (2)` + // For if expressions with multiple statements or another if expression inside, it's much + // easier to use a temp var, than do C tricks with commas, introduce special vars etc + // (as it used to be done). + // Always use this in -autofree, since ?: can have tmp expressions that have to be freed. + needs_tmp_var := g.need_tmp_var_in_if(node) + tmp := if needs_tmp_var { g.new_tmp_var() } else { '' } + mut cur_line := '' + if needs_tmp_var { + if node.typ.has_flag(.optional) { + g.inside_if_optional = true + } + styp := g.typ(node.typ) + cur_line = g.go_before_stmt(0) + g.empty_line = true + g.writeln('$styp $tmp; /* if prepend */') + if g.infix_left_var_name.len > 0 { + g.writeln('if ($g.infix_left_var_name) {') + g.indent++ + } + } else if node.is_expr || g.inside_ternary != 0 { + g.inside_ternary++ + g.write('(') + for i, branch in node.branches { + if i > 0 { + g.write(' : ') + } + if i < node.branches.len - 1 || !node.has_else { + g.expr(branch.cond) + g.write(' ? ') + } + g.stmts(branch.stmts) + } + if node.branches.len == 1 { + g.write(': 0') + } + g.write(')') + g.decrement_inside_ternary() + return + } + mut is_guard := false + mut guard_idx := 0 + mut guard_vars := []string{} + for i, branch in node.branches { + cond := branch.cond + if cond is ast.IfGuardExpr { + if !is_guard { + is_guard = true + guard_idx = i + guard_vars = []string{len: node.branches.len} + } + if cond.expr !is ast.IndexExpr && cond.expr !is ast.PrefixExpr { + var_name := g.new_tmp_var() + guard_vars[i] = var_name + g.writeln('${g.typ(cond.expr_type)} $var_name;') + } else { + guard_vars[i] = '' + } + } + } + for i, branch in node.branches { + if i > 0 { + g.write('} else ') + } + // if last branch is `else {` + if i == node.branches.len - 1 && node.has_else { + g.writeln('{') + // define `err` only for simple `if val := opt {...} else {` + if is_guard && guard_idx == i - 1 { + cvar_name := guard_vars[guard_idx] + g.writeln('\tIError err = ${cvar_name}.err;') + } + } else { + match branch.cond { + ast.IfGuardExpr { + mut var_name := guard_vars[i] + mut short_opt := false + if var_name == '' { + short_opt = true // we don't need a further tmp, so use the one we'll get later + var_name = g.new_tmp_var() + guard_vars[i] = var_name // for `else` + g.tmp_count-- + g.writeln('if (${var_name}.state == 0) {') + } else { + g.write('if ($var_name = ') + g.expr(branch.cond.expr) + g.writeln(', ${var_name}.state == 0) {') + } + if short_opt || branch.cond.var_name != '_' { + base_type := g.base_type(branch.cond.expr_type) + if short_opt { + cond_var_name := if branch.cond.var_name == '_' { + '_dummy_${g.tmp_count + 1}' + } else { + branch.cond.var_name + } + g.write('\t$base_type $cond_var_name = ') + g.expr(branch.cond.expr) + g.writeln(';') + } else { + mut is_auto_heap := false + if branch.stmts.len > 0 { + scope := g.file.scope.innermost(ast.Node(branch.stmts[branch.stmts.len - 1]).position().pos) + if v := scope.find_var(branch.cond.var_name) { + is_auto_heap = v.is_auto_heap + } + } + if is_auto_heap { + g.writeln('\t$base_type* $branch.cond.var_name = HEAP($base_type, *($base_type*)${var_name}.data);') + } else { + g.writeln('\t$base_type $branch.cond.var_name = *($base_type*)${var_name}.data;') + } + } + } + } + else { + g.write('if (') + g.expr(branch.cond) + g.writeln(') {') + } + } + } + if needs_tmp_var { + g.stmts_with_tmp_var(branch.stmts, tmp) + } else { + // restore if_expr stmt header pos + stmt_pos := g.nth_stmt_pos(0) + g.stmts(branch.stmts) + g.stmt_path_pos << stmt_pos + } + } + g.writeln('}') + if needs_tmp_var { + if g.infix_left_var_name.len > 0 { + g.indent-- + g.writeln('}') + } + g.empty_line = false + g.write('$cur_line $tmp') + } + if node.typ.has_flag(.optional) { + g.inside_if_optional = false + } +} + +[inline] +fn (g &Gen) expr_is_multi_return_call(expr ast.Expr) bool { + match expr { + ast.CallExpr { return g.table.get_type_symbol(expr.return_type).kind == .multi_return } + else { return false } + } +} + +fn (mut g Gen) gen_optional_error(target_type ast.Type, expr ast.Expr) { + styp := g.typ(target_type) + g.write('($styp){ .state=2, .err=') + g.expr(expr) + g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }') +} + +fn (mut g Gen) return_stmt(node ast.Return) { + g.write_v_source_line_info(node.pos) + if node.exprs.len > 0 { + // skip `return $vweb.html()` + if node.exprs[0] is ast.ComptimeCall { + g.expr(node.exprs[0]) + g.writeln(';') + return + } + } + + g.inside_return = true + defer { + g.inside_return = false + } + // got to do a correct check for multireturn + sym := g.table.get_type_symbol(g.fn_decl.return_type) + fn_return_is_multi := sym.kind == .multi_return + fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional) + mut has_semicolon := false + if node.exprs.len == 0 { + g.write_defer_stmts_when_needed() + if fn_return_is_optional { + styp := g.typ(g.fn_decl.return_type) + g.writeln('return ($styp){0};') + } else { + if g.is_autofree { + g.trace_autofree('// free before return (no values returned)') + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.writeln('return;') + } + return + } + tmpvar := g.new_tmp_var() + ret_typ := g.typ(g.fn_decl.return_type) + mut use_tmp_var := g.defer_stmts.len > 0 || g.defer_profile_code.len > 0 + // handle promoting none/error/function returning 'Option' + if fn_return_is_optional { + optional_none := node.exprs[0] is ast.None + ftyp := g.typ(node.types[0]) + mut is_regular_option := ftyp == 'Option' + if optional_none || is_regular_option || node.types[0] == ast.error_type_idx { + if !isnil(g.fn_decl) && g.fn_decl.is_test { + test_error_var := g.new_tmp_var() + g.write('$ret_typ $test_error_var = ') + g.gen_optional_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + g.write_defer_stmts_when_needed() + g.gen_failing_return_error_for_test_fn(node, test_error_var) + return + } + if use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + g.write('return ') + } + g.gen_optional_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + if use_tmp_var { + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + } + return + } + } + // regular cases + if fn_return_is_multi && node.exprs.len > 0 && !g.expr_is_multi_return_call(node.exprs[0]) { + if node.exprs.len == 1 && node.exprs[0] is ast.IfExpr { + // use a temporary for `return if cond { x,y } else { a,b }` + g.write('$ret_typ $tmpvar = ') + g.expr(node.exprs[0]) + g.writeln(';') + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + return + } + // typ_sym := g.table.get_type_symbol(g.fn_decl.return_type) + // mr_info := typ_sym.info as ast.MultiReturn + mut styp := '' + if fn_return_is_optional { + g.writeln('$ret_typ $tmpvar;') + styp = g.base_type(g.fn_decl.return_type) + g.write('opt_ok(&($styp/*X*/[]) { ') + } else { + if use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + g.write('return ') + } + styp = g.typ(g.fn_decl.return_type) + } + // Use this to keep the tmp assignments in order + mut multi_unpack := '' + g.write('($styp){') + mut arg_idx := 0 + for i, expr in node.exprs { + // Check if we are dealing with a multi return and handle it seperately + if g.expr_is_multi_return_call(expr) { + c := expr as ast.CallExpr + expr_sym := g.table.get_type_symbol(c.return_type) + // Create a tmp for this call + mut tmp := g.new_tmp_var() + if !c.return_type.has_flag(.optional) { + s := g.go_before_stmt(0) + expr_styp := g.typ(c.return_type) + g.write('$expr_styp $tmp=') + g.expr(expr) + g.writeln(';') + multi_unpack += g.go_before_stmt(0) + g.write(s) + } else { + s := g.go_before_stmt(0) + // TODO + // I (emily) am sorry for doing this + // I cant find another way to do this so right now + // this will have to do. + g.tmp_count-- + g.expr(expr) + multi_unpack += g.go_before_stmt(0) + g.write(s) + // modify tmp so that it is the opt deref + // TODO copy-paste from cgen.v:2397 + expr_styp := g.base_type(c.return_type) + tmp = ('/*opt*/(*($expr_styp*)${tmp}.data)') + } + expr_types := expr_sym.mr_info().types + for j, _ in expr_types { + g.write('.arg$arg_idx=${tmp}.arg$j') + if j < expr_types.len || i < node.exprs.len - 1 { + g.write(',') + } + arg_idx++ + } + continue + } + g.write('.arg$arg_idx=') + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + arg_idx++ + if i < node.exprs.len - 1 { + g.write(', ') + } + } + g.write('}') + if fn_return_is_optional { + g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') + g.write_defer_stmts_when_needed() + g.write('return $tmpvar') + } + // Make sure to add our unpacks + if multi_unpack.len > 0 { + g.insert_before_stmt(multi_unpack) + } + if use_tmp_var && !fn_return_is_optional { + if !has_semicolon { + g.writeln(';') + } + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + has_semicolon = true + } + } else if node.exprs.len >= 1 { + // normal return + return_sym := g.table.get_type_symbol(node.types[0]) + expr0 := node.exprs[0] + // `return opt_ok(expr)` for functions that expect an optional + expr_type_is_opt := match expr0 { + ast.CallExpr { + expr0.return_type.has_flag(.optional) && expr0.or_block.kind == .absent + } + else { + node.types[0].has_flag(.optional) + } + } + if fn_return_is_optional && !expr_type_is_opt && return_sym.name != 'Option' { + styp := g.base_type(g.fn_decl.return_type) + g.writeln('$ret_typ $tmpvar;') + g.write('opt_ok(&($styp[]) { ') + if !g.fn_decl.return_type.is_ptr() && node.types[0].is_ptr() { + if !(node.exprs[0] is ast.Ident && !g.is_amp) { + g.write('*') + } + } + for i, expr in node.exprs { + g.expr_with_cast(expr, node.types[i], g.fn_decl.return_type.clear_flag(.optional)) + if i < node.exprs.len - 1 { + g.write(', ') + } + } + g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') + g.write_defer_stmts_when_needed() + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + g.writeln('return $tmpvar;') + return + } + // autofree before `return` + // set free_parent_scopes to true, since all variables defined in parent + // scopes need to be freed before the return + if g.is_autofree { + expr := node.exprs[0] + if expr is ast.Ident { + g.returned_var_name = expr.name + } + } + // free := g.is_autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr + // Create a temporary variable for the return expression + use_tmp_var = use_tmp_var || !g.is_builtin_mod // node.exprs[0] is ast.CallExpr + if use_tmp_var { + // `return foo(a, b, c)` + // `tmp := foo(a, b, c); free(a); free(b); free(c); return tmp;` + // Save return value in a temp var so that all args (a,b,c) can be freed + // Don't use a tmp var if a variable is simply returned: `return x` + // Just in case of defer statements exists, that the return values cannot + // be modified. + if node.exprs[0] !is ast.Ident || use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + use_tmp_var = false + g.write_defer_stmts_when_needed() + if !g.is_builtin_mod { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.write('return ') + } + } else { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + g.write('return ') + } + if expr0.is_auto_deref_var() { + if g.fn_decl.return_type.is_ptr() { + var_str := g.expr_string(expr0) + g.write(var_str.trim('&')) + } else { + g.write('*') + g.expr(expr0) + } + } else { + g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) + } + if use_tmp_var { + g.writeln(';') + has_semicolon = true + g.write_defer_stmts_when_needed() + if !g.is_builtin_mod { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.write('return $tmpvar') + has_semicolon = false + } + } else { // if node.exprs.len == 0 { + println('this should never happen') + g.write('/*F*/return') + } + if !has_semicolon { + g.writeln(';') + } +} + +fn (mut g Gen) const_decl(node ast.ConstDecl) { + g.inside_const = true + defer { + g.inside_const = false + } + for field in node.fields { + if g.pref.skip_unused { + if field.name !in g.table.used_consts { + $if trace_skip_unused_consts ? { + eprintln('>> skipping unused const name: $field.name') + } + continue + } + } + name := c_name(field.name) + field_expr := field.expr + match field.expr { + ast.ArrayInit { + if field.expr.is_fixed { + styp := g.typ(field.expr.typ) + if g.pref.build_mode != .build_module { + val := g.expr_string(field.expr) + g.definitions.writeln('$styp _const_$name = $val; // fixed array const') + } else { + g.definitions.writeln('$styp _const_$name; // fixed array const') + } + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + ast.StringLiteral { + g.definitions.writeln('string _const_$name; // a string literal, inited later') + if g.pref.build_mode != .build_module { + val := g.expr_string(field.expr) + g.stringliterals.writeln('\t_const_$name = $val;') + } + } + ast.CallExpr { + if field.expr.return_type.has_flag(.optional) { + unwrap_option := field.expr.or_block.kind != .absent + g.const_decl_init_later(field.mod, name, field.expr, field.typ, unwrap_option) + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + else { + if g.pref.build_mode != .build_module { + if ct_value := field.comptime_expr_value() { + if g.const_decl_precomputed(field.mod, name, ct_value, field.typ) { + continue + } + } + } + if field.is_simple_define_const() { + // "Simple" expressions are not going to need multiple statements, + // only the ones which are inited later, so it's safe to use expr_string + g.const_decl_simple_define(name, g.expr_string(field_expr)) + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + } + } +} + +fn (mut g Gen) const_decl_precomputed(mod string, name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { + mut styp := g.typ(typ) + cname := '_const_$name' + $if trace_const_precomputed ? { + eprintln('> styp: $styp | cname: $cname | ct_value: $ct_value | $ct_value.type_name()') + } + match ct_value { + i8 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + i16 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + int { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + i64 { + if typ == ast.i64_type { + return false + } + if typ == ast.int_type { + // TODO: use g.const_decl_write_precomputed here too. + // For now, use #define macros, so existing code compiles + // with -cstrict. Add checker errors for overflows instead, + // so V can catch them earlier, instead of relying on the + // C compiler for that. + g.const_decl_simple_define(name, ct_value.str()) + return true + } + if typ == ast.u64_type { + g.const_decl_write_precomputed(styp, cname, ct_value.str() + 'U') + } else { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + } + byte { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u16 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u32 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str() + 'U') + } + f32 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + f64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + rune { + rune_code := u32(ct_value) + if rune_code <= 255 { + if rune_code in [`"`, `\\`, `'`] { + return false + } + escval := util.smart_quote(byte(rune_code).ascii_str(), false) + g.const_decl_write_precomputed(styp, cname, "'$escval'") + } else { + g.const_decl_write_precomputed(styp, cname, u32(ct_value).str()) + } + } + string { + escaped_val := util.smart_quote(ct_value, false) + // g.const_decl_write_precomputed(styp, cname, '_SLIT("$escaped_val")') + // TODO: ^ the above for strings, cause: + // `error C2099: initializer is not a constant` errors in MSVC, + // so fall back to the delayed initialisation scheme: + g.definitions.writeln('$styp $cname; // inited later') + g.inits[mod].writeln('\t$cname = _SLIT("$escaped_val");') + if g.is_autofree { + g.cleanups[mod].writeln('\tstring_free(&$cname);') + } + } + ast.EmptyExpr { + return false + } + } + return true +} + +fn (mut g Gen) const_decl_write_precomputed(styp string, cname string, ct_value string) { + g.definitions.writeln('$styp $cname = $ct_value; // precomputed') +} + +fn (mut g Gen) const_decl_simple_define(name string, val string) { + // Simple expressions should use a #define + // so that we don't pollute the binary with unnecessary global vars + // Do not do this when building a module, otherwise the consts + // will not be accessible. + g.definitions.write_string('#define _const_$name ') + g.definitions.writeln(val) +} + +fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ ast.Type, unwrap_option bool) { + // Initialize more complex consts in `void _vinit/2{}` + // (C doesn't allow init expressions that can't be resolved at compile time). + mut styp := g.typ(typ) + cname := '_const_$name' + g.definitions.writeln('$styp $cname; // inited later') + if cname == '_const_os__args' { + if g.pref.os == .windows { + g.inits[mod].writeln('\t_const_os__args = os__init_os_args_wide(___argc, (byteptr*)___argv);') + } else { + g.inits[mod].writeln('\t_const_os__args = os__init_os_args(___argc, (byte**)___argv);') + } + } else { + if unwrap_option { + g.inits[mod].writeln(g.expr_string_surround('\t$cname = *($styp*)', expr, + '.data;')) + } else { + g.inits[mod].writeln(g.expr_string_surround('\t$cname = ', expr, ';')) + } + } + if g.is_autofree { + sym := g.table.get_type_symbol(typ) + if styp.starts_with('Array_') { + g.cleanups[mod].writeln('\tarray_free(&$cname);') + } else if styp == 'string' { + g.cleanups[mod].writeln('\tstring_free(&$cname);') + } else if sym.kind == .map { + g.cleanups[mod].writeln('\tmap_free(&$cname);') + } else if styp == 'IError' { + g.cleanups[mod].writeln('\tIError_free(&$cname);') + } + } +} + +fn (mut g Gen) global_decl(node ast.GlobalDecl) { + mod := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } else { '' } + key := node.mod // module name + for field in node.fields { + if g.pref.skip_unused { + if field.name !in g.table.used_globals { + $if trace_skip_unused_globals ? { + eprintln('>> skipping unused global name: $field.name') + } + continue + } + } + styp := g.typ(field.typ) + if field.has_expr { + g.definitions.write_string('$mod$styp $field.name') + if field.expr.is_literal() { + g.definitions.writeln(' = ${g.expr_string(field.expr)}; // global') + } else { + g.definitions.writeln(';') + g.global_inits[key].writeln('\t$field.name = ${g.expr_string(field.expr)}; // global') + } + } else { + default_initializer := g.type_default(field.typ) + if default_initializer == '{0}' { + g.definitions.writeln('$mod$styp $field.name = {0}; // global') + } else { + g.definitions.writeln('$mod$styp $field.name; // global') + if field.name !in ['as_cast_type_indexes', 'g_memory_block'] { + g.global_inits[key].writeln('\t$field.name = *($styp*)&(($styp[]){${g.type_default(field.typ)}}[0]); // global') + } + } + } + } +} + +fn (mut g Gen) go_back_out(n int) { + g.out.go_back(n) +} + +const ( + skip_struct_init = ['struct stat', 'struct addrinfo'] +) + +fn (mut g Gen) struct_init(struct_init ast.StructInit) { + styp := g.typ(struct_init.typ) + mut shared_styp := '' // only needed for shared x := St{... + if styp in c.skip_struct_init { + // needed for c++ compilers + g.go_back_out(3) + return + } + sym := g.table.get_final_type_symbol(g.unwrap_generic(struct_init.typ)) + is_amp := g.is_amp + is_multiline := struct_init.fields.len > 5 + g.is_amp = false // reset the flag immediately so that other struct inits in this expr are handled correctly + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared && !g.inside_opt_data && !g.is_arraymap_set { + mut shared_typ := struct_init.typ.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =($styp){') + } else if is_amp || g.inside_cast_in_heap > 0 { + g.write('($styp*)memdup(&($styp){') + } else if struct_init.typ.is_ptr() { + basetyp := g.typ(struct_init.typ.set_nr_muls(0)) + if is_multiline { + g.writeln('&($basetyp){') + } else { + g.write('&($basetyp){') + } + } else { + if is_multiline { + g.writeln('($styp){') + } else { + g.write('($styp){') + } + } + // mut fields := []string{} + mut inited_fields := map[string]int{} // TODO this is done in checker, move to ast node + /* + if struct_init.fields.len == 0 && struct_init.exprs.len > 0 { + // Get fields for {a,b} short syntax. Fields array wasn't set in the parser. + for f in info.fields { + fields << f.name + } + } else { + fields = struct_init.fields + } + */ + if is_multiline { + g.indent++ + } + // User set fields + mut initialized := false + mut old_is_shared := g.is_shared + for i, field in struct_init.fields { + if !field.typ.has_flag(.shared_f) { + g.is_shared = false + } + inited_fields[field.name] = i + if sym.kind != .struct_ { + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + g.write('.$field_name = ') + if field.typ == 0 { + g.checker_bug('struct init, field.typ is 0', field.pos) + } + field_type_sym := g.table.get_type_symbol(field.typ) + mut cloned := false + if g.is_autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] { + g.write('/*clone1*/') + if g.gen_clone_assignment(field.expr, field_type_sym, false) { + cloned = true + } + } + if !cloned { + if (field.expected_type.is_ptr() && !field.expected_type.has_flag(.shared_f)) + && !(field.typ.is_ptr() || field.typ.is_pointer()) && !field.typ.is_number() { + g.write('/* autoref */&') + } + g.expr_with_cast(field.expr, field.typ, field.expected_type) + } + if i != struct_init.fields.len - 1 { + if is_multiline { + g.writeln(',') + } else { + g.write(', ') + } + } + initialized = true + } + g.is_shared = old_is_shared + } + g.is_shared = old_is_shared + // The rest of the fields are zeroed. + // `inited_fields` is a list of fields that have been init'ed, they are skipped + mut nr_fields := 1 + if sym.kind == .struct_ { + info := sym.info as ast.Struct + nr_fields = info.fields.len + if info.is_union && struct_init.fields.len > 1 { + verror('union must not have more than 1 initializer') + } + if !info.is_union { + old_is_shared2 := g.is_shared + mut used_embed_fields := []string{} + init_field_names := info.fields.map(it.name) + // fields that are initialized but belong to the embedding + init_fields_to_embed := struct_init.fields.filter(it.name !in init_field_names) + for embed in info.embeds { + embed_sym := g.table.get_type_symbol(embed) + embed_name := embed_sym.embed_name() + if embed_name !in inited_fields { + embed_info := embed_sym.info as ast.Struct + embed_field_names := embed_info.fields.map(it.name) + fields_to_embed := init_fields_to_embed.filter(it.name !in used_embed_fields + && it.name in embed_field_names) + used_embed_fields << fields_to_embed.map(it.name) + default_init := ast.StructInit{ + typ: embed + fields: fields_to_embed + } + g.write('.$embed_name = ') + g.struct_init(default_init) + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + } + } + g.is_shared = old_is_shared2 + } + // g.zero_struct_fields(info, inited_fields) + // nr_fields = info.fields.len + for mut field in info.fields { + if !field.typ.has_flag(.shared_f) { + g.is_shared = false + } + if mut sym.info is ast.Struct { + mut found_equal_fields := 0 + for mut sifield in sym.info.fields { + if sifield.name == field.name { + found_equal_fields++ + break + } + } + if found_equal_fields == 0 { + continue + } + } + if field.name in inited_fields { + sfield := struct_init.fields[inited_fields[field.name]] + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + if sfield.typ == 0 { + continue + } + g.write('.$field_name = ') + field_type_sym := g.table.get_type_symbol(sfield.typ) + mut cloned := false + if g.is_autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] { + g.write('/*clone1*/') + if g.gen_clone_assignment(sfield.expr, field_type_sym, false) { + cloned = true + } + } + if !cloned { + if field_type_sym.kind == .array_fixed && sfield.expr is ast.Ident { + fixed_array_info := field_type_sym.info as ast.ArrayFixed + g.write('{') + for i in 0 .. fixed_array_info.size { + g.expr(sfield.expr) + g.write('[$i]') + if i != fixed_array_info.size - 1 { + g.write(', ') + } + } + g.write('}') + } else { + if (sfield.expected_type.is_ptr() + && !sfield.expected_type.has_flag(.shared_f)) && !(sfield.typ.is_ptr() + || sfield.typ.is_pointer()) && !sfield.typ.is_number() { + g.write('/* autoref */&') + } + g.expr_with_cast(sfield.expr, sfield.typ, sfield.expected_type) + } + } + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + continue + } + if info.is_union { + // unions thould have exactly one explicit initializer + continue + } + if field.typ.has_flag(.optional) { + field_name := c_name(field.name) + g.write('.$field_name = {EMPTY_STRUCT_INITIALIZATION},') + initialized = true + continue + } + if field.typ in info.embeds { + continue + } + if struct_init.has_update_expr { + g.expr(struct_init.update_expr) + if struct_init.update_expr_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(field.name) + } else { + if !g.zero_struct_field(field) { + nr_fields-- + continue + } + } + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + g.is_shared = old_is_shared + } + g.is_shared = old_is_shared + } + if is_multiline { + g.indent-- + } + + if !initialized { + if nr_fields > 0 { + g.write('0') + } else { + g.write('EMPTY_STRUCT_INITIALIZATION') + } + } + + g.write('}') + if g.is_shared && !g.inside_opt_data && !g.is_arraymap_set { + g.write('}, sizeof($shared_styp))') + } else if is_amp || g.inside_cast_in_heap > 0 { + g.write(', sizeof($styp))') + } +} + +fn (mut g Gen) zero_struct_field(field ast.StructField) bool { + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if info.fields.len == 0 { + return false + } + } + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + g.write('.$field_name = ') + if field.has_default_expr { + if sym.kind in [.sum_type, .interface_] { + g.expr_with_cast(field.default_expr, field.default_expr_typ, field.typ) + return true + } + g.expr(field.default_expr) + } else { + g.write(g.type_default(field.typ)) + } + return true +} + +// fn (mut g Gen) zero_struct_fields(info ast.Struct, inited_fields map[string]int) { +// } +// { user | name: 'new name' } +fn (mut g Gen) assoc(node ast.Assoc) { + g.writeln('// assoc') + if node.typ == 0 { + return + } + styp := g.typ(node.typ) + g.writeln('($styp){') + mut inited_fields := map[string]int{} + for i, field in node.fields { + inited_fields[field] = i + } + // Merge inited_fields in the rest of the fields. + sym := g.table.get_type_symbol(node.typ) + info := sym.info as ast.Struct + for field in info.fields { + field_name := c_name(field.name) + if field.name in inited_fields { + g.write('\t.$field_name = ') + g.expr(node.exprs[inited_fields[field.name]]) + g.writeln(', ') + } else { + g.writeln('\t.$field_name = ${node.var_name}.$field_name,') + } + } + g.write('}') + if g.is_amp { + g.write(', sizeof($styp))') + } +} + +[noreturn] +fn verror(s string) { + util.verror('cgen error', s) +} + +[noreturn] +fn (g &Gen) error(s string, pos token.Position) { + ferror := util.formatted_error('cgen error:', s, g.file.path, pos) + eprintln(ferror) + exit(1) +} + +fn (g &Gen) checker_bug(s string, pos token.Position) { + g.error('checker bug; $s', pos) +} + +fn (mut g Gen) write_init_function() { + if g.pref.is_liveshared { + return + } + fn_vinit_start_pos := g.out.len + // ___argv is declared as voidptr here, because that unifies the windows/unix logic + g.writeln('void _vinit(int ___argc, voidptr ___argv) {') + if g.pref.prealloc { + g.writeln('prealloc_vinit();') + } + // NB: the as_cast table should be *before* the other constant initialize calls, + // because it may be needed during const initialization of builtin and during + // calling module init functions too, just in case they do fail... + g.write('\tas_cast_type_indexes = ') + g.writeln(g.as_cast_name_table()) + // + g.writeln('\tbuiltin_init();') + g.writeln('\tvinit_string_literals();') + // + for mod_name in g.table.modules { + g.writeln('\t// Initializations for module $mod_name :') + g.write(g.inits[mod_name].str()) + g.write(g.global_inits[mod_name].str()) + init_fn_name := '${mod_name}.init' + if initfn := g.table.find_fn(init_fn_name) { + if initfn.return_type == ast.void_type && initfn.params.len == 0 { + mod_c_name := util.no_dots(mod_name) + init_fn_c_name := '${mod_c_name}__init' + g.writeln('\t${init_fn_c_name}();') + } + } + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list { + println(g.out.after(fn_vinit_start_pos)) + } + // + fn_vcleanup_start_pos := g.out.len + g.writeln('void _vcleanup() {') + if g.is_autofree { + // g.writeln('puts("cleaning up...");') + reversed_table_modules := g.table.modules.reverse() + for mod_name in reversed_table_modules { + g.writeln('\t// Cleanups for module $mod_name :') + g.writeln(g.cleanups[mod_name].str()) + } + g.writeln('\tarray_free(&as_cast_type_indexes);') + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { + println(g.out.after(fn_vcleanup_start_pos)) + } + // + needs_constructor := g.pref.is_shared && g.pref.os != .windows + if needs_constructor { + // shared libraries need a way to call _vinit/2. For that purpose, + // provide a constructor/destructor pair, ensuring that all constants + // are initialized just once, and that they will be freed too. + // NB: os.args in this case will be []. + g.writeln('__attribute__ ((constructor))') + g.writeln('void _vinit_caller() {') + g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') + g.writeln('\t_vinit(0,0);') + g.writeln('}') + + g.writeln('__attribute__ ((destructor))') + g.writeln('void _vcleanup_caller() {') + g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') + g.writeln('\t_vcleanup();') + g.writeln('}') + } +} + +const ( + builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', 'Option'] +) + +fn (mut g Gen) write_builtin_types() { + mut builtin_types := []ast.TypeSymbol{} // builtin types + // builtin types need to be on top + // everything except builtin will get sorted + for builtin_name in c.builtins { + sym := g.table.type_symbols[g.table.type_idxs[builtin_name]] + if sym.kind == .interface_ { + g.write_interface_typedef(sym) + g.write_interface_typesymbol_declaration(sym) + } else { + builtin_types << sym + } + } + g.write_types(builtin_types) +} + +// C struct definitions, ordered +// Sort the types, make sure types that are referenced by other types +// are added before them. +fn (mut g Gen) write_sorted_types() { + mut types := []ast.TypeSymbol{} // structs that need to be sorted + for typ in g.table.type_symbols { + if typ.name !in c.builtins { + types << typ + } + } + // sort structs + types_sorted := g.sort_structs(types) + // Generate C code + g.type_definitions.writeln('// builtin types:') + g.type_definitions.writeln('//------------------ #endbuiltin') + g.write_types(types_sorted) +} + +fn (mut g Gen) write_types(types []ast.TypeSymbol) { + for typ in types { + if typ.name.starts_with('C.') { + continue + } + // sym := g.table.get_type_symbol(typ) + mut name := typ.cname + match mut typ.info { + ast.Struct { + if typ.info.is_generic { + continue + } + if name.contains('_T_') { + g.typedefs.writeln('typedef struct $name $name;') + } + // TODO avoid buffer manip + start_pos := g.type_definitions.len + + mut pre_pragma := '' + mut post_pragma := '' + + for attr in typ.info.attrs { + match attr.name { + '_pack' { + pre_pragma += '#pragma pack(push, $attr.arg)\n' + post_pragma += '#pragma pack(pop)' + } + else {} + } + } + + g.type_definitions.writeln(pre_pragma) + + if typ.info.is_union { + g.type_definitions.writeln('union $name {') + } else { + g.type_definitions.writeln('struct $name {') + } + if typ.info.fields.len > 0 || typ.info.embeds.len > 0 { + for field in typ.info.fields { + // Some of these structs may want to contain + // optionals that may not be defined at this point + // if this is the case then we are going to + // buffer manip out in front of the struct + // write the optional in and then continue + if field.typ.has_flag(.optional) { + // Dont use g.typ() here becuase it will register + // optional and we dont want that + styp, base := g.optional_type_name(field.typ) + if styp !in g.optionals { + last_text := g.type_definitions.cut_to(start_pos).clone() + g.optionals << styp + g.typedefs2.writeln('typedef struct $styp $styp;') + g.type_definitions.writeln('${g.optional_type_text(styp, + base)};') + g.type_definitions.write_string(last_text) + } + } + type_name := g.typ(field.typ) + field_name := c_name(field.name) + g.type_definitions.writeln('\t$type_name $field_name;') + } + } else { + g.type_definitions.writeln('\tEMPTY_STRUCT_DECLARATION;') + } + // g.type_definitions.writeln('} $name;\n') + // + ti_attrs := if typ.info.attrs.contains('packed') { + '__attribute__((__packed__))' + } else { + '' + } + g.type_definitions.writeln('}$ti_attrs;\n') + g.type_definitions.writeln(post_pragma) + } + ast.Alias { + // ast.Alias { TODO + } + ast.Thread { + if g.pref.os == .windows { + if name == '__v_thread' { + g.type_definitions.writeln('typedef HANDLE $name;') + } else { + // Windows can only return `u32` (no void*) from a thread, so the + // V gohandle must maintain a pointer to the return value + g.type_definitions.writeln('typedef struct {') + g.type_definitions.writeln('\tvoid* ret_ptr;') + g.type_definitions.writeln('\tHANDLE handle;') + g.type_definitions.writeln('} $name;') + } + } else { + if !g.pref.is_bare { + g.type_definitions.writeln('typedef pthread_t $name;') + } + } + } + ast.SumType { + if typ.info.is_generic { + continue + } + g.typedefs.writeln('typedef struct $name $name;') + g.type_definitions.writeln('') + g.type_definitions.writeln('// Union sum type $name = ') + for variant in typ.info.variants { + g.type_definitions.writeln('// | ${variant:4d} = ${g.typ(variant.idx()):-20s}') + } + g.type_definitions.writeln('struct $name {') + g.type_definitions.writeln('\tunion {') + for variant in typ.info.variants { + variant_sym := g.table.get_type_symbol(variant) + g.type_definitions.writeln('\t\t${g.typ(variant.to_ptr())} _$variant_sym.cname;') + } + g.type_definitions.writeln('\t};') + g.type_definitions.writeln('\tint _typ;') + if typ.info.fields.len > 0 { + g.writeln('\t// pointers to common sumtype fields') + for field in typ.info.fields { + g.type_definitions.writeln('\t${g.typ(field.typ.to_ptr())} $field.name;') + } + } + g.type_definitions.writeln('};') + g.type_definitions.writeln('') + } + ast.ArrayFixed { + elem_sym := g.table.get_type_symbol(typ.info.elem_type) + if !elem_sym.is_builtin() && !typ.info.elem_type.has_flag(.generic) { + // .array_fixed { + styp := typ.cname + // array_fixed_char_300 => char x[300] + // [16]&&&EventListener{} => Array_fixed_main__EventListener_16_ptr3 + // => typedef main__EventListener*** Array_fixed_main__EventListener_16_ptr3 [16] + mut fixed_elem_name := g.typ(typ.info.elem_type.set_nr_muls(0)) + if typ.info.elem_type.is_ptr() { + fixed_elem_name += '*'.repeat(typ.info.elem_type.nr_muls()) + } + len := typ.info.size + if fixed_elem_name.starts_with('C__') { + fixed_elem_name = fixed_elem_name[3..] + } + if elem_sym.info is ast.FnType { + pos := g.out.len + g.write_fn_ptr_decl(&elem_sym.info, '') + fixed_elem_name = g.out.cut_to(pos) + mut def_str := 'typedef $fixed_elem_name;' + def_str = def_str.replace_once('(*)', '(*$styp[$len])') + g.type_definitions.writeln(def_str) + } else { + g.type_definitions.writeln('typedef $fixed_elem_name $styp [$len];') + } + } + } + else {} + } + } +} + +// sort structs by dependant fields +fn (g &Gen) sort_structs(typesa []ast.TypeSymbol) []ast.TypeSymbol { + mut dep_graph := depgraph.new_dep_graph() + // types name list + mut type_names := []string{} + for typ in typesa { + type_names << typ.name + } + // loop over types + for t in typesa { + if t.kind == .interface_ { + dep_graph.add(t.name, []) + continue + } + // create list of deps + mut field_deps := []string{} + match mut t.info { + ast.ArrayFixed { + dep := g.table.get_type_symbol(t.info.elem_type).name + if dep in type_names { + field_deps << dep + } + } + ast.Struct { + for embed in t.info.embeds { + dep := g.table.get_type_symbol(embed).name + // skip if not in types list or already in deps + if dep !in type_names || dep in field_deps { + continue + } + field_deps << dep + } + for field in t.info.fields { + dep := g.table.get_type_symbol(field.typ).name + // skip if not in types list or already in deps + if dep !in type_names || dep in field_deps || field.typ.is_ptr() { + continue + } + field_deps << dep + } + } + // ast.Interface {} + else {} + } + // add type and dependant types to graph + dep_graph.add(t.name, field_deps) + } + // sort graph + dep_graph_sorted := dep_graph.resolve() + if !dep_graph_sorted.acyclic { + // this should no longer be called since it's catched in the parser + // TODO: should it be removed? + verror('cgen.sort_structs(): the following structs form a dependency cycle:\n' + + dep_graph_sorted.display_cycles() + + '\nyou can solve this by making one or both of the dependant struct fields references, eg: field &MyStruct' + + '\nif you feel this is an error, please create a new issue here: https://github.com/vlang/v/issues and tag @joe-conigliaro') + } + // sort types + mut types_sorted := []ast.TypeSymbol{} + for node in dep_graph_sorted.nodes { + types_sorted << g.table.type_symbols[g.table.type_idxs[node.name]] + } + return types_sorted +} + +[inline] +fn (g &Gen) nth_stmt_pos(n int) int { + return g.stmt_path_pos[g.stmt_path_pos.len - (1 + n)] +} + +fn (mut g Gen) go_before_stmt(n int) string { + stmt_pos := g.nth_stmt_pos(n) + return g.out.cut_to(stmt_pos) +} + +[inline] +fn (mut g Gen) go_before_ternary() string { + return g.go_before_stmt(g.inside_ternary) +} + +fn (mut g Gen) insert_before_stmt(s string) { + cur_line := g.go_before_stmt(0) + g.writeln(s) + g.write(cur_line) +} + +fn (mut g Gen) insert_at(pos int, s string) { + cur_line := g.out.cut_to(pos) + g.writeln(s) + g.write(cur_line) +} + +// fn (mut g Gen) start_tmp() { +// } +// If user is accessing the return value eg. in assigment, pass the variable name. +// If the user is not using the optional return value. We need to pass a temp var +// to access its fields (`.ok`, `.error` etc) +// `os.cp(...)` => `Option bool tmp = os__cp(...); if (tmp.state != 0) { ... }` +// Returns the type of the last stmt +fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) { + cvar_name := c_name(var_name) + mr_styp := g.base_type(return_type) + is_none_ok := return_type == ast.ovoid_type + g.writeln(';') + if is_none_ok { + g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {') + } else { + g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') + } + if or_block.kind == .block { + if g.inside_or_block { + g.writeln('\terr = ${cvar_name}.err;') + } else { + g.writeln('\tIError err = ${cvar_name}.err;') + } + g.inside_or_block = true + defer { + g.inside_or_block = false + } + stmts := or_block.stmts + if stmts.len > 0 && stmts[or_block.stmts.len - 1] is ast.ExprStmt + && (stmts[stmts.len - 1] as ast.ExprStmt).typ != ast.void_type { + g.indent++ + for i, stmt in stmts { + if i == stmts.len - 1 { + expr_stmt := stmt as ast.ExprStmt + g.stmt_path_pos << g.out.len + g.write('*($mr_styp*) ${cvar_name}.data = ') + old_inside_opt_data := g.inside_opt_data + g.inside_opt_data = true + g.expr_with_cast(expr_stmt.expr, expr_stmt.typ, return_type.clear_flag(.optional)) + g.inside_opt_data = old_inside_opt_data + if g.inside_ternary == 0 { + g.writeln(';') + } + g.stmt_path_pos.delete_last() + } else { + g.stmt(stmt) + } + } + g.indent-- + } else { + g.stmts(stmts) + if stmts.len > 0 && stmts[or_block.stmts.len - 1] is ast.ExprStmt { + g.writeln(';') + } + } + } else if or_block.kind == .propagate { + if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.is_main) { + // In main(), an `opt()?` call is sugar for `opt() or { panic(err) }` + if g.pref.is_debug { + paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) + g.writeln('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *${cvar_name}.err.msg );') + } else { + g.writeln('\tpanic_optional_not_set(*${cvar_name}.err.msg);') + } + } else if !isnil(g.fn_decl) && g.fn_decl.is_test { + g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name) + } else { + // In ordinary functions, `opt()?` call is sugar for: + // `opt() or { return err }` + // Since we *do* return, first we have to ensure that + // the defered statements are generated. + g.write_defer_stmts() + // Now that option types are distinct we need a cast here + if g.fn_decl.return_type == ast.void_type { + g.writeln('\treturn;') + } else { + styp := g.typ(g.fn_decl.return_type) + err_obj := g.new_tmp_var() + g.writeln('\t$styp $err_obj;') + g.writeln('\tmemcpy(&$err_obj, &$cvar_name, sizeof(Option));') + g.writeln('\treturn $err_obj;') + } + } + } + g.writeln('}') + g.stmt_path_pos << g.out.len +} + +[inline] +fn c_name(name_ string) string { + name := util.no_dots(name_) + if name in c.c_reserved_map { + return '_v_$name' + } + return name +} + +fn (mut g Gen) type_default(typ_ ast.Type) string { + typ := g.unwrap_generic(typ_) + if typ.has_flag(.optional) { + return '{0}' + } + // Always set pointers to 0 + if typ.is_ptr() && !typ.has_flag(.shared_f) { + return '0' + } + if typ.idx() < ast.string_type_idx { + // Default values for other types are not needed because of mandatory initialization + return '0' + } + sym := g.table.get_type_symbol(typ) + match sym.kind { + .string { + return '(string){.str=(byteptr)"", .is_lit=1}' + } + .interface_, .sum_type, .array_fixed, .multi_return { + return '{0}' + } + .alias { + return g.type_default((sym.info as ast.Alias).parent_type) + } + .chan { + elem_type := sym.chan_info().elem_type + elemtypstr := g.typ(elem_type) + noscan := g.check_noscan(elem_type) + return 'sync__new_channel_st${noscan}(0, sizeof($elemtypstr))' + } + .array { + elem_typ := sym.array_info().elem_type + elem_sym := g.typ(elem_typ) + mut elem_type_str := util.no_dots(elem_sym) + if elem_type_str.starts_with('C__') { + elem_type_str = elem_type_str[3..] + } + noscan := g.check_noscan(elem_typ) + init_str := '__new_array${noscan}(0, 0, sizeof($elem_type_str))' + if typ.has_flag(.shared_f) { + atyp := '__shared__Array_${g.table.get_type_symbol(elem_typ).cname}' + return '($atyp*)__dup_shared_array(&($atyp){.mtx = {0}, .val =$init_str}, sizeof($atyp))' + } else { + return init_str + } + } + .map { + info := sym.map_info() + key_typ := g.table.get_type_symbol(info.key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) + noscan_key := g.check_noscan(info.key_type) + noscan_value := g.check_noscan(info.value_type) + mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } + if noscan.len != 0 { + if noscan_key.len != 0 { + noscan += '_key' + } + if noscan_value.len != 0 { + noscan += '_value' + } + } + init_str := 'new_map${noscan}(sizeof(${g.typ(info.key_type)}), sizeof(${g.typ(info.value_type)}), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)' + if typ.has_flag(.shared_f) { + mtyp := '__shared__Map_${key_typ.cname}_${g.table.get_type_symbol(info.value_type).cname}' + return '($mtyp*)__dup_shared_map(&($mtyp){.mtx = {0}, .val =$init_str}, sizeof($mtyp))' + } else { + return init_str + } + } + .struct_ { + mut has_none_zero := false + mut init_str := '{' + info := sym.info as ast.Struct + typ_is_shared_f := typ.has_flag(.shared_f) + if sym.language == .v && !typ_is_shared_f { + for field in info.fields { + field_sym := g.table.get_type_symbol(field.typ) + if field.has_default_expr + || field_sym.kind in [.array, .map, .string, .bool, .alias, .size_t, .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .char, .voidptr, .byteptr, .charptr, .struct_] { + field_name := c_name(field.name) + if field.has_default_expr { + expr_str := g.expr_string(field.default_expr) + init_str += '.$field_name = $expr_str,' + } else { + init_str += '.$field_name = ${g.type_default(field.typ)},' + } + has_none_zero = true + } + } + } + if has_none_zero { + init_str += '}' + type_name := g.typ(typ) + init_str = '($type_name)' + init_str + } else { + init_str += '0}' + } + if typ.has_flag(.shared_f) { + styp := '__shared__${g.table.get_type_symbol(typ).cname}' + return '($styp*)__dup${styp}(&($styp){.mtx = {0}, .val =$init_str}, sizeof($styp))' + } else { + return init_str + } + } + else { + return '0' + } + } +} + +fn (g &Gen) get_all_test_function_names() []string { + mut tfuncs := []string{} + mut tsuite_begin := '' + mut tsuite_end := '' + for _, f in g.table.fns { + if f.name.ends_with('.testsuite_begin') { + tsuite_begin = f.name + continue + } + if f.name.contains('.test_') { + tfuncs << f.name + continue + } + if f.name.ends_with('.testsuite_end') { + tsuite_end = f.name + continue + } + } + mut all_tfuncs := []string{} + if tsuite_begin.len > 0 { + all_tfuncs << tsuite_begin + } + all_tfuncs << tfuncs + if tsuite_end.len > 0 { + all_tfuncs << tsuite_end + } + return all_tfuncs +} + +fn (g &Gen) is_importing_os() bool { + return 'os' in g.table.imports +} + +fn (mut g Gen) go_expr(node ast.GoExpr) { + line := g.go_before_stmt(0) + mut handle := '' + tmp := g.new_tmp_var() + mut expr := node.call_expr + mut name := expr.name // util.no_dots(expr.name) + // TODO: fn call is duplicated. merge with fn_call(). + for i, concrete_type in expr.concrete_types { + if concrete_type != ast.void_type && concrete_type != 0 { + // Using _T_ to differentiate between get and get_string + // `foo()` => `foo_T_int()` + if i == 0 { + name += '_T' + } + name += '_' + g.typ(concrete_type) + } + } + if expr.is_method { + receiver_sym := g.table.get_type_symbol(expr.receiver_type) + name = receiver_sym.name + '_' + name + } else if mut expr.left is ast.AnonFn { + g.gen_anon_fn_decl(mut expr.left) + name = expr.left.decl.name + } + name = util.no_dots(name) + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + mut key := expr.name + if expr.is_method { + sym := g.table.get_type_symbol(expr.receiver_type) + key = sym.name + '.' + expr.name + } + g.write('/* obf go: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + g.writeln('// go') + wrapper_struct_name := 'thread_arg_' + name + wrapper_fn_name := name + '_thread_wrapper' + arg_tmp_var := 'arg_' + tmp + g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));') + if expr.is_method { + g.write('$arg_tmp_var->arg0 = ') + // TODO is this needed? + /* + if false && !expr.return_type.is_ptr() { + g.write('&') + } + */ + g.expr(expr.left) + g.writeln(';') + } + for i, arg in expr.args { + g.write('$arg_tmp_var->arg${i + 1} = ') + g.expr(arg.expr) + g.writeln(';') + } + s_ret_typ := g.typ(node.call_expr.return_type) + if g.pref.os == .windows && node.call_expr.return_type != ast.void_type { + g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));') + } + is_opt := node.call_expr.return_type.has_flag(.optional) + mut gohandle_name := '' + if node.call_expr.return_type == ast.void_type { + gohandle_name = if is_opt { '__v_thread_Option_void' } else { '__v_thread' } + } else { + opt := if is_opt { 'Option_' } else { '' } + gohandle_name = '__v_thread_$opt${g.table.get_type_symbol(g.unwrap_generic(node.call_expr.return_type)).cname}' + } + if g.pref.os == .windows { + simple_handle := if node.is_expr && node.call_expr.return_type != ast.void_type { + 'thread_handle_$tmp' + } else { + 'thread_$tmp' + } + g.writeln('HANDLE $simple_handle = CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);') + g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));') + if node.is_expr && node.call_expr.return_type != ast.void_type { + g.writeln('$gohandle_name thread_$tmp = {') + g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,') + g.writeln('\t.handle = thread_handle_$tmp') + g.writeln('};') + } + if !node.is_expr { + g.writeln('CloseHandle(thread_$tmp);') + } + } else { + g.writeln('pthread_t thread_$tmp;') + g.writeln('int ${tmp}_thr_res = pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);') + g.writeln('if (${tmp}_thr_res) panic_error_number(tos3("`go ${name}()`: "), ${tmp}_thr_res);') + if !node.is_expr { + g.writeln('pthread_detach(thread_$tmp);') + } + } + g.writeln('// endgo\n') + if node.is_expr { + handle = 'thread_$tmp' + // create wait handler for this return type if none exists + waiter_fn_name := gohandle_name + '_wait' + if waiter_fn_name !in g.waiter_fns { + g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {') + mut c_ret_ptr_ptr := 'NULL' + if node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;') + c_ret_ptr_ptr = '&ret_ptr' + } + if g.pref.os == .windows { + if node.call_expr.return_type == ast.void_type { + g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);') + } else { + g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);') + g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;') + } + } else { + g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)$c_ret_ptr_ptr);') + } + g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }') + if g.pref.os == .windows { + if node.call_expr.return_type == ast.void_type { + g.gowrappers.writeln('\tCloseHandle(thread);') + } else { + g.gowrappers.writeln('\tCloseHandle(thread.handle);') + } + } + if node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;') + g.gowrappers.writeln('\tfree(ret_ptr);') + g.gowrappers.writeln('\treturn ret;') + } else { + g.gowrappers.writeln('\treturn;') + } + g.gowrappers.writeln('}') + g.waiter_fns << waiter_fn_name + } + } + // Register the wrapper type and function + if name !in g.threaded_fns { + g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {') + if expr.is_method { + styp := g.typ(expr.receiver_type) + g.type_definitions.writeln('\t$styp arg0;') + } + need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != ast.void_type + if expr.args.len == 0 && !need_return_ptr { + g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;') + } else { + for i, arg in expr.args { + styp := g.typ(arg.typ) + g.type_definitions.writeln('\t$styp arg${i + 1};') + } + } + if need_return_ptr { + g.type_definitions.writeln('\tvoid* ret_ptr;') + } + g.type_definitions.writeln('} $wrapper_struct_name;') + thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' } + g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);') + g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {') + if node.call_expr.return_type != ast.void_type { + if g.pref.os == .windows { + g.gowrappers.write_string('\t*(($s_ret_typ*)(arg->ret_ptr)) = ') + } else { + g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));') + g.gowrappers.write_string('\t*ret_ptr = ') + } + } else { + g.gowrappers.write_string('\t') + } + if expr.is_method { + unwrapped_rec_type := g.unwrap_generic(expr.receiver_type) + typ_sym := g.table.get_type_symbol(unwrapped_rec_type) + if typ_sym.kind == .interface_ + && (typ_sym.info as ast.Interface).defines_method(expr.name) { + rec_cc_type := g.cc_type(unwrapped_rec_type, false) + receiver_type_name := util.no_dots(rec_cc_type) + g.gowrappers.write_string('${c_name(receiver_type_name)}_name_table[') + g.gowrappers.write_string('arg->arg0') + dot := if expr.left_type.is_ptr() { '->' } else { '.' } + mname := c_name(expr.name) + g.gowrappers.write_string('${dot}_typ]._method_${mname}(') + g.gowrappers.write_string('arg->arg0') + g.gowrappers.write_string('${dot}_object') + } else { + g.gowrappers.write_string('${name}(') + g.gowrappers.write_string('arg->arg0') + } + if expr.args.len > 0 { + g.gowrappers.write_string(', ') + } + } else { + g.gowrappers.write_string('${name}(') + } + if expr.args.len > 0 { + mut has_cast := false + for i in 0 .. expr.args.len { + if g.table.get_type_symbol(expr.expected_arg_types[i]).kind == .interface_ + && g.table.get_type_symbol(expr.args[i].typ).kind != .interface_ { + has_cast = true + break + } + } + if has_cast { + pos := g.out.len + g.call_args(expr) + mut call_args_str := g.out.after(pos) + g.out.go_back(call_args_str.len) + mut rep_group := []string{cap: 2 * expr.args.len} + for i in 0 .. expr.args.len { + rep_group << g.expr_string(expr.args[i].expr) + rep_group << 'arg->arg${i + 1}' + } + call_args_str = call_args_str.replace_each(rep_group) + g.gowrappers.write_string(call_args_str) + } else { + for i in 0 .. expr.args.len { + g.gowrappers.write_string('arg->arg${i + 1}') + if i != expr.args.len - 1 { + g.gowrappers.write_string(', ') + } + } + } + } + g.gowrappers.writeln(');') + g.gowrappers.writeln('\tfree(arg);') + if g.pref.os != .windows && node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\treturn ret_ptr;') + } else { + g.gowrappers.writeln('\treturn 0;') + } + g.gowrappers.writeln('}') + g.threaded_fns << name + } + if node.is_expr { + g.empty_line = false + g.write(line) + g.write(handle) + } +} + +fn (mut g Gen) as_cast(node ast.AsCast) { + // Make sure the sum type can be cast to this type (the types + // are the same), otherwise panic. + // g.insert_before(' + styp := g.typ(node.typ) + sym := g.table.get_type_symbol(node.typ) + mut expr_type_sym := g.table.get_type_symbol(node.expr_type) + if mut expr_type_sym.info is ast.SumType { + dot := if node.expr_type.is_ptr() { '->' } else { '.' } + g.write('/* as */ *($styp*)__as_cast(') + g.write('(') + g.expr(node.expr) + g.write(')') + g.write(dot) + g.write('_$sym.cname,') + g.write('(') + g.expr(node.expr) + g.write(')') + g.write(dot) + // g.write('typ, /*expected:*/$node.typ)') + sidx := g.type_sidx(node.typ) + expected_sym := g.table.get_type_symbol(node.typ) + g.write('_typ, $sidx) /*expected idx: $sidx, name: $expected_sym.name */ ') + + // fill as cast name table + for variant in expr_type_sym.info.variants { + idx := u32(variant).str() + if idx in g.as_cast_type_names { + continue + } + variant_sym := g.table.get_type_symbol(variant) + g.as_cast_type_names[idx] = variant_sym.name + } + } else if expr_type_sym.kind == .interface_ && sym.kind == .interface_ { + g.write('I_${expr_type_sym.cname}_as_I_${sym.cname}(') + if node.expr_type.is_ptr() { + g.write('*') + } + g.expr(node.expr) + g.write(')') + + mut info := expr_type_sym.info as ast.Interface + if node.typ !in info.conversions { + left_variants := g.table.iface_types[expr_type_sym.name] + right_variants := g.table.iface_types[sym.name] + info.conversions[node.typ] = left_variants.filter(it in right_variants) + } + expr_type_sym.info = info + } else { + g.expr(node.expr) + } +} + +fn (g Gen) as_cast_name_table() string { + if g.as_cast_type_names.len == 0 { + return 'new_array_from_c_array(1, 1, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[1]){(VCastTypeIndexName){.tindex = 0,.tname = _SLIT("unknown")}}));\n' + } + mut name_ast := strings.new_builder(1024) + casts_len := g.as_cast_type_names.len + 1 + name_ast.writeln('new_array_from_c_array($casts_len, $casts_len, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[$casts_len]){') + name_ast.writeln('\t\t (VCastTypeIndexName){.tindex = 0, .tname = _SLIT("unknown")}') + for key, value in g.as_cast_type_names { + name_ast.writeln('\t\t, (VCastTypeIndexName){.tindex = $key, .tname = _SLIT("$value")}') + } + name_ast.writeln('\t}));\n') + return name_ast.str() +} + +// Generates interface table and interface indexes +fn (mut g Gen) interface_table() string { + mut sb := strings.new_builder(100) + mut conversion_functions := strings.new_builder(100) + for ityp in g.table.type_symbols { + if ityp.kind != .interface_ { + continue + } + inter_info := ityp.info as ast.Interface + if inter_info.is_generic { + continue + } + // interface_name is for example Speaker + interface_name := ityp.cname + // generate a struct that references interface methods + methods_struct_name := 'struct _${interface_name}_interface_methods' + mut methods_struct_def := strings.new_builder(100) + methods_struct_def.writeln('$methods_struct_name {') + mut methodidx := map[string]int{} + for k, method in inter_info.methods { + methodidx[method.name] = k + ret_styp := g.typ(method.return_type) + methods_struct_def.write_string('\t$ret_styp (*_method_${c_name(method.name)})(void* _') + // the first param is the receiver, it's handled by `void*` above + for i in 1 .. method.params.len { + arg := method.params[i] + methods_struct_def.write_string(', ${g.typ(arg.typ)} $arg.name') + } + // TODO g.fn_args(method.args[1..]) + methods_struct_def.writeln(');') + } + methods_struct_def.writeln('};') + // generate an array of the interface methods for the structs using the interface + // as well as case functions from the struct to the interface + mut methods_struct := strings.new_builder(100) + // + iname_table_length := inter_info.types.len + if iname_table_length == 0 { + // msvc can not process `static struct x[0] = {};` + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[1];') + } else { + if g.pref.build_mode != .build_module { + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[$iname_table_length] = {') + } else { + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[$iname_table_length];') + } + } + mut cast_functions := strings.new_builder(100) + mut methods_wrapper := strings.new_builder(100) + methods_wrapper.writeln('// Methods wrapper for interface "$interface_name"') + mut already_generated_mwrappers := map[string]int{} + iinidx_minimum_base := 1000 // NB: NOT 0, to avoid map entries set to 0 later, so `if already_generated_mwrappers[name] > 0 {` works. + mut current_iinidx := iinidx_minimum_base + for st in inter_info.types { + st_sym := g.table.get_type_symbol(st) + // cctype is the Cleaned Concrete Type name, *without ptr*, + // i.e. cctype is always just Cat, not Cat_ptr: + cctype := g.cc_type(st, true) + $if debug_interface_table ? { + eprintln( + '>> interface name: $ityp.name | concrete type: $st.debug() | st symname: ' + + st_sym.name) + } + // Speaker_Cat_index = 0 + interface_index_name := '_${interface_name}_${cctype}_index' + if already_generated_mwrappers[interface_index_name] > 0 { + continue + } + already_generated_mwrappers[interface_index_name] = current_iinidx + current_iinidx++ + if ityp.name != 'vweb.DbInterface' { // TODO remove this + // eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name') + sb.writeln('static $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x);') + mut cast_struct := strings.new_builder(100) + cast_struct.writeln('($interface_name) {') + cast_struct.writeln('\t\t._$cctype = x,') + cast_struct.writeln('\t\t._typ = $interface_index_name,') + for field in inter_info.fields { + cname := c_name(field.name) + field_styp := g.typ(field.typ) + if _ := st_sym.find_field(field.name) { + cast_struct.writeln('\t\t.$cname = ($field_styp*)((char*)x + __offsetof_ptr(x, $cctype, $cname)),') + } else { + // the field is embedded in another struct + cast_struct.write_string('\t\t.$cname = ($field_styp*)((char*)x') + if st == ast.voidptr_type { + cast_struct.write_string('/*.... ast.voidptr_type */') + } else { + for embed_type in st_sym.struct_info().embeds { + embed_sym := g.table.get_type_symbol(embed_type) + if _ := embed_sym.find_field(field.name) { + cast_struct.write_string(' + __offsetof_ptr(x, $cctype, $embed_sym.embed_name()) + __offsetof_ptr(x, $embed_sym.cname, $cname)') + break + } + } + } + cast_struct.writeln('),') + } + } + cast_struct.write_string('\t}') + cast_struct_str := cast_struct.str() + + cast_functions.writeln(' +// Casting functions for converting "$cctype" to interface "$interface_name" +static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x) { + return $cast_struct_str; +}') + } + + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t{') + } + if st == ast.voidptr_type { + for mname, _ in methodidx { + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t\t._method_${c_name(mname)} = (void*) 0,') + } + } + } + mut methods := st_sym.methods + match st_sym.info { + ast.Struct, ast.Interface, ast.SumType { + if st_sym.info.parent_type.has_flag(.generic) { + parent_sym := g.table.get_type_symbol(st_sym.info.parent_type) + for method in parent_sym.methods { + if method.name in methodidx { + methods << st_sym.find_method_with_generic_parent(method.name) or { + continue + } + } + } + } + } + else {} + } + for method in methods { + mut name := method.name + if inter_info.parent_type.has_flag(.generic) { + parent_sym := g.table.get_type_symbol(inter_info.parent_type) + match mut parent_sym.info { + ast.Struct, ast.Interface, ast.SumType { + name = g.generic_fn_name(parent_sym.info.concrete_types, method.name, + false) + } + else {} + } + } + + if method.name !in methodidx { + // a method that is not part of the interface should be just skipped + continue + } + // .speak = Cat_speak + if st_sym.info is ast.Struct { + if st_sym.info.parent_type.has_flag(.generic) { + name = g.generic_fn_name(st_sym.info.concrete_types, method.name, + false) + } + } + mut method_call := '${cctype}_$name' + if !method.params[0].typ.is_ptr() { + // inline void Cat_speak_Interface_Animal_method_wrapper(Cat c) { return Cat_speak(*c); } + iwpostfix := '_Interface_${interface_name}_method_wrapper' + methods_wrapper.write_string('static inline ${g.typ(method.return_type)} $method_call${iwpostfix}(') + // + params_start_pos := g.out.len + mut params := method.params.clone() + // hack to mutate typ + params[0] = ast.Param{ + ...params[0] + typ: params[0].typ.set_nr_muls(1) + } + fargs, _, _ := g.fn_args(params, voidptr(0)) + methods_wrapper.write_string(g.out.cut_last(g.out.len - params_start_pos)) + methods_wrapper.writeln(') {') + methods_wrapper.write_string('\t') + if method.return_type != ast.void_type { + methods_wrapper.write_string('return ') + } + methods_wrapper.writeln('${method_call}(*${fargs.join(', ')});') + methods_wrapper.writeln('}') + // .speak = Cat_speak_Interface_Animal_method_wrapper + method_call += iwpostfix + } + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t\t._method_${c_name(method.name)} = (void*) $method_call,') + } + } + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t},') + } + iin_idx := already_generated_mwrappers[interface_index_name] - iinidx_minimum_base + if g.pref.build_mode != .build_module { + sb.writeln('const int $interface_index_name = $iin_idx;') + } else { + sb.writeln('extern const int $interface_index_name;') + } + } + for vtyp, variants in inter_info.conversions { + vsym := g.table.get_type_symbol(vtyp) + conversion_functions.write_string('static inline bool I_${interface_name}_is_I_${vsym.cname}($interface_name x) {\n\treturn ') + for i, variant in variants { + variant_sym := g.table.get_type_symbol(variant) + if i > 0 { + conversion_functions.write_string(' || ') + } + conversion_functions.write_string('(x._typ == _${interface_name}_${variant_sym.cname}_index)') + } + conversion_functions.writeln(';\n}') + + conversion_functions.writeln('static inline $vsym.cname I_${interface_name}_as_I_${vsym.cname}($interface_name x) {') + for variant in variants { + variant_sym := g.table.get_type_symbol(variant) + conversion_functions.writeln('\tif (x._typ == _${interface_name}_${variant_sym.cname}_index) return I_${variant_sym.cname}_to_Interface_${vsym.cname}(x._$variant_sym.cname);') + } + pmessage := 'string__plus(string__plus(tos3("`as_cast`: cannot convert "), tos3(v_typeof_interface_${interface_name}(x._typ))), tos3(" to ${util.strip_main_name(vsym.name)}"))' + if g.pref.is_debug { + // TODO: actually return a valid position here + conversion_functions.write_string('\tpanic_debug(1, tos3("builtin.v"), tos3("builtin"), tos3("__as_cast"), ') + conversion_functions.write_string(pmessage) + conversion_functions.writeln(');') + } else { + conversion_functions.write_string('\t_v_panic(') + conversion_functions.write_string(pmessage) + conversion_functions.writeln(');') + } + conversion_functions.writeln('\treturn ($vsym.cname){0};') + conversion_functions.writeln('}') + } + sb.writeln('// ^^^ number of types for interface $interface_name: ${current_iinidx - iinidx_minimum_base}') + if iname_table_length == 0 { + methods_struct.writeln('') + } else { + if g.pref.build_mode != .build_module { + methods_struct.writeln('};') + } + } + // add line return after interface index declarations + sb.writeln('') + if inter_info.methods.len > 0 { + sb.writeln(methods_wrapper.str()) + sb.writeln(methods_struct_def.str()) + sb.writeln(methods_struct.str()) + } + sb.writeln(cast_functions.str()) + } + sb.writeln(conversion_functions.str()) + return sb.str() +} + +fn (mut g Gen) panic_debug_info(pos token.Position) (int, string, string, string) { + paline := pos.line_nr + 1 + if isnil(g.fn_decl) { + return paline, '', 'main', 'C._vinit' + } + pafile := g.fn_decl.file.replace('\\', '/') + pafn := g.fn_decl.name.after('.') + pamod := g.fn_decl.modname() + return paline, pafile, pamod, pafn +} + +pub fn get_guarded_include_text(iname string, imessage string) string { + res := ' + |#if defined(__has_include) + | + |#if __has_include($iname) + |#include $iname + |#else + |#error VERROR_MESSAGE $imessage + |#endif + | + |#else + |#include $iname + |#endif + '.strip_margin() + return res +} + +fn (mut g Gen) trace(fbase string, message string) { + if g.file.path_base == fbase { + println('> g.trace | ${fbase:-10s} | $message') + } +} + +pub fn (mut g Gen) get_array_depth(el_typ ast.Type) int { + typ := g.unwrap_generic(el_typ) + sym := g.table.get_final_type_symbol(typ) + if sym.kind == .array { + info := sym.info as ast.Array + return 1 + g.get_array_depth(info.elem_type) + } else { + return 0 + } +} + +// returns true if `t` includes any pointer(s) - during garbage collection heap regions +// that contain no pointers do not have to be scanned +pub fn (mut g Gen) contains_ptr(el_typ ast.Type) bool { + if el_typ.is_ptr() || el_typ.is_pointer() { + return true + } + typ := g.unwrap_generic(el_typ) + if typ.is_ptr() { + return true + } + sym := g.table.get_final_type_symbol(typ) + if sym.language != .v { + return true + } + match sym.kind { + .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .char, .size_t, .rune, .bool, + .enum_ { + return false + } + .array_fixed { + info := sym.info as ast.ArrayFixed + return g.contains_ptr(info.elem_type) + } + .struct_ { + info := sym.info as ast.Struct + for embed in info.embeds { + if g.contains_ptr(embed) { + return true + } + } + for field in info.fields { + if g.contains_ptr(field.typ) { + return true + } + } + return false + } + .aggregate { + info := sym.info as ast.Aggregate + for atyp in info.types { + if g.contains_ptr(atyp) { + return true + } + } + return false + } + .multi_return { + info := sym.info as ast.MultiReturn + for mrtyp in info.types { + if g.contains_ptr(mrtyp) { + return true + } + } + return false + } + else { + return true + } + } +} + +fn (mut g Gen) check_noscan(elem_typ ast.Type) string { + if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { + if !g.contains_ptr(elem_typ) { + return '_noscan' + } + } + return '' +} diff --git a/v_windows/v/vlib/v/gen/c/cheaders.v b/v_windows/v/vlib/v/gen/c/cheaders.v new file mode 100644 index 0000000..1c03c6f --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cheaders.v @@ -0,0 +1,718 @@ +module c + +import strings +import v.pref + +// NB: @@@ here serve as placeholders. +// They will be replaced with correct strings +// for each constant, during C code generation. + +// V_COMMIT_HASH is generated by cmd/tools/gen_vc.v . +const c_commit_hash_default = ' +#ifndef V_COMMIT_HASH + #define V_COMMIT_HASH "@@@" +#endif +' + +// V_CURRENT_COMMIT_HASH is updated, when V is rebuilt inside a git repo. +const c_current_commit_hash_default = ' +#ifndef V_CURRENT_COMMIT_HASH + #define V_CURRENT_COMMIT_HASH "@@@" +#endif +' + +const c_concurrency_helpers = ' +typedef struct __shared_map __shared_map; +struct __shared_map { map val; sync__RwMutex mtx; }; +static inline voidptr __dup_shared_map(voidptr src, int sz) { + __shared_map* dest = memdup(src, sz); + sync__RwMutex_init(&dest->mtx); + return dest; +} +typedef struct __shared_array __shared_array; +struct __shared_array { array val; sync__RwMutex mtx; }; +static inline voidptr __dup_shared_array(voidptr src, int sz) { + __shared_array* dest = memdup(src, sz); + sync__RwMutex_init(&dest->mtx); + return dest; +} +static inline void __sort_ptr(uintptr_t a[], bool b[], int l) { + for (int i=1; i0 && a[j-1] > ins) { + a[j] = a[j-1]; + b[j] = b[j-1]; + j--; + } + a[j] = ins; + b[j] = insb; + } +} +' + +// Heavily based on Chris Wellons's work +// https://nullprogram.com/blog/2017/01/08/ + +fn c_closure_helpers(pref &pref.Preferences) string { + if pref.os == .windows { + verror('closures are not implemented on Windows yet') + } + if pref.arch != .amd64 { + verror('closures are not implemented on this architecture yet: $pref.arch') + } + mut builder := strings.new_builder(2048) + if pref.os != .windows { + builder.writeln('#include ') + } + if pref.arch == .amd64 { + builder.write_string(' +static unsigned char __closure_thunk[6][13] = { + { + 0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, +}; +') + } + builder.write_string(' +static void __closure_set_data(void *closure, void *data) { + void **p = closure; + p[-2] = data; +} + +static void __closure_set_function(void *closure, void *f) { + void **p = closure; + p[-1] = f; +} +') + if pref.os != .windows { + builder.write_string(' +static void * __closure_create(void *f, int nargs, void *userdata) { + long page_size = sysconf(_SC_PAGESIZE); + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_ANONYMOUS | MAP_PRIVATE; + char *p = mmap(0, page_size * 2, prot, flags, -1, 0); + if (p == MAP_FAILED) + return 0; + void *closure = p + page_size; + memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0])); + mprotect(closure, page_size, PROT_READ | PROT_EXEC); + __closure_set_function(closure, f); + __closure_set_data(closure, userdata); + return closure; +} + +static void __closure_destroy(void *closure) { + long page_size = sysconf(_SC_PAGESIZE); + munmap((char *)closure - page_size, page_size * 2); +} +') + } + return builder.str() +} + +const c_common_macros = ' +#define EMPTY_VARG_INITIALIZATION 0 +#define EMPTY_STRUCT_DECLARATION +#define EMPTY_STRUCT_INITIALIZATION +// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is... +#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[]) +#define TCCSKIP(x) x + +#define __NOINLINE __attribute__((noinline)) +#define __IRQHANDLER __attribute__((interrupt)) + +#define __V_architecture 0 +#if defined(__x86_64__) + #define __V_amd64 1 + #undef __V_architecture + #define __V_architecture 1 +#endif + +#if defined(__aarch64__) || defined(__arm64__) + #define __V_arm64 1 + #undef __V_architecture + #define __V_architecture 2 +#endif + +// Using just __GNUC__ for detecting gcc, is not reliable because other compilers define it too: +#ifdef __GNUC__ + #define __V_GCC__ +#endif +#ifdef __TINYC__ + #undef __V_GCC__ +#endif +#ifdef __cplusplus + #undef __V_GCC__ +#endif +#ifdef __clang__ + #undef __V_GCC__ +#endif +#ifdef _MSC_VER + #undef __V_GCC__ + #undef EMPTY_STRUCT_INITIALIZATION + #define EMPTY_STRUCT_INITIALIZATION 0 +#endif + +#ifdef __TINYC__ + #undef EMPTY_STRUCT_DECLARATION + #define EMPTY_STRUCT_DECLARATION char _dummy + #undef EMPTY_ARRAY_OF_ELEMS + #define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n]) + #undef __NOINLINE + #undef __IRQHANDLER + // tcc does not support inlining at all + #define __NOINLINE + #define __IRQHANDLER + #undef TCCSKIP + #define TCCSKIP(x) + // #include + #ifndef _WIN32 + #include + int tcc_backtrace(const char *fmt, ...); + #endif +#endif + +// Use __offsetof_ptr instead of __offset_of, when you *do* have a valid pointer, to avoid UB: +#ifndef __offsetof_ptr + #define __offsetof_ptr(ptr,PTYPE,FIELDNAME) ((size_t)((byte *)&((PTYPE *)ptr)->FIELDNAME - (byte *)ptr)) +#endif + +// for __offset_of +#ifndef __offsetof + #define __offsetof(PTYPE,FIELDNAME) ((size_t)((char *)&((PTYPE *)0)->FIELDNAME - (char *)0)) +#endif + +#define OPTION_CAST(x) (x) + +#ifndef V64_PRINTFORMAT + #ifdef PRIx64 + #define V64_PRINTFORMAT "0x%"PRIx64 + #elif defined(__WIN32__) + #define V64_PRINTFORMAT "0x%I64x" + #elif defined(__linux__) && defined(__LP64__) + #define V64_PRINTFORMAT "0x%lx" + #else + #define V64_PRINTFORMAT "0x%llx" + #endif +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) + #define VV_EXPORTED_SYMBOL extern __declspec(dllexport) + #define VV_LOCAL_SYMBOL static +#else + // 4 < gcc < 5 is used by some older Ubuntu LTS and Centos versions, + // and does not support __has_attribute(visibility) ... + #ifndef __has_attribute + #define __has_attribute(x) 0 // Compatibility with non-clang compilers. + #endif + #if (defined(__GNUC__) && (__GNUC__ >= 4)) || (defined(__clang__) && __has_attribute(visibility)) + #ifdef ARM + #define VV_EXPORTED_SYMBOL extern __attribute__((externally_visible,visibility("default"))) + #else + #define VV_EXPORTED_SYMBOL extern __attribute__((visibility("default"))) + #endif + #define VV_LOCAL_SYMBOL __attribute__ ((visibility ("hidden"))) + #else + #define VV_EXPORTED_SYMBOL extern + #define VV_LOCAL_SYMBOL static + #endif +#endif + +#ifdef __cplusplus + #include + #define _MOV std::move +#else + #define _MOV +#endif + +// tcc does not support has_include properly yet, turn it off completely +#if defined(__TINYC__) && defined(__has_include) +#undef __has_include +#endif + +#if !defined(VNORETURN) + #if defined(__TINYC__) + #include + #define VNORETURN noreturn + #endif + # if !defined(__TINYC__) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + # define VNORETURN _Noreturn + # elif defined(__GNUC__) && __GNUC__ >= 2 + # define VNORETURN __attribute__((noreturn)) + # endif + #ifndef VNORETURN + #define VNORETURN + #endif +#endif + +#if !defined(VUNREACHABLE) + #if defined(__GNUC__) && !defined(__clang__) + #define V_GCC_VERSION (__GNUC__ * 10000L + __GNUC_MINOR__ * 100L + __GNUC_PATCHLEVEL__) + #if (V_GCC_VERSION >= 40500L) + #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) + #endif + #endif + #if defined(__clang__) && defined(__has_builtin) + #if __has_builtin(__builtin_unreachable) + #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) + #endif + #endif + #ifndef VUNREACHABLE + #define VUNREACHABLE() do { } while (0) + #endif + #if defined(__FreeBSD__) && defined(__TINYC__) + #define VUNREACHABLE() do { } while (0) + #endif +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define _likely_(x) __builtin_expect(x,1) + #define _unlikely_(x) __builtin_expect(x,0) +#else + #define _likely_(x) (x) + #define _unlikely_(x) (x) +#endif + +' + +const c_unsigned_comparison_functions = ' +// unsigned/signed comparisons +static inline bool _us32_gt(uint32_t a, int32_t b) { return a > INT32_MAX || (int32_t)a > b; } +static inline bool _us32_ge(uint32_t a, int32_t b) { return a >= INT32_MAX || (int32_t)a >= b; } +static inline bool _us32_eq(uint32_t a, int32_t b) { return a <= INT32_MAX && (int32_t)a == b; } +static inline bool _us32_ne(uint32_t a, int32_t b) { return a > INT32_MAX || (int32_t)a != b; } +static inline bool _us32_le(uint32_t a, int32_t b) { return a <= INT32_MAX && (int32_t)a <= b; } +static inline bool _us32_lt(uint32_t a, int32_t b) { return a < INT32_MAX && (int32_t)a < b; } +static inline bool _us64_gt(uint64_t a, int64_t b) { return a > INT64_MAX || (int64_t)a > b; } +static inline bool _us64_ge(uint64_t a, int64_t b) { return a >= INT64_MAX || (int64_t)a >= b; } +static inline bool _us64_eq(uint64_t a, int64_t b) { return a <= INT64_MAX && (int64_t)a == b; } +static inline bool _us64_ne(uint64_t a, int64_t b) { return a > INT64_MAX || (int64_t)a != b; } +static inline bool _us64_le(uint64_t a, int64_t b) { return a <= INT64_MAX && (int64_t)a <= b; } +static inline bool _us64_lt(uint64_t a, int64_t b) { return a < INT64_MAX && (int64_t)a < b; } +' + +const c_helper_macros = '//============================== HELPER C MACROS =============================*/ +// _SLIT0 is used as NULL string for literal arguments +// `"" s` is used to enforce a string literal argument +#define _SLIT0 (string){.str=(byteptr)(""), .len=0, .is_lit=1} +#define _SLIT(s) ((string){.str=(byteptr)("" s), .len=(sizeof(s)-1), .is_lit=1}) +#define _SLEN(s, n) ((string){.str=(byteptr)("" s), .len=n, .is_lit=1}) + +// take the address of an rvalue +#define ADDR(type, expr) (&((type[]){expr}[0])) + +// copy something to the heap +#define HEAP(type, expr) ((type*)memdup((void*)&((type[]){expr}[0]), sizeof(type))) +#define HEAP_noscan(type, expr) ((type*)memdup_noscan((void*)&((type[]){expr}[0]), sizeof(type))) + +#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);} +#define _PUSH_MANY_noscan(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many_noscan(arr, tmp.data, tmp.len);} +' + +const c_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_macros + + r' +// c_headers +typedef int (*qsort_callback_func)(const void*, const void*); +#include // TODO remove all these includes, define all function signatures and types manually +#include +#include + +#if defined(_WIN32) || defined(__CYGWIN__) + #define VV_EXPORTED_SYMBOL extern __declspec(dllexport) + #define VV_LOCAL_SYMBOL static +#else + // 4 < gcc < 5 is used by some older Ubuntu LTS and Centos versions, + // and does not support __has_attribute(visibility) ... + #ifndef __has_attribute + #define __has_attribute(x) 0 // Compatibility with non-clang compilers. + #endif + #if (defined(__GNUC__) && (__GNUC__ >= 4)) || (defined(__clang__) && __has_attribute(visibility)) + #ifdef ARM + #define VV_EXPORTED_SYMBOL extern __attribute__((externally_visible,visibility("default"))) + #else + #define VV_EXPORTED_SYMBOL extern __attribute__((visibility("default"))) + #endif + #define VV_LOCAL_SYMBOL __attribute__ ((visibility ("hidden"))) + #else + #define VV_EXPORTED_SYMBOL extern + #define VV_LOCAL_SYMBOL static + #endif +#endif + +#if defined(__TINYC__) && defined(__has_include) +// tcc does not support has_include properly yet, turn it off completely +#undef __has_include +#endif + +#ifndef _WIN32 + #if defined __has_include + #if __has_include () + #include + #else + // Most probably musl OR __ANDROID__ ... + int backtrace (void **__array, int __size) { return 0; } + char **backtrace_symbols (void *const *__array, int __size){ return 0; } + void backtrace_symbols_fd (void *const *__array, int __size, int __fd){} + #endif + #endif +#endif + +#include // for va_list + +//================================== GLOBALS =================================*/ +int load_so(byteptr); +void reload_so(); +void _vinit(int ___argc, voidptr ___argv); +void _vcleanup(); +#define sigaction_size sizeof(sigaction); +#define _ARR_LEN(a) ( (sizeof(a)) / (sizeof(a[0])) ) + +void v_free(voidptr ptr); +voidptr memdup(voidptr src, int sz); + +#if INTPTR_MAX == INT32_MAX + #define TARGET_IS_32BIT 1 +#elif INTPTR_MAX == INT64_MAX + #define TARGET_IS_64BIT 1 +#else + #error "The environment is not 32 or 64-bit." +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) + #define TARGET_ORDER_IS_BIG 1 +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_AMD64) || defined(_M_X64) || defined(_M_IX86) + #define TARGET_ORDER_IS_LITTLE 1 +#else + #error "Unknown architecture endianness" +#endif + +#ifndef _WIN32 + #include + #include // tolower + #include + #include // sleep + extern char **environ; +#endif + +#if defined(__CYGWIN__) && !defined(_WIN32) + #error Cygwin is not supported, please use MinGW or Visual Studio. +#endif + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__vinix__) || defined(__serenity__) || defined(__sun) + #include + #include // os__wait uses wait on nix +#endif + +#ifdef __OpenBSD__ + #include + #include + #include // os__wait uses wait on nix +#endif + +#ifdef __NetBSD__ + #include // os__wait uses wait on nix +#endif + +#ifdef _WIN32 + #define WINVER 0x0600 + #ifdef _WIN32_WINNT + #undef _WIN32_WINNT + #endif + #define _WIN32_WINNT 0x0600 + #ifndef WIN32_FULL + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef _UNICODE + #define _UNICODE + #endif + #ifndef UNICODE + #define UNICODE + #endif + #include + + #include // _waccess + #include // _wgetcwd + + #ifdef _MSC_VER + // On MSVC these are the same (as long as /volatile:ms is passed) + #define _Atomic volatile + + // MSVC cannot parse some things properly + #undef EMPTY_STRUCT_DECLARATION + #undef OPTION_CAST + + #define EMPTY_STRUCT_DECLARATION char __pad + #define OPTION_CAST(x) + #undef __NOINLINE + #undef __IRQHANDLER + #define __NOINLINE __declspec(noinline) + #define __IRQHANDLER __declspec(naked) + + #include + #pragma comment(lib, "Dbghelp") + #endif +#else + #include + #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + // musl does not have that + #define pthread_rwlockattr_setkind_np(a, b) + #endif +#endif + +// g_live_info is used by live.info() +static void* g_live_info = NULL; + +#if defined(__MINGW32__) || defined(__MINGW64__) || (defined(_WIN32) && defined(__TINYC__)) + #undef PRId64 + #undef PRIi64 + #undef PRIo64 + #undef PRIu64 + #undef PRIx64 + #undef PRIX64 + #define PRId64 "lld" + #define PRIi64 "lli" + #define PRIo64 "llo" + #define PRIu64 "llu" + #define PRIx64 "llx" + #define PRIX64 "llX" +#endif + +#ifdef _VFREESTANDING +#undef _VFREESTANDING +#endif +' + +const c_builtin_types = ' +//================================== builtin types ================================*/ +typedef int64_t i64; +typedef int16_t i16; +typedef int8_t i8; +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint8_t byte; +typedef uint32_t rune; +typedef float f32; +typedef double f64; +typedef int64_t int_literal; +typedef double float_literal; +typedef unsigned char* byteptr; +typedef void* voidptr; +typedef char* charptr; +typedef byte array_fixed_byte_300 [300]; + +typedef struct sync__Channel* chan; + +#ifndef __cplusplus + #ifndef bool + typedef byte bool; + #define true 1 + #define false 0 + #endif +#endif + +typedef u64 (*MapHashFn)(voidptr); +typedef bool (*MapEqFn)(voidptr, voidptr); +typedef void (*MapCloneFn)(voidptr, voidptr); +typedef void (*MapFreeFn)(voidptr); +' + +const c_bare_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_macros + + ' +#define _VFREESTANDING + +typedef long unsigned int size_t; + +// Memory allocation related headers +void *malloc(size_t size); +void *calloc(size_t nitems, size_t size); +void *realloc(void *ptr, size_t size); +void *memcpy(void *dest, void *src, size_t n); +void *memset(void *s, int c, size_t n); +void *memmove(void *dest, void *src, size_t n); + +// varargs implementation, TODO: works on tcc and gcc, but is very unportable and hacky +typedef __builtin_va_list va_list; +#define va_start(a, b) __builtin_va_start(a, b) +#define va_end(a) __builtin_va_end(a) +#define va_arg(a, b) __builtin_va_arg(a, b) +#define va_copy(a, b) __builtin_va_copy(a, b) + +//================================== GLOBALS =================================*/ +int load_so(byteptr); +void reload_so(); +void _vinit(int ___argc, voidptr ___argv); +void _vcleanup(); +#define sigaction_size sizeof(sigaction); +#define _ARR_LEN(a) ( (sizeof(a)) / (sizeof(a[0])) ) + +void v_free(voidptr ptr); +voidptr memdup(voidptr src, int sz); + +' + +const c_wyhash_headers = ' +// ============== wyhash ============== +#ifndef wyhash_final_version_3 +#define wyhash_final_version_3 + +#ifndef WYHASH_CONDOM +// protections that produce different results: +// 1: normal valid behavior +// 2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +// 0: normal version, slow on 32 bit systems +// 1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +// includes +#include +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif + +// 128bit multiply function +static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); } +static inline void _wymum(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +// multiply and xor mix function, aka MUM +static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; } + +// endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #ifdef TARGET_ORDER_IS_LITTLE + #define WYHASH_LITTLE_ENDIAN 1 + #else + #define WYHASH_LITTLE_ENDIAN 0 + #endif +#endif + +// read functions +#if (WYHASH_LITTLE_ENDIAN) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else + static inline uint64_t _wyr8(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); + } + static inline uint64_t _wyr4(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); + } +#endif +static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} +// wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=*secret; uint64_t a, b; + if (_likely_(len<=16)) { + if (_likely_(len>=4)) { a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); } + else if (_likely_(len>0)) { a=_wyr3(p,len); b=0; } + else a=b=0; + } else { + size_t i=len; + if (_unlikely_(i>48)) { + uint64_t see1=seed, see2=seed; + do { + seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); + see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1); + see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2); + p+=48; i-=48; + } while(_likely_(i>48)); + seed^=see1^see2; + } + while(_unlikely_(i>16)) { seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; } + a=_wyr8(p+i-16); b=_wyr8(p+i-8); + } + return _wymix(secret[1]^len,_wymix(a^secret[1],b^seed)); +} +// the default secret parameters +static const uint64_t _wyp[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +// a useful 64bit-64bit mix function to produce deterministic pseudo random numbers that can pass BigCrush and PractRand +static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0xa0761d6478bd642full; B^=0xe7037ed1a0b428dbull; _wymum(&A,&B); return _wymix(A^0xa0761d6478bd642full,B^0xe7037ed1a0b428dbull);} + +// the wyrand PRNG that pass BigCrush and PractRand +static inline uint64_t wyrand(uint64_t *seed){ *seed+=0xa0761d6478bd642full; return _wymix(*seed,*seed^0xe7037ed1a0b428dbull);} + +#ifndef __vinix__ +// convert any 64 bit pseudo random numbers to uniform distribution [0,1). It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2u01(uint64_t r){ const double _wynorm=1.0/(1ull<<52); return (r>>12)*_wynorm;} + +// convert any 64 bit pseudo random numbers to APPROXIMATE Gaussian distribution. It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2gau(uint64_t r){ const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0;} +#endif + +#if(!WYHASH_32BIT_MUM) +// fast range integer random number generation on [0,k) credit to Daniel Lemire. May not work when WYHASH_32BIT_MUM=1. It can be combined with wyrand, wyhash64 or wyhash. +static inline uint64_t wy2u0k(uint64_t r, uint64_t k){ _wymum(&r,&k); return k; } +#endif +#endif + +#define _IN_MAP(val, m) map_exists(m, val) + +' diff --git a/v_windows/v/vlib/v/gen/c/cmain.v b/v_windows/v/vlib/v/gen/c/cmain.v new file mode 100644 index 0000000..6e741a6 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cmain.v @@ -0,0 +1,214 @@ +module c + +import v.util +import v.ast + +pub fn (mut g Gen) gen_c_main() { + if !g.has_main { + return + } + if g.pref.is_liveshared { + return + } + g.out.writeln('') + main_fn_start_pos := g.out.len + + is_sokol := 'sokol' in g.table.imports + + if (g.pref.os == .android && g.pref.is_apk) || (g.pref.os == .ios && is_sokol) { + g.gen_c_android_sokol_main() + } else { + g.gen_c_main_header() + g.writeln('\tmain__main();') + g.gen_c_main_footer() + if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { + println(g.out.after(main_fn_start_pos)) + } + } +} + +fn (mut g Gen) gen_vlines_reset() { + if g.pref.is_vlines { + // At this point, the v files are transpiled. + // The rest is auto generated code, which will not have + // different .v source file/line numbers. + // + // TODO: calculate the proper line here, based on + // the actual C lines in all the buffers + lines_so_far := 1000000 + g.vlines_path = util.vlines_escape_path(g.pref.out_name_c, g.pref.ccompiler) + g.writeln('') + g.writeln('\n// Reset the file/line numbers') + g.writeln('\n#line $lines_so_far "$g.vlines_path"') + g.writeln('') + } +} + +fn (mut g Gen) gen_c_main_function_header() { + if g.pref.os == .windows { + if g.is_gui_app() { + $if msvc { + // This is kinda bad but I dont see a way that is better + g.writeln('#pragma comment(linker, "/SUBSYSTEM:WINDOWS")') + } + // GUI application + g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd){') + g.writeln('\tLPWSTR full_cmd_line = GetCommandLineW(); // NB: do not use cmd_line') + g.writeln('\ttypedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);') + g.writeln('\tHMODULE shell32_module = LoadLibrary(L"shell32.dll");') + g.writeln('\tcmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");') + g.writeln('\tint ___argc;') + g.writeln('\twchar_t** ___argv = CommandLineToArgvW(full_cmd_line, &___argc);') + } else { + // Console application + g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]){') + } + } else { + g.writeln('int main(int ___argc, char** ___argv){') + } +} + +fn (mut g Gen) gen_c_main_header() { + g.gen_c_main_function_header() + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } + g.writeln('\tGC_INIT();') + if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] { + g.writeln('\tGC_enable_incremental();') + } + g.writeln('#endif') + } + g.writeln('\t_vinit(___argc, (voidptr)___argv);') + if g.pref.is_prof { + g.writeln('') + g.writeln('\tatexit(vprint_profile_stats);') + g.writeln('') + } + if g.pref.is_livemain { + g.generate_hotcode_reloading_main_caller() + } +} + +pub fn (mut g Gen) gen_c_main_footer() { + g.writeln('\t_vcleanup();') + g.writeln('\treturn 0;') + g.writeln('}') +} + +pub fn (mut g Gen) gen_c_android_sokol_main() { + // Weave autofree into sokol lifecycle callback(s) + if g.is_autofree { + g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup() +void (*_vsokol_user_cleanup_ptr)(void); +void (*_vsokol_user_cleanup_cb_ptr)(void *); + +void (_vsokol_cleanup_cb)(void) { + if (_vsokol_user_cleanup_ptr) { + _vsokol_user_cleanup_ptr(); + } + _vcleanup(); +} + +void (_vsokol_cleanup_userdata_cb)(void* user_data) { + if (_vsokol_user_cleanup_cb_ptr) { + _vsokol_user_cleanup_cb_ptr(g_desc.user_data); + } + _vcleanup(); +} +') + } + g.writeln('// The sokol_main entry point on Android +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; (void)argv; + + _vinit(argc, (voidptr)argv); + main__main(); +') + if g.is_autofree { + g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup() + if (g_desc.cleanup_cb) { + _vsokol_user_cleanup_ptr = g_desc.cleanup_cb; + g_desc.cleanup_cb = _vsokol_cleanup_cb; + } + else if (g_desc.cleanup_userdata_cb) { + _vsokol_user_cleanup_cb_ptr = g_desc.cleanup_userdata_cb; + g_desc.cleanup_userdata_cb = _vsokol_cleanup_userdata_cb; + } +') + } + g.writeln(' return g_desc;') + g.writeln('}') +} + +pub fn (mut g Gen) write_tests_definitions() { + g.includes.writeln('#include // write_tests_main') + g.definitions.writeln('int g_test_oks = 0;') + g.definitions.writeln('int g_test_fails = 0;') + g.definitions.writeln('jmp_buf g_jump_buffer;') +} + +pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) { + // in test_() functions, an `opt()?` call is sugar for + // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg) }` + // and the test is considered failed + paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) + g.writeln('\tmain__cb_propagate_test_error($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *(${cvar_name}.err.msg) );') + g.writeln('\tg_test_fails++;') + g.writeln('\tlongjmp(g_jump_buffer, 1);') +} + +pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, cvar_name string) { + // in test_() functions, a `return error('something')` is sugar for + // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg) return err }` + // and the test is considered failed + paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos) + g.writeln('\tmain__cb_propagate_test_error($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *(${cvar_name}.err.msg) );') + g.writeln('\tg_test_fails++;') + g.writeln('\tlongjmp(g_jump_buffer, 1);') +} + +pub fn (mut g Gen) gen_c_main_for_tests() { + main_fn_start_pos := g.out.len + g.writeln('') + g.gen_c_main_function_header() + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } + g.writeln('\tGC_INIT();') + if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] { + g.writeln('\tGC_enable_incremental();') + } + g.writeln('#endif') + } + g.writeln('\t_vinit(___argc, (voidptr)___argv);') + all_tfuncs := g.get_all_test_function_names() + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests bt = main__start_testing($all_tfuncs.len, _SLIT("$g.pref.path"));') + } + g.writeln('') + for tname in all_tfuncs { + tcname := util.no_dots(tname) + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_testing_step_start(&bt, _SLIT("$tcname"));') + } + g.writeln('\tif (!setjmp(g_jump_buffer)) ${tcname}();') + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_testing_step_end(&bt);') + } + } + g.writeln('') + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_end_testing(&bt);') + } + g.writeln('\t_vcleanup();') + g.writeln('\treturn g_test_fails > 0;') + g.writeln('}') + if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { + println(g.out.after(main_fn_start_pos)) + } +} diff --git a/v_windows/v/vlib/v/gen/c/comptime.v b/v_windows/v/vlib/v/gen/c/comptime.v new file mode 100644 index 0000000..9689a8e --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/comptime.v @@ -0,0 +1,676 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import os +import v.ast +import v.util +import v.pref + +fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { + g.expr(node.left) + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + // check for field.name + if node.field_expr is ast.SelectorExpr { + if node.field_expr.expr is ast.Ident { + if node.field_expr.expr.name == g.comp_for_field_var + && node.field_expr.field_name == 'name' { + g.write(c_name(g.comp_for_field_value.name)) + return + } + } + } + g.expr(node.field_expr) +} + +fn (mut g Gen) comptime_call(node ast.ComptimeCall) { + if node.is_embed { + // $embed_file('/path/to/file') + g.gen_embed_file_init(node) + return + } + if node.method_name == 'env' { + // $env('ENV_VAR_NAME') + val := util.cescaped_path(os.getenv(node.args_var)) + g.write('_SLIT("$val")') + return + } + if node.is_vweb { + is_html := node.method_name == 'html' + for stmt in node.vweb_tmpl.stmts { + if stmt is ast.FnDecl { + // insert stmts from vweb_tmpl fn + if stmt.name.starts_with('main.vweb_tmpl') { + if is_html { + g.inside_vweb_tmpl = true + } + g.stmts(stmt.stmts) + g.inside_vweb_tmpl = false + break + } + } + } + if is_html { + // return vweb html template + g.writeln('vweb__Context_html(&app->Context, _tmpl_res_$g.fn_decl.name); strings__Builder_free(&sb); string_free(&_tmpl_res_$g.fn_decl.name);') + } else { + // return $tmpl string + fn_name := g.fn_decl.name.replace('.', '__') + g.writeln('return _tmpl_res_$fn_name;') + } + return + } + g.trace_autofree('// \$method call. sym="$node.sym.name"') + if node.method_name == 'method' { + // `app.$method()` + m := node.sym.find_method(g.comp_for_method) or { return } + /* + vals := m.attrs[0].split('/') + args := vals.filter(it.starts_with(':')).map(it[1..]) + println(vals) + for val in vals { + } + */ + expand_strs := if node.args.len > 0 && m.params.len - 1 >= node.args.len { + arg := node.args[node.args.len - 1] + param := m.params[node.args.len] + + arg.expr is ast.Ident && g.table.type_to_str(arg.typ) == '[]string' + && g.table.type_to_str(param.typ) != '[]string' + } else { + false + } + // check argument length and types + if m.params.len - 1 != node.args.len && !expand_strs { + // do not generate anything if the argument lengths don't match + g.writeln('/* skipping ${node.sym.name}.$m.name due to mismatched arguments list */') + // verror('expected ${m.params.len-1} arguments to method ${node.sym.name}.$m.name, but got $node.args.len') + return + } + // TODO: check argument types + g.write('${util.no_dots(node.sym.name)}_${g.comp_for_method}(') + + // try to see if we need to pass a pointer + if node.left is ast.Ident { + scope := g.file.scope.innermost(node.pos.pos) + if v := scope.find_var(node.left.name) { + if m.params[0].typ.is_ptr() && !v.typ.is_ptr() { + g.write('&') + } + } + } + g.expr(node.left) + if m.params.len > 1 { + g.write(', ') + } + for i in 1 .. m.params.len { + if node.left is ast.Ident { + if m.params[i].name == node.left.name { + continue + } + } + if i - 1 < node.args.len - 1 { + g.expr(node.args[i - 1].expr) + g.write(', ') + } else if !expand_strs && i == node.args.len { + g.expr(node.args[i - 1].expr) + break + } else { + // last argument; try to expand if it's []string + idx := i - node.args.len + if m.params[i].typ.is_int() || m.params[i].typ.idx() == ast.bool_type_idx { + // Gets the type name and cast the string to the type with the string_ function + type_name := g.table.type_symbols[int(m.params[i].typ)].str() + g.write('string_${type_name}(((string*)${node.args[node.args.len - 1]}.data) [$idx])') + } else { + g.write('((string*)${node.args[node.args.len - 1]}.data) [$idx] ') + } + if i < m.params.len - 1 { + g.write(', ') + } + } + } + g.write(' ); // vweb action call with args') + return + } + mut j := 0 + for method in node.sym.methods { + // if method.return_type != ast.void_type { + if method.return_type != node.result_type { + continue + } + if method.params.len != 1 { + continue + } + // receiver := method.args[0] + // if !p.expr_var.ptr { + // p.error('`$p.expr_var.name` needs to be a reference') + // } + amp := '' // if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } + if node.is_vweb { + if j > 0 { + g.write(' else ') + } + g.write('if (string__eq($node.method_name, _SLIT("$method.name"))) ') + } + g.write('${util.no_dots(node.sym.name)}_${method.name}($amp ') + g.expr(node.left) + g.writeln(');') + j++ + } +} + +fn cgen_attrs(attrs []ast.Attr) []string { + mut res := []string{cap: attrs.len} + for attr in attrs { + // we currently don't quote 'arg' (otherwise we could just use `s := attr.str()`) + mut s := attr.name + if attr.arg.len > 0 { + s += ': $attr.arg' + } + res << '_SLIT("$s")' + } + return res +} + +fn (mut g Gen) comp_at(node ast.AtExpr) { + if node.kind == .vmod_file { + val := cnewlines(node.val.replace('\r', '')) + g.write('_SLIT("$val")') + } else { + val := node.val.replace('\\', '\\\\') + g.write('_SLIT("$val")') + } +} + +fn (mut g Gen) comp_if(node ast.IfExpr) { + if !node.is_expr && !node.has_else && node.branches.len == 1 { + if node.branches[0].stmts.len == 0 { + // empty ifdef; result of target OS != conditional => skip + return + } + if !g.pref.output_cross_c { + if node.branches[0].cond is ast.Ident { + if g.pref.os == (pref.os_from_string(node.branches[0].cond.name) or { + pref.OS._auto + }) { + // Same target OS as the conditional... + // => skip the #if defined ... #endif wrapper + // and just generate the branch statements: + g.indent-- + g.stmts(node.branches[0].stmts) + g.indent++ + return + } + } + } + } + line := if node.is_expr { + stmt_str := g.go_before_stmt(0) + g.write(util.tabs(g.indent)) + stmt_str.trim_space() + } else { + '' + } + mut comp_if_stmts_skip := false // don't write any statements if the condition is false + // (so that for example windows calls don't get generated inside `$if macos` which + // will lead to compilation errors) + + for i, branch in node.branches { + start_pos := g.out.len + if i == node.branches.len - 1 && node.has_else { + g.writeln('#else') + comp_if_stmts_skip = false + } else { + if i == 0 { + g.write('#if ') + } else { + g.write('#elif ') + } + comp_if_stmts_skip = !g.comp_if_cond(branch.cond, branch.pkg_exist) + g.writeln('') + } + expr_str := g.out.last_n(g.out.len - start_pos).trim_space() + g.defer_ifdef = expr_str + if node.is_expr { + len := branch.stmts.len + if len > 0 { + last := branch.stmts[len - 1] as ast.ExprStmt + if len > 1 { + tmp := g.new_tmp_var() + styp := g.typ(last.typ) + g.indent++ + g.writeln('$styp $tmp;') + g.writeln('{') + g.stmts(branch.stmts[0..len - 1]) + g.write('\t$tmp = ') + g.stmt(last) + g.writeln('}') + g.indent-- + g.writeln('$line $tmp;') + } else { + g.write('$line ') + g.stmt(last) + } + } + } else { + // Only wrap the contents in {} if we're inside a function, not on the top level scope + should_create_scope := g.fn_decl != 0 + if should_create_scope { + g.writeln('{') + } + if !comp_if_stmts_skip { + g.stmts(branch.stmts) + } + if should_create_scope { + g.writeln('}') + } + } + g.defer_ifdef = '' + } + g.writeln('#endif') +} + +/* +// returning `false` means the statements inside the $if can be skipped +*/ +// returns the value of the bool comptime expression +fn (mut g Gen) comp_if_cond(cond ast.Expr, pkg_exist bool) bool { + match cond { + ast.BoolLiteral { + g.expr(cond) + return true + } + ast.ParExpr { + g.write('(') + is_cond_true := g.comp_if_cond(cond.expr, pkg_exist) + g.write(')') + return is_cond_true + } + ast.PrefixExpr { + g.write(cond.op.str()) + return g.comp_if_cond(cond.right, pkg_exist) + } + ast.PostfixExpr { + ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) or { + verror(err.msg) + return false + } + g.write('defined($ifdef)') + return true + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + l := g.comp_if_cond(cond.left, pkg_exist) + g.write(' $cond.op ') + r := g.comp_if_cond(cond.right, pkg_exist) + return if cond.op == .and { l && r } else { l || r } + } + .key_is, .not_is { + left := cond.left + mut name := '' + mut exp_type := ast.Type(0) + got_type := (cond.right as ast.TypeNode).typ + // Handle `$if x is Interface {` + // mut matches_interface := 'false' + if left is ast.TypeNode && cond.right is ast.TypeNode + && g.table.get_type_symbol(got_type).kind == .interface_ { + // `$if Foo is Interface {` + interface_sym := g.table.get_type_symbol(got_type) + if interface_sym.info is ast.Interface { + // q := g.table.get_type_symbol(interface_sym.info.types[0]) + checked_type := g.unwrap_generic(left.typ) + // TODO PERF this check is run twice (also in the checker) + // store the result in a field + is_true := g.table.does_type_implement_interface(checked_type, + got_type) + // true // exp_type in interface_sym.info.types + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else if cond.op == .not_is { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + // matches_interface = '/*iface:$got_type $exp_type*/ true' + //} + } + } else if left is ast.SelectorExpr { + name = '${left.expr}.$left.field_name' + exp_type = g.comptime_var_type_map[name] + } else if left is ast.TypeNode { + name = left.str() + // this is only allowed for generics currently, otherwise blocked by checker + exp_type = g.unwrap_generic(left.typ) + } + + if cond.op == .key_is { + g.write('$exp_type == $got_type') + return exp_type == got_type + } else { + g.write('$exp_type != $got_type') + return exp_type != got_type + } + } + .eq, .ne { + // TODO Implement `$if method.args.len == 1` + g.write('1') + return true + } + else { + return true + } + } + } + ast.Ident { + ifdef := g.comp_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker + g.write('defined($ifdef)') + return true + } + ast.ComptimeCall { + g.write('$pkg_exist') + return true + } + else { + // should be unreachable, but just in case + g.write('1') + return true + } + } +} + +fn (mut g Gen) comp_for(node ast.CompFor) { + sym := g.table.get_type_symbol(g.unwrap_generic(node.typ)) + g.writeln('/* \$for $node.val_var in ${sym.name}($node.kind.str()) */ {') + g.indent++ + // vweb_result_type := ast.new_type(g.table.find_type_idx('vweb.Result')) + mut i := 0 + // g.writeln('string method = _SLIT("");') + if node.kind == .methods { + mut methods := sym.methods.filter(it.attrs.len == 0) // methods without attrs first + methods_with_attrs := sym.methods.filter(it.attrs.len > 0) // methods with attrs second + methods << methods_with_attrs + if methods.len > 0 { + g.writeln('FunctionData $node.val_var = {0};') + } + for method in methods { // sym.methods { + /* + if method.return_type != vweb_result_type { // ast.void_type { + continue + } + */ + g.comp_for_method = method.name + g.writeln('/* method $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$method.name");') + if method.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + attrs := cgen_attrs(method.attrs) + g.writeln( + '\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + + attrs.join(', ') + '}));\n') + } + if method.params.len < 2 { + // 0 or 1 (the receiver) args + g.writeln('\t${node.val_var}.args = __new_array_with_default(0, 0, sizeof(MethodArgs), 0);') + } else { + len := method.params.len - 1 + g.write('\t${node.val_var}.args = new_array_from_c_array($len, $len, sizeof(MethodArgs), _MOV((MethodArgs[$len]){') + // Skip receiver arg + for j, arg in method.params[1..] { + typ := arg.typ.idx() + g.write('{$typ.str(), _SLIT("$arg.name")}') + if j < len - 1 { + g.write(', ') + } + g.comptime_var_type_map['${node.val_var}.args[$j].typ'] = typ + } + g.writeln('}));\n') + } + mut sig := 'anon_fn_' + // skip the first (receiver) arg + for j, arg in method.params[1..] { + // TODO: ignore mut/pts in sig for now + typ := arg.typ.set_nr_muls(0) + sig += '$typ' + if j < method.params.len - 2 { + sig += '_' + } + } + sig += '_$method.return_type' + styp := g.table.find_type_idx(sig) + // println(styp) + // if styp == 0 { } + // TODO: type aliases + ret_typ := method.return_type.idx() + g.writeln('\t${node.val_var}.typ = $styp;') + g.writeln('\t${node.val_var}.return_type = $ret_typ;') + // + g.comptime_var_type_map['${node.val_var}.return_type'] = ret_typ + g.comptime_var_type_map['${node.val_var}.typ'] = styp + g.stmts(node.stmts) + i++ + g.writeln('}') + // + mut delete_keys := []string{} + for key, _ in g.comptime_var_type_map { + if key.starts_with(node.val_var) { + delete_keys << key + } + } + for key in delete_keys { + g.comptime_var_type_map.delete(key) + } + } + } else if node.kind == .fields { + // TODO add fields + if sym.info is ast.Struct { + mut fields := sym.info.fields.filter(it.attrs.len == 0) + fields_with_attrs := sym.info.fields.filter(it.attrs.len > 0) + fields << fields_with_attrs + if fields.len > 0 { + g.writeln('\tFieldData $node.val_var = {0};') + } + for field in fields { + g.comp_for_field_var = node.val_var + g.comp_for_field_value = field + g.writeln('/* field $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$field.name");') + if field.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + attrs := cgen_attrs(field.attrs) + g.writeln( + '\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + + attrs.join(', ') + '}));\n') + } + // field_sym := g.table.get_type_symbol(field.typ) + // g.writeln('\t${node.val_var}.typ = _SLIT("$field_sym.name");') + styp := field.typ + g.writeln('\t${node.val_var}.typ = $styp;') + g.writeln('\t${node.val_var}.is_pub = $field.is_pub;') + g.writeln('\t${node.val_var}.is_mut = $field.is_mut;') + g.writeln('\t${node.val_var}.is_shared = ${field.typ.has_flag(.shared_f)};') + g.comptime_var_type_map['${node.val_var}.typ'] = styp + g.stmts(node.stmts) + i++ + g.writeln('}') + } + g.comptime_var_type_map.delete(node.val_var) + } + } else if node.kind == .attributes { + if sym.info is ast.Struct { + if sym.info.attrs.len > 0 { + g.writeln('\tStructAttribute $node.val_var = {0};') + } + for attr in sym.info.attrs { + g.writeln('/* attribute $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$attr.name");') + g.writeln('\t${node.val_var}.has_arg = $attr.has_arg;') + g.writeln('\t${node.val_var}.arg = _SLIT("$attr.arg");') + g.writeln('\t${node.val_var}.kind = AttributeKind__$attr.kind;') + g.stmts(node.stmts) + g.writeln('}') + } + } + } + g.indent-- + g.writeln('}// \$for') +} + +fn (mut g Gen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?string { + match name { + // platforms/os-es: + 'windows' { + return '_WIN32' + } + 'ios' { + return '__TARGET_IOS__' + } + 'macos' { + return '__APPLE__' + } + 'mach' { + return '__MACH__' + } + 'darwin' { + return '__DARWIN__' + } + 'hpux' { + return '__HPUX__' + } + 'gnu' { + return '__GNU__' + } + 'qnx' { + return '__QNX__' + } + 'linux' { + return '__linux__' + } + 'serenity' { + return '__serenity__' + } + 'vinix' { + return '__vinix__' + } + 'freebsd' { + return '__FreeBSD__' + } + 'openbsd' { + return '__OpenBSD__' + } + 'netbsd' { + return '__NetBSD__' + } + 'bsd' { + return '__BSD__' + } + 'dragonfly' { + return '__DragonFly__' + } + 'android' { + return '__ANDROID__' + } + 'solaris' { + return '__sun' + } + 'haiku' { + return '__HAIKU__' + } + // + 'js' { + return '_VJS' + } + // compilers: + 'gcc' { + return '__V_GCC__' + } + 'tinyc' { + return '__TINYC__' + } + 'clang' { + return '__clang__' + } + 'mingw' { + return '__MINGW32__' + } + 'msvc' { + return '_MSC_VER' + } + 'cplusplus' { + return '__cplusplus' + } + // other: + 'threads' { + return '__VTHREADS__' + } + 'gcboehm' { + return '_VGCBOEHM' + } + 'debug' { + return '_VDEBUG' + } + 'prod' { + return '_VPROD' + } + 'test' { + return '_VTEST' + } + 'glibc' { + return '__GLIBC__' + } + 'prealloc' { + return '_VPREALLOC' + } + 'no_bounds_checking' { + return 'CUSTOM_DEFINE_no_bounds_checking' + } + 'freestanding' { + return '_VFREESTANDING' + } + // architectures: + 'amd64' { + return '__V_amd64' + } + 'aarch64', 'arm64' { + return '__V_arm64' + } + // bitness: + 'x64' { + return 'TARGET_IS_64BIT' + } + 'x32' { + return 'TARGET_IS_32BIT' + } + // endianness: + 'little_endian' { + return 'TARGET_ORDER_IS_LITTLE' + } + 'big_endian' { + return 'TARGET_ORDER_IS_BIG' + } + else { + if is_comptime_optional + || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { + return 'CUSTOM_DEFINE_$name' + } + return error('bad os ifdef name "$name"') // should never happen, caught in the checker + } + } + return none +} diff --git a/v_windows/v/vlib/v/gen/c/coutput_test.v b/v_windows/v/vlib/v/gen/c/coutput_test.v new file mode 100644 index 0000000..b8e2729 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/coutput_test.v @@ -0,0 +1,174 @@ +import os +import rand +import term +import v.util.diff +import v.util.vtest + +const vexe = @VEXE + +const vroot = os.real_path(@VMODROOT) + +const testdata_folder = os.join_path(vroot, 'vlib', 'v', 'gen', 'c', 'testdata') + +const diff_cmd = diff.find_working_diff_command() or { '' } + +fn test_out_files() ? { + println(term.colorize(term.green, '> testing whether .out files match:')) + os.chdir(vroot) or {} + output_path := os.join_path(os.temp_dir(), 'coutput', 'out') + os.mkdir_all(output_path) ? + defer { + os.rmdir_all(output_path) or {} + } + files := os.ls(testdata_folder) or { [] } + tests := files.filter(it.ends_with('.out')) + if tests.len == 0 { + eprintln('no `.out` tests found in $testdata_folder') + return + } + paths := vtest.filter_vtest_only(tests, basepath: testdata_folder) + mut total_errors := 0 + for out_path in paths { + basename, path, relpath, out_relpath := target2paths(out_path, '.out') + print(term.colorize(term.magenta, 'v run $relpath') + ' == ' + + term.colorize(term.magenta, out_relpath) + ' ') + pexe := os.join_path(output_path, '${basename}.exe') + compilation := os.execute('$vexe -o $pexe $path') + ensure_compilation_succeeded(compilation) + res := os.execute(pexe) + if res.exit_code < 0 { + println('nope') + panic(res.output) + } + mut found := res.output.trim_right('\r\n').replace('\r\n', '\n') + mut expected := os.read_file(out_path) ? + expected = expected.trim_right('\r\n').replace('\r\n', '\n') + if expected.contains('================ V panic ================') { + // panic include backtraces and absolute file paths, so can't do char by char comparison + n_found := normalize_panic_message(found, vroot) + n_expected := normalize_panic_message(expected, vroot) + if found.contains('================ V panic ================') { + if n_found.starts_with(n_expected) { + println(term.green('OK (panic)')) + continue + } else { + // Both have panics, but there was a difference... + // Pass the normalized strings for further reporting. + // There is no point in comparing the backtraces too. + found = n_found + expected = n_expected + } + } + } + if expected != found { + println(term.red('FAIL')) + println(term.header('expected:', '-')) + println(expected) + println(term.header('found:', '-')) + println(found) + if diff_cmd != '' { + println(term.header('difference:', '-')) + println(diff.color_compare_strings(diff_cmd, rand.ulid(), expected, found)) + } else { + println(term.h_divider('-')) + } + total_errors++ + } else { + println(term.green('OK')) + } + } + assert total_errors == 0 +} + +fn test_c_must_have_files() ? { + println(term.colorize(term.green, '> testing whether `.c.must_have` files match:')) + os.chdir(vroot) or {} + output_path := os.join_path(os.temp_dir(), 'coutput', 'c_must_have') + os.mkdir_all(output_path) ? + defer { + os.rmdir_all(output_path) or {} + } + files := os.ls(testdata_folder) or { [] } + tests := files.filter(it.ends_with('.c.must_have')) + if tests.len == 0 { + eprintln('no `.c.must_have` files found in $testdata_folder') + return + } + paths := vtest.filter_vtest_only(tests, basepath: testdata_folder) + mut total_errors := 0 + for must_have_path in paths { + basename, path, relpath, must_have_relpath := target2paths(must_have_path, '.c.must_have') + print(term.colorize(term.magenta, 'v -o - $relpath') + ' matches all line paterns in ' + + term.colorize(term.magenta, must_have_relpath) + ' ') + compilation := os.execute('$vexe -o - $path') + ensure_compilation_succeeded(compilation) + expected_lines := os.read_lines(must_have_path) or { [] } + generated_c_lines := compilation.output.split_into_lines() + mut nmatches := 0 + for idx_expected_line, eline in expected_lines { + if does_line_match_one_of_generated_lines(eline, generated_c_lines) { + nmatches++ + // eprintln('> testing: $must_have_path has line: $eline') + } else { + println(term.red('FAIL')) + eprintln('$must_have_path:${idx_expected_line + 1}: expected match error:') + eprintln('`$vexe -o - $path` does NOT produce expected line:') + eprintln(term.colorize(term.red, eline)) + total_errors++ + continue + } + } + if nmatches == expected_lines.len { + println(term.green('OK')) + } else { + eprintln('> ALL lines:') + eprintln(compilation.output) + } + } + assert total_errors == 0 +} + +fn does_line_match_one_of_generated_lines(line string, generated_c_lines []string) bool { + for cline in generated_c_lines { + if line == cline { + return true + } + if cline.contains(line) { + return true + } + } + return false +} + +fn normalize_panic_message(message string, vroot string) string { + mut msg := message.all_before('=========================================') + // change windows to nix path + s := vroot.replace(os.path_separator, '/') + msg = msg.replace(s + '/', '') + msg = msg.trim_space() + return msg +} + +fn vroot_relative(opath string) string { + nvroot := vroot.replace(os.path_separator, '/') + '/' + npath := opath.replace(os.path_separator, '/') + return npath.replace(nvroot, '') +} + +fn ensure_compilation_succeeded(compilation os.Result) { + if compilation.exit_code < 0 { + panic(compilation.output) + } + if compilation.exit_code != 0 { + panic('compilation failed: $compilation.output') + } +} + +fn target2paths(target_path string, postfix string) (string, string, string, string) { + basename := os.file_name(target_path).replace(postfix, '') + target_dir := os.dir(target_path) + path := os.join_path(target_dir, '${basename}.vv') + relpath := vroot_relative(path) + target_relpath := vroot_relative(target_path) + return basename, path, relpath, target_relpath +} diff --git a/v_windows/v/vlib/v/gen/c/ctempvars.v b/v_windows/v/vlib/v/gen/c/ctempvars.v new file mode 100644 index 0000000..f0484ed --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/ctempvars.v @@ -0,0 +1,25 @@ +module c + +import v.ast + +fn (mut g Gen) new_ctemp_var(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + return ast.CTempVar{ + name: g.new_tmp_var() + typ: expr_type + is_ptr: expr_type.is_ptr() + orig: expr + } +} + +fn (mut g Gen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + x := g.new_ctemp_var(expr, expr_type) + g.gen_ctemp_var(x) + return x +} + +fn (mut g Gen) gen_ctemp_var(tvar ast.CTempVar) { + styp := g.typ(tvar.typ) + g.write('$styp $tvar.name = ') + g.expr(tvar.orig) + g.writeln(';') +} diff --git a/v_windows/v/vlib/v/gen/c/dumpexpr.v b/v_windows/v/vlib/v/gen/c/dumpexpr.v new file mode 100644 index 0000000..fcb6bbd --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/dumpexpr.v @@ -0,0 +1,72 @@ +module c + +import v.ast +import strings + +fn (mut g Gen) dump_expr(node ast.DumpExpr) { + sexpr := ctoslit(node.expr.str()) + fpath := cestring(g.file.path) + line := node.pos.line_nr + 1 + if 'nop_dump' in g.pref.compile_defines { + g.expr(node.expr) + return + } + dump_fn_name := '_v_dump_expr_$node.cname' + (if node.expr_type.is_ptr() { '_ptr' } else { '' }) + g.write(' ${dump_fn_name}(${ctoslit(fpath)}, $line, $sexpr, ') + g.expr(node.expr) + g.write(' )') +} + +fn (mut g Gen) dump_expr_definitions() { + mut dump_typedefs := map[string]bool{} + mut dump_fns := strings.new_builder(100) + for dump_type, cname in g.table.dumps { + to_string_fn_name := g.gen_str_for_type(dump_type) + is_ptr := ast.Type(dump_type).is_ptr() + ptr_asterisk := if is_ptr { '*' } else { '' } + dump_sym := g.table.get_type_symbol(dump_type) + mut str_dumparg_type := '$cname$ptr_asterisk' + if dump_sym.kind == .function { + fninfo := dump_sym.info as ast.FnType + str_dumparg_type = 'DumpFNType_$cname' + tdef_pos := g.out.len + g.write_fn_ptr_decl(&fninfo, str_dumparg_type) + str_tdef := g.out.after(tdef_pos) + g.out.go_back(str_tdef.len) + dump_typedefs['typedef $str_tdef;'] = true + } + dump_fn_name := '_v_dump_expr_$cname' + (if is_ptr { '_ptr' } else { '' }) + if g.writeln_fn_header('$str_dumparg_type ${dump_fn_name}(string fpath, int line, string sexpr, $str_dumparg_type x)', mut + dump_fns) + { + continue + } + dump_fns.writeln('\teprint(${ctoslit('[')});') + dump_fns.writeln('\teprint(fpath);') + dump_fns.writeln('\teprint(${ctoslit(':')});') + dump_fns.writeln('\teprint(int_str(line));') + dump_fns.writeln('\teprint(${ctoslit('] ')});') + // dump_fns.writeln('\t/* dump_type: $dump_type | to_string_fn_name: $to_string_fn_name | is_ptr: $is_ptr | ptr_asterisk: $ptr_asterisk | dump_fn_name: $dump_fn_name | cnam: $cname */') + dump_fns.writeln('\teprint(sexpr);') + dump_fns.writeln('\teprint(${ctoslit(': ')});') + if is_ptr { + dump_fns.writeln('\teprint(${ctoslit('&')});') + } + dump_fns.writeln('\teprintln(${to_string_fn_name}(${ptr_asterisk}x));') + dump_fns.writeln('\treturn x;') + dump_fns.writeln('}') + } + for tdef, _ in dump_typedefs { + g.definitions.writeln(tdef) + } + g.definitions.writeln(dump_fns.str()) +} + +fn (mut g Gen) writeln_fn_header(s string, mut sb strings.Builder) bool { + if g.pref.build_mode == .build_module { + sb.writeln('$s;') + return true + } + sb.writeln('$s {') + return false +} diff --git a/v_windows/v/vlib/v/gen/c/embed.v b/v_windows/v/vlib/v/gen/c/embed.v new file mode 100644 index 0000000..4ee02eb --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/embed.v @@ -0,0 +1,69 @@ +module c + +import os +import v.ast + +fn (mut g Gen) embed_file_is_prod_mode() bool { + if g.pref.is_prod || 'debug_embed_file_in_prod' in g.pref.compile_defines { + return true + } + return false +} + +// gen_embed_file_struct generates C code for `$embed_file('...')` calls. +fn (mut g Gen) gen_embed_file_init(node ast.ComptimeCall) { + g.writeln('(v__embed_file__EmbedFileData){') + g.writeln('\t\t.path = ${ctoslit(node.embed_file.rpath)},') + g.writeln('\t\t.apath = ${ctoslit(node.embed_file.apath)},') + file_size := os.file_size(node.embed_file.apath) + if file_size > 5242880 { + eprintln('Warning: embedding of files >= ~5MB is currently not supported') + } + if g.embed_file_is_prod_mode() { + // Use function generated in Gen.gen_embedded_data() + g.writeln('\t\t.compressed = v__embed_file__find_index_entry_by_path((voidptr)_v_embed_file_index, ${ctoslit(node.embed_file.rpath)})->data,') + } + g.writeln('\t\t.uncompressed = NULL,') + g.writeln('\t\t.free_compressed = 0,') + g.writeln('\t\t.free_uncompressed = 0,') + g.writeln('\t\t.len = $file_size') + g.writeln('} // \$embed_file("$node.embed_file.apath")') +} + +// gen_embedded_data embeds data into the V target executable. +fn (mut g Gen) gen_embedded_data() { + /* + TODO implement compression. + See also the vlib/embed module where decompression should occur. + */ + /* + TODO implement support for large files - right now the setup has problems + // with even just 10 - 50 MB files - the problem is both in V and C compilers. + // maybe we need to write to separate files or have an external tool for large files + // like the `rcc` tool in Qt? + */ + for i, emfile in g.embedded_files { + fbytes := os.read_bytes(emfile.apath) or { panic('Error while embedding file: $err') } + g.embedded_data.write_string('static const unsigned char _v_embed_blob_$i[$fbytes.len] = {\n ') + for j := 0; j < fbytes.len; j++ { + b := fbytes[j].hex() + if j < fbytes.len - 1 { + g.embedded_data.write_string('0x$b,') + } else { + g.embedded_data.write_string('0x$b') + } + if 0 == ((j + 1) % 16) { + g.embedded_data.write_string('\n ') + } + } + g.embedded_data.writeln('\n};') + } + g.embedded_data.writeln('') + g.embedded_data.writeln('const v__embed_file__EmbedFileIndexEntry _v_embed_file_index[] = {') + for i, emfile in g.embedded_files { + g.embedded_data.writeln('\t{$i, { .str=(byteptr)("${cestring(emfile.rpath)}"), .len=${emfile.rpath.len - 1}, .is_lit=1 }, _v_embed_blob_$i},') + } + g.embedded_data.writeln('\t{-1, { .str=(byteptr)(""), .len=0, .is_lit=1 }, NULL}') + g.embedded_data.writeln('};') + // see vlib/v/embed_file/embed_file.v, find_index_entry_by_id/2 and find_index_entry_by_path/2 +} diff --git a/v_windows/v/vlib/v/gen/c/fn.v b/v_windows/v/vlib/v/gen/c/fn.v new file mode 100644 index 0000000..7838221 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/fn.v @@ -0,0 +1,1558 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import strings +import v.ast +import v.util + +fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { + mut is_used_by_main := true + if g.pref.skip_unused { + fkey := node.fkey() + is_used_by_main = g.table.used_fns[fkey] + $if trace_skip_unused_fns ? { + println('> is_used_by_main: $is_used_by_main | node.name: $node.name | fkey: $fkey | node.is_method: $node.is_method') + } + if !is_used_by_main { + $if trace_skip_unused_fns_in_c_code ? { + g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $fkey') + } + } + } else { + $if trace_skip_unused_fns_in_c_code ? { + g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $node.fkey()') + } + } + return is_used_by_main +} + +fn (mut g Gen) process_fn_decl(node ast.FnDecl) { + if !g.is_used_by_main(node) { + return + } + if node.should_be_skipped { + return + } + if g.is_builtin_mod && g.pref.gc_mode == .boehm_leak && node.name == 'malloc' { + g.definitions.write_string('#define _v_malloc GC_MALLOC\n') + return + } + g.gen_attrs(node.attrs) + mut skip := false + pos := g.out.len + should_bundle_module := util.should_bundle_module(node.mod) + if g.pref.build_mode == .build_module { + // if node.name.contains('parse_text') { + // println('!!! $node.name mod=$node.mod, built=$g.module_built') + // } + // TODO true for not just "builtin" + // TODO: clean this up + mod := if g.is_builtin_mod { 'builtin' } else { node.name.all_before_last('.') } + if (mod != g.module_built && node.mod != g.module_built.after('/')) || should_bundle_module { + // Skip functions that don't have to be generated for this module. + // println('skip bm $node.name mod=$node.mod module_built=$g.module_built') + skip = true + } + if g.is_builtin_mod && g.module_built == 'builtin' && node.mod == 'builtin' { + skip = false + } + if !skip && g.pref.is_verbose { + println('build module `$g.module_built` fn `$node.name`') + } + } + if g.pref.use_cache { + // We are using prebuilt modules, we do not need to generate + // their functions in main.c. + if node.mod != 'main' && node.mod != 'help' && !should_bundle_module && !g.pref.is_test + && node.generic_names.len == 0 { + skip = true + } + } + keep_fn_decl := g.fn_decl + unsafe { + g.fn_decl = &node + } + if node.is_main { + g.has_main = true + } + if node.name == 'backtrace' || node.name == 'backtrace_symbols' + || node.name == 'backtrace_symbols_fd' { + g.write('\n#ifndef __cplusplus\n') + } + g.gen_fn_decl(node, skip) + if node.name == 'backtrace' || node.name == 'backtrace_symbols' + || node.name == 'backtrace_symbols_fd' { + g.write('\n#endif\n') + } + g.fn_decl = keep_fn_decl + if skip { + g.out.go_back_to(pos) + } + if !g.pref.skip_unused { + if node.language != .c { + g.writeln('') + } + } +} + +fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { + // TODO For some reason, build fails with autofree with this line + // as it's only informative, comment it for now + // g.gen_attrs(it.attrs) + if node.language == .c { + // || node.no_body { + return + } + + tmp_defer_vars := g.defer_vars // must be here because of workflow + if !g.anon_fn { + g.defer_vars = []string{} + } else { + if node.defer_stmts.len > 0 { + g.defer_vars = []string{} + defer { + g.defer_vars = tmp_defer_vars + } + } + } + // Skip [if xxx] if xxx is not defined + /* + for attr in node.attrs { + if !attr.is_comptime_define { + continue + } + if attr.name !in g.pref.compile_defines_all { + // println('skipping [if]') + return + } + } + */ + + g.returned_var_name = '' + // + old_g_autofree := g.is_autofree + if node.is_manualfree { + g.is_autofree = false + } + defer { + g.is_autofree = old_g_autofree + } + // + // if g.fileis('vweb.v') { + // println('\ngen_fn_decl() $node.name $node.is_generic $g.cur_generic_type') + // } + if node.generic_names.len > 0 && g.table.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion + // loop thru each generic type and generate a function + for concrete_types in g.table.fn_generic_types[node.name] { + if g.pref.is_verbose { + syms := concrete_types.map(g.table.get_type_symbol(it)) + the_type := syms.map(it.name).join(', ') + println('gen fn `$node.name` for type `$the_type`') + } + g.table.cur_concrete_types = concrete_types + g.gen_fn_decl(node, skip) + } + g.table.cur_concrete_types = [] + return + } + cur_fn_save := g.table.cur_fn + defer { + g.table.cur_fn = cur_fn_save + } + unsafe { + // TODO remove unsafe + g.table.cur_fn = node + } + fn_start_pos := g.out.len + is_closure := node.scope.has_inherited_vars() + mut cur_closure_ctx := '' + if is_closure { + cur_closure_ctx = closure_ctx_struct(node) + // declare the struct before its implementation + g.definitions.write_string(cur_closure_ctx) + g.definitions.writeln(';') + } + + g.write_v_source_line_info(node.pos) + msvc_attrs := g.write_fn_attrs(node.attrs) + // Live + is_livefn := node.attrs.contains('live') + is_livemain := g.pref.is_livemain && is_livefn + is_liveshared := g.pref.is_liveshared && is_livefn + is_livemode := g.pref.is_livemain || g.pref.is_liveshared + is_live_wrap := is_livefn && is_livemode + if is_livefn && !is_livemode { + eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function $node.name .') + } + // + mut name := node.name + if name in ['+', '-', '*', '/', '%', '<', '=='] { + name = util.replace_op(name) + } + if node.is_method { + unwrapped_rec_sym := g.table.get_type_symbol(g.unwrap_generic(node.receiver.typ)) + if unwrapped_rec_sym.kind == .placeholder { + return + } + name = g.cc_type(node.receiver.typ, false) + '_' + name + // name = g.table.get_type_symbol(node.receiver.typ).name + '_' + name + } + if node.language == .c { + name = util.no_dots(name) + } else { + name = c_name(name) + } + mut type_name := g.typ(node.return_type) + + name = g.generic_fn_name(g.table.cur_concrete_types, name, true) + + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main + && node.name != 'str' { + mut key := node.name + if node.is_method { + sym := g.table.get_type_symbol(node.receiver.typ) + key = sym.name + '.' + node.name + } + g.writeln('/* obf: $key */') + name = g.obf_table[key] or { + panic('cgen: fn_decl: obf name "$key" not found, this should never happen') + } + } + // if g.pref.show_cc && it.is_builtin { + // println(name) + // } + // type_name := g.ast.Type_to_str(it.return_type) + // Live functions are protected by a mutex, because otherwise they + // can be changed by the live reload thread, *while* they are + // running, with unpredictable results (usually just crashing). + // For this purpose, the actual body of the live function, + // is put under a non publicly accessible function, that is prefixed + // with 'impl_live_' . + if is_livemain { + g.hotcode_fn_names << name + } + mut impl_fn_name := name + if is_live_wrap { + impl_fn_name = 'impl_live_$name' + } + last_fn_c_name_save := g.last_fn_c_name + defer { + g.last_fn_c_name = last_fn_c_name_save + } + g.last_fn_c_name = impl_fn_name + // + if is_live_wrap { + if is_livemain { + g.definitions.write_string('$type_name (* $impl_fn_name)(') + g.write('$type_name no_impl_${name}(') + } + if is_liveshared { + g.definitions.write_string('$type_name ${impl_fn_name}(') + g.write('$type_name ${impl_fn_name}(') + } + } else { + if !(node.is_pub || g.pref.is_debug) { + // Private functions need to marked as static so that they are not exportable in the + // binaries + if g.pref.build_mode != .build_module && !g.pref.use_cache { + // if !(g.pref.build_mode == .build_module && g.is_builtin_mod) { + // If we are building vlib/builtin, we need all private functions like array_get + // to be public, so that all V programs can access them. + g.write('VV_LOCAL_SYMBOL ') + g.definitions.write_string('VV_LOCAL_SYMBOL ') + } + } + fn_header := if msvc_attrs.len > 0 { + '$type_name $msvc_attrs ${name}(' + } else { + '$type_name ${name}(' + } + g.definitions.write_string(fn_header) + g.write(fn_header) + } + arg_start_pos := g.out.len + fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.scope) + if is_closure { + mut s := '$cur_closure_ctx *$c.closure_ctx' + if node.params.len > 0 { + s = ', ' + s + } else { + // remove generated `void` + g.out.cut_to(arg_start_pos) + } + g.definitions.write_string(s) + g.write(s) + g.nr_closures++ + } + arg_str := g.out.after(arg_start_pos) + if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin + && !g.is_test) || skip { + // Just a function header. Builtin function bodies are defined in builtin.o + g.definitions.writeln(');') // // NO BODY') + g.writeln(');') + return + } + g.definitions.writeln(');') + g.writeln(') {') + for i, is_promoted in heap_promoted { + if is_promoted { + g.writeln('${fargtypes[i]}* ${fargs[i]} = HEAP(${fargtypes[i]}, _v_toheap_${fargs[i]});') + } + } + for defer_stmt in node.defer_stmts { + g.writeln('bool ${g.defer_flag_var(defer_stmt)} = false;') + for var in defer_stmt.defer_vars { + if var.name in fargs || var.kind == .constant { + continue + } + if var.kind == .variable { + if var.name !in g.defer_vars { + g.defer_vars << var.name + mut deref := '' + if v := var.scope.find_var(var.name) { + if v.is_auto_heap { + deref = '*' + } + } + info := var.obj as ast.Var + g.writeln('${g.typ(info.typ)}$deref $var.name;') + } + } + } + } + if is_live_wrap { + // The live function just calls its implementation dual, while ensuring + // that the call is wrapped by the mutex lock & unlock calls. + // Adding the mutex lock/unlock inside the body of the implementation + // function is not reliable, because the implementation function can do + // an early exit, which will leave the mutex locked. + mut fn_args_list := []string{} + for ia, fa in fargs { + fn_args_list << '${fargtypes[ia]} $fa' + } + mut live_fncall := '${impl_fn_name}(' + fargs.join(', ') + ');' + mut live_fnreturn := '' + if type_name != 'void' { + live_fncall = '$type_name res = $live_fncall' + live_fnreturn = 'return res;' + } + g.definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + ');') + g.hotcode_definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + '){') + g.hotcode_definitions.writeln(' pthread_mutex_lock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fncall') + g.hotcode_definitions.writeln(' pthread_mutex_unlock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fnreturn') + g.hotcode_definitions.writeln('}') + } + // Profiling mode? Start counting at the beginning of the function (save current time). + if g.pref.is_prof && g.pref.build_mode != .build_module { + g.profile_fn(node) + } + // we could be in an anon fn so save outer fn defer stmts + prev_defer_stmts := g.defer_stmts + g.defer_stmts = [] + ctmp := g.tmp_count + g.tmp_count = 0 + defer { + g.tmp_count = ctmp + } + g.stmts(node.stmts) + if node.is_noreturn { + g.writeln('\twhile(1);') + } + // clear g.fn_mut_arg_names + + if !node.has_return { + g.write_defer_stmts_when_needed() + } + if node.is_anon { + g.defer_stmts = prev_defer_stmts + } else { + g.defer_stmts = [] + } + if node.return_type != ast.void_type && node.stmts.len > 0 && node.stmts.last() !is ast.Return + && !node.attrs.contains('_naked') { + default_expr := g.type_default(node.return_type) + // TODO: perf? + if default_expr == '{0}' { + // if node.return_type.idx() == 1 && node.return_type.has_flag(.optional) { + // // The default return for anonymous functions that return `?, + // // should have .ok = true set, otherwise calling them with + // // optfn() or { panic(err) } will cause a panic: + // g.writeln('\treturn (Option_void){0};') + // } else { + g.writeln('\treturn ($type_name)$default_expr;') + // } + } else { + g.writeln('\treturn $default_expr;') + } + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && g.last_fn_c_name in g.pref.printfn_list { + println(g.out.after(fn_start_pos)) + } + for attr in node.attrs { + if attr.name == 'export' { + g.writeln('// export alias: $attr.arg -> $name') + export_alias := '$type_name ${attr.arg}($arg_str)' + g.definitions.writeln('VV_EXPORTED_SYMBOL $export_alias; // exported fn $node.name') + g.writeln('$export_alias {') + g.write('\treturn ${name}(') + g.write(fargs.join(', ')) + g.writeln(');') + g.writeln('}') + } + } +} + +const closure_ctx = '_V_closure_ctx' + +fn closure_ctx_struct(node ast.FnDecl) string { + return 'struct _V_${node.name}_Ctx' +} + +fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { + g.gen_anon_fn_decl(mut node) + if !node.decl.scope.has_inherited_vars() { + g.write(node.decl.name) + return + } + // it may be possible to optimize `memdup` out if the closure never leaves current scope + ctx_var := g.new_tmp_var() + cur_line := g.go_before_stmt(0) + ctx_struct := closure_ctx_struct(node.decl) + g.writeln('$ctx_struct* $ctx_var = ($ctx_struct*) memdup(&($ctx_struct){') + g.indent++ + for var in node.inherited_vars { + g.writeln('.$var.name = $var.name,') + } + g.indent-- + g.writeln('}, sizeof($ctx_struct));') + g.empty_line = false + g.write(cur_line) + // TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data) + g.write('__closure_create($node.decl.name, ${node.decl.params.len + 1}, $ctx_var)') +} + +fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { + if node.has_gen { + return + } + node.has_gen = true + mut builder := strings.new_builder(256) + if node.inherited_vars.len > 0 { + ctx_struct := closure_ctx_struct(node.decl) + builder.writeln('$ctx_struct {') + for var in node.inherited_vars { + styp := g.typ(var.typ) + builder.writeln('\t$styp $var.name;') + } + builder.writeln('};\n') + } + pos := g.out.len + was_anon_fn := g.anon_fn + g.anon_fn = true + g.process_fn_decl(node.decl) + g.anon_fn = was_anon_fn + builder.write_string(g.out.cut_to(pos)) + g.anon_fn_definitions << builder.str() +} + +fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string { + return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn' +} + +fn (mut g Gen) write_defer_stmts_when_needed() { + // unlock all mutexes, in case we are in a lock statement. defers are not allowed in lock statements + g.unlock_locks() + if g.defer_stmts.len > 0 { + g.write_defer_stmts() + } + if g.defer_profile_code.len > 0 { + g.writeln('') + g.writeln('\t// defer_profile_code') + g.writeln(g.defer_profile_code) + g.writeln('') + } +} + +// fn decl args +fn (mut g Gen) fn_args(args []ast.Param, scope &ast.Scope) ([]string, []string, []bool) { + mut fargs := []string{} + mut fargtypes := []string{} + mut heap_promoted := []bool{} + if args.len == 0 { + // in C, `()` is untyped, unlike `(void)` + g.write('void') + } + for i, arg in args { + mut caname := if arg.name == '_' { g.new_tmp_declaration_name() } else { c_name(arg.name) } + typ := g.unwrap_generic(arg.typ) + arg_type_sym := g.table.get_type_symbol(typ) + mut arg_type_name := g.typ(typ) // util.no_dots(arg_type_sym.name) + if arg_type_sym.kind == .function { + info := arg_type_sym.info as ast.FnType + func := info.func + g.write('${g.typ(func.return_type)} (*$caname)(') + g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(') + g.fn_args(func.params, voidptr(0)) + g.write(')') + g.definitions.write_string(')') + fargs << caname + fargtypes << arg_type_name + } else { + mut heap_prom := false + if scope != voidptr(0) { + if arg.name != '_' { + if v := scope.find_var(arg.name) { + if !v.is_stack_obj && v.is_auto_heap { + heap_prom = true + } + } + } + } + var_name_prefix := if heap_prom { '_v_toheap_' } else { '' } + const_prefix := if arg.typ.is_any_kind_of_pointer() && !arg.is_mut + && arg.name.starts_with('const_') { + 'const ' + } else { + '' + } + s := '$const_prefix$arg_type_name $var_name_prefix$caname' + g.write(s) + g.definitions.write_string(s) + fargs << caname + fargtypes << arg_type_name + heap_promoted << heap_prom + } + if i < args.len - 1 { + g.write(', ') + g.definitions.write_string(', ') + } + } + return fargs, fargtypes, heap_promoted +} + +fn (mut g Gen) call_expr(node ast.CallExpr) { + // g.write('/*call expr*/') + // NOTE: everything could be done this way + // see my comment in parser near anon_fn + if node.left is ast.AnonFn { + g.expr(node.left) + } + if node.left is ast.IndexExpr && node.name == '' { + g.is_fn_index_call = true + g.expr(node.left) + g.is_fn_index_call = false + } + if node.should_be_skipped { + return + } + g.inside_call = true + defer { + g.inside_call = false + } + gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type + && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] + gen_or := node.or_block.kind != .absent // && !g.is_autofree + is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result + cur_line := if is_gen_or_and_assign_rhs || gen_keep_alive { // && !g.is_autofree { + // `x := foo() or { ...}` + // cut everything that has been generated to prepend optional variable creation + line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + // g.write('/*is_gen_or_and_assign_rhs*/') + line + } else { + '' + } + tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' } + if gen_or || gen_keep_alive { + mut ret_typ := node.return_type + if gen_or { + ret_typ = ret_typ.set_flag(.optional) + } + styp := g.typ(ret_typ) + g.write('$styp $tmp_opt = ') + } + if node.is_method && !node.is_field { + if node.name == 'writeln' && g.pref.experimental && node.args.len > 0 + && node.args[0].expr is ast.StringInterLiteral + && g.table.get_type_symbol(node.receiver_type).name == 'strings.Builder' { + g.string_inter_literal_sb_optimized(node) + } else { + g.method_call(node) + } + } else { + g.fn_call(node) + } + if gen_or { // && !g.autofree { + // if !g.is_autofree { + g.or_block(tmp_opt, node.or_block, node.return_type) + //} + if is_gen_or_and_assign_rhs { + unwrapped_typ := node.return_type.clear_flag(.optional) + unwrapped_styp := g.typ(unwrapped_typ) + if unwrapped_typ == ast.void_type { + g.write('\n $cur_line') + } else if g.table.get_type_symbol(node.return_type).kind == .multi_return { + g.write('\n $cur_line $tmp_opt /*U*/') + } else { + if !g.inside_const { + g.write('\n $cur_line (*($unwrapped_styp*)${tmp_opt}.data)') + } else { + g.write('\n $cur_line $tmp_opt') + } + } + } + } else if gen_keep_alive { + if node.return_type == ast.void_type { + g.write('\n $cur_line') + } else { + g.write('\n $cur_line $tmp_opt') + } + } + if node.is_noreturn { + if g.inside_ternary == 0 { + g.writeln(';') + g.write('VUNREACHABLE()') + } else { + $if msvc { + // MSVC has no support for the statement expressions used below + } $else { + g.write(', ({VUNREACHABLE();})') + } + } + } +} + +fn (mut g Gen) conversion_function_call(prefix string, postfix string, node ast.CallExpr) { + g.write('${prefix}( (') + g.expr(node.left) + dot := if node.left_type.is_ptr() { '->' } else { '.' } + g.write(')${dot}_typ )$postfix') +} + +fn (mut g Gen) method_call(node ast.CallExpr) { + // TODO: there are still due to unchecked exprs (opt/some fn arg) + if node.left_type == 0 { + g.checker_bug('CallExpr.left_type is 0 in method_call', node.pos) + } + if node.receiver_type == 0 { + g.checker_bug('CallExpr.receiver_type is 0 in method_call', node.pos) + } + mut unwrapped_rec_type := node.receiver_type + if g.table.cur_fn.generic_names.len > 0 { // in generic fn + unwrapped_rec_type = g.unwrap_generic(node.receiver_type) + } else { // in non-generic fn + sym := g.table.get_type_symbol(node.receiver_type) + match sym.info { + ast.Struct, ast.Interface, ast.SumType { + generic_names := sym.info.generic_types.map(g.table.get_type_symbol(it).name) + if utyp := g.table.resolve_generic_to_concrete(node.receiver_type, generic_names, + sym.info.concrete_types) + { + unwrapped_rec_type = utyp + } + } + else {} + } + } + mut typ_sym := g.table.get_type_symbol(unwrapped_rec_type) + // alias type that undefined this method (not include `str`) need to use parent type + if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) { + unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type + typ_sym = g.table.get_type_symbol(unwrapped_rec_type) + } + rec_cc_type := g.cc_type(unwrapped_rec_type, false) + mut receiver_type_name := util.no_dots(rec_cc_type) + if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method(node.name) { + // Speaker_name_table[s._interface_idx].speak(s._object) + $if debug_interface_method_call ? { + eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name') + } + g.write('${c_name(receiver_type_name)}_name_table[') + g.expr(node.left) + dot := if node.left_type.is_ptr() { '->' } else { '.' } + mname := c_name(node.name) + g.write('${dot}_typ]._method_${mname}(') + g.expr(node.left) + g.write('${dot}_object') + if node.args.len > 0 { + g.write(', ') + g.call_args(node) + } + g.write(')') + return + } + left_sym := g.table.get_type_symbol(node.left_type) + final_left_sym := g.table.get_final_type_symbol(node.left_type) + if left_sym.kind == .array { + match node.name { + 'filter' { + g.gen_array_filter(node) + return + } + 'sort' { + g.gen_array_sort(node) + return + } + 'insert' { + g.gen_array_insert(node) + return + } + 'map' { + g.gen_array_map(node) + return + } + 'prepend' { + g.gen_array_prepend(node) + return + } + 'contains' { + g.gen_array_contains(node) + return + } + 'index' { + g.gen_array_index(node) + return + } + 'wait' { + g.gen_array_wait(node) + return + } + 'any' { + g.gen_array_any(node) + return + } + 'all' { + g.gen_array_all(node) + return + } + else {} + } + } + + if left_sym.kind == .map && node.name == 'delete' { + left_info := left_sym.info as ast.Map + elem_type_str := g.typ(left_info.key_type) + g.write('map_delete(') + if node.left_type.is_ptr() { + g.expr(node.left) + } else { + g.write('&') + g.expr(node.left) + } + g.write(', &($elem_type_str[]){') + g.expr(node.args[0].expr) + g.write('})') + return + } + + if left_sym.kind in [.sum_type, .interface_] { + if node.name == 'type_name' { + if left_sym.kind == .sum_type { + g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_sumtype_$typ_sym.cname', + ')', node) + return + } + if left_sym.kind == .interface_ { + g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_interface_$typ_sym.cname', + ')', node) + return + } + } + if node.name == 'type_idx' { + if left_sym.kind == .sum_type { + g.conversion_function_call('/* $left_sym.name */ v_typeof_sumtype_idx_$typ_sym.cname', + '', node) + return + } + if left_sym.kind == .interface_ { + g.conversion_function_call('/* $left_sym.name */ v_typeof_interface_idx_$typ_sym.cname', + '', node) + return + } + } + } + + if node.name == 'str' { + mut rec_type := node.receiver_type + if rec_type.has_flag(.shared_f) { + rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0) + } + g.gen_str_for_type(rec_type) + } + mut has_cast := false + if left_sym.kind == .map && node.name in ['clone', 'move'] { + receiver_type_name = 'map' + } + if final_left_sym.kind == .array + && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { + // `array_Xyz_clone` => `array_clone` + receiver_type_name = 'array' + } + if node.name in ['last', 'first', 'pop'] { + return_type_str := g.typ(node.return_type) + has_cast = true + g.write('(*($return_type_str*)') + } + } + mut name := util.no_dots('${receiver_type_name}_$node.name') + mut array_depth := -1 + mut noscan := '' + if left_sym.kind == .array { + needs_depth := node.name in ['clone', 'repeat'] + if needs_depth { + elem_type := (left_sym.info as ast.Array).elem_type + array_depth = g.get_array_depth(elem_type) + } + maybe_noscan := needs_depth + || node.name in ['pop', 'push', 'push_many', 'reverse', 'grow_cap', 'grow_len'] + if maybe_noscan { + info := left_sym.info as ast.Array + noscan = g.check_noscan(info.elem_type) + } + } else if left_sym.kind == .chan { + if node.name in ['close', 'try_pop', 'try_push'] { + name = 'sync__Channel_$node.name' + } + } else if left_sym.kind == .map { + if node.name == 'keys' { + name = 'map_keys' + } + } + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') + && node.name != 'str' { + sym := g.table.get_type_symbol(node.receiver_type) + key := sym.name + '.' + node.name + g.write('/* obf method call: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + // Check if expression is: arr[a..b].clone(), arr[a..].clone() + // if so, then instead of calling array_clone(&array_slice(...)) + // call array_clone_static(array_slice(...)) + mut is_range_slice := false + if node.receiver_type.is_ptr() && !node.left_type.is_ptr() { + if node.left is ast.IndexExpr { + idx := node.left.index + if idx is ast.RangeExpr { + // expr is arr[range].clone() + // use array_clone_static instead of array_clone + name = util.no_dots('${receiver_type_name}_${node.name}_static') + is_range_slice = true + } + } + } + name = g.generic_fn_name(node.concrete_types, name, false) + // TODO2 + // g.generate_tmp_autofree_arg_vars(node, name) + // + // if node.receiver_type != 0 { + // g.write('/*${g.typ(node.receiver_type)}*/') + // g.write('/*expr_type=${g.typ(node.left_type)} rec type=${g.typ(node.receiver_type)}*/') + // } + if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name == 'str' { + g.write('ptr_str(') + } else { + if left_sym.kind == .array { + if array_depth >= 0 { + name = name + '_to_depth' + } + g.write('$name${noscan}(') + } else { + g.write('${name}(') + } + } + if node.receiver_type.is_ptr() + && (!node.left_type.is_ptr() || node.left_type.has_flag(.variadic) + || node.from_embed_type != 0 + || (node.left_type.has_flag(.shared_f) && node.name != 'str')) { + // The receiver is a reference, but the caller provided a value + // Add `&` automatically. + // TODO same logic in call_args() + if !is_range_slice { + if !node.left.is_lvalue() { + g.write('ADDR($rec_cc_type, ') + has_cast = true + } else { + g.write('&') + } + } + } else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str' + && node.from_embed_type == 0 { + if !node.left_type.has_flag(.shared_f) { + g.write('/*rec*/*') + } + } else if !is_range_slice && node.from_embed_type == 0 && node.name != 'str' { + diff := node.left_type.nr_muls() - node.receiver_type.nr_muls() + if diff < 0 { + // TODO + // g.write('&') + } else if diff > 0 { + g.write('/*diff=$diff*/') + g.write([]byte{len: diff, init: `*`}.bytestr()) + } + } + + // if node.left_type.idx() != node.receiver_type.idx() { + // println('${g.typ(node.left_type)} ${g.typ(node.receiver_type)}') + // } + + if g.is_autofree && node.free_receiver && !g.inside_lambda && !g.is_builtin_mod { + // The receiver expression needs to be freed, use the temp var. + fn_name := node.name.replace('.', '_') + arg_name := '_arg_expr_${fn_name}_0_$node.pos.pos' + g.write('/*af receiver arg*/' + arg_name) + } else { + if left_sym.kind == .array && node.left.is_auto_deref_var() + && node.name in ['first', 'last', 'repeat'] { + g.write('*') + } + if node.left is ast.MapInit { + g.write('(map[]){') + g.expr(node.left) + g.write('}[0]') + } else { + g.expr(node.left) + } + if node.from_embed_type != 0 { + embed_name := typ_sym.embed_name() + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(embed_name) + } + if node.left_type.has_flag(.shared_f) { + g.write('->val') + } + } + if has_cast { + g.write(')') + } + is_variadic := node.expected_arg_types.len > 0 + && node.expected_arg_types[node.expected_arg_types.len - 1].has_flag(.variadic) + if node.args.len > 0 || is_variadic { + g.write(', ') + } + // ///////// + /* + if name.contains('subkeys') { + println('call_args $name $node.arg_types.len') + for t in node.arg_types { + sym := g.table.get_type_symbol(t) + print('$sym.name ') + } + println('') +} + */ + // /////// + g.call_args(node) + if array_depth >= 0 { + g.write(', $array_depth') + } + g.write(')') +} + +fn (mut g Gen) fn_call(node ast.CallExpr) { + // call struct field with fn type + // TODO: test node.left instead + // left & left_type will be `x` and `x type` in `x.fieldfn()` + // will be `0` for `foo()` + mut is_interface_call := false + mut is_selector_call := false + if node.left_type != 0 { + left_sym := g.table.get_type_symbol(node.left_type) + if left_sym.kind == .interface_ { + is_interface_call = true + g.write('(*') + } + g.expr(node.left) + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + is_selector_call = true + } + mut name := node.name + is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic'] + print_method := name + is_json_encode := name == 'json.encode' + is_json_encode_pretty := name == 'json.encode_pretty' + is_json_decode := name == 'json.decode' + g.is_json_fn = is_json_encode || is_json_encode_pretty || is_json_decode + mut json_type_str := '' + mut json_obj := '' + if g.is_json_fn { + json_obj = g.new_tmp_var() + mut tmp2 := '' + cur_line := g.go_before_stmt(0) + if is_json_encode || is_json_encode_pretty { + g.gen_json_for_type(node.args[0].typ) + json_type_str = g.typ(node.args[0].typ) + // `json__encode` => `json__encode_User` + // encode_name := c_name(name) + '_' + util.no_dots(json_type_str) + encode_name := js_enc_name(json_type_str) + g.writeln('// json.encode') + g.write('cJSON* $json_obj = ${encode_name}(') + if node.args[0].typ.is_ptr() { + g.write('*') + } + g.call_args(node) + g.writeln(');') + tmp2 = g.new_tmp_var() + if is_json_encode { + g.writeln('string $tmp2 = json__json_print($json_obj);') + } else { + g.writeln('string $tmp2 = json__json_print_pretty($json_obj);') + } + } else { + ast_type := node.args[0].expr as ast.TypeNode + // `json.decode(User, s)` => json.decode_User(s) + typ := c_name(g.typ(ast_type.typ)) + fn_name := c_name(name) + '_' + typ + g.gen_json_for_type(ast_type.typ) + g.writeln('// json.decode') + g.write('cJSON* $json_obj = json__json_parse(') + // Skip the first argument in json.decode which is a type + // its name was already used to generate the function call + g.is_js_call = true + g.call_args(node) + g.is_js_call = false + g.writeln(');') + tmp2 = g.new_tmp_var() + g.writeln('Option_$typ $tmp2 = $fn_name ($json_obj);') + } + if !g.is_autofree { + g.write('cJSON_Delete($json_obj); //del') + } + g.write('\n$cur_line') + name = '' + json_obj = tmp2 + } + if node.language == .c { + // Skip "C." + name = util.no_dots(name[2..]) + } else { + name = c_name(name) + } + // Obfuscate only functions in the main module for now + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + key := node.name + g.write('/* obf call: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + if !is_selector_call { + name = g.generic_fn_name(node.concrete_types, name, false) + } + // TODO2 + // cgen shouldn't modify ast nodes, this should be moved + // g.generate_tmp_autofree_arg_vars(node, name) + // Handle `print(x)` + mut print_auto_str := false + if is_print && node.args[0].typ != ast.string_type { // && !free_tmp_arg_vars { + mut typ := node.args[0].typ + if typ == 0 { + g.checker_bug('print arg.typ is 0', node.pos) + } + if typ != ast.string_type { + expr := node.args[0].expr + if g.is_autofree && !typ.has_flag(.optional) { + // Create a temporary variable so that the value can be freed + tmp := g.new_tmp_var() + // tmps << tmp + g.write('string $tmp = ') + g.gen_expr_to_string(expr, typ) + g.writeln('; ${c_name(print_method)}($tmp); string_free(&$tmp);') + } else { + g.write('${c_name(print_method)}(') + g.gen_expr_to_string(expr, typ) + g.write(')') + } + print_auto_str = true + } + } + if !print_auto_str { + if g.pref.is_debug && node.name == 'panic' { + paline, pafile, pamod, pafn := g.panic_debug_info(node.pos) + g.write('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), ') + g.call_args(node) + g.write(')') + } else { + // Simple function call + // if free_tmp_arg_vars { + // g.writeln(';') + // g.write(cur_line + ' /* <== af cur line*/') + // } + g.write(g.get_ternary_name(name)) + if is_interface_call { + g.write(')') + } + mut tmp_cnt_save := -1 + g.write('(') + if g.is_json_fn { + g.write(json_obj) + } else { + if node.is_keep_alive + && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] { + cur_line := g.go_before_stmt(0) + tmp_cnt_save = g.keep_alive_call_pregen(node) + g.write(cur_line) + for i in 0 .. node.args.len { + if i > 0 { + g.write(', ') + } + g.write('__tmp_arg_${tmp_cnt_save + i}') + } + } else { + g.call_args(node) + } + } + g.write(')') + if tmp_cnt_save >= 0 { + g.writeln(';') + g.keep_alive_call_postgen(node, tmp_cnt_save) + } + } + } + g.is_json_fn = false +} + +fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) { + // g.writeln('// autofree_call_pregen()') + // Create a temporary var before fn call for each argument in order to free it (only if it's a complex expression, + // like `foo(get_string())` or `foo(a + b)` + mut free_tmp_arg_vars := g.is_autofree && !g.is_builtin_mod && node.args.len > 0 + && !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v + if !free_tmp_arg_vars { + return + } + if g.is_js_call { + return + } + if g.inside_const { + return + } + free_tmp_arg_vars = false // set the flag to true only if we have at least one arg to free + g.tmp_count2++ + mut scope := g.file.scope.innermost(node.pos.pos) + // prepend the receiver for now (TODO turn the receiver into a CallArg everywhere?) + mut args := [ast.CallArg{ + typ: node.receiver_type + expr: node.left + is_tmp_autofree: node.free_receiver + }] + args << node.args + // for i, arg in node.args { + for i, arg in args { + if !arg.is_tmp_autofree { + continue + } + if arg.expr is ast.CallExpr { + // Any argument can be an expression that has to be freed. Generate a tmp expression + // for each of those recursively. + g.autofree_call_pregen(arg.expr) + } + free_tmp_arg_vars = true + // t := g.new_tmp_var() + '_arg_expr_${name}_$i' + fn_name := node.name.replace('.', '_') // can't use name... + // t := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + t := '_arg_expr_${fn_name}_${i}_$node.pos.pos' + // g.called_fn_name = name + used := false // scope.known_var(t) + mut s := '$t = ' + if used { + // This means this tmp var name was already used (the same function was called and + // `_arg_fnname_1` was already generated). + // We do not need to declare this variable again, so just generate `t = ...` + // instead of `string t = ...`, and we need to mark this variable as unused, + // so that it's freed after the call. (Used tmp arg vars are not freed to avoid double frees). + if x := scope.find(t) { + match mut x { + ast.Var { x.is_used = false } + else {} + } + } + s = '$t = ' + } else { + scope.register(ast.Var{ + name: t + typ: ast.string_type + is_autofree_tmp: true + pos: node.pos + }) + s = 'string $t = ' + } + // g.expr(arg.expr) + s += g.expr_string(arg.expr) + // g.writeln(';// new af pre') + s += ';// new af2 pre' + g.strs_to_free0 << s + // This tmp arg var will be freed with the rest of the vars at the end of the scope. + } +} + +fn (mut g Gen) autofree_call_postgen(node_pos int) { + // if g.strs_to_free.len == 0 { + // return + // } + // g.writeln('\n/* strs_to_free3: $g.nr_vars_to_free */') + // if g.nr_vars_to_free <= 0 { + // return + // } + /* + for s in g.strs_to_free { + g.writeln('string_free(&$s);') + } + if !g.inside_or_block { + // we need to free the vars both inside the or block (in case of an error) and after it + // if we reset the array here, then the vars will not be freed after the block. + g.strs_to_free = [] + } + */ + if g.inside_vweb_tmpl { + return + } + // g.doing_autofree_tmp = true + // g.write('/* postgen */') + scope := g.file.scope.innermost(node_pos) + for _, obj in scope.objects { + match mut obj { + ast.Var { + // if var.typ == 0 { + // // TODO why 0? + // continue + // } + is_optional := obj.typ.has_flag(.optional) + if is_optional { + // TODO: free optionals + continue + } + if !obj.is_autofree_tmp { + continue + } + if obj.is_used { + // this means this tmp expr var has already been freed + continue + } + obj.is_used = true // TODO bug? sets all vars is_used to true + g.autofree_variable(obj) + // g.nr_vars_to_free-- + } + else {} + } + } + // g.write('/* postgen end */') + // g.doing_autofree_tmp = false +} + +fn (mut g Gen) call_args(node ast.CallExpr) { + args := if g.is_js_call { node.args[1..] } else { node.args } + expected_types := node.expected_arg_types + // only v variadic, C variadic args will be appeneded like normal args + is_variadic := expected_types.len > 0 && expected_types.last().has_flag(.variadic) + && node.language == .v + for i, arg in args { + if is_variadic && i == expected_types.len - 1 { + break + } + use_tmp_var_autofree := g.is_autofree && arg.typ == ast.string_type && arg.is_tmp_autofree + && !g.inside_const && !g.is_builtin_mod + // g.write('/* af=$arg.is_tmp_autofree */') + // some c fn definitions dont have args (cfns.v) or are not updated in checker + // when these are fixed we wont need this check + if i < expected_types.len { + if use_tmp_var_autofree { + if arg.is_tmp_autofree { // && !g.is_js_call { + // We saved expressions in temp variables so that they can be freed later. + // `foo(str + str2) => x := str + str2; foo(x); x.free()` + // g.write('_arg_expr_${g.called_fn_name}_$i') + // Use these variables here. + fn_name := node.name.replace('.', '_') + // name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos' + g.write('/*af arg*/' + name) + } + } else { + if node.concrete_types.len > 0 && arg.expr.is_auto_deref_var() && !arg.is_mut + && !expected_types[i].is_ptr() { + g.write('*') + } + g.ref_or_deref_arg(arg, expected_types[i], node.language) + } + } else { + if use_tmp_var_autofree { + // TODO copypasta, move to an inline fn + fn_name := node.name.replace('.', '_') + // name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos' + g.write('/*af arg2*/' + name) + } else { + g.expr(arg.expr) + } + } + if i < args.len - 1 || is_variadic { + g.write(', ') + } + } + arg_nr := expected_types.len - 1 + if is_variadic { + varg_type := expected_types.last() + variadic_count := args.len - arg_nr + arr_sym := g.table.get_type_symbol(varg_type) + mut arr_info := arr_sym.info as ast.Array + if varg_type.has_flag(.generic) { + if fn_def := g.table.find_fn(node.name) { + if utyp := g.table.resolve_generic_to_concrete(arr_info.elem_type, fn_def.generic_names, + node.concrete_types) + { + arr_info.elem_type = utyp + } + } else { + g.error('unable to find function $node.name', node.pos) + } + } + elem_type := g.typ(arr_info.elem_type) + if g.pref.translated && args.len == 1 { + // Handle `foo(c'str')` for `fn foo(args ...&u8)` + // TODOC2V handle this in a better place + // println(g.table.type_to_str(args[0].typ)) + g.expr(args[0].expr) + } else if args.len > 0 && args[args.len - 1].expr is ast.ArrayDecompose { + g.expr(args[args.len - 1].expr) + } else { + if variadic_count > 0 { + noscan := g.check_noscan(arr_info.elem_type) + g.write('new_array_from_c_array${noscan}($variadic_count, $variadic_count, sizeof($elem_type), _MOV(($elem_type[$variadic_count]){') + for j in arg_nr .. args.len { + g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language) + if j < args.len - 1 { + g.write(', ') + } + } + g.write('}))') + } else { + g.write('__new_array(0, 0, sizeof($elem_type))') + } + } + } +} + +// similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions +fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int { + g.empty_line = true + g.writeln('// keep_alive_call_pregen()') + // reserve the next tmp_vars for arguments + tmp_cnt_save := g.tmp_count + 1 + g.tmp_count += node.args.len + for i, arg in node.args { + // save all arguments in temp vars (not only pointers) to make sure the + // evaluation order is preserved + expected_type := node.expected_arg_types[i] + typ := g.table.get_type_symbol(expected_type).cname + g.write('$typ __tmp_arg_${tmp_cnt_save + i} = ') + // g.expr(arg.expr) + g.ref_or_deref_arg(arg, expected_type, node.language) + g.writeln(';') + } + g.empty_line = false + return tmp_cnt_save +} + +fn (mut g Gen) keep_alive_call_postgen(node ast.CallExpr, tmp_cnt_save int) { + g.writeln('// keep_alive_call_postgen()') + for i, expected_type in node.expected_arg_types { + if expected_type.is_ptr() || expected_type.is_pointer() { + g.writeln('GC_reachable_here(__tmp_arg_${tmp_cnt_save + i});') + } + } +} + +[inline] +fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang ast.Language) { + arg_is_ptr := expected_type.is_ptr() || expected_type.idx() in ast.pointer_type_idxs + expr_is_ptr := arg.typ.is_ptr() || arg.typ.idx() in ast.pointer_type_idxs + if expected_type == 0 { + g.checker_bug('ref_or_deref_arg expected_type is 0', arg.pos) + } + exp_sym := g.table.get_type_symbol(expected_type) + mut needs_closing := false + if arg.is_mut && !arg_is_ptr { + g.write('&/*mut*/') + } else if arg_is_ptr && !expr_is_ptr { + if arg.is_mut { + if exp_sym.kind == .array { + if (arg.expr is ast.Ident && (arg.expr as ast.Ident).kind == .variable) + || arg.expr is ast.SelectorExpr { + g.write('&/*arr*/') + g.expr(arg.expr) + } else { + // Special case for mutable arrays. We can't `&` function + // results, have to use `(array[]){ expr }[0]` hack. + g.write('&/*111*/(array[]){') + g.expr(arg.expr) + g.write('}[0]') + } + return + } + } + if !g.is_json_fn { + if arg.typ == 0 { + g.checker_bug('ref_or_deref_arg arg.typ is 0', arg.pos) + } + arg_typ_sym := g.table.get_type_symbol(arg.typ) + expected_deref_type := if expected_type.is_ptr() { + expected_type.deref() + } else { + expected_type + } + deref_sym := g.table.get_type_symbol(expected_deref_type) + if !((arg_typ_sym.kind == .function) + || deref_sym.kind in [.sum_type, .interface_]) && lang != .c { + if arg.expr.is_lvalue() { + g.write('(voidptr)&/*qq*/') + } else { + mut atype := expected_deref_type + if atype.has_flag(.generic) { + atype = g.unwrap_generic(atype) + } + if atype.has_flag(.generic) { + g.write('(voidptr)&/*qq*/') + } else { + needs_closing = true + g.write('ADDR(${g.typ(atype)}/*qq*/, ') + } + } + } + } + } else if arg.typ.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) { + if expected_type.is_ptr() { + g.write('&') + } + g.expr(arg.expr) + g.write('->val') + return + } + g.expr_with_cast(arg.expr, arg.typ, expected_type) + if needs_closing { + g.write(')') + } +} + +fn (mut g Gen) is_gui_app() bool { + $if windows { + if g.force_main_console { + return false + } + for cf in g.table.cflags { + if cf.value == 'gdi32' { + return true + } + } + } + return false +} + +fn (g &Gen) fileis(s string) bool { + return g.file.path.contains(s) +} + +fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string { + mut msvc_attrs := '' + for attr in attrs { + match attr.name { + 'inline' { + g.write('inline ') + } + 'noinline' { + // since these are supported by GCC, clang and MSVC, we can consider them officially supported. + g.write('__NOINLINE ') + } + 'noreturn' { + // a `[noreturn]` tag tells the compiler, that a function + // *DOES NOT RETURN* to its callsites. + // See: https://en.cppreference.com/w/c/language/_Noreturn + // Such functions should have no return type. They can be used + // in places where `panic(err)` or `exit(0)` can be used. + // panic/1 and exit/0 themselves will also be marked as + // `[noreturn]` soon. + // These functions should have busy `for{}` loops injected + // at their end, when they do not end by calling other fns + // marked by `[noreturn]`. + g.write('VNORETURN ') + } + 'irq_handler' { + g.write('__IRQHANDLER ') + } + '_cold' { + // GCC/clang attributes + // prefixed by _ to indicate they're for advanced users only and not really supported by V. + // source for descriptions: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes + // The cold attribute on functions is used to inform the compiler that the function is unlikely + // to be executed. The function is optimized for size rather than speed and on many targets it + // is placed into a special subsection of the text section so all cold functions appear close + // together, improving code locality of non-cold parts of program. + g.write('__attribute__((cold)) ') + } + '_constructor' { + // The constructor attribute causes the function to be called automatically before execution + // enters main (). + g.write('__attribute__((constructor)) ') + } + '_destructor' { + // The destructor attribute causes the function to be called automatically after main () + // completes or exit () is called. + g.write('__attribute__((destructor)) ') + } + '_flatten' { + // Generally, inlining into a function is limited. For a function marked with this attribute, + // every call inside this function is inlined, if possible. + g.write('__attribute__((flatten)) ') + } + '_hot' { + // The hot attribute on a function is used to inform the compiler that the function is a hot + // spot of the compiled program. + g.write('__attribute__((hot)) ') + } + '_malloc' { + // This tells the compiler that a function is malloc-like, i.e., that the pointer P returned by + // the function cannot alias any other pointer valid when the function returns, and moreover no + // pointers to valid objects occur in any storage addressed by P. + g.write('__attribute__((malloc)) ') + } + '_pure' { + // Calls to functions whose return value is not affected by changes to the observable state + // of the program and that have no observable effects on such state other than to return a + // value may lend themselves to optimizations such as common subexpression elimination. + // Declaring such functions with the const attribute allows GCC to avoid emitting some calls in + // repeated invocations of the function with the same argument values. + g.write('__attribute__((const)) ') + } + '_naked' { + g.write('__attribute__((naked)) ') + } + 'windows_stdcall' { + // windows attributes (msvc/mingw) + // prefixed by windows to indicate they're for advanced users only and not really supported by V. + msvc_attrs += '__stdcall ' + } + 'console' { + g.force_main_console = true + } + else { + // nothing but keep V happy + } + } + } + return msvc_attrs +} diff --git a/v_windows/v/vlib/v/gen/c/index.v b/v_windows/v/vlib/v/gen/c/index.v new file mode 100644 index 0000000..26dc455 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/index.v @@ -0,0 +1,452 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast +import v.util + +fn (mut g Gen) index_expr(node ast.IndexExpr) { + if node.index is ast.RangeExpr { + g.range_expr(node, node.index) + } else { + sym := g.table.get_final_type_symbol(node.left_type) + if sym.kind == .array { + g.index_of_array(node, sym) + } else if sym.kind == .array_fixed { + g.index_of_fixed_array(node, sym) + } else if sym.kind == .map { + g.index_of_map(node, sym) + } else if sym.kind == .string && !node.left_type.is_ptr() { + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + if is_direct_array_access { + g.expr(node.left) + g.write('.str[ ') + g.expr(node.index) + g.write(']') + } else { + gen_or := node.or_expr.kind != .absent || node.is_option + if gen_or { + tmp_opt := g.new_tmp_var() + cur_line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + opt_elem_type := g.typ(ast.byte_type.set_flag(.optional)) + g.write('$opt_elem_type $tmp_opt = string_at_with_check(') + g.expr(node.left) + g.write(', ') + g.expr(node.index) + g.writeln(');') + if !node.is_option { + g.or_block(tmp_opt, node.or_expr, ast.byte_type) + } + g.write('\n$cur_line*(byte*)&${tmp_opt}.data') + } else { + g.write('string_at(') + g.expr(node.left) + g.write(', ') + g.expr(node.index) + g.write(')') + } + } + } else { + g.expr(node.left) + g.write('[') + g.expr(node.index) + g.write(']') + } + } +} + +fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { + sym := g.table.get_final_type_symbol(node.left_type) + if sym.kind == .string { + g.write('string_substr(') + g.expr(node.left) + } else if sym.kind == .array { + g.write('array_slice(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + } else if sym.kind == .array_fixed { + // Convert a fixed array to V array when doing `fixed_arr[start..end]` + info := sym.info as ast.ArrayFixed + noscan := g.check_noscan(info.elem_type) + g.write('array_slice(new_array_from_c_array${noscan}(') + g.write('$info.size') + g.write(', $info.size') + g.write(', sizeof(') + if node.left_type.is_ptr() { + g.write('(*') + } + g.expr(node.left) + if node.left_type.is_ptr() { + g.write(')') + } + g.write('[0]), ') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(')') + } else { + g.expr(node.left) + } + g.write(', ') + if range.has_low { + g.expr(range.low) + } else { + g.write('0') + } + g.write(', ') + if range.has_high { + g.expr(range.high) + } else if sym.kind == .array_fixed { + info := sym.info as ast.ArrayFixed + g.write('$info.size') + } else if node.left_type.is_ptr() { + g.write('(') + g.write('*') + g.expr(node.left) + g.write(')') + g.write('.len') + } else { + g.expr(node.left) + g.write('.len') + } + g.write(')') +} + +fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { + gen_or := node.or_expr.kind != .absent || node.is_option + left_is_ptr := node.left_type.is_ptr() + info := sym.info as ast.Array + elem_type_str := g.typ(info.elem_type) + elem_type := info.elem_type + elem_typ := g.table.get_type_symbol(elem_type) + // `vals[i].field = x` is an exception and requires `array_get`: + // `(*(Val*)array_get(vals, i)).field = x;` + is_selector := node.left is ast.SelectorExpr + if g.is_assign_lhs && !is_selector && node.is_setter { + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + is_op_assign := g.assign_op != .assign && info.elem_type != ast.string_type + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else if is_op_assign { + g.write('(*($array_ptr_type_str)array_get(') + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('*') + } + } else { + g.is_arraymap_set = true // special handling of assign_op and closing with '})' + g.write('array_set(') + if !left_is_ptr || node.left_type.has_flag(.shared_f) { + g.write('&') + } + } + g.expr(node.left) + if node.left_type.has_flag(.shared_f) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + if is_direct_array_access { + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('->') + } else { + g.write('.') + } + g.write('data)[') + g.expr(node.index) + g.write(']') + } else { + g.write(', ') + g.expr(node.index) + if !is_op_assign { + mut need_wrapper := true + /* + match node.right { + ast.EnumVal, ast.Ident { + // `&x` is enough for variables and enums + // `&(Foo[]){ ... }` is only needed for function calls and literals + need_wrapper = false + } + else {} + } + */ + if need_wrapper { + if elem_typ.kind == .function { + g.write(', &(voidptr[]) { ') + } else { + g.write(', &($elem_type_str[]) { ') + } + } else { + g.write(', &') + } + } else { + // `x[0] *= y` + g.write('))') + } + } + } else { + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + // do not clone inside `opt_ok(opt_ok(&(string[]) {..})` before returns + needs_clone := info.elem_type == ast.string_type_idx && g.is_autofree && !(g.inside_return + && g.fn_decl.return_type.has_flag(.optional)) && !g.is_assign_lhs + is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result + cur_line := if is_gen_or_and_assign_rhs { + line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + line + } else { + '' + } + tmp_opt := if gen_or { g.new_tmp_var() } else { '' } + tmp_opt_ptr := if gen_or { g.new_tmp_var() } else { '' } + if gen_or { + g.write('$array_ptr_type_str $tmp_opt_ptr = ($array_ptr_type_str)/*ee elem_ptr_typ */(array_get_with_check(') + } else { + if needs_clone { + g.write('/*2*/string_clone(') + } + if g.is_fn_index_call { + if elem_typ.info is ast.FnType { + g.write('((') + g.write_fn_ptr_decl(&elem_typ.info, '') + g.write(')(*($array_ptr_type_str)/*ee elem_typ */array_get(') + } + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('*') + } + } else if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else { + g.write('(*($array_ptr_type_str)/*ee elem_typ */array_get(') + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('*') + } + } + } + g.expr(node.left) + // TODO: test direct_array_access when 'shared' is implemented + if node.left_type.has_flag(.shared_f) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + if is_direct_array_access && !gen_or { + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('->') + } else { + g.write('.') + } + g.write('data)[') + g.expr(node.index) + g.write(']') + } else { + g.write(', ') + g.expr(node.index) + if g.is_fn_index_call { + g.write(')))') + } else { + g.write('))') + } + } + if needs_clone { + g.write(')') + } + if gen_or { + g.writeln(';') + opt_elem_type := g.typ(elem_type.set_flag(.optional)) + g.writeln('$opt_elem_type $tmp_opt = {0};') + g.writeln('if ($tmp_opt_ptr) {') + g.writeln('\t*(($elem_type_str*)&${tmp_opt}.data) = *(($elem_type_str*)$tmp_opt_ptr);') + g.writeln('} else {') + g.writeln('\t${tmp_opt}.state = 2; ${tmp_opt}.err = _v_error(_SLIT("array index out of range"));') + g.writeln('}') + if !node.is_option { + g.or_block(tmp_opt, node.or_expr, elem_type) + } + g.write('\n$cur_line*($elem_type_str*)${tmp_opt}.data') + } + } +} + +fn (mut g Gen) index_of_fixed_array(node ast.IndexExpr, sym ast.TypeSymbol) { + info := sym.info as ast.ArrayFixed + elem_type := info.elem_type + elem_sym := g.table.get_type_symbol(elem_type) + is_fn_index_call := g.is_fn_index_call && elem_sym.info is ast.FnType + + if is_fn_index_call { + g.write('(*') + } + if node.left_type.is_ptr() { + g.write('(*') + g.expr(node.left) + g.write(')') + } else { + g.expr(node.left) + } + g.write('[') + direct := g.fn_decl != 0 && g.fn_decl.is_direct_arr + if direct || node.index is ast.IntegerLiteral { + g.expr(node.index) + } else { + // bounds check + g.write('v_fixed_index(') + g.expr(node.index) + g.write(', $info.size)') + } + g.write(']') + if is_fn_index_call { + g.write(')') + } +} + +fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { + gen_or := node.or_expr.kind != .absent || node.is_option + left_is_ptr := node.left_type.is_ptr() + info := sym.info as ast.Map + key_type_str := g.typ(info.key_type) + elem_type := info.value_type + elem_type_str := g.typ(elem_type) + elem_typ := g.table.get_type_symbol(elem_type) + get_and_set_types := elem_typ.kind in [.struct_, .map] + if g.is_assign_lhs && !g.is_arraymap_set && !get_and_set_types { + if g.assign_op == .assign || info.value_type == ast.string_type { + g.is_arraymap_set = true + g.write('map_set(') + } else { + if node.is_setter { + g.write('(*(($elem_type_str*)map_get_and_set((map*)') + } else { + g.write('(*(($elem_type_str*)map_get((map*)') + } + } + if !left_is_ptr || node.left_type.has_flag(.shared_f) { + g.write('&') + } + if node.left is ast.IndexExpr { + g.inside_map_index = true + g.expr(node.left) + g.inside_map_index = false + } else { + g.expr(node.left) + } + if node.left_type.has_flag(.shared_f) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', &($key_type_str[]){') + g.expr(node.index) + g.write('}') + if elem_typ.kind == .function { + g.write(', &(voidptr[]) { ') + } else { + g.arraymap_set_pos = g.out.len + g.write(', &($elem_type_str[]) { ') + } + if g.assign_op != .assign && info.value_type != ast.string_type { + zero := g.type_default(info.value_type) + g.write('$zero })))') + } + } else if g.inside_map_postfix || g.inside_map_infix || g.inside_map_index + || (g.is_assign_lhs && !g.is_arraymap_set && get_and_set_types) { + zero := g.type_default(info.value_type) + if node.is_setter { + g.write('(*($elem_type_str*)map_get_and_set((map*)') + } else { + g.write('(*($elem_type_str*)map_get((map*)') + } + if !left_is_ptr { + g.write('&') + } + g.expr(node.left) + g.write(', &($key_type_str[]){') + g.expr(node.index) + g.write('}, &($elem_type_str[]){ $zero }))') + } else { + zero := g.type_default(info.value_type) + is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result + cur_line := if is_gen_or_and_assign_rhs { + line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + line + } else { + '' + } + tmp_opt := if gen_or { g.new_tmp_var() } else { '' } + tmp_opt_ptr := if gen_or { g.new_tmp_var() } else { '' } + if gen_or { + g.write('$elem_type_str* $tmp_opt_ptr = ($elem_type_str*)/*ee elem_ptr_typ */(map_get_check(') + } else { + if g.is_fn_index_call { + if elem_typ.info is ast.FnType { + g.write('((') + g.write_fn_ptr_decl(&elem_typ.info, '') + g.write(')(*(voidptr*)map_get(') + } + } else if elem_typ.kind == .function { + g.write('(*(voidptr*)map_get(') + } else { + g.write('(*($elem_type_str*)map_get(') + } + } + if !left_is_ptr || node.left_type.has_flag(.shared_f) { + g.write('ADDR(map, ') + g.expr(node.left) + } else { + g.write('(') + g.expr(node.left) + } + if node.left_type.has_flag(.shared_f) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + g.write('), &($key_type_str[]){') + g.expr(node.index) + g.write('}') + if gen_or { + g.write('))') + } else if g.is_fn_index_call { + g.write(', &(voidptr[]){ $zero })))') + } else if elem_typ.kind == .function { + g.write(', &(voidptr[]){ $zero }))') + } else { + g.write(', &($elem_type_str[]){ $zero }))') + } + if gen_or { + g.writeln(';') + opt_elem_type := g.typ(elem_type.set_flag(.optional)) + g.writeln('$opt_elem_type $tmp_opt = {0};') + g.writeln('if ($tmp_opt_ptr) {') + g.writeln('\t*(($elem_type_str*)&${tmp_opt}.data) = *(($elem_type_str*)$tmp_opt_ptr);') + g.writeln('} else {') + g.writeln('\t${tmp_opt}.state = 2; ${tmp_opt}.err = _v_error(_SLIT("array index out of range"));') + g.writeln('}') + if !node.is_option { + g.or_block(tmp_opt, node.or_expr, elem_type) + } + g.write('\n$cur_line*($elem_type_str*)${tmp_opt}.data') + } + } +} diff --git a/v_windows/v/vlib/v/gen/c/infix_expr.v b/v_windows/v/vlib/v/gen/c/infix_expr.v new file mode 100644 index 0000000..dc842c9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/infix_expr.v @@ -0,0 +1,628 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast +import v.token +import v.util + +fn (mut g Gen) infix_expr(node ast.InfixExpr) { + if node.auto_locked != '' { + g.writeln('sync__RwMutex_lock(&$node.auto_locked->mtx);') + } + match node.op { + .arrow { + g.infix_expr_arrow_op(node) + } + .eq, .ne { + g.infix_expr_eq_op(node) + } + .gt, .ge, .lt, .le { + g.infix_expr_cmp_op(node) + } + .key_in, .not_in { + g.infix_expr_in_op(node) + } + .key_is, .not_is { + g.infix_expr_is_op(node) + } + .plus, .minus, .mul, .div, .mod { + g.infix_expr_arithmetic_op(node) + } + .left_shift { + g.infix_expr_left_shift_op(node) + } + .and, .logical_or { + g.infix_expr_and_or_op(node) + } + else { + // `x & y == 0` => `(x & y) == 0` in C + need_par := node.op in [.amp, .pipe, .xor] + if need_par { + g.write('(') + } + g.gen_plain_infix_expr(node) + if need_par { + g.write(')') + } + } + } + if node.auto_locked != '' { + g.writeln(';') + g.write('sync__RwMutex_unlock(&$node.auto_locked->mtx)') + } +} + +// infix_expr_arrow_op generates C code for pushing into channels (chan <- val) +fn (mut g Gen) infix_expr_arrow_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + styp := left.sym.cname + elem_type := (left.sym.info as ast.Chan).elem_type + gen_or := node.or_block.kind != .absent + tmp_opt := if gen_or { g.new_tmp_var() } else { '' } + if gen_or { + elem_styp := g.typ(elem_type) + g.register_chan_push_optional_call(elem_styp, styp) + g.write('Option_void $tmp_opt = __Option_${styp}_pushval(') + } else { + g.write('__${styp}_pushval(') + } + g.expr(node.left) + g.write(', ') + g.expr(node.right) + g.write(')') + if gen_or { + g.or_block(tmp_opt, node.or_block, ast.void_type) + } +} + +// infix_expr_eq_op generates code for `==` and `!=` +fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '==') + if (left.typ.is_ptr() && right.typ.is_int()) || (right.typ.is_ptr() && left.typ.is_int()) { + g.gen_plain_infix_expr(node) + } else if (left.typ.idx() == ast.string_type_idx || (!has_operator_overloading + && left.unaliased.idx() == ast.string_type_idx)) && node.right is ast.StringLiteral + && (node.right as ast.StringLiteral).val == '' { + // `str == ''` -> `str.len == 0` optimization + g.write('(') + g.expr(node.left) + g.write(')') + arrow := if left.typ.is_ptr() { '->' } else { '.' } + g.write('${arrow}len $node.op 0') + } else if has_operator_overloading { + if node.op == .ne { + g.write('!') + } + g.write(g.typ(left.unaliased.set_nr_muls(0))) + g.write('__eq(') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(', ') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(')') + } else if left.typ.idx() == right.typ.idx() + && left.sym.kind in [.array, .array_fixed, .alias, .map, .struct_, .sum_type] { + match left.sym.kind { + .alias { + ptr_typ := g.gen_alias_equality_fn(left.typ) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_alias_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .array { + ptr_typ := g.gen_array_equality_fn(left.unaliased.clear_flag(.shared_f)) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_eq(') + if left.typ.is_ptr() && !left.typ.has_flag(.shared_f) { + g.write('*') + } + g.expr(node.left) + if left.typ.has_flag(.shared_f) { + if left.typ.is_ptr() { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', ') + if right.typ.is_ptr() && !right.typ.has_flag(.shared_f) { + g.write('*') + } + g.expr(node.right) + if right.typ.has_flag(.shared_f) { + if right.typ.is_ptr() { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(')') + } + .array_fixed { + ptr_typ := g.gen_fixed_array_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_eq(') + if left.typ.is_ptr() { + g.write('*') + } + if node.left is ast.ArrayInit { + s := g.typ(left.unaliased) + g.write('($s)') + } + g.expr(node.left) + g.write(', ') + if node.right is ast.ArrayInit { + s := g.typ(right.unaliased) + g.write('($s)') + } + g.expr(node.right) + g.write(')') + } + .map { + ptr_typ := g.gen_map_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_map_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .struct_ { + ptr_typ := g.gen_struct_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_struct_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .sum_type { + ptr_typ := g.gen_sumtype_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_sumtype_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + else {} + } + } else if left.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && right.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + unsigned_type: left.unaliased + unsigned_expr: node.left + signed_type: right.unaliased + signed_expr: node.right + ) + } else if right.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && left.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + reverse: true + unsigned_type: right.unaliased + unsigned_expr: node.right + signed_type: left.unaliased + signed_expr: node.left + ) + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_cmp_op generates code for `<`, `<=`, `>`, `>=` +// It handles operator overloading when necessary +fn (mut g Gen) infix_expr_cmp_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '<') + if left.sym.kind == right.sym.kind && has_operator_overloading { + if node.op in [.le, .ge] { + g.write('!') + } + g.write(g.typ(left.typ.set_nr_muls(0))) + g.write('__lt') + if node.op in [.lt, .ge] { + g.write('(') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(', ') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(')') + } else { + g.write('(') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(', ') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(')') + } + } else if left.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && right.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + unsigned_type: left.unaliased + unsigned_expr: node.left + signed_type: right.unaliased + signed_expr: node.right + ) + } else if right.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && left.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + reverse: true + unsigned_type: right.unaliased + unsigned_expr: node.right + signed_type: left.unaliased + signed_expr: node.left + ) + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_in_op generates code for `in` and `!in` +fn (mut g Gen) infix_expr_in_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + if node.op == .not_in { + g.write('!') + } + if right.unaliased_sym.kind == .array { + if mut node.right is ast.ArrayInit { + if node.right.exprs.len > 0 { + // `a in [1,2,3]` optimization => `a == 1 || a == 2 || a == 3` + // avoids an allocation + g.write('(') + g.infix_expr_in_optimization(node.left, node.right) + g.write(')') + return + } + } + fn_name := g.gen_array_contains_method(node.right_type) + g.write('(${fn_name}(') + if right.typ.is_ptr() && right.typ.share() != .shared_t { + g.write('*') + } + g.expr(node.right) + if right.typ.share() == .shared_t { + g.write('->val') + } + g.write(', ') + g.expr(node.left) + g.write('))') + return + } else if right.unaliased_sym.kind == .map { + g.write('_IN_MAP(') + if !left.typ.is_ptr() { + styp := g.typ(node.left_type) + g.write('ADDR($styp, ') + g.expr(node.left) + g.write(')') + } else { + g.expr(node.left) + } + g.write(', ') + if !right.typ.is_ptr() { + g.write('ADDR(map, ') + g.expr(node.right) + g.write(')') + } else { + g.expr(node.right) + } + g.write(')') + } else if right.unaliased_sym.kind == .string { + g.write('string_contains(') + g.expr(node.right) + g.write(', ') + g.expr(node.left) + g.write(')') + } +} + +// infix_expr_in_optimization optimizes ` in ` expressions, +// and transform them in a serie of equality comparison +// i.e. `a in [1,2,3]` => `a == 1 || a == 2 || a == 3` +fn (mut g Gen) infix_expr_in_optimization(left ast.Expr, right ast.ArrayInit) { + is_str := right.elem_type.idx() == ast.string_type_idx + elem_sym := g.table.get_type_symbol(right.elem_type) + is_array := elem_sym.kind == .array + for i, array_expr in right.exprs { + if is_str { + g.write('string__eq(') + } else if is_array { + ptr_typ := g.gen_array_equality_fn(right.elem_type) + g.write('${ptr_typ}_arr_eq(') + } + g.expr(left) + if is_str || is_array { + g.write(', ') + } else { + g.write(' == ') + } + g.expr(array_expr) + if is_str || is_array { + g.write(')') + } + if i < right.exprs.len - 1 { + g.write(' || ') + } + } +} + +// infix_expr_is_op generates code for `is` and `!is` +fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { + sym := g.table.get_type_symbol(node.left_type) + right_sym := g.table.get_type_symbol(node.right_type) + if sym.kind == .interface_ && right_sym.kind == .interface_ { + g.gen_interface_is_op(node) + return + } + + cmp_op := if node.op == .key_is { '==' } else { '!=' } + g.write('(') + g.expr(node.left) + g.write(')') + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + if sym.kind == .interface_ { + g.write('_typ $cmp_op ') + // `_Animal_Dog_index` + sub_type := match mut node.right { + ast.TypeNode { node.right.typ } + ast.None { g.table.type_idxs['None__'] } + else { ast.Type(0) } + } + sub_sym := g.table.get_type_symbol(sub_type) + g.write('_${c_name(sym.name)}_${c_name(sub_sym.name)}_index') + return + } else if sym.kind == .sum_type { + g.write('_typ $cmp_op ') + } + g.expr(node.right) +} + +fn (mut g Gen) gen_interface_is_op(node ast.InfixExpr) { + mut left_sym := g.table.get_type_symbol(node.left_type) + right_sym := g.table.get_type_symbol(node.right_type) + + mut info := left_sym.info as ast.Interface + + common_variants := info.conversions[node.right_type] or { + left_variants := g.table.iface_types[left_sym.name] + right_variants := g.table.iface_types[right_sym.name] + c := left_variants.filter(it in right_variants) + info.conversions[node.right_type] = c + c + } + left_sym.info = info + if common_variants.len == 0 { + g.write('false') + return + } + g.write('I_${left_sym.cname}_is_I_${right_sym.cname}(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(')') +} + +// infix_expr_arithmetic_op generates code for `+`, `-`, `*`, `/`, and `%` +// It handles operator overloading when necessary +fn (mut g Gen) infix_expr_arithmetic_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + method := g.table.type_find_method(left.sym, node.op.str()) or { + g.gen_plain_infix_expr(node) + return + } + left_styp := g.typ(left.typ.set_nr_muls(0)) + g.write(left_styp) + g.write('_') + g.write(util.replace_op(node.op.str())) + g.write('(') + g.op_arg(node.left, method.params[0].typ, left.typ) + g.write(', ') + g.op_arg(node.right, method.params[1].typ, right.typ) + g.write(')') +} + +// infix_expr_left_shift_op generates code for the `<<` operator +// This can either be a value pushed into an array or a bit shift +fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + if left.unaliased_sym.kind == .array { + // arr << val + tmp_var := g.new_tmp_var() + array_info := left.unaliased_sym.info as ast.Array + noscan := g.check_noscan(array_info.elem_type) + //&& array_info.elem_type != g.unwrap_generic(node.right_type) + if right.unaliased_sym.kind == .array && array_info.elem_type != right.typ { + // push an array => PUSH_MANY, but not if pushing an array to 2d array (`[][]int << []int`) + g.write('_PUSH_MANY${noscan}(') + mut expected_push_many_atype := left.typ + if !expected_push_many_atype.is_ptr() { + // fn f(mut a []int) { a << [1,2,3] } -> type of `a` is `array_int*` -> no need for & + g.write('&') + } else { + expected_push_many_atype = expected_push_many_atype.deref() + } + g.expr(node.left) + g.write(', (') + g.expr_with_cast(node.right, node.right_type, left.unaliased) + styp := g.typ(expected_push_many_atype) + g.write('), $tmp_var, $styp)') + } else { + // push a single element + elem_type_str := g.typ(array_info.elem_type) + elem_sym := g.table.get_type_symbol(array_info.elem_type) + g.write('array_push${noscan}((array*)') + if !left.typ.is_ptr() { + g.write('&') + } + g.expr(node.left) + if elem_sym.kind == .function { + g.write(', _MOV((voidptr[]){ ') + } else { + g.write(', _MOV(($elem_type_str[]){ ') + } + // if g.autofree + needs_clone := array_info.elem_type.idx() == ast.string_type_idx && !g.is_builtin_mod + if needs_clone { + g.write('string_clone(') + } + if right.unaliased_sym.kind == .interface_ && node.right.is_auto_deref_var() { + g.write('*') + } + g.expr_with_cast(node.right, node.right_type, array_info.elem_type) + if needs_clone { + g.write(')') + } + g.write(' }))') + } + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_and_or_op generates code for `&&` and `||` +fn (mut g Gen) infix_expr_and_or_op(node ast.InfixExpr) { + if node.right is ast.IfExpr { + // b := a && if true { a = false ...} else {...} + prev_inside_ternary := g.inside_ternary + g.inside_ternary = 0 + if g.need_tmp_var_in_if(node.right) { + tmp := g.new_tmp_var() + cur_line := g.go_before_stmt(0).trim_space() + g.empty_line = true + g.write('bool $tmp = (') + g.expr(node.left) + g.writeln(');') + g.stmt_path_pos << g.out.len + g.write('$cur_line $tmp $node.op.str() ') + g.infix_left_var_name = if node.op == .and { tmp } else { '!$tmp' } + g.expr(node.right) + g.infix_left_var_name = '' + g.inside_ternary = prev_inside_ternary + return + } + g.inside_ternary = prev_inside_ternary + } + g.gen_plain_infix_expr(node) +} + +// gen_plain_infix_expr generates basic code for infix expressions, +// without any overloading of any kind +// i.e. v`a + 1` => c`a + 1` +// It handles auto dereferencing of variables, as well as automatic casting +// (see Gen.expr_with_cast for more details) +fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) { + if node.left_type.is_ptr() && node.left.is_auto_deref_var() { + g.write('*') + } + g.expr(node.left) + g.write(' $node.op.str() ') + g.expr_with_cast(node.right, node.right_type, node.left_type) +} + +fn (mut g Gen) op_arg(expr ast.Expr, expected ast.Type, got ast.Type) { + mut needs_closing := false + mut nr_muls := got.nr_muls() + if expected.is_ptr() { + if nr_muls > 0 { + nr_muls-- + } else { + if expr.is_lvalue() { + g.write('&') + } else { + styp := g.typ(got.set_nr_muls(0)) + g.write('ADDR($styp, ') + needs_closing = true + } + } + } + g.write('*'.repeat(nr_muls)) + g.expr(expr) + if needs_closing { + g.write(')') + } +} + +struct GenSafeIntegerCfg { + op token.Kind + reverse bool + unsigned_type ast.Type + unsigned_expr ast.Expr + signed_type ast.Type + signed_expr ast.Expr +} + +// gen_safe_integer_infix_expr generates code for comparison of +// unsigned and signed integers +fn (mut g Gen) gen_safe_integer_infix_expr(cfg GenSafeIntegerCfg) { + bitsize := if cfg.unsigned_type.idx() == ast.u32_type_idx + && cfg.signed_type.idx() != ast.i64_type_idx { + 32 + } else { + 64 + } + op_idx := int(cfg.op) - int(token.Kind.eq) + op_str := if cfg.reverse { cmp_rev[op_idx] } else { cmp_str[op_idx] } + g.write('_us${bitsize}_${op_str}(') + g.expr(cfg.unsigned_expr) + g.write(',') + g.expr(cfg.signed_expr) + g.write(')') +} diff --git a/v_windows/v/vlib/v/gen/c/json.v b/v_windows/v/vlib/v/gen/c/json.v new file mode 100644 index 0000000..cfc480d --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/json.v @@ -0,0 +1,339 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast +import v.util +import strings + +// TODO replace with comptime code generation. +// TODO remove cJSON dependency. +// OLD: User decode_User(string js) { +// now it's +// User decode_User(cJSON* root) { +// User res; +// res.name = decode_string(js_get(root, "name")); +// res.profile = decode_Profile(js_get(root, "profile")); +// return res; +// } +// Codegen json_decode/encode funcs +fn (mut g Gen) gen_json_for_type(typ ast.Type) { + utyp := g.unwrap_generic(typ) + mut dec := strings.new_builder(100) + mut enc := strings.new_builder(100) + sym := g.table.get_type_symbol(utyp) + styp := g.typ(utyp) + if is_js_prim(sym.name) || sym.kind == .enum_ { + return + } + if sym.kind == .array { + // return + } + if sym.name in g.json_types { + return + } + g.json_types << sym.name + // println('gen_json_for_type($sym.name)') + // decode_TYPE funcs receive an actual cJSON* object to decode + // cJSON_Parse(str) call is added by the compiler + // Code gen decoder + dec_fn_name := js_dec_name(styp) + // Make sure that this optional type actually exists + g.register_optional(utyp) + dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)' + dec.writeln(' +$dec_fn_dec { + $styp res; + if (!root) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + // fprintf(stderr, "Error in decode() for $styp error_ptr=: %s\\n", error_ptr); + // printf("\\nbad js=%%s\\n", js.str); + return (Option_$styp){.state = 2,.err = _v_error(tos2((byteptr)error_ptr)),.data = {0}}; + } + } +') + g.json_forward_decls.writeln('$dec_fn_dec;') + // Code gen encoder + // encode_TYPE funcs receive an object to encode + enc_fn_name := js_enc_name(styp) + enc_fn_dec := 'cJSON* ${enc_fn_name}($styp val)' + g.json_forward_decls.writeln('$enc_fn_dec;\n') + enc.writeln(' +$enc_fn_dec { +\tcJSON *o;') + if sym.kind == .array { + // Handle arrays + value_type := g.table.value_type(utyp) + // If we have `[]Profile`, have to register a Profile en(de)coder first + g.gen_json_for_type(value_type) + dec.writeln(g.decode_array(value_type)) + enc.writeln(g.encode_array(value_type)) + // enc += g.encode_array(t) + } else if sym.kind == .map { + // Handle maps + m := sym.info as ast.Map + g.gen_json_for_type(m.key_type) + g.gen_json_for_type(m.value_type) + dec.writeln(g.decode_map(m.key_type, m.value_type)) + enc.writeln(g.encode_map(m.key_type, m.value_type)) + } else if sym.kind == .alias { + a := sym.info as ast.Alias + parent_typ := a.parent_type + psym := g.table.get_type_symbol(parent_typ) + if is_js_prim(g.typ(parent_typ)) { + g.gen_json_for_type(parent_typ) + return + } + enc.writeln('\to = cJSON_CreateObject();') + if psym.info !is ast.Struct { + verror('json: $sym.name is not struct') + } + g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec) + } else { + enc.writeln('\to = cJSON_CreateObject();') + // Structs. Range through fields + if sym.info !is ast.Struct { + verror('json: $sym.name is not struct') + } + g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec) + } + // cJSON_delete + // p.cgen.fns << '$dec return opt_ok(res); \n}' + dec.writeln('\tOption_$styp ret;') + dec.writeln('\topt_ok(&res, (Option*)&ret, sizeof(res));') + dec.writeln('\treturn ret;\n}') + enc.writeln('\treturn o;\n}') + g.definitions.writeln(dec.str()) + g.gowrappers.writeln(enc.str()) +} + +[inline] +fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc strings.Builder, mut dec strings.Builder) { + info := type_info as ast.Struct + for field in info.fields { + mut name := field.name + mut is_raw := false + mut is_skip := false + mut is_required := false + for attr in field.attrs { + match attr.name { + 'json' { + name = attr.arg + } + 'skip' { + is_skip = true + } + 'raw' { + is_raw = true + } + 'required' { + is_required = true + } + else {} + } + } + if is_skip { + continue + } + field_type := g.typ(field.typ) + field_sym := g.table.get_type_symbol(field.typ) + // First generate decoding + if is_raw { + dec.writeln('\tres.${c_name(field.name)} = tos5(cJSON_PrintUnformatted(' + + 'js_get(root, "$name")));') + } else { + // Now generate decoders for all field types in this struct + // need to do it here so that these functions are generated first + g.gen_json_for_type(field.typ) + dec_name := js_dec_name(field_type) + if is_js_prim(field_type) { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = $dec_name (jsonroot_$tmp);') + } else if field_sym.kind == .enum_ { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(jsonroot_$tmp);') + } else if field_sym.name == 'time.Time' { + // time struct requires special treatment + // it has to be decoded from a unix timestamp number + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_$tmp));') + } else if field_sym.kind == .alias { + alias := field_sym.info as ast.Alias + parent_type := g.typ(alias.parent_type) + parent_dec_name := js_dec_name(parent_type) + if is_js_prim(parent_type) { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = $parent_dec_name (jsonroot_$tmp);') + } else { + g.gen_json_for_type(field.typ) + tmp := g.new_tmp_var() + gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } else { + tmp := g.new_tmp_var() + gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } + // Encoding + mut enc_name := js_enc_name(field_type) + if !is_js_prim(field_type) { + if field_sym.kind == .alias { + ainfo := field_sym.info as ast.Alias + enc_name = js_enc_name(g.typ(ainfo.parent_type)) + } + } + if field_sym.kind == .enum_ { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));\n') + } else { + if field_sym.name == 'time.Time' { + // time struct requires special treatment + // it has to be encoded as a unix timestamp number + enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}._v_unix));') + } else { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));\n') + } + } + } +} + +fn gen_js_get(styp string, tmp string, name string, mut dec strings.Builder, is_required bool) { + dec.writeln('\tcJSON *jsonroot_$tmp = js_get(root,"$name");') + if is_required { + dec.writeln('\tif(jsonroot_$tmp == 0) {') + dec.writeln('\t\treturn (Option_$styp){ .state = 2, .err = _v_error(_SLIT("expected field \'$name\' is missing")), .data = {0} };') + dec.writeln('\t}') + } +} + +fn gen_js_get_opt(dec_name string, field_type string, styp string, tmp string, name string, mut dec strings.Builder, is_required bool) { + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tOption_$field_type $tmp = $dec_name (jsonroot_$tmp);') + dec.writeln('\tif(${tmp}.state != 0) {') + dec.writeln('\t\treturn (Option_$styp){ .state = ${tmp}.state, .err = ${tmp}.err, .data = {0} };') + dec.writeln('\t}') +} + +fn js_enc_name(typ string) string { + suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ } + name := 'json__encode_$suffix' + return util.no_dots(name) +} + +fn js_dec_name(typ string) string { + name := 'json__decode_$typ' + return util.no_dots(name) +} + +fn is_js_prim(typ string) bool { + return typ in ['int', 'string', 'bool', 'f32', 'f64', 'i8', 'i16', 'i64', 'u16', 'u32', 'u64', + 'byte', + ] +} + +fn (mut g Gen) decode_array(value_type ast.Type) string { + styp := g.typ(value_type) + fn_name := js_dec_name(styp) + mut s := '' + if is_js_prim(styp) { + s = '$styp val = ${fn_name}((cJSON *)jsval); ' + } else { + s = ' + Option_$styp val2 = $fn_name ((cJSON *)jsval); + if(val2.state != 0) { + array_free(&res); + return *(Option_Array_$styp*)&val2; + } + $styp val = *($styp*)val2.data; +' + } + noscan := g.check_noscan(value_type) + return ' + if(root && !cJSON_IsArray(root) && !cJSON_IsNull(root)) { + return (Option_Array_$styp){.state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an array: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}}; + } + res = __new_array${noscan}(0, 0, sizeof($styp)); + const cJSON *jsval = NULL; + cJSON_ArrayForEach(jsval, root) + { + $s + array_push${noscan}((array*)&res, &val); + } +' +} + +fn (mut g Gen) encode_array(value_type ast.Type) string { + styp := g.typ(value_type) + fn_name := js_enc_name(styp) + return ' + o = cJSON_CreateArray(); + for (int i = 0; i < val.len; i++){ + cJSON_AddItemToArray(o, $fn_name ( (($styp*)val.data)[i] )); + } +' +} + +fn (mut g Gen) decode_map(key_type ast.Type, value_type ast.Type) string { + styp := g.typ(key_type) + styp_v := g.typ(value_type) + key_type_symbol := g.table.get_type_symbol(key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_type_symbol) + fn_name_v := js_dec_name(styp_v) + mut s := '' + if is_js_prim(styp_v) { + s = '$styp_v val = $fn_name_v (js_get(root, jsval->string));' + } else { + s = ' + Option_$styp_v val2 = $fn_name_v (js_get(root, jsval->string)); + if(val2.state != 0) { + map_free(&res); + return *(Option_Map_${styp}_$styp_v*)&val2; + } + $styp_v val = *($styp_v*)val2.data; +' + } + return ' + if(!cJSON_IsObject(root) && !cJSON_IsNull(root)) { + return (Option_Map_${styp}_$styp_v){ .state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an object: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}}; + } + res = new_map(sizeof($styp), sizeof($styp_v), $hash_fn, $key_eq_fn, $clone_fn, $free_fn); + cJSON *jsval = NULL; + cJSON_ArrayForEach(jsval, root) + { + $s + string key = tos2((byteptr)jsval->string); + map_set(&res, &key, &val); + } +' +} + +fn (mut g Gen) encode_map(key_type ast.Type, value_type ast.Type) string { + styp := g.typ(key_type) + styp_v := g.typ(value_type) + fn_name_v := js_enc_name(styp_v) + zero := g.type_default(value_type) + keys_tmp := g.new_tmp_var() + mut key := 'string key = ' + if key_type.is_string() { + key += '(($styp*)${keys_tmp}.data)[i];' + } else { + // key += '${styp}_str((($styp*)${keys_tmp}.data)[i]);' + verror('json: encode only maps with string keys') + } + return ' + o = cJSON_CreateObject(); + Array_$styp $keys_tmp = map_keys(&val); + for (int i = 0; i < ${keys_tmp}.len; ++i) { + $key + cJSON_AddItemToObject(o, (char*) key.str, $fn_name_v ( *($styp_v*) map_get(&val, &key, &($styp_v[]) { $zero } ) ) ); + } + array_free(&$keys_tmp); +' +} diff --git a/v_windows/v/vlib/v/gen/c/live.v b/v_windows/v/vlib/v/gen/c/live.v new file mode 100644 index 0000000..69709f9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/live.v @@ -0,0 +1,104 @@ +module c + +import v.pref +import v.util + +fn (mut g Gen) generate_hotcode_reloading_declarations() { + if g.pref.os == .windows { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex = 0;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex;') + } + g.hotcode_definitions.writeln(' +void pthread_mutex_lock(HANDLE *m) { + WaitForSingleObject(*m, INFINITE); +} +void pthread_mutex_unlock(HANDLE *m) { + ReleaseMutex(*m); +} +') + } else { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex;') + } + } +} + +fn (mut g Gen) generate_hotcode_reloader_code() { + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('') + return + } + // Hot code reloading + if g.pref.is_livemain { + mut phd := '' + mut load_code := []string{} + if g.pref.os != .windows { + for so_fn in g.hotcode_fn_names { + load_code << 'impl_live_$so_fn = dlsym(live_lib, "impl_live_$so_fn");' + } + phd = c.posix_hotcode_definitions_1 + } else { + for so_fn in g.hotcode_fn_names { + load_code << 'impl_live_$so_fn = (void *)GetProcAddress(live_lib, "impl_live_$so_fn"); ' + } + phd = c.windows_hotcode_definitions_1 + } + g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n'))) + } +} + +const ( + posix_hotcode_definitions_1 = ' +void v_bind_live_symbols(void* live_lib){ + @LOAD_FNS@ +} +' + windows_hotcode_definitions_1 = ' +void v_bind_live_symbols(void* live_lib){ + @LOAD_FNS@ +} +' +) + +fn (mut g Gen) generate_hotcode_reloading_main_caller() { + if !g.pref.is_livemain { + return + } + g.writeln('') + // We are in live code reload mode, so start the .so loader in the background + g.writeln('\t// live code initialization section:') + g.writeln('\t{') + g.writeln('\t\t// initialization of live function pointers') + for fname in g.hotcode_fn_names { + g.writeln('\t\timpl_live_$fname = 0;') + } + vexe := util.cescaped_path(pref.vexe_path()) + file := util.cescaped_path(g.pref.path) + msvc := if g.pref.ccompiler == 'msvc' { '-cc msvc' } else { '' } + so_debug_flag := if g.pref.is_debug { '-cg' } else { '' } + vopts := '$msvc $so_debug_flag -sharedlive -shared' + // + g.writeln('\t\t// start background reloading thread') + if g.pref.os == .windows { + g.writeln('\t\tlive_fn_mutex = CreateMutexA(0, 0, 0);') + } + g.writeln('\t\tv__live__LiveReloadInfo* live_info = v__live__executable__new_live_reload_info(') + g.writeln('\t\t\t\t\t tos2("$file"),') + g.writeln('\t\t\t\t\t tos2("$vexe"),') + g.writeln('\t\t\t\t\t tos2("$vopts"),') + g.writeln('\t\t\t\t\t &live_fn_mutex,') + g.writeln('\t\t\t\t\t v_bind_live_symbols') + g.writeln('\t\t);') + // g_live_info gives access to the LiveReloadInfo methods, + // to the custom user code, through calling v_live_info() + g.writeln('\t\t g_live_info = (void*)live_info;') + g.writeln('\t\tv__live__executable__start_reloader(live_info);') + g.writeln('\t}\t// end of live code initialization section') + g.writeln('') +} diff --git a/v_windows/v/vlib/v/gen/c/profile.v b/v_windows/v/vlib/v/gen/c/profile.v new file mode 100644 index 0000000..602c7b0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/profile.v @@ -0,0 +1,52 @@ +module c + +import v.ast + +pub struct ProfileCounterMeta { + fn_name string + vpc_name string + vpc_calls string +} + +fn (mut g Gen) profile_fn(fn_decl ast.FnDecl) { + if g.pref.profile_no_inline && fn_decl.attrs.contains('inline') { + g.defer_profile_code = '' + return + } + fn_name := fn_decl.name + if fn_name.starts_with('time.vpc_now') { + g.defer_profile_code = '' + } else { + measure_fn_name := if g.pref.os == .macos { 'time__vpc_now_darwin' } else { 'time__vpc_now' } + fn_profile_counter_name := 'vpc_$g.last_fn_c_name' + fn_profile_counter_name_calls := '${fn_profile_counter_name}_calls' + g.writeln('') + g.writeln('\tdouble _PROF_FN_START = ${measure_fn_name}(); $fn_profile_counter_name_calls++; // $fn_name') + g.writeln('') + g.defer_profile_code = '\t$fn_profile_counter_name += ${measure_fn_name}() - _PROF_FN_START;' + g.pcs_declarations.writeln('double $fn_profile_counter_name = 0.0; u64 $fn_profile_counter_name_calls = 0;') + g.pcs << ProfileCounterMeta{ + fn_name: g.last_fn_c_name + vpc_name: fn_profile_counter_name + vpc_calls: fn_profile_counter_name_calls + } + } +} + +pub fn (mut g Gen) gen_vprint_profile_stats() { + g.pcs_declarations.writeln('void vprint_profile_stats(){') + fstring := '"%14llu %14.3fms %14.0fns %s \\n"' + if g.pref.profile_file == '-' { + for pc_meta in g.pcs { + g.pcs_declarations.writeln('\tif ($pc_meta.vpc_calls) printf($fstring, $pc_meta.vpc_calls, $pc_meta.vpc_name/1000000.0, $pc_meta.vpc_name/$pc_meta.vpc_calls, "$pc_meta.fn_name" );') + } + } else { + g.pcs_declarations.writeln('\tFILE * fp;') + g.pcs_declarations.writeln('\tfp = fopen ("$g.pref.profile_file", "w+");') + for pc_meta in g.pcs { + g.pcs_declarations.writeln('\tif ($pc_meta.vpc_calls) fprintf(fp, $fstring, $pc_meta.vpc_calls, $pc_meta.vpc_name/1000000.0, $pc_meta.vpc_name/$pc_meta.vpc_calls, "$pc_meta.fn_name" );') + } + g.pcs_declarations.writeln('\tfclose(fp);') + } + g.pcs_declarations.writeln('}') +} diff --git a/v_windows/v/vlib/v/gen/c/sql.v b/v_windows/v/vlib/v/gen/c/sql.v new file mode 100644 index 0000000..001f68c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/sql.v @@ -0,0 +1,836 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module c + +import v.ast +import v.util + +enum SqlExprSide { + left + right +} + +enum SqlType { + sqlite3 + mysql + psql + mssql + unknown +} + +fn (mut g Gen) sql_stmt(node ast.SqlStmt) { + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' + typ := g.parse_db_type(node.db_expr) + match typ { + .sqlite3 { + fn_prefix = 'sqlite__DB' + } + .mysql { + fn_prefix = 'mysql__Connection' + } + .psql { + fn_prefix = 'pg__DB' + } + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error + } + } + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + for line in node.lines { + g.sql_stmt_line(line, conn) + } +} + +fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { + mut node := nd + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + res := g.new_tmp_var() + mut subs := false + mut dcheck := false + + if node.kind != .create { + mut fields := []ast.StructField{} + for f in node.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fields << f + } + } + node.fields = fields.clone() + unsafe { fields.free() } + } + + if node.kind == .create { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_create_table(node, expr, table_name) + subs = true + } else if node.kind == .drop { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.writeln('drop(${expr}._object, _SLIT("$table_name"));') + subs = true + } else if node.kind == .insert { + arr := g.new_tmp_var() + g.writeln('Array_orm__Primitive $arr = new_array_from_c_array(0, 0, sizeof(orm__Primitive), NULL);') + g.sql_insert(node, expr, table_name, arr, res, '', false, '') + dcheck = true + } else if node.kind == .update { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_update(node, expr, table_name) + } else if node.kind == .delete { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_delete(node, expr, table_name) + } + if !dcheck { + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + } + if subs { + for _, sub in node.sub_structs { + g.sql_stmt_line(sub, expr) + } + } +} + +fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name string) { + g.write('create(${expr}._object, _SLIT("$table_name"), new_array_from_c_array($node.fields.len, $node.fields.len, sizeof(orm__TableField),') + if node.fields.len > 0 { + g.write(' _MOV((orm__TableField[$node.fields.len]){') + for field in node.fields { + sym := g.table.get_type_symbol(field.typ) + g.write('(orm__TableField){') + g.write('.name = _SLIT("$field.name"),') + mut typ := int(field.typ) + if sym.name == 'time.Time' { + typ = -2 + } + g.write('.typ = $typ,') + g.write('.is_arr = ${sym.kind == .array}, ') + g.write('.is_time = ${int(g.table.get_type_name(field.typ) == 'time__Time')},') + g.write('.default_val = (string){.str = (byteptr) "$field.default_val", .is_lit = 1},') + g.write('.attrs = new_array_from_c_array($field.attrs.len, $field.attrs.len, sizeof(StructAttribute),') + if field.attrs.len > 0 { + g.write(' _MOV((StructAttribute[$field.attrs.len]){') + for attr in field.attrs { + g.write('(StructAttribute){') + g.write('.name = _SLIT("$attr.name"),') + g.write('.has_arg = ${int(attr.has_arg)},') + g.write('.arg = _SLIT("$attr.arg"),') + g.write('.kind = ${int(attr.kind)},') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.writeln('));') +} + +fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string) { + mut subs := []ast.SqlStmtLine{} + mut arrs := []ast.SqlStmtLine{} + mut fkeys := []string{} + mut field_names := []string{} + + for f in node.fields { + sym := g.table.get_type_symbol(f.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + subs << node.sub_structs[int(f.typ)] + } else if sym.kind == .array { + mut f_key := '' + for attr in f.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + f_key = attr.arg + } + } + if f_key == '' { + verror('An field which holds an array, needs a fkey defined') + } + fkeys << f_key + info := sym.array_info() + if info.nr_dims == 1 { + arrs << node.sub_structs[int(info.elem_type)] + field_names << f.name + } else { + verror('V ORM only supports 1 dimensional arrays') + } + } + } + + fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + + for sub in subs { + g.sql_stmt_line(sub, expr) + g.writeln('array_push(&$last_ids_arr, _MOV((orm__Primitive[]){orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object)}));') + } + + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.write('insert(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') + + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write('_MOV((string[$fields.len]){') + for f in fields { + g.write('_SLIT("${g.get_field_name(f)}"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.data = new_array_from_c_array($fields.len, $fields.len, sizeof(orm__Primitive),') + if fields.len > 0 { + g.write(' _MOV((orm__Primitive[$fields.len]){') + mut structs := 0 + for f in fields { + if f.name == fkey { + g.write('$pid, ') + continue + } + mut sym := g.table.get_type_symbol(f.typ) + mut typ := sym.cname + if sym.kind == .struct_ && typ != 'time__Time' { + g.write('(*(orm__Primitive*) array_get($last_ids_arr, $structs)),') + structs++ + continue + } + if typ == 'time__Time' { + typ = 'time' + } + g.write('orm__${typ}_to_primitive(${node.object_var_name}.$f.name),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.writeln('});') + + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + if arrs.len > 0 { + mut id_name := g.new_tmp_var() + g.writeln('orm__Primitive $id_name = orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object);') + for i, mut arr in arrs { + idx := g.new_tmp_var() + g.writeln('for (int $idx = 0; $idx < ${arr.object_var_name}.${field_names[i]}.len; $idx++) {') + last_ids := g.new_tmp_var() + res_ := g.new_tmp_var() + tmp_var := g.new_tmp_var() + ctyp := g.typ(arr.table_expr.typ) + g.writeln('$ctyp $tmp_var = (*($ctyp*)array_get(${arr.object_var_name}.${field_names[i]}, $idx));') + arr.object_var_name = tmp_var + mut fff := []ast.StructField{} + for f in arr.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fff << f + } + } + arr.fields = fff.clone() + unsafe { fff.free() } + g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_, + id_name, true, fkeys[i]) + g.writeln('}') + } + } +} + +fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) { + g.write('update(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),') + if node.updated_columns.len > 0 { + g.write(' _MOV((string[$node.updated_columns.len]){') + for field in node.updated_columns { + g.write('_SLIT("$field"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.data = new_array_from_c_array($node.update_exprs.len, $node.update_exprs.len, sizeof(orm__Primitive),') + if node.update_exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$node.update_exprs.len]){') + for e in node.update_exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } + g.write('),},') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') +} + +fn (mut g Gen) sql_delete(node ast.SqlStmtLine, expr string, table_name string) { + g.write('_v_delete(${expr}._object, _SLIT("$table_name"),') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') +} + +fn (mut g Gen) sql_expr_to_orm_primitive(expr ast.Expr) { + match expr { + ast.InfixExpr { + g.sql_write_orm_primitive(g.table.find_type_idx('orm.InfixType'), expr) + } + ast.StringLiteral { + g.sql_write_orm_primitive(ast.string_type, expr) + } + ast.IntegerLiteral { + g.sql_write_orm_primitive(ast.int_type, expr) + } + ast.BoolLiteral { + g.sql_write_orm_primitive(ast.bool_type, expr) + } + ast.Ident { + info := expr.info as ast.IdentVar + g.sql_write_orm_primitive(info.typ, expr) + } + ast.SelectorExpr { + g.sql_write_orm_primitive(expr.typ, expr) + } + else { + eprintln(expr) + verror('Unknown expr') + } + } +} + +fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) { + mut sym := g.table.get_type_symbol(t) + mut typ := sym.cname + if typ == 'orm__Primitive' { + g.expr(expr) + g.write(',') + return + } + if typ == 'time__Time' { + typ = 'time' + } + if typ == 'orm__InfixType' { + typ = 'infix' + } + g.write('orm__${typ}_to_primitive(') + if expr is ast.InfixExpr { + g.write('(orm__InfixType){') + g.write('.name = _SLIT("$expr.left"),') + mut kind := match expr.op { + .plus { + 'orm__MathOperationKind__add' + } + .minus { + 'orm__MathOperationKind__sub' + } + .div { + 'orm__MathOperationKind__div' + } + .mul { + 'orm__MathOperationKind__mul' + } + else { + '' + } + } + g.write('.operator = $kind,') + g.write('.right = ') + g.sql_expr_to_orm_primitive(expr.right) + g.write('}') + } else { + g.expr(expr) + } + g.write('),') +} + +fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut kinds []string, mut data []ast.Expr, mut is_and []bool) { + match expr { + ast.InfixExpr { + g.sql_side = .left + g.sql_where_data(expr.left, mut fields, mut kinds, mut data, mut is_and) + mut kind := match expr.op { + .ne { + 'orm__OperationKind__neq' + } + .eq { + 'orm__OperationKind__eq' + } + .lt { + 'orm__OperationKind__lt' + } + .gt { + 'orm__OperationKind__gt' + } + .ge { + 'orm__OperationKind__ge' + } + .le { + 'orm__OperationKind__le' + } + else { + '' + } + } + if kind == '' { + if expr.op == .logical_or { + is_and << false + } else if expr.op == .and { + is_and << true + } else { + kind = 'orm__OperationKind__eq' + } + } + if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr { + kinds << kind + } + g.sql_side = .right + g.sql_where_data(expr.right, mut fields, mut kinds, mut data, mut is_and) + } + ast.Ident { + if g.sql_side == .left { + fields << g.get_field_name(g.get_struct_field(expr.name)) + } else { + data << expr + } + } + ast.StringLiteral { + data << expr + } + ast.IntegerLiteral { + data << expr + } + ast.SelectorExpr { + data << expr + } + ast.BoolLiteral { + data << expr + } + else {} + } +} + +fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) { + g.write('(orm__QueryData){') + mut fields := []string{} + mut kinds := []string{} + mut data := []ast.Expr{} + mut is_and := []bool{} + g.sql_where_data(where_expr, mut fields, mut kinds, mut data, mut is_and) + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write(' _MOV((string[$fields.len]){') + for field in fields { + g.write('_SLIT("$field"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.data = new_array_from_c_array($data.len, $data.len, sizeof(orm__Primitive),') + if data.len > 0 { + g.write(' _MOV((orm__Primitive[$data.len]){') + for e in data { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } + g.write('),') + + g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),') + if kinds.len > 0 { + g.write(' _MOV((orm__OperationKind[$kinds.len]){') + for k in kinds { + g.write('$k,') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.is_and = new_array_from_c_array($is_and.len, $is_and.len, sizeof(bool),') + if is_and.len > 0 { + g.write(' _MOV((bool[$is_and.len]){') + for b in is_and { + g.write('$b, ') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),}') +} + +fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { + left := g.go_before_stmt(0) + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' + typ := g.parse_db_type(node.db_expr) + match typ { + .sqlite3 { + fn_prefix = 'sqlite__DB' + } + .mysql { + fn_prefix = 'mysql__Connection' + } + .psql { + fn_prefix = 'pg__DB' + } + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error + } + } + + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + g.sql_select(node, conn, left) +} + +fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { + mut fields := []ast.StructField{} + mut prim := '' + for f in node.fields { + mut skip := false + for attr in f.attrs { + if attr.name == 'primary' { + prim = f.name + } + if attr.name == 'skip' { + skip = true + } + } + if !skip { + fields << f + } + } + + res := g.new_tmp_var() + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + g.write('Option_Array_Array_orm__Primitive _o$res = orm__Connection_name_table[${expr}._typ]._method_select(${expr}._object, ') + g.write('(orm__SelectConfig){') + g.write('.table = _SLIT("$table_name"),') + g.write('.is_count = $node.is_count,') + g.write('.has_where = $node.has_where,') + g.write('.has_order = $node.has_order,') + if node.has_order { + g.write('.order = _SLIT("') + g.expr(node.order_expr) + g.write('"),') + if node.has_desc { + g.write('.order_type = orm__OrderType__desc,') + } else { + g.write('.order_type = orm__OrderType__asc,') + } + } + g.write('.has_limit = $node.has_limit,') + g.write('.has_offset = $node.has_offset,') + if prim != '' { + g.write('.primary = _SLIT("$prim"),') + } + select_fields := fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + g.write('.fields = new_array_from_c_array($select_fields.len, $select_fields.len, sizeof(string),') + mut types := []int{} + if select_fields.len > 0 { + g.write(' _MOV((string[$select_fields.len]){') + for field in select_fields { + g.write('_SLIT("${g.get_field_name(field)}"),') + sym := g.table.get_type_symbol(field.typ) + if sym.name == 'time.Time' { + types << -2 + continue + } + if sym.kind == .struct_ { + types << int(ast.int_type) + continue + } + types << int(field.typ) + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.types = new_array_from_c_array($types.len, $types.len, sizeof(int),') + if types.len > 0 { + g.write(' _MOV((int[$types.len]){') + for typ in types { + g.write('$typ,') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),},') + + mut exprs := []ast.Expr{} + if node.has_limit { + exprs << node.limit_expr + } + if node.has_offset { + exprs << node.offset_expr + } + g.write('(orm__QueryData) {') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),') + if exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$exprs.len]){') + for e in exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')},') + + if node.has_where { + g.sql_gen_where_data(node.where_expr) + } else { + g.write('(orm__QueryData) {}') + } + g.writeln(');') + g.writeln('if (_o${res}.state != 0 && _o${res}.err._typ != _IError_None___index) { _v_panic(IError_str(_o${res}.err)); }') + g.writeln('Array_Array_orm__Primitive $res = (*(Array_Array_orm__Primitive*)_o${res}.data);') + + if node.is_count { + g.writeln('$left *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get($res, 0)), 0))._int);') + } else { + tmp := g.new_tmp_var() + styp := g.typ(node.typ) + idx := g.new_tmp_var() + g.writeln('int $idx = 0;') + mut typ_str := '' + if node.is_array { + info := g.table.get_type_symbol(node.typ).array_info() + typ_str = g.typ(info.elem_type) + g.writeln('$styp ${tmp}_array = __new_array(0, ${res}.len, sizeof($typ_str));') + g.writeln('for (; $idx < ${res}.len; $idx++) {') + g.write('\t$typ_str $tmp = ($typ_str) {') + inf := g.table.get_type_symbol(info.elem_type).struct_info() + for i, field in inf.fields { + g.zero_struct_field(field) + if i != inf.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } else { + g.write('$styp $tmp = ($styp){') + info := g.table.get_type_symbol(node.typ).struct_info() + for i, field in info.fields { + g.zero_struct_field(field) + if i != info.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } + + g.writeln('if (${res}.len > 0) {') + for i, field in fields { + sel := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get($res, $idx)), $i))' + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + mut sub := node.sub_structs[int(field.typ)] + mut where_expr := sub.where_expr as ast.InfixExpr + mut ident := where_expr.right as ast.Ident + name := sel + s := g.table.find_type_idx('orm.Primitive') + if s != 0 { + if ident.info is ast.IdentVar { + mut info := ident.info as ast.IdentVar + info.typ = s + ident.info = info + } + } + ident.name = name + where_expr.right = ident + sub.where_expr = where_expr + + g.sql_select(sub, expr, '${tmp}.$field.name = ') + } else if sym.kind == .array { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + fkey = attr.arg + } + } + if fkey == '' { + verror('An field which holds an array, needs a fkey defined') + } + info := sym.array_info() + arr_typ := info.elem_type + sub := node.sub_structs[int(arr_typ)] + mut where_expr := sub.where_expr as ast.InfixExpr + mut l := where_expr.left as ast.Ident + mut r := where_expr.right as ast.Ident + l.name = fkey + r.name = tmp + where_expr.left = l + where_expr.right = ast.SelectorExpr{ + pos: r.pos + field_name: prim + is_mut: false + expr: r + expr_type: (r.info as ast.IdentVar).typ + typ: ast.int_type + scope: 0 + } + mut arr := ast.SqlExpr{ + typ: field.typ + is_count: sub.is_count + db_expr: sub.db_expr + has_where: sub.has_where + has_offset: sub.has_offset + offset_expr: sub.offset_expr + has_order: sub.has_order + order_expr: sub.order_expr + has_desc: sub.has_desc + is_array: true + pos: sub.pos + has_limit: sub.has_limit + limit_expr: sub.limit_expr + table_expr: sub.table_expr + fields: sub.fields + where_expr: where_expr + } + + g.sql_select(arr, expr, '${tmp}.$field.name = ') + } else { + mut typ := sym.cname + g.writeln('${tmp}.$field.name = *(${sel}._$typ);') + } + } + g.writeln('}') + + if node.is_array { + g.writeln('array_push(&${tmp}_array, _MOV(($typ_str[]){ $tmp }));') + g.writeln('}') + } + + g.write('$left $tmp') + if node.is_array { + g.write('_array') + } + g.writeln(';') + } +} + +fn (mut g Gen) parse_db_type(expr ast.Expr) SqlType { + match expr { + ast.Ident { + if expr.info is ast.IdentVar { + return g.parse_db_from_type_string(g.table.get_type_name(expr.info.typ)) + } + } + ast.SelectorExpr { + return g.parse_db_from_type_string(g.table.get_type_name(expr.typ)) + } + else { + return .unknown + } + } + return .unknown +} + +fn (mut g Gen) parse_db_from_type_string(name string) SqlType { + match name { + 'sqlite.DB' { + return .sqlite3 + } + 'mysql.Connection' { + return .mysql + } + 'pg.DB' { + return .psql + } + 'mssql.Connection' { + return .mssql + } + else { + return .unknown + } + } +} + +fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string { + info := g.table.get_type_symbol(table_expr.typ).struct_info() + mut tablename := util.strip_mod_name(g.table.get_type_symbol(table_expr.typ).name) + for attr in info.attrs { + if attr.kind == .string && attr.name == 'table' && attr.arg != '' { + tablename = attr.arg + break + } + } + return tablename +} + +fn (mut g Gen) get_struct_field(name string) ast.StructField { + info := g.table.get_type_symbol(g.table.type_idxs[g.sql_table_name]).struct_info() + mut f := ast.StructField{} + for field in info.fields { + if field.name == name { + f = field + } + } + return f +} + +fn (mut g Gen) get_field_name(field ast.StructField) string { + mut name := field.name + for attr in field.attrs { + if attr.kind == .string && attr.name == 'sql' && attr.arg != '' { + name = attr.arg + break + } + } + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + name = '${name}_id' + } + return name +} diff --git a/v_windows/v/vlib/v/gen/c/str.v b/v_windows/v/vlib/v/gen/c/str.v new file mode 100644 index 0000000..788da2c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/str.v @@ -0,0 +1,145 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module c + +import v.ast +import v.util + +fn (mut g Gen) string_literal(node ast.StringLiteral) { + escaped_val := util.smart_quote(node.val, node.is_raw) + if node.language == .c { + g.write('"$escaped_val"') + } else { + g.write('_SLIT("$escaped_val")') + } +} + +// optimize string interpolation in string builders: +// `sb.writeln('a=$a')` => +// `sb.writeln('a='); sb.writeln(a.str())` +fn (mut g Gen) string_inter_literal_sb_optimized(call_expr ast.CallExpr) { + node := call_expr.args[0].expr as ast.StringInterLiteral + // sb_name := g.cur_call_expr.left + // g.go_before_stmt(0) + g.writeln('// sb inter opt') + is_nl := call_expr.name == 'writeln' + // println('optimize sb $call_expr.name') + for i, val in node.vals { + escaped_val := util.smart_quote(val, false) + // if val == '' { + // break + // continue + // } + g.write('strings__Builder_write_string(&') + g.expr(call_expr.left) + g.write(', _SLIT("') + g.write(escaped_val) + g.writeln('"));') + // + if i >= node.exprs.len { + break + } + // if node.expr_types.len <= i || node.exprs.len <= i { + // continue + // } + if is_nl && i == node.exprs.len - 1 { + g.write('strings__Builder_writeln(&') + } else { + g.write('strings__Builder_write_string(&') + } + g.expr(call_expr.left) + g.write(', ') + typ := node.expr_types[i] + g.write(g.typ(typ)) + g.write('_str(') + sym := g.table.get_type_symbol(typ) + if sym.kind != .function { + g.expr(node.exprs[i]) + } + g.writeln('));') + } + g.writeln('') + // println(node.vals) + return +} + +fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + is_shared := etype.has_flag(.shared_f) + mut typ := etype + if is_shared { + typ = typ.clear_flag(.shared_f).set_nr_muls(0) + } + mut sym := g.table.get_type_symbol(typ) + // when type is alias, print the aliased value + if mut sym.info is ast.Alias { + parent_sym := g.table.get_type_symbol(sym.info.parent_type) + if parent_sym.has_method('str') { + typ = sym.info.parent_type + sym = parent_sym + } + } + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + if typ.has_flag(.variadic) { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + g.expr(expr) + g.write(')') + } else if typ == ast.string_type { + g.expr(expr) + } else if typ == ast.bool_type { + g.expr(expr) + g.write(' ? _SLIT("true") : _SLIT("false")') + } else if sym.kind == .none_ { + g.write('_SLIT("")') + } else if sym.kind == .enum_ { + if expr !is ast.EnumVal { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + g.enum_expr(expr) + g.write(')') + } else { + g.write('_SLIT("') + g.enum_expr(expr) + g.write('")') + } + } else if sym_has_str_method + || sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return, .sum_type, .interface_] { + is_ptr := typ.is_ptr() + is_var_mut := expr.is_auto_deref_var() + str_fn_name := g.gen_str_for_type(typ) + if is_ptr && !is_var_mut { + g.write('str_intp(1, _MOV((StrIntpData[]){{_SLIT("&"), $si_s_code ,{.d_s=') + } + g.write('${str_fn_name}(') + if str_method_expects_ptr && !is_ptr { + g.write('&') + } else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut { + g.write('*') + } + if expr is ast.ArrayInit { + if expr.is_fixed { + s := g.typ(expr.typ) + g.write('($s)') + } + } + g.expr_with_cast(expr, typ, typ) + if is_shared { + g.write('->val') + } + g.write(')') + if is_ptr && !is_var_mut { + g.write('}}}))') + // g.write(')') + } + } else { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + if expr.is_auto_deref_var() { + g.write('*') + } + if sym.kind != .function { + g.expr_with_cast(expr, typ, typ) + } + g.write(')') + } +} diff --git a/v_windows/v/vlib/v/gen/c/str_intp.v b/v_windows/v/vlib/v/gen/c/str_intp.v new file mode 100644 index 0000000..7ace443 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/str_intp.v @@ -0,0 +1,206 @@ +/* +str_intp.v + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains string interpolation V functions +*/ +module c + +import v.ast +import v.util + +fn (mut g Gen) str_format(node ast.StringInterLiteral, i int) (u64, string) { + mut base := 0 // numeric base + mut upper_case := false // set upercase for the result string + mut typ := g.unwrap_generic(node.expr_types[i]) + sym := g.table.get_type_symbol(typ) + if sym.kind == .alias { + typ = (sym.info as ast.Alias).parent_type + } + mut remove_tail_zeros := false + fspec := node.fmts[i] + mut fmt_type := StrIntpType{} + + // upper cases + if (fspec - `A`) <= (`Z` - `A`) { + upper_case = true + } + + if fspec in [`s`, `S`] { + /* + if node.fwidths[i] == 0 { + fmt_type = .si_s + } else { + fmt_type = .si_s + } + */ + fmt_type = .si_s + } else if typ.is_float() { + if fspec in [`g`, `G`] { + match typ { + ast.f32_type { fmt_type = .si_g32 } + // ast.f64_type { fmt_type = .si_g64 } + else { fmt_type = .si_g64 } + } + remove_tail_zeros = true + } else if fspec in [`e`, `E`] { + match typ { + ast.f32_type { fmt_type = .si_e32 } + // ast.f64_type { fmt_type = .si_e64 } + else { fmt_type = .si_e64 } + } + } else if fspec in [`f`, `F`] { + match typ { + ast.f32_type { fmt_type = .si_f32 } + // ast.f64_type { fmt_type = .si_f64 } + else { fmt_type = .si_f64 } + } + } + } else if typ.is_pointer() { + if fspec in [`x`, `X`] { + base = 16 - 2 // our base start from 2 + } + if fspec in [`p`, `x`, `X`] { + fmt_type = .si_p + } else { + fmt_type = .si_vp + } + } else if typ.is_int() { + if fspec in [`x`, `X`] { + base = 16 - 2 // our base start from 2 + } + // if fspec in [`o`] { + if fspec == `o` { + base = 8 - 2 // our base start from 2 + } + if fspec == `c` { + fmt_type = .si_c + } else { + match typ { + ast.i8_type { fmt_type = .si_i8 } + ast.byte_type { fmt_type = .si_u8 } + ast.i16_type { fmt_type = .si_i16 } + ast.u16_type { fmt_type = .si_u16 } + ast.i64_type { fmt_type = .si_i64 } + ast.u64_type { fmt_type = .si_u64 } + ast.u32_type { fmt_type = .si_u32 } + else { fmt_type = .si_i32 } + } + } + } else { + // TODO: better check this case + fmt_type = .si_p + } + + /* + // pad filling 64bit format + mut pad_ch := u8(0) + if node.fills[i] { + pad_ch = u8(`0`) + } + res := get_str_intp_u64_format(fmt_type, node.fwidths[i], node.precisions[i], remove_tail_zeros, node.pluss[i], pad_ch, base, upper_case) + */ + + // pad filling 32bit format + mut pad_ch := 0 + if node.fills[i] { + pad_ch = 1 + } + res := get_str_intp_u32_format(fmt_type, node.fwidths[i], node.precisions[i], remove_tail_zeros, + node.pluss[i], byte(pad_ch), base, upper_case) + // + return res, fmt_type.str() +} + +fn (mut g Gen) str_val(node ast.StringInterLiteral, i int) { + expr := node.exprs[i] + + typ := g.unwrap_generic(node.expr_types[i]) + if typ == ast.string_type { + if g.inside_vweb_tmpl { + g.write('vweb__filter(') + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(')') + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } + } else if node.fmts[i] == `s` || typ.has_flag(.variadic) { + g.gen_expr_to_string(expr, typ) + } else if typ.is_number() || typ.is_pointer() || node.fmts[i] == `d` { + if typ.is_signed() && node.fmts[i] in [`x`, `X`, `o`] { + // convert to unsigned first befors C's integer propagation strikes + if typ == ast.i8_type { + g.write('(byte)(') + } else if typ == ast.i16_type { + g.write('(u16)(') + } else if typ == ast.int_type { + g.write('(u32)(') + } else { + g.write('(u64)(') + } + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(')') + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } +} + +fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { + // fn (mut g Gen) str_int2(node ast.StringInterLiteral) { + g.write(' str_intp($node.vals.len, ') + g.write('_MOV((StrIntpData[]){') + for i, val in node.vals { + escaped_val := util.smart_quote(val, false) + + if escaped_val.len > 0 { + g.write('{_SLIT("$escaped_val"), ') + } else { + g.write('{_SLIT0, ') + } + + if i >= node.exprs.len { + // last part of the string + g.write('0, { .d_c = 0 }}') + break + } + + ft_u64, ft_str := g.str_format(node, i) + g.write('0x$ft_u64.hex(), {.d_$ft_str = ') + + // for pointers we need a void* cast + if unsafe { ft_str.str[0] } == `p` { + g.write('(void*)(') + g.str_val(node, i) + g.write(')') + } else { + g.str_val(node, i) + } + + g.write('}}') + if i < (node.vals.len - 1) { + g.write(', ') + } + } + g.write('}))') +} diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have b/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have new file mode 100644 index 0000000..300bd6a --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have @@ -0,0 +1 @@ +int zzz = 579 \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.out b/v_windows/v/vlib/v/gen/c/testdata/addition.out new file mode 100644 index 0000000..72b67e0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.out @@ -0,0 +1 @@ +579 \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.vv b/v_windows/v/vlib/v/gen/c/testdata/addition.vv new file mode 100644 index 0000000..ff95896 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.vv @@ -0,0 +1,4 @@ +fn main() { + zzz := 123 + 456 + println(zzz) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have b/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have new file mode 100644 index 0000000..11a2cb8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have @@ -0,0 +1,3 @@ +VV_LOCAL_SYMBOL int main__a_const_accepting_fn(int* x, const int* const_x); +VV_LOCAL_SYMBOL int main__a_const_accepting_fn(int* x, const int* const_x) { +main__a_const_accepting_fn(&a, &b) diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.out b/v_windows/v/vlib/v/gen/c/testdata/const_references.out new file mode 100644 index 0000000..01e79c3 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.out @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.vv b/v_windows/v/vlib/v/gen/c/testdata/const_references.vv new file mode 100644 index 0000000..ae94cc7 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.vv @@ -0,0 +1,11 @@ +fn a_const_accepting_fn(x &int, const_x &int) int { + return *x + *const_x +} + +fn main() { + a := 1 + b := 2 + println(a) + println(b) + println(a_const_accepting_fn(&a, &b)) +} diff --git a/v_windows/v/vlib/v/gen/c/utils.v b/v_windows/v/vlib/v/gen/c/utils.v new file mode 100644 index 0000000..a4768c8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/utils.v @@ -0,0 +1,49 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast + +fn (mut g Gen) unwrap_generic(typ ast.Type) ast.Type { + if typ.has_flag(.generic) { + if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names, + g.table.cur_concrete_types) + { + return t_typ + } + } + return typ +} + +struct Type { + // typ is the original type + typ ast.Type [required] + sym &ast.TypeSymbol [required] + // unaliased is `typ` once aliased have been resolved + // it may not contain informations such as flags and nr_muls + unaliased ast.Type [required] + unaliased_sym &ast.TypeSymbol [required] +} + +// unwrap returns the following variants of a type: +// * generics unwrapped +// * alias unwrapped +fn (mut g Gen) unwrap(typ ast.Type) Type { + no_generic := g.unwrap_generic(typ) + no_generic_sym := g.table.get_type_symbol(no_generic) + if no_generic_sym.kind != .alias { + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic + unaliased_sym: no_generic_sym + } + } + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic_sym.parent_idx + unaliased_sym: g.table.get_type_symbol(no_generic_sym.parent_idx) + } +} diff --git a/v_windows/v/vlib/v/gen/js/array.v b/v_windows/v/vlib/v/gen/js/array.v new file mode 100644 index 0000000..c9d5ef6 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/array.v @@ -0,0 +1,89 @@ +module js + +import v.ast + +const ( + special_array_methods = [ + 'sort', + 'insert', + 'prepend', + ] +) + +fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { + node := it + match node.name { + 'insert' { + arg2_sym := g.table.get_type_symbol(node.args[1].typ) + is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type + if is_arg2_array { + g.write('insert_many(') + } else { + g.write('insert(') + } + + g.expr(node.args[0].expr) + g.write(',') + if is_arg2_array { + g.expr(node.args[1].expr) + g.write('.arr,') + g.expr(node.args[1].expr) + g.write('.len') + } else { + g.expr(node.args[1].expr) + } + g.write(')') + return + } + 'prepend' { + arg_sym := g.table.get_type_symbol(node.args[0].typ) + is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type + if is_arg_array { + g.write('prepend_many(') + } else { + g.write('prepend(') + } + + if is_arg_array { + g.expr(node.args[0].expr) + g.write('.arr, ') + g.expr(node.args[0].expr) + g.write('.len') + } else { + g.expr(node.args[0].expr) + } + g.write(')') + return + } + 'sort' { + rec_sym := g.table.get_type_symbol(node.receiver_type) + if rec_sym.kind != .array { + println(node.name) + println(g.typ(node.receiver_type)) + // println(rec_sym.kind) + verror('.sort() is an array method') + } + + // `users.sort(a.age > b.age)` + + if node.args.len == 0 { + g.write('sort()') + return + } else { + infix_expr := node.args[0].expr as ast.InfixExpr + left_name := infix_expr.left.str() + is_reverse := (left_name.starts_with('a') && infix_expr.op == .gt) + || (left_name.starts_with('b') && infix_expr.op == .lt) + if is_reverse { + g.write('arr.sort(function (b,a) {') + } else { + g.write('arr.sort(function (a,b) {') + } + g.write('return ') + g.write('\$sortComparator(a,b)') + g.write('})') + } + } + else {} + } +} diff --git a/v_windows/v/vlib/v/gen/js/builtin_types.v b/v_windows/v/vlib/v/gen/js/builtin_types.v new file mode 100644 index 0000000..916490a --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/builtin_types.v @@ -0,0 +1,419 @@ +module js + +import v.ast + +fn (mut g JsGen) to_js_typ_def_val(s string) string { + mut dval := '' + match s { + 'JS.Number' { dval = '0' } + 'JS.String' { dval = '""' } + 'JS.Boolean' { dval = 'false' } + 'JS.Array', 'JS.Map' { dval = '' } + else { dval = '{}' } + } + return dval +} + +fn (mut g JsGen) to_js_typ_val(t ast.Type) string { + sym := g.table.get_type_symbol(t) + mut styp := '' + mut prefix := if g.file.mod.name == 'builtin' { 'new ' } else { 'new builtin.' } + match sym.kind { + .i8, .i16, .int, .i64, .byte, .u8, .u16, .u32, .u64, .f32, .f64, .int_literal, + .float_literal, .size_t { + styp = '$prefix${g.sym_to_js_typ(sym)}(0)' + } + .bool { + styp = '$prefix${g.sym_to_js_typ(sym)}(false)' + } + .string { + styp = '$prefix${g.sym_to_js_typ(sym)}("")' + } + .map { + styp = 'new map(new Map())' + } + .array { + styp = '$prefix${g.sym_to_js_typ(sym)}()' + } + .struct_ { + styp = 'new ${g.js_name(sym.name)}(${g.to_js_typ_def_val(sym.name)})' + } + .voidptr { + styp = 'null' + } + else { + // TODO + styp = 'undefined' + } + } + return styp +} + +fn (mut g JsGen) sym_to_js_typ(sym ast.TypeSymbol) string { + mut styp := '' + match sym.kind { + .i8 { + styp = 'i8' + } + .i16 { + styp = 'i16' + } + .int { + styp = 'int' + } + .i64 { + styp = 'i64' + } + .byte { + styp = 'byte' + } + .u16 { + styp = 'u16' + } + .u32 { + styp = 'u32' + } + .u64 { + styp = 'u64' + } + .f32 { + styp = 'f32' + } + .f64 { + styp = 'f64' + } + .int_literal { + styp = 'int_literal' + } + .float_literal { + styp = 'float_literal' + } + .size_t { + styp = 'size_t' + } + .bool { + styp = 'bool' + } + .string { + styp = 'string' + } + .map { + styp = 'map' + } + .array { + styp = 'array' + } + .voidptr { + styp = 'any' + } + else { + // TODO + styp = 'undefined' + } + } + return styp +} + +// V type to JS type +pub fn (mut g JsGen) typ(t ast.Type) string { + sym := g.table.get_type_symbol(t) + mut styp := '' + match sym.kind { + .placeholder { + // This should never happen: means checker bug + styp = 'any' + } + .void { + styp = 'void' + } + .voidptr { + styp = 'any' + } + .byteptr, .charptr { + styp = '${g.sym_to_js_typ(sym)}' + } + .i8, .i16, .int, .i64, .byte, .u8, .u16, .u32, .u64, .f32, .f64, .int_literal, + .float_literal, .size_t { + styp = '${g.sym_to_js_typ(sym)}' + } + .bool { + styp = '${g.sym_to_js_typ(sym)}' + } + .none_ { + styp = 'undefined' + } + .string, .char { + styp = '${g.sym_to_js_typ(sym)}' + } + // 'array_array_int' => 'number[][]' + .array { + info := sym.info as ast.Array + styp = '${g.sym_to_js_typ(sym)}(${g.typ(info.elem_type)})' + } + .array_fixed { + info := sym.info as ast.ArrayFixed + styp = '${g.sym_to_js_typ(sym)}(${g.typ(info.elem_type)})' + } + .chan { + styp = 'chan' + } + // 'map[string]int' => 'Map' + .map { + info := sym.info as ast.Map + key := g.typ(info.key_type) + val := g.typ(info.value_type) + styp = 'Map<$key, $val>' + } + .any { + styp = 'any' + } + // ns.Foo => alias["Foo"]["prototype"] + .struct_ { + styp = g.struct_typ(sym.name) + } + .generic_inst {} + // 'multi_return_int_int' => '[number, number]' + .multi_return { + info := sym.info as ast.MultiReturn + types := info.types.map(g.typ(it)) + joined := types.join(', ') + styp = '[$joined]' + } + .sum_type { + // TODO: Implement sumtypes + styp = 'union_sym_type' + } + .alias { + fsym := g.table.get_final_type_symbol(t) + name := g.js_name(fsym.name) + styp += '$name' + } + .enum_ { + // NB: We could declare them as TypeScript enums but TS doesn't like + // our namespacing so these break if declared in a different module. + // Until this is fixed, We need to use the type of an enum's members + // rather than the enum itself, and this can only be 'number' for now + styp = 'number' + } + // 'anon_fn_7_7_1' => '(a number, b number) => void' + .function { + info := sym.info as ast.FnType + styp = g.fn_typ(info.func.params, info.func.return_type) + } + .interface_ { + styp = g.js_name(sym.name) + } + .rune { + styp = 'any' + } + .aggregate { + panic('TODO: unhandled aggregate in JS') + } + .thread { + panic('TODO: unhandled thread in JS') + } + } + /* + else { + println('jsgen.typ: Unhandled type $t') + styp = sym.name + } + */ + if styp.starts_with('JS.') { + return styp[3..] + } + return styp +} + +fn (mut g JsGen) fn_typ(args []ast.Param, return_type ast.Type) string { + mut res := '(' + for i, arg in args { + res += '$arg.name: ${g.typ(arg.typ)}' + if i < args.len - 1 { + res += ', ' + } + } + return res + ') => ' + g.typ(return_type) +} + +fn (mut g JsGen) struct_typ(s string) string { + ns := get_ns(s) + if ns == 'JS' { + return s[3..] + } + mut name := if ns == g.ns.name { s.split('.').last() } else { g.get_alias(s) } + mut styp := '' + for i, v in name.split('.') { + if i == 0 { + styp = v + } else { + styp += '["$v"]' + } + } + if ns in ['', g.ns.name] { + return styp + } + return styp + '["prototype"]' +} + +struct BuiltinPrototypeConfig { + typ_name string + val_name string = 'val' + default_value string + constructor string = 'this.val = val' + value_of string = 'this.val' + to_string string = 'this.val.toString()' + eq string = 'this.val === other.val' + to_jsval string = 'this' + extras string + has_strfn bool +} + +fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeConfig) { + g.writeln('function ${c.typ_name}($c.val_name = $c.default_value) { $c.constructor }') + g.writeln('${c.typ_name}.prototype = {') + g.inc_indent() + g.writeln('$c.val_name: $c.default_value,') + if c.extras.len > 0 { + g.writeln('$c.extras,') + } + for method in g.method_fn_decls[c.typ_name] { + g.inside_def_typ_decl = true + g.gen_method_decl(method, .struct_method) + g.inside_def_typ_decl = false + g.writeln(',') + } + g.writeln('valueOf() { return $c.value_of },') + g.writeln('toString() { return $c.to_string },') + g.writeln('eq(other) { return $c.eq },') + g.writeln('\$toJS() { return $c.to_jsval }, ') + if c.has_strfn { + g.writeln('str() { return new string(this.toString()) }') + } + g.dec_indent() + g.writeln('};\n') +} + +// generate builtin type definitions, used for casting and methods. +fn (mut g JsGen) gen_builtin_type_defs() { + g.inc_indent() + for typ_name in v_types { + // TODO: JsDoc + match typ_name { + 'i8', 'i16', 'int', 'u16', 'u32', 'int_literal', 'size_t' { + // TODO: Bounds checking + g.gen_builtin_prototype( + typ_name: typ_name + default_value: 'new Number(0)' + constructor: 'this.val = Number(val)' + value_of: 'Number(this.val)' + to_string: 'this.valueOf().toString()' + eq: 'this.valueOf() === other.valueOf()' + to_jsval: '+this' + ) + } + // u64 and i64 are so big that their values do not fit into JS number so we use BigInt. + 'u64' { + g.gen_builtin_prototype( + typ_name: typ_name + default_value: 'BigInt(0)' + constructor: 'this.val = BigInt.asUintN(64,BigInt(val))' + value_of: 'this.val' + to_string: 'this.val.toString()' + eq: 'this.valueOf() === other.valueOf()' + to_jsval: 'this.val' + ) + } + 'i64' { + g.gen_builtin_prototype( + typ_name: typ_name + default_value: 'BigInt(0)' + constructor: 'this.val = BigInt.asIntN(64,BigInt(val))' + value_of: 'this.val' + to_string: 'this.val.toString()' + eq: 'this.valueOf() === other.valueOf()' + to_jsval: 'this.val' + ) + } + 'byte' { + g.gen_builtin_prototype( + typ_name: typ_name + default_value: 'new Number(0)' + constructor: 'if (typeof(val) == "string") { this.val = val.charCodeAt() } else if (val instanceof string) { this.val = val.str.charCodeAt(); } else { this.val = val | 0 }' + value_of: 'this.val | 0' + to_string: 'new string(this.val + "")' + eq: 'this.valueOf() === other.valueOf()' + to_jsval: '+this' + ) + } + 'f32', 'f64', 'float_literal' { + g.gen_builtin_prototype( + typ_name: typ_name + constructor: 'this.val = Number(val)' + default_value: 'new Number(0)' + to_jsval: '+this' + ) + } + 'bool' { + g.gen_builtin_prototype( + constructor: 'this.val = +val !== 0' + typ_name: typ_name + default_value: 'new Boolean(false)' + to_jsval: '+this != 0' + eq: 'this.val === other.valueOf()' + ) + } + 'string' { + g.gen_builtin_prototype( + typ_name: typ_name + val_name: 'str' + default_value: 'new String("")' + constructor: 'this.str = str.toString(); this.len = this.str.length' + value_of: 'this.str' + to_string: 'this.str' + eq: 'this.str === other.str' + has_strfn: false + to_jsval: 'this.str' + ) + } + 'map' { + g.gen_builtin_prototype( + typ_name: typ_name + val_name: 'map' + default_value: 'new map(new Map())' + constructor: 'this.map = map' + value_of: 'this' + to_string: 'this.map.toString()' + eq: 'vEq(this, other)' + to_jsval: 'this.map' + ) + } + 'array' { + g.gen_builtin_prototype( + typ_name: typ_name + val_name: 'arr' + default_value: 'new Array()' + constructor: 'this.arr = arr' + value_of: 'this' + to_string: 'JSON.stringify(this.arr.map(it => it.valueOf()))' + eq: 'vEq(this, other)' + to_jsval: 'this.arr' + ) + } + 'any' { + g.gen_builtin_prototype( + typ_name: typ_name + val_name: 'any' + default_value: 'null' + constructor: 'this.val = any' + value_of: 'this.val' + to_string: '"&" + this.val' + eq: 'this == other' // compare by ptr + to_jsval: 'this.val.\$toJS()' + ) + } + else {} + } + } + g.dec_indent() +} diff --git a/v_windows/v/vlib/v/gen/js/comptime.v b/v_windows/v/vlib/v/gen/js/comptime.v new file mode 100644 index 0000000..d4c55ee --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/comptime.v @@ -0,0 +1,311 @@ +module js + +import v.ast +import v.pref + +fn (mut g JsGen) comp_if(node ast.IfExpr) { + if !node.is_expr && !node.has_else && node.branches.len == 1 { + if node.branches[0].stmts.len == 0 { + // empty ifdef; result of target OS != conditional => skip + return + } + } + + for i, branch in node.branches { + if i == node.branches.len - 1 && node.has_else { + g.writeln('else') + } else { + if i == 0 { + g.write('if (') + } else { + g.write('else if (') + } + g.comp_if_cond(branch.cond, branch.pkg_exist) + g.writeln(')') + } + + if node.is_expr { + print('$branch.stmts') + len := branch.stmts.len + if len > 0 { + last := branch.stmts[len - 1] as ast.ExprStmt + if len > 1 { + tmp := g.new_tmp_var() + g.inc_indent() + g.writeln('let $tmp;') + g.writeln('{') + g.stmts(branch.stmts[0..len - 1]) + g.write('\t$tmp = ') + g.stmt(last) + g.writeln('}') + g.dec_indent() + g.writeln('$tmp;') + } else { + g.stmt(last) + } + } + } else { + g.writeln('{') + g.stmts(branch.stmts) + g.writeln('}') + } + } +} + +/* +// returning `false` means the statements inside the $if can be skipped +*/ +// returns the value of the bool comptime expression +fn (mut g JsGen) comp_if_cond(cond ast.Expr, pkg_exist bool) bool { + match cond { + ast.BoolLiteral { + g.expr(cond) + return true + } + ast.ParExpr { + g.write('(') + is_cond_true := g.comp_if_cond(cond.expr, pkg_exist) + g.write(')') + return is_cond_true + } + ast.PrefixExpr { + g.write(cond.op.str()) + return g.comp_if_cond(cond.right, pkg_exist) + } + ast.PostfixExpr { + ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) or { + verror(err.msg) + return false + } + g.write('$ifdef') + return true + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + l := g.comp_if_cond(cond.left, pkg_exist) + g.write(' $cond.op ') + r := g.comp_if_cond(cond.right, pkg_exist) + return if cond.op == .and { l && r } else { l || r } + } + .key_is, .not_is { + left := cond.left + mut name := '' + mut exp_type := ast.Type(0) + got_type := (cond.right as ast.TypeNode).typ + // Handle `$if x is Interface {` + // mut matches_interface := 'false' + if left is ast.TypeNode && cond.right is ast.TypeNode + && g.table.get_type_symbol(got_type).kind == .interface_ { + // `$if Foo is Interface {` + interface_sym := g.table.get_type_symbol(got_type) + if interface_sym.info is ast.Interface { + // q := g.table.get_type_symbol(interface_sym.info.types[0]) + checked_type := g.unwrap_generic(left.typ) + // TODO PERF this check is run twice (also in the checker) + // store the result in a field + is_true := g.table.does_type_implement_interface(checked_type, + got_type) + // true // exp_type in interface_sym.info.types + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else if cond.op == .not_is { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + // matches_interface = '/*iface:$got_type $exp_type*/ true' + //} + } + } else if left is ast.SelectorExpr { + name = '${left.expr}.$left.field_name' + exp_type = g.comptime_var_type_map[name] + } else if left is ast.TypeNode { + name = left.str() + // this is only allowed for generics currently, otherwise blocked by checker + exp_type = g.unwrap_generic(left.typ) + } + + if cond.op == .key_is { + g.write('$exp_type == $got_type') + return exp_type == got_type + } else { + g.write('$exp_type != $got_type') + return exp_type != got_type + } + } + .eq, .ne { + // TODO Implement `$if method.args.len == 1` + g.write('1') + return true + } + else { + return true + } + } + } + ast.Ident { + ifdef := g.comp_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker + g.write('$ifdef') + return true + } + ast.ComptimeCall { + g.write('$pkg_exist') + return true + } + else { + // should be unreachable, but just in case + g.write('1') + return true + } + } +} + +fn (mut g JsGen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?string { + match name { + // platforms/os-es: + 'windows' { + return '(\$process.platform == "windows")' + } + 'ios' { + return '(\$process.platform == "darwin")' + } + 'macos' { + return '(\$process.platform == "darwin")' + } + 'mach' { + return '(\$process.platform == "darwin")' + } + 'darwin' { + return '(\$process.platform == "darwin")' + } + 'linux' { + return '(\$process.platform == "linux")' + } + 'freebsd' { + return '(\$process.platform == "freebsd")' + } + 'openbsd' { + return '(\$process.platform == "openbsd")' + } + 'bsd' { + return '(\$process.platform == "freebsd" || (\$process.platform == "openbsd"))' + } + 'android' { + return '(\$process.platform == "android")' + } + 'solaris' { + return '(\$process.platform == "sunos")' + } + 'js_node' { + if g.pref.backend == .js_node { + return 'true' + } else { + return 'false' + } + } + 'js_freestanding' { + if g.pref.backend == .js_freestanding { + return 'true' + } else { + return 'false' + } + } + 'js_browser' { + if g.pref.backend == .js_browser { + return 'true' + } else { + return 'false' + } + } + // + 'js' { + return 'true' + } + // compilers: + 'gcc' { + return 'false' + } + 'tinyc' { + return 'false' + } + 'clang' { + return 'false' + } + 'mingw' { + return 'false' + } + 'msvc' { + return 'false' + } + 'cplusplus' { + return 'false' + } + // other: + 'threads' { + return 'false' + } + 'gcboehm' { + return 'false' + } + // todo(playX): these should return true or false depending on CLI options + 'debug' { + return 'false' + } + 'prod' { + return 'false' + } + 'test' { + return 'false' + } + 'glibc' { + return 'false' + } + 'prealloc' { + return 'false' + } + 'no_bounds_checking' { + return 'CUSTOM_DEFINE_no_bounds_checking' + } + 'freestanding' { + return '_VFREESTANDING' + } + // architectures: + 'amd64' { + return '(\$process.arch == "x64")' + } + 'aarch64', 'arm64' { + return '(\$process.arch == "arm64)' + } + // bitness: + 'x64' { + return '(\$process.arch == "x64")' + } + 'x32' { + return '(\$process.arch == "x32")' + } + // endianness: + 'little_endian' { + return '(\$os.endianess == "LE")' + } + 'big_endian' { + return '(\$os.endianess == "BE")' + } + else { + if is_comptime_optional + || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { + return 'CUSTOM_DEFINE_$name' + } + return error('bad os ifdef name "$name"') // should never happen, caught in the checker + } + } + return none +} diff --git a/v_windows/v/vlib/v/gen/js/fast_deep_equal.js b/v_windows/v/vlib/v/gen/js/fast_deep_equal.js new file mode 100644 index 0000000..50d37e8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/fast_deep_equal.js @@ -0,0 +1,72 @@ +// https://www.npmjs.com/package/fast-deep-equal - 3/3/2021 +const envHasBigInt64Array = typeof BigInt64Array !== 'undefined'; +function vEq(a, b) { + if (a === b) return true; + + if (a && b && typeof a == 'object' && typeof b == 'object') { + if (a.constructor !== b.constructor) return false; + // we want to convert all V types to JS for comparison. + if ('$toJS' in a) + a = a.$toJS(); + + if ('$toJS' in b) + b = b.$toJS(); + + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (!vEq(a[i], b[i])) return false; + return true; + } + + + if ((a instanceof Map) && (b instanceof Map)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + for (i of a.entries()) + if (!vEq(i[1], b.get(i[0]))) return false; + return true; + } + + if ((a instanceof Set) && (b instanceof Set)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + return true; + } + + if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (a[i] !== b[i]) return false; + return true; + } + + + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + + for (i = length; i-- !== 0;) + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + + for (i = length; i-- !== 0;) { + var key = keys[i]; + + if (!vEq(a[key], b[key])) return false; + } + + return true; + } + + // true if both NaN, false otherwise + return a !== a && b !== b; +}; \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/fn.v b/v_windows/v/vlib/v/gen/js/fn.v new file mode 100644 index 0000000..6bde0a1 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/fn.v @@ -0,0 +1,241 @@ +module js + +import v.ast + +fn (mut g JsGen) gen_method_call(it ast.CallExpr) bool { + g.call_stack << it + + mut name := g.js_name(it.name) + call_return_is_optional := it.return_type.has_flag(.optional) + if call_return_is_optional { + g.writeln('(function(){') + g.inc_indent() + g.writeln('try {') + g.inc_indent() + g.write('return builtin.unwrap(') + } + sym := g.table.get_type_symbol(it.receiver_type) + if sym.kind == .array { + if sym.kind == .array && it.name in ['map', 'filter'] { + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.') + // Prevent 'it' from getting shadowed inside the match + node := it + g.write(it.name) + g.write('(') + expr := node.args[0].expr + match expr { + ast.AnonFn { + g.gen_fn_decl(expr.decl) + g.write(')') + return true + } + ast.Ident { + if expr.kind == .function { + g.write(g.js_name(expr.name)) + g.write(')') + return true + } else if expr.kind == .variable { + v_sym := g.table.get_type_symbol(expr.var_info().typ) + if v_sym.kind == .function { + g.write(g.js_name(expr.name)) + g.write(')') + return true + } + } + } + else {} + } + + g.write('it => ') + g.expr(node.args[0].expr) + g.write(')') + return true + } + + left_sym := g.table.get_type_symbol(it.left_type) + if left_sym.kind == .array { + if it.name in special_array_methods { + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.') + + g.gen_array_method_call(it) + return true + } + } + } + + mut ltyp := it.left_type + mut lsym := g.table.get_type_symbol(ltyp) + if lsym.kind == .interface_ { + g.write(g.js_name(lsym.name)) + g.write('.${name}.call(') + g.expr(it.left) + g.write(',') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end method call + g.write(')') + } else { + g.write('Object.getPrototypeOf(') + g.expr(it.left) + + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write(').$name .call(') + g.expr(it.left) + g.write(',') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end method call + g.write(')') + } + + if call_return_is_optional { + // end unwrap + g.writeln(')') + g.dec_indent() + // begin catch block + g.writeln('} catch(err) {') + g.inc_indent() + // gen or block contents + match it.or_block.kind { + .block { + if it.or_block.stmts.len > 1 { + g.stmts(it.or_block.stmts[..it.or_block.stmts.len - 1]) + } + // g.write('return ') + g.stmt(it.or_block.stmts.last()) + } + .propagate { + panicstr := '`optional not set (\${err})`' + if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { + g.writeln('return builtin.panic($panicstr)') + } else { + g.writeln('builtin.js_throw(err)') + } + } + else {} + } + // end catch + g.dec_indent() + g.writeln('}') + // end anon fn + g.dec_indent() + g.write('})()') + } + g.call_stack.delete_last() + return true +} + +fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { + if it.is_method { + if g.gen_method_call(it) { + return + } + } + node := it + g.call_stack << it + mut name := g.js_name(it.name) + is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic'] + print_method := name + ret_sym := g.table.get_type_symbol(it.return_type) + if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write(ret_sym.name) + g.write('(') + } + call_return_is_optional := it.return_type.has_flag(.optional) + if call_return_is_optional { + g.writeln('(function(){') + g.inc_indent() + g.writeln('try {') + g.inc_indent() + g.write('return builtin.unwrap(') + } + if is_print { + mut typ := node.args[0].typ + + expr := node.args[0].expr + g.write('builtin.$print_method (') + g.gen_expr_to_string(expr, typ) + g.write(')') + return + } + g.expr(it.left) + + if name in g.builtin_fns { + g.write('builtin.') + } + + g.write('${name}(') + for i, arg in it.args { + g.expr(arg.expr) + if i != it.args.len - 1 { + g.write(', ') + } + } + // end method call + g.write(')') + if call_return_is_optional { + // end unwrap + g.writeln(')') + g.dec_indent() + // begin catch block + g.writeln('} catch(err) {') + g.inc_indent() + // gen or block contents + match it.or_block.kind { + .block { + if it.or_block.stmts.len > 1 { + g.stmts(it.or_block.stmts[..it.or_block.stmts.len - 1]) + } + + // g.write('return ') + g.stmt(it.or_block.stmts.last()) + } + .propagate { + panicstr := '`optional not set (\${err})`' + if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { + g.writeln('return builtin.panic($panicstr)') + } else { + g.writeln('builtin.js_throw(err)') + } + } + else {} + } + // end catch + g.dec_indent() + g.writeln('}') + // end anon fn + g.dec_indent() + g.write('})()') + } + if it.language == .js && ret_sym.name in v_types && ret_sym.name != 'void' { + g.write(')') + } + g.call_stack.delete_last() +} diff --git a/v_windows/v/vlib/v/gen/js/js.v b/v_windows/v/vlib/v/gen/js/js.v new file mode 100644 index 0000000..d35feed --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/js.v @@ -0,0 +1,3000 @@ +module js + +import strings +import v.ast +import v.token +import v.pref +import v.util +import v.util.version +import v.depgraph +import encoding.base64 +import v.gen.js.sourcemap + +struct MutArg { + tmp_var string + expr ast.Expr = ast.empty_expr() +} + +const ( + // https://ecma-international.org/ecma-262/#sec-reserved-words + js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', + 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function', + 'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package', + 'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', + 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield', 'Number', 'String', 'Boolean', + 'Array', 'Map'] + // used to generate type structs + v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', + 'int_literal', 'float_literal', 'size_t', 'bool', 'string', 'map', 'array', 'any'] + shallow_equatables = [ast.Kind.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, + .int_literal, .float_literal, .size_t, .bool, .string] +) + +struct SourcemapHelper { + src_path string + src_line u32 + ns_pos u32 +} + +struct Namespace { + name string +mut: + out strings.Builder = strings.new_builder(128) + pub_vars []string + imports map[string]string + indent int + methods map[string][]ast.FnDecl + sourcemap_helper []SourcemapHelper +} + +[heap] +struct JsGen { + pref &pref.Preferences +mut: + table &ast.Table + definitions strings.Builder + ns &Namespace + namespaces map[string]&Namespace + doc &JsDoc + enable_doc bool + file &ast.File + tmp_count int + inside_ternary bool + inside_loop bool + inside_map_set bool // map.set(key, value) + inside_builtin bool + inside_if_optional bool + generated_builtin bool + inside_def_typ_decl bool + is_test bool + stmt_start_pos int + defer_stmts []ast.DeferStmt + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + str_types []string // types that need automatic str() generation + method_fn_decls map[string][]ast.FnDecl + builtin_fns []string // Functions defined in `builtin` + empty_line bool + cast_stack []ast.Type + call_stack []ast.CallExpr + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + sourcemap sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line + comptime_var_type_map map[string]ast.Type + defer_ifdef string +} + +fn (mut g JsGen) write_tests_definitions() { + g.definitions.writeln('globalThis.g_test_oks = 0;') + g.definitions.writeln('globalThis.g_test_fails = 0;') +} + +pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { + mut g := &JsGen{ + definitions: strings.new_builder(100) + table: table + pref: pref + fn_decl: 0 + empty_line: true + doc: 0 + ns: 0 + enable_doc: true + file: 0 + } + g.doc = new_jsdoc(g) + // TODO: Add '[-no]-jsdoc' flag + if pref.is_prod { + g.enable_doc = false + g.is_vlines_enabled = false + } + g.init() + mut graph := depgraph.new_dep_graph() + if g.pref.sourcemap { + mut sg := sourcemap.generate_empty_map() + g.sourcemap = sg.add_map('', '', g.pref.sourcemap_src_included, 0, 0) + } + mut tests_inited := false + + // Get class methods + for file in files { + g.file = file + g.enter_namespace(g.file.mod.name) + g.is_test = g.pref.is_test + g.find_class_methods(file.stmts) + g.escape_namespace() + } + + for file in files { + g.file = file + g.enter_namespace(g.file.mod.name) + g.is_test = g.pref.is_test + // store imports + mut imports := []string{} + for imp in g.file.imports { + imports << imp.mod + } + graph.add(g.file.mod.name, imports) + // builtin types + if g.file.mod.name == 'builtin' && !g.generated_builtin { + g.gen_builtin_type_defs() + g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(string.prototype,"len", { get: function() {return new builtin.int(this.str.length);}, set: function(l) {/* ignore */ } }); ') + g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new builtin.int(this.map.length);}, set: function(l) { this.map.length = l.valueOf(); } }); ') + g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') + g.generated_builtin = true + } + if g.is_test && !tests_inited { + g.write_tests_definitions() + tests_inited = true + } + g.stmts(file.stmts) + g.writeln('try { init() } catch (_) {}') + // store the current namespace + g.escape_namespace() + } + if g.pref.is_test { + g.gen_js_main_for_tests() + } + // resolve imports + deps_resolved := graph.resolve() + nodes := deps_resolved.nodes + + mut out := g.hashes() + g.definitions.str() + // equality check for js objects + // TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') + // unsafe { + // mut eq_fn := $embed_file('fast_deep_equal.js') + // out += eq_fn.data().vstring() + //} + out += fast_deep_eq_fn + for node in nodes { + name := g.js_name(node.name).replace('.', '_') + if g.enable_doc { + out += '/** @namespace $name */\n' + } + out += 'const $name = (function (' + mut namespace := g.namespaces[node.name] + mut first := true + for _, val in namespace.imports { + if !first { + out += ', ' + } + first = false + out += val + } + out += ') {\n\t' + namespace_code := namespace.out.str() + if g.pref.sourcemap { + // calculate current output start line + mut current_line := u32(out.count('\n') + 1) + mut sm_pos := u32(0) + for sourcemap_ns_entry in namespace.sourcemap_helper { + // calculate final generated location in output based on position + current_segment := namespace_code.substr(int(sm_pos), int(sourcemap_ns_entry.ns_pos)) + current_line += u32(current_segment.count('\n')) + current_column := if last_nl_pos := current_segment.last_index('\n') { + u32(current_segment.len - last_nl_pos - 1) + } else { + u32(0) + } + g.sourcemap.add_mapping(sourcemap_ns_entry.src_path, sourcemap.SourcePosition{ + source_line: sourcemap_ns_entry.src_line + source_column: 0 // sourcemap_ns_entry.src_column + }, current_line, current_column, '') + sm_pos = sourcemap_ns_entry.ns_pos + } + } + out += namespace_code + + // public scope + out += '\n' + if g.enable_doc { + out += '\n\t/* module exports */' + } + out += '\n\treturn {' + // export builtin types + if name == 'builtin' { + for typ in js.v_types { + out += '\n\t\t$typ,' + } + } + for i, pub_var in namespace.pub_vars { + out += '\n\t\t$pub_var' + if i < namespace.pub_vars.len - 1 { + out += ',' + } + } + if namespace.pub_vars.len > 0 { + out += '\n\t' + } + out += '};' + out += '\n})(' + first = true + for key, _ in namespace.imports { + if !first { + out += ', ' + } + first = false + out += key.replace('.', '_') + } + out += ');\n' + // generate builtin basic type casts + if name == 'builtin' { + out += '// builtin type casts\n' + out += 'const [' + for i, typ in js.v_types { + if i > 0 { + out += ', ' + } + out += '$typ' + } + out += '] = [' + for i, typ in js.v_types { + if i > 0 { + out += ',' + } + out += '\n\tfunction(val) { return new builtin.${typ}(val) }' + } + out += '\n]\n' + } + } + if pref.is_shared { + // Export, through CommonJS, the module of the entry file if `-shared` was passed + export := nodes[nodes.len - 1].name + out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n' + } + out += '\n' + if g.pref.sourcemap { + out += g.create_sourcemap() + } + return out +} + +fn (g JsGen) create_sourcemap() string { + mut sm := g.sourcemap + mut out := '\n//# sourceMappingURL=data:application/json;base64,' + out += base64.encode(sm.to_json().str().bytes()) + out += '\n' + + return out +} + +pub fn (mut g JsGen) gen_js_main_for_tests() { + g.enter_namespace('main') + g.writeln('(function() { ') + g.inc_indent() + all_tfuncs := g.get_all_test_function_names() + + g.writeln('') + g.writeln('globalThis.VTEST=1') + if g.pref.is_stats { + g.writeln('let bt = start_testing($all_tfuncs.len, "$g.pref.path")') + } + for tname in all_tfuncs { + tcname := g.js_name(tname) + + if g.pref.is_stats { + g.writeln('bt.testing_step_start("$tcname")') + } + + g.writeln('try { ${tcname}(); } catch (_e) {} ') + if g.pref.is_stats { + g.writeln('bt.testing_step_end();') + } + } + + g.writeln('') + if g.pref.is_stats { + g.writeln('bt.end_testing();') + } + g.dec_indent() + g.writeln('})();') + g.escape_namespace() +} + +fn (g &JsGen) get_all_test_function_names() []string { + mut tfuncs := []string{} + mut tsuite_begin := '' + mut tsuite_end := '' + for _, f in g.table.fns { + if f.name.ends_with('.testsuite_begin') { + tsuite_begin = f.name + continue + } + if f.name.contains('.test_') { + tfuncs << f.name + continue + } + if f.name.ends_with('.testsuite_end') { + tsuite_end = f.name + continue + } + } + mut all_tfuncs := []string{} + if tsuite_begin.len > 0 { + all_tfuncs << tsuite_begin + } + all_tfuncs << tfuncs + if tsuite_end.len > 0 { + all_tfuncs << tsuite_end + } + return all_tfuncs +} + +pub fn (mut g JsGen) enter_namespace(name string) { + if g.namespaces[name] == 0 { + // create a new namespace + ns := &Namespace{ + name: name + } + g.namespaces[name] = ns + g.ns = ns + } else { + g.ns = g.namespaces[name] + } + g.inside_builtin = name == 'builtin' +} + +pub fn (mut g JsGen) escape_namespace() { + g.ns = &Namespace(0) + g.inside_builtin = false +} + +pub fn (mut g JsGen) push_pub_var(s string) { + g.ns.pub_vars << g.js_name(s) +} + +pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) { + for stmt in stmts { + match stmt { + ast.FnDecl { + if stmt.is_method { + // Found struct method, store it to be generated along with the class. + mut class_name := g.table.get_type_name(stmt.receiver.typ) + // Workaround until `map[key] << val` works. + mut arr := g.method_fn_decls[class_name] + arr << stmt + g.method_fn_decls[class_name] = arr + } + } + else {} + } + } +} + +pub fn (mut g JsGen) init() { + g.definitions.writeln('// Generated by the V compiler\n') + // g.definitions.writeln('"use strict";') + g.definitions.writeln('') + g.definitions.writeln('var \$global = (new Function("return this"))();') + g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ') + g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ') + if g.pref.backend != .js_node { + g.definitions.writeln('const \$process = {') + g.definitions.writeln(' arch: "js",') + if g.pref.backend == .js_freestanding { + g.definitions.writeln(' platform: "freestanding"') + } else { + g.definitions.writeln(' platform: "browser"') + } + g.definitions.writeln('}') + + g.definitions.writeln('const \$os = {') + g.definitions.writeln(' endianess: "LE",') + + g.definitions.writeln('}') + } else { + g.definitions.writeln('const \$os = require("os");') + g.definitions.writeln('const \$process = process;') + } + g.definitions.writeln('function alias(value) { return value; } ') + + g.definitions.writeln('function \$v_fmt(value) { let res = ""; + if (Object.getPrototypeOf(s).hasOwnProperty("str") && typeof s.str == "function") res = s.str().str + else res = s.toString() + return res + } ') +} + +pub fn (g JsGen) hashes() string { + mut res := '// V_COMMIT_HASH $version.vhash()\n' + res += '// V_CURRENT_COMMIT_HASH ${version.githash(g.pref.building_v)}\n' + return res +} + +[noreturn] +fn verror(msg string) { + eprintln('jsgen error: $msg') + exit(1) +} + +[inline] +pub fn (mut g JsGen) gen_indent() { + if g.ns.indent > 0 && g.empty_line { + g.ns.out.write_string(util.tabs(g.ns.indent)) + } + g.empty_line = false +} + +[inline] +pub fn (mut g JsGen) inc_indent() { + g.ns.indent++ +} + +[inline] +pub fn (mut g JsGen) dec_indent() { + g.ns.indent-- +} + +[inline] +pub fn (mut g JsGen) write(s string) { + if g.ns == 0 { + verror('g.write: not in a namespace') + } + g.gen_indent() + g.ns.out.write_string(s) +} + +[inline] +pub fn (mut g JsGen) writeln(s string) { + if g.ns == 0 { + verror('g.writeln: not in a namespace') + } + g.gen_indent() + g.ns.out.writeln(s) + g.empty_line = true +} + +[inline] +pub fn (mut g JsGen) new_tmp_var() string { + g.tmp_count++ + return '_tmp$g.tmp_count' +} + +// 'mod1.mod2.fn' => 'mod1.mod2' +// 'fn' => '' +[inline] +fn get_ns(s string) string { + idx := s.last_index('.') or { return '' } + return s.substr(0, idx) +} + +fn (mut g JsGen) get_alias(name string) string { + ns := get_ns(name) + if ns == '' { + return name + } + alias := g.ns.imports[ns] + if alias == '' { + return name + } + return alias + '.' + name.split('.').last() +} + +fn (mut g JsGen) js_name(name_ string) string { + mut is_js := false + is_overload := ['+', '-', '*', '/', '==', '<', '>'] + mut name := name_ + if name.starts_with('JS.') { + name = name[3..] + is_js = true + } + ns := get_ns(name) + name = if name in is_overload { + match name { + '+' { + '\$add' + } + '-' { + '\$sub' + } + '/' { + '\$div' + } + '*' { + '\$mul' + } + '%' { + '\$mod' + } + '==' { + 'eq' + } + '>' { + '\$gt' + } + '<' { + '\$lt' + } + else { + '' + } + } + } else if g.ns == 0 { + name + } else if ns == g.ns.name { + name.split('.').last() + } else { + g.get_alias(name) + } + mut parts := name.split('.') + if !is_js { + for i, p in parts { + if p in js.js_reserved { + parts[i] = 'v_$p' + } + } + } + return parts.join('.') +} + +fn (mut g JsGen) stmts(stmts []ast.Stmt) { + g.inc_indent() + for stmt in stmts { + g.stmt(stmt) + } + g.dec_indent() +} + +[inline] +fn (mut g JsGen) write_v_source_line_info(pos token.Position) { + // g.inside_ternary == 0 && + if g.pref.sourcemap { + g.ns.sourcemap_helper << SourcemapHelper{ + src_path: util.vlines_escape_path(g.file.path, g.pref.ccompiler) + src_line: u32(pos.line_nr + 1) + ns_pos: u32(g.ns.out.len) + } + } + if g.pref.is_vlines && g.is_vlines_enabled { + g.write(' /* ${pos.line_nr + 1} $g.ns.out.len */ ') + } +} + +fn (mut g JsGen) gen_global_decl(node ast.GlobalDecl) { + mod := if g.pref.build_mode == .build_module { 'enumerable: false' } else { 'enumerable: true' } + for field in node.fields { + if field.has_expr { + tmp_var := g.new_tmp_var() + g.write('const $tmp_var = ') + g.expr(field.expr) + g.writeln(';') + g.writeln('Object.defineProperty(\$global,"$field.name", { + configurable: false, + $mod , + writable: true, + value: $tmp_var + } + ); // global') + } else { + // TODO(playXE): Initialize with default value of type + + if field.typ.is_ptr() { + g.writeln('Object.defineProperty(\$global,"$field.name", { + configurable: false, + $mod , + writable: true, + value: new \$ref({}) + } + ); // global') + } else { + g.writeln('Object.defineProperty(\$global,"$field.name", { + configurable: false, + $mod , + writable: true, + value: {} + } + ); // global') + } + } + } +} + +fn (mut g JsGen) stmt_no_semi(node ast.Stmt) { + g.stmt_start_pos = g.ns.out.len + match node { + ast.EmptyStmt {} + ast.AsmStmt { + panic('inline asm is not supported by js') + } + ast.AssertStmt { + g.write_v_source_line_info(node.pos) + g.gen_assert_stmt(node) + } + ast.AssignStmt { + g.write_v_source_line_info(node.pos) + g.gen_assign_stmt(node, false) + } + ast.Block { + g.write_v_source_line_info(node.pos) + g.gen_block(node) + g.writeln('') + } + ast.BranchStmt { + g.write_v_source_line_info(node.pos) + g.gen_branch_stmt(node) + } + ast.CompFor {} + ast.ConstDecl { + g.write_v_source_line_info(node.pos) + g.gen_const_decl(node) + } + ast.DeferStmt { + g.defer_stmts << node + } + ast.EnumDecl { + g.write_v_source_line_info(node.pos) + g.gen_enum_decl(node) + g.writeln('') + } + ast.ExprStmt { + g.write_v_source_line_info(node.pos) + g.gen_expr_stmt_no_semi(node) + } + ast.FnDecl { + g.write_v_source_line_info(node.pos) + g.fn_decl = unsafe { &node } + g.gen_fn_decl(node) + } + ast.ForCStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_c_stmt(node) + g.writeln('') + } + ast.ForInStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_in_stmt(node) + g.writeln('') + } + ast.ForStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_stmt(node) + g.writeln('') + } + ast.GlobalDecl { + g.write_v_source_line_info(node.pos) + g.gen_global_decl(node) + g.writeln('') + } + ast.GotoLabel { + g.write_v_source_line_info(node.pos) + g.writeln('${g.js_name(node.name)}:') + } + ast.GotoStmt { + // skip: JS has no goto + } + ast.HashStmt { + g.write_v_source_line_info(node.pos) + g.gen_hash_stmt(node) + } + ast.Import { + g.ns.imports[node.mod] = node.alias + } + ast.InterfaceDecl { + g.write_v_source_line_info(node.pos) + g.gen_interface_decl(node) + } + ast.Module { + // skip: namespacing implemented externally + } + ast.NodeError {} + ast.Return { + if g.defer_stmts.len > 0 { + g.gen_defer_stmts() + } + g.gen_return_stmt(node) + } + ast.SqlStmt {} + ast.StructDecl { + g.write_v_source_line_info(node.pos) + g.gen_struct_decl(node) + } + ast.TypeDecl {} + } +} + +fn (mut g JsGen) stmt(node ast.Stmt) { + g.stmt_start_pos = g.ns.out.len + match node { + ast.EmptyStmt {} + ast.AsmStmt { + panic('inline asm is not supported by js') + } + ast.AssertStmt { + g.write_v_source_line_info(node.pos) + g.gen_assert_stmt(node) + } + ast.AssignStmt { + g.write_v_source_line_info(node.pos) + g.gen_assign_stmt(node, true) + } + ast.Block { + g.write_v_source_line_info(node.pos) + g.gen_block(node) + g.writeln('') + } + ast.BranchStmt { + g.write_v_source_line_info(node.pos) + g.gen_branch_stmt(node) + } + ast.CompFor {} + ast.ConstDecl { + g.write_v_source_line_info(node.pos) + g.gen_const_decl(node) + } + ast.DeferStmt { + g.defer_stmts << node + } + ast.EnumDecl { + g.write_v_source_line_info(node.pos) + g.gen_enum_decl(node) + g.writeln('') + } + ast.ExprStmt { + g.write_v_source_line_info(node.pos) + g.gen_expr_stmt(node) + } + ast.FnDecl { + g.write_v_source_line_info(node.pos) + g.fn_decl = unsafe { &node } + g.gen_fn_decl(node) + } + ast.ForCStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_c_stmt(node) + g.writeln('') + } + ast.ForInStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_in_stmt(node) + g.writeln('') + } + ast.ForStmt { + g.write_v_source_line_info(node.pos) + g.gen_for_stmt(node) + g.writeln('') + } + ast.GlobalDecl { + g.write_v_source_line_info(node.pos) + g.gen_global_decl(node) + g.writeln('') + } + ast.GotoLabel { + g.write_v_source_line_info(node.pos) + g.writeln('${g.js_name(node.name)}:') + } + ast.GotoStmt { + // skip: JS has no goto + } + ast.HashStmt { + g.write_v_source_line_info(node.pos) + g.gen_hash_stmt(node) + } + ast.Import { + g.ns.imports[node.mod] = node.alias + } + ast.InterfaceDecl { + g.write_v_source_line_info(node.pos) + g.gen_interface_decl(node) + } + ast.Module { + // skip: namespacing implemented externally + } + ast.NodeError {} + ast.Return { + if g.defer_stmts.len > 0 { + g.gen_defer_stmts() + } + g.gen_return_stmt(node) + } + ast.SqlStmt {} + ast.StructDecl { + g.write_v_source_line_info(node.pos) + g.gen_struct_decl(node) + } + ast.TypeDecl { + // skip JS has no typedecl + } + } +} + +fn (mut g JsGen) expr(node ast.Expr) { + match node { + ast.NodeError {} + ast.EmptyExpr {} + ast.CTempVar { + g.write('/* ast.CTempVar: node.name */') + } + ast.DumpExpr { + g.write('/* ast.DumpExpr: $node.expr */') + } + ast.AnonFn { + g.gen_fn_decl(node.decl) + } + ast.ArrayInit { + g.gen_array_init_expr(node) + } + ast.AsCast { + // skip: JS has no types, so no need to cast + // TODO: Is jsdoc needed here for TS support? + } + ast.Assoc { + // TODO + } + ast.BoolLiteral { + if node.val == true { + g.write('true') + } else { + g.write('false') + } + } + ast.CallExpr { + g.gen_call_expr(node) + } + ast.ChanInit { + // TODO + } + ast.CastExpr { + g.gen_type_cast_expr(node) + } + ast.CharLiteral { + // todo(playX): char type? + g.write("new builtin.string('$node.val')") + } + ast.Comment {} + ast.ConcatExpr { + // TODO + } + ast.EnumVal { + sym := g.table.get_type_symbol(node.typ) + styp := g.js_name(sym.name) + g.write('${styp}.$node.val') + } + ast.FloatLiteral { + g.gen_float_literal_expr(node) + } + ast.GoExpr { + g.gen_go_expr(node) + } + ast.Ident { + g.gen_ident(node) + } + ast.IfExpr { + g.gen_if_expr(node) + } + ast.IfGuardExpr { + // TODO no optionals yet + } + ast.IndexExpr { + g.gen_index_expr(node) + } + ast.InfixExpr { + g.gen_infix_expr(node) + } + ast.IntegerLiteral { + g.gen_integer_literal_expr(node) + } + ast.LockExpr { + g.gen_lock_expr(node) + } + ast.MapInit { + g.gen_map_init_expr(node) + } + ast.None { + g.write('builtin.none__') + } + ast.MatchExpr { + g.match_expr(node) + } + ast.OrExpr { + // TODO + } + ast.ParExpr { + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.PostfixExpr { + g.expr(node.expr) + if node.op in [.inc, .dec] { + g.write('.val $node.op') + } else { + g.write(node.op.str()) + } + } + ast.PrefixExpr { + if node.op in [.amp, .mul] { + if node.op == .amp { + // if !node.right_type.is_pointer() { + // kind of weird way to handle references but it allows us to access type methods easily. + g.write('(function(x) {') + g.write(' return { val: x, __proto__: Object.getPrototypeOf(x), valueOf: function() { return this.val; } }})( ') + g.expr(node.right) + g.write(')') + //} else { + // g.expr(node.right) + // } + } else { + g.write('(') + g.expr(node.right) + g.write(').valueOf()') + } + } else { + g.write(node.op.str()) + + if node.op in [.inc, .dec] { + g.expr(node.right) + g.write('.val ') + } else { + g.write('(') + g.expr(node.right) + g.write('.valueOf()') + g.write(')') + } + } + } + ast.RangeExpr { + // Only used in IndexExpr, requires index type info + } + ast.SelectExpr { + // TODO: to be implemented + } + ast.SelectorExpr { + g.gen_selector_expr(node) + } + ast.SizeOf, ast.IsRefType { + // TODO + } + ast.OffsetOf { + // TODO + } + ast.SqlExpr { + // TODO + } + ast.StringInterLiteral { + g.gen_string_inter_literal(node) + } + ast.StringLiteral { + g.gen_string_literal(node) + } + ast.StructInit { + // TODO: once generic fns/unwrap_generic is implemented + // if node.unresolved { + // g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) + // } else { + // // `user := User{name: 'Bob'}` + // g.gen_struct_init(node) + // } + // `user := User{name: 'Bob'}` + g.gen_struct_init(node) + } + ast.TypeNode { + typ := g.unwrap_generic(node.typ) + sym := g.table.get_type_symbol(typ) + name := sym.name.replace_once('${g.ns.name}.', '') + g.write('$name') + } + ast.Likely { + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.TypeOf { + g.gen_typeof_expr(node) + // TODO: Should this print the V type or the JS type? + } + ast.AtExpr { + g.write('"$node.val"') + } + ast.ComptimeCall { + // TODO + } + ast.ComptimeSelector { + // TODO + } + ast.UnsafeExpr { + g.expr(node.expr) + } + ast.ArrayDecompose {} + } +} + +fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { + mod_path := g.file.path + fn_name := g.fn_decl.name + line_nr := node.pos.line_nr + src := node.expr.str() + metaname := 'v_assert_meta_info_$g.new_tmp_var()' + g.writeln('let $metaname = {}') + g.writeln('${metaname}.fpath = new builtin.string("$mod_path");') + g.writeln('${metaname}.line_nr = new builtin.int("$line_nr")') + g.writeln('${metaname}.fn_name = new builtin.string("$fn_name")') + metasrc := src + g.writeln('${metaname}.src = "$metasrc"') + + match mut node.expr { + ast.InfixExpr { + expr_op_str := node.expr.op.str() + expr_left_str := node.expr.left.str() + expr_right_str := node.expr.right.str() + g.writeln('\t${metaname}.op = new builtin.string("$expr_op_str");') + g.writeln('\t${metaname}.llabel = new builtin.string("$expr_left_str");') + g.writeln('\t${metaname}.rlabel = new builtin.string("$expr_right_str");') + g.write('\t${metaname}.lvalue = new builtin.string("') + g.gen_assert_single_expr(node.expr.left, node.expr.left_type) + g.writeln('");') + g.write('\t${metaname}.rvalue = new builtin.string("') + g.gen_assert_single_expr(node.expr.right, node.expr.right_type) + g.writeln('");') + } + ast.CallExpr { + g.writeln('\t${metaname}.op = new builtin.string("call");') + } + else {} + } + return metaname +} + +fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { + // eprintln('> gen_assert_single_expr typ: $typ | expr: $expr | typeof(expr): ${typeof(expr)}') + unknown_value := '*unknown value*' + match expr { + ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr { + g.write(unknown_value) + } + ast.PrefixExpr { + g.write(unknown_value) + } + ast.TypeNode { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + g.write('$sym.name') + } + else { + g.writeln(unknown_value) + } + } + g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') +} + +// TODO +fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { + if !a.is_used { + return + } + g.writeln('// assert') + g.write('if( ') + g.expr(a.expr) + g.write(' ) {') + s_assertion := a.expr.str().replace('"', "'") + mut mod_path := g.file.path.replace('\\', '\\\\') + if g.is_test { + metaname_ok := g.gen_assert_metainfo(a) + g.writeln(' g_test_oks++;') + g.writeln(' cb_assertion_ok($metaname_ok);') + g.writeln('} else {') + metaname_fail := g.gen_assert_metainfo(a) + g.writeln(' g_test_fails++;') + g.writeln(' cb_assertion_failed($metaname_fail);') + g.writeln(' builtin.exit(1);') + g.writeln('}') + return + } + g.writeln('} else {') + g.inc_indent() + g.writeln('builtin.eprintln("$mod_path:${a.pos.line_nr + 1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion");') + g.writeln('builtin.exit(1);') + g.dec_indent() + g.writeln('}') +} + +fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { + if stmt.left.len > stmt.right.len { + // multi return + g.write('const [') + for i, left in stmt.left { + if !left.is_blank_ident() { + g.expr(left) + } + if i < stmt.left.len - 1 { + g.write(', ') + } + } + g.write('] = ') + g.expr(stmt.right[0]) + if semicolon { + g.writeln(';') + } + } else { + // `a := 1` | `a,b := 1,2` + for i, left in stmt.left { + mut op := stmt.op + if stmt.op == .decl_assign { + op = .assign + } + is_assign := stmt.op in [.plus_assign, .minus_assign, .mult_assign, .div_assign, + .xor_assign, .mod_assign, .or_assign, .and_assign, .right_shift_assign, + .left_shift_assign, + ] + + val := stmt.right[i] + mut is_mut := false + if left is ast.Ident { + is_mut = left.is_mut + if left.kind == .blank_ident || left.name in ['', '_'] { + tmp_var := g.new_tmp_var() + // TODO: Can the tmp_var declaration be omitted? + g.write('const $tmp_var = ') + g.expr(val) + g.writeln(';') + continue + } + } + mut styp := g.typ(stmt.left_types[i]) + l_sym := g.table.get_type_symbol(stmt.left_types[i]) + if !g.inside_loop && styp.len > 0 { + g.doc.gen_typ(styp) + } + if stmt.op == .decl_assign { + if g.inside_loop || is_mut { + g.write('let ') + } else { + g.write('const ') + } + } + g.expr(left) + mut is_ptr := false + if stmt.op == .assign && stmt.left_types[i].is_ptr() { + is_ptr = true + g.write('.val') + } + if g.inside_map_set && op == .assign { + g.inside_map_set = false + g.write(', ') + g.expr(val) + if is_ptr { + g.write('.val') + } + g.write(')') + } else { + if is_assign { + if l_sym.kind == .string { + g.write('.str') + } else { + g.write('.val') + } + g.write(' = ') + g.expr(left) + + match op { + .plus_assign { + g.write(' + ') + } + .minus_assign { + g.write(' - ') + } + .mult_assign { + g.write(' * ') + } + .div_assign { + g.write(' / ') + } + .mod_assign { + g.write(' % ') + } + .xor_assign { + g.write(' ^ ') + } + .and_assign { + g.write(' & ') + } + .right_shift_assign { + g.write(' >> ') + } + .left_shift_assign { + g.write(' << ') + } + .or_assign { + g.write(' | ') + } + else { + panic('unexpected op $op') + } + } + } else { + g.write(' $op ') + } + // TODO: Multiple types?? + should_cast := + (g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables) + && (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last()) + + if should_cast { + g.cast_stack << stmt.left_types.first() + g.write('new ') + if g.file.mod.name != 'builtin' { + g.write('builtin.') + } + g.write('${g.typ(stmt.left_types.first())}(') + } + g.expr(val) + if is_ptr { + g.write('.val') + } + if should_cast { + g.write(')') + g.cast_stack.delete_last() + } + } + if semicolon { + if g.inside_loop { + g.write('; ') + } else { + g.writeln(';') + } + } + } + } +} + +fn (mut g JsGen) gen_attrs(attrs []ast.Attr) { + for attr in attrs { + g.writeln('/* [$attr.name] */') + } +} + +fn (mut g JsGen) gen_block(it ast.Block) { + g.writeln('{') + g.stmts(it.stmts) + g.writeln('}') +} + +fn (mut g JsGen) gen_branch_stmt(it ast.BranchStmt) { + // continue or break + g.write(it.kind.str()) + g.writeln(';') +} + +fn (mut g JsGen) gen_const_decl(it ast.ConstDecl) { + for field in it.fields { + g.doc.gen_const(g.typ(field.typ)) + if field.is_pub { + g.push_pub_var(field.name) + } + g.write('const ${g.js_name(field.name)} = ') + g.expr(field.expr) + g.writeln(';') + } + g.writeln('') +} + +fn (mut g JsGen) gen_defer_stmts() { + g.writeln('(function defer() {') + for defer_stmt in g.defer_stmts { + g.stmts(defer_stmt.stmts) + } + g.defer_stmts = [] + g.writeln('})();') +} + +fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) { + g.doc.gen_enum() + g.writeln('const ${g.js_name(it.name)} = {') + g.inc_indent() + mut i := 0 + for field in it.fields { + g.write('$field.name: ') + if field.has_expr && field.expr is ast.IntegerLiteral { + i = field.expr.val.int() + } + g.writeln('$i,') + i++ + } + g.dec_indent() + g.writeln('};') + if it.is_pub { + g.push_pub_var(it.name) + } +} + +fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) { + g.expr(it.expr) + if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary && !g.inside_if_optional { + g.writeln(';') + } +} + +fn (mut g JsGen) gen_expr_stmt_no_semi(it ast.ExprStmt) { + g.expr(it.expr) +} + +enum FnGenType { + function + struct_method + alias_method + iface_method +} + +fn (g &JsGen) fn_gen_type(it &ast.FnDecl) FnGenType { + if it.is_method && g.table.get_type_symbol(it.params[0].typ).kind == .alias { + return .alias_method + } else if it.is_method && g.table.get_type_symbol(it.params[0].typ).kind == .interface_ { + return .iface_method + } else if it.is_method || it.no_body { + return .struct_method + } else { + return .function + } +} + +fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { + res := g.fn_gen_type(it) + if res == .struct_method { + // Struct methods are handled by class generation code. + return + } + if g.inside_builtin { + g.builtin_fns << it.name + } + cur_fn_decl := g.fn_decl + g.gen_method_decl(it, res) + g.fn_decl = cur_fn_decl +} + +fn fn_has_go(node ast.FnDecl) bool { + mut has_go := false + for stmt in node.stmts { + if stmt is ast.ExprStmt { + if stmt.expr is ast.GoExpr { + has_go = true + break + } + } + } + return has_go +} + +fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { + unsafe { + g.fn_decl = &it + } + if typ == .alias_method || typ == .iface_method { + sym := g.table.get_final_type_symbol(it.params[0].typ.set_nr_muls(0)) + name := g.js_name(sym.name) + if name in js.v_types { + g.writeln('builtin.') + } + g.writeln('${name}.prototype.$it.name = function ') + } + has_go := fn_has_go(it) + is_main := it.name == 'main.main' + g.gen_attrs(it.attrs) + if is_main { + // there is no concept of main in JS but we do have iife + g.writeln('/* program entry point */') + + g.write('(') + if has_go { + g.write('async ') + } + g.write('function(') + } else if it.is_anon { + g.write('function (') + } else { + mut name := g.js_name(it.name) + c := name[0] + if c in [`+`, `-`, `*`, `/`] { + name = util.replace_op(name) + } + // type_name := g.typ(it.return_type) + // generate jsdoc for the function + g.doc.gen_fn(it) + if has_go { + g.write('async ') + } + if !it.is_method { + g.write('function ') + } else { + if it.attrs.contains('js_getter') { + g.write('get ') + } else if it.attrs.contains('js_setter') { + g.write('set ') + } + } + g.write('${name}(') + if it.is_pub && !it.is_method { + g.push_pub_var(name) + } + } + mut args := it.params + if it.is_method { + args = args[1..] + } + g.fn_args(args, it.is_variadic) + if it.is_method { + if args.len > 0 { + g.write(', ') + } + if it.params[0].is_mut || it.params[0].typ.is_ptr() { + g.write('${it.params[0].name} = new \$ref(this)') + } else { + g.write('${it.params[0].name} = this') + } + } + g.writeln(') {') + for i, arg in args { + is_varg := i == args.len - 1 && it.is_variadic + name := g.js_name(arg.name) + if is_varg { + g.writeln('$name = new array($name);') + } else { + if arg.typ.is_ptr() || arg.is_mut { + g.writeln('$name = new \$ref($name)') + } + } + } + + g.stmts(it.stmts) + g.writeln('}') + + if is_main { + g.write(')();') + } else if typ != .struct_method { + // g.write(';') + } + if typ == .struct_method || typ == .alias_method || typ == .iface_method { + g.writeln('\n') + } + + g.fn_decl = voidptr(0) +} + +fn (mut g JsGen) fn_args(args []ast.Param, is_variadic bool) { + for i, arg in args { + name := g.js_name(arg.name) + is_varg := i == args.len - 1 && is_variadic + if is_varg { + g.write('...$name') + } else { + g.write(name) + } + // if its not the last argument + if i < args.len - 1 { + g.write(', ') + } + } +} + +fn (mut g JsGen) gen_for_c_stmt(it ast.ForCStmt) { + g.inside_loop = true + g.write('for (') + if it.has_init { + g.stmt(it.init) + } else { + g.write('; ') + } + if it.has_cond { + g.write('+') // convert to number or boolean + g.expr(it.cond) + } + g.write('; ') + if it.has_inc { + g.stmt_no_semi(it.inc) + } + g.writeln(') {') + g.stmts(it.stmts) + g.writeln('}') + g.inside_loop = false +} + +fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { + if it.is_range { + // `for x in 1..10 {` + mut i := it.val_var + if i in ['', '_'] { + i = g.new_tmp_var() + } + g.inside_loop = true + g.write('for (let $i = ') + g.expr(it.cond) + g.write('; $i < ') + g.expr(it.high) + g.writeln('; $i = new builtin.int($i + 1)) {') + g.inside_loop = false + g.stmts(it.stmts) + g.writeln('}') + } else if it.kind in [.array, .string] || it.cond_type.has_flag(.variadic) { + // `for num in nums {` + val := if it.val_var in ['', '_'] { '_' } else { it.val_var } + // styp := g.typ(it.val_type) + if it.key_var.len > 0 { + g.write('for (const [$it.key_var, $val] of ') + if it.kind == .string { + g.write('Array.from(') + g.expr(it.cond) + if it.cond_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.str.split(\'\').entries(), ([$it.key_var, $val]) => [$it.key_var, ') + if g.ns.name == 'builtin' { + g.write('new ') + } + g.write('byte($val)])') + } else { + g.expr(it.cond) + if it.cond_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.entries()') + } + } else { + g.write('for (const $val of ') + g.expr(it.cond) + if it.cond_type.is_ptr() { + g.write('.valueOf()') + } + if it.kind == .string { + g.write(".str.split('')") + } + // cast characters to bytes + if val !in ['', '_'] && it.kind == .string { + g.write('.map(c => ') + if g.ns.name == 'builtin' { + g.write('new ') + } + g.write('byte(c))') + } + } + g.writeln(') {') + g.stmts(it.stmts) + g.writeln('}') + } else if it.kind == .map { + // `for key, val in map[string]int {` + // key_styp := g.typ(it.key_type) + // val_styp := g.typ(it.val_type) + key := if it.key_var in ['', '_'] { '' } else { it.key_var } + val := if it.val_var in ['', '_'] { '' } else { it.val_var } + g.write('for (let [$key, $val] of ') + g.expr(it.cond) + if it.cond_type.is_ptr() { + g.write('.valueOf()') + } + g.writeln(') {') + g.stmts(it.stmts) + g.writeln('}') + } +} + +fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) { + g.write('while (') + if it.is_inf { + g.write('true') + } else { + g.write('+') // convert expr to number or boolean + g.expr(it.cond) + } + g.writeln(') {') + g.stmts(it.stmts) + g.writeln('}') +} + +fn (mut g JsGen) gen_go_expr(node ast.GoExpr) { + // TODO Handle joinable expressions + // node.is_expr + mut name := node.call_expr.name + if node.call_expr.is_method { + receiver_sym := g.table.get_type_symbol(node.call_expr.receiver_type) + name = receiver_sym.name + '.' + name + } + // todo: please add a name feild without the mod name for ast.CallExpr + if name.starts_with('${node.call_expr.mod}.') { + name = name[node.call_expr.mod.len + 1..] + } + g.writeln('await new Promise(function(resolve){') + g.inc_indent() + g.write('${name}(') + for i, arg in node.call_expr.args { + g.expr(arg.expr) + if i < node.call_expr.args.len - 1 { + g.write(', ') + } + } + g.writeln(');') + g.writeln('resolve();') + g.dec_indent() + g.writeln('});') +} + +fn (mut g JsGen) gen_import_stmt(it ast.Import) { + g.ns.imports[it.mod] = it.alias +} + +fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) { + // JS is dynamically typed, so we don't need any codegen at all + // We just need the JSDoc so TypeScript type checking works + g.doc.gen_interface(it) + // This is a hack to make the interface's type accessible outside its namespace + // TODO: interfaces are always `pub`? + name := g.js_name(it.name) + g.push_pub_var('/** @type $name */\n\t\t$name') + g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }') +} + +fn (mut g JsGen) gen_optional_error(expr ast.Expr) { + g.write('new builtin.Option({ state: new builtin.byte(2),err: ') + g.expr(expr) + g.write('})') +} + +fn (mut g JsGen) gen_return_stmt(it ast.Return) { + node := it + // sym := g.table.get_type_symbol(g.fn_decl.return_type) + fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional) + if node.exprs.len == 0 { + if fn_return_is_optional { + g.writeln('return {}') + } else { + g.writeln('return;') + } + return + } + + if fn_return_is_optional { + optional_none := node.exprs[0] is ast.None + ftyp := g.typ(node.types[0]) + mut is_regular_option := ftyp == 'Option' + if optional_none || is_regular_option || node.types[0] == ast.error_type_idx { + if !isnil(g.fn_decl) && g.fn_decl.is_test { + test_error_var := g.new_tmp_var() + g.writeln('let $test_error_var = "TODO";') + g.writeln('return $test_error_var;') + return + } + g.write('return ') + g.gen_optional_error(it.exprs[0]) + g.writeln(';') + return + } + } + + g.write('return ') + if it.exprs.len == 1 { + g.expr(it.exprs[0]) + } else { // Multi return + g.gen_array_init_values(it.exprs) + } + g.writeln(';') +} + +fn (mut g JsGen) gen_hash_stmt(it ast.HashStmt) { + g.writeln(it.val) +} + +fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { + mut name := node.name + if name.starts_with('JS.') { + return + } + if name in js.v_types && g.ns.name == 'builtin' { + return + } + js_name := g.js_name(name) + g.gen_attrs(node.attrs) + g.doc.gen_fac_fn(node.fields) + g.write('function ${js_name}({ ') + for i, field in node.fields { + g.write('$field.name = ') + if field.has_default_expr { + g.expr(field.default_expr) + } else { + g.write('${g.to_js_typ_val(field.typ)}') + } + if i < node.fields.len - 1 { + g.write(', ') + } + } + g.writeln(' }) {') + g.inc_indent() + for field in node.fields { + g.writeln('this.$field.name = $field.name') + } + g.dec_indent() + g.writeln('};') + g.writeln('${js_name}.prototype = {') + g.inc_indent() + for embed in node.embeds { + etyp := g.typ(embed.typ) + g.writeln('...${etyp}.prototype,') + } + fns := g.method_fn_decls[name] + for field in node.fields { + typ := g.typ(field.typ) + g.doc.gen_typ(typ) + g.write('$field.name: ${g.to_js_typ_val(field.typ)}') + g.writeln(',') + } + + for cfn in fns { + g.gen_method_decl(cfn, .struct_method) + g.writeln(',') + } + // gen toString method + fn_names := fns.map(it.name) + if 'toString' !in fn_names { + g.writeln('toString() {') + g.inc_indent() + g.write('return `$js_name {') + for i, field in node.fields { + if i == 0 { + g.write(' ') + } else { + g.write(', ') + } + match g.typ(field.typ).split('.').last() { + 'string' { g.write('$field.name: "\${this["$field.name"].toString()}"') } + else { g.write('$field.name: \${this["$field.name"].toString()} ') } + } + } + g.writeln('}`') + g.dec_indent() + g.writeln('},') + } + g.writeln('\$toJS() { return this; }') + g.dec_indent() + g.writeln('};\n') + if node.is_pub { + g.push_pub_var(name) + } +} + +fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) { + // NB: Fixed arrays and regular arrays are handled the same, since fixed arrays: + // 1) Are only available for number types + // 2) Give the code unnecessary complexity + // 3) Have several limitations like missing most `Array.prototype` methods + // 4) Modern engines can optimize regular arrays into typed arrays anyways, + // offering similar performance + g.write('new array(') + g.inc_indent() + + if it.has_len { + t1 := g.new_tmp_var() + t2 := g.new_tmp_var() + g.writeln('(function() {') + g.inc_indent() + g.writeln('const $t1 = [];') + g.write('for (let $t2 = 0; $t2 < ') + g.expr(it.len_expr) + g.writeln('; $t2++) {') + g.inc_indent() + g.write('${t1}.push(') + if it.has_default { + g.expr(it.default_expr) + } else { + // Fill the array with the default values for its type + t := g.to_js_typ_val(it.elem_type) + g.write(t) + } + g.writeln(');') + g.dec_indent() + g.writeln('};') + g.writeln('return $t1;') + g.dec_indent() + g.write('})()') + } else if it.is_fixed && it.exprs.len == 1 { + // [100]byte codegen + t1 := g.new_tmp_var() + t2 := g.new_tmp_var() + g.writeln('(function() {') + g.inc_indent() + g.writeln('const $t1 = [];') + g.write('for (let $t2 = 0; $t2 < ') + g.expr(it.exprs[0]) + g.writeln('; $t2++) {') + g.inc_indent() + g.write('${t1}.push(') + if it.has_default { + g.expr(it.default_expr) + } else { + // Fill the array with the default values for its type + t := g.to_js_typ_val(it.elem_type) + g.write(t) + } + g.writeln(');') + g.dec_indent() + g.writeln('};') + g.writeln('return $t1;') + g.dec_indent() + g.write('})()') + } else { + g.gen_array_init_values(it.exprs) + } + g.dec_indent() + g.write(')') +} + +fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) { + g.write('[') + for i, expr in exprs { + g.expr(expr) + if i < exprs.len - 1 { + g.write(', ') + } + } + g.write(']') +} + +fn (mut g JsGen) gen_ident(node ast.Ident) { + mut name := g.js_name(node.name) + if node.kind == .blank_ident || name in ['', '_'] { + name = g.new_tmp_var() + } + // TODO `is` + // TODO handle optionals + g.write(name) + + // TODO: Generate .val for basic types +} + +fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) { + // TODO: implement this +} + +fn (mut g JsGen) need_tmp_var_in_match(node ast.MatchExpr) bool { + if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 { + sym := g.table.get_type_symbol(node.return_type) + if sym.kind == .multi_return { + return false + } + for branch in node.branches { + if branch.stmts.len > 1 { + return true + } + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr || stmt.expr is ast.IfExpr + || stmt.expr is ast.MatchExpr || (stmt.expr is ast.IndexExpr + && (stmt.expr as ast.IndexExpr).or_expr.kind != .absent) { + return true + } + } + } + } + } + return false +} + +fn (mut g JsGen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) { + type_sym := g.table.get_type_symbol(node.cond_type) + for j, branch in node.branches { + is_last := j == node.branches.len - 1 + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if node.branches.len > 1 { + if is_expr && tmp_var.len == 0 { + // TODO too many branches. maybe separate ?: matches + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.writeln('else {') + } + } + } else { + if j > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.write('else ') + } + } + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + if j == 0 { + g.writeln('') + } + g.write_v_source_line_info(branch.pos) + g.write('if (') + } + for i, expr in branch.exprs { + if i > 0 { + g.write(' || ') + } + match type_sym.kind { + .array { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + .array_fixed { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + .map { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + .string { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + .struct_ { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + else { + if expr is ast.RangeExpr { + // if type is unsigned and low is 0, check is unneeded + mut skip_low := false + if expr.low is ast.IntegerLiteral { + if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] + && expr.low.val == '0' { + skip_low = true + } + } + g.write('(') + if !skip_low { + g.match_cond(cond_var) + g.write(' >= ') + g.expr(expr.low) + g.write(' && ') + } + g.match_cond(cond_var) + g.write(' <= ') + g.expr(expr.high) + g.write(')') + } else { + g.write('vEq(') + g.match_cond(cond_var) + g.write(',') + g.expr(expr) + g.write(')') + } + } + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + if !g.inside_ternary && node.branches.len >= 1 { + g.write('}') + } + } +} + +type MatchCond = CondExpr | CondString + +struct CondString { + s string +} + +struct CondExpr { + expr ast.Expr +} + +fn (mut g JsGen) match_cond(cond MatchCond) { + match cond { + CondString { + g.writeln(cond.s) + } + CondExpr { + g.expr(cond.expr) + } + } +} + +fn (mut g JsGen) match_expr(node ast.MatchExpr) { + if node.cond_type == 0 { + g.writeln('// match 0') + return + } + prev := g.inside_ternary + need_tmp_var := g.need_tmp_var_in_match(node) + is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary + mut cond_var := MatchCond(CondString{''}) + mut tmp_var := '' + if is_expr && !need_tmp_var { + g.inside_ternary = true + } + + if node.cond is ast.Ident || node.cond is ast.SelectorExpr || node.cond is ast.IntegerLiteral + || node.cond is ast.StringLiteral || node.cond is ast.FloatLiteral + || node.cond is ast.CallExpr { + cond_var = CondExpr{node.cond} + } else { + s := g.new_tmp_var() + cond_var = CondString{s} + g.write('let $s = ') + g.expr(node.cond) + g.writeln(';') + } + if need_tmp_var { + tmp_var = g.new_tmp_var() + g.writeln('let $tmp_var = undefined;') + } + if is_expr && !need_tmp_var { + g.write('(') + } + if node.is_sum_type { + g.match_expr_sumtype(node, is_expr, cond_var, tmp_var) + } else { + g.match_expr_classic(node, is_expr, cond_var, tmp_var) + } + if need_tmp_var { + g.write('$tmp_var') + } + if is_expr && !need_tmp_var { + g.write(')') + g.inside_ternary = prev + } +} + +fn (mut g JsGen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) { + g.inc_indent() + if g.inside_ternary { + g.write('(') + } + for i, stmt in stmts { + if i == stmts.len - 1 && tmp_var != '' { + if g.inside_if_optional { + if stmt is ast.ExprStmt { + if stmt.typ == ast.error_type_idx || stmt.expr is ast.None { + g.writeln('${tmp_var}.state = 2;') + g.write('${tmp_var}.err = ') + g.expr(stmt.expr) + g.writeln(';') + } else { + g.write('builtin.opt_ok(') + g.stmt(stmt) + g.writeln(', $tmp_var);') + } + } + } else { + g.write('$tmp_var = ') + g.stmt(stmt) + g.writeln('') + } + } else { + g.stmt(stmt) + if g.inside_if_optional && stmt is ast.ExprStmt { + g.writeln(';') + } + } + if g.inside_ternary && i < stmts.len - 1 { + g.write(',') + } + } + g.dec_indent() + if g.inside_ternary { + g.write(')') + } +} + +fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) { + for j, branch in node.branches { + mut sumtype_index := 0 + for { + is_last := j == node.branches.len - 1 + sym := g.table.get_type_symbol(node.cond_type) + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.writeln('') + g.writeln('else {') + } + } else { + if j > 0 || sumtype_index > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.write('else ') + } + } + + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + g.write('if (') + } + g.match_cond(cond_var) + if sym.kind == .sum_type { + g.write(' instanceof ') + g.expr(branch.exprs[sumtype_index]) + } else if sym.kind == .interface_ { + if branch.exprs[sumtype_index] is ast.TypeNode { + g.write(' instanceof ') + g.expr(branch.exprs[sumtype_index]) + } else { + g.write(' instanceof ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write('None__') + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + if !g.inside_ternary { + g.writeln('}') + } + sumtype_index++ + if branch.exprs.len == 0 || sumtype_index == branch.exprs.len { + break + } + } + } +} + +fn (mut g JsGen) need_tmp_var_in_if(node ast.IfExpr) bool { + if node.is_expr && g.inside_ternary { + if node.typ.has_flag(.optional) { + return true + } + + for branch in node.branches { + if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 { + return true + } + + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr { + if stmt.expr.is_method { + left_sym := g.table.get_type_symbol(stmt.expr.receiver_type) + if left_sym.kind in [.array, .array_fixed, .map] { + return true + } + } + } + } + } + } + } + return false +} + +fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { + if node.is_comptime { + g.comp_if(node) + return + } + // For simpe if expressions we can use C's `?:` + // `if x > 0 { 1 } else { 2 }` => `(x > 0) ? (1) : (2)` + // For if expressions with multiple statements or another if expression inside, it's much + // easier to use a temp var, than do C tricks with commas, introduce special vars etc + // (as it used to be done). + // Always use this in -autofree, since ?: can have tmp expressions that have to be freed. + needs_tmp_var := g.need_tmp_var_in_if(node) + tmp := if needs_tmp_var { g.new_tmp_var() } else { '' } + + if needs_tmp_var { + if node.typ.has_flag(.optional) { + g.inside_if_optional = true + } + + g.writeln('let $tmp; /* if prepend */') + } else if node.is_expr || g.inside_ternary { + g.write('(') + prev := g.inside_ternary + g.inside_ternary = true + for i, branch in node.branches { + if i > 0 { + g.write(' : ') + } + if i < node.branches.len - 1 || !node.has_else { + g.write('(') + g.expr(branch.cond) + g.write(').valueOf()') + g.write(' ? ') + } + g.stmts(branch.stmts) + } + g.inside_ternary = prev + g.write(')') + return + } + + mut is_guard := false + mut guard_idx := 0 + mut guard_vars := []string{} + + for i, branch in node.branches { + cond := branch.cond + if cond is ast.IfGuardExpr { + if !is_guard { + is_guard = true + guard_idx = i + guard_vars = []string{len: node.branches.len} + } + if cond.expr !is ast.IndexExpr && cond.expr !is ast.PrefixExpr { + var_name := g.new_tmp_var() + guard_vars[i] = var_name + g.writeln('let $var_name;') + } else { + guard_vars[i] = '' + } + } + } + + for i, branch in node.branches { + if i > 0 { + g.write('} else ') + } + // if last branch is `else {` + if i == node.branches.len - 1 && node.has_else { + g.writeln('{') + // define `err` only for simple `if val := opt {...} else {` + if is_guard && guard_idx == i - 1 { + cvar_name := guard_vars[guard_idx] + g.writeln('\tlet err = ${cvar_name}.err;') + } + } else { + match branch.cond { + ast.IfGuardExpr { + mut var_name := guard_vars[i] + mut short_opt := false + if var_name == '' { + short_opt = true // we don't need a further tmp, so use the one we'll get later + var_name = g.new_tmp_var() + guard_vars[i] = var_name // for `else` + g.tmp_count-- + g.writeln('if (${var_name}.state == 0) {') + } else { + g.write('if ($var_name = ') + g.expr(branch.cond.expr) + g.writeln(', ${var_name}.state == 0) {') + } + if short_opt || branch.cond.var_name != '_' { + if short_opt { + cond_var_name := if branch.cond.var_name == '_' { + '_dummy_${g.tmp_count + 1}' + } else { + branch.cond.var_name + } + g.write('\tlet $cond_var_name = ') + g.expr(branch.cond.expr) + g.writeln(';') + } else { + g.writeln('\tlet $branch.cond.var_name = ${var_name}.data;') + } + } + } + else { + g.write('if ((') + g.expr(branch.cond) + g.writeln(').valueOf()) {') + } + } + } + if needs_tmp_var { + g.stmts_with_tmp_var(branch.stmts, tmp) + } else { + g.stmts(branch.stmts) + } + } + g.writeln('}') + if needs_tmp_var { + g.write('$tmp') + } + if node.typ.has_flag(.optional) { + g.inside_if_optional = false + } +} + +fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { + left_typ := g.table.get_type_symbol(expr.left_type) + // TODO: Handle splice setting if it's implemented + if expr.index is ast.RangeExpr { + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.slice(') + if expr.index.has_low { + g.expr(expr.index.low) + } else { + g.write('0') + } + g.write(', ') + if expr.index.has_high { + g.expr(expr.index.high) + } else { + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.length') + } + g.write(')') + } else if left_typ.kind == .map { + g.expr(expr.left) + if expr.is_setter { + g.inside_map_set = true + g.write('.map.set(') + } else { + g.write('.map.get(') + } + g.expr(expr.index) + g.write('.toString()') + if !expr.is_setter { + g.write(')') + } + } else if left_typ.kind == .string { + if expr.is_setter { + // TODO: What's the best way to do this? + // 'string'[3] = `o` + } else { + // TODO: Maybe use u16 there? JS String returns values up to 2^16-1 + g.write('new byte(') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.str.charCodeAt(') + g.expr(expr.index) + g.write('))') + } + } else { + // TODO Does this cover all cases? + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.arr') + g.write('[+') + g.cast_stack << ast.int_type_idx + g.expr(expr.index) + g.cast_stack.delete_last() + g.write(']') + } +} + +fn (mut g JsGen) gen_deref_ptr(ty ast.Type) { + mut t := ty + for t.is_ptr() { + g.write('.val') + t = t.deref() + } +} + +fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { + l_sym := g.table.get_final_type_symbol(it.left_type) + r_sym := g.table.get_final_type_symbol(it.right_type) + + is_not := it.op in [.not_in, .not_is, .ne] + if is_not { + g.write('!(') + } + is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod, .right_shift, .left_shift, + .amp, .pipe, .xor] + + if is_arithmetic && ((l_sym.kind == .i64 || l_sym.kind == .u64) + || (r_sym.kind == .i64 || r_sym.kind == .u64)) { + // if left or right is i64 or u64 we convert them to bigint to perform operation. + greater_typ := if l_sym.kind == .i64 || l_sym.kind == .u64 { + it.left_type + } else { + it.right_type + } // g.greater_typ(it.left_type, it.right_type) + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write('${g.typ(greater_typ)}(') + g.cast_stack << greater_typ + g.write('BigInt((') + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(').\$toJS())') + g.write(' $it.op ') + g.write('BigInt((') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(').\$toJS())') + g.cast_stack.delete_last() + g.write(')') + if is_not { + g.write(')') + } + return + } + if it.op == .logical_or || it.op == .and { + if g.ns.name == 'builtin' { + g.write('new ') + } + g.write('bool(') + g.expr(it.left) + g.write('.valueOf()') + g.write(it.op.str()) + g.expr(it.right) + g.write('.valueOf()') + g.write(')') + } else if it.op == .eq || it.op == .ne { + has_operator_overloading := g.table.type_has_method(l_sym, '==') + if has_operator_overloading { + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write('.eq(') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(')') + // Shallow equatables + } else if l_sym.kind in js.shallow_equatables && r_sym.kind in js.shallow_equatables { + // wrap left expr in parens so binary operations will work correctly. + g.write('(') + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(')') + g.write('.eq(') + g.cast_stack << int(l_sym.kind) + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.cast_stack.delete_last() + g.write(')') + } else { + g.write('vEq(') + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(', ') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(')') + } + } else if l_sym.kind == .array && it.op == .left_shift { // arr << 1 + g.write('Array.prototype.push.call(') + g.expr(it.left) + mut ltyp := it.left_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.arr,') + array_info := l_sym.info as ast.Array + // arr << [1, 2] + if r_sym.kind == .array && array_info.elem_type != it.right_type { + g.write('...') + } + g.expr(it.right) + g.write(')') + } else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] { + g.expr(it.right) + + mut ltyp := it.right_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + if r_sym.kind == .map { + g.write('.map.has(') + } else if r_sym.kind == .string { + g.write('.str.includes(') + } else { + g.write('.\$includes(') + } + g.expr(it.left) + if l_sym.kind == .string { + g.write('.str') + } + g.write(')') + } else if it.op in [.key_is, .not_is] { // foo is Foo + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(' instanceof ') + g.write(g.typ(it.right_type)) + } else if it.op in [.lt, .gt, .ge, .le] && g.table.type_has_method(l_sym, '<') + && l_sym.kind == r_sym.kind { + if it.op in [.le, .ge] { + g.write('!') + } + if it.op in [.lt, .ge] { + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write('.\$lt (') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(')') + } else { + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write('.\$lt (') + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + g.write(')') + } + } else { + has_operator_overloading := g.table.type_has_method(l_sym, it.op.str()) + if has_operator_overloading { + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + name := match it.op.str() { + '+' { + '\$add' + } + '-' { + '\$sub' + } + '/' { + '\$div' + } + '*' { + '\$mul' + } + '%' { + '\$mod' + } + else { + panic('unreachable') + '' + } + } + g.write('.$name (') + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + g.write(')') + } else { + mut greater_typ := 0 + // todo(playX): looks like this cast is always required to perform .eq operation on types. + if is_arithmetic { + greater_typ = g.greater_typ(it.left_type, it.right_type) + if g.cast_stack.len > 0 { + // needs_cast = g.cast_stack.last() != greater_typ + } + } + + if is_arithmetic { + if g.ns.name == 'builtin' { + g.write('new ') + } + g.write('${g.typ(greater_typ)}(') + g.cast_stack << greater_typ + } + + g.expr(it.left) + g.gen_deref_ptr(it.left_type) + // g.write('.val') + g.write(' $it.op ') + + g.expr(it.right) + g.gen_deref_ptr(it.right_type) + // g.write('.val') + + if is_arithmetic { + g.cast_stack.delete_last() + g.write(')') + } + } + } + + if is_not { + g.write(')') + } +} + +fn (mut g JsGen) greater_typ(left ast.Type, right ast.Type) ast.Type { + l := int(left) + r := int(right) + lr := [l, r] + if ast.string_type_idx in lr { + return ast.Type(ast.string_type_idx) + } + should_float := (l in ast.integer_type_idxs && r in ast.float_type_idxs) + || (r in ast.integer_type_idxs && l in ast.float_type_idxs) + if should_float { + if ast.f64_type_idx in lr { + return ast.Type(ast.f64_type_idx) + } + if ast.f32_type_idx in lr { + return ast.Type(ast.f32_type_idx) + } + return ast.Type(ast.float_literal_type) + } + should_int := (l in ast.integer_type_idxs && r in ast.integer_type_idxs) + if should_int { + if ast.u64_type_idx in lr { + return ast.Type(ast.u64_type_idx) + } + // just guessing this order + if ast.i64_type_idx in lr { + return ast.Type(ast.i64_type_idx) + } + if ast.u32_type_idx in lr { + return ast.Type(ast.u32_type_idx) + } + if ast.int_type_idx in lr { + return ast.Type(ast.int_type_idx) + } + if ast.u16_type_idx in lr { + return ast.Type(ast.u16_type_idx) + } + if ast.i16_type_idx in lr { + return ast.Type(ast.i16_type_idx) + } + if ast.byte_type_idx in lr { + return ast.Type(ast.byte_type_idx) + } + if ast.i8_type_idx in lr { + return ast.Type(ast.i8_type_idx) + } + return ast.Type(ast.int_literal_type_idx) + } + return ast.Type(l) +} + +fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) { + // key_typ_sym := g.table.get_type_symbol(it.key_type) + // value_typ_sym := g.table.get_type_symbol(it.value_type) + // key_typ_str := util.no_dots(key_typ_sym.name) + // value_typ_str := util.no_dots(value_typ_sym.name) + g.writeln('new map(') + g.inc_indent() + if it.vals.len > 0 { + g.writeln('new Map([') + g.inc_indent() + for i, key in it.keys { + val := it.vals[i] + g.write('[') + g.expr(key) + g.write(', ') + g.expr(val) + g.write(']') + if i < it.keys.len - 1 { + g.write(',') + } + g.writeln('') + } + g.dec_indent() + g.write('])') + } else { + g.write('new Map()') + } + g.dec_indent() + g.write(')') +} + +fn (mut g JsGen) type_name(raw_type ast.Type) { + typ := raw_type + sym := g.table.get_type_symbol(typ) + mut s := '' + if sym.kind == .function { + // todo: properly print function signatures + if typ.is_ptr() { + s = '&function' + } else { + s = 'function' + } + } else { + s = g.table.type_to_str(g.unwrap_generic(typ)) + } + g.write('new builtin.string("$s")') +} + +fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { + if it.name_type > 0 { + node := it + match node.gkind_field { + .name { + g.type_name(it.name_type) + return + } + .typ { + g.write('new builtin.int(') + + g.write('${int(g.unwrap_generic(it.name_type))}') + g.write(')') + g.write(')') + return + } + .unknown { + if node.field_name == 'name' { + g.type_name(it.name_type) + return + } else if node.field_name == 'idx' { + g.write('new builtin.int(') + g.write('${int(g.unwrap_generic(it.name_type))}') + g.write(')') + return + } + panic('unknown generic field $it.pos') + } + } + } + g.expr(it.expr) + mut ltyp := it.expr_type + for ltyp.is_ptr() { + g.write('.val') + ltyp = ltyp.deref() + } + g.write('.$it.field_name') +} + +fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { + should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) + if should_cast { + if g.file.mod.name == 'builtin' { + g.write('new ') + } + g.write('string(') + } + g.write('`') + for i, val in it.vals { + escaped_val := val.replace('`', '\\`') + g.write(escaped_val) + if i >= it.exprs.len { + continue + } + expr := it.exprs[i] + fmt := it.fmts[i] + fwidth := it.fwidths[i] + precision := it.precisions[i] + g.write('\${') + if fmt != `_` || fwidth != 0 || precision != 987698 { + // TODO: Handle formatting + g.expr(expr) + } else { + sym := g.table.get_type_symbol(it.expr_types[i]) + g.expr(expr) + if sym.kind == .struct_ && sym.has_method('str') { + g.write('.str()') + } + } + g.write('}') + } + g.write('`') + if should_cast { + g.write(')') + } +} + +fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) { + mut text := it.val.replace("'", "'") + text = text.replace('"', '\\"') + should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx) + if true || should_cast { + if g.file.mod.name == 'builtin' { + g.write('new ') + } + g.write('string(') + } + if it.is_raw { + g.writeln('(function() { let s = String(); ') + for x in text { + g.writeln('s += String.fromCharCode($x);') + } + g.writeln('return s; })()') + } else { + g.write("\"$text\"") + } + if true || should_cast { + g.write(')') + } +} + +fn (mut g JsGen) gen_struct_init(it ast.StructInit) { + type_sym := g.table.get_type_symbol(it.typ) + name := type_sym.name + if it.fields.len == 0 { + g.write('new ${g.js_name(name)}({})') + } else { + g.writeln('new ${g.js_name(name)}({') + g.inc_indent() + for i, field in it.fields { + g.write('$field.name: ') + g.expr(field.expr) + if i < it.fields.len - 1 { + g.write(',') + } + g.writeln('') + } + g.dec_indent() + g.write('})') + } +} + +fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) { + sym := g.table.get_type_symbol(it.expr_type) + if sym.kind == .sum_type { + // TODO: JS sumtypes not implemented yet + } else if sym.kind == .array_fixed { + fixed_info := sym.info as ast.ArrayFixed + typ_name := g.table.get_type_name(fixed_info.elem_type) + g.write('"[$fixed_info.size]$typ_name"') + } else if sym.kind == .function { + info := sym.info as ast.FnType + fn_info := info.func + mut repr := 'fn (' + for i, arg in fn_info.params { + if i > 0 { + repr += ', ' + } + repr += g.table.get_type_name(arg.typ) + } + repr += ')' + if fn_info.return_type != ast.void_type { + repr += ' ${g.table.get_type_name(fn_info.return_type)}' + } + g.write('"$repr"') + } else { + g.write('"$sym.name"') + } +} + +fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { + is_literal := ((it.expr is ast.IntegerLiteral && it.typ in ast.integer_type_idxs) + || (it.expr is ast.FloatLiteral && it.typ in ast.float_type_idxs)) + // Skip cast if type is the same as the parrent caster + tsym := g.table.get_final_type_symbol(it.typ) + if it.expr is ast.IntegerLiteral && (tsym.kind == .i64 || tsym.kind == .u64) { + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write(tsym.kind.str()) + g.write('(BigInt(') + g.write(it.expr.val) + g.write('n))') + return + } + if g.cast_stack.len > 0 && is_literal { + if it.typ == g.cast_stack[g.cast_stack.len - 1] { + g.expr(it.expr) + return + } + } + g.cast_stack << it.typ + typ := g.typ(it.typ) + if !is_literal { + if it.typ.is_ptr() { + g.write('new \$ref(') + } + if typ !in js.v_types || g.ns.name == 'builtin' { + g.write('new ') + } + g.write('${typ}(') + } + g.expr(it.expr) + if typ == 'string' && it.expr !is ast.StringLiteral { + g.write('.toString()') + } + if !is_literal { + g.write(')') + if it.typ.is_ptr() { + g.write(')') + } + } + g.cast_stack.delete_last() +} + +fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) { + typ := ast.Type(ast.int_type) + + // Don't wrap integers for use in JS.foo functions. + // TODO: call.language always seems to be "v", parser bug? + if g.call_stack.len > 0 { + call := g.call_stack[g.call_stack.len - 1] + if call.language == .js { + for t in call.args { + if t.expr is ast.IntegerLiteral { + if t.expr == it { + g.write(it.val) + return + } + } + } + } + } + + // Skip cast if type is the same as the parrent caster + if g.cast_stack.len > 0 { + if g.cast_stack[g.cast_stack.len - 1] in ast.integer_type_idxs { + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + g.write('int($it.val)') + return + } + } + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + + g.write('${g.typ(typ)}($it.val)') +} + +fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) { + typ := ast.Type(ast.f32_type) + + // Don't wrap integers for use in JS.foo functions. + // TODO: call.language always seems to be "v", parser bug? + if g.call_stack.len > 0 { + call := g.call_stack[g.call_stack.len - 1] + if call.language == .js { + for i, t in call.args { + if t.expr is ast.FloatLiteral { + if t.expr == it { + if call.expected_arg_types[i] in ast.integer_type_idxs { + g.write(int(it.val.f64()).str()) + } else { + g.write(it.val) + } + return + } + } + } + } + } + + // Skip cast if type is the same as the parrent caster + if g.cast_stack.len > 0 { + if g.cast_stack[g.cast_stack.len - 1] in ast.float_type_idxs { + g.write('new f32($it.val)') + return + } else if g.cast_stack[g.cast_stack.len - 1] in ast.integer_type_idxs { + g.write(int(it.val.f64()).str()) + return + } + } + g.write('new ') + if g.ns.name != 'builtin' { + g.write('builtin.') + } + + g.write('${g.typ(typ)}($it.val)') +} + +fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type { + if typ.has_flag(.generic) { + if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names, + g.table.cur_concrete_types) + { + return t_typ + } + } + return typ +} diff --git a/v_windows/v/vlib/v/gen/js/jsdoc.v b/v_windows/v/vlib/v/gen/js/jsdoc.v new file mode 100644 index 0000000..359687e --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/jsdoc.v @@ -0,0 +1,96 @@ +module js + +import v.ast + +struct JsDoc { +mut: + gen &JsGen +} + +fn new_jsdoc(gen &JsGen) &JsDoc { + return &JsDoc{ + gen: gen + } +} + +fn (mut d JsDoc) write(s string) { + if !d.gen.enable_doc { + return + } + d.gen.write(s) +} + +fn (mut d JsDoc) writeln(s string) { + if !d.gen.enable_doc { + return + } + d.gen.writeln(s) +} + +fn (mut d JsDoc) gen_typ(typ string) { + d.writeln('/** @type {$typ} */') +} + +fn (mut d JsDoc) gen_const(typ string) { + d.writeln('/** @constant {$typ} */') +} + +fn (mut d JsDoc) gen_enum() { + // Enum values can only be ints for now + typ := 'number' + d.writeln('/** @enum {$typ} */') +} + +fn (mut d JsDoc) gen_fac_fn(fields []ast.StructField) { + d.writeln('/**') + d.writeln(' * @constructor') + d.write(' * @param {{') + for i, field in fields { + // Marked as optional: structs have default default values, + // so all struct members don't have to be initialized. + d.write('$field.name?: ${d.gen.typ(field.typ)}') + if i < fields.len - 1 { + d.write(', ') + } + } + d.writeln('}} init') + d.writeln('*/') +} + +fn (mut d JsDoc) gen_fn(it ast.FnDecl) { + type_name := d.gen.typ(it.return_type) + d.writeln('/**') + d.writeln(' * @function') + if it.is_deprecated { + d.writeln(' * @deprecated') + } + for i, arg in it.params { + if (it.is_method || it.receiver.typ == 0) && i == 0 { + continue + } + arg_type_name := d.gen.typ(arg.typ) + is_varg := i == it.params.len - 1 && it.is_variadic + name := d.gen.js_name(arg.name) + if is_varg { + d.writeln(' * @param {...$arg_type_name} $name') + } else { + d.writeln(' * @param {$arg_type_name} $name') + } + } + d.writeln(' * @returns {$type_name}') + d.writeln('*/') +} + +fn (mut d JsDoc) gen_interface(it ast.InterfaceDecl) { + name := d.gen.js_name(it.name) + d.writeln('/**') + d.writeln(' * @interface $name') + d.writeln(' * @typedef $name') + for method in it.methods { + // Skip receiver + typ := d.gen.fn_typ(method.params[1..], method.return_type) + method_name := d.gen.js_name(method.name) + d.writeln(' * @property {$typ} $method_name') + } + d.writeln(' */\n') +} diff --git a/v_windows/v/vlib/v/gen/js/jsgen_test.v b/v_windows/v/vlib/v/gen/js/jsgen_test.v new file mode 100644 index 0000000..d3033e0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/jsgen_test.v @@ -0,0 +1,86 @@ +import os + +const ( + test_dir = os.join_path('vlib', 'v', 'gen', 'js', 'tests') + output_dir = os.join_path(os.temp_dir(), '_js_tests/') + v_options = '-b js -w' + node_options = '' +) + +fn testsuite_end() { + os.rmdir_all(output_dir) or {} +} + +const there_is_node_available = is_nodejs_working() + +const there_is_grep_available = is_grep_working() + +fn test_example_compilation() { + vexe := os.getenv('VEXE') + os.chdir(os.dir(vexe)) or {} + os.mkdir_all(output_dir) or { panic(err) } + files := find_test_files() + for file in files { + path := os.join_path(test_dir, file) + println('Testing $file') + mut v_options_file := v_options + mut node_options_file := node_options + should_create_source_map := file.ends_with('_sourcemap.v') + if should_create_source_map { + println('activate -sourcemap creation') + v_options_file += ' -sourcemap' // activate souremap generation + + println('add node option: --enable-source-maps') // requieres node >=12.12.0 + node_options_file += ' --enable-source-maps' // activate souremap generation + } + v_code := os.system('$vexe $v_options_file -o $output_dir${file}.js $path') + if v_code != 0 { + assert false + } + // Compilation failed + assert v_code == 0 + if !there_is_node_available { + println(' ... skipping running $file, there is no NodeJS present') + continue + } + js_code := os.system('node $output_dir${file}.js') + if js_code != 0 { + assert false + } + // Running failed + assert js_code == 0 + if should_create_source_map { + if there_is_grep_available { + grep_code_sourcemap_found := os.system('grep -q -E "//#\\ssourceMappingURL=data:application/json;base64,[-A-Za-z0-9+/=]+$" $output_dir${file}.js') + assert grep_code_sourcemap_found == 0 + println('file has a source map embeded') + } else { + println(' ... skipping testing for sourcemap $file, there is no grep present') + } + } + } +} + +fn find_test_files() []string { + files := os.ls(test_dir) or { panic(err) } + // The life example never exits, so tests would hang with it, skip + mut tests := files.filter(it.ends_with('.v')).filter(it != 'life.v') + tests.sort() + return tests +} + +fn is_nodejs_working() bool { + node_res := os.execute('node --version') + if node_res.exit_code != 0 { + return false + } + return true +} + +fn is_grep_working() bool { + node_res := os.execute('grep --version') + if node_res.exit_code != 0 { + return false + } + return true +} diff --git a/v_windows/v/vlib/v/gen/js/program_test.v b/v_windows/v/vlib/v/gen/js/program_test.v new file mode 100644 index 0000000..9682732 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/program_test.v @@ -0,0 +1,98 @@ +import os +import term +import rand +import v.util.vtest +import v.util.diff + +const vexe = @VEXE + +const vroot = @VMODROOT + +const diff_cmd = find_diff_cmd() + +fn find_diff_cmd() string { + return diff.find_working_diff_command() or { '' } +} + +[noreturn] +fn exit_because(msg string) { + eprintln('$msg, tests will not run') + exit(0) +} + +fn test_node_exists() { + res := os.execute('node --version') + if res.exit_code != 0 { + exit_because('node does not exist') + } + if !res.output.starts_with('v') { + exit_because('invalid node version') + } + version := res.output.trim_left('v').int() + if version < 10 { + exit_because('node should be at least version 10, but is currently version: $version') + } + println('Using node version: $version') +} + +fn test_running_programs_compiled_with_the_js_backend() ? { + os.setenv('VCOLORS', 'never', true) + os.chdir(vroot) or {} + test_dir := 'vlib/v/gen/js/tests/testdata' + main_files := get_main_files_in_dir(test_dir) + fails := check_path(test_dir, main_files) ? + assert fails == 0 +} + +fn get_main_files_in_dir(dir string) []string { + mut mfiles := os.walk_ext(dir, '.v') + mfiles.sort() + return mfiles +} + +fn check_path(dir string, tests []string) ?int { + mut nb_fail := 0 + paths := vtest.filter_vtest_only(tests, basepath: vroot) + for path in paths { + program := path.replace(vroot + os.path_separator, '') + program_out := program.replace('.v', '.out') + if !os.exists(program_out) { + os.write_file(program_out, '') ? + } + print(program + ' ') + res := os.execute('$vexe -b js_node run $program') + if res.exit_code < 0 { + panic(res.output) + } + mut expected := os.read_file(program_out) ? + expected = clean_line_endings(expected) + found := clean_line_endings(res.output) + if expected != found { + println(term.red('FAIL')) + println('============') + println('expected $program_out content:') + println(expected) + println('============') + println('found:') + println(found) + println('============\n') + println('diff:') + println(diff.color_compare_strings(diff_cmd, rand.ulid(), found, expected)) + println('============\n') + nb_fail++ + } else { + println(term.green('OK')) + assert true + } + } + return nb_fail +} + +fn clean_line_endings(s string) string { + mut res := s.trim_space() + res = res.replace(' \n', '\n') + res = res.replace(' \r\n', '\n') + res = res.replace('\r\n', '\n') + res = res.trim('\n') + return res +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/basic_test.v b/v_windows/v/vlib/v/gen/js/sourcemap/basic_test.v new file mode 100644 index 0000000..fc61b55 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/basic_test.v @@ -0,0 +1,158 @@ +module sourcemap + +fn test_simple() { + mut sg := generate_empty_map() + mut sm := sg.add_map('hello.js', '/', true, 0, 0) + sm.set_source_content('hello.v', "fn main(){nprintln('Hello World! Helo \$a')\n}") + + mlist := [ + MappingInput{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 0 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 2 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 9 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 7 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 10 + } + name: 'hello_name' + source_position: SourcePosition{ + source_line: 1 + source_column: 8 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 13 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 14 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 12 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 27 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 28 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 2 + gen_column: 29 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + MappingInput{ + GenPosition: GenPosition{ + gen_line: 3 + gen_column: 0 + } + name: '' + source_position: SourcePosition{ + source_line: 1 + source_column: 0 + } + }, + ] + sm.add_mapping_list('hello.v', mlist) or { panic('x') } + + json_data := sm.to_json() + + expected := '{"version":3,"file":"hello.js","sourceRoot":"\\/","sources":["hello.v"],"sourcesContent":["fn main(){nprintln(\'Hello World! Helo \$a\')\\n}"],"names":["hello_name"],"mappings":"AAAA;AAAA,EAAA,OAAO,CAACA,GAAR,CAAY,aAAZ,CAAA,CAAA;AAAA"}' + assert json_data.str() == expected +} + +fn test_source_null() { + mut sg := generate_empty_map() + mut sm := sg.add_map('hello.js', '/', true, 0, 0) + sm.add_mapping('hello.v', SourcePosition{ + source_line: 0 + source_column: 0 + }, 1, 1, '') + sm.add_mapping('hello_lib1.v', SourcePosition{ + source_line: 0 + source_column: 0 + }, 2, 1, '') + sm.add_mapping('hello_lib2.v', SourcePosition{ + source_line: 0 + source_column: 0 + }, 3, 1, '') + json_data := sm.to_json() + + expected := '{"version":3,"file":"hello.js","sourceRoot":"\\/","sources":["hello.v","hello_lib1.v","hello_lib2.v"],"sourcesContent":[null,null,null],"names":[],"mappings":"CA+\\/\\/\\/\\/\\/HA;CCAA;CCAA"}' + assert json_data.str() == expected +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/compare_test.v b/v_windows/v/vlib/v/gen/js/sourcemap/compare_test.v new file mode 100644 index 0000000..0a755be --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/compare_test.v @@ -0,0 +1,322 @@ +module sourcemap + +fn test_cmp_eq() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: IndexNumber(3) + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: IndexNumber(3) + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert !compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_name() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: IndexNumber(3) + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: IndexNumber(4) + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_name_empty() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: IndexNumber(3) + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_name_empty_empty() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert !compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_source_position_empty_eq() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: Empty{} + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: Empty{} + } + + assert !compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_source_position_empty_diff() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: Empty{} + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_source_position_column_diff() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 99 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_source_position_line_diff() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 88 + source_column: 99 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_sources() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 99 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_gen_column() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 99 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} + +fn test_cmp_gen_line() { + a := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 0 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + b := Mapping{ + GenPosition: GenPosition{ + gen_line: 1 + gen_column: 99 + } + sources_ind: 2 + names_ind: Empty{} + source_position: SourcePosition{ + source_line: 4 + source_column: 5 + } + } + + assert compare_by_generated_positions_inflated(a, b) +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/mappings.v b/v_windows/v/vlib/v/gen/js/sourcemap/mappings.v new file mode 100644 index 0000000..d7aff13 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/mappings.v @@ -0,0 +1,170 @@ +module sourcemap + +import v.gen.js.sourcemap.vlq +import io + +struct Empty {} + +pub struct SourcePosition { + source_line u32 + source_column u32 +} + +type IndexNumber = u32 +type SourcePositionType = Empty | SourcePosition +type NameIndexType = Empty | IndexNumber + +struct GenPosition { + gen_line u32 + gen_column u32 +} + +struct MappingInput { + GenPosition + name string + source_position SourcePositionType +} + +struct Mapping { + GenPosition + sources_ind u32 + names_ind NameIndexType + source_position SourcePositionType +} + +struct Mappings { +mut: + sorted bool + last Mapping + values []Mapping +} + +fn new_mappings() Mappings { + return Mappings{ + last: Mapping{ + GenPosition: GenPosition{ + gen_column: 0 + gen_line: 0 + } + } + sorted: true + } +} + +// Add the given source mapping +fn (mut m Mappings) add_mapping(gen_line u32, gen_column u32, sources_ind u32, source_position SourcePositionType, names_ind NameIndexType) { + if !(gen_line > m.last.gen_line + || (gen_line == m.last.gen_line && gen_column >= m.last.gen_column)) { + m.sorted = false + } + m.values << Mapping{ + GenPosition: GenPosition{ + gen_line: gen_line + gen_column: gen_column + } + sources_ind: sources_ind + names_ind: names_ind + source_position: source_position + } +} + +// Returns the flat, sorted array of mappings. The mappings are sorted by generated position. + +fn (mut m Mappings) get_sorted_array() []Mapping { + if !m.sorted { + panic('not implemented') + } + return m.values +} + +fn (mut m Mappings) export_mappings(mut output io.Writer) ? { + mut previous_generated_line := u32(1) + mut previous_generated_column := u32(0) + mut previous_source_index := i64(0) + mut previous_source_line := i64(0) + mut previous_source_column := i64(0) + mut previous_name_index := i64(0) + + line_mappings := m.get_sorted_array() + len := line_mappings.len + for i := 0; i < len; i++ { + mapping := line_mappings[i] + + cloned_generated_line := mapping.gen_line + if cloned_generated_line > 0 { + // Write a ';' for each line between this and last line, way more efficient than storing empty lines or looping... + output.write(';'.repeat(int(cloned_generated_line - previous_generated_line)).bytes()) or { + panic('Writing vql failed!') + } + } + if cloned_generated_line != previous_generated_line { + previous_generated_column = 0 + previous_generated_line = cloned_generated_line + } else { + if i > 0 { + if !compare_by_generated_positions_inflated(mapping, line_mappings[i - 1]) { + continue + } + output.write(','.bytes()) or { panic('Writing vql failed!') } + } + } + + vlq.encode(i64(mapping.gen_column - previous_generated_column), mut &output) ? + previous_generated_column = mapping.gen_column + match mapping.source_position { + Empty {} + SourcePosition { + vlq.encode(i64(mapping.sources_ind - previous_source_index), mut &output) ? + previous_source_index = mapping.sources_ind + // lines are stored 0-based in SourceMap spec version 3 + vlq.encode(i64(mapping.source_position.source_line - 1 - previous_source_line), mut + output) ? + previous_source_line = mapping.source_position.source_line - 1 + vlq.encode(i64(mapping.source_position.source_column - previous_source_column), mut + output) ? + previous_source_column = mapping.source_position.source_column + + match mapping.names_ind { + Empty {} + IndexNumber { + vlq.encode(i64(mapping.names_ind - previous_name_index), mut &output) ? + previous_name_index = mapping.names_ind + } + } + } + } + } +} + +fn compare_by_generated_positions_inflated(mapping_a Mapping, mapping_b Mapping) bool { + if mapping_a.gen_line != mapping_b.gen_line { + return true + } + if mapping_a.gen_column != mapping_b.gen_column { + return true + } + + if mapping_a.sources_ind != mapping_b.sources_ind { + return true + } + + if mapping_a.source_position.type_name() == mapping_b.source_position.type_name() + && mapping_a.source_position is SourcePosition + && mapping_b.source_position is SourcePosition { + if mapping_a.source_position.source_line != mapping_b.source_position.source_line + || mapping_a.source_position.source_column != mapping_b.source_position.source_column { + return true + } + } else { + if mapping_a.source_position.type_name() != mapping_b.source_position.type_name() { + return true + } + } + + if mapping_a.names_ind.type_name() == mapping_b.names_ind.type_name() + && mapping_a.names_ind is IndexNumber && mapping_b.names_ind is IndexNumber { + return mapping_a.names_ind != mapping_b.names_ind + } else { + return mapping_a.names_ind.type_name() != mapping_b.names_ind.type_name() + } +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/sets.v b/v_windows/v/vlib/v/gen/js/sourcemap/sets.v new file mode 100644 index 0000000..70f7482 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/sets.v @@ -0,0 +1,16 @@ +module sourcemap + +struct Sets { +mut: + value map[string]u32 +} + +// adds a new element to a Set if new and returns index position of new or existing element +fn (mut s Sets) add(element string) u32 { + index := s.value[element] or { + index := u32(s.value.len) + s.value[element] = index + return index + } + return index +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/source_map.v b/v_windows/v/vlib/v/gen/js/sourcemap/source_map.v new file mode 100644 index 0000000..44f79d1 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/source_map.v @@ -0,0 +1,131 @@ +module sourcemap + +import io +import os +import x.json2 + +const ( + source_map_version = 3 +) + +type SourceMapJson = map[string]json2.Any + +struct SourceMap { +pub mut: + version int [json: version] + file string [json: file] + source_root string [json: source_root] + sources Sets [json: sources] + sources_content map[string]string + names Sets + mappings Mappings + sources_content_inline bool +} + +struct StringWriter { +pub mut: + bytes []byte +} + +pub fn new_sourcemap(file string, source_root string, sources_content_inline bool) SourceMap { + return SourceMap{ + version: sourcemap.source_map_version + file: file + source_root: source_root + mappings: new_mappings() + sources_content_inline: sources_content_inline + } +} + +// Add a single mapping from original source line and column to the generated source's line and column for this source map being created. +pub fn (mut sm SourceMap) add_mapping(source_name string, source_position SourcePositionType, gen_line u32, gen_column u32, name string) { + assert source_name.len != 0 + + sources_ind := sm.sources.add(source_name) + + names_ind := if name.len != 0 { + NameIndexType(IndexNumber(sm.names.add(name))) + } else { + NameIndexType(Empty{}) + } + sm.mappings.add_mapping(gen_line, gen_column, sources_ind, source_position, names_ind) +} + +// Add multiple mappings from the same source +pub fn (mut sm SourceMap) add_mapping_list(source_name string, mapping_list []MappingInput) ? { + assert source_name.len != 0 + + sources_ind := sm.sources.add(source_name) + + for mapping in mapping_list { + names_ind := if mapping.name.len != 0 { + NameIndexType(IndexNumber(sm.names.add(mapping.name))) + } else { + NameIndexType(Empty{}) + } + sm.mappings.add_mapping(mapping.gen_line, mapping.gen_column, sources_ind, mapping.source_position, + names_ind) + } +} + +// Set the source content for a source file. +pub fn (mut sm SourceMap) set_source_content(source_name string, source_content string) { + sm.sources_content[source_name] = source_content +} + +fn (mut sm SourceMap) export_mappings(mut writer io.Writer) { + sm.mappings.export_mappings(mut writer) or { panic('export failed') } +} + +fn (mut sm SourceMap) export_mappings_string() string { + mut output := StringWriter{} + + sm.mappings.export_mappings(mut output) or { panic('export failed') } + return output.bytes.bytestr() +} + +// create a JSON representing the sourcemap +// Sourcemap Specs http://sourcemaps.info/spec.html +pub fn (mut sm SourceMap) to_json() SourceMapJson { + mut source_map_json := map[string]json2.Any{} + source_map_json['version'] = sm.version + if sm.file != '' { + source_map_json['file'] = json2.Any(sm.file) + } + if sm.source_root != '' { + source_map_json['sourceRoot'] = json2.Any(sm.source_root) + } + mut sources_json := []json2.Any{} + mut sources_content_json := []json2.Any{} + for source_file, _ in sm.sources.value { + sources_json << source_file + if source_file in sm.sources_content { + sources_content_json << sm.sources_content[source_file] + } else { + if sm.sources_content_inline { + if source_file_content := os.read_file(source_file) { + sources_content_json << source_file_content + } else { + sources_content_json << json2.null + } + } else { + sources_content_json << json2.null + } + } + } + source_map_json['sources'] = json2.Any(sources_json) + source_map_json['sourcesContent'] = json2.Any(sources_content_json) + + mut names_json := []json2.Any{} + for name, _ in sm.names.value { + names_json << name + } + source_map_json['names'] = json2.Any(names_json) + source_map_json['mappings'] = sm.export_mappings_string() + return source_map_json +} + +fn (mut w StringWriter) write(buf []byte) ?int { + w.bytes << buf + return buf.len +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/source_map_generator.v b/v_windows/v/vlib/v/gen/js/sourcemap/source_map_generator.v new file mode 100644 index 0000000..f943286 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/source_map_generator.v @@ -0,0 +1,46 @@ +module sourcemap + +struct V3 { + SourceMap +pub: + sections []Section [json: sections] +} + +struct Offset { +pub mut: + line int [json: line] + column int [json: column] +} + +struct Section { +pub mut: + offset Offset [json: offset] + source_map SourceMap [json: map] +} + +struct Generator { +mut: + file string + // source_root string + sections []Section +} + +pub fn generate_empty_map() &Generator { + return &Generator{} +} + +pub fn (mut g Generator) add_map(file string, source_root string, sources_content_inline bool, line_offset int, column_offset int) &SourceMap { + source_map := new_sourcemap(file, source_root, sources_content_inline) + + offset := Offset{ + line: line_offset + column: column_offset + } + + g.sections << Section{ + offset: offset + source_map: source_map + } + + return &source_map +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq.v b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq.v new file mode 100644 index 0000000..7dfef05 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq.v @@ -0,0 +1,115 @@ +module vlq + +import io + +const ( + shift = byte(5) + mask = byte((1 << shift) - 1) + continued = byte(1 << shift) + max_i64 = u64(9223372036854775807) + + // index start is: byte - vlq.enc_char_special_plus + enc_index = [62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]! + + enc_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + + enc_char_start_au = 65 + enc_char_end_zu = 90 + enc_char_start_al = 97 + enc_char_end_zl = 122 + enc_char_start_zero = 48 + enc_char_end_nine = 57 + enc_char_special_plus = 43 + enc_char_special_slash = 47 +) + +[inline] +fn abs64(x i64) u64 { + return if x < 0 { u64(-x) } else { u64(x) } +} + +// Decode a single base64 digit. +[inline] +fn decode64(input byte) byte { + $if debug { + assert input >= vlq.enc_char_special_plus + assert input <= vlq.enc_char_end_zl + } + return byte(vlq.enc_index[input - vlq.enc_char_special_plus]) +} + +// Decode a single VLQ value from the input stream, returning the value. +// +// # Range +// +// Supports all numbers that can be represented by a sign bit and a 63 bit +// absolute value: `[-(2^63 - 1), 2^63 - 1]`. +// +// Note that `i64::MIN = -(2^63)` cannot be represented in that form, and this +// NOT IMPLEMENTED: function will return `Error::Overflowed` when attempting to decode it. +pub fn decode(mut input io.Reader) ?i64 { + mut buf := []byte{len: 1} + + mut accum := u64(0) + mut shifter := 0 + mut digit := byte(0) + + mut keep_going := true + for keep_going { + len := input.read(mut buf) or { return error('Unexpected EOF') } + if len == 0 { + return error('no content') + } + digit = decode64(buf[0]) + keep_going = (digit & vlq.continued) != 0 + + digit_value := u64(digit & vlq.mask) << u32(shifter) // TODO: check Overflow + + accum += digit_value + shifter += vlq.shift + } + + abs_value := accum / 2 + if abs_value > vlq.max_i64 { + return error('Overflow') + } + + // The low bit holds the sign. + return if (accum & 1) != 0 { (-i64(abs_value)) } else { i64(abs_value) } +} + +[inline] +fn encode64(input byte) byte { + $if debug { + assert input < 64 + } + return vlq.enc_table[input] +} + +// Encode a value as Base64 VLQ, sending it to the writer +pub fn encode(value i64, mut output io.Writer) ? { + signed := value < 0 + mut value_u64 := abs64(value) << 1 + if signed { + if value_u64 == 0 { + // Wrapped + value_u64 = vlq.max_i64 + 1 + } + value_u64 |= 1 + } + for { + mut digit := byte(value_u64) & vlq.mask + value_u64 >>= vlq.shift + if value_u64 > 0 { + digit |= vlq.continued + } + bytes := [encode64(digit)] + output.write(bytes) or { return error('Write failed') } + if value_u64 == 0 { + break + } + } +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_decode_test.v b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_decode_test.v new file mode 100644 index 0000000..fdb4acf --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_decode_test.v @@ -0,0 +1,52 @@ +module vlq + +import io + +struct TestReader { +pub: + bytes []byte +mut: + i int +} + +struct TestData { + decode_val string + expected i64 +} + +type TestDataList = []TestData + +fn test_decode_a() ? { + decode_values := [ + TestData{'A', 0}, + TestData{'C', 1}, + TestData{'D', -1}, + TestData{'2H', 123}, + TestData{'qxmvrH', 123456789}, + TestData{'+/////B', 1073741823} /* 2^30-1 */, + // TestData{'hgggggggggggI', 9_223_372_036_854_775_808} /* 2^63 */, + ] + + for _, test_data in decode_values { + mut input := make_test_reader(test_data.decode_val) + + res := decode(mut &input) ? + assert res == test_data.expected + } +} + +fn (mut b TestReader) read(mut buf []byte) ?int { + if !(b.i < b.bytes.len) { + return none + } + n := copy(buf, b.bytes[b.i..]) + b.i += n + return n +} + +fn make_test_reader(data string) io.Reader { + buf := &TestReader{ + bytes: data.bytes() + } + return io.new_buffered_reader(reader: buf) +} diff --git a/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_encode_test.v b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_encode_test.v new file mode 100644 index 0000000..ad2db3b --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/sourcemap/vlq/vlq_encode_test.v @@ -0,0 +1,35 @@ +module vlq + +struct TestData { + expected string + data_val i64 +} + +struct TestWriter { +pub mut: + bytes []byte +} + +fn test_encode_a() ? { + decode_values := [ + TestData{'A', 0}, + TestData{'C', 1}, + TestData{'D', -1}, + TestData{'2H', 123}, + TestData{'qxmvrH', 123456789}, + TestData{'+/////B', 1073741823} /* 2^30-1 */, + // TestData{'hgggggggggggI', 9_223_372_036_854_775_808} /* 2^63 */, + ] + for _, test_data in decode_values { + mut output := TestWriter{} + + encode(test_data.data_val, mut &output) ? + // dump(output.bytes) + assert output.bytes == test_data.expected.bytes() + } +} + +fn (mut w TestWriter) write(buf []byte) ?int { + w.bytes << buf + return buf.len +} diff --git a/v_windows/v/vlib/v/gen/js/str.v b/v_windows/v/vlib/v/gen/js/str.v new file mode 100644 index 0000000..ba963b1 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/str.v @@ -0,0 +1,85 @@ +module js + +import v.ast + +fn (mut g JsGen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + mut typ := etype + if etype.has_flag(.shared_f) { + typ = typ.clear_flag(.shared_f).set_nr_muls(0) + } + + mut sym := g.table.get_type_symbol(typ) + + if mut sym.info is ast.Alias { + parent_sym := g.table.get_type_symbol(sym.info.parent_type) + if parent_sym.has_method('str') { + typ = sym.info.parent_type + sym = parent_sym + } + } + + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + if typ.has_flag(.variadic) { + // todo(playX): generate str method just like in the C backend + g.write('new string(') + g.expr(expr) + g.write('.valueOf()') + g.write('.toString())') + } else if typ == ast.string_type { + g.expr(expr) + } else if typ == ast.bool_type { + g.write('new string(') + g.expr(expr) + g.write('.valueOf() ? "true" : "false")') + } else if sym.kind == .none_ { + g.write('new string("")') + } else if sym.kind == .enum_ { + g.write('new string(') + if expr !is ast.EnumVal { + g.expr(expr) + g.write('.valueOf()') + g.write('.toString()') + } else { + g.write('"') + g.expr(expr) + g.write('"') + } + g.write(')') + } else if sym.kind == .interface_ && sym_has_str_method { + is_ptr := typ.is_ptr() + g.write(sym.mod.replace_once('${g.ns.name}.', '')) + g.write('.') + g.write(sym.name) + g.write('.prototype.str.call(') + g.expr(expr) + if !str_method_expects_ptr && is_ptr { + g.gen_deref_ptr(typ) + } + g.write(')') + } + //|| sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return,.sum_type, .interface_] + else if sym_has_str_method { + g.write('new string(') + g.write('Object.getPrototypeOf(/*str exists*/') + g.expr(expr) + is_ptr := typ.is_ptr() + g.gen_deref_ptr(typ) + g.write(').str.call(') + g.expr(expr) + if !str_method_expects_ptr && is_ptr { + g.gen_deref_ptr(typ) + } + + g.write('))') + } else if sym.kind == .struct_ && !sym_has_str_method { + g.write('new string(') + g.expr(expr) + g.gen_deref_ptr(typ) + g.write('.toString())') + } else { + g.write('new string(') + g.expr(expr) + g.gen_deref_ptr(typ) + g.write('.valueOf().toString())') + } +} diff --git a/v_windows/v/vlib/v/gen/js/temp_fast_deep_equal.v b/v_windows/v/vlib/v/gen/js/temp_fast_deep_equal.v new file mode 100644 index 0000000..a22d2d8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/temp_fast_deep_equal.v @@ -0,0 +1,89 @@ +module js + +// TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') +const ( + fast_deep_eq_fn = "// https://www.npmjs.com/package/fast-deep-equal - 3/3/2021 +const envHasBigInt64Array = typeof BigInt64Array !== 'undefined'; +function vEq(a, b) { + if (a === b) return true; + + if (a && b && typeof a == 'object' && typeof b == 'object') { + if (a.constructor !== b.constructor) return false; + // we want to convert all V types to JS for comparison. + if ('\$toJS' in a) + a = a.\$toJS(); + + if ('\$toJS' in b) + b = b.\$toJS(); + + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (!vEq(a[i], b[i])) return false; + return true; + } + + + if ((a instanceof Map) && (b instanceof Map)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + for (i of a.entries()) + if (!vEq(i[1], b.get(i[0]))) return false; + return true; + } + + if ((a instanceof Set) && (b instanceof Set)) { + if (a.size !== b.size) return false; + for (i of a.entries()) + if (!b.has(i[0])) return false; + return true; + } + + if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0;) + if (a[i] !== b[i]) return false; + return true; + } + + + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + + for (i = length; i-- !== 0;) + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + + for (i = length; i-- !== 0;) { + var key = keys[i]; + + if (!vEq(a[key], b[key])) return false; + } + + return true; + } + + // true if both NaN, false otherwise + return a!==a && b!==b; +}; + +function \$sortComparator(a, b) +{ +a = a.\$toJS(); +b = b.\$toJS(); +if (a > b) return 1; +if (a < b) return -1; +return 0; + + +} +" +) diff --git a/v_windows/v/vlib/v/gen/js/tests/.gitignore b/v_windows/v/vlib/v/gen/js/tests/.gitignore new file mode 100644 index 0000000..4c43fe6 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/.gitignore @@ -0,0 +1 @@ +*.js \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/array.v b/v_windows/v/vlib/v/gen/js/tests/array.v new file mode 100644 index 0000000..b8f1f2d --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/array.v @@ -0,0 +1,136 @@ +fn map_cb(s string) string { + return 'CB: $s' +} + +fn filter_cb(n int) bool { + return n < 4 +} + +fn variadic(args ...int) { + println(args) + println(args[0]) + println(args[1]) +} + +fn vararg_test() { + variadic(1, 2, 3) +} + +// TODO Remove `fn main` once vet supports scripts +fn main() { + vararg_test() + + arr1 := ['Hello', 'JS', 'Backend'] + mut arr2 := [1, 2, 3, 4, 5] + + // Array slices + slice1 := arr1[1..3] + slice2 := arr2[..3] + slice3 := arr2[3..] + + // Array indexes + idx1 := slice1[1] + arr2[0] = 1 + arr2[0 + 1] = 2 + println(arr2) + + // TODO: This does not work for now + // arr2[0..1] = arr2[3..4] + // println(arr2) + + // Array push operator + arr2 << 6 + arr2 << [7, 8, 9] + println(arr2) + println('\n\n') + + // String slices + mut slice4 := idx1[..4] + print('Back\t=> ') + println(slice4) // 'Back' + + // String indexes + idx2 := slice4[0] + print('66\t=> ') + println(idx2) + // TODO: + // slice4[3] = `c` + + // Maps + mut m := map[string]string{} + key := 'key' + m[key] = 'value' + val := m['key'] + print('value\t=> ') + println(val) + + // 'in' / '!in' + print('true\t=> ') + println('JS' in arr1) + print('false\t=> ') + println(3 !in arr2) + print('true\t=> ') + println('key' in m) + print('true\t=> ') + println('badkey' !in m) + + // for in + for _ in arr1 {} + println('0 to 8\t=>') + for i, _ in arr2 { + println(i) + } + println('\n\n4 to 5\t=> ') + for _, v in slice3 { + println(v) + } + + println(int(1.5)) + + println('\n\n') + + // map + a := arr1.map('VAL: $it') + b := arr1.map(map_cb) + c := arr1.map(map_cb(it)) + d := arr1.map(fn (a string) string { + return 'ANON: $a' + }) + // I don't know when this would ever be used, + // but it's what the C backend does ¯\_(ツ)_/¯ + e := arr1.map(456) + + println(a) + println(b) + println(c) + println(d) + println(e) + + println('\n\n') + + // filter + aa := arr2.filter(it < 4) + bb := arr2.filter(filter_cb) + cc := arr2.filter(filter_cb(it)) + dd := arr2.filter(fn (a int) bool { + return a < 4 + }) + + println(aa) + println(bb) + println(cc) + println(dd) + + // fixed arrays: implemented as normal arrays + f1 := [1, 2, 3, 4, 5]! + mut f2 := [8]f32{} + f2[0] = f32(1.23) + f3 := ['foo', 'bar']! + f4 := [u64(0xffffffffffffffff), 0xdeadface]! + + println(' +$f1 +$f2 +$f3 +$f4') +} diff --git a/v_windows/v/vlib/v/gen/js/tests/auto_deref_args.v b/v_windows/v/vlib/v/gen/js/tests/auto_deref_args.v new file mode 100644 index 0000000..a9775e7 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/auto_deref_args.v @@ -0,0 +1,13 @@ +struct Foo { + field &int +} + +fn bar(x int) { + println(x) +} + +fn main() { + x := 4 + foo := Foo{&x} + bar(foo.field) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/enum.v b/v_windows/v/vlib/v/gen/js/tests/enum.v new file mode 100644 index 0000000..2d1bfc0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/enum.v @@ -0,0 +1,19 @@ +import v.gen.js.tests.hello + +enum Test { + foo = 2 + bar = 5 + baz +} + +// TODO Remove `fn main` once vet supports scripts +fn main() { + mut a := hello.Ccc.a + a = .b + a = .c + println(a) + + mut b := Test.foo + b = .bar + println(b) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/hello/hello.js.v b/v_windows/v/vlib/v/gen/js/tests/hello/hello.js.v new file mode 100644 index 0000000..0b3fe40 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/hello/hello.js.v @@ -0,0 +1,5 @@ +module hello + +pub fn raw_js_log() { + #console.log('hello') +} diff --git a/v_windows/v/vlib/v/gen/js/tests/hello/hello.v b/v_windows/v/vlib/v/gen/js/tests/hello/hello.v new file mode 100644 index 0000000..9b75511 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/hello/hello.v @@ -0,0 +1,33 @@ +module hello + +import v.gen.js.tests.hello.hello1 + +pub const ( + hello = 'Hello' +) + +pub struct Aaa { +pub mut: + foo string +} + +pub fn (mut a Aaa) update(s string) { + a.foo = s +} + +struct Bbb {} + +pub enum Ccc { + a + b = 5 + c +} + +pub fn debugger() string { + v := Bbb{} + return hello.hello +} + +pub fn excited() string { + return '$hello1.nested() $debugger()!' +} diff --git a/v_windows/v/vlib/v/gen/js/tests/hello/hello1/hello1.v b/v_windows/v/vlib/v/gen/js/tests/hello/hello1/hello1.v new file mode 100644 index 0000000..030e704 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/hello/hello1/hello1.v @@ -0,0 +1,5 @@ +module hello1 + +pub fn nested() string { + return 'Nested' +} diff --git a/v_windows/v/vlib/v/gen/js/tests/interface.v b/v_windows/v/vlib/v/gen/js/tests/interface.v new file mode 100644 index 0000000..a43cadf --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/interface.v @@ -0,0 +1,47 @@ +struct Dog { + name string + age int +} + +struct Cat { + name string + age int +} + +interface Animal { + say(s string) + greet() int +} + +fn (d Dog) say(s string) { + println('Dog $d.name: "$s"') +} + +fn (c Cat) say(s string) { + println('Cat $c.name: "$s"') +} + +fn (d Dog) greet() int { + d.say('Hello!') + return d.age +} + +fn (c Cat) greet() int { + c.say('Hello!') + return c.age +} + +fn use(a Animal) { + if a is Dog { + println('dog') + } else if a is Cat { + println('cat') + } else { + println('its a bug!') + } +} + +fn main() { + use(Dog{'Doggo', 5}) + use(Cat{'Nyancat', 6}) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/interp.v b/v_windows/v/vlib/v/gen/js/tests/interp.v new file mode 100644 index 0000000..5b00aa8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/interp.v @@ -0,0 +1,188 @@ +fn test_fn(s1 string, s2 string) { + print(if s1 == s2 { 'true' } else { 'false' }) + print('\t=> ') + println('"$s1", "$s2"') +} + +fn simple_string_interpolation() { + a := 'Hello' + b := 'World' + res := '$a $b' + test_fn(res, 'Hello World') +} + +fn mixed_string_interpolation() { + num := 7 + str := 'abc' + s1 := 'number=$num' + test_fn(s1, 'number=7') + s2 := 'string=$str' + test_fn(s2, 'string=abc') + s3 := 'a: $num | b: $str' + test_fn(s3, 'a: 7 | b: abc') +} + +fn formatted_string_interpolation() { + x := 'abc' + axb := 'a:$x:b' + test_fn(axb, 'a:abc:b') + x_10 := 'a:${x:10s}:b' + x10_ := 'a:${x:-10s}:b' + test_fn(x_10, 'a: abc:b') + test_fn(x10_, 'a:abc :b') + i := 23 + si_right := '${i:10d}' + si__left := '${i:-10d}' + test_fn(si_right, ' 23') + test_fn(si__left, '23 ') +} + +/* +excape_dollar_in_string() +fn excape_dollar_in_string() { + i := 42 + test_fn('($i)', '(42)') + println('(\$i)'.contains('i') && !'(\$i)'.contains('42')) + println(!'(\\$i)'.contains('i') && '(\\$i)'.contains('42') && '(\\$i)'.contains('\\')) + println('(\\\$i)'.contains('i') && !'(\\\$i)'.contains('42') && '(\\$i)'.contains('\\')) + println(!'(\\\\$i)'.contains('i') && '(\\\\$i)'.contains('42') && '(\\\\$i)'.contains('\\\\')) + test_fn('(${i})', '(42)') + println('(\${i})'.contains('i') && !'(\${i})'.contains('42')) + println(!'(\\${i})'.contains('i') && '(\\${i})'.contains('42') && '(\\${i})'.contains('\\')) + println('(\\\${i})'.contains('i') && !'(\\\${i})'.contains('42') && '(\\${i})'.contains('\\')) + println(!'(\\\\${i})'.contains('i') && '(\\\\${i})'.contains('42') && '(\\\\${i})'.contains('\\\\')) + test_fn(i, 42) +} +*/ + +fn implicit_str() { + i := 42 + test_fn('int $i', 'int 42') + test_fn('$i', '42') + check := '$i' == '42' + // println(check) + text := '$i' + '42' + test_fn(text, '4242') +} + +fn string_interpolation_percent_escaping() { + test := 'hello' + hello := 'world' + x := '%.*s$hello$test |${hello:-30s}|' + test_fn(x, '%.*sworldhello |world |') +} + +fn string_interpolation_string_prefix() { + // `r`, `c` and `js` are also used as a string prefix. + r := 'r' + rr := '$r$r' + test_fn(rr, 'rr') + c := 'c' + cc := '$c$c' + test_fn(cc, 'cc') + js := 'js' + jsjs := '$js$js' + test_fn(jsjs, 'jsjs') +} + +fn interpolation_string_prefix_expr() { + r := 1 + c := 2 + js := 1 + test_fn('>${3 + r}<', '>4<') + test_fn('${r == js} $js', 'true 1') + test_fn('>${js + c} ${js + r == c}<', '>3 true<') +} + +/* +inttypes_string_interpolation() +fn inttypes_string_interpolation() { + c := i8(-103) + uc := byte(217) + uc2 := byte(13) + s := i16(-23456) + us := u16(54321) + i := -1622999040 + ui := u32(3421958087) + vp := voidptr(ui) + bp := byteptr(15541149836) + l := i64(-7694555558525237396) + ul := u64(17234006112912956370) + test_fn('$s $us', '-23456 54321') + test_fn('$ui $i', '3421958087 -1622999040') + test_fn('$l $ul', '-7694555558525237396 17234006112912956370') + test_fn('>${s:11}:${us:-13}<', '> -23456:54321 <') + test_fn('0x${ul:-19x}:${l:22d}', '0xef2b7d4001165bd2 : -7694555558525237396') + test_fn('${c:5}${uc:-7}x', ' -103217 x') + test_fn('${c:x}:${uc:x}:${uc2:02X}', '99:d9:0D') + test_fn('${s:X}:${us:x}:${u16(uc):04x}', 'A460:d431:00d9') + test_fn('${i:x}:${ui:X}:${int(s):x}', '9f430000:CBF6EFC7:ffffa460') + test_fn('${l:x}:${ul:X}', '9537727cad98876c:EF2B7D4001165BD2') + // default pointer format is platform dependent, so try a few + println("platform pointer format: '${vp:p}:$bp'") + test_fn('${vp:p}:$bp', '0xcbf6efc7:0x39e53208c' || + '${vp:p}:$bp' == 'CBF6EFC7:39E53208C' || + '${vp:p}:$bp' == 'cbf6efc7:39e53208c' || + '${vp:p}:$bp' == '00000000CBF6EFC7:000000039E53208C') +} +*/ + +fn utf8_string_interpolation() { + a := 'à-côté' + st := 'Sträßle' + m := '10€' + test_fn('$a $st $m', 'à-côté Sträßle 10€') + zz := '>${a:10}< >${st:-8}< >${m:5}<-' + zz_expected := '> à-côté< >Sträßle < > 10€<-' + // println(' zz: $zz') + // println('zz_expected: $zz_expected') + test_fn(zz, zz_expected) + // e := '\u20AC' // Eurosign doesn' work with MSVC and tcc + e := '€' + test_fn('100.00 $e', '100.00 €') + m2 := 'Москва́' // cyrillic а́: combination of U+0430 and U+0301, UTF-8: d0 b0 cc 81 + d := 'Antonín Dvořák' // latin á: U+00E1, UTF-8: c3 a1 + test_fn(':${m2:7}:${d:-15}:', ': Москва́:Antonín Dvořák :') + g := 'Πελοπόννησος' + test_fn('>${g:-13}<', '>Πελοπόννησος <') +} + +struct Sss { + v1 int + v2 f64 +} + +fn (s Sss) str() string { + return '[$s.v1, ${s.v2:.3f}]' +} + +fn string_interpolation_str_evaluation() { + mut x := Sss{17, 13.455893} + test_fn('$x', '[17, 13.456]') +} + +/* +string_interpolation_with_negative_format_width_should_compile_and_run_without_segfaulting() +fn string_interpolation_with_negative_format_width_should_compile_and_run_without_segfaulting() { + // discovered during debugging VLS + i := 3 + input := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' + println('---------------------------------------------------------------------------------------------') + println('+60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():60} | $input') + println('-60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():-60} | $input') + println('---------------------------------------------------------------------------------------------') + println(true) +} +*/ + +fn main() { + simple_string_interpolation() + mixed_string_interpolation() + formatted_string_interpolation() + implicit_str() + string_interpolation_percent_escaping() + string_interpolation_string_prefix() + interpolation_string_prefix_expr() + utf8_string_interpolation() + string_interpolation_str_evaluation() +} diff --git a/v_windows/v/vlib/v/gen/js/tests/js.v b/v_windows/v/vlib/v/gen/js/tests/js.v new file mode 100644 index 0000000..90bcb18 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/js.v @@ -0,0 +1,141 @@ +import v.gen.js.tests.hello as hl +import v.gen.js.tests.hello.hello1 as hl1 + +const ( + i_am_a_const = 21214 + super = 'amazing keyword' +) + +struct Foo { +mut: + a hl.Aaa +} + +struct Companies { + google int + amazon bool + yahoo string +} + +enum POSITION { + go_back + dont_go_back +} + +fn class(extends string, instanceof int) { + delete := instanceof + _ = delete +} + +fn main() { + println('Hello from V.js!') + println(JS.Math.atan2(1, 0)) + println(JS.eval("console.log('Hello!')")) + mut a := 1 + a *= 2 + a += 3 + println(a) + mut b := hl.Aaa{} + b.update('an update') + println(b) + mut c := Foo{hl.Aaa{}} + c.a.update('another update') + println(c) + println('int(1.5) == "${int(1.5)}"') + d := int(10) + f32(127) + println('typeof (int + f32) == "${typeof(d)}"') + _ = 'done' + { + _ = 'block' + } + _ = POSITION.go_back + _ = hl.Ccc.a + debugger := 'JS keywords' + // TODO: Implement interpolation + await := '$super: $debugger' + mut finally := 'implemented' + println('$await $finally') + dun := i_am_a_const * 20 + 2 + dunn := hl.hello // External constant + _ = hl1.nested() + for i := 0; i < 10; i++ { + } + for i, x in 'hello' { + } + mut evens := []int{} + for x in 1 .. 10 { + y := error_if_even(x) or { x + 1 } + evens << y + } + println(evens) + arr := [1, 2, 3, 4, 5] + for i in arr { + } + ma := { + 'str': 'done' + 'ddo': 'baba' + } + // panic('foo') + for m, n in ma { + iss := m + } + go async(0, 'hello') + fn_in_var := fn (number int) { + println('number: $number') + } + hl.debugger() + anon_consumer(hl.excited(), fn (message string) { + println(message) + }) + hl.raw_js_log() + propagation() or { println(err) } +} + +fn anon_consumer(greeting string, anon fn (string)) { + anon(greeting) +} + +fn async(num int, def string) { +} + +[deprecated; inline] +fn hello(game_on int, dummy ...string) (int, int) { + defer { + do := 'not' + } + for dd in dummy { + l := dd + } + return game_on + 2, 221 +} + +fn (it Companies) method() int { + ss := Companies{ + google: 2 + amazon: true + yahoo: 'hello' + } + a, b := hello(2, 'google', 'not google') + glue := if a > 2 { + 'more_glue' + } else if a > 5 { + 'more glueee' + } else { + 'less glue' + } + if a != 2 { + } + return 0 +} + +fn error_if_even(num int) ?int { + if num % 2 == 0 { + return error('number is even') + } + return num +} + +fn propagation() ? { + println('Propagation test:') + return error('"Task failed successfully" - Windows XP') +} diff --git a/v_windows/v/vlib/v/gen/js/tests/life.v b/v_windows/v/vlib/v/gen/js/tests/life.v new file mode 100644 index 0000000..a89da5a --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/life.v @@ -0,0 +1,98 @@ +fn clear() { + JS.console.clear() +} + +const ( + w = 30 + h = 30 +) + +fn get(game [][]bool, x int, y int) bool { + if y < 0 || x < 0 { + return false + } + if y >= h || x >= w { + return false + } + + return game[y][x] +} + +fn neighbours(game [][]bool, x int, y int) int { + mut count := 0 + if get(game, x - 1, y - 1) { + count++ + } + if get(game, x, y - 1) { + count++ + } + if get(game, x + 1, y - 1) { + count++ + } + if get(game, x - 1, y) { + count++ + } + if get(game, x + 1, y) { + count++ + } + if get(game, x - 1, y + 1) { + count++ + } + if get(game, x, y + 1) { + count++ + } + if get(game, x + 1, y + 1) { + count++ + } + return count +} + +fn step(game [][]bool) [][]bool { + mut new_game := [][]bool{} + for y, row in game { + mut new_row := []bool{} + new_game[y] = new_row + for x, cell in row { + count := neighbours(game, x, y) + new_row[x] = (cell && count in [2, 3]) || count == 3 + } + } + return new_game +} + +fn row_str(row []bool) string { + mut str := '' + for cell in row { + if cell { + str += '◼ ' + } else { + str += '◻ ' + } + } + return str +} + +fn show(game [][]bool) { + clear() + for row in game { + println(row_str(row)) + } +} + +// TODO Remove `fn main` once vet supports scripts +fn main() { + mut game := [][]bool{len: h, init: []bool{len: w}} + + game[11][15] = true + game[11][16] = true + game[12][16] = true + game[10][21] = true + game[12][20] = true + game[12][21] = true + game[12][22] = true + + JS.setInterval(fn () { + show(game) + game = step(game) + }, 500) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/optional.v b/v_windows/v/vlib/v/gen/js/tests/optional.v new file mode 100644 index 0000000..6d52eac --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/optional.v @@ -0,0 +1,33 @@ +module main + +fn main() { + try_propagation() or { println('captured: $err') } +} + +fn try_propagation() ? { + try_numbers() ? +} + +fn try_numbers() ? { + for x in 1 .. 10 { + y := error_if_even(x) or { x + 1 } + println('$x rounded to $y') + error_if_prime(y) ? + } +} + +fn error_if_even(num int) ?int { + if num % 2 == 0 { + return error('number is even') + } + return num +} + +fn error_if_prime(num int) ?int { + for i in 2 .. num { + if num % i == 0 { + return error('$num is prime') + } + } + return num +} diff --git a/v_windows/v/vlib/v/gen/js/tests/simple.v b/v_windows/v/vlib/v/gen/js/tests/simple.v new file mode 100644 index 0000000..ec46286 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/simple.v @@ -0,0 +1,5 @@ +module main + +fn main() { + println('hello world') +} diff --git a/v_windows/v/vlib/v/gen/js/tests/simple_sourcemap.v b/v_windows/v/vlib/v/gen/js/tests/simple_sourcemap.v new file mode 100644 index 0000000..0dd8a64 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/simple_sourcemap.v @@ -0,0 +1,23 @@ +module main + +fn main() { + e := JS.Error{} + s := e.stack + node_version := js_node_process().version + node_main := get_node_main_version(node_version) + if node_main >= 12 { + if s.contains('simple_sourcemap.v:') { + panic('node found no source map!') + } else { + println('source map is working') + } + } else { + println('skiping test! node version >=12.12.0 required. Current Version is $node_version') + } +} + +fn get_node_main_version(str string) int { + a := str.slice(1, int(str.len)) + b := a.split('.') + return b[0].int() +} diff --git a/v_windows/v/vlib/v/gen/js/tests/struct.v b/v_windows/v/vlib/v/gen/js/tests/struct.v new file mode 100644 index 0000000..306d46a --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/struct.v @@ -0,0 +1,40 @@ +module main + +struct Int { +mut: + value int + test map[string]int + hello []int +} + +fn (mut i Int) add(value int) { + i.value += value +} + +fn (i Int) get() int { + return i.value +} + +struct Config { + foo int + bar string +} + +fn use_config(c Config) { +} + +fn main() { + mut a := Int{ + value: 10 + } + a.add(5) + println(a) // 15 + mut b := Int{} + b.add(10) + println(b.get()) // 10 + use_config(Config{2, 'bar'}) + use_config( + foo: 2 + bar: 'bar' + ) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/array.out b/v_windows/v/vlib/v/gen/js/tests/testdata/array.out new file mode 100644 index 0000000..a7d0c13 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/array.out @@ -0,0 +1,303 @@ +3 +1 +2 +4 +255 +256 +2 +4 +131 +4 +1 +2 +3 +5 +4 +4 +[1, 5, 2, 3, 4] +5 +4 +5 +[1, 5, 2, 3, 4] +[5, 2, 3, 4] +4 +[5, 3, 4] +3 +[5, 3] +2 +[2.5, 3.25, 4.5, 5.75] +true +true +true +true +true +true +true +true +true +true +true +1 +2 +3 +10000 +234 +0 +1 +0 +1 +3 +[1, 3] +3 +2 +3 +4 +1 +4 +5 +2 +5 +0 +1 +1.1 +[[1, 2], 3, 4] +[[1, 2], [5, 6], 3, 4] +0 +1 +1 +0 +1 +1.1 +[[1, 2], 3, 4] +[[5, 6], [1, 2], 3, 4] +5 +true +1.1 +1.1 +1.1 +-123 +-123 +-123 +123 +123 +123 +1.1 +1.1 +1.1 +1 +2 +1 +2 +1 +abc +1 +abc +0 +abc +2 +3 +2 +3 +1 +2 +1 +2 +2 +1 +4 +6 +1 +4 +6 +[4, 3, 2, 1] +true +true +true +true +true +true +true +0 +[0, 0, 0, 0] +[0, 7, 0, 0] +0 +[2, 4, 6, 8, 10, 12, 14, 16, 18, 20] +[2, 4, 6, 8, 10, 12, 14, 16, 18, 20] +[2, 4, 6, 8, 10] +2 +[1, 2] +0 +1 +-1 +0 +3 +-1 +0 +2 +-1 +1 +2 +-1 +2 +3 +1 +3 +6 +true +true +true +true +true +true +true +true +true +15 +20 +14 +-6 +-7 +[2, 4, 6] +[is, awesome] +[2, 3, 4, 6, 8, 9, 10] +[4, 5, 6] +[5, 10] +[2, 4] +[2, 4] +[1, 2, 3, 4, 5, 6] +[v, is, awesome] +[0, 0, 0, 0, 0, 0] +0 +[10, 20, 30, 40, 50, 60] +[1, 4, 9, 16, 25, 36] +[1, 2, 3, 4, 5, 6] +[false, true, false, true, false, true] +[V, IS, AWESOME] +[false, false, true] +[true, true, false] +[7, 7, 7] +[1, 4, 9, 16, 25, 36] +[3, 4, 5, 6, 7, 8] +[3, 9, 4, 6, 12, 7] +[] +[true, true, true, true, true, true] +[1a, 2a, 3a, 4a, 5a, 6a] +[2, 3, 4, 5, 6, 7] +[2, 3, 8] +[1v, 2is, 3awesome] +[1, 4, 9, 16, 25, 36] +[1, 25, 100] +[1, 2, 3, 4, 5, 6] +[v, is, awesome] +[2, 3, 4] +[2, 3, 4] +[3, 4, 5] +[2, 3, 4] +[1, 2, 3] +[1, 2, 3] +[[1, 2, 3], [4, 5, 6]] +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +[1, 3, 5, hi] +[-3, 7, 42, 67, 108] +[a, b, c, d, e, f] +0 +1 +79 +[0, 1, 15, 27, 38, 50, 79] +[0, 1, 15, 27, 38, 50, 79] +3 +[14, 2, 3] +test b +[true, false, true] +1,1 +2,2 +3,3 +4,4 +1,1 +2,2 +3,3 +4,4 +6 +[2, 0, 2, 2] +[[1, 0, 0], [0, 0, 0]] +[[1, 0, 0], [1, 0, 0]] +[abc] +[0, 0, 0, 0] +[2, 2] +[1, 2, 3, 4] +[4, 3, 2, 1] +[c, b, a] +[[5, 6], [3, 4], [1, 2]] +5,5 +4 +1 +xyz +def +abc +3 +1 +abc +a +3 +4 +def +a +11 +33 +[21, 24, 14, 20] +2 +3 +4 +123 +123 +[[1, 2, 3]] +[[[1, 2, 3]]] +[[1, 2, 3]] +[[[1, 2, 3]]] +true +true +true +true +true +false +true +true +true +true +true +0 +`exists`: true and `not exists`: false +[[], [], [], []] +[[], [], [123], []] +[{}, {}, {}, {}] +[{}, {}, {'123': 123}, {}] +Numbers { odds: [1, 3, 5] , evens: [2, 4] } +Numbers { odds: [3, 5, 7] , evens: [2, 6, 10] } +[[10, 10, 10], [10, 10, 10], [10, 10, 10]] diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/array.v b/v_windows/v/vlib/v/gen/js/tests/testdata/array.v new file mode 100644 index 0000000..efe5a8a --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/array.v @@ -0,0 +1,1180 @@ +struct Chunk { + val string +} + +struct Kkk { + q []Chunk +} + +const ( + c_n = 5 +) + +struct Coord { + x int + y int + z int +} + +struct Numbers { + odds []int + evens []int +} + +struct Person { + name string + nums []int + kv map[string]string +} + +// test array add in function with mut argument +fn add_nums(mut arr []int) { + arr << 4 +} + +const ( + grid_size_1 = 2 + grid_size_2 = 3 + grid_size_3 = 4 + cell_value = 123 +) + +struct User { + age int + name string +} + +struct Foo { +mut: + bar []int +} + +fn array_in_mut(mut a []int) { + if 1 in a { + a[0] = 2 + } +} + +fn mut_arr_with_eq_in_fn(mut a []int) { + if a == [1, 2, 3, 4] { + a[0] = 0 + } + if [0, 2, 3, 4] == a { + a[1] = 0 + } + if !(a != [0, 0, 3, 4]) { + a[2] = 0 + } + if !([0, 0, 0, 4] != a) { + a[3] = 0 + } +} + +fn map_test_helper_1(i int) int { + return i * i +} + +fn map_test_helper_2(i int, b string) int { + return i + b.len +} + +fn map_test_helper_3(i int, b []string) int { + return i + b.map(it.len)[i % b.len] +} + +fn filter_test_helper_1(a int) bool { + return a > 3 +} + +fn sum(prev int, curr int) int { + return prev + curr +} + +fn sub(prev int, curr int) int { + return prev - curr +} + +struct Foooj { + a [5]int // c_n +} + +fn double_up(mut a []int) { + for i := 0; i < a.len; i++ { + a[i] = a[i] * 2 + } +} + +fn double_up_v2(mut a []int) { + for i, _ in a { + a[i] = a[i] * 2 // or val*2, doesn't matter + } +} + +fn modify(mut numbers []int) { + numbers[0] = 777 +} + +fn main() { + { + // test pointer + mut arr := []&int{} + a := 1 + b := 2 + c := 3 + arr << &a + arr << &b + arr << &c + assert *arr[0] == 1 + arr[1] = &c + assert *arr[1] == 3 + mut d_arr := [arr] // [][]&int + d_arr << arr + println(*d_arr[0][1]) // 3 + println(*d_arr[1][0]) // 1 + } + { + // test assign + mut arr := [2, 4, 8, 16, 32, 64, 128] + arr[0] = 2 + arr[1] &= 255 + arr[2] |= 255 + arr[3] <<= 4 + arr[4] >>= 4 + arr[5] %= 5 + arr[6] ^= 3 + println(arr[0]) + println(arr[1]) + println(arr[2]) + println(arr[3]) + println(arr[4]) + println(arr[5]) + println(arr[6]) + } + { + // test ints + mut a := [1, 5, 2, 3] + println(a.len) // 4 + println(a[0]) + println(a[2]) + println(a.last()) + + a << 4 + println(a.len) + println(a[4]) + println(a.last()) + + s := a.str() + println(s) + println(a[1]) + println(a.last()) + } + { + // test deleting + mut a := [1, 5, 2, 3, 4] + + println(a.len) + println(a.str()) + + a.delete(0) + + println(a.str()) + println(a.len) // 4 + + a.delete(1) + + println(a.str()) + println(a.len) + a.delete(a.len - 1) + + println(a.str()) + println(a.len) + } + { + // test slice delete + mut a := [1.5, 2.5, 3.25, 4.5, 5.75] + b := a[2..4] + a.delete(0) + // assert a == [2.5, 3.25, 4.5, 5.75] + // assert b == [3.25, 4.5] + println(a) + println(a == [2.5, 3.25, 4.5, 5.75]) + println(b == [3.25, 4.5]) + a = [3.75, 4.25, -1.5, 2.25, 6.0] + c := a[..3] + a.delete(2) + println(a == [3.75, 4.25, 2.25, 6.0]) + println(c == [3.75, 4.25, -1.5]) + } + { + // test delete many + mut a := [1, 2, 3, 4, 5, 6, 7, 8, 9] + b := a[2..6] + a.delete_many(4, 3) + println(a == [1, 2, 3, 4, 8, 9]) + println(b == [3, 4, 5, 6]) + + c := a[..a.len] + a.delete_many(2, 0) // this should just clone + a[1] = 17 + + println(a == [1, 17, 3, 4, 8, 9]) + println(c == [1, 2, 3, 4, 8, 9]) + a.delete_many(0, a.len) + println(a == []int{}) + } + { + // test short + a := [1, 2, 3] + println(a.len == 3) + println(a.cap == 3) + println(a[0]) + println(a[1]) + println(a[2]) + } + { + // test large + mut a := [0].repeat(0) + for i in 0 .. 10000 { + a << i + } + println(a.len) + println(a[234]) + } + { + // test empty + mut chunks := []Chunk{} + a := Chunk{} + println(chunks.len) + chunks << a + println(chunks.len) + chunks = [] + println(chunks.len) + chunks << a + println(chunks.len) + } + { + // test push + mut a := []int{} + a << 1 + a << 3 + println(a[1]) + println(a.str()) + } + { + // test insert + mut a := [1, 2] + a.insert(0, 3) + println(a[0]) + println(a[2]) + println(a.len) + a.insert(1, 4) + println(a[1]) + println(a[2]) + println(a.len) + a.insert(4, 5) + println(a[4]) + println(a[3]) + println(a.len) + mut b := []f64{} + println(b.len) + b.insert(0, f64(1.1)) + println(b.len) + println(b[0]) + } + { + // test insert many + mut a := [3, 4] + a.insert(0, [1, 2]) + println(a) + + b := [5, 6] + a.insert(1, b) + println(a) + } + { + // test prepend + mut a := []int{} + println(a.len) + a.prepend(1) + println(a.len) + println(a[0]) + mut b := []f64{} + + println(b.len) + + b.prepend(f64(1.1)) + + println(b.len) + + println(b[0]) + } + { + // test prepend many + mut a := [3, 4] + a.prepend([1, 2]) + println(a) + b := [5, 6] + a.prepend(b) + println(a) + } + { + // test repeat + { + a := [0].repeat(5) + println(a.len) + println(a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0) + } + { + a := [1.1].repeat(10) + println(a[0]) + println(a[5]) + println(a[9]) + } + { + a := [i64(-123)].repeat(10) + println(a[0]) + println(a[5]) + println(a[9]) + } + { + a := [u64(123)].repeat(10) + println(a[0]) + println(a[5]) + println(a[9]) + } + { + a := [1.1].repeat(10) + println(a[0]) + println(a[5]) + println(a[9]) + } + { + a := [1, 2].repeat(2) + println(a[0]) + println(a[1]) + println(a[2]) + println(a[3]) + } + { + a := ['1', 'abc'].repeat(2) + println(a[0]) + println(a[1]) + println(a[2]) + println(a[3]) + } + { + mut a := ['1', 'abc'].repeat(0) + println(a.len) + a << 'abc' + println(a[0]) + } + } + { + // todo(playX): deep repeat does not yet work. + /* + // test deep repeat + mut a3 := [[[1, 1], [2, 2], [3, 3]], [[4, 4], [5, 5], [6, 6]]] + r := a3.repeat(3) + a3[1][1][0] = 17 + print(r) + assert r == [ + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + [[1, 1], [2, 2], [3, 3]], + [[4, 4], [5, 5], [6, 6]], + ] + assert a3 == [[[1, 1], [2, 2], [3, 3]], [[4, 4], [17, 5], + [6, 6], + ]] + */ + } + { + // test right + a := [1, 2, 3, 4] + c := a[1..a.len] + d := a[1..] + println(c[0]) + println(c[1]) + println(d[0]) + println(d[1]) + } + { + // test left + a := [1, 2, 3] + c := a[0..2] + d := a[..2] + println(c[0]) + println(c[1]) + println(d[0]) + println(d[1]) + } + { + // test slice + a := [1, 2, 3, 4] + b := a[2..4] + println(b.len) + println(a[1..2].len) + println(a.len) + } + { + // test push many + mut a := [1, 2, 3] + b := [4, 5, 6] + a << b + println(a.len) + println(a[0]) + println(a[3]) + println(a[5]) + } + { + // test reverse + a := [1, 2, 3, 4] + b := ['test', 'array', 'reverse'] + c := a.reverse() + println(c) + d := b.reverse() + for i, _ in c { + println(c[i] == a[a.len - i - 1]) + } + for i, _ in d { + println(d[i] == b[b.len - i - 1]) + } + e := []int{} + f := e.reverse() + println(f.len) + } + { + // test fixed + mut nums := [4]int{} + // x := nums[1..3] + // assert x.len == 2 + + println(nums) + nums[1] = 7 + println(nums) + nums2 := [5]int{} // c_n + println(nums2[c_n - 1]) + } + { + // test mut slice + /* + todo(playX): slices do not work yet. We have to implement custom wrapper for them. + mut n := [1, 2, 3] + // modify(mut n) + modify(mut n[..2]) + assert n[0] == 777 + modify(mut n[2..]) + assert n[2] == 777 + println(n) + */ + } + { + // test mut arg + mut arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + double_up(mut arr) + println(arr.str()) + arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + double_up_v2(mut arr) + println(arr.str()) + } + { + // test doubling + mut nums := [1, 2, 3, 4, 5] + for i in 0 .. nums.len { + nums[i] *= 2 + } + println(nums.str()) + } + { + // test single element + + mut a := [1] + a << 2 + println(a.len) + assert a[0] == 1 + assert a[1] == 2 + println(a) + } + { + // test find index + + // string + a := ['v', 'is', 'great'] + println(a.index('v')) + println(a.index('is')) + println(a.index('gre')) + // int + b := [1, 2, 3, 4] + println(b.index(1)) + println(b.index(4)) + println(b.index(5)) + // byte + c := [0x22, 0x33, 0x55] + println(c.index(0x22)) + println(c.index(0x55)) + println(c.index(0x99)) + // char + d := [`a`, `b`, `c`] + println(d.index(`b`)) + println(d.index(`c`)) + println(d.index(`u`)) + } + { + // test multi + + a := [[1, 2, 3], [4, 5, 6]] + println(a.len) + println(a[0].len) + println(a[0][0]) + println(a[0][2]) + println(a[1][2]) + } + { + // test in + a := [1, 2, 3] + println(1 in a) + println(2 in a) + println(3 in a) + println(4 !in a) + println(0 !in a) + println(0 !in a) + println(4 !in a) + b := [1, 4, 0] + c := [3, 6, 2, 0] + println(0 in b) + println(0 in c) + } + { + // test reduce + a := [1, 2, 3, 4, 5] + b := a.reduce(sum, 0) + c := a.reduce(sum, 5) + d := a.reduce(sum, -1) + println(b) + println(c) + println(d) + e := [1, 2, 3] + f := e.reduce(sub, 0) + g := e.reduce(sub, -1) + println(f) + println(g) + } + { + a := [1, 2, 3, 4, 5, 6] + b := a.filter(it % 2 == 0) + println(b) + + c := ['v', 'is', 'awesome'] + d := c.filter(it.len > 1) + println(d) + assert d[0] == 'is' + assert d[1] == 'awesome' + //////// + arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + println(arr.filter(it % 2 == 0 || it % 3 == 0)) + + mut mut_arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + mut_arr = mut_arr.filter(it < 4) + assert mut_arr.len == 3 + println(a.filter(filter_test_helper_1)) + println([1, 5, 10].filter(filter_test_helper_1)) + } + { + // test anon fn filter + filter_num := fn (i int) bool { + return i % 2 == 0 + } + println([1, 2, 3, 4, 5].filter(filter_num)) + } + { + a := [1, 2, 3, 4].filter(fn (i int) bool { + return i % 2 == 0 + }) + println(a) + } + { + // test map + nums := [1, 2, 3, 4, 5, 6] + strs := ['v', 'is', 'awesome'] + // assert nums.map() == + // assert nums.map(it, 'excessive') == + // identity + println(nums.map(it)) + println(strs.map(it)) + println(nums.map(it - it)) + println(nums.map(it - it)[0]) + // type switch + println(nums.map(it * 10)) + println(nums.map(it * it)) + println(nums.map('$it')) + println(nums.map(it % 2 == 0)) + println(strs.map(it.to_upper())) + println(strs.map(it == 'awesome')) + println(strs.map(it.len in nums)) + println(strs.map(int(7))) + // external func + println(nums.map(map_test_helper_1(it))) + println(nums.map(map_test_helper_2(it, 'bb'))) + println(nums.map(map_test_helper_3(it, strs))) + // empty array as input + println([]int{len: 0}.map(it * 2)) + // nested maps (where it is of same type) + println(nums.map(strs.map(int(7)) == [7, 7, 7])) + println(nums.map('$it' + strs.map('a')[0])) + // assert nums.map(it + strs.map(int(7))[0]) == [8, 9, 10, 11, 12, 13] + println(nums.map(it + strs.map(it.len)[0])) + println(strs.map(it.len + strs.map(it.len)[0])) + // nested (different it types) + // todo(playX): this one produces invalid JS code. + // assert strs.map(it[nums.map(it - it)[0]]) == [byte(`v`), `i`, `a`] + println(nums[0..3].map('$it' + strs.map(it)[it - 1])) + println(nums.map(map_test_helper_1)) + println([1, 5, 10].map(map_test_helper_1)) + println(nums) + println(strs) + } + { + // test anon fn map + add_num := fn (i int) int { + return i + 1 + } + println([1, 2, 3].map(add_num)) + } + { + // test multi anon fn map + a := [1, 2, 3].map(fn (i int) int { + return i + 1 + }) + b := [1, 2, 3].map(fn (i int) int { + return i + 2 + }) + println(a) + println(b) + } + { + // test anon fn arg map + a := [1, 2, 3].map(fn (i int) int { + return i + 1 + }) + println(a) + } + { + // test anon fn arg different type map + i_to_str := fn (i int) string { + return i.str() + } + a := [1, 2, 3].map(i_to_str) + println(a) + } + { + // test anon fn inline different type map + a := [1, 2, 3].map(fn (i int) string { + return i.str() + }) + println(a) + } + { + // test array str + + numbers := [1, 2, 3] + assert numbers == [1, 2, 3] + numbers2 := [numbers, [4, 5, 6]] // dup str() bug + println(numbers2) + assert true + assert numbers.str() == '[1, 2, 3]' + } + { + // test eq + println([5, 6, 7] != [6, 7]) + println([`a`, `b`] == [`a`, `b`]) + println([User{ + age: 22 + name: 'bob' + }] == [User{ + age: 22 + name: 'bob' + }]) + // todo(playX): map cmp does not work yet + /* + assert [map{ + 'bob': 22 + }, map{ + 'tom': 33 + }] == [map{ + 'bob': 22 + }, map{ + 'tom': 33 + }]*/ + println([[1, 2, 3], [4]] == [[1, 2, 3], [4]]) + } + { + // test fixed array eq + a1 := [1, 2, 3]! + println(a1 == [1, 2, 3]!) + println(a1 != [2, 3, 4]!) + + a2 := [[1, 2]!, [3, 4]!]! + println(a2 == [[1, 2]!, [3, 4]!]!) + println(a2 != [[3, 4]!, [1, 2]!]!) + + a3 := [[1, 2], [3, 4]]! + println(a3 == [[1, 2], [3, 4]]!) + println(a3 != [[1, 1], [2, 2]]!) + + a4 := [[`a`, `b`], [`c`, `d`]]! + println(a4 == [[`a`, `b`], [`c`, `d`]]!) + println(a4 != [[`c`, `a`], [`a`, `b`]]!) + + a5 := [['aaa', 'bbb'], ['ccc', 'ddd']]! + println(a5 == [['aaa', 'bbb'], ['ccc', 'ddd']]!) + println(a5 != [['abc', 'def'], ['ccc', 'ddd']]!) + + a6 := [['aaa', 'bbb']!, ['ccc', 'ddd']!]! + println(a6 == [['aaa', 'bbb']!, ['ccc', 'ddd']!]!) + println(a6 != [['aaa', 'bbb']!, ['aaa', 'ddd']!]!) + + a7 := [[1, 2]!, [3, 4]!] + println(a7 == [[1, 2]!, [3, 4]!]) + println(a7 != [[2, 3]!, [1, 2]!]) + + a8 := [['aaa', 'bbb']!, ['ccc', 'ddd']!] + println(a8 == [['aaa', 'bbb']!, ['ccc', 'ddd']!]) + println(a8 != [['bbb', 'aaa']!, ['cccc', 'dddd']!]) + } + { + // test fixed array literal eq + println([1, 2, 3]! == [1, 2, 3]!) + println([1, 1, 1]! != [1, 2, 3]!) + + println([[1, 2], [3, 4]]! == [[1, 2], [3, 4]]!) + println([[1, 1], [2, 2]]! != [[1, 2], [3, 4]]!) + + println([[1, 1]!, [2, 2]!]! == [[1, 1]!, [2, 2]!]!) + println([[1, 1]!, [2, 2]!]! != [[1, 2]!, [2, 3]!]!) + + println([[1, 1]!, [2, 2]!] == [[1, 1]!, [2, 2]!]) + println([[1, 1]!, [2, 2]!] != [[1, 2]!, [2, 3]!]) + } + { + // test sort + mut a := ['hi', '1', '5', '3'] + a.sort() + println(a) + + mut nums := [67, -3, 108, 42, 7] + nums.sort() + println(nums) + assert nums[0] == -3 + assert nums[1] == 7 + assert nums[2] == 42 + assert nums[3] == 67 + assert nums[4] == 108 + // todo(playX): add codegen for comparator fn passed + /* + nums.sort(a < b) + assert nums[0] == -3 + assert nums[1] == 7 + assert nums[2] == 42 + assert nums[3] == 67 + assert nums[4] == 108 + + mut users := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}] + users.sort(a.age < b.age) + assert users[0].age == 20 + assert users[1].age == 22 + assert users[2].age == 25 + assert users[0].name == 'Bob' + assert users[1].name == 'Peter' + assert users[2].name == 'Alice' + + users.sort(a.age > b.age) + assert users[0].age == 25 + assert users[1].age == 22 + assert users[2].age == 20 + + users.sort(a.name < b.name) // Test sorting by string fields + */ + } + { + // test rune sort + mut bs := [`f`, `e`, `d`, `b`, `c`, `a`] + bs.sort() + println(bs) + + /* + bs.sort(a > b) + println(bs) + assert '$bs' == '[`f`, `e`, `d`, `c`, `b`, `a`]' + + bs.sort(a < b) + println(bs) + assert '$bs' == '[`a`, `b`, `c`, `d`, `e`, `f`]' + */ + } + { + // test f32 sort + mut f := [f32(50.0), 15, 1, 79, 38, 0, 27] + f.sort() + println(f[0]) + println(f[1]) + println(f[6]) + } + { + // test f64 sort + mut f := [50.0, 15, 1, 79, 38, 0, 27] + f.sort() + println(f) + assert f[0] == 0.0 + assert f[1] == 1.0 + assert f[6] == 79.0 + } + { + // test i64 sort + mut f := [i64(50), 15, 1, 79, 38, 0, 27] + f.sort() + println(f) + assert f[0] == 0 + assert f[1] == 1 + assert f[6] == 79 + } + { + // test in struct + + mut baz := Foo{ + bar: [0, 0, 0] + } + baz.bar[0] += 2 + baz.bar[0]++ + println(baz.bar[0]) + } + { + // test direct modification + mut foo := [2, 0, 5] + foo[1] = 3 + foo[0] *= 7 + foo[1]-- + foo[2] -= 2 + println(foo) + } + { + // test bools + println('test b') + mut a := [true, false] + a << true + println(a) + } + { + // test push many self + mut actual_arr := [1, 2, 3, 4] + actual_arr << actual_arr + expected_arr := [1, 2, 3, 4, 1, 2, 3, 4] + assert actual_arr.len == expected_arr.len + for i in 0 .. actual_arr.len { + print(actual_arr[i]) + print(',') + println(expected_arr[i]) + } + } + { + // test for + nums := [1, 2, 3] + mut sum := 0 + for num in nums { + sum += num + } + println(sum) + } + { + // test left shift precedence + + mut arr := []int{} + arr << 1 + 1 + arr << 1 - 1 + arr << 2 / 1 + arr << 2 * 1 + println(arr) + } + { + // todo(playX): what we should do with cap on the JS backend? + /* + // test array with cap + a4 := []int{len: 1, cap: 10} + println(a4.cap) + assert a4.len == 1 + assert a4.cap == 10 + a5 := []int{len: 1, cap: 10} + assert a5.len == 1 + assert a5.cap == 10 + */ + } + { + // test multi array index + mut a := [][]int{len: 2, init: []int{len: 3, init: 0}} + a[0][0] = 1 + println('$a') + mut b := [[0].repeat(3)].repeat(2) + b[0][0] = 1 + println('$b') + } + { + // test plus assign string + mut a := [''] + a[0] += 'abc' + + println(a) + } + { + // test mut arr with eq in fn + mut a := [1, 2, 3, 4] + mut_arr_with_eq_in_fn(mut a) + println(a) + } + { + // test array in mut + mut a := [1, 2] + array_in_mut(mut a) + println(a) + } + { + mut nums := [1, 2, 3] + add_nums(mut nums) + println(nums) + } + { + // test reverse in place + mut a := [1, 2, 3, 4] + a.reverse_in_place() + println(a) + mut b := ['a', 'b', 'c'] + b.reverse_in_place() + println(b) + mut c := [[1, 2], [3, 4], [5, 6]] + c.reverse_in_place() + println(c) + } + { + // test array int pop + mut a := [1, 2, 3, 4, 5] + assert a.len == 5 + x := a.last() + y := a.pop() + println('$x,$y') + assert a.len == 4 + z := a.pop() + assert a.len == 3 + println(z) + a.pop() + a.pop() + final := a.pop() + println(final) + } + { + // test array string pop + mut a := ['abc', 'def', 'xyz'] + assert a.len == 3 + println(a.pop()) + println(a.pop()) + println(a.pop()) + assert a.len == 0 + } + { + // test array first + a := [3] + println(a.first()) + b := [1, 2, 3, 4] + println(b.first()) + c := ['abc', 'def'] + println(c.first()) + // todo(playX): we should implement byte str cmp + + s := [Chunk{'a'}] + println(s.first().val) + } + { + // test array last + a := [3] + println(a.last()) + b := [1, 2, 3, 4] + println(b.last()) + c := ['abc', 'def'] + println(c.last()) + s := [Chunk{'a'}] + println(s.last().val) + } + { + // test direct array access + mut a := [11, 22, 33, 44] + println(a[0]) + println(a[2]) + x := a[0] + a[0] = 21 + a[1] += 2 + a[2] = x + 3 + a[3] -= a[1] + println(a) + } + { + // test multidimensional array initialization with consts + mut data := [][][]int{len: grid_size_1, init: [][]int{len: grid_size_2, init: []int{len: grid_size_3, init: cell_value}}} + println(data.len) + println(data[0].len) + println(data[0][0].len) + println(data[0][0][0]) + println(data[1][1][1]) + } + { + // test multi array prepend + mut a := [][]int{} + a.prepend([1, 2, 3]) + println(a) + mut b := [][]int{} + b.prepend([[1, 2, 3]]) + println(b) + } + { + // test multi array insert + mut a := [][]int{} + a.insert(0, [1, 2, 3]) + println(a) + mut b := [][]int{} + b.insert(0, [[1, 2, 3]]) + println(b) + } + { + // test multi array in + a := [[1]] + println([1] in a) + } + { + // test any type array contains + a := [true, false] + println(a.contains(true)) + println(true in a) + println(a.contains(false)) + println(false in a) + b := [i64(2), 3, 4] + println(b.contains(i64(3))) + println(5 !in b) + c := [[1], [2]] + println(c.contains([1])) + println([2] in c) + println([3] !in c) + } + { + // test struct array of multi type in + ivan := Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + } + people := [Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + }, Person{ + name: 'bob' + nums: [2] + kv: { + 'bbb': '222' + } + }] + println(ivan in people) + } + { + // test struct array of multi type index + ivan := Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + } + people := [Person{ + name: 'ivan' + nums: [1, 2, 3] + kv: { + 'aaa': '111' + } + }, Person{ + name: 'bob' + nums: [2] + kv: { + 'bbb': '222' + } + }] + println(people.index(ivan)) + assert people.index(ivan) == 0 + } + { + // test array struct contains + + mut coords := []Coord{} + coord_1 := Coord{ + x: 1 + y: 2 + z: -1 + } + coords << coord_1 + exists := coord_1 in coords + not_exists := coord_1 !in coords + println('`exists`: $exists and `not exists`: $not_exists') + assert exists == true + assert not_exists == false + } + { + // test array of array append + mut x := [][]int{len: 4} + println(x) // OK + x[2] << 123 // RTE + println(x) + } + { + // test array of map insert + mut x := []map[string]int{len: 4} + println(x) // OK + x[2]['123'] = 123 // RTE + println(x) + } + { + // todo(playX): does not work + /* + // test multi fixed array init + a := [3][3]int{} + println(a) + */ + } + { + // test array of multi filter + arr := [1, 2, 3, 4, 5] + nums := Numbers{ + odds: arr.filter(it % 2 == 1) + evens: arr.filter(it % 2 == 0) + } + println(nums) + assert nums.odds == [1, 3, 5] + assert nums.evens == [2, 4] + } + { + // test array of multi map + arr := [1, 3, 5] + nums := Numbers{ + odds: arr.map(it + 2) + evens: arr.map(it * 2) + } + println(nums) + assert nums.odds == [3, 5, 7] + assert nums.evens == [2, 6, 10] + } + { + // test multi fixed array with default init + a := [3][3]int{init: [3]int{init: 10}} + println(a) + assert a == [[10, 10, 10]!, [10, 10, 10]!, [10, 10, 10]!]! + } +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.out b/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.out new file mode 100644 index 0000000..d252328 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.out @@ -0,0 +1,2 @@ +true +false \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.v b/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.v new file mode 100644 index 0000000..8d99196 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/byte_is_space.v @@ -0,0 +1,4 @@ +x := ' x' + +println(x[0].is_space()) +println(x[1].is_space()) diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.out b/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.out new file mode 100644 index 0000000..2db62ac --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.out @@ -0,0 +1 @@ +2 > 1 \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.v b/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.v new file mode 100644 index 0000000..f214466 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/compare_ints.v @@ -0,0 +1,15 @@ +if 2 > 1 { + println('2 > 1') +} + +if 2 < 1 { + println('2 < 1') +} + +if 2 == 1 { + println('2 == 1') +} + +if 2 == 0 { + println('2 == 0') +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/hw.out b/v_windows/v/vlib/v/gen/js/tests/testdata/hw.out new file mode 100644 index 0000000..95d09f2 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/hw.out @@ -0,0 +1 @@ +hello world \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/hw.v b/v_windows/v/vlib/v/gen/js/tests/testdata/hw.v new file mode 100644 index 0000000..2a082f9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/hw.v @@ -0,0 +1 @@ +println('hello world') diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/match.out b/v_windows/v/vlib/v/gen/js/tests/testdata/match.out new file mode 100644 index 0000000..a63d980 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/match.out @@ -0,0 +1,4 @@ +Vec2d(42,43) +Vec2d(46,74,21) +life +V is running on JS \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/match.v b/v_windows/v/vlib/v/gen/js/tests/testdata/match.v new file mode 100644 index 0000000..78e135d --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/match.v @@ -0,0 +1,57 @@ +struct Vec2d { + x int + y int +} + +struct Vec3d { + x int + y int + z int +} + +type Vec = Vec2d | Vec3d + +fn match_vec(v Vec) { + match v { + Vec2d { + println('Vec2d($v.x,$v.y)') + } + Vec3d { + println('Vec2d($v.x,$v.y,$v.z)') + } + } +} + +fn match_classic_num() { + match 42 { + 0 { + assert (false) + } + 1 { + assert (false) + } + 42 { + println('life') + } + else { + assert (false) + } + } +} + +fn match_classic_string() { + os := 'JS' + print('V is running on ') + match os { + 'darwin' { println('macOS.') } + 'linux' { println('Linux.') } + else { println(os) } + } +} + +fn main() { + match_vec(Vec2d{42, 43}) + match_vec(Vec3d{46, 74, 21}) + match_classic_num() + match_classic_string() +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.out b/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.out new file mode 100644 index 0000000..11dc3d8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.out @@ -0,0 +1,3 @@ +Foo { x: 5 , y: 5 } +true +true \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.v b/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.v new file mode 100644 index 0000000..c061bc5 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/overloading.v @@ -0,0 +1,24 @@ +struct Foo { + x f32 + y f32 +} + +pub fn (x Foo) == (y Foo) bool { + return x.x == y.y +} + +pub fn (x Foo) < (y Foo) bool { + return x.x < y.x && x.y < y.y +} + +pub fn (a Foo) + (b Foo) Foo { + return Foo{a.x + b.x, a.y + b.y} +} + +fn main() { + x := Foo{4.0, 3.0} + y := Foo{1.0, 2.0} + println(x + y) + println(Foo{42.42, 0.0} == Foo{0.0, 42.42}) + println(x > y) +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/string.out b/v_windows/v/vlib/v/gen/js/tests/testdata/string.out new file mode 100644 index 0000000..4070999 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/string.out @@ -0,0 +1,7 @@ +ab +1000 +true +true +true +man +true diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/string.v b/v_windows/v/vlib/v/gen/js/tests/testdata/string.v new file mode 100644 index 0000000..d8ecb5e --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/string.v @@ -0,0 +1,200 @@ +struct Foo { + bar int +mut: + str string +} + +fn main() { + { + // test add + mut a := 'a' + a += 'b' + println(a) + a = 'a' + for i := 1; i < 1000; i++ { + a += 'b' + } + println(a.len) + println(a.ends_with('bbbbb')) + a += '123' + println(a.ends_with('3')) + } + { + // test ends with + a := 'browser.v' + println(a.ends_with('.v')) + + s := 'V Programming Language' + assert s.ends_with('guage') == true + assert s.ends_with('Language') == true + assert s.ends_with('Programming Language') == true + assert s.ends_with('V') == false + } + { + // test between + s := 'hello [man] how you doing' + println(s.find_between('[', ']')) + } + { + // test compare + a := 'Music' + b := 'src' + println(b >= a) + } + { + // test lt + a := '' + b := 'a' + c := 'a' + d := 'b' + e := 'aa' + f := 'ab' + + assert a < b + assert !(b < c) + assert c < d + assert !(d < e) + assert c < e + assert e < f + } + { + // test ge + a := 'aa' + b := 'aa' + c := 'ab' + d := 'abc' + e := 'aaa' + assert b >= a + assert c >= b + assert d >= c + assert !(c >= d) + assert e >= a + } + { + // test compare strings + a := 'aa' + b := 'aa' + c := 'ab' + d := 'abc' + e := 'aaa' + assert compare_strings(a, b) == 0 + assert compare_strings(b, c) == -1 + assert compare_strings(c, d) == -1 + assert compare_strings(d, e) == 1 + assert compare_strings(a, e) == -1 + assert compare_strings(e, a) == 1 + } + { + // test sort + mut vals := [ + 'arr', + 'an', + 'a', + 'any', + ] + len := vals.len + vals.sort() + assert len == vals.len + assert vals[0] == 'a' + assert vals[1] == 'an' + assert vals[2] == 'any' + assert vals[3] == 'arr' + } + { + // todo(playX): sort codegen + /*// test sort reverse + mut vals := [ + 'arr', + 'an', + 'a', + 'any', + ] + len := vals.len + vals.sort(b > a) + assert len == vals.len + assert vals[0] == 'a' + assert vals[1] == 'an' + assert vals[2] == 'any' + assert vals[3] == 'arr'*/ + } + { + // todo: split nth + /* + a := '1,2,3' + assert a.split(',').len == 3 + assert a.split_nth(',', -1).len == 3 + assert a.split_nth(',', 0).len == 3 + assert a.split_nth(',', 1).len == 1 + assert a.split_nth(',', 2).len == 2 + assert a.split_nth(',', 10).len == 3 + b := '1::2::3' + assert b.split('::').len == 3 + assert b.split_nth('::', -1).len == 3 + assert b.split_nth('::', 0).len == 3 + assert b.split_nth('::', 1).len == 1 + assert b.split_nth('::', 2).len == 2 + assert b.split_nth('::', 10).len == 3 + c := 'ABCDEF' + println(c.split('').len) + assert c.split('').len == 6 + assert c.split_nth('', 3).len == 3 + assert c.split_nth('BC', -1).len == 2 + d := ',' + assert d.split(',').len == 2 + assert d.split_nth('', 3).len == 1 + assert d.split_nth(',', -1).len == 2 + assert d.split_nth(',', 3).len == 2 + e := ',,,0,,,,,a,,b,' + assert e.split(',,').len == 5 + assert e.split_nth(',,', 3).len == 3 + assert e.split_nth(',', -1).len == 12 + assert e.split_nth(',', 3).len == 3 + */ + } + { + // test split + mut s := 'volt/twitch.v:34' + mut vals := s.split(':') + assert vals.len == 2 + assert vals[0] == 'volt/twitch.v' + assert vals[1] == '34' + // ///////// + s = '2018-01-01z13:01:02' + vals = s.split('z') + assert vals.len == 2 + assert vals[0] == '2018-01-01' + assert vals[1] == '13:01:02' + // ////////// + s = '4627a862c3dec29fb3182a06b8965e0025759e18___1530207969___blue' + vals = s.split('___') + assert vals.len == 3 + assert vals[0] == '4627a862c3dec29fb3182a06b8965e0025759e18' + assert vals[1] == '1530207969' + assert vals[2] == 'blue' + // ///////// + s = 'lalala' + vals = s.split('a') + assert vals.len == 4 + assert vals[0] == 'l' + assert vals[1] == 'l' + assert vals[2] == 'l' + assert vals[3] == '' + // ///////// + s = 'awesome' + a := s.split('') + assert a.len == 7 + assert a[0] == 'a' + assert a[1] == 'w' + assert a[2] == 'e' + assert a[3] == 's' + assert a[4] == 'o' + assert a[5] == 'm' + assert a[6] == 'e' + // ///////// + s = 'wavy turquoise bags' + vals = s.split(' bags') + assert vals.len == 2 + assert vals[0] == 'wavy turquoise' + assert vals[1] == '' + } +} diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.out b/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.out new file mode 100644 index 0000000..de2a355 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.out @@ -0,0 +1,3 @@ +Hello V developer + Hello V +Hello V \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.v b/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.v new file mode 100644 index 0000000..1594b48 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/string_methods.v @@ -0,0 +1,3 @@ +println('d Hello V developer'.trim_left(' d')) +println(' Hello V d'.trim_right(' d')) +println('WorldHello V'.trim_prefix('World')) diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/u64.out b/v_windows/v/vlib/v/gen/js/tests/testdata/u64.out new file mode 100644 index 0000000..99738c4 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/u64.out @@ -0,0 +1,4 @@ +18446744073709551615 +0 +true +25600000 \ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/js/tests/testdata/u64.v b/v_windows/v/vlib/v/gen/js/tests/testdata/u64.v new file mode 100644 index 0000000..2f9e0f0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/js/tests/testdata/u64.v @@ -0,0 +1,7 @@ +fn main() { + println(u64(18446744073709551615)) + println(u64(18446744073709551615) + 1) + + println(u64(42) == u64(42)) + println(u64(100000) * 256) +} diff --git a/v_windows/v/vlib/v/gen/native/amd64.v b/v_windows/v/vlib/v/gen/native/amd64.v new file mode 100644 index 0000000..2dea254 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/amd64.v @@ -0,0 +1,1425 @@ +module native + +import term +import v.ast +import v.token + +pub struct Amd64 { +mut: + g Gen + // arm64 specific stuff for code generation +} + +// The registers are ordered for faster generation +// push rax => 50 +// push rcx => 51 etc +enum Register { + rax + rcx + rdx + rbx + rsp + rbp + rsi + rdi + eax + edi + edx + r8 + r9 + r10 + r11 + r12 + r13 + r14 + r15 +} + +const ( + fn_arg_registers = [Register.rdi, .rsi, .rdx, .rcx, .r8, .r9] + amd64_cpuregs = ['eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi'] +) + +fn (mut g Gen) dec(reg Register) { + g.write16(0xff48) + match reg { + .rax { g.write8(0xc8) } + .rbx { g.write8(0xcb) } + .rcx { g.write8(0xc9) } + .rsi { g.write8(0xce) } + .rdi { g.write8(0xcf) } + .r12 { g.write8(0xc4) } + else { panic('unhandled inc $reg') } + } + g.println('dec $reg') +} + +[inline] +fn byt(n int, s int) byte { + return byte((n >> (s * 8)) & 0xff) +} + +fn (mut g Gen) inc(reg Register) { + g.write16(0xff49) + match reg { + .rcx { g.write8(0xc1) } + .r12 { g.write8(0xc4) } + else { panic('unhandled inc $reg') } + } + g.println('inc $reg') +} + +fn (mut g Gen) cmp(reg Register, size Size, val i64) { + // Second byte depends on the size of the value + match size { + ._8 { + g.write8(0x48) + g.write8(0x83) + } + ._32 { + g.write8(0x4a) + g.write8(0x81) + } + else { + panic('unhandled cmp') + } + } + // Third byte depends on the register being compared to + match reg { + .r12 { g.write8(0xfc) } + .rsi { g.write8(0x3f) } + .eax { g.write8(0xf8) } + .rbx { g.write8(0xfb) } + else { panic('unhandled cmp') } + } + match size { + ._8 { + g.write8(int(val)) + } + ._32 { + g.write32(int(val)) + } + else { + panic('unhandled cmp') + } + } + g.println('cmp $reg, $val') +} + +/* +rax // 0 + rcx // 1 + rdx // 2 + rbx // 3 + rsp // 4 + rbp // 5 + rsi // 6 + rdi // 7 +*/ +// `a == 1` +// `cmp DWORD [rbp-0x4],0x1` +fn (mut g Gen) cmp_var(var_name string, val int) { + g.write8(0x81) // 83 for 1 byte? + g.write8(0x7d) + offset := g.get_var_offset(var_name) + g.write8(0xff - offset + 1) + g.write32(val) + g.println('cmp var `$var_name` $val') +} + +// `add DWORD [rbp-0x4], 1` +fn (mut g Gen) inc_var(var_name string) { + g.write16(0x4581) // 83 for 1 byte + offset := g.get_var_offset(var_name) + g.write8(0xff - offset + 1) + g.write32(1) + g.println('inc_var `$var_name`') +} + +enum JumpOp { + je = 0x840f + jne = 0x850f + jg = 0x8f0f + jge = 0x8d0f + lt = 0x8c0f + jle = 0x8e0f +} + +fn (mut g Gen) cjmp(op JumpOp) int { + g.write16(u16(op)) + pos := g.pos() + g.write32(placeholder) + g.println('$op') + return int(pos) +} + +// Returns the position of the address to jump to (set later). + +fn (mut g Gen) jmp(addr int) { + g.write8(0xe9) + g.write32(addr) // 0xffffff + g.println('jmp') +} + +fn abs(a i64) i64 { + return if a < 0 { -a } else { a } +} + +fn (mut g Gen) tmp_jle(addr i64) { + // Calculate the relative offset to jump to + // (`addr` is absolute address) + offset := 0xff - int(abs(addr - g.buf.len)) - 1 + g.write8(0x7e) + g.write8(offset) + g.println('jle') +} + +fn (mut g Gen) jl(addr i64) { + offset := 0xff - int(abs(addr - g.buf.len)) - 1 + g.write8(0x7c) + g.write8(offset) + g.println('jl') +} + +fn (g &Gen) abs_to_rel_addr(addr i64) int { + return int(abs(addr - g.buf.len)) - 1 +} + +/* +fn (mut g Gen) jmp(addr i64) { + offset := 0xff - g.abs_to_rel_addr(addr) + g.write8(0xe9) + g.write8(offset) +} +*/ +fn (mut g Gen) mov64(reg Register, val i64) { + match reg { + .rax { + g.write8(0x48) + g.write8(0xb8) + } + .rcx { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc1) + } + .rbx { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc3) + } + .rsi { + g.write8(0x48) + g.write8(0xbe) + } + else { + eprintln('unhandled mov64 $reg') + } + } + g.write64(val) + g.println('mov64 $reg, $val') +} + +fn (mut g Gen) mov_reg_to_var(var_offset int, reg Register) { + // 89 7d fc mov DWORD PTR [rbp-0x4],edi + match reg { + .rax, .rsi { + g.write8(0x48) + } + else {} + } + g.write8(0x89) + match reg { + .eax, .rax { g.write8(0x45) } + .edi, .rdi { g.write8(0x7d) } + .rsi { g.write8(0x75) } + .rdx { g.write8(0x55) } + .rcx { g.write8(0x4d) } + else { g.n_error('mov_from_reg $reg') } + } + g.write8(0xff - var_offset + 1) + g.println('mov DWORD PTR[rbp-$var_offset.hex2()],$reg') +} + +fn (mut g Gen) mov_var_to_reg(reg Register, var_offset int) { + // 8b 7d f8 mov edi,DWORD PTR [rbp-0x8] + match reg { + .rax, .rsi { + g.write8(0x48) + } + else {} + } + g.write8(0x8b) + match reg { + .eax, .rax { g.write8(0x45) } + .edi, .rdi { g.write8(0x7d) } + .rsi { g.write8(0x75) } + .rdx { g.write8(0x55) } + .rbx { g.write8(0x5d) } + .rcx { g.write8(0x4d) } + else { g.n_error('mov_var_to_reg $reg') } + } + g.write8(0xff - var_offset + 1) + g.println('mov $reg,DWORD PTR[rbp-$var_offset.hex2()]') +} + +fn (mut g Gen) call(addr int) { + if g.pref.arch == .arm64 { + g.bl() + return + } + // Need to calculate the difference between current position (position after the e8 call) + // and the function to call. + // +5 is to get the posistion "e8 xx xx xx xx" + // Not sure about the -1. + rel := 0xffffffff - (g.buf.len + 5 - addr - 1) + // println('call addr=$addr.hex2() rel_addr=$rel.hex2() pos=$g.buf.len') + g.write8(0xe8) + g.write32(rel) + g.println('call $addr') +} + +fn (mut g Gen) syscall() { + // g.write(0x050f) + g.write8(0x0f) + g.write8(0x05) + g.println('syscall') +} + +pub fn (mut g Gen) ret() { + g.write8(0xc3) + g.println('ret') +} + +pub fn (mut g Gen) push(reg Register) { + if int(reg) < int(Register.r8) { + g.write8(0x50 + int(reg)) + } else { + g.write8(0x41) + g.write8(0x50 + int(reg) - 8) + } + /* + match reg { + .rbp { g.write8(0x55) } + else {} + } + */ + g.println('push $reg') +} + +pub fn (mut g Gen) pop(reg Register) { + g.write8(0x58 + int(reg)) + // TODO r8... + g.println('pop $reg') +} + +pub fn (mut g Gen) sub32(reg Register, val int) { + g.write8(0x48) + g.write8(0x81) + g.write8(0xe8 + int(reg)) // TODO rax is different? + g.write32(val) + g.println('sub32 $reg,$val.hex2()') +} + +pub fn (mut g Gen) sub8(reg Register, val int) { + g.write8(0x48) + g.write8(0x83) + g.write8(0xe8 + int(reg)) // TODO rax is different? + g.write8(val) + g.println('sub8 $reg,$val.hex2()') +} + +pub fn (mut g Gen) sub(reg Register, val int) { + g.write8(0x48) + g.write8(0x81) + g.write8(0xe8 + int(reg)) // TODO rax is different? + g.write32(val) + g.println('add $reg,$val.hex2()') +} + +pub fn (mut g Gen) add(reg Register, val int) { + if reg != .rax { + panic('add only works with .rax') + } + g.write8(0x48) + g.write8(0x05) + g.write32(val) + g.println('add $reg,$val.hex2()') +} + +pub fn (mut g Gen) add8(reg Register, val int) { + g.write8(0x48) + g.write8(0x83) + // g.write8(0xe8 + reg) // TODO rax is different? + g.write8(0xc4) + g.write8(val) + g.println('add8 $reg,$val.hex2()') +} + +fn (mut g Gen) add8_var(reg Register, var_offset int) { + g.write8(0x03) + match reg { + .eax, .rax { g.write8(0x45) } + else { g.n_error('add8_var') } + } + g.write8(0xff - var_offset + 1) + g.println('add8 $reg,DWORD PTR[rbp-$var_offset.hex2()]') +} + +fn (mut g Gen) sub8_var(reg Register, var_offset int) { + g.write8(0x2b) + match reg { + .eax, .rax { g.write8(0x45) } + else { g.n_error('sub8_var') } + } + g.write8(0xff - var_offset + 1) + g.println('sub8 $reg,DWORD PTR[rbp-$var_offset.hex2()]') +} + +fn (mut g Gen) div8_var(reg Register, var_offset int) { + if reg == .rax || reg == .eax { + g.mov_var_to_reg(.rbx, var_offset) + g.div_reg(.rax, .rbx) + g.mov_reg_to_var(var_offset, .rax) + } else { + panic('div8_var invalid source register') + } +} + +fn (mut g Gen) mul8_var(reg Register, var_offset int) { + g.write8(0x0f) + g.write8(0xaf) + match reg { + .eax, .rax { g.write8(0x45) } + else { g.n_error('mul8_var') } + } + g.write8(0xff - var_offset + 1) + g.println('mul8 $reg,DWORD PTR[rbp-$var_offset.hex2()]') +} + +fn (mut g Gen) leave() { + g.write8(0xc9) + g.println('leave') +} + +// returns label's relative address +pub fn (mut g Gen) gen_loop_start(from int) int { + g.mov(.r12, from) + label := g.buf.len + g.inc(.r12) + return label +} + +pub fn (mut g Gen) gen_loop_end(to int, label int) { + g.cmp(.r12, ._8, to) + g.jl(label) +} + +pub fn (mut g Gen) save_main_fn_addr() { + g.main_fn_addr = i64(g.buf.len) +} + +pub fn (mut g Gen) allocate_string(s string, opsize int) int { + g.strings << s + str_pos := g.buf.len + opsize + g.str_pos << str_pos + return 0 +} + +pub fn (mut g Gen) cld_repne_scasb() { + g.write8(0xfc) + g.println('cld') + g.write8(0xf2) + g.write8(0xae) + g.println('repne scasb') +} + +pub fn (mut g Gen) xor(r Register, v int) { + if v == -1 { + match r { + .rcx { + g.write8(0x48) + g.write8(0x83) + g.write8(0xf1) + g.write8(0xff) + g.println('xor rcx, -1') + } + else { + g.n_error('unhandled xor') + } + } + } else { + g.n_error('unhandled xor') + } +} + +// return length in .rax of string pointed by given register +pub fn (mut g Gen) inline_strlen(r Register) { + g.mov_reg(.rdi, r) + g.mov(.rcx, -1) + g.mov(.eax, 0) + g.cld_repne_scasb() + g.xor(.rcx, -1) + g.dec(.rcx) + g.mov_reg(.rax, .rcx) + g.println('strlen rax, $r') +} + +// TODO: strlen of string at runtime +pub fn (mut g Gen) gen_print_reg(r Register, n int, fd int) { + mystrlen := true + g.mov_reg(.rsi, r) + if mystrlen { + g.inline_strlen(.rsi) + g.mov_reg(.rdx, .rax) + } else { + g.mov(.edx, n) + } + g.mov(.eax, g.nsyscall_write()) + g.mov(.edi, fd) + g.syscall() +} + +pub fn (mut g Gen) gen_print(s string, fd int) { + // + // qq := s + '\n' + // + g.mov(.eax, g.nsyscall_write()) + g.mov(.edi, fd) + // segment_start + 0x9f) // str pos // placeholder + g.mov64(.rsi, g.allocate_string(s, 2)) // for rsi its 2 + g.mov(.edx, s.len) // len + g.syscall() +} + +fn (mut g Gen) nsyscall_write() int { + match g.pref.os { + .linux { + return 1 + } + .macos { + return 0x2000004 + } + else { + g.n_error('unsupported exit syscall for this platform') + } + } + return 0 +} + +fn (mut g Gen) nsyscall_exit() int { + match g.pref.os { + .linux { + return 60 + } + .macos { + return 0x2000001 + } + else { + g.n_error('unsupported exit syscall for this platform') + } + } + return 0 +} + +pub fn (mut a Amd64) gen_exit(mut g Gen, node ast.Expr) { + g.gen_amd64_exit(node) +} + +pub fn (mut g Gen) gen_amd64_exit(expr ast.Expr) { + // ret value + match expr { + ast.CallExpr { + right := expr.return_type + g.n_error('native exit builtin: Unsupported call $right') + } + ast.Ident { + var_offset := g.get_var_offset(expr.name) + g.mov_var_to_reg(.edi, var_offset) + } + ast.IntegerLiteral { + g.mov(.edi, expr.val.int()) + } + else { + g.n_error('native builtin exit expects a numeric argument') + } + } + g.mov(.eax, g.nsyscall_exit()) + g.syscall() +} + +fn (mut g Gen) mov(reg Register, val int) { + if val == -1 { + match reg { + .rax { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc0) + g.write32(-1) + return + } + .rcx { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc1) + g.write32(-1) + return + } + else { + g.n_error('unhandled mov $reg, -1') + } + } + } + if val == 0 { + // Optimise to xor reg, reg when val is 0 + match reg { + .eax, .rax { + g.write8(0x31) + g.write8(0xc0) + } + .edi, .rdi { + g.write8(0x31) + g.write8(0xff) + } + .rcx { + g.write8(0x48) + g.write8(0x31) + g.write8(0xc7) + } + .rdx { + g.write8(0x48) + g.write8(0x31) + g.write8(0xd2) + } + .edx { + g.write8(0x31) + g.write8(0xd2) + } + .rsi { + g.write8(0x48) + g.write8(0x31) + g.write8(0xf6) + } + .r12 { + g.write8(0x4d) + g.write8(0x31) + g.write8(0xe4) + } + else { + g.n_error('unhandled mov $reg, $reg') + } + } + g.println('xor $reg, $reg') + } else { + match reg { + .eax, .rax { + g.write8(0xb8) + } + .edi, .rdi { + g.write8(0xbf) + } + .rcx { + g.write8(0xc7) + } + .edx { + g.write8(0xba) + } + .rsi { + g.write8(0x48) + g.write8(0xbe) + } + .r12 { + g.write8(0x41) + g.write8(0xbc) // r11 is 0xbb etc + } + else { + g.n_error('unhandled mov $reg') + } + } + g.write32(val) + g.println('mov $reg, $val') + } +} + +fn (mut g Gen) mul_reg(a Register, b Register) { + if a != .rax { + panic('mul always operates on rax') + } + match b { + .rax { + g.write8(0x48) + g.write8(0xf7) + g.write8(0xe8) + } + .rbx { + g.write8(0x48) + g.write8(0xf7) + g.write8(0xeb) + } + else { + panic('unhandled div $a') + } + } + g.println('mul $a') +} + +fn (mut g Gen) div_reg(a Register, b Register) { + if a != .rax { + panic('div always operates on rax') + } + match b { + .rax { + g.write8(0x48) + g.write8(0xf7) + g.write8(0xf8) + } + .rbx { + g.mov(.edx, 0) + g.write8(0x48) + g.write8(0xf7) + g.write8(0xfb) // idiv ebx + } + else { + panic('unhandled div $a') + } + } + g.println('div $a') +} + +fn (mut g Gen) sub_reg(a Register, b Register) { + if a == .rax && b == .rbx { + g.write8(0x48) + g.write8(0x29) + g.write8(0xd8) + } else { + panic('unhandled add $a, $b') + } + g.println('sub $a, $b') +} + +fn (mut g Gen) add_reg(a Register, b Register) { + if a == .rax && b == .rbx { + g.write8(0x48) + g.write8(0x01) + g.write8(0xd8) + } else if a == .rax && b == .rdi { + g.write8(0x48) + g.write8(0x01) + g.write8(0xf8) + } else { + panic('unhandled add $a, $b') + } + g.println('add $a, $b') +} + +fn (mut g Gen) mov_reg(a Register, b Register) { + if a == .rbp && b == .rsp { + g.write8(0x48) + g.write8(0x89) + } else if a == .rdx && b == .rax { + g.write8(0x48) + g.write8(0x89) + g.write8(0xc2) + } else if a == .rax && b == .rcx { + g.write8(0x48) + g.write8(0x89) + g.write8(0xc8) + } else if a == .rax && b == .rdi { + g.write8(0x48) + g.write8(0x89) + g.write8(0xf8) + } else if a == .rdi && b == .rsi { + g.write8(0x48) + g.write8(0x89) + g.write8(0xf7) + } else if a == .rsi && b == .rax { + g.write8(0x48) + g.write8(0x89) + g.write8(0xc6) + } else { + g.n_error('unhandled mov_reg combination for $a $b') + } + g.println('mov $a, $b') +} + +// generates `mov rbp, rsp` +fn (mut g Gen) mov_rbp_rsp() { + g.write8(0x48) + g.write8(0x89) + g.write8(0xe5) + g.println('mov rbp, rsp') +} + +pub fn (mut g Gen) call_fn(node ast.CallExpr) { + if g.pref.arch == .arm64 { + g.call_fn_arm64(node) + return + } + name := node.name + mut n := name + if !n.contains('.') { + n = 'main.$n' + } + eprintln('call fn ($n)') + addr := g.fn_addr[n] + if addr == 0 { + g.n_error('fn addr of `$name` = 0') + } + // Copy values to registers (calling convention) + // g.mov(.eax, 0) + for i in 0 .. node.args.len { + expr := node.args[i].expr + match expr { + ast.IntegerLiteral { + // `foo(2)` => `mov edi,0x2` + g.mov(native.fn_arg_registers[i], expr.val.int()) + } + ast.Ident { + // `foo(x)` => `mov edi,DWORD PTR [rbp-0x8]` + var_offset := g.get_var_offset(expr.name) + if g.pref.is_verbose { + println('i=$i fn name= $name offset=$var_offset') + println(int(native.fn_arg_registers[i])) + } + g.mov_var_to_reg(native.fn_arg_registers[i], var_offset) + } + else { + g.v_error('unhandled call_fn (name=$name) node: $expr.type_name()', node.pos) + } + } + } + if node.args.len > 6 { + g.v_error('more than 6 args not allowed for now', node.pos) + } + g.call(int(addr)) + g.println('fn call `${name}()`') + // println('call $name $addr') +} + +fn (mut g Gen) assign_stmt(node ast.AssignStmt) { + // `a := 1` | `a,b := 1,2` + for i, left in node.left { + right := node.right[i] + name := left.str() + // if left is ast.Ident { + // ident := left as ast.Ident + match right { + ast.IntegerLiteral { + // g.allocate_var(name, 4, right.val.int()) + match node.op { + .plus_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.add(.rax, right.val.int()) + g.mov_reg_to_var(dest, .rax) + } + .minus_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.mov_var_to_reg(.rbx, g.get_var_offset(name)) + g.sub_reg(.rax, .rbx) + g.mov_reg_to_var(dest, .rax) + } + .mult_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.mov_var_to_reg(.rbx, g.get_var_offset(name)) + g.mul_reg(.rax, .rbx) + g.mov_reg_to_var(dest, .rax) + } + .div_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.mov_var_to_reg(.rbx, g.get_var_offset(name)) + g.div_reg(.rax, .rbx) + g.mov_reg_to_var(dest, .rax) + } + .decl_assign { + g.allocate_var(name, 4, right.val.int()) + } + .assign { + match node.left_types[i] { + 7 { // ast.IndexExpr { + ie := node.left[i] as ast.IndexExpr + bracket := name.index('[') or { + g.v_error('bracket expected', node.pos) + exit(1) + } + var_name := name[0..bracket] + mut dest := g.get_var_offset(var_name) + index := ie.index as ast.IntegerLiteral + dest += index.val.int() * 8 + // TODO check if out of bounds access + g.mov(.rax, right.val.int()) + g.mov_reg_to_var(dest, .rax) + // eprintln('${var_name}[$index] = ${right.val.int()}') + } + else { + tn := node.left[i].type_name() + dump(node.left_types) + g.n_error('unhandled assign type: $tn') + } + } + } + else { + eprintln('ERROR 2') + dump(node) + } + } + } + ast.InfixExpr { + // eprintln('infix') dump(node) dump(right) + g.infix_expr(right) + offset := g.allocate_var(name, 4, 0) + // `mov DWORD PTR [rbp-0x8],eax` + if g.pref.is_verbose { + println('infix assignment $name offset=$offset.hex2()') + } + g.mov_reg_to_var(offset, .eax) + } + ast.Ident { + // eprintln('identr') dump(node) dump(right) + match node.op { + .plus_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.add8_var(.rax, g.get_var_offset(right.name)) + g.mov_reg_to_var(dest, .rax) + } + .minus_assign { + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.mov_var_to_reg(.rbx, g.get_var_offset(right.name)) + g.sub_reg(.rax, .rbx) + g.mov_reg_to_var(dest, .rax) + } + .div_assign { + // this should be called when `a /= b` but it's not :? + dest := g.get_var_offset(name) + g.mov_var_to_reg(.rax, dest) + g.mov_var_to_reg(.rbx, g.get_var_offset(right.name)) + g.div_reg(.rax, .rbx) + g.mov_reg_to_var(dest, .rax) + } + .decl_assign { + dest := g.allocate_var(name, 4, 0) + g.mov_var_to_reg(.rax, g.get_var_offset(right.name)) + g.mov_reg_to_var(dest, .rax) + } + .assign { + g.mov_var_to_reg(.rax, g.get_var_offset(right.name)) + g.mov_reg_to_var(g.get_var_offset(name), .rax) + } + else { + eprintln('TODO: unhandled assign ident case') + dump(node) + } + } + // a += b + } + ast.StructInit { + sym := g.table.get_type_symbol(right.typ) + info := sym.info as ast.Struct + for field in info.fields { + field_name := name + '.' + field.name + println(field_name) + g.allocate_var(field_name, 4, 0) + } + } + ast.ArrayInit { + // check if array is empty + mut pos := g.allocate_array(name, 8, right.exprs.len) + // allocate array of right.exprs.len vars + for e in right.exprs { + match e { + ast.IntegerLiteral { + g.mov(.rax, e.val.int()) + g.mov_reg_to_var(pos, .rax) + pos += 8 + } + ast.StringLiteral { + g.mov64(.rsi, g.allocate_string('$e.val', 2)) // for rsi its 2 + g.mov_reg_to_var(pos, .rsi) + pos += 8 + } + else { + dump(e) + g.n_error('unhandled array init type') + } + } + } + } + ast.IndexExpr { + // a := arr[0] + offset := g.allocate_var(name, 4, 0) + if g.pref.is_verbose { + println('infix assignment $name offset=$offset.hex2()') + } + ie := node.right[i] as ast.IndexExpr + var_name := ie.left.str() + mut dest := g.get_var_offset(var_name) + if ie.index is ast.IntegerLiteral { + index := ie.index + dest += index.val.int() * 8 + g.mov_var_to_reg(.rax, dest) + } else if ie.index is ast.Ident { + ident := ie.index + var_offset := g.get_var_offset(ident.name) + g.mov_var_to_reg(.edi, var_offset) + g.mov_var_to_reg(.rax, dest) + g.add_reg(.rax, .rdi) + } else { + g.n_error('only integers and idents can be used as indexes') + } + // TODO check if out of bounds access + g.mov_reg_to_var(offset, .eax) + } + ast.StringLiteral { + dest := g.allocate_var(name, 4, 0) + ie := node.right[i] as ast.StringLiteral + g.mov64(.rsi, g.allocate_string(ie.str(), 2)) // for rsi its 2 + g.mov_reg_to_var(dest, .rsi) + } + ast.CallExpr { + dest := g.allocate_var(name, 4, 0) + g.call_fn(right) + g.mov_reg_to_var(dest, .rax) + g.mov_var_to_reg(.rsi, dest) + } + ast.GoExpr { + g.v_error('threads not implemented for the native backend', node.pos) + } + ast.CastExpr { + g.warning('cast expressions are work in progress', right.pos) + match right.typname { + 'u64' { + g.allocate_var(name, 8, right.expr.str().int()) + } + 'int' { + g.allocate_var(name, 4, right.expr.str().int()) + } + else { + g.v_error('unsupported cast type $right.typ', node.pos) + } + } + } + else { + // dump(node) + g.v_error('unhandled assign_stmt expression: $right.type_name()', right.position()) + } + } + // } + } +} + +fn (mut g Gen) infix_expr(node ast.InfixExpr) { + if g.pref.is_verbose { + println('infix expr op=$node.op') + } + // TODO + if node.left is ast.InfixExpr { + g.n_error('only simple expressions are supported right now (not more than 2 operands)') + } + match mut node.left { + ast.Ident { + g.mov_var_to_reg(.eax, g.get_var_offset(node.left.name)) + } + else {} + } + if mut node.right is ast.Ident { + var_offset := g.get_var_offset(node.right.name) + match node.op { + .plus { g.add8_var(.eax, var_offset) } + .mul { g.mul8_var(.eax, var_offset) } + .div { g.div8_var(.eax, var_offset) } + .minus { g.sub8_var(.eax, var_offset) } + else {} + } + } +} + +fn (mut g Gen) trap() { + // funnily works on x86 and arm64 + g.write32(0xcccccccc) + g.println('trap') +} + +fn (mut g Gen) gen_asm_stmt(asm_node ast.AsmStmt) { + if g.pref.arch == .arm64 { + g.gen_asm_stmt_arm64(asm_node) + } else { + g.gen_asm_stmt_amd64(asm_node) + } +} + +fn (mut g Gen) gen_asm_stmt_amd64(asm_node ast.AsmStmt) { + // inline assembly using vasm + g.println('// asm inline') + mut reg := 0 + mut imm := 0 + mut regname := '' + // dump(asm_node) + for t in asm_node.templates { + mut line := t.name + mut comma := false + for a in t.args { + if comma { + line += ', ' + } else { + comma = true + } + match a { + ast.AsmRegister { + regname = a.name + reg = native.amd64_cpuregs.index(regname) + line += a.typ.str() + } + ast.IntegerLiteral { + line += a.val + imm = a.val.int() + } + ast.BoolLiteral { + line += a.val.str() + imm = if a.val { 1 } else { 0 } + } + /* + ast.AsmAddressing { + } + ast.AsmAlias { + } + ast.AsmDisp { + } + ast.CharLiteral { + } + ast.FloatLiteral { + } + */ + string { + // XXX + g.v_error('no strings allowed in this context', asm_node.pos) + } + else { + g.v_error('unsupported instruction argument argument', asm_node.pos) + } + } + } + g.println(': $line') + match t.name { + 'nop' { + g.write8(byte(0x90)) + } + 'syscall' { + g.write8(byte(0x0f)) + g.write8(byte(0x05)) + } + 'ret' { + g.write8(byte(0xc3)) + } + 'int3' { + g.write8(byte(0xcc)) + g.write8(byte(imm)) + } + 'sti' { + g.write8(byte(0xfb)) + } + 'cli' { + g.write8(byte(0xfa)) + } + 'int' { + g.write8(byte(0xcd)) + g.write8(byte(imm)) + } + 'cpuid' { + g.write8(byte(0x0f)) + g.write8(byte(0xa2)) + } + 'mov' { + g.write8(byte(0xb8 + reg)) + g.write8(byt(imm, 0)) + g.write8(byt(imm, 1)) + g.write8(byt(imm, 2)) + g.write8(byt(imm, 3)) + } + else { + g.v_error('unsupported instruction $t.name', asm_node.pos) + } + } + } +} + +fn (mut g Gen) gen_assert(assert_node ast.AssertStmt) { + mut cjmp_addr := 0 + mut ine := ast.InfixExpr{} + ane := assert_node.expr + if ane is ast.ParExpr { // assert(1==1) + ine = ane.expr as ast.InfixExpr + } else if ane is ast.InfixExpr { // assert 1==1 + ine = ane + } else { + g.n_error('Unsupported expression in assert') + } + cjmp_addr = g.condition(ine, true) + g.expr(assert_node.expr) + g.trap() + g.write32_at(cjmp_addr, int(g.pos() - cjmp_addr - 4)) // 4 is for "00 00 00 00" +} + +fn (mut g Gen) cjmp_notop(op token.Kind) int { + return match op { + .gt { + g.cjmp(.jle) + } + .lt { + g.cjmp(.jge) + } + .ne { + g.cjmp(.je) + } + .eq { + g.cjmp(.jne) + } + else { + g.cjmp(.je) + } + } +} + +fn (mut g Gen) cjmp_op(op token.Kind) int { + return match op { + .gt { + g.cjmp(.jg) + } + .lt { + g.cjmp(.lt) + } + .ne { + g.cjmp(.jne) + } + .eq { + g.cjmp(.je) + } + else { + g.cjmp(.jne) + } + } +} + +fn (mut g Gen) condition(infix_expr ast.InfixExpr, neg bool) int { + match mut infix_expr.left { + ast.IntegerLiteral { + match mut infix_expr.right { + ast.IntegerLiteral { + // 3 < 4 + a0 := infix_expr.left.val.int() + // a0 := (infix_expr.left as ast.IntegerLiteral).val.int() + a1 := (infix_expr.right as ast.IntegerLiteral).val.int() + // TODO. compute at compile time + g.mov(.eax, a0) + g.cmp(.eax, ._32, a1) + } + ast.Ident { + // 3 < var + // lit := infix_expr.right as ast.IntegerLiteral + // g.cmp_var(infix_expr.left.name, lit.val.int()) + // +not + g.n_error('unsupported if construction') + } + else { + g.n_error('unsupported if construction') + } + } + } + ast.Ident { + match mut infix_expr.right { + ast.IntegerLiteral { + // var < 4 + lit := infix_expr.right as ast.IntegerLiteral + g.cmp_var(infix_expr.left.name, lit.val.int()) + } + ast.Ident { + // var < var2 + g.n_error('unsupported if construction') + } + else { + g.n_error('unsupported if construction') + } + } + } + else { + // dump(infix_expr) + g.n_error('unhandled $infix_expr.left') + } + } + + // mut cjmp_addr := 0 // location of `jne *00 00 00 00*` + return if neg { g.cjmp_op(infix_expr.op) } else { g.cjmp_notop(infix_expr.op) } +} + +fn (mut g Gen) if_expr(node ast.IfExpr) { + branch := node.branches[0] + infix_expr := branch.cond as ast.InfixExpr + cjmp_addr := g.condition(infix_expr, false) + if node.is_comptime { + g.n_error('ignored comptime') + } + g.stmts(branch.stmts) + // Now that we know where we need to jump if the condition is false, update the `jne` call. + // The value is the relative address, difference between current position and the location + // after `jne 00 00 00 00` + // println('after if g.pos=$g.pos() jneaddr=$cjmp_addr') + g.write32_at(cjmp_addr, int(g.pos() - cjmp_addr - 4)) // 4 is for "00 00 00 00" + + if node.has_else { + g.n_error('else statements not yet supported') + } +} + +fn (mut g Gen) infloop() { + if g.pref.arch == .arm64 { + g.write32(byte(0x14)) + } else { + g.write8(byte(0xeb)) + g.write8(byte(0xfe)) + } + g.println('jmp $$') +} + +fn (mut g Gen) for_stmt(node ast.ForStmt) { + if node.is_inf { + if node.stmts.len == 0 { + g.infloop() + return + } + // infinite loop + start := g.pos() + g.stmts(node.stmts) + g.jmp(int(0xffffffff - (g.pos() + 5 - start) + 1)) + g.println('jmp after infinite for') + return + } + infix_expr := node.cond as ast.InfixExpr + // g.mov(.eax, 0x77777777) + mut jump_addr := 0 // location of `jne *00 00 00 00*` + start := g.pos() + match mut infix_expr.left { + ast.Ident { + lit := infix_expr.right as ast.IntegerLiteral + g.cmp_var(infix_expr.left.name, lit.val.int()) + jump_addr = g.cjmp(.jge) + } + else { + g.n_error('unhandled infix.left') + } + } + g.stmts(node.stmts) + // Go back to `cmp ...` + // Diff between `jmp 00 00 00 00 X` and `cmp` + g.jmp(int(0xffffffff - (g.pos() + 5 - start) + 1)) + // Update the jump addr to current pos + g.write32_at(jump_addr, int(g.pos() - jump_addr - 4)) // 4 is for "00 00 00 00" + g.println('jmp after for') +} + +fn (mut g Gen) fn_decl(node ast.FnDecl) { + if g.pref.is_verbose { + println(term.green('\n$node.name:')) + } + // if g.is_method + if node.is_deprecated { + eprintln('fn_decl: $node.name is deprecated') + } + if node.is_builtin { + eprintln('fn_decl: $node.name is builtin') + } + g.stack_var_pos = 0 + is_main := node.name == 'main.main' + // println('saving addr $node.name $g.buf.len.hex2()') + if is_main { + g.save_main_fn_addr() + } else { + g.register_function_address(node.name) + } + if g.pref.arch == .arm64 { + g.fn_decl_arm64(node) + return + } + + g.push(.rbp) + g.mov_rbp_rsp() + locals_count := node.scope.objects.len + node.params.len + stackframe_size := (locals_count * 8) + 0x10 + g.sub8(.rsp, stackframe_size) + + if node.params.len > 0 { + // g.mov(.r12, 0x77777777) + } + // Copy values from registers to local vars (calling convention) + mut offset := 0 + for i in 0 .. node.params.len { + name := node.params[i].name + // TODO optimize. Right now 2 mov's are used instead of 1. + g.allocate_var(name, 4, 0) + // `mov DWORD PTR [rbp-0x4],edi` + offset += 4 + g.mov_reg_to_var(offset, native.fn_arg_registers[i]) + } + // + g.stmts(node.stmts) + if is_main { + // println('end of main: gen exit') + zero := ast.IntegerLiteral{} + g.gen_exit(zero) + g.ret() + return + } + // g.leave() + g.add8(.rsp, stackframe_size) + g.pop(.rbp) + g.ret() +} + +pub fn (mut x Amd64) allocate_var(name string, size int, initial_val int) { + // do nothing as interface call is crashing +} + +pub fn (mut g Gen) allocate_array(name string, size int, items int) int { + pos := g.allocate_var(name, size, items) + g.stack_var_pos += (size * items) + return pos +} + +pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) int { + // `a := 3` => + // `move DWORD [rbp-0x4],0x3` + match size { + 1 { + // BYTE + g.write8(0xc6) + g.write8(0x45) + } + 4 { + // DWORD + g.write8(0xc7) + g.write8(0x45) + } + 8 { + // QWORD + g.write8(0x48) + g.write8(0xc7) + g.write8(0x45) + } + else { + g.n_error('allocate_var: bad size $size') + } + } + // Generate N in `[rbp-N]` + n := g.stack_var_pos + size + g.write8(0xff - n + 1) + g.stack_var_pos += size + g.var_offset[name] = g.stack_var_pos + // Generate the value assigned to the variable + g.write32(initial_val) + // println('allocate_var(size=$size, initial_val=$initial_val)') + g.println('mov DWORD [rbp-$n.hex2()],$initial_val (Allocate var `$name`)') + return g.stack_var_pos +} diff --git a/v_windows/v/vlib/v/gen/native/arm64.v b/v_windows/v/vlib/v/gen/native/arm64.v new file mode 100644 index 0000000..22336c9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/arm64.v @@ -0,0 +1,203 @@ +module native + +import v.ast + +enum Arm64Register { + x0 // v---- + x1 // | + x2 // | + x3 // | parameter and result registers + x4 // | + x5 // | + x6 // | + x7 // ^---- + x8 // XR - indirect result location register + x9 // v---- + x10 // | + x11 // | + x12 // | caller saved registers + x13 // | + x14 // | + x15 // ^---- + x16 // IP0 - inter procedure call scratch register + x17 // IP1 - inter procedure call scratch register + x18 // PR - platform register + x19 // v---- + x20 // | + x21 // | + x22 // | + x23 // | callee saved registers + x24 // | + x25 // | + x26 // | + x27 // | + x28 // ^---- + x29 // FP - frame pointer + x30 // LR - link register +} + +pub struct Arm64 { +mut: + g Gen + // arm64 specific stuff for code generation +} + +pub fn (mut x Arm64) allocate_var(name string, size int, initial_val int) { + eprintln('TODO: allocating var on arm64 ($name) = $size = $initial_val') +} + +fn (mut g Gen) mov_arm(reg Arm64Register, val u64) { + // m := u64(0xffff) + // x := u64(val) + // println('========') + // println(x & ~m) + // println(x & ~(m << 16)) + // g.write32(0x777777) + r := int(reg) + if r == 0 && val == 1 { + g.write32(0xd2800020) + g.println('mov x0, 1') + } else if r >= 0 && r <= 16 { + g.write32(0xd2800000 + int(r) + (int(val) << 5)) + g.println('mov x$r, $val') + } else { + g.n_error('mov_arm unsupported values') + } + /* + if 1 ^ (x & ~m) != 0 { + // println('yep') + g.write32(int(u64(0x52800000) | u64(r) | x << 5)) + g.write32(0x88888888) + g.write32(int(u64(0x52800000) | u64(r) | x >> 11)) + } else if 1 ^ (x & ~(m << 16)) != 0 { + // g.write32(int(u64(0x52800000) | u64(r) | x >> 11)) + // println('yep2') + // g.write32(0x52a00000 | r | val >> 11) + } + */ +} + +pub fn (mut g Gen) fn_decl_arm64(node ast.FnDecl) { + g.gen_arm64_helloworld() + // TODO +} + +pub fn (mut g Gen) call_fn_arm64(node ast.CallExpr) { + name := node.name + // println('call fn $name') + addr := g.fn_addr[name] + if addr == 0 { + g.n_error('fn addr of `$name` = 0') + } + // Copy values to registers (calling convention) + // g.mov_arm(.eax, 0) + for i in 0 .. node.args.len { + expr := node.args[i].expr + match expr { + ast.IntegerLiteral { + // `foo(2)` => `mov edi,0x2` + // g.mov_arm(native.fn_arg_registers[i], expr.val.int()) + } + /* + ast.Ident { + // `foo(x)` => `mov edi,DWORD PTR [rbp-0x8]` + var_offset := g.get_var_offset(expr.name) + if g.pref.is_verbose { + println('i=$i fn name= $name offset=$var_offset') + println(int(native.fn_arg_registers[i])) + } + g.mov_var_to_reg(native.fn_arg_registers[i], var_offset) + } + */ + else { + g.n_error('unhandled call_fn (name=$name) node: ' + expr.type_name()) + } + } + } + if node.args.len > 6 { + g.n_error('more than 6 args not allowed for now') + } + g.call(int(addr)) + g.println('fn call `${name}()`') + // println('call $name $addr') +} + +fn (mut g Gen) gen_arm64_helloworld() { + if g.pref.os == .linux { + g.mov_arm(.x0, 1) + g.adr(.x1, 0x10) + g.mov_arm(.x2, 13) + g.mov_arm(.x8, 64) // write (linux-arm64) + g.svc() + } else { + g.mov_arm(.x0, 0) + g.mov_arm(.x16, 1) + g.svc() + } + zero := ast.IntegerLiteral{} + g.gen_exit(zero) + g.write_string('Hello World!\n') + g.write8(0) // padding? + g.write8(0) + g.write8(0) +} + +fn (mut g Gen) adr(r Arm64Register, delta int) { + g.write32(0x10000000 | int(r) | (delta << 4)) + g.println('adr $r, $delta') +} + +fn (mut g Gen) bl() { + // g.write32(0xa9400000) + g.write32(0x94000000) + g.println('bl 0') +} + +fn (mut g Gen) svc() { + g.write32(0xd4001001) + g.println('svc 0x80') +} + +pub fn (mut c Arm64) gen_exit(mut g Gen, expr ast.Expr) { + mut return_code := u64(0) + match expr { + ast.IntegerLiteral { + return_code = expr.val.u64() + } + else { + g.n_error('native builtin exit expects a numeric argument') + } + } + match c.g.pref.os { + .macos { + c.g.mov_arm(.x0, return_code) + c.g.mov_arm(.x16, 1) // syscall exit + } + .linux { + c.g.mov_arm(.x16, return_code) + c.g.mov_arm(.x8, 93) + c.g.mov_arm(.x0, 0) + } + else { + g.n_error('unsupported os $c.g.pref.os') + } + } + g.svc() +} + +pub fn (mut g Gen) gen_arm64_exit(expr ast.Expr) { + match expr { + ast.IntegerLiteral { + g.mov_arm(.x16, expr.val.u64()) + } + else { + g.n_error('native builtin exit expects a numeric argument') + } + } + g.mov_arm(.x0, 0) + g.svc() +} + +fn (mut g Gen) gen_asm_stmt_arm64(asm_node ast.AsmStmt) { + g.v_error('The asm statement for arm64 not yet implemented', asm_node.pos) +} diff --git a/v_windows/v/vlib/v/gen/native/elf.v b/v_windows/v/vlib/v/gen/native/elf.v new file mode 100644 index 0000000..88084fe --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/elf.v @@ -0,0 +1,113 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module native + +const ( + mag0 = byte(0x7f) + mag1 = `E` + mag2 = `L` + mag3 = `F` + ei_class = 4 + elfclass64 = 2 + elfdata2lsb = 1 + ev_current = 1 +) + +// ELF file types +const ( + elf_osabi = 0 + et_rel = 1 + et_exec = 2 + et_dyn = 3 + e_machine_amd64 = 0x3e + e_machine_arm64 = 0xb7 + shn_xindex = 0xffff + sht_null = 0 +) + +const ( + segment_start = 0x400000 + placeholder = 0 + sevens = 0x77777777 +) + +pub fn (mut g Gen) generate_elf_header() { + g.buf << [byte(native.mag0), native.mag1, native.mag2, native.mag3] + g.buf << native.elfclass64 // file class + g.buf << native.elfdata2lsb // data encoding + g.buf << native.ev_current // file version + g.buf << native.elf_osabi + g.write64(0) // et_rel) // et_rel for .o + g.write16(2) // e_type + if g.pref.arch == .arm64 { + g.write16(native.e_machine_arm64) + } else { + g.write16(native.e_machine_amd64) + } + g.write32(native.ev_current) // e_version + eh_size := 0x40 + phent_size := 0x38 + g.write64(native.segment_start + eh_size + phent_size) // e_entry + g.write64(0x40) // e_phoff + g.write64(0) // e_shoff + g.write32(0) // e_flags + g.write16(eh_size) // e_ehsize + g.write16(phent_size) // e_phentsize + g.write16(1) // e_phnum + g.write16(0) // e_shentsize + g.write16(0) // e_shnum (number of sections) + g.write16(0) // e_shstrndx + // Elf64_Phdr + g.write32(1) // p_type + g.write32(5) // p_flags + g.write64(0) // p_offset + g.write64(native.segment_start) // p_vaddr addr:050 + g.write64(native.segment_start) // + g.file_size_pos = i64(g.buf.len) + g.write64(0) // p_filesz PLACEHOLDER, set to file_size later // addr: 060 + g.write64(0) // p_memsz + g.write64(0x1000) // p_align + // user code starts here at + // address: 00070 and a half + if g.pref.is_verbose { + eprintln('code_start_pos = $g.buf.len.hex()') + } + g.code_start_pos = i64(g.buf.len) + g.debug_pos = g.buf.len + g.call(native.placeholder) // call main function, it's not guaranteed to be the first, we don't know its address yet + g.println('; call fn main') +} + +pub fn (mut g Gen) generate_elf_footer() { + // Return 0 + /* + g.mov(.edi, 0) // ret value + g.mov(.eax, 60) + g.syscall() + */ + // Strings table + // Loop thru all strings and set the right addresses + for i, s in g.strings { + g.write64_at(native.segment_start + g.buf.len, int(g.str_pos[i])) + g.write_string(s) + g.write8(0) + } + // Now we know the file size, set it + file_size := g.buf.len + g.write64_at(file_size, g.file_size_pos) // set file size 64 bit value + g.write64_at(file_size, g.file_size_pos + 8) + if g.pref.arch == .arm64 { + bl_next := u32(0x94000001) + g.write32_at(g.code_start_pos, int(bl_next)) + } else { + // amd64 + // call main function, it's not guaranteed to be the first + // we generated call(0) ("e8 0") + // now need to replace "0" with a relative address of the main function + // +1 is for "e8" + // -5 is for "e8 00 00 00 00" + g.write32_at(g.code_start_pos + 1, int(g.main_fn_addr - g.code_start_pos) - 5) + } + g.create_executable() +} diff --git a/v_windows/v/vlib/v/gen/native/elf_obj.v b/v_windows/v/vlib/v/gen/native/elf_obj.v new file mode 100644 index 0000000..d3b7f42 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/elf_obj.v @@ -0,0 +1,157 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module native + +/* +This file is unused right now, since binaries without sections +are generated. + +But it will be necessary once we have dynamic linking. +*/ +enum SectionType { + null = 0 + progbits = 1 + symtab = 2 + strtab = 3 + rela = 4 +} + +struct SectionConfig { + name string + typ SectionType + flags i64 + data voidptr + is_saa bool + datalen i64 + link int + info int + align i64 + entsize i64 +} + +fn (mut g Gen) section_header(c SectionConfig) { + g.write32(g.sect_header_name_pos) + g.sect_header_name_pos += c.name.len + 1 + g.write32(int(c.typ)) + g.write64(c.flags) + g.write64(0) // sh_addr + g.write64(g.offset) // offset + g.offset += c.datalen + 1 + g.write64(c.datalen) + g.write32(c.link) + g.write32(c.info) + g.write64(c.align) + g.write64(c.entsize) +} + +fn genobj() { + /* + // SHN_UNDEF + mut g := Gen{} + nr_sections := 7 + g.section_header(SectionConfig{ + name: '' + typ: .null + flags:0 + data: 0 + is_saa: false + link: 0 + info:0 + align:0 + entsize: 0 + }) + + /* + for sect in sections { + g.section_header(SectionConfig{ + name:0 + typ: sect.typ + flags: sect.flags + data: sect.data + is_saa: true + datalen: sect.len + link: 0 + info: 0 + align: sect.align + entsize: sect.entsize + }) + + } + */ + + g.section_header(SectionConfig{ + name: '.DATA' + typ: .progbits + flags: 0x2 + //data: sect.data + is_saa: true + datalen: 0xd + link: 0 + info: 0 + align: 1 + entsize: 0 + }) + + g.section_header(SectionConfig{ + name: '.TEXT' + typ: .progbits + flags: 0x2 + //data: sect.data + is_saa: true + datalen: 0xd + link: 0 + info: 0 + align: 1 + entsize: 0 + }) + g.section_header(SectionConfig{ + name: '.shstrtab' + typ: .strtab + flags: 0x2 + //data: sect.data + is_saa: true + datalen: 0x22 + link: 0 + info: 0 + align: 1 + entsize: 0 + }) + g.section_header(SectionConfig{ + name: '.symtab' + typ: .symtab + flags: 0x2 + //data: sect.data + is_saa: true + datalen: 0xd + link: 0 + info: 0 + align: 1 + entsize: 0 + }) + g.section_header(SectionConfig{ + name: '.strtab' + typ: .symtab + flags: 0x2 + //data: sect.data + is_saa: true + datalen: 0xd + link: 0 + info: 0 + align: 1 + entsize: 0 + }) + g.section_header(SectionConfig{ + name: '.rela.TEXT' + typ: .rela + flags: 0x0 + //data: sect.data + is_saa: true + datalen: 0x18 + link: 4 + info: 2 + align: 8 + entsize: 0x18 + }) + */ +} diff --git a/v_windows/v/vlib/v/gen/native/gen.v b/v_windows/v/vlib/v/gen/native/gen.v new file mode 100644 index 0000000..f24a92c --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/gen.v @@ -0,0 +1,495 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module native + +import os +import strings +import v.ast +import v.util +import v.token +import v.errors +import v.pref +import term + +pub const builtins = ['assert', 'print', 'eprint', 'println', 'eprintln', 'exit'] + +interface CodeGen { +mut: + g Gen + gen_exit(mut g Gen, expr ast.Expr) + // XXX WHY gen_exit fn (expr ast.Expr) +} + +[heap] +pub struct Gen { + out_name string + pref &pref.Preferences // Preferences shared from V struct +mut: + cgen CodeGen + table &ast.Table + buf []byte + sect_header_name_pos int + offset i64 + str_pos []i64 + strings []string // TODO use a map and don't duplicate strings + file_size_pos i64 + main_fn_addr i64 + code_start_pos i64 // location of the start of the assembly instructions + fn_addr map[string]i64 + var_offset map[string]int // local var stack offset + stack_var_pos int + debug_pos int + errors []errors.Error + warnings []errors.Warning + syms []Symbol + relocs []Reloc + size_pos []int + nlines int +} + +enum Size { + _8 + _16 + _32 + _64 +} + +fn get_backend(arch pref.Arch) ?CodeGen { + match arch { + .arm64 { + return Arm64{} + } + .amd64 { + return Amd64{} + } + else {} + } + return error('unsupported architecture') +} + +pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Preferences) (int, int) { + mut g := &Gen{ + table: table + sect_header_name_pos: 0 + out_name: out_name + pref: pref + // TODO: workaround, needs to support recursive init + cgen: get_backend(pref.arch) or { + eprintln('No available backend for this configuration. Use `-a arm64` or `-a amd64`.') + exit(1) + } + } + g.cgen.g = g + g.generate_header() + for file in files { + if file.warnings.len > 0 { + eprintln('warning: ${file.warnings[0]}') + } + if file.errors.len > 0 { + g.n_error(file.errors[0].str()) + } + g.stmts(file.stmts) + } + g.generate_footer() + return g.nlines, g.buf.len +} + +pub fn (mut g Gen) generate_header() { + match g.pref.os { + .macos { + g.generate_macho_header() + } + .linux { + g.generate_elf_header() + } + .raw { + if g.pref.arch == .arm64 { + g.gen_arm64_helloworld() + } + } + else { + g.n_error('only `raw`, `linux` and `macos` are supported for -os in -native') + } + } +} + +pub fn (mut g Gen) create_executable() { + // Create the binary // should be .o ? + os.write_file_array(g.out_name, g.buf) or { panic(err) } + os.chmod(g.out_name, 0o775) or { panic(err) } // make it executable + if g.pref.is_verbose { + println('\n$g.out_name: native binary has been successfully generated') + } +} + +pub fn (mut g Gen) generate_footer() { + match g.pref.os { + .macos { + g.generate_macho_footer() + } + .linux { + g.generate_elf_footer() + } + .raw { + g.create_executable() + } + else {} + } +} + +pub fn (mut g Gen) stmts(stmts []ast.Stmt) { + for stmt in stmts { + g.stmt(stmt) + } +} + +pub fn (g &Gen) pos() i64 { + return g.buf.len +} + +fn (mut g Gen) write8(n int) { + // write 1 byte + g.buf << byte(n) +} + +fn (mut g Gen) write16(n int) { + // write 2 bytes + g.buf << byte(n) + g.buf << byte(n >> 8) +} + +fn (mut g Gen) read32_at(at int) int { + return int(g.buf[at] | (g.buf[at + 1] << 8) | (g.buf[at + 2] << 16) | (g.buf[at + 3] << 24)) +} + +fn (mut g Gen) write32(n int) { + // write 4 bytes + g.buf << byte(n) + g.buf << byte(n >> 8) + g.buf << byte(n >> 16) + g.buf << byte(n >> 24) +} + +fn (mut g Gen) write64(n i64) { + // write 8 bytes + g.buf << byte(n) + g.buf << byte(n >> 8) + g.buf << byte(n >> 16) + g.buf << byte(n >> 24) + g.buf << byte(n >> 32) + g.buf << byte(n >> 40) + g.buf << byte(n >> 48) + g.buf << byte(n >> 56) +} + +fn (mut g Gen) write64_at(n i64, at i64) { + // write 8 bytes + g.buf[at] = byte(n) + g.buf[at + 1] = byte(n >> 8) + g.buf[at + 2] = byte(n >> 16) + g.buf[at + 3] = byte(n >> 24) + g.buf[at + 4] = byte(n >> 32) + g.buf[at + 5] = byte(n >> 40) + g.buf[at + 6] = byte(n >> 48) + g.buf[at + 7] = byte(n >> 56) +} + +fn (mut g Gen) write32_at(at i64, n int) { + // write 4 bytes + g.buf[at] = byte(n) + g.buf[at + 1] = byte(n >> 8) + g.buf[at + 2] = byte(n >> 16) + g.buf[at + 3] = byte(n >> 24) +} + +fn (mut g Gen) write_string(s string) { + for c in s { + g.write8(int(c)) + } + // g.write8(0) // null terminated strings +} + +fn (mut g Gen) write_string_with_padding(s string, max int) { + for c in s { + g.write8(int(c)) + } + for _ in 0 .. max - s.len { + g.write8(0) + } +} + +fn (mut g Gen) get_var_offset(var_name string) int { + offset := g.var_offset[var_name] + if offset == 0 { + g.n_error('unknown variable `$var_name`') + } + return offset +} + +pub fn (mut g Gen) gen_print_from_expr(expr ast.Expr, name string) { + newline := name in ['println', 'eprintln'] + fd := if name in ['eprint', 'eprintln'] { 2 } else { 1 } + match expr { + ast.StringLiteral { + if newline { + g.gen_print(expr.val + '\n', fd) + } else { + g.gen_print(expr.val, fd) + } + } + ast.CallExpr { + g.call_fn(expr) + g.gen_print_reg(.rax, 3, fd) + } + ast.Ident { + g.expr(expr) + g.gen_print_reg(.rax, 3, fd) + } + else { + dump(typeof(expr).name) + dump(expr) + g.n_error('expected string as argument for print') + } + } +} + +pub fn (mut g Gen) register_function_address(name string) { + addr := g.pos() + // eprintln('register function $name = $addr') + g.fn_addr[name] = addr +} + +fn (mut g Gen) println(comment string) { + g.nlines++ + if !g.pref.is_verbose { + return + } + addr := g.debug_pos.hex() + // println('$g.debug_pos "$addr"') + print(term.red(strings.repeat(`0`, 6 - addr.len) + addr + ' ')) + for i := g.debug_pos; i < g.pos(); i++ { + s := g.buf[i].hex() + if s.len == 1 { + print(term.blue('0')) + } + gbihex := g.buf[i].hex() + hexstr := term.blue(gbihex) + ' ' + print(hexstr) + } + g.debug_pos = g.buf.len + println(' ' + comment) +} + +fn (mut g Gen) for_in_stmt(node ast.ForInStmt) { + g.v_error('for-in statement is not yet implemented', node.pos) + /* + if node.is_range { + // `for x in 1..10 {` + // i := if node.val_var == '_' { g.new_tmp_var() } else { c_name(node.val_var) } + // val_typ := g.table.mktyp(node.val_type) + g.write32(0x3131) // 'for (${g.typ(val_typ)} $i = ') + g.expr(node.cond) + g.write32(0x3232) // ; $i < ') + g.expr(node.high) + g.write32(0x3333) // '; ++$i) {') + } else if node.kind == .array { + } else if node.kind == .array_fixed { + } else if node.kind == .map { + } else if node.kind == .string { + } else if node.kind == .struct_ { + } + */ +} + +pub fn (mut g Gen) gen_exit(node ast.Expr) { + // check node type and then call the cgen method + g.cgen.gen_exit(mut g, node) +} + +fn (mut g Gen) stmt(node ast.Stmt) { + match node { + ast.AssignStmt { + g.assign_stmt(node) + } + ast.Block { + g.stmts(node.stmts) + } + ast.ConstDecl {} + ast.ExprStmt { + g.expr(node.expr) + } + ast.FnDecl { + g.fn_decl(node) + } + ast.ForInStmt { + g.for_in_stmt(node) + } + ast.ForStmt { + g.for_stmt(node) + } + ast.HashStmt { + words := node.val.split(' ') + for word in words { + if word.len != 2 { + g.n_error('opcodes format: xx xx xx xx') + } + b := unsafe { C.strtol(&char(word.str), 0, 16) } + // b := word.byte() + // println('"$word" $b') + g.write8(b) + } + } + ast.Module {} + ast.Return { + // dump(node.exprs[0]) + // if in main + // zero := ast.IntegerLiteral{} + // g.gen_exit(zero) + // dump(node) + // dump(node.types) + mut s := '?' //${node.exprs[0].val.str()}' + e0 := node.exprs[0] + match e0 { + ast.IntegerLiteral { + g.mov64(.rax, e0.val.int()) + } + ast.InfixExpr { + // TODO + // verror('expr') + } + ast.CastExpr { + g.mov64(.rax, e0.expr.str().int()) + // do the job + } + ast.StringLiteral { + s = e0.val.str() + g.expr(node.exprs[0]) + g.mov64(.rax, g.allocate_string(s, 2)) + } + ast.Ident { + g.expr(e0) + eprintln('ident $e0.name') + } + else { + g.n_error('unknown return type $e0.type_name()') + } + } + // intel specific + g.add8(.rsp, 0x20) // XXX depends on scope frame size + g.pop(.rbp) + g.ret() + } + ast.AsmStmt { + g.gen_asm_stmt(node) + } + ast.AssertStmt { + g.gen_assert(node) + } + ast.StructDecl {} + else { + println('native.stmt(): bad node: ' + node.type_name()) + } + } +} + +fn C.strtol(str &char, endptr &&char, base int) int + +fn (mut g Gen) expr(node ast.Expr) { + match node { + ast.ParExpr { + g.expr(node.expr) + } + ast.ArrayInit { + g.n_error('array init expr not supported yet') + } + ast.BoolLiteral {} + ast.CallExpr { + if node.name == 'exit' { + g.gen_exit(node.args[0].expr) + } else if node.name in ['println', 'print', 'eprintln', 'eprint'] { + expr := node.args[0].expr + g.gen_print_from_expr(expr, node.name) + } else { + g.call_fn(node) + } + } + ast.FloatLiteral {} + ast.Ident {} + ast.IfExpr { + if node.is_comptime { + eprintln('Warning: ignored compile time conditional not yet supported for the native backend.') + } else { + g.if_expr(node) + } + } + ast.InfixExpr {} + ast.IntegerLiteral {} + ast.PostfixExpr { + g.postfix_expr(node) + } + ast.StringLiteral {} + ast.StructInit {} + ast.GoExpr { + g.v_error('native backend doesnt support threads yet', node.pos) // token.Position{}) + } + else { + g.n_error('expr: unhandled node type: $node.type_name()') + } + } +} + +/* +fn (mut g Gen) allocate_var(name string, size int, initial_val int) { + g.cgen.allocate_var(name, size, initial_val) +} +*/ + +fn (mut g Gen) postfix_expr(node ast.PostfixExpr) { + if node.expr !is ast.Ident { + return + } + ident := node.expr as ast.Ident + var_name := ident.name + if node.op == .inc { + g.inc_var(var_name) + } +} + +[noreturn] +pub fn (mut g Gen) n_error(s string) { + util.verror('native error', s) +} + +pub fn (mut g Gen) warning(s string, pos token.Position) { + if g.pref.output_mode == .stdout { + werror := util.formatted_error('warning', s, g.pref.path, pos) + eprintln(werror) + } else { + g.warnings << errors.Warning{ + file_path: g.pref.path + pos: pos + reporter: .gen + message: s + } + } +} + +pub fn (mut g Gen) v_error(s string, pos token.Position) { + // TODO: store a file index in the Position too, + // so that the file path can be retrieved from the pos, instead + // of guessed from the pref.path ... + mut kind := 'error:' + if g.pref.output_mode == .stdout { + ferror := util.formatted_error(kind, s, g.pref.path, pos) + eprintln(ferror) + exit(1) + } else { + g.errors << errors.Error{ + file_path: g.pref.path + pos: pos + reporter: .gen + message: s + } + } +} diff --git a/v_windows/v/vlib/v/gen/native/macho.v b/v_windows/v/vlib/v/gen/native/macho.v new file mode 100644 index 0000000..b25bf05 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/macho.v @@ -0,0 +1,405 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module native + +const ( + s_attr_some_instructions = 0x00000400 + s_attr_pure_instructions = 0x80000000 + s_attr_ext_reloc = 0x00000200 + s_attr_loc_reloc = 0x00000100 + // + macho_symcmd_size = 0x18 + macho_d_size = 0x50 + mh_object = 1 + mh_execute = 2 + lc_dysymtab = 0xb + lc_load_dylib = 0xc + lc_load_dylinker = 0xe + lc_main = 0x80000028 + lc_segment_64 = 0x19 + lc_symtab = 0x2 +) + +struct Symbol { + str_entry int + symbol_typ int + section int + desc int + val i64 + name string + is_ext bool +} + +struct Reloc { + addr int + pcrel int + len int + ext int + typ int + snum int // symbol index (if ext) or infile section number +} + +fn (mut g Gen) macho_segment64_pagezero() { + g.write32(native.lc_segment_64) // LC_SEGMENT_64 + g.write32(72) // cmdsize + g.write_string_with_padding('__PAGEZERO', 16) // section name + g.write64(0) // vmaddr + g.write64(0x1000) // vmsize + g.write64(0) // fileoff + g.write64(0) // filesize + + g.write32(0) // maxprot + g.write32(0) // initprot + g.write32(0) // nsects + g.write32(0) // flags +} + +fn (mut g Gen) macho_segment64_linkedit() { + g.write32(native.lc_segment_64) + g.write32(0x48) // cmdsize + g.write_string_with_padding('__LINKEDIT', 16) + + g.write64(0x3000) // vmaddr + g.write64(0x1000) // vmsize + g.write64(0x1000) // fileoff + g.write64(0) // filesize + g.write32(7) // maxprot + g.write32(3) // initprot + g.write32(0) // nsects + g.write32(0) // flags +} + +fn (mut g Gen) macho_header(ncmds int, bintype int) int { + g.write32(0xfeedfacf) // MH_MAGIC_64 + if g.pref.arch == .arm64 { + g.write32(0x0100000c) // CPU_TYPE_ARM64 + g.write32(0x00000000) // CPU_SUBTYPE_ARM64_ALL + } else { + g.write32(0x01000007) // CPU_TYPE_X64 + g.write32(0x80000003) // CPU_SUBTYPE_X64 + } + g.write32(native.mh_execute) // filetype + g.write32(ncmds) // ncmds + + cmdsize_offset := g.buf.len + g.write32(0) // size of load commands + + g.write32(0) // flags + g.write32(0) // reserved + return cmdsize_offset +} + +fn (mut g Gen) macho_segment64_text() []int { + mut patch := []int{} + g.write32(native.lc_segment_64) // LC_SEGMENT_64 + g.write32(152) // 152 + g.write_string_with_padding('__TEXT', 16) // section name + g.write64(0x100000000) // vmaddr + patch << g.buf.len + g.write64(0x00001000) // + codesize) // vmsize + g.write64(0x00000000) // filesize + patch << g.buf.len + g.write64(0x00001000) // + codesize) // filesize + + g.write32(7) // maxprot + g.write32(5) // initprot + g.write32(1) // nsects + g.write32(0) // flags + + g.write_string_with_padding('__text', 16) // section name + g.write_string_with_padding('__TEXT', 16) // segment name + g.write64(0x0000000100001000) // vmaddr + patch << g.buf.len + g.write64(0) // vmsize + g.write32(4096) // offset + g.write32(0) // align + + g.write32(0) // reloff + g.write32(0) // nreloc + + g.write32(0) // flags + g.write32(0) + + g.write32(0) // reserved1 + g.write32(0) // reserved2 + return patch +} + +fn (mut g Gen) macho_symtab() { + g.write32(native.lc_symtab) + g.write32(24) + g.write32(0x1000) + g.write32(0) + g.write32(0x1000) + g.write32(0) + + // lc_dysymtab + g.write32(native.lc_dysymtab) + g.write32(0x50) + g.write32(0) // ilocalsym + g.write32(0) // nlocalsym + g.write32(0) // iextdefsym + g.write32(0) // nextdefsym + g.write32(0) // iundefsym + g.write32(0) // nundefsym + g.write32(0) // tocoff + g.write32(0) // ntoc + g.write32(0) // modtaboff + g.write32(0) // nmodtab + g.write32(0) // extrefsymoff + g.write32(0) // nextrefsyms + g.write32(0) // indirectsymoff + g.write32(0) // nindirectsyms + g.write32(0) // extreloff + g.write32(0) // nextrel + g.write32(0) // locreloff + g.write32(0) // nlocrel +} + +fn (mut g Gen) macho_dylibs() { + g.write32(native.lc_load_dylinker) + g.write32(32) // cmdsize (must be aligned to int32) + g.write32(12) // offset + g.write_string_with_padding('/usr/lib/dyld', 16) + g.write32(0) // padding // can be removed + + g.write32(native.lc_load_dylib) + g.write32(56) // cmdsize + g.write32(24) // offset + g.write32(0) // ts + g.write32(1) // ver + g.write32(1) // compat + g.write_string_with_padding('/usr/lib/libSystem.B.dylib', 32) +} + +fn (mut g Gen) macho_main(addr int) { + g.write32(int(native.lc_main)) // LC_MAIN + g.write32(24) // cmdsize + g.write32(addr) // entrypoint + g.write32(0) // initial_stacksize +} + +pub fn (mut g Gen) generate_macho_header() { + g.code_start_pos = 0x1000 + g.debug_pos = 0x1000 + cmdsize_offset := g.macho_header(8, native.mh_execute) + g.macho_segment64_pagezero() + + g.size_pos = g.macho_segment64_text() + g.macho_segment64_linkedit() + g.macho_symtab() + g.macho_dylibs() + g.macho_main(0x1000) + + g.write32_at(cmdsize_offset, g.buf.len - 24) + g.write_nulls(0x1000 - g.buf.len) + g.call(0) +} + +fn (mut g Gen) write_nulls(len int) { + pad := 0x1000 - g.buf.len + for _ in 0 .. pad { + g.write8(0) + } +} + +pub fn (mut g Gen) generate_macho_object_header() { + if g.pref.arch == .arm64 { + g.write32(0xfeedfacf) // MH_MAGIC_64 + g.write32(0x0100000c) // CPU_TYPE_ARM64 + g.write32(0x00000000) // CPU_SUBTYPE_ARM64_ALL + } else { + g.write32(0xfeedfacf) // MH_MAGIC_64 + g.write32(0x01000007) // CPU_TYPE_X64 + g.write32(0x00000003) // CPU_SUBTYPE_X64 + } + g.write32(native.mh_object) // MH_OBJECT + text_offset := 0x138 + g.write32(4) // # of load commands + g.write32(text_offset - 0x20) // size of load commands // 0x138-0x20 + // g.write32(0x00002000) // MH_SUBSECTIONS_VIA_SYMBOLS + g.write32(0) // MH_SUBSECTIONS_VIA_SYMBOLS + g.write32(0) // reserved + //// + g.write32(0x19) // LC_SEGMENT_64 + g.write32(0x98) // command size + g.zeroes(16) // segment name + g.write64(0) // VM address + g.write64(0x25) // VM size + g.write64(text_offset) // file offset + g.write64(0x25) // file size + g.write32(0x7) // max vm protection + g.write32(0x7) // initial vm protection + g.write32(0x1) // # of sections + g.write32(0) // flags + //// + g.write_string_with_padding('__text', 16) // section name + g.write_string_with_padding('__TEXT', 16) // segment name + g.write64(0) // address + g.write64(0x25) // size + g.write32(text_offset) // offset + g.write32(0x4) // alignment + g.write32(0x160) // relocation offset + g.write32(0x1) // # of relocations + g.write32(int(native.s_attr_some_instructions | native.s_attr_pure_instructions)) + g.write32(0) + g.write32(0) + g.write32(0) + /// ??? + g.write32(0x32) + g.write32(0x18) + + g.write32(0x01) + g.write32(0x000a0000) // minOS 10.0 + g.write32(0) + g.write32(0) + // lc_symtab + g.sym_table_command() + // + g.write32(native.lc_dysymtab) + g.write32(native.macho_d_size) + g.write32(0) + g.write32(2) + g.write32(2) + g.write32(1) + g.write32(3) + g.write32(1) + for _ in 0 .. 12 { + g.write32(0) + } + if g.pref.is_verbose { + println('commands size = $g.buf.len') + if g.buf.len != 0x138 { + println('macho: invalid header size') + } + } + + if g.pref.arch == .arm64 { + g.gen_arm64_helloworld() + } else { + // do nothing + } +} + +pub fn (mut g Gen) generate_macho_footer() { + codesize := g.buf.len - 0x1000 + g.write_relocs() + g.sym_table() + stringtablesize := g.sym_string_table() + delta := codesize + stringtablesize + 12 // code_offset_end - 0x1000// + stringtablesize + g.write8(0) + for o in g.size_pos { + n := g.read32_at(o) + g.write32_at(o, n + delta) + } + g.write64(0) + // this is amd64-specific + call_delta := int(g.main_fn_addr - g.code_start_pos) - 5 + g.write32_at(g.code_start_pos + 1, call_delta) + g.create_executable() +} + +fn (mut g Gen) sym_table_command() { + g.syms << Symbol{ + str_entry: 0x19 + symbol_typ: 0xe + section: 1 + val: 0 + name: '_start' + is_ext: true + } + g.syms << Symbol{ + str_entry: 0x0e + symbol_typ: 0xe + // symbol_typ: SYM_DEF + section: 1 + val: 0x18 + name: '_puts' + is_ext: false + } + g.syms << Symbol{ + str_entry: 0x01 + symbol_typ: 0xf + // symbol_typ: SYM_DEF + section: 1 + // val: 0x27 + val: 0 + name: 'helloworld' + is_ext: false + } + g.syms << Symbol{ + str_entry: 0x08 + symbol_typ: 0x1 + // symbol_typ: SYM_DEF + section: 0 + // val: 0x27 + val: 0 + name: 'ltmp1' + is_ext: false + } + g.write32(native.lc_symtab) + g.write32(native.macho_symcmd_size) + sym_table_offset := 0x168 + g.write32(sym_table_offset) + g_syms_len := 4 + g.write32(g_syms_len) + str_offset := 0x1a8 + g.write32(str_offset) + str_size := 0x20 + g.write32(str_size) +} + +pub fn (mut g Gen) zeroes(n int) { + for _ in 0 .. n { + g.buf << 0 + } +} + +fn (mut g Gen) write_relocs() { + if g.pref.is_verbose { + println('relocs at $g.buf.len should be 0x160') + } + g.write32(0x8) + g.write32(0x2d000003) +} + +fn (mut g Gen) sym_table() { + // strings first + for sym in g.syms { + // if !sym.is_ext { + g.write_symbol(sym) + //} + } + // now fns (external syms) + /* + for sym in g.syms { + if sym.is_ext { + g.write_symbol(sym) + } + } + */ +} + +fn (mut g Gen) write_symbol(s Symbol) { + // g.write8(0x77) + g.write32(s.str_entry) + g.write8(s.symbol_typ) + g.write8(s.section) + g.write8(0) + g.write8(0) + g.write64(s.val) + // g.write16(s.desc) +} + +fn (mut g Gen) sym_string_table() int { + begin := g.buf.len + g.zeroes(1) + at := i64(0x100000000) + for i, s in g.strings { + g.write64_at(at + g.buf.len, int(g.str_pos[i])) + g.write_string(s) + g.write8(0) + } + return g.buf.len - begin +} diff --git a/v_windows/v/vlib/v/gen/native/macho_test.v b/v_windows/v/vlib/v/gen/native/macho_test.v new file mode 100644 index 0000000..2909cad --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/macho_test.v @@ -0,0 +1,16 @@ +import os +import v.gen.native +import v.pref +import v.ast + +fn test_macho() { + os.chdir(os.temp_dir()) or {} + mut g := native.Gen{ + pref: &pref.Preferences{} + out_name: 'test.bin' + table: ast.new_table() + cgen: native.Amd64{} + } + g.generate_macho_header() + g.generate_macho_footer() +} diff --git a/v_windows/v/vlib/v/gen/native/tests/asm.vv b/v_windows/v/vlib/v/gen/native/tests/asm.vv new file mode 100644 index 0000000..1c08411 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/asm.vv @@ -0,0 +1,10 @@ +fn asm_test() { + asm amd64 { + nop + } + println('inline works') +} + +fn main() { + asm_test() +} diff --git a/v_windows/v/vlib/v/gen/native/tests/asm.vv.out b/v_windows/v/vlib/v/gen/native/tests/asm.vv.out new file mode 100644 index 0000000..9e07c4d --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/asm.vv.out @@ -0,0 +1 @@ +inline works diff --git a/v_windows/v/vlib/v/gen/native/tests/assert.vv b/v_windows/v/vlib/v/gen/native/tests/assert.vv new file mode 100644 index 0000000..0140021 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/assert.vv @@ -0,0 +1,10 @@ +fn main() { + assert 1 == 1 + assert 3 == 3 + assert 1 == 1 + assert 3 == 3 + if 1 == 2 { + assert 1 == 2 + } + println('assert pass') +} diff --git a/v_windows/v/vlib/v/gen/native/tests/assert.vv.out b/v_windows/v/vlib/v/gen/native/tests/assert.vv.out new file mode 100644 index 0000000..c8741a3 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/assert.vv.out @@ -0,0 +1 @@ +assert pass diff --git a/v_windows/v/vlib/v/gen/native/tests/expressions.vv b/v_windows/v/vlib/v/gen/native/tests/expressions.vv new file mode 100644 index 0000000..6d36760 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/expressions.vv @@ -0,0 +1,60 @@ +fn print_number(n int) { + if n == 0 { + println('0') + } + if n == 1 { + println('1') + } + if n == 2 { + println('2') + } + if n == 3 { + println('3') + } + if n == 4 { + println('4') + } + if n == 5 { + println('5') + } + if n == 6 { + println('6') + } + if n == 7 { + println('7') + } + if n == 8 { + println('8') + } + if n == 9 { + println('9') + } +} + +struct User { + age int +} + +fn print_user(u User) { +} + +fn test_add() { + println('test_add()') + x := 2 + y := 3 + sum := x + y + product := x * y + // diff := y - x + print_number(x) + print_number(y) + print_number(sum) + print_number(product) + // XXX fails on linux-amd64 but works on macos-amd64 + // print_number(diff) +} + +fn main() { + println('start') + test_add() + println('end') +} diff --git a/v_windows/v/vlib/v/gen/native/tests/expressions.vv.out b/v_windows/v/vlib/v/gen/native/tests/expressions.vv.out new file mode 100644 index 0000000..2232a58 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/expressions.vv.out @@ -0,0 +1,8 @@ +start +test_add() +2 +3 +5 +6 +end + diff --git a/v_windows/v/vlib/v/gen/native/tests/general.vv b/v_windows/v/vlib/v/gen/native/tests/general.vv new file mode 100644 index 0000000..7e19273 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/general.vv @@ -0,0 +1,95 @@ +fn cast_test() { + a := int(1) + assert a == 1 + b := u64(2) + assert b == 2 +} + +fn if_test() { + mut a := 1 + if a == 1 { + println('a == 1') + b := 2 + if b == 2 { + println('b == 2') + } + // if b != 2 { + // println('b != 2') + //} + } + if a == 2 { + println('a == 2') + } + a++ + if a == 2 { + println('now a == 2') + } +} + +fn loop() { + mut i := 0 + for i < 5 { + println('hello') + i++ + } +} + +fn foo(a int) { + println('foo:') + if a == 1 { + println('a == 1') + } + if a == 7 { + println('a == 7') + } + /* + // native gen doesnt support more than one variable + mut b := a + 1 + if b == 2 { + println('b == 2') + } + if b == 3 { + println('b == 3') + } + if b == 8 { + println('b == 8') + } + */ +} + +fn args() { + x := 7 + println('===args===') + foo(1) // prints 1 2 + foo(x) // prints 7 8 + foo(2) // prints 3 +} + +/* +fn expr() { + println('===expr===') + a := 1 + b := 2 + c := a + b + println('c==') + if c == 0 { + println('0') + } + if c == 3 { + println('3') + } +} +*/ + +struct User { + age int + nr_orders int +} + +fn main() { + if_test() + cast_test() + loop() + args() + // expr() +} diff --git a/v_windows/v/vlib/v/gen/native/tests/general.vv.out b/v_windows/v/vlib/v/gen/native/tests/general.vv.out new file mode 100644 index 0000000..3c73504 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/general.vv.out @@ -0,0 +1,14 @@ +a == 1 +b == 2 +now a == 2 +hello +hello +hello +hello +hello +===args=== +foo: +a == 1 +foo: +a == 7 +foo: diff --git a/v_windows/v/vlib/v/gen/native/tests/hello.vv b/v_windows/v/vlib/v/gen/native/tests/hello.vv new file mode 100644 index 0000000..778a1b4 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/hello.vv @@ -0,0 +1,3 @@ +fn main() { + println('hello from native V') +} diff --git a/v_windows/v/vlib/v/gen/native/tests/hello.vv.out b/v_windows/v/vlib/v/gen/native/tests/hello.vv.out new file mode 100644 index 0000000..20d1b8b --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/hello.vv.out @@ -0,0 +1 @@ +hello from native V diff --git a/v_windows/v/vlib/v/gen/native/tests/ifs.vv b/v_windows/v/vlib/v/gen/native/tests/ifs.vv new file mode 100644 index 0000000..674d4aa --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/ifs.vv @@ -0,0 +1,65 @@ +fn print_number(n int) { + if n == 0 { + println('print_number') + } +} + +fn test_add() { + n := 3 + print_number(0) + print_number(1) + if n > 1 { + println('var(3) > 1') + } + /* + if 1 < n { + println('1 < var(3)') + } + if 1 > n { + println('1 > 3 ERROR') + } + */ + if 1 < 3 { + println('1 < 3') + } + if 1 == 1 { + println('1 == 1') + // TODO assert here + } + if 1 != 3 { + println('1 != 3') + // TODO assert here + } + if 3 != 3 { + println('3 != 3 ERROR') + // TODO assert here + } + if 1 > 3 { + println('1 > 3 ERROR') + // TODO assert here + } +} + +/* +fn test_elses() { + println('start else') + if 1 < 2 { + println('ok') + } else { + println('1<2else ERROR') + } + if 1 > 2 { + println('1<2else ERROR') + } else { + println('ok') + } + println('end else') +} +*/ + +fn main() { + println('start') + test_add() + // test_elses() + println('end') +} diff --git a/v_windows/v/vlib/v/gen/native/tests/ifs.vv.out b/v_windows/v/vlib/v/gen/native/tests/ifs.vv.out new file mode 100644 index 0000000..1298ea4 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/ifs.vv.out @@ -0,0 +1,7 @@ +start +print_number +var(3) > 1 +1 < 3 +1 == 1 +1 != 3 +end diff --git a/v_windows/v/vlib/v/gen/native/tests/native_test.v b/v_windows/v/vlib/v/gen/native/tests/native_test.v new file mode 100644 index 0000000..9ff8998 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/native_test.v @@ -0,0 +1,98 @@ +import os +import benchmark +import term + +const is_verbose = os.getenv('VTEST_SHOW_CMD') != '' + +// TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module +fn test_native() { + $if arm64 { + return + } + // some tests are running fine in macos + if os.user_os() != 'linux' && os.user_os() != 'macos' { + eprintln('native tests only run on Linux and macOS for now.') + exit(0) + } + mut bench := benchmark.new_benchmark() + vexe := os.getenv('VEXE') + vroot := os.dir(vexe) + dir := os.join_path(vroot, 'vlib/v/gen/native/tests') + files := os.ls(dir) or { panic(err) } + // + wrkdir := os.join_path(os.temp_dir(), 'vtests', 'native') + os.mkdir_all(wrkdir) or { panic(err) } + os.chdir(wrkdir) or {} + tests := files.filter(it.ends_with('.vv')) + if tests.len == 0 { + println('no native tests found') + assert false + } + bench.set_total_expected_steps(tests.len) + for test in tests { + bench.step() + full_test_path := os.real_path(os.join_path(dir, test)) + test_file_name := os.file_name(test) + relative_test_path := full_test_path.replace(vroot + '/', '') + work_test_path := '$wrkdir/$test_file_name' + exe_test_path := '$wrkdir/${test_file_name}.exe' + cmd := '"$vexe" -o "$exe_test_path" -b native "$full_test_path"' + if is_verbose { + println(cmd) + } + res_native := os.execute(cmd) + if res_native.exit_code != 0 { + bench.fail() + eprintln(bench.step_message_fail(cmd)) + continue + } + tmperrfile := '$dir/${test}.tmperr' + res := os.execute('$exe_test_path 2> $tmperrfile') + if res.exit_code != 0 { + bench.fail() + eprintln(bench.step_message_fail('$full_test_path failed to run')) + eprintln(res.output) + continue + } + mut expected := os.read_file('$dir/${test}.out') or { panic(err) } + errfile := '$dir/${test}.err' + if os.exists(errfile) { + mut err_expected := os.read_file('$dir/${test}.err') or { panic(err) } + err_expected = err_expected.trim_right('\r\n').replace('\r\n', '\n') + errstr := os.read_file(tmperrfile) or { panic(err) } + mut err_found := errstr.trim_right('\r\n').replace('\r\n', '\n') + if err_expected != err_found { + println(term.red('FAIL')) + println('============') + println('stderr expected: "$err_expected" len=$err_expected.len') + println('============') + println('stderr found:"$err_found" len=$err_found.len') + println('============\n') + bench.fail() + continue + } + } + os.rm(tmperrfile) or {} + expected = expected.trim_right('\r\n').replace('\r\n', '\n') + mut found := res.output.trim_right('\r\n').replace('\r\n', '\n') + found = found.trim_space() + if expected != found { + println(term.red('FAIL')) + println('============') + println('expected: "$expected" len=$expected.len') + println('============') + println('found:"$found" len=$found.len') + println('============\n') + bench.fail() + continue + } + bench.ok() + eprintln(bench.step_message_ok(relative_test_path)) + } + bench.stop() + eprintln(term.h_divider('-')) + eprintln(bench.total_message('native')) + if bench.nfail > 0 { + exit(1) + } +} diff --git a/v_windows/v/vlib/v/gen/native/tests/print.vv b/v_windows/v/vlib/v/gen/native/tests/print.vv new file mode 100644 index 0000000..5ddae64 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/print.vv @@ -0,0 +1,14 @@ +fn test_stdout() { + print('Hello ') + println('World') +} + +fn test_stderr() { + eprint('2(Hello)') + eprintln('2(World)') +} + +fn main() { + test_stdout() + test_stderr() +} diff --git a/v_windows/v/vlib/v/gen/native/tests/print.vv.err b/v_windows/v/vlib/v/gen/native/tests/print.vv.err new file mode 100644 index 0000000..651cf35 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/print.vv.err @@ -0,0 +1 @@ +2(Hello)2(World) diff --git a/v_windows/v/vlib/v/gen/native/tests/print.vv.out b/v_windows/v/vlib/v/gen/native/tests/print.vv.out new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/print.vv.out @@ -0,0 +1 @@ +Hello World diff --git a/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv b/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv new file mode 100644 index 0000000..0b39c98 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv @@ -0,0 +1,14 @@ +fn print_greeting() { + println('hello from native V') +} + +fn print_123() { + println('123') +} + +fn main() { + println('start') + print_greeting() + print_123() + println('end') +} diff --git a/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv.out b/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv.out new file mode 100644 index 0000000..a9c6927 --- /dev/null +++ b/v_windows/v/vlib/v/gen/native/tests/simple_fn_calls.vv.out @@ -0,0 +1,4 @@ +start +hello from native V +123 +end diff --git a/v_windows/v/vlib/v/live/common.v b/v_windows/v/vlib/v/live/common.v new file mode 100644 index 0000000..bd29a90 --- /dev/null +++ b/v_windows/v/vlib/v/live/common.v @@ -0,0 +1,69 @@ +module live + +pub const ( + is_used = 1 +) + +pub type FNLinkLiveSymbols = fn (linkcb voidptr) + +pub type FNLiveReloadCB = fn (info &LiveReloadInfo) + +pub struct LiveReloadInfo { +pub: + vexe string // full path to the v compiler + vopts string // v compiler options for a live shared library + original string // full path to the original source file, compiled with -live + live_fn_mutex voidptr // the address of the C mutex, that locks the [live] fns during reloads. + live_linkfn FNLinkLiveSymbols // generated C callback; receives a dlopen handle + so_extension string // .so or .dll + so_name_template string // a sprintf template for the shared libraries location +pub mut: + live_lib voidptr // the result of dl.open + reloads int // how many times a reloading was tried + reloads_ok int // how many times the reloads succeeded + reload_time_ms int // how much time the last reload took (compilation + loading) + last_mod_ts int // a timestamp for when the original was last changed + recheck_period_ms int = 100 // how often do you want to check for changes + cb_recheck FNLiveReloadCB = voidptr(0) // executed periodically + cb_compile_failed FNLiveReloadCB = voidptr(0) // executed when a reload compilation failed + cb_before FNLiveReloadCB = voidptr(0) // executed before a reload try happens + cb_after FNLiveReloadCB = voidptr(0) // executed after a reload try happened, even if failed + cb_locked_before FNLiveReloadCB = voidptr(0) // executed before lib reload, in the mutex section + cb_locked_after FNLiveReloadCB = voidptr(0) // executed after lib reload, in the mutex section + user_ptr voidptr = voidptr(0) // you can set it to anything, then retrieve it in the cb_ fns +} + +// LiveReloadInfo.live_linkfn should be called by the reloader +// to dlsym all live functions. TODO: research a way to implement +// live_linkfn in pure V, without complicating live code generation +// too much. +// +// The callbacks: cb_compile_fail, cb_before, cb_after will be +// executed outside the mutex protected section, so be careful, +// if you modify your data inside them. They can race with your +// [live] functions. +// +// cb_locked_before and cb_locked_after will be executed *inside* +// the mutex protected section. They can NOT race with your [live] +// functions. They should be very quick in what they do though, +// otherwise your live functions can be delayed. +// +// live.info - give user access to program's LiveReloadInfo struct, +// so that the user can set callbacks, read meta information, etc. +pub fn info() &LiveReloadInfo { + if C.g_live_info != 0 { + return unsafe { &LiveReloadInfo(C.g_live_info) } + } + // When the current program is not compiled with -live, simply + // return a new empty struct LiveReloadInfo in order to prevent + // crashes. In this case, the background reloader thread is not + // started, and the structure LiveReloadInfo will not get updated. + // All its fields will be 0, but still safe to access. + mut x := &LiveReloadInfo{} + unsafe { + mut p := &u64(&C.g_live_info) + *p = &u64(x) + _ = p + } + return x +} diff --git a/v_windows/v/vlib/v/live/executable/reloader.v b/v_windows/v/vlib/v/live/executable/reloader.v new file mode 100644 index 0000000..960f543 --- /dev/null +++ b/v_windows/v/vlib/v/live/executable/reloader.v @@ -0,0 +1,163 @@ +module executable + +import os +import time +import dl +import strconv +import v.live + +pub const ( + is_used = 1 +) + +// The live reloader code is implemented here. +// NB: new_live_reload_info will be called by generated C code inside main() +pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_mutex voidptr, live_linkfn live.FNLinkLiveSymbols) &live.LiveReloadInfo { + file_base := os.file_name(original).replace('.v', '') + so_dir := os.cache_dir() + mut so_extension := dl.dl_ext + $if macos { + so_extension = '.dylib' + } + // $if msvc { so_extension = '.dll' } $else { so_extension = '.so' } + return &live.LiveReloadInfo{ + original: original + vexe: vexe + vopts: vopts + live_fn_mutex: live_fn_mutex + live_linkfn: live_linkfn + so_extension: so_extension + so_name_template: '$so_dir/tmp.%d.$file_base' + live_lib: 0 + reloads: 0 + reload_time_ms: 0 + } +} + +// NB: start_reloader will be called by generated code inside main(), to start +// the hot code reloader thread. start_reloader is executed in the context of +// the original main thread. +pub fn start_reloader(mut r live.LiveReloadInfo) { + // The shared library should be loaded once in the main thread + // If that fails, the program would crash anyway, just provide + // an error message to the user and exit: + r.reloads++ + compile_and_reload_shared_lib(mut r) or { + eprintln(err) + exit(1) + } + go reloader(mut r) +} + +fn elog(r &live.LiveReloadInfo, s string) { + $if debuglive ? { + eprintln(s) + } +} + +fn compile_and_reload_shared_lib(mut r live.LiveReloadInfo) ?bool { + sw := time.new_stopwatch() + new_lib_path := compile_lib(mut r) or { return error('errors while compiling $r.original') } + elog(r, '> compile_and_reload_shared_lib compiled: $new_lib_path') + load_lib(mut r, new_lib_path) + r.reload_time_ms = int(sw.elapsed().milliseconds()) + return true +} + +fn compile_lib(mut r live.LiveReloadInfo) ?string { + new_lib_path, new_lib_path_with_extension := current_shared_library_path(mut r) + cmd := '$r.vexe $r.vopts -o $new_lib_path $r.original' + elog(r, '> compilation cmd: $cmd') + cwatch := time.new_stopwatch() + recompilation_result := os.execute(cmd) + elog(r, 'compilation took: ${cwatch.elapsed().milliseconds()}ms') + if recompilation_result.exit_code != 0 { + eprintln('recompilation error:') + eprintln(recompilation_result.output) + return none + } + if !os.exists(new_lib_path_with_extension) { + eprintln('new_lib_path: $new_lib_path_with_extension does not exist') + return none + } + return new_lib_path_with_extension +} + +fn current_shared_library_path(mut r live.LiveReloadInfo) (string, string) { + lib_path := strconv.v_sprintf(r.so_name_template.replace('\\', '\\\\'), r.reloads) + lib_path_with_extension := lib_path + r.so_extension + return lib_path, lib_path_with_extension +} + +fn load_lib(mut r live.LiveReloadInfo, new_lib_path string) { + elog(r, 'live mutex locking...') + C.pthread_mutex_lock(r.live_fn_mutex) + elog(r, 'live mutex locked') + // + if r.cb_locked_before != voidptr(0) { + r.cb_locked_before(r) + } + // + protected_load_lib(mut r, new_lib_path) + // + r.reloads_ok++ + if r.cb_locked_after != voidptr(0) { + r.cb_locked_after(r) + } + // + elog(r, 'live mutex unlocking...') + C.pthread_mutex_unlock(r.live_fn_mutex) + elog(r, 'live mutex unlocked') +} + +fn protected_load_lib(mut r live.LiveReloadInfo, new_lib_path string) { + if r.live_lib != 0 { + dl.close(r.live_lib) + r.live_lib = C.NULL + } + r.live_lib = dl.open(new_lib_path, dl.rtld_lazy) + if r.live_lib == 0 { + eprintln('opening $new_lib_path failed') + exit(1) + } + r.live_linkfn(r.live_lib) + elog(r, '> load_lib OK, new live_lib: $r.live_lib') + // removing the .so file from the filesystem after dlopen-ing + // it is safe, since it will still be mapped in memory + os.rm(new_lib_path) or {} +} + +// NB: r.reloader() is executed in a new, independent thread +fn reloader(mut r live.LiveReloadInfo) { + // elog(r,'reloader, r: $r') + mut last_ts := os.file_last_mod_unix(r.original) + for { + if r.cb_recheck != voidptr(0) { + r.cb_recheck(r) + } + now_ts := os.file_last_mod_unix(r.original) + if last_ts != now_ts { + r.reloads++ + last_ts = now_ts + r.last_mod_ts = last_ts + if r.cb_before != voidptr(0) { + r.cb_before(r) + } + compile_and_reload_shared_lib(mut r) or { + if r.cb_compile_failed != voidptr(0) { + r.cb_compile_failed(r) + } + if r.cb_after != voidptr(0) { + r.cb_after(r) + } + continue + } + if r.cb_after != voidptr(0) { + r.cb_after(r) + } + } + if r.recheck_period_ms > 0 { + time.sleep(r.recheck_period_ms * time.millisecond) + } + } +} diff --git a/v_windows/v/vlib/v/live/live_test.v b/v_windows/v/vlib/v/live/live_test.v new file mode 100644 index 0000000..2468075 --- /dev/null +++ b/v_windows/v/vlib/v/live/live_test.v @@ -0,0 +1,177 @@ +import os +import time + +/* +The goal of this test, is to simulate a developer, that has run a program, compiled with -live flag. + +It does so by writing a new generated program containing a [live] fn pmessage() string {...} function, +(that program is in `vlib/v/live/live_test_template.vv`) +then runs the generated program at the start *in the background*, +waits some time, so that the program could run a few iterations, then modifies its source +(simulates a developer that has saved a new version of the program source), +then it waits some more, modifies it again and saves it once more. + +On each modification, the running program, should detect that its source code has changed, +and recompile a shared library, which it then it should load, and thus modify its own +behavior at runtime (the pmessage function). + +If everything works fine, the output of the generated program would have changed at least 1-2 times, +which then is detected by the test program (the histogram checks). + +Since this test program is sensitive to coordination (or lack of) of several processes, +it tries to sidestep the coordination issue by polling the file system for the existance +of files, ORIGINAL.txt ... STOP.txt , which are appended to by the generated program. + +NB: That approach of monitoring the state of the running generated program, is clearly not ideal, +but sidesteps the issue of coordinating processes through IPC or stdin/stdout in hopefully +not very flaky way. + +TODO: Cleanup this when/if v has better process control/communication primitives. +*/ +const ( + vexe = os.getenv('VEXE') + tmp_file = os.join_path(os.temp_dir(), 'generated_live_program.tmp.v') + source_file = os.join_path(os.temp_dir(), 'generated_live_program.v') + genexe_file = os.join_path(os.temp_dir(), 'generated_live_program') + output_file = os.join_path(os.temp_dir(), 'generated_live_program.output.txt') + res_original_file = os.join_path(os.temp_dir(), 'ORIGINAL.txt') + res_changed_file = os.join_path(os.temp_dir(), 'CHANGED.txt') + res_another_file = os.join_path(os.temp_dir(), 'ANOTHER.txt') + res_stop_file = os.join_path(os.temp_dir(), 'STOP.txt') + cleanup_files = [tmp_file, source_file, genexe_file, output_file, res_original_file, + res_changed_file, res_another_file, res_stop_file] + live_program_source = get_source_template() +) + +fn get_source_template() string { + src := os.read_file(os.join_path(os.dir(@FILE), 'live_test_template.vv')) or { panic(err) } + return src.replace('#OUTPUT_FILE#', output_file) +} + +fn edefault(name string, default string) string { + res := os.getenv(name) + if res == '' { + return default + } + return res +} + +fn atomic_write_source(source string) { + // NB: here wrtiting is done in 2 steps, since os.write_file can take some time, + // during which the file will be modified, but it will still be not completely written. + // The os.mv after that, guarantees that the reloader will see a complete valid V program. + os.write_file(tmp_file, source) or { panic(err) } + os.mv(tmp_file, source_file) or { panic(err) } +} + +// +fn testsuite_begin() { + if os.user_os() !in ['linux', 'solaris'] && os.getenv('FORCE_LIVE_TEST').len == 0 { + eprintln('Testing the runtime behaviour of -live mode,') + eprintln('is reliable only on Linux/macOS for now.') + eprintln('You can still do it by setting FORCE_LIVE_TEST=1 .') + exit(0) + } + for f in [tmp_file, source_file, output_file, res_original_file, res_changed_file, + res_another_file, res_stop_file] { + os.rm(f) or {} + } + atomic_write_source(live_program_source) +} + +[debuglivetest] +fn vprintln(s string) { + eprintln(s) +} + +fn testsuite_end() { + vprintln('source: $source_file') + vprintln('output: $output_file') + vprintln('---------------------------------------------------------------------------') + output_lines := os.read_lines(output_file) or { + panic('could not read $output_file, error: $err') + } + mut histogram := map[string]int{} + for line in output_lines { + histogram[line] = histogram[line] + 1 + } + for k, v in histogram { + eprintln('> found ${v:5d} times: $k') + } + vprintln('---------------------------------------------------------------------------') + assert histogram['START'] > 0 + assert histogram['ORIGINAL'] > 0 + assert histogram['CHANGED'] + histogram['ANOTHER'] > 0 + // assert histogram['END'] > 0 + for tfile in cleanup_files { + os.rm(tfile) or {} + } +} + +fn change_source(new string) { + time.sleep(100 * time.millisecond) + vprintln('> change ORIGINAL to: $new') + atomic_write_source(live_program_source.replace('ORIGINAL', new)) + wait_for_file(new) +} + +fn wait_for_file(new string) { + time.sleep(100 * time.millisecond) + expected_file := os.join_path(os.temp_dir(), new + '.txt') + eprintln('waiting for $expected_file ...') + max_wait_cycles := edefault('WAIT_CYCLES', '1').int() + for i := 0; i <= max_wait_cycles; i++ { + if i % 25 == 0 { + vprintln(' checking ${i:-10d} for $expected_file ...') + } + if os.exists(expected_file) { + assert true + vprintln('> done.') + time.sleep(100 * time.millisecond) + break + } + time.sleep(5 * time.millisecond) + } +} + +fn setup_cycles_environment() { + mut max_live_cycles := 1000 + mut max_wait_cycles := 400 + if os.user_os() == 'macos' { + // max_live_cycles *= 5 + // max_wait_cycles *= 5 + } + os.setenv('LIVE_CYCLES', '$max_live_cycles', true) + os.setenv('WAIT_CYCLES', '$max_wait_cycles', true) +} + +// +fn test_live_program_can_be_compiled() { + setup_cycles_environment() + eprintln('Compiling...') + os.system('$vexe -nocolor -live -o $genexe_file $source_file') + // + cmd := '$genexe_file > /dev/null &' + eprintln('Running with: $cmd') + res := os.system(cmd) + assert res == 0 + eprintln('... running in the background') + wait_for_file('ORIGINAL') +} + +fn test_live_program_can_be_changed_1() { + change_source('CHANGED') + assert true +} + +fn test_live_program_can_be_changed_2() { + change_source('ANOTHER') + assert true +} + +fn test_live_program_can_be_changed_3() { + change_source('STOP') + change_source('STOP') + change_source('STOP') + assert true +} diff --git a/v_windows/v/vlib/v/live/live_test_template.vv b/v_windows/v/vlib/v/live/live_test_template.vv new file mode 100644 index 0000000..9630fff --- /dev/null +++ b/v_windows/v/vlib/v/live/live_test_template.vv @@ -0,0 +1,66 @@ +module main + +import time +import os +import v.live + +fn append_to_file(fname string, s string) { + mut f := os.open_append(fname) or { + println('>>>> could not open file $fname for appending, err: $err ') + return + } + f.writeln(s) or { + println('>>>> could not write to $fname, err: $err ') + return + } + // info := live.info() + // f.writeln('>>> reloads: ${info.reloads} | ok reloads: ${info.reloads_ok}') + f.close() +} + +fn myprintln(s string) { + append_to_file('#OUTPUT_FILE#', s) + println(s) + os.flush() +} + +[live] +fn pmessage() string { + return 'ORIGINAL' +} + +const ( + delay = 20 +) + +fn edefault(name string, default string) string { + res := os.getenv(name) + if res == '' { + return default + } + return res +} + +fn main() { + mut info := live.info() + info.recheck_period_ms = 5 + myprintln('START') + myprintln('DATE: ' + time.now().str()) + pmessage() + pmessage() + max_cycles := edefault('LIVE_CYCLES', '1').int() + // NB: 1000 * 20 = maximum of ~20s runtime + for i := 0; i < max_cycles; i++ { + s := pmessage() + myprintln(s) + append_to_file(os.resource_abs_path(s + '.txt'), s) + if s == 'STOP' { + break + } + time.sleep(delay * time.millisecond) + } + pmessage() + pmessage() + myprintln('DATE: ' + time.now().str()) + myprintln('END') +} diff --git a/v_windows/v/vlib/v/live/sharedlib/live_sharedlib.v b/v_windows/v/vlib/v/live/sharedlib/live_sharedlib.v new file mode 100644 index 0000000..6ae965d --- /dev/null +++ b/v_windows/v/vlib/v/live/sharedlib/live_sharedlib.v @@ -0,0 +1,7 @@ +module sharedlib + +import v.live + +pub const ( + is_used = live.is_used + 1 +) diff --git a/v_windows/v/vlib/v/markused/markused.v b/v_windows/v/vlib/v/markused/markused.v new file mode 100644 index 0000000..c40dcbe --- /dev/null +++ b/v_windows/v/vlib/v/markused/markused.v @@ -0,0 +1,424 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module markused + +import v.ast +import v.util +import v.pref + +// mark_used walks the AST, starting at main() and marks all used fns transitively +pub fn mark_used(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File) { + mut all_fns, all_consts, all_globals := all_fn_const_and_global(ast_files) + util.timing_start(@METHOD) + defer { + util.timing_measure(@METHOD) + } + mut all_fn_root_names := [ + 'main.main', + '__new_array', + 'str_intp', + 'format_sb', + '__new_array_with_default', + '__new_array_with_array_default', + 'v_realloc' /* needed for _STR */, + 'malloc', + 'malloc_noscan', + 'vcalloc', + 'vcalloc_noscan', + 'new_array_from_c_array', + 'v_fixed_index', + 'memdup', + 'vstrlen', + '__as_cast', + 'tos', + 'tos2', + 'tos3', + 'isnil', + 'opt_ok', + 'error', + '__print_assert_failure', + // utf8_str_visible_length is used by c/str.v + 'utf8_str_visible_length', + 'compare_ints', + 'compare_u64s', + 'compare_strings', + 'compare_ints_reverse', + 'compare_u64s_reverse', + 'compare_strings_reverse', + 'builtin_init', + // byteptr and charptr + '3.vstring', + '3.vstring_with_len', + '3.vstring_literal', + '4.vstring', + '4.vstring_with_len', + '4.vstring_literal', + // byte. methods + '9.str_escaped', + // string. methods + '18.add', + '18.trim_space', + '18.repeat', + '18.replace', + '18.clone', + '18.clone_static', + '18.trim', + '18.substr', + '18.at', + '18.at_with_check', + '18.index_kmp', + // string. ==, !=, etc... + '18.eq', + '18.ne', + '18.lt', + '18.gt', + '18.le', + '18.ge', + 'fast_string_eq', + // other array methods + '20.get', + '20.set', + '20.get_unsafe', + '20.set_unsafe', + '20.get_with_check' /* used for `x := a[i] or {}` */, + '20.clone_static_to_depth', + '20.clone_to_depth', + '20.first', + '20.last', + '20.pointers' /* TODO: handle generic methods calling array primitives more precisely in pool_test.v */, + '20.reverse', + '20.repeat_to_depth', + '20.slice', + '20.slice2', + '59.get', + '59.set', + '65556.last', + '65556.pop', + '65556.push', + '65556.insert_many', + '65556.prepend_many', + '65556.reverse', + '65556.set', + '65556.set_unsafe', + // TODO: process the _vinit const initializations automatically too + 'json.decode_string', + 'json.decode_int', + 'json.decode_bool', + 'json.decode_u64', + 'json.encode_int', + 'json.encode_string', + 'json.encode_bool', + 'json.encode_u64', + 'json.json_print', + 'json.json_parse', + 'main.cb_propagate_test_error', + 'os.getwd', + 'os.init_os_args', + 'os.init_os_args_wide', + 'v.embed_file.find_index_entry_by_path', + ] + + if pref.is_bare { + all_fn_root_names << [ + 'strlen', + 'memcmp', + 'memcpy', + 'realloc', + 'vsnprintf', + 'vsprintf', + ] + } + + if pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { + all_fn_root_names << [ + 'memdup_noscan', + '__new_array_noscan', + '__new_array_with_default_noscan', + '__new_array_with_array_default_noscan', + 'new_array_from_c_array_noscan', + '20.clone_static_to_depth_noscan', + '20.clone_to_depth_noscan', + '20.reverse_noscan', + '20.repeat_to_depth_noscan', + '65556.pop_noscan', + '65556.push_noscan', + '65556.push_many_noscan', + '65556.insert_noscan', + '65556.insert_many_noscan', + '65556.prepend_noscan', + '65556.prepend_many_noscan', + '65556.reverse_noscan', + '65556.grow_cap_noscan', + '65556.grow_len_noscan', + ] + } + + for k, mut mfn in all_fns { + $if trace_skip_unused_all_fns ? { + println('k: $k | mfn: $mfn.name') + } + mut method_receiver_typename := '' + if mfn.is_method { + method_receiver_typename = table.type_to_str(mfn.receiver.typ) + } + if method_receiver_typename == '&wyrand.WyRandRNG' { + // WyRandRNG is the default rand pseudo random generator + all_fn_root_names << k + continue + } + if method_receiver_typename == '&strings.Builder' { + // implicit string builders are generated in auto_eq_methods.v + all_fn_root_names << k + continue + } + // auto generated string interpolation functions, may + // call .str or .auto_str methods for user types: + if k.ends_with('.str') || k.ends_with('.auto_str') { + all_fn_root_names << k + continue + } + if k.ends_with('.init') { + all_fn_root_names << k + continue + } + if k.ends_with('.free') { + all_fn_root_names << k + continue + } + + // sync: + if k == 'sync.new_channel_st' { + all_fn_root_names << k + continue + } + if k == 'sync.channel_select' { + all_fn_root_names << k + continue + } + if method_receiver_typename == '&sync.Channel' { + all_fn_root_names << k + continue + } + if k.ends_with('.lock') || k.ends_with('.unlock') || k.ends_with('.rlock') + || k.ends_with('.runlock') { + all_fn_root_names << k + continue + } + // testing framework: + if pref.is_test { + if k.starts_with('test_') || k.contains('.test_') { + all_fn_root_names << k + continue + } + if k.starts_with('testsuite_') || k.contains('.testsuite_') { + // eprintln('>>> test suite: $k') + all_fn_root_names << k + continue + } + } + // public/exported functions can not be skipped, + // especially when producing a shared library: + if mfn.is_pub && pref.is_shared { + all_fn_root_names << k + continue + } + if mfn.name in ['+', '-', '*', '%', '/', '<', '=='] { + // TODO: mark the used operators in the checker + all_fn_root_names << k + continue + } + if pref.prealloc && k.starts_with('prealloc_') { + all_fn_root_names << k + continue + } + } + + // handle assertions and testing framework callbacks: + if pref.is_debug { + all_fn_root_names << 'panic_debug' + } + all_fn_root_names << 'panic_optional_not_set' + if pref.is_test { + all_fn_root_names << 'main.cb_assertion_ok' + all_fn_root_names << 'main.cb_assertion_failed' + if benched_tests_sym := table.find_type('main.BenchedTests') { + bts_type := benched_tests_sym.methods[0].params[0].typ + all_fn_root_names << '${bts_type}.testing_step_start' + all_fn_root_names << '${bts_type}.testing_step_end' + all_fn_root_names << '${bts_type}.end_testing' + all_fn_root_names << 'main.start_testing' + } + } + + // handle interface implementation methods: + for isym in table.type_symbols { + if isym.kind != .interface_ { + continue + } + interface_info := isym.info as ast.Interface + if interface_info.methods.len == 0 { + continue + } + for itype in interface_info.types { + pitype := itype.set_nr_muls(1) + mut itypes := [itype] + if pitype != itype { + itypes << pitype + } + for method in interface_info.methods { + for typ in itypes { + interface_implementation_method_name := '${int(typ)}.$method.name' + $if trace_skip_unused_interface_methods ? { + eprintln('>> isym.name: $isym.name | interface_implementation_method_name: $interface_implementation_method_name') + } + all_fn_root_names << interface_implementation_method_name + } + } + } + } + + // handle vweb magic router methods: + typ_vweb_result := table.find_type_idx('vweb.Result') + if typ_vweb_result != 0 { + all_fn_root_names << 'vweb.filter' + typ_vweb_context := ast.Type(table.find_type_idx('vweb.Context')).set_nr_muls(1) + all_fn_root_names << '${int(typ_vweb_context)}.html' + for vgt in table.used_vweb_types { + sym_app := table.get_type_symbol(vgt) + for m in sym_app.methods { + if m.return_type == typ_vweb_result { + pvgt := vgt.set_nr_muls(1) + // eprintln('vgt: $vgt | pvgt: $pvgt | sym_app.name: $sym_app.name | m.name: $m.name') + all_fn_root_names << '${int(pvgt)}.$m.name' + } + } + } + } + + // handle ORM drivers: + orm_connection_implementations := table.iface_types['orm.Connection'] or { []ast.Type{} } + if orm_connection_implementations.len > 0 { + for k, _ in all_fns { + if k.starts_with('orm.') { + all_fn_root_names << k + } + } + for orm_type in orm_connection_implementations { + all_fn_root_names << '${int(orm_type)}.select' + all_fn_root_names << '${int(orm_type)}.insert' + all_fn_root_names << '${int(orm_type)}.update' + all_fn_root_names << '${int(orm_type)}.delete' + all_fn_root_names << '${int(orm_type)}.create' + all_fn_root_names << '${int(orm_type)}.drop' + all_fn_root_names << '${int(orm_type)}.last_id' + } + } + + // handle -live main programs: + if pref.is_livemain { + all_fn_root_names << 'v.live.executable.start_reloader' + all_fn_root_names << 'v.live.executable.new_live_reload_info' + } + + mut walker := Walker{ + table: table + files: ast_files + all_fns: all_fns + all_consts: all_consts + all_globals: all_globals + pref: pref + } + // println( all_fns.keys() ) + walker.mark_exported_fns() + walker.mark_root_fns(all_fn_root_names) + + if walker.n_asserts > 0 { + walker.fn_decl(mut all_fns['__print_assert_failure']) + } + if table.used_maps > 0 { + for k, mut mfn in all_fns { + mut method_receiver_typename := '' + if mfn.is_method { + method_receiver_typename = table.type_to_str(mfn.receiver.typ) + } + if k in ['new_map', 'new_map_init', 'map_hash_string'] + || method_receiver_typename == '&map' || method_receiver_typename == '&DenseArray' + || k.starts_with('map_') { + walker.fn_decl(mut mfn) + } + if pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { + if k in ['new_map_noscan_key', 'new_map_noscan_value', 'new_map_noscan_key_value', + 'new_map_init_noscan_key', 'new_map_init_noscan_value', + 'new_map_init_noscan_key_value', + ] { + walker.fn_decl(mut mfn) + } + } + } + } else { + for map_fn_name in ['new_map', 'new_map_init', 'map_hash_string', 'new_dense_array'] { + walker.used_fns.delete(map_fn_name) + } + for k, mut mfn in all_fns { + if !mfn.is_method { + continue + } + method_receiver_typename := table.type_to_str(mfn.receiver.typ) + if method_receiver_typename in ['&map', '&mapnode', '&SortedMap', '&DenseArray'] { + walker.used_fns.delete(k) + } + } + } + + $if trace_skip_unused_fn_names ? { + for key, _ in walker.used_fns { + println('> used fn key: $key') + } + } + + table.used_fns = walker.used_fns.move() + table.used_consts = walker.used_consts.move() + table.used_globals = walker.used_globals.move() + + $if trace_skip_unused ? { + eprintln('>> t.used_fns: $table.used_fns.keys()') + eprintln('>> t.used_consts: $table.used_consts.keys()') + eprintln('>> t.used_globals: $table.used_globals.keys()') + eprintln('>> walker.table.used_maps: $walker.table.used_maps') + } +} + +fn all_fn_const_and_global(ast_files []&ast.File) (map[string]ast.FnDecl, map[string]ast.ConstField, map[string]ast.GlobalField) { + util.timing_start(@METHOD) + defer { + util.timing_measure(@METHOD) + } + mut all_fns := map[string]ast.FnDecl{} + mut all_consts := map[string]ast.ConstField{} + mut all_globals := map[string]ast.GlobalField{} + for i in 0 .. ast_files.len { + file := ast_files[i] + for node in file.stmts { + match node { + ast.FnDecl { + fkey := node.fkey() + all_fns[fkey] = node + } + ast.ConstDecl { + for cfield in node.fields { + ckey := cfield.name + all_consts[ckey] = cfield + } + } + ast.GlobalDecl { + for gfield in node.fields { + gkey := gfield.name + all_globals[gkey] = gfield + } + } + else {} + } + } + } + return all_fns, all_consts, all_globals +} diff --git a/v_windows/v/vlib/v/markused/walker.v b/v_windows/v/vlib/v/markused/walker.v new file mode 100644 index 0000000..fea9c09 --- /dev/null +++ b/v_windows/v/vlib/v/markused/walker.v @@ -0,0 +1,464 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. +module markused + +// Walk the entire program starting at fn main and marks used (called) functions. +// Unused functions can be safely skipped by the backends to save CPU time and space. +import v.ast +import v.pref + +pub struct Walker { +pub mut: + table &ast.Table + used_fns map[string]bool // used_fns['println'] == true + used_consts map[string]bool // used_consts['os.args'] == true + used_globals map[string]bool + n_asserts int + pref &pref.Preferences +mut: + files []&ast.File + all_fns map[string]ast.FnDecl + all_consts map[string]ast.ConstField + all_globals map[string]ast.GlobalField +} + +pub fn (mut w Walker) mark_fn_as_used(fkey string) { + $if trace_skip_unused_marked ? { + eprintln(' fn > |$fkey|') + } + w.used_fns[fkey] = true +} + +pub fn (mut w Walker) mark_const_as_used(ckey string) { + $if trace_skip_unused_marked ? { + eprintln(' const > |$ckey|') + } + w.used_consts[ckey] = true + cfield := w.all_consts[ckey] or { return } + w.expr(cfield.expr) +} + +pub fn (mut w Walker) mark_global_as_used(ckey string) { + $if trace_skip_unused_marked ? { + eprintln(' global > |$ckey|') + } + w.used_globals[ckey] = true + gfield := w.all_globals[ckey] or { return } + w.expr(gfield.expr) +} + +pub fn (mut w Walker) mark_root_fns(all_fn_root_names []string) { + for fn_name in all_fn_root_names { + if fn_name !in w.used_fns { + $if trace_skip_unused_roots ? { + println('>>>> $fn_name uses: ') + } + w.fn_decl(mut w.all_fns[fn_name]) + } + } +} + +pub fn (mut w Walker) mark_exported_fns() { + for _, mut func in w.all_fns { + if func.is_exported { + w.fn_decl(mut func) + } + } +} + +pub fn (mut w Walker) stmt(node ast.Stmt) { + match mut node { + ast.EmptyStmt {} + ast.AsmStmt { + w.asm_io(node.output) + w.asm_io(node.input) + } + ast.AssertStmt { + if node.is_used { + w.expr(node.expr) + w.n_asserts++ + } + } + ast.AssignStmt { + w.exprs(node.left) + w.exprs(node.right) + } + ast.Block { + w.stmts(node.stmts) + } + ast.CompFor { + w.stmts(node.stmts) + } + ast.ConstDecl { + w.const_fields(node.fields) + } + ast.ExprStmt { + w.expr(node.expr) + } + ast.FnDecl { + w.fn_decl(mut node) + } + ast.ForCStmt { + w.expr(node.cond) + w.stmt(node.inc) + w.stmts(node.stmts) + } + ast.ForInStmt { + w.expr(node.cond) + w.expr(node.high) + w.stmts(node.stmts) + if node.kind == .map { + w.table.used_maps++ + } + } + ast.ForStmt { + w.expr(node.cond) + w.stmts(node.stmts) + } + ast.Return { + w.exprs(node.exprs) + } + ast.SqlStmt { + w.expr(node.db_expr) + for line in node.lines { + w.expr(line.where_expr) + w.exprs(line.update_exprs) + } + } + ast.StructDecl { + w.struct_fields(node.fields) + } + ast.DeferStmt { + w.stmts(node.stmts) + } + ast.GlobalDecl { + for gf in node.fields { + if gf.has_expr { + w.expr(gf.expr) + } + } + } + ast.BranchStmt {} + ast.EnumDecl {} + ast.GotoLabel {} + ast.GotoStmt {} + ast.HashStmt {} + ast.Import {} + ast.InterfaceDecl {} + ast.Module {} + ast.TypeDecl {} + ast.NodeError {} + } +} + +fn (mut w Walker) asm_io(ios []ast.AsmIO) { + for io in ios { + w.expr(io.expr) + } +} + +fn (mut w Walker) defer_stmts(stmts []ast.DeferStmt) { + for stmt in stmts { + w.stmts(stmt.stmts) + } +} + +fn (mut w Walker) stmts(stmts []ast.Stmt) { + for stmt in stmts { + w.stmt(stmt) + } +} + +fn (mut w Walker) exprs(exprs []ast.Expr) { + for expr in exprs { + w.expr(expr) + } +} + +fn (mut w Walker) expr(node ast.Expr) { + match mut node { + ast.EmptyExpr { + // TODO make sure this doesn't happen + // panic('Walker: EmptyExpr') + } + ast.AnonFn { + w.fn_decl(mut node.decl) + } + ast.ArrayInit { + w.expr(node.len_expr) + w.expr(node.cap_expr) + w.expr(node.default_expr) + w.exprs(node.exprs) + } + ast.Assoc { + w.exprs(node.exprs) + } + ast.ArrayDecompose { + w.expr(node.expr) + } + ast.CallExpr { + w.call_expr(mut node) + } + ast.CastExpr { + w.expr(node.expr) + w.expr(node.arg) + } + ast.ChanInit { + w.expr(node.cap_expr) + } + ast.ConcatExpr { + w.exprs(node.vals) + } + ast.ComptimeSelector { + w.expr(node.left) + w.expr(node.field_expr) + } + ast.ComptimeCall { + w.expr(node.left) + if node.is_vweb { + w.stmts(node.vweb_tmpl.stmts) + } + } + ast.DumpExpr { + w.expr(node.expr) + w.fn_by_name('eprint') + w.fn_by_name('eprintln') + } + ast.GoExpr { + w.expr(node.call_expr) + if w.pref.os == .windows { + w.fn_by_name('panic_lasterr') + w.fn_by_name('winapi_lasterr_str') + } else { + w.fn_by_name('c_error_number_str') + w.fn_by_name('panic_error_number') + } + } + ast.IndexExpr { + w.expr(node.left) + w.expr(node.index) + w.or_block(node.or_expr) + sym := w.table.get_final_type_symbol(node.left_type) + if sym.kind == .map { + w.table.used_maps++ + } + } + ast.InfixExpr { + w.expr(node.left) + w.expr(node.right) + w.or_block(node.or_block) + if node.left_type == 0 { + return + } + sym := w.table.get_type_symbol(node.left_type) + if sym.kind == .struct_ { + if opmethod := sym.find_method(node.op.str()) { + w.fn_decl(mut &ast.FnDecl(opmethod.source_fn)) + } + } + right_sym := w.table.get_type_symbol(node.right_type) + if node.op in [.not_in, .key_in] && right_sym.kind == .map { + w.table.used_maps++ + } + } + ast.IfGuardExpr { + w.expr(node.expr) + } + ast.IfExpr { + w.expr(node.left) + for b in node.branches { + w.expr(b.cond) + w.stmts(b.stmts) + } + } + ast.Ident { + match node.kind { + .constant { + w.mark_const_as_used(node.name) + } + .function { + w.fn_by_name(node.name) + } + .global { + w.mark_global_as_used(node.name) + } + else { + // `.unresolved`, `.blank_ident`, `.variable`, `.function` + // println('>>> else, ast.Ident kind: $node.kind') + } + } + } + ast.Likely { + w.expr(node.expr) + } + ast.MapInit { + w.exprs(node.keys) + w.exprs(node.vals) + w.table.used_maps++ + } + ast.MatchExpr { + w.expr(node.cond) + for b in node.branches { + w.exprs(b.exprs) + w.stmts(b.stmts) + } + } + ast.None {} + ast.ParExpr { + w.expr(node.expr) + } + ast.PrefixExpr { + w.expr(node.right) + } + ast.PostfixExpr { + w.expr(node.expr) + } + ast.RangeExpr { + if node.has_low { + w.expr(node.low) + } + if node.has_high { + w.expr(node.high) + } + } + ast.SizeOf, ast.IsRefType { + w.expr(node.expr) + } + ast.StringInterLiteral { + w.exprs(node.exprs) + } + ast.SelectorExpr { + w.expr(node.expr) + } + ast.SqlExpr { + w.expr(node.db_expr) + w.expr(node.offset_expr) + w.expr(node.order_expr) + w.expr(node.limit_expr) + w.expr(node.where_expr) + } + ast.StructInit { + sym := w.table.get_type_symbol(node.typ) + if sym.kind == .struct_ { + info := sym.info as ast.Struct + for ifield in info.fields { + if ifield.has_default_expr { + w.expr(ifield.default_expr) + } + } + } + if node.has_update_expr { + w.expr(node.update_expr) + } + for sif in node.fields { + w.expr(sif.expr) + } + for sie in node.embeds { + w.expr(sie.expr) + } + } + ast.TypeOf { + w.expr(node.expr) + } + /// + ast.AsCast { + w.expr(node.expr) + } + ast.AtExpr {} + ast.BoolLiteral {} + ast.FloatLiteral {} + ast.CharLiteral {} + ast.IntegerLiteral {} + ast.StringLiteral {} + ast.CTempVar { + w.expr(node.orig) + } + ast.Comment {} + ast.EnumVal {} + ast.LockExpr { + w.stmts(node.stmts) + } + ast.OffsetOf {} + ast.OrExpr { + w.or_block(node) + } + ast.SelectExpr { + for branch in node.branches { + w.stmt(branch.stmt) + w.stmts(branch.stmts) + } + } + ast.TypeNode {} + ast.UnsafeExpr { + w.expr(node.expr) + } + ast.NodeError {} + } +} + +pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { + if node.language == .c { + return + } + fkey := node.fkey() + if w.used_fns[fkey] { + // This function is already known to be called, meaning it has been processed already. + // Save CPU time and do nothing. + return + } + w.mark_fn_as_used(fkey) + w.stmts(node.stmts) + w.defer_stmts(node.defer_stmts) +} + +pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { + for arg in node.args { + w.expr(arg.expr) + } + if node.language == .c { + return + } + w.expr(node.left) + w.or_block(node.or_block) + // + fn_name := node.fkey() + if w.used_fns[fn_name] { + return + } + w.mark_fn_as_used(fn_name) + stmt := w.all_fns[fn_name] or { return } + if stmt.name == node.name { + if !node.is_method || (node.receiver_type == stmt.receiver.typ) { + w.stmts(stmt.stmts) + } + } +} + +pub fn (mut w Walker) fn_by_name(fn_name string) { + if w.used_fns[fn_name] { + return + } + stmt := w.all_fns[fn_name] or { return } + w.mark_fn_as_used(fn_name) + w.stmts(stmt.stmts) +} + +pub fn (mut w Walker) struct_fields(sfields []ast.StructField) { + for sf in sfields { + if sf.has_default_expr { + w.expr(sf.default_expr) + } + } +} + +pub fn (mut w Walker) const_fields(cfields []ast.ConstField) { + for cf in cfields { + w.expr(cf.expr) + } +} + +pub fn (mut w Walker) or_block(node ast.OrExpr) { + if node.kind == .block { + w.stmts(node.stmts) + } +} diff --git a/v_windows/v/vlib/v/parser/assign.v b/v_windows/v/vlib/v/parser/assign.v new file mode 100644 index 0000000..76c074a --- /dev/null +++ b/v_windows/v/vlib/v/parser/assign.v @@ -0,0 +1,236 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast + +fn (mut p Parser) assign_stmt() ast.Stmt { + mut defer_vars := p.defer_vars + p.defer_vars = []ast.Ident{} + + exprs, comments := p.expr_list() + + if !(p.inside_defer && p.tok.kind == .decl_assign) { + defer_vars << p.defer_vars + } + p.defer_vars = defer_vars + return p.partial_assign_stmt(exprs, comments) +} + +fn (mut p Parser) check_undefined_variables(exprs []ast.Expr, val ast.Expr) ? { + match val { + ast.Ident { + for expr in exprs { + if expr is ast.Ident { + if expr.name == val.name && expr.kind != .blank_ident { + p.error_with_pos('undefined variable: `$val.name`', val.pos) + return error('undefined variable: `$val.name`') + } + } + } + } + ast.ArrayInit { + if val.has_cap { + p.check_undefined_variables(exprs, val.cap_expr) ? + } + if val.has_len { + p.check_undefined_variables(exprs, val.len_expr) ? + } + if val.has_default { + p.check_undefined_variables(exprs, val.default_expr) ? + } + for expr in val.exprs { + p.check_undefined_variables(exprs, expr) ? + } + } + ast.CallExpr { + p.check_undefined_variables(exprs, val.left) ? + for arg in val.args { + p.check_undefined_variables(exprs, arg.expr) ? + } + } + ast.InfixExpr { + p.check_undefined_variables(exprs, val.left) ? + p.check_undefined_variables(exprs, val.right) ? + } + ast.MapInit { + for key in val.keys { + p.check_undefined_variables(exprs, key) ? + } + for value in val.vals { + p.check_undefined_variables(exprs, value) ? + } + } + ast.ParExpr { + p.check_undefined_variables(exprs, val.expr) ? + } + ast.PostfixExpr { + p.check_undefined_variables(exprs, val.expr) ? + } + ast.PrefixExpr { + p.check_undefined_variables(exprs, val.right) ? + } + ast.StringInterLiteral { + for expr_ in val.exprs { + p.check_undefined_variables(exprs, expr_) ? + } + } + else {} + } +} + +fn (mut p Parser) check_cross_variables(exprs []ast.Expr, val ast.Expr) bool { + val_str := val.str() + match val { + ast.Ident { + for expr in exprs { + if expr is ast.Ident { + if expr.name == val.name { + return true + } + } + } + } + ast.IndexExpr { + for expr in exprs { + if expr.str() == val_str { + return true + } + } + } + ast.InfixExpr { + return p.check_cross_variables(exprs, val.left) + || p.check_cross_variables(exprs, val.right) + } + ast.PrefixExpr { + return p.check_cross_variables(exprs, val.right) + } + ast.PostfixExpr { + return p.check_cross_variables(exprs, val.expr) + } + ast.SelectorExpr { + for expr in exprs { + if expr.str() == val_str { + return true + } + } + } + else {} + } + return false +} + +fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comment) ast.Stmt { + p.is_stmt_ident = false + op := p.tok.kind + mut pos := p.tok.position() + p.next() + mut comments := []ast.Comment{cap: 2 * left_comments.len + 1} + comments << left_comments + comments << p.eat_comments() + mut right_comments := []ast.Comment{} + mut right := []ast.Expr{cap: left.len} + right, right_comments = p.expr_list() + comments << right_comments + end_comments := p.eat_comments(same_line: true) + mut has_cross_var := false + if op == .decl_assign { + // a, b := a + 1, b + for r in right { + p.check_undefined_variables(left, r) or { + return p.error('check_undefined_variables failed') + } + } + } else if left.len > 1 { + // a, b = b, a + for r in right { + has_cross_var = p.check_cross_variables(left, r) + if op !in [.assign, .decl_assign] { + return p.error_with_pos('unexpected $op.str(), expecting := or = or comma', + pos) + } + if has_cross_var { + break + } + } + } + mut is_static := false + for i, lx in left { + match mut lx { + ast.Ident { + if op == .decl_assign { + if p.scope.known_var(lx.name) { + return p.error_with_pos('redefinition of `$lx.name`', lx.pos) + } + mut share := ast.ShareType(0) + if lx.info is ast.IdentVar { + iv := lx.info as ast.IdentVar + share = iv.share + if iv.is_static { + if !p.pref.translated && !p.pref.is_fmt && !p.inside_unsafe_fn { + return p.error_with_pos('static variables are supported only in -translated mode or in [unsafe] fn', + lx.pos) + } + is_static = true + } + } + r0 := right[0] + mut v := ast.Var{ + name: lx.name + expr: if left.len == right.len { right[i] } else { ast.empty_expr() } + share: share + is_mut: lx.is_mut || p.inside_for + pos: lx.pos + is_stack_obj: p.inside_for + } + if p.pref.autofree { + if r0 is ast.CallExpr { + // Set correct variable position (after the or block) + // so that autofree doesn't free it in cgen before + // it's declared. (`Or` variables are declared after the or block). + if r0.or_block.pos.pos > 0 && r0.or_block.stmts.len > 0 { + v.is_or = true + // v.pos = r0.or_block.pos. + } + } + } + obj := ast.ScopeObject(v) + lx.obj = obj + p.scope.register(obj) + } + } + ast.IndexExpr { + if op == .decl_assign { + return p.error_with_pos('non-name `$lx.left[$lx.index]` on left side of `:=`', + lx.pos) + } + lx.is_setter = true + } + ast.ParExpr {} + ast.PrefixExpr {} + ast.SelectorExpr { + if op == .decl_assign { + return p.error_with_pos('struct fields can only be declared during the initialization', + lx.pos) + } + } + else { + // TODO: parexpr ( check vars) + // else { p.error_with_pos('unexpected `${typeof(lx)}`', lx.position()) } + } + } + } + pos.update_last_line(p.prev_tok.line_nr) + return ast.AssignStmt{ + op: op + left: left + right: right + comments: comments + end_comments: end_comments + pos: pos + has_cross_var: has_cross_var + is_simple: p.inside_for && p.tok.kind == .lcbr + is_static: is_static + } +} diff --git a/v_windows/v/vlib/v/parser/comptime.v b/v_windows/v/vlib/v/parser/comptime.v new file mode 100644 index 0000000..b85042d --- /dev/null +++ b/v_windows/v/vlib/v/parser/comptime.v @@ -0,0 +1,359 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import os +import v.ast +import v.pref +import v.token + +const ( + supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig'] +) + +// // #include, #flag, #v +fn (mut p Parser) hash() ast.HashStmt { + pos := p.tok.position() + val := p.tok.lit + kind := val.all_before(' ') + p.next() + mut main_str := '' + mut msg := '' + content := val.all_after('$kind ').all_before('//') + if content.contains(' #') { + main_str = content.all_before(' #').trim_space() + msg = content.all_after(' #').trim_space() + } else { + main_str = content.trim_space() + msg = '' + } + return ast.HashStmt{ + mod: p.mod + source_file: p.file_name + val: val + kind: kind + main: main_str + msg: msg + pos: pos + } +} + +fn (mut p Parser) comp_call() ast.ComptimeCall { + err_node := ast.ComptimeCall{ + scope: 0 + } + p.check(.dollar) + start_pos := p.prev_tok.position() + error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()` and `\$vweb.html()` comptime functions are supported right now' + if p.peek_tok.kind == .dot { + name := p.check_name() // skip `vweb.html()` TODO + if name != 'vweb' { + p.error(error_msg) + return err_node + } + p.check(.dot) + } + method_name := p.check_name() // (.name) + if method_name !in parser.supported_comptime_calls { + p.error(error_msg) + return err_node + } + is_embed_file := method_name == 'embed_file' + is_html := method_name == 'html' + // $env('ENV_VAR_NAME') + p.check(.lpar) + spos := p.tok.position() + if method_name == 'env' { + s := p.tok.lit + p.check(.string) + p.check(.rpar) + return ast.ComptimeCall{ + scope: 0 + method_name: method_name + args_var: s + is_env: true + env_pos: spos + pos: spos.extend(p.prev_tok.position()) + } + } + if method_name == 'pkgconfig' { + s := p.tok.lit + p.check(.string) + p.check(.rpar) + return ast.ComptimeCall{ + scope: 0 + method_name: method_name + args_var: s + is_pkgconfig: true + env_pos: spos + pos: spos.extend(p.prev_tok.position()) + } + } + literal_string_param := if is_html { '' } else { p.tok.lit } + path_of_literal_string_param := literal_string_param.replace('/', os.path_separator) + if !is_html { + p.check(.string) + } + p.check(.rpar) + // $embed_file('/path/to/file') + if is_embed_file { + mut epath := path_of_literal_string_param + // Validate that the epath exists, and that it is actually a file. + if epath == '' { + p.error_with_pos('supply a valid relative or absolute file path to the file to embed', + spos) + return err_node + } + if !p.pref.is_fmt { + abs_path := os.real_path(epath) + // check absolute path first + if !os.exists(abs_path) { + // ... look relative to the source file: + epath = os.real_path(os.join_path(os.dir(p.file_name), epath)) + if !os.exists(epath) { + p.error_with_pos('"$epath" does not exist so it cannot be embedded', + spos) + return err_node + } + if !os.is_file(epath) { + p.error_with_pos('"$epath" is not a file so it cannot be embedded', + spos) + return err_node + } + } else { + epath = abs_path + } + } + p.register_auto_import('v.embed_file') + return ast.ComptimeCall{ + scope: 0 + is_embed: true + embed_file: ast.EmbeddedFile{ + rpath: literal_string_param + apath: epath + } + pos: start_pos.extend(p.prev_tok.position()) + } + } + // Compile vweb html template to V code, parse that V code and embed the resulting V function + // that returns an html string. + fn_path := p.cur_fn_name.split('_') + fn_path_joined := fn_path.join(os.path_separator) + compiled_vfile_path := os.real_path(p.scanner.file_path.replace('/', os.path_separator)) + tmpl_path := if is_html { '${fn_path.last()}.html' } else { path_of_literal_string_param } + // Looking next to the vweb program + dir := os.dir(compiled_vfile_path) + mut path := os.join_path(dir, fn_path_joined) + path += '.html' + path = os.real_path(path) + if !is_html { + path = os.join_path(dir, tmpl_path) + } + if !os.exists(path) { + if is_html { + // can be in `templates/` + path = os.join_path(dir, 'templates', fn_path_joined) + path += '.html' + } + if !os.exists(path) { + if p.pref.is_fmt { + return ast.ComptimeCall{ + scope: 0 + is_vweb: true + method_name: method_name + args_var: literal_string_param + pos: start_pos.extend(p.prev_tok.position()) + } + } + if is_html { + p.error('vweb HTML template "$path" not found') + } else { + p.error('template file "$path" not found') + } + return err_node + } + // println('path is now "$path"') + } + tmp_fn_name := p.cur_fn_name.replace('.', '__') + $if trace_comptime ? { + println('>>> compiling comptime template file "$path" for $tmp_fn_name') + } + v_code := p.compile_template_file(path, tmp_fn_name) + $if print_vweb_template_expansions ? { + lines := v_code.split('\n') + for i, line in lines { + println('$path:${i + 1}: $line') + } + } + mut scope := &ast.Scope{ + start_pos: 0 + parent: p.table.global_scope + } + $if trace_comptime ? { + println('') + println('>>> template for $path:') + println(v_code) + println('>>> end of template END') + println('') + } + mut file := parse_comptime(v_code, p.table, p.pref, scope) + file.path = tmpl_path + // copy vars from current fn scope into vweb_tmpl scope + for stmt in file.stmts { + if stmt is ast.FnDecl { + if stmt.name == 'main.vweb_tmpl_$tmp_fn_name' { + // mut tmpl_scope := file.scope.innermost(stmt.body_pos.pos) + mut tmpl_scope := stmt.scope + for _, obj in p.scope.objects { + if obj is ast.Var { + mut v := obj + v.pos = stmt.body_pos + tmpl_scope.register(ast.Var{ + ...v + is_used: true + }) + // set the controller action var to used + // if it's unused in the template it will warn + v.is_used = true + } + } + break + } + } + } + return ast.ComptimeCall{ + scope: 0 + is_vweb: true + vweb_tmpl: file + method_name: method_name + args_var: literal_string_param + pos: start_pos.extend(p.prev_tok.position()) + } +} + +fn (mut p Parser) comp_for() ast.CompFor { + // p.comp_for() handles these special forms: + // $for method in App(methods) { + // $for field in App(fields) { + p.next() + p.check(.key_for) + var_pos := p.tok.position() + val_var := p.check_name() + p.check(.key_in) + mut typ_pos := p.tok.position() + lang := p.parse_language() + typ := p.parse_any_type(lang, false, false) + typ_pos = typ_pos.extend(p.prev_tok.position()) + p.check(.dot) + for_val := p.check_name() + mut kind := ast.CompForKind.methods + p.open_scope() + if for_val == 'methods' { + p.scope.register(ast.Var{ + name: val_var + typ: p.table.find_type_idx('FunctionData') + pos: var_pos + }) + } else if for_val == 'fields' { + p.scope.register(ast.Var{ + name: val_var + typ: p.table.find_type_idx('FieldData') + pos: var_pos + }) + kind = .fields + } else if for_val == 'attributes' { + p.scope.register(ast.Var{ + name: val_var + typ: p.table.find_type_idx('StructAttribute') + pos: var_pos + }) + kind = .attributes + } else { + p.error_with_pos('unknown kind `$for_val`, available are: `methods`, `fields` or `attributes`', + p.prev_tok.position()) + return ast.CompFor{} + } + spos := p.tok.position() + stmts := p.parse_block() + p.close_scope() + return ast.CompFor{ + val_var: val_var + stmts: stmts + kind: kind + typ: typ + typ_pos: typ_pos + pos: spos.extend(p.tok.position()) + } +} + +// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens +fn (mut p Parser) at() ast.AtExpr { + name := p.tok.lit + kind := match name { + '@FN' { token.AtKind.fn_name } + '@METHOD' { token.AtKind.method_name } + '@MOD' { token.AtKind.mod_name } + '@STRUCT' { token.AtKind.struct_name } + '@FILE' { token.AtKind.file_path } + '@LINE' { token.AtKind.line_nr } + '@COLUMN' { token.AtKind.column_nr } + '@VHASH' { token.AtKind.vhash } + '@VMOD_FILE' { token.AtKind.vmod_file } + '@VEXE' { token.AtKind.vexe_path } + '@VEXEROOT' { token.AtKind.vexeroot_path } + '@VMODROOT' { token.AtKind.vmodroot_path } + '@VROOT' { token.AtKind.vroot_path } // deprecated, use @VEXEROOT or @VMODROOT + else { token.AtKind.unknown } + } + p.next() + return ast.AtExpr{ + name: name + pos: p.tok.position() + kind: kind + } +} + +fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { + p.check(.dollar) + start_pos := p.prev_tok.position() + if p.peek_tok.kind == .lpar { + method_pos := p.tok.position() + method_name := p.check_name() + p.mark_var_as_used(method_name) + // `app.$action()` (`action` is a string) + p.check(.lpar) + args := p.call_args() + p.check(.rpar) + if p.tok.kind == .key_orelse { + p.check(.key_orelse) + p.check(.lcbr) + } + return ast.ComptimeCall{ + left: left + method_name: method_name + method_pos: method_pos + scope: p.scope + args_var: '' + args: args + pos: start_pos.extend(p.prev_tok.position()) + } + } + mut has_parens := false + if p.tok.kind == .lpar { + p.check(.lpar) + has_parens = true + } else { + p.warn_with_pos('use brackets instead e.g. `s.$(field.name)` - run vfmt', p.tok.position()) + } + expr := p.expr(0) + if has_parens { + p.check(.rpar) + } + return ast.ComptimeSelector{ + has_parens: has_parens + left: left + field_expr: expr + pos: start_pos.extend(p.prev_tok.position()) + } +} diff --git a/v_windows/v/vlib/v/parser/containers.v b/v_windows/v/vlib/v/parser/containers.v new file mode 100644 index 0000000..1b22ca8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/containers.v @@ -0,0 +1,190 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast + +fn (mut p Parser) array_init() ast.ArrayInit { + first_pos := p.tok.position() + mut last_pos := p.tok.position() + p.check(.lsbr) + // p.warn('array_init() exp=$p.expected_type') + mut array_type := ast.void_type + mut elem_type := ast.void_type + mut elem_type_pos := first_pos + mut exprs := []ast.Expr{} + mut ecmnts := [][]ast.Comment{} + mut pre_cmnts := []ast.Comment{} + mut is_fixed := false + mut has_val := false + mut has_type := false + mut has_default := false + mut default_expr := ast.empty_expr() + if p.tok.kind == .rsbr { + last_pos = p.tok.position() + // []typ => `[]` and `typ` must be on the same line + line_nr := p.tok.line_nr + p.next() + // []string + if p.tok.kind in [.name, .amp, .lsbr, .key_shared] && p.tok.line_nr == line_nr { + elem_type_pos = p.tok.position() + elem_type = p.parse_type() + // this is set here because it's a known type, others could be the + // result of expr so we do those in checker + idx := p.table.find_or_register_array(elem_type) + if elem_type.has_flag(.generic) { + array_type = ast.new_type(idx).set_flag(.generic) + } else { + array_type = ast.new_type(idx) + } + has_type = true + } + last_pos = p.tok.position() + } else { + // [1,2,3] or [const]byte + old_inside_array_lit := p.inside_array_lit + p.inside_array_lit = true + pre_cmnts = p.eat_comments() + for i := 0; p.tok.kind !in [.rsbr, .eof]; i++ { + exprs << p.expr(0) + ecmnts << p.eat_comments() + if p.tok.kind == .comma { + p.next() + } + ecmnts.last() << p.eat_comments() + } + p.inside_array_lit = old_inside_array_lit + line_nr := p.tok.line_nr + $if tinyc { + // NB: do not remove the next line without testing + // v selfcompilation with tcc first + tcc_stack_bug := 12345 + _ = tcc_stack_bug + } + last_pos = p.tok.position() + p.check(.rsbr) + if exprs.len == 1 && p.tok.kind in [.name, .amp, .lsbr] && p.tok.line_nr == line_nr { + // [100]byte + elem_type = p.parse_type() + last_pos = p.tok.position() + is_fixed = true + if p.tok.kind == .lcbr { + p.next() + if p.tok.kind != .rcbr { + pos := p.tok.position() + n := p.check_name() + if n != 'init' { + p.error_with_pos('expected `init:`, not `$n`', pos) + return ast.ArrayInit{} + } + p.check(.colon) + has_default = true + default_expr = p.expr(0) + } + last_pos = p.tok.position() + p.check(.rcbr) + } else { + p.warn_with_pos('use e.g. `x := [1]Type{}` instead of `x := [1]Type`', + first_pos.extend(last_pos)) + } + } else { + if p.tok.kind == .not { // && p.tok.line_nr == p.prev_tok.line_nr { + last_pos = p.tok.position() + is_fixed = true + has_val = true + p.next() + } + if p.tok.kind == .not && p.tok.line_nr == p.prev_tok.line_nr { + last_pos = p.tok.position() + p.error_with_pos('use e.g. `[1, 2, 3]!` instead of `[1, 2, 3]!!`', last_pos) + p.next() + } + } + } + if exprs.len == 0 && p.tok.kind != .lcbr && has_type { + if !p.pref.is_fmt { + p.warn_with_pos('use `x := []Type{}` instead of `x := []Type`', first_pos.extend(last_pos)) + } + } + mut has_len := false + mut has_cap := false + mut len_expr := ast.empty_expr() + mut cap_expr := ast.empty_expr() + if p.tok.kind == .lcbr && exprs.len == 0 && array_type != ast.void_type { + // `[]int{ len: 10, cap: 100}` syntax + p.next() + for p.tok.kind != .rcbr { + key := p.check_name() + p.check(.colon) + match key { + 'len' { + has_len = true + len_expr = p.expr(0) + } + 'cap' { + has_cap = true + cap_expr = p.expr(0) + } + 'init' { + has_default = true + default_expr = p.expr(0) + } + else { + p.error('wrong field `$key`, expecting `len`, `cap`, or `init`') + return ast.ArrayInit{} + } + } + if p.tok.kind != .rcbr { + p.check(.comma) + } + } + p.check(.rcbr) + } + pos := first_pos.extend_with_last_line(last_pos, p.prev_tok.line_nr) + return ast.ArrayInit{ + is_fixed: is_fixed + has_val: has_val + mod: p.mod + elem_type: elem_type + typ: array_type + exprs: exprs + ecmnts: ecmnts + pre_cmnts: pre_cmnts + pos: pos + elem_type_pos: elem_type_pos + has_len: has_len + len_expr: len_expr + has_cap: has_cap + has_default: has_default + cap_expr: cap_expr + default_expr: default_expr + } +} + +// parse tokens between braces +fn (mut p Parser) map_init() ast.MapInit { + first_pos := p.prev_tok.position() + mut keys := []ast.Expr{} + mut vals := []ast.Expr{} + mut comments := [][]ast.Comment{} + pre_cmnts := p.eat_comments() + for p.tok.kind !in [.rcbr, .eof] { + key := p.expr(0) + keys << key + p.check(.colon) + val := p.expr(0) + vals << val + if p.tok.kind == .comma { + p.next() + } + comments << p.eat_comments() + } + return ast.MapInit{ + keys: keys + vals: vals + pos: first_pos.extend_with_last_line(p.tok.position(), p.tok.line_nr) + comments: comments + pre_cmnts: pre_cmnts + } +} diff --git a/v_windows/v/vlib/v/parser/expr.v b/v_windows/v/vlib/v/parser/expr.v new file mode 100644 index 0000000..a126c95 --- /dev/null +++ b/v_windows/v/vlib/v/parser/expr.v @@ -0,0 +1,628 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast +import v.vet +import v.token + +pub fn (mut p Parser) expr(precedence int) ast.Expr { + return p.check_expr(precedence) or { + p.error_with_pos('invalid expression: unexpected $p.tok', p.tok.position()) + } +} + +pub fn (mut p Parser) check_expr(precedence int) ?ast.Expr { + $if trace_parser ? { + tok_pos := p.tok.position() + eprintln('parsing file: ${p.file_name:-30} | tok.kind: ${p.tok.kind:-10} | tok.lit: ${p.tok.lit:-10} | tok_pos: ${tok_pos.str():-45} | expr($precedence)') + } + // println('\n\nparser.expr()') + mut node := ast.empty_expr() + is_stmt_ident := p.is_stmt_ident + p.is_stmt_ident = false + if !p.pref.is_fmt { + p.eat_comments() + } + inside_array_lit := p.inside_array_lit + p.inside_array_lit = false + defer { + p.inside_array_lit = inside_array_lit + } + // Prefix + match p.tok.kind { + .key_mut, .key_shared, .key_atomic, .key_static { + ident := p.parse_ident(ast.Language.v) + node = ident + if p.inside_defer { + if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) + && ident.name != 'err' { + p.defer_vars << ident + } + } + p.is_stmt_ident = is_stmt_ident + } + .name, .question { + if p.tok.lit == 'sql' && p.peek_tok.kind == .name { + p.inside_match = true // reuse the same var for perf instead of inside_sql TODO rename + node = p.sql_expr() + p.inside_match = false + } else if p.tok.lit == 'map' && p.peek_tok.kind == .lcbr && !(p.builtin_mod + && p.file_base in ['map.v', 'map_d_gcboehm_opt.v']) { + p.warn_with_pos("deprecated map syntax, use syntax like `{'age': 20}`", + p.tok.position()) + p.next() // `map` + p.next() // `{` + node = p.map_init() + p.check(.rcbr) // `}` + } else { + if p.inside_if && p.is_generic_name() && p.peek_tok.kind != .dot { + // $if T is string {} + p.expecting_type = true + } + node = p.name_expr() + p.is_stmt_ident = is_stmt_ident + } + } + .string { + node = p.string_expr() + } + .comment { + node = p.comment() + return node + } + .dot { + // .enum_val + node = p.enum_val() + } + .at { + node = p.at() + } + .dollar { + match p.peek_tok.kind { + .name { + return p.comp_call() + } + .key_if { + return p.if_expr(true) + } + else { + return p.error_with_pos('unexpected `$`', p.peek_tok.position()) + } + } + } + .chartoken { + node = ast.CharLiteral{ + val: p.tok.lit + pos: p.tok.position() + } + p.next() + } + .amp, .mul, .not, .bit_not, .arrow { + // &x, *x, !x, ~x, <-x + node = p.prefix_expr() + } + .minus { + // -1, -a + if p.peek_tok.kind == .number { + node = p.parse_number_literal() + } else { + node = p.prefix_expr() + } + } + .key_go { + mut go_expr := p.go_expr() + go_expr.is_expr = true + node = go_expr + } + .key_true, .key_false { + node = ast.BoolLiteral{ + val: p.tok.kind == .key_true + pos: p.tok.position() + } + p.next() + } + .key_match { + node = p.match_expr() + } + .key_select { + node = p.select_expr() + } + .number { + node = p.parse_number_literal() + } + .lpar { + mut pos := p.tok.position() + p.check(.lpar) + node = p.expr(0) + p.check(.rpar) + node = ast.ParExpr{ + expr: node + pos: pos.extend(p.prev_tok.position()) + } + } + .key_if { + node = p.if_expr(false) + } + .key_unsafe { + // unsafe { + mut pos := p.tok.position() + p.next() + if p.inside_unsafe { + return p.error_with_pos('already inside `unsafe` block', pos) + } + p.inside_unsafe = true + p.check(.lcbr) + e := p.expr(0) + p.check(.rcbr) + pos.update_last_line(p.prev_tok.line_nr) + node = ast.UnsafeExpr{ + expr: e + pos: pos + } + p.inside_unsafe = false + } + .key_lock, .key_rlock { + node = p.lock_expr() + } + .lsbr { + if p.expecting_type { + // parse json.decode type (`json.decode([]User, s)`) + node = p.name_expr() + } else if p.is_amp && p.peek_tok.kind == .rsbr && p.peek_token(3).kind != .lcbr { + pos := p.tok.position() + typ := p.parse_type() + typname := p.table.get_type_symbol(typ).name + p.check(.lpar) + expr := p.expr(0) + p.check(.rpar) + node = ast.CastExpr{ + typ: typ + typname: typname + expr: expr + pos: pos + } + } else { + node = p.array_init() + } + } + .key_none { + pos := p.tok.position() + p.next() + node = ast.None{ + pos: pos + } + } + .key_sizeof, .key_isreftype { + is_reftype := p.tok.kind == .key_isreftype + p.next() // sizeof + p.check(.lpar) + pos := p.tok.position() + is_known_var := p.mark_var_as_used(p.tok.lit) + // assume mod. prefix leads to a type + if is_known_var || !(p.known_import(p.tok.lit) || p.tok.kind.is_start_of_type()) { + expr := p.expr(0) + if is_reftype { + node = ast.IsRefType{ + is_type: false + expr: expr + pos: pos + } + } else { + node = ast.SizeOf{ + is_type: false + expr: expr + pos: pos + } + } + } else { + if p.tok.kind == .name { + p.register_used_import(p.tok.lit) + } + save_expr_mod := p.expr_mod + p.expr_mod = '' + arg_type := p.parse_type() + p.expr_mod = save_expr_mod + if is_reftype { + node = ast.IsRefType{ + is_type: true + typ: arg_type + pos: pos + } + } else { + node = ast.SizeOf{ + is_type: true + typ: arg_type + pos: pos + } + } + } + p.check(.rpar) + } + .key_typeof { + spos := p.tok.position() + p.next() + p.check(.lpar) + expr := p.expr(0) + p.check(.rpar) + if p.tok.kind != .dot && p.tok.line_nr == p.prev_tok.line_nr { + p.warn_with_pos('use e.g. `typeof(expr).name` or `sum_type_instance.type_name()` instead', + spos) + } + node = ast.TypeOf{ + expr: expr + pos: spos.extend(p.tok.position()) + } + } + .key_dump { + spos := p.tok.position() + p.next() + p.check(.lpar) + expr := p.expr(0) + p.check(.rpar) + node = ast.DumpExpr{ + expr: expr + pos: spos.extend(p.tok.position()) + } + } + .key_offsetof { + pos := p.tok.position() + p.next() // __offsetof + p.check(.lpar) + st := p.parse_type() + p.check(.comma) + if p.tok.kind != .name { + return p.error_with_pos('unexpected `$p.tok.lit`, expecting struct field', + p.tok.position()) + } + field := p.tok.lit + p.next() + p.check(.rpar) + node = ast.OffsetOf{ + struct_type: st + field: field + pos: pos + } + } + .key_likely, .key_unlikely { + is_likely := p.tok.kind == .key_likely + p.next() + p.check(.lpar) + lpos := p.tok.position() + expr := p.expr(0) + p.check(.rpar) + node = ast.Likely{ + expr: expr + pos: lpos + is_likely: is_likely + } + } + .lcbr { + // Map `{"age": 20}` + p.next() + node = p.map_init() + p.check(.rcbr) + } + .key_fn { + if p.expecting_type { + // Anonymous function type + start_pos := p.tok.position() + return ast.TypeNode{ + typ: p.parse_type() + pos: start_pos.extend(p.prev_tok.position()) + } + } else { + // Anonymous function + node = p.anon_fn() + // its a call + // NOTE: this could be moved to just before the pratt loop + // then anything can be a call, eg. `index[2]()` or `struct.field()` + // but this would take a bit of modification + if p.tok.kind == .lpar { + p.next() + pos := p.tok.position() + args := p.call_args() + p.check(.rpar) + node = ast.CallExpr{ + name: 'anon' + left: node + args: args + pos: pos + scope: p.scope + } + } + return node + } + } + else { + if p.tok.kind != .eof && !(p.tok.kind == .rsbr && p.inside_asm) { + // eof should be handled where it happens + return none + // return p.error_with_pos('invalid expression: unexpected $p.tok', p.tok.position()) + } + } + } + if inside_array_lit { + if p.tok.kind in [.minus, .mul, .amp, .arrow] && p.tok.pos + 1 == p.peek_tok.pos + && p.prev_tok.pos + p.prev_tok.len + 1 != p.peek_tok.pos { + return node + } + } + return p.expr_with_left(node, precedence, is_stmt_ident) +} + +pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bool) ast.Expr { + mut node := left + if p.inside_asm && p.prev_tok.position().line_nr < p.tok.position().line_nr { + return node + } + // Infix + for precedence < p.tok.precedence() { + if p.tok.kind == .dot { + node = p.dot_expr(node) + if p.name_error { + return node + } + p.is_stmt_ident = is_stmt_ident + } else if p.tok.kind == .lsbr && (p.inside_fn || p.tok.line_nr == p.prev_tok.line_nr) { + node = p.index_expr(node) + p.is_stmt_ident = is_stmt_ident + if p.tok.kind == .lpar && p.tok.line_nr == p.prev_tok.line_nr && node is ast.IndexExpr { + p.next() + pos := p.tok.position() + args := p.call_args() + p.check(.rpar) + node = ast.CallExpr{ + left: node + args: args + pos: pos + scope: p.scope + } + p.is_stmt_ident = is_stmt_ident + } + } else if p.tok.kind == .key_as { + // sum type as cast `x := SumType as Variant` + if !p.inside_asm { + pos := p.tok.position() + p.next() + typ := p.parse_type() + node = ast.AsCast{ + expr: node + typ: typ + pos: pos + } + } else { + return node + } + } else if p.tok.kind == .left_shift && p.is_stmt_ident { + // arr << elem + tok := p.tok + mut pos := tok.position() + p.next() + right := p.expr(precedence - 1) + pos.update_last_line(p.prev_tok.line_nr) + if mut node is ast.IndexExpr { + node.recursive_mapset_is_setter(true) + } + node = ast.InfixExpr{ + left: node + right: right + op: tok.kind + pos: pos + is_stmt: true + } + } else if p.tok.kind.is_infix() { + if p.tok.kind.is_prefix() && p.tok.line_nr != p.prev_tok.line_nr { + // return early for deref assign `*x = 2` goes to prefix expr + if p.tok.kind == .mul && p.peek_token(2).kind == .assign { + return node + } + // added 10/2020: LATER this will be parsed as PrefixExpr instead + p.warn_with_pos('move infix `$p.tok.kind` operator before new line (if infix intended) or use brackets for a prefix expression', + p.tok.position()) + } + // continue on infix expr + node = p.infix_expr(node) + // return early `if bar is SumType as b {` + if p.tok.kind == .key_as && p.inside_if { + return node + } + } else if p.tok.kind in [.inc, .dec] || (p.tok.kind == .question && p.inside_ct_if_expr) { + // Postfix + // detect `f(x++)`, `a[x++]` + if p.peek_tok.kind in [.rpar, .rsbr] { + if !p.inside_ct_if_expr { + p.warn_with_pos('`$p.tok.kind` operator can only be used as a statement', + p.peek_tok.position()) + } + } + if p.tok.kind in [.inc, .dec] && p.prev_tok.line_nr != p.tok.line_nr { + p.error_with_pos('$p.tok must be on the same line as the previous token', + p.tok.position()) + } + if mut node is ast.IndexExpr { + node.recursive_mapset_is_setter(true) + } + node = ast.PostfixExpr{ + op: p.tok.kind + expr: node + pos: p.tok.position() + } + p.next() + // return node // TODO bring back, only allow ++/-- in exprs in translated code + } else { + return node + } + } + return node +} + +fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { + op := p.tok.kind + if op == .arrow { + p.or_is_handled = true + p.register_auto_import('sync') + } + precedence := p.tok.precedence() + mut pos := p.tok.position() + p.next() + mut right := ast.empty_expr() + prev_expecting_type := p.expecting_type + if op in [.key_is, .not_is] { + p.expecting_type = true + } + right = p.expr(precedence) + p.expecting_type = prev_expecting_type + if p.pref.is_vet && op in [.key_in, .not_in] && right is ast.ArrayInit + && (right as ast.ArrayInit).exprs.len == 1 { + p.vet_error('Use `var == value` instead of `var in [value]`', pos.line_nr, vet.FixKind.vfmt, + .default) + } + mut or_stmts := []ast.Stmt{} + mut or_kind := ast.OrKind.absent + mut or_pos := p.tok.position() + // allow `x := <-ch or {...}` to handle closed channel + if op == .arrow { + if p.tok.kind == .key_orelse { + p.next() + p.open_scope() + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.position() + is_used: true + is_stack_obj: true + }) + or_kind = .block + or_stmts = p.parse_block_no_scope(false) + or_pos = or_pos.extend(p.prev_tok.position()) + p.close_scope() + } + if p.tok.kind == .question { + p.next() + or_kind = .propagate + } + p.or_is_handled = false + } + pos.update_last_line(p.prev_tok.line_nr) + return ast.InfixExpr{ + left: left + right: right + op: op + pos: pos + is_stmt: p.is_stmt_ident + or_block: ast.OrExpr{ + stmts: or_stmts + kind: or_kind + pos: or_pos + } + } +} + +fn (mut p Parser) go_expr() ast.GoExpr { + p.next() + spos := p.tok.position() + expr := p.expr(0) + call_expr := if expr is ast.CallExpr { + expr + } else { + p.error_with_pos('expression in `go` must be a function call', expr.position()) + ast.CallExpr{ + scope: p.scope + } + } + pos := spos.extend(p.prev_tok.position()) + p.register_auto_import('sync.threads') + p.table.gostmts++ + return ast.GoExpr{ + call_expr: call_expr + pos: pos + } +} + +fn (p &Parser) fileis(s string) bool { + return p.file_name.contains(s) +} + +fn (mut p Parser) prefix_expr() ast.Expr { + mut pos := p.tok.position() + op := p.tok.kind + if op == .amp { + p.is_amp = true + } + if op == .arrow { + p.or_is_handled = true + p.register_auto_import('sync') + } + // if op == .mul && !p.inside_unsafe { + // p.warn('unsafe') + // } + p.next() + mut right := p.expr(int(token.Precedence.prefix)) + p.is_amp = false + if op == .amp { + if mut right is ast.CastExpr { + // Handle &Type(x), as well as &&Type(x) etc: + p.recast_as_pointer(mut right, pos) + return right + } + if mut right is ast.SelectorExpr { + // Handle &Type(x).name : + if mut right.expr is ast.CastExpr { + p.recast_as_pointer(mut right.expr, pos) + return right + } + } + if mut right is ast.IndexExpr { + // Handle &u64(x)[idx] : + if mut right.left is ast.CastExpr { + p.recast_as_pointer(mut right.left, pos) + return right + } + } + } + mut or_stmts := []ast.Stmt{} + mut or_kind := ast.OrKind.absent + mut or_pos := p.tok.position() + // allow `x := <-ch or {...}` to handle closed channel + if op == .arrow { + if p.tok.kind == .key_orelse { + p.next() + p.open_scope() + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.position() + is_used: true + is_stack_obj: true + }) + or_kind = .block + or_stmts = p.parse_block_no_scope(false) + or_pos = or_pos.extend(p.prev_tok.position()) + p.close_scope() + } + if p.tok.kind == .question { + p.next() + or_kind = .propagate + } + p.or_is_handled = false + } + pos.update_last_line(p.prev_tok.line_nr) + return ast.PrefixExpr{ + op: op + right: right + pos: pos + or_block: ast.OrExpr{ + stmts: or_stmts + kind: or_kind + pos: or_pos + } + } +} + +fn (mut p Parser) recast_as_pointer(mut cast_expr ast.CastExpr, pos token.Position) { + cast_expr.typ = cast_expr.typ.to_ptr() + cast_expr.typname = p.table.get_type_symbol(cast_expr.typ).name + cast_expr.pos = pos.extend(cast_expr.pos) +} diff --git a/v_windows/v/vlib/v/parser/fn.v b/v_windows/v/vlib/v/parser/fn.v new file mode 100644 index 0000000..e2eefa8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/fn.v @@ -0,0 +1,1029 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast +import v.token +import v.util + +pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr { + first_pos := p.tok.position() + mut fn_name := if language == .c { + 'C.$p.check_name()' + } else if language == .js { + 'JS.$p.check_js_name()' + } else if mod.len > 0 { + '${mod}.$p.check_name()' + } else { + p.check_name() + } + if language != .v { + p.check_for_impure_v(language, first_pos) + } + mut or_kind := ast.OrKind.absent + if fn_name == 'json.decode' { + p.expecting_type = true // Makes name_expr() parse the type `User` in `json.decode(User, txt)` + or_kind = .block + } + + old_expr_mod := p.expr_mod + defer { + p.expr_mod = old_expr_mod + } + p.expr_mod = '' + + mut concrete_types := []ast.Type{} + mut concrete_list_pos := p.tok.position() + if p.tok.kind == .lt { + // `foo(10)` + p.expr_mod = '' + concrete_types = p.parse_generic_type_list() + concrete_list_pos = concrete_list_pos.extend(p.prev_tok.position()) + // In case of `foo()` + // T is unwrapped and registered in the checker. + full_generic_fn_name := if fn_name.contains('.') { fn_name } else { p.prepend_mod(fn_name) } + has_generic := concrete_types.any(it.has_flag(.generic)) + if !has_generic { + // will be added in checker + p.table.register_fn_concrete_types(full_generic_fn_name, concrete_types) + } + } + p.check(.lpar) + args := p.call_args() + last_pos := p.tok.position() + p.check(.rpar) + // ! in mutable methods + if p.tok.kind == .not { + p.next() + } + mut pos := first_pos.extend(last_pos) + mut or_stmts := []ast.Stmt{} // TODO remove unnecessary allocations by just using .absent + mut or_pos := p.tok.position() + if p.tok.kind == .key_orelse { + // `foo() or {}`` + was_inside_or_expr := p.inside_or_expr + p.inside_or_expr = true + p.next() + p.open_scope() + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.position() + is_used: true + }) + or_kind = .block + or_stmts = p.parse_block_no_scope(false) + or_pos = or_pos.extend(p.prev_tok.position()) + p.close_scope() + p.inside_or_expr = was_inside_or_expr + } + if p.tok.kind == .question { + // `foo()?` + p.next() + if p.inside_defer { + p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.position()) + } + or_kind = .propagate + } + if fn_name in p.imported_symbols { + fn_name = p.imported_symbols[fn_name] + } + comments := p.eat_comments(same_line: true) + pos.update_last_line(p.prev_tok.line_nr) + return ast.CallExpr{ + name: fn_name + name_pos: first_pos + args: args + mod: p.mod + pos: pos + language: language + concrete_types: concrete_types + concrete_list_pos: concrete_list_pos + or_block: ast.OrExpr{ + stmts: or_stmts + kind: or_kind + pos: or_pos + } + scope: p.scope + comments: comments + } +} + +pub fn (mut p Parser) call_args() []ast.CallArg { + mut args := []ast.CallArg{} + start_pos := p.tok.position() + for p.tok.kind != .rpar { + if p.tok.kind == .eof { + p.error_with_pos('unexpected eof reached, while parsing call argument', start_pos) + return [] + } + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + if is_mut { + p.next() + } + mut comments := p.eat_comments() + arg_start_pos := p.tok.position() + mut array_decompose := false + if p.tok.kind == .ellipsis { + p.next() + array_decompose = true + } + mut expr := ast.empty_expr() + if p.tok.kind == .name && p.peek_tok.kind == .colon { + // `foo(key:val, key2:val2)` + expr = p.struct_init(true) // short_syntax:true + } else { + expr = p.expr(0) + } + if array_decompose { + expr = ast.ArrayDecompose{ + expr: expr + pos: p.tok.position() + } + } + if mut expr is ast.StructInit { + expr.pre_comments << comments + comments = []ast.Comment{} + } + pos := arg_start_pos.extend(p.prev_tok.position()) + comments << p.eat_comments() + args << ast.CallArg{ + is_mut: is_mut + share: ast.sharetype_from_flags(is_shared, is_atomic) + expr: expr + comments: comments + pos: pos + } + if p.tok.kind != .rpar { + p.check(.comma) + } + } + return args +} + +struct ReceiverParsingInfo { +mut: + name string + pos token.Position + typ ast.Type + type_pos token.Position + is_mut bool + language ast.Language +} + +fn (mut p Parser) fn_decl() ast.FnDecl { + p.top_level_statement_start() + start_pos := p.tok.position() + + mut is_manualfree := p.is_manualfree + mut is_deprecated := false + mut is_direct_arr := false + mut is_keep_alive := false + mut is_exported := false + mut is_unsafe := false + mut is_trusted := false + mut is_noreturn := false + mut is_c2v_variadic := false + for fna in p.attrs { + match fna.name { + 'noreturn' { is_noreturn = true } + 'manualfree' { is_manualfree = true } + 'deprecated' { is_deprecated = true } + 'direct_array_access' { is_direct_arr = true } + 'keep_args_alive' { is_keep_alive = true } + 'export' { is_exported = true } + 'unsafe' { is_unsafe = true } + 'trusted' { is_trusted = true } + 'c2v_variadic' { is_c2v_variadic = true } + else {} + } + } + conditional_ctdefine_idx := p.attrs.find_comptime_define() or { -1 } + is_pub := p.tok.kind == .key_pub + if is_pub { + p.next() + } + p.check(.key_fn) + p.open_scope() + // C. || JS. + mut language := ast.Language.v + if p.tok.kind == .name && p.tok.lit == 'C' { + is_unsafe = !is_trusted + language = .c + } else if p.tok.kind == .name && p.tok.lit == 'JS' { + language = .js + } + if language != .v { + for fna in p.attrs { + if fna.name == 'export' { + p.error_with_pos('interop function cannot be exported', fna.pos) + break + } + } + } + if is_keep_alive && language != .c { + p.error_with_pos('attribute [keep_args_alive] is only supported for C functions', + p.tok.position()) + } + if language != .v { + p.next() + p.check(.dot) + p.check_for_impure_v(language, p.tok.position()) + } + // Receiver? + mut rec := ReceiverParsingInfo{ + typ: ast.void_type + language: language + } + mut is_method := false + mut params := []ast.Param{} + if p.tok.kind == .lpar { + is_method = true + p.fn_receiver(mut params, mut rec) or { return ast.FnDecl{ + scope: 0 + } } + + // rec.language was initialized with language variable. + // So language is changed only if rec.language has been changed. + language = rec.language + } + mut name := '' + name_pos := p.tok.position() + if p.tok.kind == .name { + // TODO high order fn + name = if language == .js { p.check_js_name() } else { p.check_name() } + if language == .v && !p.pref.translated && util.contains_capital(name) && !p.builtin_mod { + p.error_with_pos('function names cannot contain uppercase letters, use snake_case instead', + name_pos) + return ast.FnDecl{ + scope: 0 + } + } + type_sym := p.table.get_type_symbol(rec.typ) + if is_method { + mut is_duplicate := type_sym.has_method(name) + // make sure this is a normal method and not an interface method + if type_sym.kind == .interface_ && is_duplicate { + if type_sym.info is ast.Interface { + // if the method is in info then its an interface method + is_duplicate = !type_sym.info.has_method(name) + } + } + if is_duplicate { + p.error_with_pos('duplicate method `$name`', name_pos) + return ast.FnDecl{ + scope: 0 + } + } + } + if !p.pref.is_fmt { + if !is_method && !p.builtin_mod && name in builtin_functions { + p.error_with_pos('cannot redefine builtin function `$name`', name_pos) + return ast.FnDecl{ + scope: 0 + } + } + if name in p.imported_symbols { + p.error_with_pos('cannot redefine imported function `$name`', name_pos) + return ast.FnDecl{ + scope: 0 + } + } + } + } else if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && p.peek_tok.kind == .lpar { + name = p.tok.kind.str() // op_to_fn_name() + if rec.typ == ast.void_type { + p.error_with_pos('cannot use operator overloading with normal functions', + p.tok.position()) + } + p.next() + } else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar { + p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated from `==` and`<`', + p.tok.position()) + } else { + p.error_with_pos('expecting method name', p.tok.position()) + return ast.FnDecl{ + scope: 0 + } + } + // + mut generic_names := p.parse_generic_names() + // generic names can be infer with receiver's generic names + if is_method && rec.typ.has_flag(.generic) && generic_names.len == 0 { + sym := p.table.get_type_symbol(rec.typ) + if sym.info is ast.Struct { + generic_names = sym.info.generic_types.map(p.table.get_type_symbol(it).name) + } + } + // Args + args2, are_args_type_only, mut is_variadic := p.fn_args() + if is_c2v_variadic { + is_variadic = true + } + params << args2 + if !are_args_type_only { + for param in params { + if p.scope.known_var(param.name) { + p.error_with_pos('redefinition of parameter `$param.name`', param.pos) + return ast.FnDecl{ + scope: 0 + } + } + is_stack_obj := !param.typ.has_flag(.shared_f) && (param.is_mut || param.typ.is_ptr()) + p.scope.register(ast.Var{ + name: param.name + typ: param.typ + is_mut: param.is_mut + is_auto_deref: param.is_mut || param.is_auto_rec + is_stack_obj: is_stack_obj + pos: param.pos + is_used: true + is_arg: true + }) + } + } + // Return type + mut return_type_pos := p.tok.position() + mut return_type := ast.void_type + // don't confuse token on the next line: fn decl, [attribute] + same_line := p.tok.line_nr == p.prev_tok.line_nr + if (p.tok.kind.is_start_of_type() && (same_line || p.tok.kind != .lsbr)) + || (same_line && p.tok.kind == .key_fn) { + return_type = p.parse_type() + return_type_pos = return_type_pos.extend(p.prev_tok.position()) + } + mut type_sym_method_idx := 0 + no_body := p.tok.kind != .lcbr + end_pos := p.prev_tok.position() + short_fn_name := name + is_main := short_fn_name == 'main' && p.mod == 'main' + mut is_test := (short_fn_name.starts_with('test_') || short_fn_name.starts_with('testsuite_')) + && (p.file_base.ends_with('_test.v') + || p.file_base.all_before_last('.v').all_before_last('.').ends_with('_test')) + + file_mode := p.file_backend_mode + // Register + if is_method { + mut type_sym := p.table.get_type_symbol(rec.typ) + // Do not allow to modify / add methods to types from other modules + // arrays/maps dont belong to a module only their element types do + // we could also check if kind is .array, .array_fixed, .map instead of mod.len + mut is_non_local := type_sym.mod.len > 0 && type_sym.mod != p.mod && type_sym.language == .v + // check maps & arrays, must be defined in same module as the elem type + if !is_non_local && !(p.builtin_mod && p.pref.is_fmt) && type_sym.kind in [.array, .map] { + elem_type_sym := p.table.get_type_symbol(p.table.value_type(rec.typ)) + is_non_local = elem_type_sym.mod.len > 0 && elem_type_sym.mod != p.mod + && elem_type_sym.language == .v + } + if is_non_local { + p.error_with_pos('cannot define new methods on non-local type $type_sym.name', + rec.type_pos) + return ast.FnDecl{ + scope: 0 + } + } + type_sym_method_idx = type_sym.register_method(ast.Fn{ + name: name + file_mode: file_mode + params: params + return_type: return_type + is_variadic: is_variadic + generic_names: generic_names + is_pub: is_pub + is_deprecated: is_deprecated + is_unsafe: is_unsafe + is_main: is_main + is_test: is_test + is_keep_alive: is_keep_alive + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // + no_body: no_body + mod: p.mod + }) + } else { + if language == .c { + name = 'C.$name' + } else if language == .js { + name = 'JS.$name' + } else { + name = p.prepend_mod(name) + } + if !p.pref.translated && language == .v { + if existing := p.table.fns[name] { + if existing.name != '' { + if file_mode == .v && existing.file_mode != .v { + // a definition made in a .c.v file, should have a priority over a .v file definition of the same function + name = p.prepend_mod('pure_v_but_overriden_by_${existing.file_mode}_$short_fn_name') + } else { + p.table.redefined_fns << name + } + } + } + } + p.table.register_fn(ast.Fn{ + name: name + file_mode: file_mode + params: params + return_type: return_type + is_variadic: is_variadic + generic_names: generic_names + is_pub: is_pub + is_deprecated: is_deprecated + is_noreturn: is_noreturn + is_unsafe: is_unsafe + is_main: is_main + is_test: is_test + is_keep_alive: is_keep_alive + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // + no_body: no_body + mod: p.mod + language: language + }) + } + // Body + p.cur_fn_name = name + mut stmts := []ast.Stmt{} + body_start_pos := p.peek_tok.position() + if p.tok.kind == .lcbr { + if language != .v { + p.error_with_pos('interop functions cannot have a body', p.tok.position()) + } + p.inside_fn = true + p.inside_unsafe_fn = is_unsafe + stmts = p.parse_block_no_scope(true) + p.inside_unsafe_fn = false + p.inside_fn = false + } + if !no_body && are_args_type_only { + p.error_with_pos('functions with type only args can not have bodies', body_start_pos) + return ast.FnDecl{ + scope: 0 + } + } + // if no_body && !name.starts_with('C.') { + // p.error_with_pos('did you mean C.$name instead of $name', start_pos) + // } + fn_decl := ast.FnDecl{ + name: name + mod: p.mod + stmts: stmts + return_type: return_type + return_type_pos: return_type_pos + params: params + is_noreturn: is_noreturn + is_manualfree: is_manualfree + is_deprecated: is_deprecated + is_exported: is_exported + is_direct_arr: is_direct_arr + is_pub: is_pub + is_variadic: is_variadic + is_main: is_main + is_test: is_test + is_keep_alive: is_keep_alive + is_unsafe: is_unsafe + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // + receiver: ast.StructField{ + name: rec.name + typ: rec.typ + } + generic_names: generic_names + receiver_pos: rec.pos + is_method: is_method + method_type_pos: rec.type_pos + method_idx: type_sym_method_idx + rec_mut: rec.is_mut + language: language + no_body: no_body + pos: start_pos.extend_with_last_line(end_pos, p.prev_tok.line_nr) + body_pos: body_start_pos + file: p.file_name + is_builtin: p.builtin_mod || p.mod in util.builtin_module_parts + scope: p.scope + label_names: p.label_names + } + p.label_names = [] + p.close_scope() + return fn_decl +} + +fn (mut p Parser) fn_receiver(mut params []ast.Param, mut rec ReceiverParsingInfo) ? { + lpar_pos := p.tok.position() + p.next() // ( + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + rec.is_mut = p.tok.kind == .key_mut || is_shared || is_atomic + if rec.is_mut { + p.next() // `mut` + } + rec_start_pos := p.tok.position() + rec.name = p.check_name() + if !rec.is_mut { + rec.is_mut = p.tok.kind == .key_mut + if rec.is_mut { + ptoken2 := p.peek_token(2) // needed to prevent codegen bug, where .position() expects &Token + p.warn_with_pos('use `(mut f Foo)` instead of `(f mut Foo)`', lpar_pos.extend(ptoken2.position())) + } + } + if p.tok.kind == .key_shared { + ptoken2 := p.peek_token(2) // needed to prevent codegen bug, where .position() expects &Token + p.error_with_pos('use `(shared f Foo)` instead of `(f shared Foo)`', lpar_pos.extend(ptoken2.position())) + } + rec.pos = rec_start_pos.extend(p.tok.position()) + is_amp := p.tok.kind == .amp + if p.tok.kind == .name && p.tok.lit == 'JS' { + rec.language = ast.Language.js + } + // if rec.is_mut { + // p.check(.key_mut) + // } + // TODO: talk to alex, should mut be parsed with the type like this? + // or should it be a property of the arg, like this ptr/mut becomes indistinguishable + rec.type_pos = p.tok.position() + rec.typ = p.parse_type_with_mut(rec.is_mut) + if rec.typ.idx() == 0 { + // error is set in parse_type + return error('void receiver type') + } + rec.type_pos = rec.type_pos.extend(p.prev_tok.position()) + if is_amp && rec.is_mut { + p.error_with_pos('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`', + lpar_pos.extend(p.tok.position())) + return error('invalid `mut f &Foo`') + } + if is_shared { + rec.typ = rec.typ.set_flag(.shared_f) + } + if is_atomic { + rec.typ = rec.typ.set_flag(.atomic_f) + } + // optimize method `automatic use fn (a &big_foo) instead of fn (a big_foo)` + type_sym := p.table.get_type_symbol(rec.typ) + mut is_auto_rec := false + if type_sym.kind == .struct_ { + info := type_sym.info as ast.Struct + if !rec.is_mut && !rec.typ.is_ptr() && info.fields.len > 8 { + rec.typ = rec.typ.to_ptr() + is_auto_rec = true + } + } + + params << ast.Param{ + pos: rec_start_pos + name: rec.name + is_mut: rec.is_mut + is_auto_rec: is_auto_rec + typ: rec.typ + type_pos: rec.type_pos + } + p.check(.rpar) + + return +} + +fn (mut p Parser) parse_generic_names() []string { + mut param_names := []string{} + if p.tok.kind != .lt { + return param_names + } + p.check(.lt) + mut first_done := false + mut count := 0 + for p.tok.kind !in [.gt, .eof] { + if first_done { + p.check(.comma) + } + name := p.tok.lit + if name.len > 0 && !name[0].is_capital() { + p.error('generic parameter needs to be uppercase') + } + if name.len > 1 { + p.error('generic parameter name needs to be exactly one char') + } + if !util.is_generic_type_name(p.tok.lit) { + p.error('`$p.tok.lit` is a reserved name and cannot be used for generics') + } + if name in param_names { + p.error('duplicated generic parameter `$name`') + } + if count > 8 { + p.error('cannot have more than 9 generic parameters') + } + p.check(.name) + param_names << name + if p.table.find_type_idx(name) == 0 { + p.table.register_type_symbol(ast.TypeSymbol{ + name: name + cname: util.no_dots(name) + mod: p.mod + kind: .any + is_public: true + }) + } + first_done = true + count++ + } + p.check(.gt) + return param_names +} + +// is_generic_name returns true if the current token is a generic name. +fn (p Parser) is_generic_name() bool { + return p.tok.kind == .name && util.is_generic_type_name(p.tok.lit) +} + +fn (mut p Parser) anon_fn() ast.AnonFn { + pos := p.tok.position() + p.check(.key_fn) + if p.pref.is_script && p.tok.kind == .name { + p.error_with_pos('function declarations in script mode should be before all script statements', + p.tok.position()) + return ast.AnonFn{} + } + old_inside_defer := p.inside_defer + p.inside_defer = false + p.open_scope() + defer { + p.close_scope() + } + p.scope.detached_from_parent = true + inherited_vars := if p.tok.kind == .lsbr { p.closure_vars() } else { []ast.Param{} } + // TODO generics + args, _, is_variadic := p.fn_args() + for arg in args { + if arg.name.len == 0 { + p.error_with_pos('use `_` to name an unused parameter', arg.pos) + } + is_stack_obj := !arg.typ.has_flag(.shared_f) && (arg.is_mut || arg.typ.is_ptr()) + p.scope.register(ast.Var{ + name: arg.name + typ: arg.typ + is_mut: arg.is_mut + pos: arg.pos + is_used: true + is_arg: true + is_stack_obj: is_stack_obj + }) + } + mut same_line := p.tok.line_nr == p.prev_tok.line_nr + mut return_type := ast.void_type + mut return_type_pos := p.tok.position() + // lpar: multiple return types + if same_line { + if (p.tok.kind.is_start_of_type() && (same_line || p.tok.kind != .lsbr)) + || (same_line && p.tok.kind == .key_fn) { + return_type = p.parse_type() + return_type_pos = return_type_pos.extend(p.tok.position()) + } else if p.tok.kind != .lcbr { + p.error_with_pos('expected return type, not $p.tok for anonymous function', + p.tok.position()) + } + } + mut stmts := []ast.Stmt{} + no_body := p.tok.kind != .lcbr + same_line = p.tok.line_nr == p.prev_tok.line_nr + if no_body && same_line { + p.error_with_pos('unexpected $p.tok after anonymous function signature, expecting `{`', + p.tok.position()) + } + mut label_names := []string{} + mut func := ast.Fn{ + params: args + is_variadic: is_variadic + return_type: return_type + } + name := 'anon_fn_${p.unique_prefix}_${p.table.fn_type_signature(func)}_$p.tok.pos' + keep_fn_name := p.cur_fn_name + p.cur_fn_name = name + if p.tok.kind == .lcbr { + tmp := p.label_names + p.label_names = [] + stmts = p.parse_block_no_scope(false) + label_names = p.label_names + p.label_names = tmp + } + p.cur_fn_name = keep_fn_name + func.name = name + idx := p.table.find_or_register_fn_type(p.mod, func, true, false) + typ := ast.new_type(idx) + p.inside_defer = old_inside_defer + // name := p.table.get_type_name(typ) + return ast.AnonFn{ + decl: ast.FnDecl{ + name: name + mod: p.mod + stmts: stmts + return_type: return_type + return_type_pos: return_type_pos + params: args + is_variadic: is_variadic + is_method: false + is_anon: true + no_body: no_body + pos: pos.extend(p.prev_tok.position()) + file: p.file_name + scope: p.scope + label_names: label_names + } + inherited_vars: inherited_vars + typ: typ + } +} + +// part of fn declaration +fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) { + p.check(.lpar) + mut args := []ast.Param{} + mut is_variadic := false + // `int, int, string` (no names, just types) + argname := if p.tok.kind == .name && p.tok.lit.len > 0 && p.tok.lit[0].is_capital() { + p.prepend_mod(p.tok.lit) + } else { + p.tok.lit + } + types_only := p.tok.kind in [.amp, .ellipsis, .key_fn, .lsbr] + || (p.peek_tok.kind == .comma && p.table.known_type(argname)) + || p.peek_tok.kind == .dot || p.peek_tok.kind == .rpar + || (p.tok.kind == .key_mut && (p.peek_token(2).kind == .comma + || p.peek_token(2).kind == .rpar)) + // TODO copy pasta, merge 2 branches + if types_only { + mut arg_no := 1 + for p.tok.kind != .rpar { + if p.tok.kind == .eof { + p.error_with_pos('expecting `)`', p.tok.position()) + return []ast.Param{}, false, false + } + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + if is_mut { + p.next() + } + if p.tok.kind == .ellipsis { + p.next() + is_variadic = true + } + pos := p.tok.position() + mut arg_type := p.parse_type() + if arg_type == 0 { + // error is added in parse_type + return []ast.Param{}, false, false + } + if is_mut { + if !arg_type.has_flag(.generic) { + if is_shared { + p.check_fn_shared_arguments(arg_type, pos) + } else if is_atomic { + p.check_fn_atomic_arguments(arg_type, pos) + } else { + p.check_fn_mutable_arguments(arg_type, pos) + } + } else if is_shared || is_atomic { + p.error_with_pos('generic object cannot be `atomic`or `shared`', pos) + return []ast.Param{}, false, false + } + // if arg_type.is_ptr() { + // p.error('cannot mut') + // } + // arg_type = arg_type.to_ptr() + arg_type = arg_type.set_nr_muls(1) + if is_shared { + arg_type = arg_type.set_flag(.shared_f) + } + if is_atomic { + arg_type = arg_type.set_flag(.atomic_f) + } + } + if is_variadic { + arg_type = ast.new_type(p.table.find_or_register_array(arg_type)).set_flag(.variadic) + } + if p.tok.kind == .eof { + p.error_with_pos('expecting `)`', p.prev_tok.position()) + return []ast.Param{}, false, false + } + if p.tok.kind == .comma { + if is_variadic { + p.error_with_pos('cannot use ...(variadic) with non-final parameter no $arg_no', + pos) + return []ast.Param{}, false, false + } + p.next() + } + args << ast.Param{ + pos: pos + name: '' + is_mut: is_mut + typ: arg_type + } + arg_no++ + if arg_no > 1024 { + p.error_with_pos('too many args', pos) + return []ast.Param{}, false, false + } + } + } else { + for p.tok.kind != .rpar { + if p.tok.kind == .eof { + p.error_with_pos('expecting `)`', p.tok.position()) + return []ast.Param{}, false, false + } + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + mut is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + if is_mut { + p.next() + } + mut arg_pos := [p.tok.position()] + mut arg_names := [p.check_name()] + mut type_pos := [p.tok.position()] + // `a, b, c int` + for p.tok.kind == .comma { + if !p.pref.is_fmt { + p.warn( + '`fn f(x, y Type)` syntax has been deprecated and will soon be removed. ' + + 'Use `fn f(x Type, y Type)` instead. You can run `v fmt -w "$p.scanner.file_path"` to automatically fix your code.') + } + p.next() + arg_pos << p.tok.position() + arg_names << p.check_name() + type_pos << p.tok.position() + } + if p.tok.kind == .key_mut { + // TODO remove old syntax + if !p.pref.is_fmt { + p.warn_with_pos('use `mut f Foo` instead of `f mut Foo`', p.tok.position()) + } + is_mut = true + } + if p.tok.kind == .key_shared { + p.error_with_pos('use `shared f Foo` instead of `f shared Foo`', p.tok.position()) + } + if p.tok.kind == .ellipsis { + p.next() + is_variadic = true + } + pos := p.tok.position() + mut typ := p.parse_type() + if typ == 0 { + // error is added in parse_type + return []ast.Param{}, false, false + } + if is_mut { + if !typ.has_flag(.generic) { + if is_shared { + p.check_fn_shared_arguments(typ, pos) + } else if is_atomic { + p.check_fn_atomic_arguments(typ, pos) + } else { + p.check_fn_mutable_arguments(typ, pos) + } + } else if is_shared || is_atomic { + p.error_with_pos('generic object cannot be `atomic` or `shared`', + pos) + return []ast.Param{}, false, false + } + typ = typ.set_nr_muls(1) + if is_shared { + typ = typ.set_flag(.shared_f) + } + if is_atomic { + typ = typ.set_flag(.atomic_f) + } + } + if is_variadic { + typ = ast.new_type(p.table.find_or_register_array(typ)).derive(typ).set_flag(.variadic) + } + for i, arg_name in arg_names { + args << ast.Param{ + pos: arg_pos[i] + name: arg_name + is_mut: is_mut + typ: typ + type_pos: type_pos[i] + } + // if typ.typ.kind == .variadic && p.tok.kind == .comma { + if is_variadic && p.tok.kind == .comma { + p.error_with_pos('cannot use ...(variadic) with non-final parameter $arg_name', + arg_pos[i]) + return []ast.Param{}, false, false + } + } + if p.tok.kind == .eof { + p.error_with_pos('expecting `)`', p.prev_tok.position()) + return []ast.Param{}, false, false + } + if p.tok.kind != .rpar { + p.check(.comma) + } + } + } + p.check(.rpar) + return args, types_only, is_variadic +} + +fn (mut p Parser) closure_vars() []ast.Param { + p.check(.lsbr) + mut vars := []ast.Param{cap: 5} + for { + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + // FIXME is_shared & is_atomic aren't used further + if is_mut { + p.next() + } + var_pos := p.tok.position() + p.check(.name) + var_name := p.prev_tok.lit + mut var := p.scope.parent.find_var(var_name) or { + p.error_with_pos('undefined ident: `$var_name`', p.prev_tok.position()) + continue + } + var.is_used = true + if is_mut { + var.is_changed = true + } + p.scope.register(ast.Var{ + ...(*var) + pos: var_pos + is_inherited: true + is_used: false + is_changed: false + is_mut: is_mut + }) + vars << ast.Param{ + pos: var_pos + name: var_name + is_mut: is_mut + } + if p.tok.kind != .comma { + break + } + p.next() + } + p.check(.rsbr) + return vars +} + +fn (mut p Parser) check_fn_mutable_arguments(typ ast.Type, pos token.Position) { + sym := p.table.get_type_symbol(typ) + if sym.kind in [.array, .array_fixed, .interface_, .map, .placeholder, .struct_, .generic_inst, + .sum_type, + ] { + return + } + if typ.is_ptr() || typ.is_pointer() { + return + } + if sym.kind == .alias { + atyp := (sym.info as ast.Alias).parent_type + p.check_fn_mutable_arguments(atyp, pos) + return + } + p.error_with_pos( + 'mutable arguments are only allowed for arrays, interfaces, maps, pointers, structs or their aliases\n' + + 'return values instead: `fn foo(mut n $sym.name) {` => `fn foo(n $sym.name) $sym.name {`', + pos) +} + +fn (mut p Parser) check_fn_shared_arguments(typ ast.Type, pos token.Position) { + sym := p.table.get_type_symbol(typ) + if sym.kind !in [.array, .struct_, .map, .placeholder] && !typ.is_ptr() { + p.error_with_pos('shared arguments are only allowed for arrays, maps, and structs\n', + pos) + } +} + +fn (mut p Parser) check_fn_atomic_arguments(typ ast.Type, pos token.Position) { + sym := p.table.get_type_symbol(typ) + if sym.kind !in [.u32, .int, .u64] { + p.error_with_pos('atomic arguments are only allowed for 32/64 bit integers\n' + + 'use shared arguments instead: `fn foo(atomic n $sym.name) {` => `fn foo(shared n $sym.name) {`', + pos) + } +} + +fn have_fn_main(stmts []ast.Stmt) bool { + for stmt in stmts { + if stmt is ast.FnDecl { + if stmt.name == 'main.main' && stmt.mod == 'main' { + return true + } + } + } + return false +} diff --git a/v_windows/v/vlib/v/parser/for.v b/v_windows/v/vlib/v/parser/for.v new file mode 100644 index 0000000..36605c3 --- /dev/null +++ b/v_windows/v/vlib/v/parser/for.v @@ -0,0 +1,201 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast + +fn (mut p Parser) for_stmt() ast.Stmt { + p.check(.key_for) + mut pos := p.tok.position() + p.open_scope() + p.inside_for = true + if p.tok.kind == .key_match { + return p.error('cannot use `match` in `for` loop') + } + // defer { p.close_scope() } + // Infinite loop + if p.tok.kind == .lcbr { + p.inside_for = false + stmts := p.parse_block_no_scope(false) + pos.update_last_line(p.prev_tok.line_nr) + for_stmt := ast.ForStmt{ + stmts: stmts + pos: pos + is_inf: true + scope: p.scope + } + p.close_scope() + return for_stmt + } else if p.peek_tok.kind in [.decl_assign, .assign, .semicolon] + || p.tok.kind == .semicolon || (p.peek_tok.kind == .comma + && p.peek_token(2).kind != .key_mut && p.peek_token(3).kind != .key_in) { + // `for i := 0; i < 10; i++ {` or `for a,b := 0,1; a < 10; a++ {` + if p.tok.kind == .key_mut { + return p.error('`mut` is not needed in `for ;;` loops: use `for i := 0; i < n; i ++ {`') + } + mut init := ast.empty_stmt() + mut cond := p.new_true_expr() + mut inc := ast.empty_stmt() + mut has_init := false + mut has_cond := false + mut has_inc := false + mut is_multi := p.peek_tok.kind == .comma && p.peek_token(2).kind != .key_mut + && p.peek_token(3).kind != .key_in + if p.peek_tok.kind in [.assign, .decl_assign] || is_multi { + init = p.assign_stmt() + has_init = true + } + // Allow `for ;; i++ {` + // Allow `for i = 0; i < ...` + p.check(.semicolon) + if p.tok.kind != .semicolon { + // Disallow `for i := 0; i++; i < ...` + if p.tok.kind == .name && p.peek_tok.kind in [.inc, .dec] { + return p.error('cannot use $p.tok.lit$p.peek_tok.kind as value') + } + cond = p.expr(0) + has_cond = true + } + p.check(.semicolon) + if !is_multi { + is_multi = p.peek_tok.kind == .comma + } + if p.tok.kind != .lcbr { + inc = p.stmt(false) + has_inc = true + } + p.inside_for = false + stmts := p.parse_block_no_scope(false) + pos.update_last_line(p.prev_tok.line_nr) + for_c_stmt := ast.ForCStmt{ + stmts: stmts + has_init: has_init + has_cond: has_cond + has_inc: has_inc + is_multi: is_multi + init: init + cond: cond + inc: inc + pos: pos + scope: p.scope + } + p.close_scope() + return for_c_stmt + } else if p.peek_tok.kind in [.key_in, .comma] + || (p.tok.kind == .key_mut && p.peek_token(2).kind in [.key_in, .comma]) { + // `for i in vals`, `for i in start .. end`, `for mut user in users`, `for i, mut user in users` + mut val_is_mut := p.tok.kind == .key_mut + mut_pos := p.tok.position() + if val_is_mut { + p.next() + } + key_var_pos := p.tok.position() + mut val_var_pos := p.tok.position() + mut key_var_name := '' + mut val_var_name := p.check_name() + if p.tok.kind == .comma { + if val_is_mut { + p.error_with_pos('index of array or key of map cannot be mutated', mut_pos) + } + p.next() + if p.tok.kind == .key_mut { + // `for i, mut user in users {` + p.next() + val_is_mut = true + } + key_var_name = val_var_name + val_var_pos = p.tok.position() + val_var_name = p.check_name() + if key_var_name == val_var_name && key_var_name != '_' { + return p.error_with_pos('key and value in a for loop cannot be the same', + val_var_pos) + } + if p.scope.known_var(key_var_name) { + return p.error('redefinition of key iteration variable `$key_var_name`') + } + if p.scope.known_var(val_var_name) { + return p.error('redefinition of value iteration variable `$val_var_name`') + } + p.scope.register(ast.Var{ + name: key_var_name + typ: ast.int_type + pos: key_var_pos + is_tmp: true + is_stack_obj: true + }) + } else if p.scope.known_var(val_var_name) { + return p.error('redefinition of value iteration variable `$val_var_name`') + } + p.check(.key_in) + if p.tok.kind == .name && p.tok.lit in [key_var_name, val_var_name] { + return p.error('in a `for x in array` loop, the key or value iteration variable `$p.tok.lit` can not be the same as the array variable') + } + // arr_expr + cond := p.expr(0) + // 0 .. 10 + // start := p.tok.lit.int() + // TODO use RangeExpr + mut high_expr := ast.empty_expr() + mut is_range := false + if p.tok.kind == .dotdot { + is_range = true + p.next() + high_expr = p.expr(0) + p.scope.register(ast.Var{ + name: val_var_name + typ: ast.int_type + pos: val_var_pos + is_tmp: true + is_stack_obj: true + }) + if key_var_name.len > 0 { + return p.error_with_pos('cannot declare index variable with range `for`', + key_var_pos) + } + } else { + // this type will be set in checker + p.scope.register(ast.Var{ + name: val_var_name + pos: val_var_pos + is_mut: val_is_mut + is_auto_deref: val_is_mut + is_tmp: true + is_stack_obj: true + }) + } + p.inside_for = false + stmts := p.parse_block_no_scope(false) + pos.update_last_line(p.prev_tok.line_nr) + // println('nr stmts=$stmts.len') + for_in_stmt := ast.ForInStmt{ + stmts: stmts + cond: cond + key_var: key_var_name + val_var: val_var_name + high: high_expr + is_range: is_range + pos: pos + val_is_mut: val_is_mut + scope: p.scope + } + p.close_scope() + return for_in_stmt + } + // `for cond {` + cond := p.expr(0) + p.inside_for = false + // extra scope for the body + p.open_scope() + stmts := p.parse_block_no_scope(false) + pos.update_last_line(p.prev_tok.line_nr) + for_stmt := ast.ForStmt{ + cond: cond + stmts: stmts + pos: pos + scope: p.scope + } + p.close_scope() + p.close_scope() + return for_stmt +} diff --git a/v_windows/v/vlib/v/parser/if_match.v b/v_windows/v/vlib/v/parser/if_match.v new file mode 100644 index 0000000..99df181 --- /dev/null +++ b/v_windows/v/vlib/v/parser/if_match.v @@ -0,0 +1,443 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast +import v.token + +fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { + was_inside_if_expr := p.inside_if_expr + was_inside_ct_if_expr := p.inside_ct_if_expr + defer { + p.inside_if_expr = was_inside_if_expr + p.inside_ct_if_expr = was_inside_ct_if_expr + } + p.inside_if_expr = true + is_expr := p.prev_tok.kind == .key_return + mut pos := p.tok.position() + if is_comptime { + p.inside_ct_if_expr = true + p.next() // `$` + pos = p.prev_tok.position().extend(p.tok.position()) + } + mut branches := []ast.IfBranch{} + mut has_else := false + mut comments := []ast.Comment{} + mut prev_guard := false + for p.tok.kind in [.key_if, .key_else] { + p.inside_if = true + start_pos := if is_comptime { + p.prev_tok.position().extend(p.tok.position()) + } else { + p.tok.position() + } + if p.tok.kind == .key_else { + comments << p.eat_comments() + p.check(.key_else) + comments << p.eat_comments() + if p.tok.kind == .key_match { + p.error('cannot use `match` with `if` statements') + return ast.IfExpr{} + } + if p.tok.kind == .lcbr { + // else { + has_else = true + p.inside_if = false + end_pos := p.prev_tok.position() + body_pos := p.tok.position() + p.open_scope() + // only declare `err` if previous branch was an `if` guard + if prev_guard { + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.position() + is_used: true + is_stack_obj: true + }) + } + branches << ast.IfBranch{ + stmts: p.parse_block_no_scope(false) + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.tok.position()) + comments: comments + scope: p.scope + } + p.close_scope() + comments = [] + break + } + if is_comptime { + p.check(.dollar) + } + } + // `if` or `else if` + p.check(.key_if) + if p.tok.kind == .key_match { + p.error('cannot use `match` with `if` statements') + return ast.IfExpr{} + } + comments << p.eat_comments() + mut cond := ast.empty_expr() + mut is_guard := false + // `if x := opt() {` + if !is_comptime && p.peek_tok.kind == .decl_assign { + p.open_scope() + is_guard = true + var_pos := p.tok.position() + var_name := p.check_name() + if p.scope.known_var(var_name) { + p.error_with_pos('redefinition of `$var_name`', var_pos) + } + comments << p.eat_comments() + p.check(.decl_assign) + comments << p.eat_comments() + expr := p.expr(0) + cond = ast.IfGuardExpr{ + var_name: var_name + expr: expr + } + p.scope.register(ast.Var{ + name: var_name + expr: cond + pos: var_pos + }) + prev_guard = true + } else { + prev_guard = false + p.comp_if_cond = true + cond = p.expr(0) + p.comp_if_cond = false + } + comments << p.eat_comments() + end_pos := p.prev_tok.position() + body_pos := p.tok.position() + p.inside_if = false + p.open_scope() + stmts := p.parse_block_no_scope(false) + branches << ast.IfBranch{ + cond: cond + stmts: stmts + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.prev_tok.position()) + comments: comments + scope: p.scope + } + p.close_scope() + if is_guard { + p.close_scope() + } + comments = p.eat_comments() + if is_comptime { + if p.tok.kind == .key_else { + p.error('use `\$else` instead of `else` in compile-time `if` branches') + return ast.IfExpr{} + } + if p.tok.kind != .rcbr && p.peek_tok.kind == .key_else { + p.check(.dollar) + } + } + if p.tok.kind != .key_else { + break + } + } + pos.update_last_line(p.prev_tok.line_nr) + if comments.len > 0 { + pos.last_line = comments.last().pos.last_line + } + return ast.IfExpr{ + is_comptime: is_comptime + branches: branches + post_comments: comments + pos: pos + has_else: has_else + is_expr: is_expr + } +} + +fn (mut p Parser) match_expr() ast.MatchExpr { + match_first_pos := p.tok.position() + p.inside_match = true + p.check(.key_match) + mut is_sum_type := false + cond := p.expr(0) + p.inside_match = false + no_lcbr := p.tok.kind != .lcbr + if !no_lcbr { + p.check(.lcbr) + } + comments := p.eat_comments() // comments before the first branch + mut branches := []ast.MatchBranch{} + for p.tok.kind != .eof { + branch_first_pos := p.tok.position() + mut exprs := []ast.Expr{} + mut ecmnts := [][]ast.Comment{} + p.open_scope() + // final else + mut is_else := false + if p.tok.kind == .key_else { + is_else = true + p.next() + } else if (p.tok.kind == .name && !(p.tok.lit == 'C' && p.peek_tok.kind == .dot) + && (((p.tok.lit in ast.builtin_type_names || p.tok.lit[0].is_capital()) + && p.peek_tok.kind != .lpar) || (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0 + && p.peek_token(2).lit[0].is_capital()))) || p.tok.kind == .lsbr { + mut types := []ast.Type{} + for { + // Sum type match + parsed_type := p.parse_type() + ecmnts << p.eat_comments() + types << parsed_type + exprs << ast.TypeNode{ + typ: parsed_type + pos: p.prev_tok.position() + } + if p.tok.kind != .comma { + break + } + p.check(.comma) + } + is_sum_type = true + } else { + // Expression match + for { + p.inside_match_case = true + expr := p.expr(0) + ecmnts << p.eat_comments() + p.inside_match_case = false + if p.tok.kind == .dotdot { + p.error_with_pos('match only supports inclusive (`...`) ranges, not exclusive (`..`)', + p.tok.position()) + return ast.MatchExpr{} + } else if p.tok.kind == .ellipsis { + p.next() + expr2 := p.expr(0) + exprs << ast.RangeExpr{ + low: expr + high: expr2 + has_low: true + has_high: true + pos: p.tok.position() + } + } else { + exprs << expr + } + if p.tok.kind != .comma { + break + } + p.check(.comma) + } + } + branch_last_pos := p.prev_tok.position() + // p.warn('match block') + p.inside_match_body = true + stmts := p.parse_block_no_scope(false) + branch_scope := p.scope + p.close_scope() + p.inside_match_body = false + pos := branch_first_pos.extend_with_last_line(branch_last_pos, p.prev_tok.line_nr) + branch_pos := branch_first_pos.extend_with_last_line(p.tok.position(), p.tok.line_nr) + post_comments := p.eat_comments() + branches << ast.MatchBranch{ + exprs: exprs + ecmnts: ecmnts + stmts: stmts + pos: pos + branch_pos: branch_pos + is_else: is_else + post_comments: post_comments + scope: branch_scope + } + if is_else && branches.len == 1 { + p.warn_with_pos('`match` must have at least one non `else` branch', pos) + } + if p.tok.kind == .rcbr || (is_else && no_lcbr) { + break + } + } + match_last_pos := p.tok.position() + mut pos := token.Position{ + line_nr: match_first_pos.line_nr + pos: match_first_pos.pos + len: match_last_pos.pos - match_first_pos.pos + match_last_pos.len + col: match_first_pos.col + } + if p.tok.kind == .rcbr { + p.check(.rcbr) + } + // return ast.StructInit{} + pos.update_last_line(p.prev_tok.line_nr) + return ast.MatchExpr{ + branches: branches + cond: cond + is_sum_type: is_sum_type + pos: pos + comments: comments + } +} + +fn (mut p Parser) select_expr() ast.SelectExpr { + match_first_pos := p.tok.position() + p.check(.key_select) + no_lcbr := p.tok.kind != .lcbr + if !no_lcbr { + p.check(.lcbr) + } + mut branches := []ast.SelectBranch{} + mut has_else := false + mut has_timeout := false + for { + branch_first_pos := p.tok.position() + comment := p.check_comment() // comment before {} + p.open_scope() + // final else + mut is_else := false + mut is_timeout := false + mut stmt := ast.empty_stmt() + if p.tok.kind == .key_else { + if has_timeout { + p.error_with_pos('timeout `> t` and `else` are mutually exclusive `select` keys', + p.tok.position()) + return ast.SelectExpr{} + } + if has_else { + p.error_with_pos('at most one `else` branch allowed in `select` block', + p.tok.position()) + return ast.SelectExpr{} + } + is_else = true + has_else = true + p.next() + } else { + mut is_gt := false + if p.tok.kind == .gt { + is_gt = true + p.note_with_pos('`>` is deprecated and will soon be forbidden - just state the timeout in nanoseconds', + p.tok.position()) + p.next() + } + p.inside_match = true + p.inside_select = true + exprs, comments := p.expr_list() + if exprs.len != 1 { + p.error('only one expression allowed as `select` key') + return ast.SelectExpr{} + } + if p.tok.kind in [.assign, .decl_assign] { + stmt = p.partial_assign_stmt(exprs, comments) + } else { + stmt = ast.ExprStmt{ + expr: exprs[0] + pos: exprs[0].position() + comments: [comment] + is_expr: true + } + } + p.inside_match = false + p.inside_select = false + match mut stmt { + ast.ExprStmt { + mut check_timeout := false + if !stmt.is_expr { + p.error_with_pos('select: invalid expression', stmt.pos) + return ast.SelectExpr{} + } else { + match mut stmt.expr { + ast.InfixExpr { + if stmt.expr.op != .arrow { + check_timeout = true + } else if is_gt { + p.error_with_pos('send expression cannot be used as timeout', + stmt.pos) + } + } + else { + check_timeout = true + } + } + } + if check_timeout { + if has_else { + p.error_with_pos('`else` and timeout value are mutually exclusive `select` keys', + stmt.pos) + return ast.SelectExpr{} + } + if has_timeout { + p.error_with_pos('at most one timeout branch allowed in `select` block', + stmt.pos) + return ast.SelectExpr{} + } + is_timeout = true + has_timeout = true + } + } + ast.AssignStmt { + expr := stmt.right[0] + match expr { + ast.PrefixExpr { + if expr.op != .arrow { + p.error_with_pos('select key: `<-` operator expected', + expr.pos) + return ast.SelectExpr{} + } + } + else { + p.error_with_pos('select key: receive expression expected', + stmt.right[0].position()) + return ast.SelectExpr{} + } + } + } + else { + p.error_with_pos('select: transmission statement, timeout (in ns) or `else` expected', + stmt.pos) + return ast.SelectExpr{} + } + } + } + branch_last_pos := p.tok.position() + p.inside_match_body = true + stmts := p.parse_block_no_scope(false) + p.close_scope() + p.inside_match_body = false + mut pos := token.Position{ + line_nr: branch_first_pos.line_nr + pos: branch_first_pos.pos + len: branch_last_pos.pos - branch_first_pos.pos + branch_last_pos.len + col: branch_first_pos.col + } + post_comments := p.eat_comments() + pos.update_last_line(p.prev_tok.line_nr) + if post_comments.len > 0 { + pos.last_line = post_comments.last().pos.last_line + } + branches << ast.SelectBranch{ + stmt: stmt + stmts: stmts + pos: pos + comment: comment + is_else: is_else + is_timeout: is_timeout + post_comments: post_comments + } + if p.tok.kind == .rcbr || ((is_else || is_timeout) && no_lcbr) { + break + } + } + match_last_pos := p.tok.position() + pos := token.Position{ + line_nr: match_first_pos.line_nr + pos: match_first_pos.pos + len: match_last_pos.pos - match_first_pos.pos + match_last_pos.len + col: match_first_pos.col + } + if p.tok.kind == .rcbr { + p.check(.rcbr) + } + return ast.SelectExpr{ + branches: branches + pos: pos.extend_with_last_line(p.prev_tok.position(), p.prev_tok.line_nr) + has_exception: has_else || has_timeout + } +} diff --git a/v_windows/v/vlib/v/parser/lock.v b/v_windows/v/vlib/v/parser/lock.v new file mode 100644 index 0000000..9ccb639 --- /dev/null +++ b/v_windows/v/vlib/v/parser/lock.v @@ -0,0 +1,113 @@ +module parser + +import v.token +import v.ast + +// parse `x` or `x.y.z` - no index, no struct literals (`{` starts lock block) +fn (mut p Parser) lockable() ast.Expr { + mut names := []string{} + mut positions := []token.Position{} + mut pos := p.tok.position() + for { + if p.tok.kind != .name { + p.error_with_pos('unexpected `$p.tok.lit` (field/variable name expected)', + p.tok.position()) + } + names << p.tok.lit + positions << pos + p.next() + if p.tok.kind != .dot { + break + } + p.next() + pos.extend(p.tok.position()) + } + mut expr := ast.Expr(ast.Ident{ + language: ast.Language.v + pos: positions[0] + mod: p.mod + name: names[0] + is_mut: true + info: ast.IdentVar{} + scope: p.scope + }) + for i := 1; i < names.len; i++ { + expr = ast.SelectorExpr{ + expr: expr + field_name: names[i] + next_token: if i < names.len - 1 { token.Kind.dot } else { p.tok.kind } + is_mut: true + pos: positions[i] + scope: p.scope + } + } + return expr +} + +// like `expr_list()` but only lockables are allowed, `{` starts lock block (not struct literal) +fn (mut p Parser) lockable_list() ([]ast.Expr, []ast.Comment) { + mut exprs := []ast.Expr{} + mut comments := []ast.Comment{} + for { + expr := p.lockable() + if expr is ast.Comment { + comments << expr + } else { + exprs << expr + if p.tok.kind != .comma { + break + } + p.next() + } + } + return exprs, comments +} + +fn (mut p Parser) lock_expr() ast.LockExpr { + // TODO Handle aliasing sync + p.register_auto_import('sync') + p.open_scope() + mut pos := p.tok.position() + mut lockeds := []ast.Expr{} + mut comments := []ast.Comment{} + mut is_rlocked := []bool{} + for { + is_rlock := p.tok.kind == .key_rlock + if !is_rlock && p.tok.kind != .key_lock { + p.error_with_pos('unexpected `$p.tok`, expected `lock` or `rlock`', p.tok.position()) + } + p.next() + if p.tok.kind == .lcbr { + break + } + if p.tok.kind == .name { + exprs, comms := p.lockable_list() + for e in exprs { + if !e.is_lockable() { + p.error_with_pos('`$e` cannot be locked - only `x` or `x.y` are supported', + e.position()) + } + lockeds << e + is_rlocked << is_rlock + } + comments << comms + } + if p.tok.kind == .lcbr { + break + } + if p.tok.kind == .semicolon { + p.next() + } + } + stmts := p.parse_block_no_scope(false) + scope := p.scope + p.close_scope() + pos.update_last_line(p.prev_tok.line_nr) + return ast.LockExpr{ + lockeds: lockeds + stmts: stmts + is_rlock: is_rlocked + pos: pos + scope: scope + } +} diff --git a/v_windows/v/vlib/v/parser/module.v b/v_windows/v/vlib/v/parser/module.v new file mode 100644 index 0000000..50173b8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/module.v @@ -0,0 +1,66 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast + +// return true if file being parsed imports `mod` +pub fn (p &Parser) known_import(mod string) bool { + return mod in p.imports +} + +fn (p &Parser) prepend_mod(name string) string { + // println('prepend_mod() name=$name p.mod=$p.mod expr_mod=$p.expr_mod') + if p.expr_mod != '' { + return p.expr_mod + '.' + name + } + if p.builtin_mod { + return name + } + return '${p.mod}.$name' +} + +fn (p &Parser) is_used_import(alias string) bool { + return alias in p.used_imports +} + +fn (mut p Parser) register_used_import(alias string) { + if !p.is_used_import(alias) { + p.used_imports << alias + } +} + +fn (mut p Parser) register_auto_import(alias string) { + if alias !in p.imports { + p.imports[alias] = alias + p.table.imports << alias + node := ast.Import{ + pos: p.tok.position() + mod: alias + alias: alias + } + p.ast_imports << node + } + if alias !in p.auto_imports { + p.auto_imports << alias + } + p.register_used_import(alias) +} + +fn (mut p Parser) check_unused_imports() { + if p.pref.is_repl || p.pref.is_fmt { + // The REPL should be much more liberal, and should not warn about + // unused imports, because they probably will be in the next few lines... + // vfmt doesn't care about unused imports either + return + } + for import_m in p.ast_imports { + alias := import_m.alias + mod := import_m.mod + if !p.is_used_import(alias) { + mod_alias := if alias == mod { alias } else { '$alias ($mod)' } + p.warn_with_pos("module '$mod_alias' is imported but never used", import_m.mod_pos) + } + } +} diff --git a/v_windows/v/vlib/v/parser/parse_type.v b/v_windows/v/vlib/v/parser/parse_type.v new file mode 100644 index 0000000..6278e03 --- /dev/null +++ b/v_windows/v/vlib/v/parser/parse_type.v @@ -0,0 +1,576 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast +import v.util +import v.token + +pub fn (mut p Parser) parse_array_type() ast.Type { + p.check(.lsbr) + // fixed array + if p.tok.kind in [.number, .name] { + mut fixed_size := 0 + size_expr := p.expr(0) + if p.pref.is_fmt { + fixed_size = 987654321 + } else { + match size_expr { + ast.IntegerLiteral { + fixed_size = size_expr.val.int() + } + ast.Ident { + mut show_non_const_error := false + if const_field := p.table.global_scope.find_const('${p.mod}.$size_expr.name') { + if const_field.expr is ast.IntegerLiteral { + fixed_size = const_field.expr.val.int() + } else { + show_non_const_error = true + } + } else { + if p.pref.is_fmt { + // for vfmt purposes, pretend the constant does exist + // it may have been defined in another .v file: + fixed_size = 1 + } else { + show_non_const_error = true + } + } + if show_non_const_error { + p.error_with_pos('non-constant array bound `$size_expr.name`', + size_expr.pos) + } + } + else { + p.error('expecting `int` for fixed size') + } + } + } + p.check(.rsbr) + elem_type := p.parse_type() + if elem_type.idx() == 0 { + // error is handled by parse_type + return 0 + } + if fixed_size <= 0 { + p.error_with_pos('fixed size cannot be zero or negative', size_expr.position()) + } + // sym := p.table.get_type_symbol(elem_type) + idx := p.table.find_or_register_array_fixed(elem_type, fixed_size, size_expr) + if elem_type.has_flag(.generic) { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) + } + // array + p.check(.rsbr) + elem_type := p.parse_type() + if elem_type.idx() == 0 { + // error is set in parse_type + return 0 + } + mut nr_dims := 1 + // detect attr + not_attr := p.peek_tok.kind != .name && p.peek_token(2).kind !in [.semicolon, .rsbr] + for p.tok.kind == .lsbr && not_attr { + p.next() + p.check(.rsbr) + nr_dims++ + } + idx := p.table.find_or_register_array_with_dims(elem_type, nr_dims) + if elem_type.has_flag(.generic) { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_map_type() ast.Type { + p.next() + if p.tok.kind != .lsbr { + return ast.map_type + } + p.check(.lsbr) + key_type := p.parse_type() + key_sym := p.table.get_type_symbol(key_type) + is_alias := key_sym.kind == .alias + if key_type.idx() == 0 { + // error is reported in parse_type + return 0 + } + key_type_supported := key_type in [ast.string_type_idx, ast.voidptr_type_idx] + || key_sym.kind in [.enum_, .placeholder, .any] + || ((key_type.is_int() || key_type.is_float() || is_alias) && !key_type.is_ptr()) + if !key_type_supported { + if is_alias { + p.error('cannot use the alias type as the parent type is unsupported') + return 0 + } + s := p.table.type_to_str(key_type) + p.error_with_pos('maps only support string, integer, float, rune, enum or voidptr keys for now (not `$s`)', + p.tok.position()) + return 0 + } + p.check(.rsbr) + value_type := p.parse_type() + if value_type.idx() == 0 { + // error is reported in parse_type + return 0 + } + if value_type.idx() == ast.void_type_idx { + p.error_with_pos('map value type cannot be void', p.tok.position()) + return 0 + } + idx := p.table.find_or_register_map(key_type, value_type) + if key_type.has_flag(.generic) || value_type.has_flag(.generic) { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_chan_type() ast.Type { + if p.peek_tok.kind != .name && p.peek_tok.kind != .key_mut && p.peek_tok.kind != .amp + && p.peek_tok.kind != .lsbr { + p.next() + return ast.chan_type + } + p.register_auto_import('sync') + p.next() + is_mut := p.tok.kind == .key_mut + elem_type := p.parse_type() + idx := p.table.find_or_register_chan(elem_type, is_mut) + if elem_type.has_flag(.generic) { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_thread_type() ast.Type { + is_opt := p.peek_tok.kind == .question + if is_opt { + p.next() + } + if p.peek_tok.kind != .name && p.peek_tok.kind != .key_mut && p.peek_tok.kind != .amp + && p.peek_tok.kind != .lsbr { + p.next() + if is_opt { + mut ret_type := ast.void_type + ret_type = ret_type.set_flag(.optional) + idx := p.table.find_or_register_thread(ret_type) + return ast.new_type(idx) + } else { + return ast.thread_type + } + } + if !is_opt { + p.next() + } + ret_type := p.parse_type() + idx := p.table.find_or_register_thread(ret_type) + if ret_type.has_flag(.generic) { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_multi_return_type() ast.Type { + p.check(.lpar) + mut mr_types := []ast.Type{} + mut has_generic := false + for p.tok.kind != .eof { + mr_type := p.parse_type() + if mr_type.idx() == 0 { + break + } + if mr_type.has_flag(.generic) { + has_generic = true + } + mr_types << mr_type + if p.tok.kind == .comma { + p.next() + } else { + break + } + } + p.check(.rpar) + if mr_types.len == 1 { + // no multi return type needed + return mr_types[0] + } + idx := p.table.find_or_register_multi_return(mr_types) + if has_generic { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +// given anon name based off signature when `name` is blank +pub fn (mut p Parser) parse_fn_type(name string) ast.Type { + // p.warn('parse fn') + p.check(.key_fn) + mut has_generic := false + line_nr := p.tok.line_nr + args, _, is_variadic := p.fn_args() + for arg in args { + if arg.typ.has_flag(.generic) { + has_generic = true + break + } + } + mut return_type := ast.void_type + mut return_type_pos := token.Position{} + if p.tok.line_nr == line_nr && p.tok.kind.is_start_of_type() && !p.is_attributes() { + return_type_pos = p.tok.position() + return_type = p.parse_type() + if return_type.has_flag(.generic) { + has_generic = true + } + return_type_pos = return_type_pos.extend(p.prev_tok.position()) + } + func := ast.Fn{ + name: name + params: args + is_variadic: is_variadic + return_type: return_type + return_type_pos: return_type_pos + } + // MapFooFn typedefs are manually added in cheaders.v + // because typedefs get generated after the map struct is generated + has_decl := p.builtin_mod && name.starts_with('Map') && name.ends_with('Fn') + idx := p.table.find_or_register_fn_type(p.mod, func, false, has_decl) + if has_generic { + return ast.new_type(idx).set_flag(.generic) + } + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_type_with_mut(is_mut bool) ast.Type { + typ := p.parse_type() + if is_mut { + return typ.set_nr_muls(1) + } + return typ +} + +// Parses any language indicators on a type. +pub fn (mut p Parser) parse_language() ast.Language { + language := if p.tok.lit == 'C' { + ast.Language.c + } else if p.tok.lit == 'JS' { + ast.Language.js + } else { + ast.Language.v + } + if language != .v { + p.next() + p.check(.dot) + } + return language +} + +pub fn (mut p Parser) parse_type() ast.Type { + // optional + mut is_optional := false + if p.tok.kind == .question { + line_nr := p.tok.line_nr + p.next() + is_optional = true + if p.tok.line_nr > line_nr { + mut typ := ast.void_type + if is_optional { + typ = typ.set_flag(.optional) + } + return typ + } + } + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + if is_shared { + p.register_auto_import('sync') + } + mut nr_muls := 0 + if p.tok.kind == .key_mut || is_shared || is_atomic { + nr_muls++ + p.next() + } + if p.tok.kind == .mul { + p.error('use `&Type` instead of `*Type` when declaring references') + return 0 + } + mut nr_amps := 0 + // &Type + for p.tok.kind == .amp { + nr_amps++ + nr_muls++ + p.next() + } + language := p.parse_language() + mut typ := ast.void_type + is_array := p.tok.kind == .lsbr + if p.tok.kind != .lcbr { + pos := p.tok.position() + typ = p.parse_any_type(language, nr_muls > 0, true) + if typ.idx() == 0 { + // error is set in parse_type + return 0 + } + if typ == ast.void_type { + p.error_with_pos('use `?` instead of `?void`', pos) + return 0 + } + } + if is_optional { + typ = typ.set_flag(.optional) + } + if is_shared { + typ = typ.set_flag(.shared_f) + } + if is_atomic { + typ = typ.set_flag(.atomic_f) + } + if nr_muls > 0 { + typ = typ.set_nr_muls(nr_muls) + if is_array && nr_amps > 0 { + p.error('V arrays are already references behind the scenes, +there is no need to use a reference to an array (e.g. use `[]string` instead of `&[]string`). +If you need to modify an array in a function, use a mutable argument instead: `fn foo(mut s []string) {}`.') + return 0 + } + } + return typ +} + +pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_dot bool) ast.Type { + mut name := p.tok.lit + if language == .c { + name = 'C.$name' + } else if language == .js { + name = 'JS.$name' + } else if p.peek_tok.kind == .dot && check_dot { + // `module.Type` + // /if !(p.tok.lit in p.table.imports) { + mut mod := name + mut mod_pos := p.tok.position() + p.next() + p.check(.dot) + mut mod_last_part := mod + for p.peek_tok.kind == .dot { + mod_pos = mod_pos.extend(p.tok.position()) + mod_last_part = p.tok.lit + mod += '.$mod_last_part' + p.next() + p.check(.dot) + } + if !p.known_import(mod) && !p.pref.is_fmt { + mut msg := 'unknown module `$mod`' + if mod.len > mod_last_part.len && p.known_import(mod_last_part) { + msg += '; did you mean `$mod_last_part`?' + } + p.error_with_pos(msg, mod_pos) + return 0 + } + if mod in p.imports { + p.register_used_import(mod) + mod = p.imports[mod] + } + // prefix with full module + name = '${mod}.$p.tok.lit' + if p.tok.lit.len > 0 && !p.tok.lit[0].is_capital() { + p.error('imported types must start with a capital letter') + return 0 + } + } else if p.expr_mod != '' && !p.in_generic_params { // p.expr_mod is from the struct and not from the generic parameter + name = p.expr_mod + '.' + name + } else if name in p.imported_symbols { + name = p.imported_symbols[name] + } else if !p.builtin_mod && name.len > 1 && name !in p.table.type_idxs { + // `Foo` in module `mod` means `mod.Foo` + name = p.mod + '.' + name + } + // p.warn('get type $name') + match p.tok.kind { + .key_fn { + // func + return p.parse_fn_type('') + } + .lsbr { + // array + return p.parse_array_type() + } + .lpar { + // multiple return + if is_ptr { + p.error('parse_type: unexpected `&` before multiple returns') + return 0 + } + return p.parse_multi_return_type() + } + else { + // no p.next() + if name == 'map' { + return p.parse_map_type() + } + if name == 'chan' { + return p.parse_chan_type() + } + if name == 'thread' { + return p.parse_thread_type() + } + mut ret := ast.Type(0) + if name == '' { + // This means the developer is using some wrong syntax like `x: int` instead of `x int` + p.error('expecting type declaration') + } else { + match name { + 'voidptr' { + ret = ast.voidptr_type + } + 'byteptr' { + ret = ast.byteptr_type + } + 'charptr' { + ret = ast.charptr_type + } + 'i8' { + ret = ast.i8_type + } + 'i16' { + ret = ast.i16_type + } + 'int' { + ret = ast.int_type + } + 'i64' { + ret = ast.i64_type + } + 'byte' { + ret = ast.byte_type + } + 'u16' { + ret = ast.u16_type + } + 'u32' { + ret = ast.u32_type + } + 'u64' { + ret = ast.u64_type + } + 'f32' { + ret = ast.f32_type + } + 'f64' { + ret = ast.f64_type + } + 'string' { + ret = ast.string_type + } + 'char' { + ret = ast.char_type + } + 'bool' { + ret = ast.bool_type + } + 'float_literal' { + ret = ast.float_literal_type + } + 'int_literal' { + ret = ast.int_literal_type + } + else { + p.next() + if name.len == 1 && name[0].is_capital() { + return p.parse_generic_type(name) + } + if p.tok.kind == .lt { + return p.parse_generic_inst_type(name) + } + return p.find_type_or_add_placeholder(name, language) + } + } + } + p.next() + return ret + } + } +} + +pub fn (mut p Parser) find_type_or_add_placeholder(name string, language ast.Language) ast.Type { + // struct / enum / placeholder + mut idx := p.table.find_type_idx(name) + if idx > 0 { + return ast.new_type(idx) + } + // not found - add placeholder + idx = p.table.add_placeholder_type(name, language) + // println('NOT FOUND: $name - adding placeholder - $idx') + return ast.new_type(idx) +} + +pub fn (mut p Parser) parse_generic_type(name string) ast.Type { + mut idx := p.table.find_type_idx(name) + if idx > 0 { + return ast.new_type(idx).set_flag(.generic) + } + idx = p.table.register_type_symbol(ast.TypeSymbol{ + name: name + cname: util.no_dots(name) + mod: p.mod + kind: .any + is_public: true + }) + return ast.new_type(idx).set_flag(.generic) +} + +pub fn (mut p Parser) parse_generic_inst_type(name string) ast.Type { + mut bs_name := name + mut bs_cname := name + p.next() + p.in_generic_params = true + bs_name += '<' + bs_cname += '_T_' + mut concrete_types := []ast.Type{} + mut is_instance := false + for p.tok.kind != .eof { + gt := p.parse_type() + if !gt.has_flag(.generic) { + is_instance = true + } + gts := p.table.get_type_symbol(gt) + bs_name += gts.name + bs_cname += gts.cname + concrete_types << gt + if p.tok.kind != .comma { + break + } + p.next() + bs_name += ', ' + bs_cname += '_' + } + p.check(.gt) + p.in_generic_params = false + bs_name += '>' + // fmt operates on a per-file basis, so is_instance might be not set correctly. Thus it's ignored. + if (is_instance || p.pref.is_fmt) && concrete_types.len > 0 { + mut gt_idx := p.table.find_type_idx(bs_name) + if gt_idx > 0 { + return ast.new_type(gt_idx) + } + gt_idx = p.table.add_placeholder_type(bs_name, .v) + mut parent_idx := p.table.type_idxs[name] + if parent_idx == 0 { + parent_idx = p.table.add_placeholder_type(name, .v) + } + idx := p.table.register_type_symbol(ast.TypeSymbol{ + kind: .generic_inst + name: bs_name + cname: util.no_dots(bs_cname) + mod: p.mod + info: ast.GenericInst{ + parent_idx: parent_idx + concrete_types: concrete_types + } + }) + return ast.new_type(idx) + } + return p.find_type_or_add_placeholder(name, .v).set_flag(.generic) +} diff --git a/v_windows/v/vlib/v/parser/parser.v b/v_windows/v/vlib/v/parser/parser.v new file mode 100644 index 0000000..0924abe --- /dev/null +++ b/v_windows/v/vlib/v/parser/parser.v @@ -0,0 +1,3454 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.scanner +import v.ast +import v.token +import v.pref +import v.util +import v.vet +import v.errors +import os +import hash.fnv1a + +const ( + builtin_functions = ['print', 'println', 'eprint', 'eprintln', 'isnil', 'panic', 'exit'] +) + +pub struct Parser { + pref &pref.Preferences +mut: + file_base string // "hello.v" + file_name string // "/home/user/hello.v" + file_name_dir string // "/home/user" + unique_prefix string // a hash of p.file_name, used for making anon fn generation unique + file_backend_mode ast.Language // .c for .c.v|.c.vv|.c.vsh files; .js for .js.v files, .amd64/.rv32/other arches for .amd64.v/.rv32.v/etc. files, .v otherwise. + scanner &scanner.Scanner + comments_mode scanner.CommentsMode = .skip_comments + // see comment in parse_file + tok token.Token + prev_tok token.Token + peek_tok token.Token + table &ast.Table + language ast.Language + inside_test_file bool // when inside _test.v or _test.vv file + inside_if bool + inside_if_expr bool + inside_ct_if_expr bool + inside_or_expr bool + inside_for bool + inside_fn bool // true even with implicit main + inside_unsafe_fn bool + inside_str_interp bool + inside_array_lit bool + or_is_handled bool // ignore `or` in this expression + builtin_mod bool // are we in the `builtin` module? + mod string // current module name + is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree + attrs []ast.Attr // attributes before next decl stmt + expr_mod string // for constructing full type names in parse_type() + scope &ast.Scope + imports map[string]string // alias => mod_name + ast_imports []ast.Import // mod_names + used_imports []string // alias + auto_imports []string // imports, the user does not need to specify + imported_symbols map[string]string + is_amp bool // for generating the right code for `&Foo{}` + returns bool + inside_match bool // to separate `match A { }` from `Struct{}` + inside_select bool // to allow `ch <- Struct{} {` inside `select` + inside_match_case bool // to separate `match_expr { }` from `Struct{}` + inside_match_body bool // to fix eval not used TODO + inside_unsafe bool + is_stmt_ident bool // true while the beginning of a statement is an ident/selector + expecting_type bool // `is Type`, expecting type + errors []errors.Error + warnings []errors.Warning + notices []errors.Notice + vet_errors []vet.Error + cur_fn_name string + label_names []string + in_generic_params bool // indicates if parsing between `<` and `>` of a method/function + name_error bool // indicates if the token is not a name or the name is on another line + n_asm int // controls assembly labels + inside_asm_template bool + inside_asm bool + global_labels []string + inside_defer bool + comp_if_cond bool + defer_vars []ast.Ident + should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop +} + +// for tests +pub fn parse_stmt(text string, table &ast.Table, scope &ast.Scope) ast.Stmt { + mut p := Parser{ + scanner: scanner.new_scanner(text, .skip_comments, &pref.Preferences{}) + inside_test_file: true + table: table + pref: &pref.Preferences{} + scope: scope + } + p.init_parse_fns() + util.timing_start('PARSE stmt') + defer { + util.timing_measure_cumulative('PARSE stmt') + } + p.read_first_token() + return p.stmt(false) +} + +pub fn parse_comptime(text string, table &ast.Table, pref &pref.Preferences, scope &ast.Scope) &ast.File { + mut p := Parser{ + scanner: scanner.new_scanner(text, .skip_comments, pref) + table: table + pref: pref + scope: scope + errors: []errors.Error{} + warnings: []errors.Warning{} + } + return p.parse() +} + +pub fn parse_text(text string, path string, table &ast.Table, comments_mode scanner.CommentsMode, pref &pref.Preferences) &ast.File { + mut p := Parser{ + scanner: scanner.new_scanner(text, comments_mode, pref) + comments_mode: comments_mode + table: table + pref: pref + scope: &ast.Scope{ + start_pos: 0 + parent: table.global_scope + } + errors: []errors.Error{} + warnings: []errors.Warning{} + } + p.set_path(path) + return p.parse() +} + +[unsafe] +pub fn (mut p Parser) free() { + unsafe { + p.scanner.free() + } +} + +pub fn (mut p Parser) set_path(path string) { + p.file_name = path + p.file_base = os.base(path) + p.file_name_dir = os.dir(path) + hash := fnv1a.sum64_string(path) + p.unique_prefix = hash.hex_full() + if p.file_base.ends_with('_test.v') || p.file_base.ends_with('_test.vv') { + p.inside_test_file = true + } + before_dot_v := path.before('.v') // also works for .vv and .vsh + language := before_dot_v.all_after_last('.') + langauge_with_underscore := before_dot_v.all_after_last('_') + if language == before_dot_v && langauge_with_underscore == before_dot_v { + p.file_backend_mode = .v + return + } + actual_language := if language == before_dot_v { langauge_with_underscore } else { language } + match actual_language { + 'c' { + p.file_backend_mode = .c + } + 'js' { + p.file_backend_mode = .js + } + else { + arch := pref.arch_from_string(actual_language) or { pref.Arch._auto } + p.file_backend_mode = ast.pref_arch_to_table_language(arch) + if arch == ._auto { + p.file_backend_mode = .v + } + } + } +} + +pub fn parse_file(path string, table &ast.Table, comments_mode scanner.CommentsMode, pref &pref.Preferences) &ast.File { + // NB: when comments_mode == .toplevel_comments, + // the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip + // all the tricky inner comments. This is needed because we do not have a good general solution + // for handling them, and should be removed when we do (the general solution is also needed for vfmt) + mut p := Parser{ + scanner: scanner.new_scanner_file(path, comments_mode, pref) + comments_mode: comments_mode + table: table + pref: pref + scope: &ast.Scope{ + start_pos: 0 + parent: table.global_scope + } + errors: []errors.Error{} + warnings: []errors.Warning{} + } + p.set_path(path) + return p.parse() +} + +pub fn parse_vet_file(path string, table_ &ast.Table, pref &pref.Preferences) (&ast.File, []vet.Error) { + global_scope := &ast.Scope{ + parent: 0 + } + mut p := Parser{ + scanner: scanner.new_scanner_file(path, .parse_comments, pref) + comments_mode: .parse_comments + table: table_ + pref: pref + scope: &ast.Scope{ + start_pos: 0 + parent: global_scope + } + errors: []errors.Error{} + warnings: []errors.Warning{} + } + p.set_path(path) + if p.scanner.text.contains_any_substr(['\n ', ' \n']) { + source_lines := os.read_lines(path) or { []string{} } + for lnumber, line in source_lines { + if line.starts_with(' ') { + p.vet_error('Looks like you are using spaces for indentation.', lnumber, + .vfmt, .space_indent) + } + if line.ends_with(' ') { + p.vet_error('Looks like you have trailing whitespace.', lnumber, .unknown, + .trailing_space) + } + } + } + p.vet_errors << p.scanner.vet_errors + file := p.parse() + return file, p.vet_errors +} + +pub fn (mut p Parser) parse() &ast.File { + util.timing_start('PARSE') + defer { + util.timing_measure_cumulative('PARSE') + } + // comments_mode: comments_mode + p.init_parse_fns() + p.read_first_token() + mut stmts := []ast.Stmt{} + for p.tok.kind == .comment { + stmts << p.comment_stmt() + } + // module + module_decl := p.module_decl() + if module_decl.is_skipped { + stmts.insert(0, ast.Stmt(module_decl)) + } else { + stmts << module_decl + } + // imports + for { + if p.tok.kind == .key_import { + stmts << p.import_stmt() + continue + } + if p.tok.kind == .comment { + stmts << p.comment_stmt() + continue + } + break + } + for { + if p.tok.kind == .eof { + p.check_unused_imports() + break + } + stmt := p.top_stmt() + // clear the attributes after each statement + if !(stmt is ast.ExprStmt && (stmt as ast.ExprStmt).expr is ast.Comment) { + p.attrs = [] + } + stmts << stmt + if p.should_abort { + break + } + } + p.scope.end_pos = p.tok.pos + return &ast.File{ + path: p.file_name + path_base: p.file_base + is_test: p.inside_test_file + nr_lines: p.scanner.line_nr + nr_bytes: p.scanner.text.len + mod: module_decl + imports: p.ast_imports + imported_symbols: p.imported_symbols + auto_imports: p.auto_imports + stmts: stmts + scope: p.scope + global_scope: p.table.global_scope + errors: p.errors + warnings: p.warnings + global_labels: p.global_labels + } +} + +/* +struct Queue { +mut: + idx int + mu &sync.Mutex + mu2 &sync.Mutex + paths []string + table &ast.Table + parsed_ast_files []&ast.File + pref &pref.Preferences + global_scope &ast.Scope +} + +fn (mut q Queue) run() { + for { + q.mu.lock() + idx := q.idx + if idx >= q.paths.len { + q.mu.unlock() + return + } + q.idx++ + q.mu.unlock() + println('run(idx=$idx)') + path := q.paths[idx] + file := parse_file(path, q.table, .skip_comments, q.pref, q.global_scope) + q.mu2.lock() + q.parsed_ast_files << file + q.mu2.unlock() + println('run done(idx=$idx)') + } +} +*/ +pub fn parse_files(paths []string, table &ast.Table, pref &pref.Preferences) []&ast.File { + mut timers := util.new_timers(false) + $if time_parsing ? { + timers.should_print = true + } + $if macos { + /* + if pref.is_parallel && paths[0].contains('/array.v') { + println('\n\n\nparse_files() nr_files=$paths.len') + println(paths) + nr_cpus := runtime.nr_cpus() + mut q := &Queue{ + paths: paths + table: table + pref: pref + global_scope: global_scope + mu: sync.new_mutex() + mu2: sync.new_mutex() + } + for _ in 0 .. nr_cpus - 1 { + go q.run() + } + time.sleep(time.second) + println('all done') + return q.parsed_ast_files + } + */ + } + mut files := []&ast.File{} + for path in paths { + timers.start('parse_file $path') + files << parse_file(path, table, .skip_comments, pref) + timers.show('parse_file $path') + } + return files +} + +pub fn (mut p Parser) init_parse_fns() { + // p.prefix_parse_fns = make(100, 100, sizeof(PrefixParseFn)) + // p.prefix_parse_fns[token.Kind.name] = parse_name +} + +pub fn (mut p Parser) read_first_token() { + // need to call next() 2 times to get peek token and current token + p.next() + p.next() +} + +[inline] +pub fn (p &Parser) peek_token(n int) token.Token { + return p.scanner.peek_token(n - 2) +} + +pub fn (mut p Parser) open_scope() { + p.scope = &ast.Scope{ + parent: p.scope + start_pos: p.tok.pos + } +} + +pub fn (mut p Parser) close_scope() { + // p.scope.end_pos = p.tok.pos + // NOTE: since this is usually called after `p.parse_block()` + // ie. when `prev_tok` is rcbr `}` we most likely want `prev_tok` + // we could do the following, but probably not needed in 99% of cases: + // `end_pos = if p.prev_tok.kind == .rcbr { p.prev_tok.pos } else { p.tok.pos }` + p.scope.end_pos = p.prev_tok.pos + p.scope.parent.children << p.scope + p.scope = p.scope.parent +} + +pub fn (mut p Parser) parse_block() []ast.Stmt { + p.open_scope() + stmts := p.parse_block_no_scope(false) + p.close_scope() + return stmts +} + +pub fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { + p.check(.lcbr) + mut stmts := []ast.Stmt{} + if p.tok.kind != .rcbr { + mut count := 0 + for p.tok.kind !in [.eof, .rcbr] { + stmts << p.stmt(is_top_level) + count++ + if count % 100000 == 0 { + eprintln('parsed $count statements so far from fn $p.cur_fn_name ...') + } + if count > 1000000 { + p.error_with_pos('parsed over $count statements from fn $p.cur_fn_name, the parser is probably stuck', + p.tok.position()) + return [] + } + } + } + if is_top_level { + p.top_level_statement_end() + } + p.check(.rcbr) + return stmts +} + +fn (mut p Parser) next() { + p.prev_tok = p.tok + p.tok = p.peek_tok + p.peek_tok = p.scanner.scan() +} + +fn (mut p Parser) check(expected token.Kind) { + p.name_error = false + if _likely_(p.tok.kind == expected) { + p.next() + } else { + if expected == .name { + p.name_error = true + } + mut s := expected.str() + // quote keywords, punctuation, operators + if token.is_key(s) || (s.len > 0 && !s[0].is_letter()) { + s = '`$s`' + } + p.error('unexpected $p.tok, expecting $s') + } +} + +// JS functions can have multiple dots in their name: +// JS.foo.bar.and.a.lot.more.dots() +fn (mut p Parser) check_js_name() string { + mut name := '' + for p.peek_tok.kind == .dot { + name += '${p.tok.lit}.' + p.next() // .name + p.next() // .dot + } + // last .name + name += p.tok.lit + p.next() + return name +} + +fn (mut p Parser) check_name() string { + name := p.tok.lit + if p.peek_tok.kind == .dot && name in p.imports { + p.register_used_import(name) + } + p.check(.name) + return name +} + +pub fn (mut p Parser) top_stmt() ast.Stmt { + $if trace_parser ? { + tok_pos := p.tok.position() + eprintln('parsing file: ${p.file_name:-30} | tok.kind: ${p.tok.kind:-10} | tok.lit: ${p.tok.lit:-10} | tok_pos: ${tok_pos.str():-45} | top_stmt') + } + for { + match p.tok.kind { + .key_pub { + match p.peek_tok.kind { + .key_const { + return p.const_decl() + } + .key_fn { + return p.fn_decl() + } + .key_struct, .key_union { + return p.struct_decl() + } + .key_interface { + return p.interface_decl() + } + .key_enum { + return p.enum_decl() + } + .key_type { + return p.type_decl() + } + else { + return p.error('wrong pub keyword usage') + } + } + } + .lsbr { + // attrs are stored in `p.attrs` + p.attributes() + continue + } + .key_asm { + return p.asm_stmt(true) + } + .key_interface { + return p.interface_decl() + } + .key_import { + p.error_with_pos('`import x` can only be declared at the beginning of the file', + p.tok.position()) + return p.import_stmt() + } + .key_global { + return p.global_decl() + } + .key_const { + return p.const_decl() + } + .key_fn { + return p.fn_decl() + } + .key_struct { + return p.struct_decl() + } + .dollar { + if_expr := p.if_expr(true) + return ast.ExprStmt{ + expr: if_expr + pos: if_expr.pos + } + } + .hash { + return p.hash() + } + .key_type { + return p.type_decl() + } + .key_enum { + return p.enum_decl() + } + .key_union { + return p.struct_decl() + } + .comment { + return p.comment_stmt() + } + else { + p.inside_fn = true + if p.pref.is_script && !p.pref.is_test { + p.open_scope() + mut stmts := []ast.Stmt{} + for p.tok.kind != .eof { + stmts << p.stmt(false) + } + p.close_scope() + return ast.FnDecl{ + name: 'main.main' + mod: 'main' + is_main: true + stmts: stmts + file: p.file_name + return_type: ast.void_type + scope: p.scope + label_names: p.label_names + } + } else if p.pref.is_fmt { + return p.stmt(false) + } else { + return p.error('bad top level statement ' + p.tok.str()) + } + } + } + if p.should_abort { + break + } + } + // TODO remove dummy return statement + // the compiler complains if it's not there + return ast.empty_stmt() +} + +// TODO [if vfmt] +pub fn (mut p Parser) check_comment() ast.Comment { + if p.tok.kind == .comment { + return p.comment() + } + return ast.Comment{} +} + +pub fn (mut p Parser) comment() ast.Comment { + mut pos := p.tok.position() + text := p.tok.lit + num_newlines := text.count('\n') + is_multi := num_newlines > 0 + is_inline := text.len + 4 == p.tok.len // 4: `/` `*` `*` `/` + pos.last_line = pos.line_nr + num_newlines + p.next() + // Filter out false positive space indent vet errors inside comments + if p.vet_errors.len > 0 && is_multi { + p.vet_errors = p.vet_errors.filter(it.typ != .space_indent + || it.pos.line_nr - 1 > pos.last_line || it.pos.line_nr - 1 <= pos.line_nr) + } + return ast.Comment{ + text: text + is_multi: is_multi + is_inline: is_inline + pos: pos + } +} + +pub fn (mut p Parser) comment_stmt() ast.ExprStmt { + comment := p.comment() + return ast.ExprStmt{ + expr: comment + pos: comment.pos + } +} + +struct EatCommentsConfig { + same_line bool // Only eat comments on the same line as the previous token + follow_up bool // Comments directly below the previous token as long as there is no empty line +} + +pub fn (mut p Parser) eat_comments(cfg EatCommentsConfig) []ast.Comment { + mut line := p.prev_tok.line_nr + mut comments := []ast.Comment{} + for { + if p.tok.kind != .comment || (cfg.same_line && p.tok.line_nr > line) + || (cfg.follow_up && (p.tok.line_nr > line + 1 || p.tok.lit.contains('\n'))) { + break + } + comments << p.comment() + if cfg.follow_up { + line = p.prev_tok.line_nr + } + } + return comments +} + +pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { + $if trace_parser ? { + tok_pos := p.tok.position() + eprintln('parsing file: ${p.file_name:-30} | tok.kind: ${p.tok.kind:-10} | tok.lit: ${p.tok.lit:-10} | tok_pos: ${tok_pos.str():-45} | stmt($is_top_level)') + } + p.is_stmt_ident = p.tok.kind == .name + match p.tok.kind { + .lcbr { + mut pos := p.tok.position() + stmts := p.parse_block() + pos.last_line = p.prev_tok.line_nr + return ast.Block{ + stmts: stmts + pos: pos + } + } + .key_assert { + p.next() + mut pos := p.tok.position() + expr := p.expr(0) + pos.update_last_line(p.prev_tok.line_nr) + return ast.AssertStmt{ + expr: expr + pos: pos.extend(p.tok.position()) + is_used: p.inside_test_file || !p.pref.is_prod + } + } + .key_for { + return p.for_stmt() + } + .name { + if p.tok.lit == 'sql' && p.peek_tok.kind == .name { + return p.sql_stmt() + } + if p.peek_tok.kind == .colon { + // `label:` + spos := p.tok.position() + name := p.check_name() + if name in p.label_names { + p.error_with_pos('duplicate label `$name`', spos) + } + p.label_names << name + p.next() + if p.tok.kind == .key_for { + for_pos := p.tok.position() + mut stmt := p.stmt(is_top_level) + match mut stmt { + ast.ForStmt { + stmt.label = name + return stmt + } + ast.ForInStmt { + stmt.label = name + return stmt + } + ast.ForCStmt { + stmt.label = name + return stmt + } + else { + p.error_with_pos('unknown kind of For statement', for_pos) + } + } + } + return ast.GotoLabel{ + name: name + pos: spos.extend(p.tok.position()) + } + } else if p.peek_tok.kind == .name { + return p.error_with_pos('unexpected name `$p.peek_tok.lit`', p.peek_tok.position()) + } else if !p.inside_if_expr && !p.inside_match_body && !p.inside_or_expr + && p.peek_tok.kind in [.rcbr, .eof] && !p.mark_var_as_used(p.tok.lit) { + return p.error_with_pos('`$p.tok.lit` evaluated but not used', p.tok.position()) + } + return p.parse_multi_expr(is_top_level) + } + .comment { + return p.comment_stmt() + } + .key_return { + if p.inside_defer { + return p.error_with_pos('`return` not allowed inside `defer` block', p.tok.position()) + } else { + return p.return_stmt() + } + } + .dollar { + match p.peek_tok.kind { + .key_if { + mut pos := p.tok.position() + expr := p.if_expr(true) + pos.update_last_line(p.prev_tok.line_nr) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + .key_for { + return p.comp_for() + } + .name { + mut pos := p.tok.position() + expr := p.comp_call() + pos.update_last_line(p.prev_tok.line_nr) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + else { + return p.error_with_pos('unexpected \$', p.tok.position()) + } + } + } + .key_continue, .key_break { + tok := p.tok + line := p.tok.line_nr + p.next() + mut label := '' + if p.tok.line_nr == line && p.tok.kind == .name { + label = p.check_name() + } + return ast.BranchStmt{ + kind: tok.kind + label: label + pos: tok.position() + } + } + .key_unsafe { + return p.unsafe_stmt() + } + .hash { + return p.hash() + } + .key_defer { + if p.inside_defer { + return p.error_with_pos('`defer` blocks cannot be nested', p.tok.position()) + } else { + p.next() + spos := p.tok.position() + p.inside_defer = true + p.defer_vars = []ast.Ident{} + stmts := p.parse_block() + p.inside_defer = false + return ast.DeferStmt{ + stmts: stmts + defer_vars: p.defer_vars.clone() + pos: spos.extend_with_last_line(p.tok.position(), p.prev_tok.line_nr) + } + } + } + .key_go { + go_expr := p.go_expr() + return ast.ExprStmt{ + expr: go_expr + pos: go_expr.pos + } + } + .key_goto { + p.next() + spos := p.tok.position() + name := p.check_name() + return ast.GotoStmt{ + name: name + pos: spos + } + } + .key_const { + return p.error_with_pos('const can only be defined at the top level (outside of functions)', + p.tok.position()) + } + .key_asm { + return p.asm_stmt(false) + } + // literals, 'if', etc. in here + else { + return p.parse_multi_expr(is_top_level) + } + } +} + +fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { + p.inside_asm = true + p.inside_asm_template = true + defer { + p.inside_asm = false + p.inside_asm_template = false + } + p.n_asm = 0 + if is_top_level { + p.top_level_statement_start() + } + mut backup_scope := p.scope + + pos := p.tok.position() + + p.check(.key_asm) + mut arch := pref.arch_from_string(p.tok.lit) or { pref.Arch._auto } + mut is_volatile := false + mut is_goto := false + if p.tok.lit == 'volatile' && p.tok.kind == .name { + arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } + is_volatile = true + p.next() + } else if p.tok.kind == .key_goto { + arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } + is_goto = true + p.next() + } + if arch == ._auto && !p.pref.is_fmt { + p.error('unknown assembly architecture') + } + if p.tok.kind != .name { + p.error('must specify assembly architecture') + } else { + p.next() + } + + p.check_for_impure_v(ast.pref_arch_to_table_language(arch), p.prev_tok.position()) + + p.check(.lcbr) + p.scope = &ast.Scope{ + parent: 0 // you shouldn't be able to reference other variables in assembly blocks + detached_from_parent: true + start_pos: p.tok.pos + objects: ast.all_registers(mut p.table, arch) // + } + + mut local_labels := []string{} + // riscv: https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf + // x86: https://www.felixcloutier.com/x86/ + // arm: https://developer.arm.com/documentation/dui0068/b/arm-instruction-reference + mut templates := []ast.AsmTemplate{} + for p.tok.kind !in [.semicolon, .rcbr] { + template_pos := p.tok.position() + mut name := '' + if p.tok.kind == .name && arch == .amd64 && p.tok.lit in ['rex', 'vex', 'xop'] { + name += p.tok.lit + p.next() + for p.tok.kind == .dot { + p.next() + name += '.' + p.tok.lit + p.check(.name) + } + name += ' ' + } + is_directive := p.tok.kind == .dot + if is_directive { + p.next() + } + if p.tok.kind in [.key_in, .key_lock, .key_orelse] { // `in`, `lock`, `or` are v keywords that are also x86/arm/riscv instructions. + name += p.tok.kind.str() + p.next() + } else if p.tok.kind == .number { + name += p.tok.lit + p.next() + } else { + name += p.tok.lit + p.check(.name) + } + // dots are part of instructions for some riscv extensions + if arch in [.rv32, .rv64] { + for p.tok.kind == .dot { + name += '.' + p.next() + name += p.tok.lit + p.check(.name) + } + } + mut is_label := false + + mut args := []ast.AsmArg{} + if p.tok.line_nr == p.prev_tok.line_nr { + args_loop: for { + if p.prev_tok.position().line_nr < p.tok.position().line_nr { + break + } + match p.tok.kind { + .name { + args << p.reg_or_alias() + } + .number { + number_lit := p.parse_number_literal() + match number_lit { + ast.FloatLiteral { + args << ast.FloatLiteral{ + ...number_lit + } + } + ast.IntegerLiteral { + if is_directive { + args << ast.AsmDisp{ + val: number_lit.val + pos: number_lit.pos + } + } else { + args << ast.IntegerLiteral{ + ...number_lit + } + } + } + else { + verror('p.parse_number_literal() invalid output: `$number_lit`') + } + } + } + .chartoken { + args << ast.CharLiteral{ + val: p.tok.lit + pos: p.tok.position() + } + p.next() + } + .colon { + is_label = true + p.next() + local_labels << name + break + } + .lsbr { + args << p.asm_addressing() + } + .rcbr { + break + } + .semicolon { + break + } + else { + p.error('invalid token in assembly block') + } + } + if p.tok.kind == .comma { + p.next() + } else { + break + } + } + // if p.prev_tok.position().line_nr < p.tok.position().line_nr { + // break + // } + } + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + if is_directive && name in ['globl', 'global'] { + for arg in args { + p.global_labels << (arg as ast.AsmAlias).name + } + } + templates << ast.AsmTemplate{ + name: name + args: args + comments: comments + is_label: is_label + is_directive: is_directive + pos: template_pos.extend(p.tok.position()) + } + } + mut scope := p.scope + p.scope = backup_scope + p.inside_asm_template = false + mut output, mut input, mut clobbered, mut global_labels := []ast.AsmIO{}, []ast.AsmIO{}, []ast.AsmClobbered{}, []string{} + if !is_top_level { + if p.tok.kind == .semicolon { + output = p.asm_ios(true) + if p.tok.kind == .semicolon { + input = p.asm_ios(false) + } + if p.tok.kind == .semicolon { + // because p.reg_or_alias() requires the scope with registers to recognize registers. + backup_scope = p.scope + p.scope = scope + p.next() + for p.tok.kind == .name { + reg := ast.AsmRegister{ + name: p.tok.lit + typ: 0 + size: -1 + } + p.next() + + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + clobbered << ast.AsmClobbered{ + reg: reg + comments: comments + } + + if p.tok.kind in [.rcbr, .semicolon] { + break + } + } + + if is_goto && p.tok.kind == .semicolon { + p.next() + for p.tok.kind == .name { + global_labels << p.tok.lit + p.next() + } + } + } + } + } else if p.tok.kind == .semicolon { + p.error('extended assembly is not allowed as a top level statement') + } + p.scope = backup_scope + p.check(.rcbr) + if is_top_level { + p.top_level_statement_end() + } + scope.end_pos = p.prev_tok.pos + + return ast.AsmStmt{ + arch: arch + is_goto: is_goto + is_volatile: is_volatile + templates: templates + output: output + input: input + clobbered: clobbered + pos: pos.extend(p.prev_tok.position()) + is_basic: is_top_level || output.len + input.len + clobbered.len == 0 + scope: scope + global_labels: global_labels + local_labels: local_labels + } +} + +fn (mut p Parser) reg_or_alias() ast.AsmArg { + p.check(.name) + if p.prev_tok.lit in p.scope.objects { + x := p.scope.objects[p.prev_tok.lit] + if x is ast.AsmRegister { + return ast.AsmArg(x as ast.AsmRegister) + } else { + verror('non-register ast.ScopeObject found in scope') + return ast.AsmDisp{} // should not be reached + } + } else if p.prev_tok.len >= 2 && p.prev_tok.lit[0] in [`b`, `f`] + && p.prev_tok.lit[1..].bytes().all(it.is_digit()) { + return ast.AsmDisp{ + val: p.prev_tok.lit[1..] + p.prev_tok.lit[0].ascii_str() + } + } else { + return ast.AsmAlias{ + name: p.prev_tok.lit + pos: p.prev_tok.position() + } + } +} + +// fn (mut p Parser) asm_addressing() ast.AsmAddressing { +// pos := p.tok.position() +// p.check(.lsbr) +// unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement] [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement] [rip + displacement]' +// mut mode := ast.AddressingMode.invalid +// if p.peek_tok.kind == .rsbr { +// if p.tok.kind == .name { +// mode = .base +// } else if p.tok.kind == .number { +// mode = .displacement +// } else { +// p.error(unknown_addressing_mode) +// } +// } else if p.peek_tok.kind == .mul { +// mode = .index_times_scale_plus_displacement +// } else if p.tok.lit == 'rip' { +// mode = .rip_plus_displacement +// } else if p.peek_tok3.kind == .mul { +// mode = .base_plus_index_times_scale_plus_displacement +// } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .rsbr { +// mode = .base_plus_displacement +// } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .plus { +// mode = .base_plus_index_plus_displacement +// } else { +// p.error(unknown_addressing_mode) +// } +// mut displacement, mut base, mut index, mut scale := u32(0), ast.AsmArg{}, ast.AsmArg{}, -1 + +// match mode { +// .base { +// base = p.reg_or_alias() +// } +// .displacement { +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .index_times_scale_plus_displacement { +// index = p.reg_or_alias() +// p.check(.mul) +// scale = p.tok.lit.int() +// p.check(.number) +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_index_times_scale_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// index = p.reg_or_alias() +// p.check(.mul) +// scale = p.tok.lit.int() +// p.check(.number) +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .rip_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .base_plus_index_plus_displacement { +// base = p.reg_or_alias() +// p.check(.plus) +// index = p.reg_or_alias() +// p.check(.plus) +// displacement = p.tok.lit.u32() +// p.check(.number) +// } +// .invalid {} // there was already an error above +// } + +// p.check(.rsbr) +// return ast.AsmAddressing{ +// base: base +// displacement: displacement +// index: index +// scale: scale +// mode: mode +// pos: pos.extend(p.prev_tok.position()) +// } +// } + +fn (mut p Parser) asm_addressing() ast.AsmAddressing { + pos := p.tok.position() + p.check(.lsbr) + unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement], [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement], [rip + displacement]' + // this mess used to look much cleaner before the removal of peek_tok2/3, see above code for cleaner version + if p.peek_tok.kind == .rsbr { // [displacement] or [base] + if p.tok.kind == .name { + base := p.reg_or_alias() + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base + base: base + pos: pos.extend(p.prev_tok.position()) + } + } else if p.tok.kind == .number { + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .displacement + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else { + p.error(unknown_addressing_mode) + } + } + if p.peek_tok.kind == .plus && p.tok.kind == .name { // [base + displacement], [base + index ∗ scale + displacement], [base + index + displacement] or [rip + displacement] + if p.tok.lit == 'rip' { + rip := p.reg_or_alias() + p.next() + + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .rip_plus_displacement + base: rip + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + base := p.reg_or_alias() + p.next() + if p.peek_tok.kind == .rsbr { + if p.tok.kind == .number { + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_displacement + base: base + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else { + p.error(unknown_addressing_mode) + } + } + index := p.reg_or_alias() + if p.tok.kind == .mul { + p.next() + scale := p.tok.lit.int() + p.check(.number) + p.check(.plus) + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_index_times_scale_plus_displacement + base: base + index: index + scale: scale + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } else if p.tok.kind == .plus { + p.next() + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .base_plus_index_plus_displacement + base: base + index: index + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + } + if p.peek_tok.kind == .mul { // [index ∗ scale + displacement] + index := p.reg_or_alias() + p.next() + scale := p.tok.lit.int() + p.check(.number) + p.check(.plus) + displacement := if p.tok.kind == .name { + p.reg_or_alias() + } else { + x := ast.AsmArg(ast.AsmDisp{ + val: p.tok.lit + pos: p.tok.position() + }) + p.check(.number) + x + } + p.check(.rsbr) + return ast.AsmAddressing{ + mode: .index_times_scale_plus_displacement + index: index + scale: scale + displacement: displacement + pos: pos.extend(p.prev_tok.position()) + } + } + p.error(unknown_addressing_mode) + return ast.AsmAddressing{} +} + +fn (mut p Parser) asm_ios(output bool) []ast.AsmIO { + mut res := []ast.AsmIO{} + p.check(.semicolon) + if p.tok.kind in [.rcbr, .semicolon] { + return [] + } + for { + pos := p.tok.position() + + mut constraint := '' + if p.tok.kind == .lpar { + constraint = if output { '+r' } else { 'r' } // default constraint, though vfmt fmts to `+r` and `r` + } else { + constraint += match p.tok.kind { + .assign { + '=' + } + .plus { + '+' + } + .mod { + '%' + } + .amp { + '&' + } + else { + '' + } + } + if constraint != '' { + p.next() + } + constraint += p.tok.lit + if p.tok.kind == .at { + p.next() + } else { + p.check(.name) + } + } + mut expr := p.expr(0) + if mut expr is ast.ParExpr { + expr = expr.expr + } else { + p.error('asm in/output must be enclosed in brackets') + } + mut alias := '' + if p.tok.kind == .key_as { + p.next() + alias = p.tok.lit + p.check(.name) + } else if mut expr is ast.Ident { + alias = expr.name + } + // for constraints like `a`, no alias is needed, it is reffered to as rcx + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + } + + res << ast.AsmIO{ + alias: alias + constraint: constraint + expr: expr + comments: comments + pos: pos.extend(p.prev_tok.position()) + } + p.n_asm++ + if p.tok.kind in [.semicolon, .rcbr] { + break + } + } + return res +} + +fn (mut p Parser) expr_list() ([]ast.Expr, []ast.Comment) { + mut exprs := []ast.Expr{} + mut comments := []ast.Comment{} + for { + expr := p.expr(0) + if expr is ast.Comment { + comments << expr + } else { + exprs << expr + if p.tok.kind != .comma { + break + } + p.next() + } + } + return exprs, comments +} + +fn (mut p Parser) is_attributes() bool { + if p.tok.kind != .lsbr { + return false + } + mut i := 0 + for { + tok := p.peek_token(i) + if tok.kind == .eof || tok.line_nr != p.tok.line_nr { + return false + } + if tok.kind == .rsbr { + break + } + i++ + } + peek_rsbr_tok := p.peek_token(i + 1) + if peek_rsbr_tok.line_nr == p.tok.line_nr && peek_rsbr_tok.kind != .rcbr { + return false + } + return true +} + +// when is_top_stmt is true attrs are added to p.attrs +fn (mut p Parser) attributes() { + p.check(.lsbr) + mut has_ctdefine := false + for p.tok.kind != .rsbr { + start_pos := p.tok.position() + attr := p.parse_attr() + if p.attrs.contains(attr.name) { + p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position())) + return + } + if attr.kind == .comptime_define { + if has_ctdefine { + p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`', + start_pos.extend(p.prev_tok.position())) + return + } else { + has_ctdefine = true + } + } + p.attrs << attr + if p.tok.kind != .semicolon { + if p.tok.kind == .rsbr { + p.next() + break + } + p.error('unexpected $p.tok, expecting `;`') + return + } + p.next() + } + if p.attrs.len == 0 { + p.error_with_pos('attributes cannot be empty', p.prev_tok.position().extend(p.tok.position())) + return + } +} + +fn (mut p Parser) parse_attr() ast.Attr { + mut kind := ast.AttrKind.plain + apos := p.prev_tok.position() + if p.tok.kind == .key_unsafe { + p.next() + return ast.Attr{ + name: 'unsafe' + kind: kind + pos: apos.extend(p.tok.position()) + } + } + mut name := '' + mut has_arg := false + mut arg := '' + mut comptime_cond := ast.empty_expr() + mut comptime_cond_opt := false + if p.tok.kind == .key_if { + kind = .comptime_define + p.next() + p.comp_if_cond = true + p.inside_if_expr = true + p.inside_ct_if_expr = true + comptime_cond = p.expr(0) + p.comp_if_cond = false + p.inside_if_expr = false + p.inside_ct_if_expr = false + if comptime_cond is ast.PostfixExpr { + comptime_cond_opt = true + } + name = comptime_cond.str() + } else if p.tok.kind == .string { + name = p.tok.lit + kind = .string + p.next() + } else { + name = p.check_name() + if p.tok.kind == .colon { + has_arg = true + p.next() + // `name: arg` + if p.tok.kind == .name { + kind = .plain + arg = p.check_name() + } else if p.tok.kind == .number { + kind = .number + arg = p.tok.lit + p.next() + } else if p.tok.kind == .string { // `name: 'arg'` + kind = .string + arg = p.tok.lit + p.next() + } else { + p.error('unexpected $p.tok, an argument is expected after `:`') + } + } + } + return ast.Attr{ + name: name + has_arg: has_arg + arg: arg + kind: kind + ct_expr: comptime_cond + ct_opt: comptime_cond_opt + pos: apos.extend(p.tok.position()) + } +} + +pub fn (mut p Parser) check_for_impure_v(language ast.Language, pos token.Position) { + if language == .v { + // pure V code is always allowed everywhere + return + } + if !p.pref.warn_impure_v { + // the stricter mode is not ON yet => allow everything for now + return + } + if p.file_backend_mode != language { + upcase_language := language.str().to_upper() + if p.file_backend_mode == .v { + p.warn_with_pos('$upcase_language code will not be allowed in pure .v files, please move it to a .${language}.v file instead', + pos) + return + } else { + p.warn_with_pos('$upcase_language code is not allowed in .${p.file_backend_mode}.v files, please move it to a .${language}.v file', + pos) + return + } + } +} + +pub fn (mut p Parser) error(s string) ast.NodeError { + return p.error_with_pos(s, p.tok.position()) +} + +pub fn (mut p Parser) warn(s string) { + p.warn_with_pos(s, p.tok.position()) +} + +pub fn (mut p Parser) note(s string) { + p.note_with_pos(s, p.tok.position()) +} + +pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError { + if p.pref.fatal_errors { + exit(1) + } + mut kind := 'error:' + if p.pref.output_mode == .stdout { + if p.pref.is_verbose { + print_backtrace() + kind = 'parser error:' + } + ferror := util.formatted_error(kind, s, p.file_name, pos) + eprintln(ferror) + exit(1) + } else { + p.errors << errors.Error{ + file_path: p.file_name + pos: pos + reporter: .parser + message: s + } + } + if p.pref.output_mode == .silent { + // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. + // In the silent mode however, the parser continues to run, even though it would have stopped. Some + // of the parser logic does not expect that, and may loop forever. + // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode. + p.next() + } + return ast.NodeError{ + idx: p.errors.len - 1 + pos: pos + } +} + +pub fn (mut p Parser) error_with_error(error errors.Error) { + if p.pref.fatal_errors { + exit(1) + } + mut kind := 'error:' + if p.pref.output_mode == .stdout { + if p.pref.is_verbose { + print_backtrace() + kind = 'parser error:' + } + ferror := util.formatted_error(kind, error.message, error.file_path, error.pos) + eprintln(ferror) + exit(1) + } else { + if p.pref.message_limit >= 0 && p.errors.len >= p.pref.message_limit { + p.should_abort = true + return + } + p.errors << error + } + if p.pref.output_mode == .silent { + // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. + // In the silent mode however, the parser continues to run, even though it would have stopped. Some + // of the parser logic does not expect that, and may loop forever. + // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode. + p.next() + } +} + +pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) { + if p.pref.warns_are_errors { + p.error_with_pos(s, pos) + return + } + if p.pref.skip_warnings { + return + } + if p.pref.output_mode == .stdout { + ferror := util.formatted_error('warning:', s, p.file_name, pos) + eprintln(ferror) + } else { + if p.pref.message_limit >= 0 && p.warnings.len >= p.pref.message_limit { + p.should_abort = true + return + } + p.warnings << errors.Warning{ + file_path: p.file_name + pos: pos + reporter: .parser + message: s + } + } +} + +pub fn (mut p Parser) note_with_pos(s string, pos token.Position) { + if p.pref.skip_warnings { + return + } + if p.pref.output_mode == .stdout { + ferror := util.formatted_error('notice:', s, p.file_name, pos) + eprintln(ferror) + } else { + p.notices << errors.Notice{ + file_path: p.file_name + pos: pos + reporter: .parser + message: s + } + } +} + +pub fn (mut p Parser) vet_error(msg string, line int, fix vet.FixKind, typ vet.ErrorType) { + pos := token.Position{ + line_nr: line + 1 + } + p.vet_errors << vet.Error{ + message: msg + file_path: p.scanner.file_path + pos: pos + kind: .error + fix: fix + typ: typ + } +} + +fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { + // in here might be 1) multi-expr 2) multi-assign + // 1, a, c ... } // multi-expression + // a, mut b ... :=/= // multi-assign + // collect things upto hard boundaries + tok := p.tok + mut pos := tok.position() + + mut defer_vars := p.defer_vars + p.defer_vars = []ast.Ident{} + + left, left_comments := p.expr_list() + + if !(p.inside_defer && p.tok.kind == .decl_assign) { + defer_vars << p.defer_vars + } + + p.defer_vars = defer_vars + + left0 := left[0] + if tok.kind == .key_mut && p.tok.kind != .decl_assign { + return p.error('expecting `:=` (e.g. `mut x :=`)') + } + // TODO remove translated + if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { + return p.partial_assign_stmt(left, left_comments) + } else if !p.pref.translated && !p.pref.is_fmt + && tok.kind !in [.key_if, .key_match, .key_lock, .key_rlock, .key_select] { + for node in left { + if node !is ast.CallExpr && (is_top_level || p.tok.kind != .rcbr) + && node !is ast.PostfixExpr && !(node is ast.InfixExpr + && (node as ast.InfixExpr).op in [.left_shift, .arrow]) && node !is ast.ComptimeCall + && node !is ast.SelectorExpr && node !is ast.DumpExpr { + return p.error_with_pos('expression evaluated but not used', node.position()) + } + } + } + pos.update_last_line(p.prev_tok.line_nr) + if left.len == 1 { + return ast.ExprStmt{ + expr: left0 + pos: left0.position() + comments: left_comments + is_expr: p.inside_for + } + } + return ast.ExprStmt{ + expr: ast.ConcatExpr{ + vals: left + pos: tok.position() + } + pos: pos + comments: left_comments + } +} + +pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { + // p.warn('name ') + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + if is_shared { + p.register_auto_import('sync') + } + mut_pos := p.tok.position() + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + if is_mut { + p.next() + } + is_static := p.tok.kind == .key_static + if is_static { + p.next() + } + if p.tok.kind != .name { + p.error('unexpected token `$p.tok.lit`') + return ast.Ident{ + scope: p.scope + } + } + pos := p.tok.position() + mut name := p.check_name() + if name == '_' { + return ast.Ident{ + tok_kind: p.tok.kind + name: '_' + comptime: p.comp_if_cond + kind: .blank_ident + pos: pos + info: ast.IdentVar{ + is_mut: false + is_static: false + } + scope: p.scope + } + } + if p.inside_match_body && name == 'it' { + // p.warn('it') + } + if p.expr_mod.len > 0 { + name = '${p.expr_mod}.$name' + } + return ast.Ident{ + tok_kind: p.tok.kind + kind: .unresolved + name: name + comptime: p.comp_if_cond + language: language + mod: p.mod + pos: pos + is_mut: is_mut + mut_pos: mut_pos + info: ast.IdentVar{ + is_mut: is_mut + is_static: is_static + share: ast.sharetype_from_flags(is_shared, is_atomic) + } + scope: p.scope + } +} + +fn (p &Parser) is_typename(t token.Token) bool { + return t.kind == .name && (t.lit[0].is_capital() || p.table.known_type(t.lit)) +} + +// heuristics to detect `func()` from `var < expr` +// 1. `f<[]` is generic(e.g. `f<[]int>`) because `var < []` is invalid +// 2. `f) +// 3. `f` and `f v2` and `v1 < foo < v2` are invalid syntax +// 4. `f` is same as case 3 +// 6. `f 0 && p.tok.lit[0].is_capital() + if lit0_is_capital || p.peek_tok.kind != .lt { + return false + } + mut tok2 := p.peek_token(2) + mut tok3 := p.peek_token(3) + mut tok4 := p.peek_token(4) + mut tok5 := p.peek_token(5) + mut kind2, mut kind3, mut kind4, mut kind5 := tok2.kind, tok3.kind, tok4.kind, tok5.kind + if kind2 == .amp { // if there is a & in front, shift everything left + tok2 = tok3 + kind2 = kind3 + tok3 = tok4 + kind3 = kind4 + tok4 = tok5 + kind4 = kind5 + tok5 = p.peek_token(6) + kind5 = tok5.kind + } + + if kind2 == .lsbr { + // case 1 + return tok3.kind == .rsbr + } + + if kind2 == .name { + if tok2.lit == 'map' && kind3 == .lsbr { + // case 2 + return true + } + return match kind3 { + .gt, .lt { true } // case 3 + .comma { p.is_typename(tok2) } // case 4 + // case 5 and 6 + .dot { kind4 == .name && (kind5 == .gt || (kind5 == .comma && p.is_typename(tok4))) } + else { false } + } + } + return false +} + +const valid_tokens_inside_types = [token.Kind.lsbr, .rsbr, .name, .dot, .comma, .key_fn, .lt] + +fn (mut p Parser) is_generic_cast() bool { + if !p.tok.can_start_type(ast.builtin_type_names) { + return false + } + mut i := 0 + mut level := 0 + mut lt_count := 0 + for { + i++ + tok := p.peek_token(i) + + if tok.kind == .lt { + lt_count++ + level++ + } else if tok.kind == .gt { + level-- + } + if lt_count > 0 && level == 0 { + break + } + + if i > 20 || tok.kind !in parser.valid_tokens_inside_types { + return false + } + } + next_tok := p.peek_token(i + 1) + // `next_tok` is the token following the closing `>` of the generic type: MyType{ + // ^ + // if `next_tok` is a left paren, then the full expression looks something like + // `Foo(` or `Foo(`, which are valid type casts - return true + if next_tok.kind == .lpar { + return true + } + // any other token is not a valid generic cast, however + return false +} + +pub fn (mut p Parser) name_expr() ast.Expr { + prev_tok_kind := p.prev_tok.kind + mut node := ast.empty_expr() + if p.expecting_type { + p.expecting_type = false + // get type position before moving to next + type_pos := p.tok.position() + typ := p.parse_type() + return ast.TypeNode{ + typ: typ + pos: type_pos + } + } + mut language := ast.Language.v + if p.tok.lit == 'C' { + language = ast.Language.c + p.check_for_impure_v(language, p.tok.position()) + } else if p.tok.lit == 'JS' { + language = ast.Language.js + p.check_for_impure_v(language, p.tok.position()) + } + mut mod := '' + // p.warn('resetting') + p.expr_mod = '' + // `map[string]int` initialization + if p.tok.lit == 'map' && p.peek_tok.kind == .lsbr { + map_type := p.parse_map_type() + if p.tok.kind == .lcbr { + p.next() + if p.tok.kind == .rcbr { + p.next() + } else { + p.error('`}` expected; explicit `map` initialization does not support parameters') + } + } + return ast.MapInit{ + typ: map_type + pos: p.prev_tok.position() + } + } + // `chan typ{...}` + if p.tok.lit == 'chan' { + first_pos := p.tok.position() + mut last_pos := first_pos + chan_type := p.parse_chan_type() + mut has_cap := false + mut cap_expr := ast.empty_expr() + p.check(.lcbr) + if p.tok.kind == .rcbr { + last_pos = p.tok.position() + p.next() + } else { + key := p.check_name() + p.check(.colon) + match key { + 'cap' { + has_cap = true + cap_expr = p.expr(0) + } + 'len', 'init' { + return p.error('`$key` cannot be initialized for `chan`. Did you mean `cap`?') + } + else { + return p.error('wrong field `$key`, expecting `cap`') + } + } + last_pos = p.tok.position() + p.check(.rcbr) + } + return ast.ChanInit{ + pos: first_pos.extend(last_pos) + has_cap: has_cap + cap_expr: cap_expr + typ: chan_type + } + } + // Raw string (`s := r'hello \n ') + if p.peek_tok.kind == .string && !p.inside_str_interp && p.peek_token(2).kind != .colon { + if p.tok.lit in ['r', 'c', 'js'] && p.tok.kind == .name { + return p.string_expr() + } else { + // don't allow any other string prefix except `r`, `js` and `c` + return p.error('only `c`, `r`, `js` are recognized string prefixes, but you tried to use `$p.tok.lit`') + } + } + // don't allow r`byte` and c`byte` + if p.tok.lit in ['r', 'c'] && p.peek_tok.kind == .chartoken { + opt := if p.tok.lit == 'r' { '`r` (raw string)' } else { '`c` (c string)' } + return p.error('cannot use $opt with `byte` and `rune`') + } + // Make sure that the var is not marked as used in assignments: `x = 1`, `x += 2` etc + // but only when it's actually used (e.g. `println(x)`) + known_var := if p.peek_tok.kind.is_assign() { + p.scope.known_var(p.tok.lit) + } else { + p.mark_var_as_used(p.tok.lit) + } + // Handle modules + mut is_mod_cast := false + if p.peek_tok.kind == .dot && !known_var && (language != .v || p.known_import(p.tok.lit) + || p.mod.all_after_last('.') == p.tok.lit) { + // p.tok.lit has been recognized as a module + if language == .c { + mod = 'C' + } else if language == .js { + mod = 'JS' + } else { + if p.tok.lit in p.imports { + // mark the imported module as used + p.register_used_import(p.tok.lit) + if p.peek_tok.kind == .dot && p.peek_token(2).kind != .eof + && p.peek_token(2).lit.len > 0 && p.peek_token(2).lit[0].is_capital() { + is_mod_cast = true + } else if p.peek_tok.kind == .dot && p.peek_token(2).kind != .eof + && p.peek_token(2).lit.len == 0 { + // incomplete module selector must be handled by dot_expr instead + ident := p.parse_ident(language) + node = ident + if p.inside_defer { + if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) + && ident.name != 'err' { + p.defer_vars << ident + } + } + return node + } + } + // prepend the full import + mod = p.imports[p.tok.lit] + } + p.next() + p.check(.dot) + p.expr_mod = mod + } + lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 { + p.tok.lit[0].is_capital() + } else { + false + } + is_optional := p.tok.kind == .question + is_generic_call := p.is_generic_call() + is_generic_cast := p.is_generic_cast() + // p.warn('name expr $p.tok.lit $p.peek_tok.str()') + same_line := p.tok.line_nr == p.peek_tok.line_nr + // `(` must be on same line as name token otherwise it's a ParExpr + if !same_line && p.peek_tok.kind == .lpar { + ident := p.parse_ident(language) + node = ident + if p.inside_defer { + if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) + && ident.name != 'err' { + p.defer_vars << ident + } + } + } else if p.peek_tok.kind == .lpar || is_generic_call || is_generic_cast + || (is_optional && p.peek_token(2).kind == .lpar) { + // foo(), foo() or type() cast + mut name := if is_optional { p.peek_tok.lit } else { p.tok.lit } + if mod.len > 0 { + name = '${mod}.$name' + } + name_w_mod := p.prepend_mod(name) + // type cast. TODO: finish + // if name in ast.builtin_type_names { + if (!known_var && (name in p.table.type_idxs || name_w_mod in p.table.type_idxs) + && name !in ['C.stat', 'C.sigaction']) || is_mod_cast || is_generic_cast + || (language == .v && name.len > 0 && name[0].is_capital()) { + // MainLetter(x) is *always* a cast, as long as it is not `C.` + // TODO handle C.stat() + start_pos := p.tok.position() + mut to_typ := p.parse_type() + // this prevents inner casts to also have an `&` + // example: &Foo(malloc(int(num))) + // without the next line int would result in int* + p.is_amp = false + p.check(.lpar) + mut expr := ast.empty_expr() + mut arg := ast.empty_expr() + mut has_arg := false + expr = p.expr(0) + // TODO, string(b, len) + if p.tok.kind == .comma && to_typ.idx() == ast.string_type_idx { + p.next() + arg = p.expr(0) // len + has_arg = true + } + end_pos := p.tok.position() + p.check(.rpar) + node = ast.CastExpr{ + typ: to_typ + typname: p.table.get_type_symbol(to_typ).name + expr: expr + arg: arg + has_arg: has_arg + pos: start_pos.extend(end_pos) + } + p.expr_mod = '' + return node + } else { + // fn call + if is_optional { + p.error_with_pos('unexpected $p.prev_tok', p.prev_tok.position()) + } + node = p.call_expr(language, mod) + } + } else if (p.peek_tok.kind == .lcbr || (p.peek_tok.kind == .lt && lit0_is_capital)) + && (!p.inside_match || (p.inside_select && prev_tok_kind == .arrow && lit0_is_capital)) + && !p.inside_match_case && (!p.inside_if || p.inside_select) + && (!p.inside_for || p.inside_select) { // && (p.tok.lit[0].is_capital() || p.builtin_mod) { + // map.v has struct literal: map{field: expr} + if p.peek_tok.kind == .lcbr && !(p.builtin_mod + && p.file_base in ['map.v', 'map_d_gcboehm_opt.v']) && p.tok.lit == 'map' { + // map{key_expr: val_expr} + p.check(.name) + p.check(.lcbr) + map_init := p.map_init() + p.check(.rcbr) + return map_init + } + return p.struct_init(false) // short_syntax: false + } else if p.peek_tok.kind == .lcbr && p.inside_if && lit0_is_capital && !known_var + && language == .v { + // if a == Foo{} {...} + return p.struct_init(false) + } else if p.peek_tok.kind == .dot && (lit0_is_capital && !known_var && language == .v) { + // T.name + if p.is_generic_name() { + pos := p.tok.position() + name := p.check_name() + p.check(.dot) + field := p.check_name() + fkind := match field { + 'name' { ast.GenericKindField.name } + 'typ' { ast.GenericKindField.typ } + else { ast.GenericKindField.unknown } + } + pos.extend(p.tok.position()) + return ast.SelectorExpr{ + expr: ast.Ident{ + name: name + scope: p.scope + } + field_name: field + gkind_field: fkind + pos: pos + scope: p.scope + } + } + // `Color.green` + mut enum_name := p.check_name() + enum_name_pos := p.prev_tok.position() + if mod != '' { + enum_name = mod + '.' + enum_name + } else { + enum_name = p.imported_symbols[enum_name] or { p.prepend_mod(enum_name) } + } + p.check(.dot) + val := p.check_name() + p.expr_mod = '' + return ast.EnumVal{ + enum_name: enum_name + val: val + pos: enum_name_pos.extend(p.prev_tok.position()) + mod: mod + } + } else if language == .js && p.peek_tok.kind == .dot && p.peek_token(2).kind == .name { + // JS. function call with more than 1 dot + node = p.call_expr(language, mod) + } else { + ident := p.parse_ident(language) + node = ident + if p.inside_defer { + if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) + && ident.name != 'err' { + p.defer_vars << ident + } + } + } + p.expr_mod = '' + return node +} + +fn (mut p Parser) index_expr(left ast.Expr) ast.IndexExpr { + // left == `a` in `a[0]` + start_pos := p.tok.position() + p.next() // [ + mut has_low := true + if p.tok.kind == .dotdot { + has_low = false + // [..end] + p.next() + mut high := ast.empty_expr() + mut has_high := false + if p.tok.kind != .rsbr { + high = p.expr(0) + has_high = true + } + pos := start_pos.extend(p.tok.position()) + p.check(.rsbr) + return ast.IndexExpr{ + left: left + pos: pos + index: ast.RangeExpr{ + low: ast.empty_expr() + high: high + has_high: has_high + pos: pos + } + } + } + expr := p.expr(0) // `[expr]` or `[expr..` + mut has_high := false + if p.tok.kind == .dotdot { + // [start..end] or [start..] + p.next() + mut high := ast.empty_expr() + if p.tok.kind != .rsbr { + has_high = true + high = p.expr(0) + } + pos := start_pos.extend(p.tok.position()) + p.check(.rsbr) + return ast.IndexExpr{ + left: left + pos: pos + index: ast.RangeExpr{ + low: expr + high: high + has_high: has_high + has_low: has_low + pos: pos + } + } + } + // [expr] + pos := start_pos.extend(p.tok.position()) + p.check(.rsbr) + mut or_kind := ast.OrKind.absent + mut or_stmts := []ast.Stmt{} + mut or_pos := token.Position{} + if !p.or_is_handled { + // a[i] or { ... } + if p.tok.kind == .key_orelse { + was_inside_or_expr := p.inside_or_expr + or_pos = p.tok.position() + p.next() + p.open_scope() + or_stmts = p.parse_block_no_scope(false) + or_pos = or_pos.extend(p.prev_tok.position()) + p.close_scope() + p.inside_or_expr = was_inside_or_expr + return ast.IndexExpr{ + left: left + index: expr + pos: pos + or_expr: ast.OrExpr{ + kind: .block + stmts: or_stmts + pos: or_pos + } + } + } + // `a[i] ?` + if p.tok.kind == .question { + or_pos = p.tok.position() + or_kind = .propagate + p.next() + } + } + return ast.IndexExpr{ + left: left + index: expr + pos: pos + or_expr: ast.OrExpr{ + kind: or_kind + stmts: or_stmts + pos: or_pos + } + } +} + +fn (mut p Parser) scope_register_it() { + p.scope.register(ast.Var{ + name: 'it' + pos: p.tok.position() + is_used: true + }) +} + +fn (mut p Parser) scope_register_ab() { + p.scope.register(ast.Var{ + name: 'a' + pos: p.tok.position() + is_used: true + }) + p.scope.register(ast.Var{ + name: 'b' + pos: p.tok.position() + is_used: true + }) +} + +fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { + p.next() + if p.tok.kind == .dollar { + return p.comptime_selector(left) + } + is_generic_call := p.is_generic_call() + name_pos := p.tok.position() + mut field_name := '' + // check if the name is on the same line as the dot + if (p.prev_tok.position().line_nr == name_pos.line_nr) || p.tok.kind != .name { + field_name = p.check_name() + } else { + p.name_error = true + } + is_filter := field_name in ['filter', 'map', 'any', 'all'] + if is_filter || field_name == 'sort' { + p.open_scope() + } + // ! in mutable methods + if p.tok.kind == .not && p.peek_tok.kind == .lpar { + p.next() + } + // Method call + // TODO move to fn.v call_expr() + mut concrete_types := []ast.Type{} + mut concrete_list_pos := p.tok.position() + if is_generic_call { + // `g.foo(10)` + concrete_types = p.parse_generic_type_list() + concrete_list_pos = concrete_list_pos.extend(p.prev_tok.position()) + // In case of `foo()` + // T is unwrapped and registered in the checker. + has_generic := concrete_types.any(it.has_flag(.generic)) + if !has_generic { + // will be added in checker + p.table.register_fn_concrete_types(field_name, concrete_types) + } + } + if p.tok.kind == .lpar { + p.next() + args := p.call_args() + p.check(.rpar) + mut or_stmts := []ast.Stmt{} + mut or_kind := ast.OrKind.absent + mut or_pos := p.tok.position() + if p.tok.kind == .key_orelse { + p.next() + p.open_scope() + p.scope.register(ast.Var{ + name: 'err' + typ: ast.error_type + pos: p.tok.position() + is_used: true + is_stack_obj: true + }) + or_kind = .block + or_stmts = p.parse_block_no_scope(false) + or_pos = or_pos.extend(p.prev_tok.position()) + p.close_scope() + } + // `foo()?` + if p.tok.kind == .question { + p.next() + or_kind = .propagate + } + // + end_pos := p.prev_tok.position() + pos := name_pos.extend(end_pos) + comments := p.eat_comments(same_line: true) + mcall_expr := ast.CallExpr{ + left: left + name: field_name + args: args + name_pos: name_pos + pos: pos + is_method: true + concrete_types: concrete_types + concrete_list_pos: concrete_list_pos + or_block: ast.OrExpr{ + stmts: or_stmts + kind: or_kind + pos: or_pos + } + scope: p.scope + comments: comments + } + if is_filter || field_name == 'sort' { + p.close_scope() + } + return mcall_expr + } + mut is_mut := false + mut mut_pos := token.Position{} + if p.inside_match || p.inside_if_expr { + match left { + ast.Ident, ast.SelectorExpr { + is_mut = left.is_mut + mut_pos = left.mut_pos + } + else {} + } + } + pos := if p.name_error { left.position().extend(name_pos) } else { name_pos } + sel_expr := ast.SelectorExpr{ + expr: left + field_name: field_name + pos: pos + is_mut: is_mut + mut_pos: mut_pos + scope: p.scope + next_token: p.tok.kind + } + if is_filter { + p.close_scope() + } + return sel_expr +} + +fn (mut p Parser) parse_generic_type_list() []ast.Type { + mut types := []ast.Type{} + if p.tok.kind != .lt { + return types + } + p.next() // `<` + mut first_done := false + for p.tok.kind !in [.eof, .gt] { + if first_done { + p.check(.comma) + } + types << p.parse_type() + first_done = true + } + p.check(.gt) // `>` + return types +} + +// `.green` +// `pref.BuildMode.default_mode` +fn (mut p Parser) enum_val() ast.EnumVal { + start_pos := p.tok.position() + p.check(.dot) + val := p.check_name() + return ast.EnumVal{ + val: val + pos: start_pos.extend(p.prev_tok.position()) + } +} + +fn (mut p Parser) filter_string_vet_errors(pos token.Position) { + if p.vet_errors.len == 0 { + return + } + p.vet_errors = p.vet_errors.filter( + (it.typ == .trailing_space && it.pos.line_nr - 1 >= pos.last_line) + || (it.typ != .trailing_space && it.pos.line_nr - 1 > pos.last_line) + || (it.typ == .space_indent && it.pos.line_nr - 1 <= pos.line_nr) + || (it.typ != .space_indent && it.pos.line_nr - 1 < pos.line_nr)) +} + +fn (mut p Parser) string_expr() ast.Expr { + is_raw := p.tok.kind == .name && p.tok.lit == 'r' + is_cstr := p.tok.kind == .name && p.tok.lit == 'c' + if is_raw || is_cstr { + p.next() + } + mut node := ast.empty_expr() + val := p.tok.lit + mut pos := p.tok.position() + pos.last_line = pos.line_nr + val.count('\n') + if p.peek_tok.kind != .str_dollar { + p.next() + p.filter_string_vet_errors(pos) + node = ast.StringLiteral{ + val: val + is_raw: is_raw + language: if is_cstr { ast.Language.c } else { ast.Language.v } + pos: pos + } + return node + } + mut exprs := []ast.Expr{} + mut vals := []string{} + mut has_fmts := []bool{} + mut fwidths := []int{} + mut precisions := []int{} + mut visible_pluss := []bool{} + mut fills := []bool{} + mut fmts := []byte{} + mut fposs := []token.Position{} + // Handle $ interpolation + p.inside_str_interp = true + for p.tok.kind == .string { + vals << p.tok.lit + p.next() + if p.tok.kind != .str_dollar { + break + } + p.next() + exprs << p.expr(0) + mut has_fmt := false + mut fwidth := 0 + mut fwidthneg := false + // 987698 is a magic default value, unlikely to be present in user input. NB: 0 is valid precision + mut precision := 987698 + mut visible_plus := false + mut fill := false + mut fmt := `_` // placeholder + if p.tok.kind == .colon { + p.next() + // ${num:-2d} + if p.tok.kind == .minus { + fwidthneg = true + p.next() + } else if p.tok.kind == .plus { + visible_plus = true + p.next() + } + // ${num:2d} + if p.tok.kind == .number { + fields := p.tok.lit.split('.') + if fields[0].len > 0 && fields[0][0] == `0` { + fill = true + } + fwidth = fields[0].int() + if fwidthneg { + fwidth = -fwidth + } + if fields.len > 1 { + precision = fields[1].int() + } + p.next() + } + if p.tok.kind == .name { + if p.tok.lit.len == 1 { + fmt = p.tok.lit[0] + has_fmt = true + p.next() + } else { + return p.error('format specifier may only be one letter') + } + } + } + fwidths << fwidth + has_fmts << has_fmt + precisions << precision + visible_pluss << visible_plus + fmts << fmt + fills << fill + fposs << p.prev_tok.position() + } + pos = pos.extend(p.prev_tok.position()) + p.filter_string_vet_errors(pos) + node = ast.StringInterLiteral{ + vals: vals + exprs: exprs + need_fmts: has_fmts + fwidths: fwidths + precisions: precisions + pluss: visible_pluss + fills: fills + fmts: fmts + fmt_poss: fposs + pos: pos + } + // need_fmts: prelimery - until checker finds out if really needed + p.inside_str_interp = false + return node +} + +fn (mut p Parser) parse_number_literal() ast.Expr { + mut pos := p.tok.position() + is_neg := p.tok.kind == .minus + if is_neg { + p.next() + pos = pos.extend(p.tok.position()) + } + lit := p.tok.lit + full_lit := if is_neg { '-' + lit } else { lit } + mut node := ast.empty_expr() + if lit.index_any('.eE') >= 0 && lit[..2] !in ['0x', '0X', '0o', '0O', '0b', '0B'] { + node = ast.FloatLiteral{ + val: full_lit + pos: pos + } + } else { + node = ast.IntegerLiteral{ + val: full_lit + pos: pos + } + } + p.next() + return node +} + +fn (mut p Parser) module_decl() ast.Module { + mut module_attrs := []ast.Attr{} + mut attrs_pos := p.tok.position() + if p.tok.kind == .lsbr { + p.attributes() + module_attrs = p.attrs + } + mut name := 'main' + is_skipped := p.tok.kind != .key_module + mut module_pos := token.Position{} + mut name_pos := token.Position{} + mut mod_node := ast.Module{} + if !is_skipped { + p.attrs = [] + module_pos = p.tok.position() + p.next() + name_pos = p.tok.position() + name = p.check_name() + mod_node = ast.Module{ + pos: module_pos + } + if module_pos.line_nr != name_pos.line_nr { + p.error_with_pos('`module` and `$name` must be at same line', name_pos) + return mod_node + } + // NB: this shouldn't be reassigned into name_pos + // as it creates a wrong position when extended + // to module_pos + n_pos := p.tok.position() + if module_pos.line_nr == n_pos.line_nr && p.tok.kind != .comment && p.tok.kind != .eof { + if p.tok.kind == .name { + p.error_with_pos('`module $name`, you can only declare one module, unexpected `$p.tok.lit`', + n_pos) + return mod_node + } else { + p.error_with_pos('`module $name`, unexpected `$p.tok.kind` after module name', + n_pos) + return mod_node + } + } + module_pos = attrs_pos.extend(name_pos) + } + full_name := util.qualify_module(p.pref, name, p.file_name) + p.mod = full_name + p.builtin_mod = p.mod == 'builtin' + mod_node = ast.Module{ + name: full_name + short_name: name + attrs: module_attrs + is_skipped: is_skipped + pos: module_pos + name_pos: name_pos + } + if !is_skipped { + for ma in module_attrs { + match ma.name { + 'manualfree' { + p.is_manualfree = true + } + else { + p.error_with_pos('unknown module attribute `[$ma.name]`', ma.pos) + return mod_node + } + } + } + } + return mod_node +} + +fn (mut p Parser) import_stmt() ast.Import { + import_pos := p.tok.position() + p.check(.key_import) + mut pos := p.tok.position() + mut import_node := ast.Import{ + pos: import_pos.extend(pos) + } + if p.tok.kind == .lpar { + p.error_with_pos('`import()` has been deprecated, use `import x` instead', pos) + return import_node + } + mut mod_name_arr := []string{} + mod_name_arr << p.check_name() + if import_pos.line_nr != pos.line_nr { + p.error_with_pos('`import` statements must be a single line', pos) + return import_node + } + mut mod_alias := mod_name_arr[0] + import_node = ast.Import{ + pos: import_pos.extend(pos) + mod_pos: pos + alias_pos: pos + } + for p.tok.kind == .dot { + p.next() + submod_pos := p.tok.position() + if p.tok.kind != .name { + p.error_with_pos('module syntax error, please use `x.y.z`', submod_pos) + return import_node + } + if import_pos.line_nr != submod_pos.line_nr { + p.error_with_pos('`import` and `submodule` must be at same line', submod_pos) + return import_node + } + submod_name := p.check_name() + mod_name_arr << submod_name + mod_alias = submod_name + pos = pos.extend(submod_pos) + import_node = ast.Import{ + pos: import_pos.extend(pos) + mod_pos: pos + alias_pos: submod_pos + mod: util.qualify_import(p.pref, mod_name_arr.join('.'), p.file_name) + alias: mod_alias + } + } + if mod_name_arr.len == 1 { + import_node = ast.Import{ + pos: import_node.pos + mod_pos: import_node.mod_pos + alias_pos: import_node.alias_pos + mod: util.qualify_import(p.pref, mod_name_arr[0], p.file_name) + alias: mod_alias + } + } + mod_name := import_node.mod + if p.tok.kind == .key_as { + p.next() + alias_pos := p.tok.position() + mod_alias = p.check_name() + if mod_alias == mod_name_arr.last() { + p.error_with_pos('import alias `$mod_name as $mod_alias` is redundant', p.prev_tok.position()) + return import_node + } + import_node = ast.Import{ + pos: import_node.pos.extend(alias_pos) + mod_pos: import_node.mod_pos + alias_pos: alias_pos + mod: import_node.mod + alias: mod_alias + } + } + if p.tok.kind == .lcbr { // import module { fn1, Type2 } syntax + mut initial_syms_pos := p.tok.position() + p.import_syms(mut import_node) + initial_syms_pos = initial_syms_pos.extend(p.tok.position()) + import_node = ast.Import{ + ...import_node + syms_pos: initial_syms_pos + pos: import_node.pos.extend(initial_syms_pos) + } + p.register_used_import(mod_alias) // no `unused import` msg for parent + } + pos_t := p.tok.position() + if import_pos.line_nr == pos_t.line_nr { + if p.tok.kind !in [.lcbr, .eof, .comment] { + p.error_with_pos('cannot import multiple modules at a time', pos_t) + return import_node + } + } + import_node.comments = p.eat_comments(same_line: true) + import_node.next_comments = p.eat_comments(follow_up: true) + p.imports[mod_alias] = mod_name + // if mod_name !in p.table.imports { + p.table.imports << mod_name + p.ast_imports << import_node + // } + return import_node +} + +// import_syms parses the inner part of `import module { submod1, submod2 }` +fn (mut p Parser) import_syms(mut parent ast.Import) { + p.next() + pos_t := p.tok.position() + if p.tok.kind == .rcbr { // closed too early + p.error_with_pos('empty `$parent.mod` import set, remove `{}`', pos_t) + return + } + if p.tok.kind != .name { // not a valid inner name + p.error_with_pos('import syntax error, please specify a valid fn or type name', + pos_t) + return + } + for p.tok.kind == .name { + pos := p.tok.position() + alias := p.check_name() + p.imported_symbols[alias] = parent.mod + '.' + alias + // so we can work with this in fmt+checker + parent.syms << ast.ImportSymbol{ + pos: pos + name: alias + } + if p.tok.kind == .comma { // go again if more than one + p.next() + continue + } + if p.tok.kind == .rcbr { // finish if closing `}` is seen + break + } + } + if p.tok.kind != .rcbr { + p.error_with_pos('import syntax error, no closing `}`', p.tok.position()) + return + } + p.next() +} + +fn (mut p Parser) const_decl() ast.ConstDecl { + p.top_level_statement_start() + start_pos := p.tok.position() + is_pub := p.tok.kind == .key_pub + if is_pub { + p.next() + } + const_pos := p.tok.position() + p.check(.key_const) + is_block := p.tok.kind == .lpar + if is_block { + p.next() // ( + } + mut fields := []ast.ConstField{} + mut comments := []ast.Comment{} + for { + comments = p.eat_comments() + if is_block && p.tok.kind == .eof { + p.error('unexpected eof, expecting ´)´') + return ast.ConstDecl{} + } + if p.tok.kind == .rpar { + break + } + pos := p.tok.position() + name := p.check_name() + if util.contains_capital(name) { + p.warn_with_pos('const names cannot contain uppercase letters, use snake_case instead', + pos) + } + full_name := p.prepend_mod(name) + p.check(.assign) + if p.tok.kind == .key_fn { + p.error('const initializer fn literal is not a constant') + return ast.ConstDecl{} + } + if p.tok.kind == .eof { + p.error('unexpected eof, expecting an expression') + return ast.ConstDecl{} + } + expr := p.expr(0) + field := ast.ConstField{ + name: full_name + mod: p.mod + is_pub: is_pub + expr: expr + pos: pos.extend(expr.position()) + comments: comments + } + fields << field + p.table.global_scope.register(field) + comments = [] + if !is_block { + break + } + } + p.top_level_statement_end() + if is_block { + p.check(.rpar) + } + return ast.ConstDecl{ + pos: start_pos.extend_with_last_line(const_pos, p.prev_tok.line_nr) + fields: fields + is_pub: is_pub + end_comments: comments + is_block: is_block + } +} + +fn (mut p Parser) return_stmt() ast.Return { + first_pos := p.tok.position() + p.next() + // no return + mut comments := p.eat_comments() + if p.tok.kind == .rcbr { + return ast.Return{ + comments: comments + pos: first_pos + } + } + // return exprs + exprs, comments2 := p.expr_list() + comments << comments2 + end_pos := exprs.last().position() + return ast.Return{ + exprs: exprs + comments: comments + pos: first_pos.extend(end_pos) + } +} + +const ( + // modules which allow globals by default + global_enabled_mods = ['rand', 'sokol.sapp'] +) + +// left hand side of `=` or `:=` in `a,b,c := 1,2,3` +fn (mut p Parser) global_decl() ast.GlobalDecl { + if !p.pref.translated && !p.pref.is_livemain && !p.builtin_mod && !p.pref.building_v + && !p.pref.enable_globals && !p.pref.is_fmt && p.mod !in parser.global_enabled_mods { + p.error('use `v -enable-globals ...` to enable globals') + return ast.GlobalDecl{} + } + start_pos := p.tok.position() + p.check(.key_global) + is_block := p.tok.kind == .lpar + if is_block { + p.next() // ( + } + mut fields := []ast.GlobalField{} + mut comments := []ast.Comment{} + for { + comments = p.eat_comments() + if is_block && p.tok.kind == .eof { + p.error('unexpected eof, expecting ´)´') + return ast.GlobalDecl{} + } + if p.tok.kind == .rpar { + break + } + pos := p.tok.position() + name := p.check_name() + has_expr := p.tok.kind == .assign + mut expr := ast.empty_expr() + mut typ := ast.void_type + mut typ_pos := token.Position{} + if has_expr { + p.next() // = + expr = p.expr(0) + match expr { + ast.CastExpr { + typ = (expr as ast.CastExpr).typ + } + ast.StructInit { + typ = (expr as ast.StructInit).typ + } + ast.ArrayInit { + typ = (expr as ast.ArrayInit).typ + } + ast.ChanInit { + typ = (expr as ast.ChanInit).typ + } + ast.BoolLiteral, ast.IsRefType { + typ = ast.bool_type + } + ast.CharLiteral { + typ = ast.char_type + } + ast.FloatLiteral { + typ = ast.f64_type + } + ast.IntegerLiteral, ast.SizeOf { + typ = ast.int_type + } + ast.StringLiteral, ast.StringInterLiteral { + typ = ast.string_type + } + else { + // type will be deduced by checker + } + } + } else { + typ_pos = p.tok.position() + typ = p.parse_type() + } + field := ast.GlobalField{ + name: name + has_expr: has_expr + expr: expr + pos: pos + typ_pos: typ_pos + typ: typ + comments: comments + } + fields << field + p.table.global_scope.register(field) + comments = [] + if !is_block { + break + } + } + if is_block { + p.check(.rpar) + } + return ast.GlobalDecl{ + pos: start_pos.extend(p.prev_tok.position()) + mod: p.mod + fields: fields + end_comments: comments + is_block: is_block + } +} + +fn (mut p Parser) enum_decl() ast.EnumDecl { + p.top_level_statement_start() + is_pub := p.tok.kind == .key_pub + start_pos := p.tok.position() + if is_pub { + p.next() + } + p.check(.key_enum) + end_pos := p.tok.position() + enum_name := p.check_name() + if enum_name.len == 1 { + p.error_with_pos('single letter capital names are reserved for generic template types.', + end_pos) + return ast.EnumDecl{} + } + if enum_name in p.imported_symbols { + p.error_with_pos('cannot register enum `$enum_name`, this type was already imported', + end_pos) + return ast.EnumDecl{} + } + name := p.prepend_mod(enum_name) + p.check(.lcbr) + enum_decl_comments := p.eat_comments() + mut vals := []string{} + // mut default_exprs := []ast.Expr{} + mut fields := []ast.EnumField{} + for p.tok.kind != .eof && p.tok.kind != .rcbr { + pos := p.tok.position() + val := p.check_name() + vals << val + mut expr := ast.empty_expr() + mut has_expr := false + // p.warn('enum val $val') + if p.tok.kind == .assign { + p.next() + expr = p.expr(0) + has_expr = true + } + fields << ast.EnumField{ + name: val + pos: pos + expr: expr + has_expr: has_expr + comments: p.eat_comments(same_line: true) + next_comments: p.eat_comments() + } + } + p.top_level_statement_end() + p.check(.rcbr) + is_flag := p.attrs.contains('flag') + is_multi_allowed := p.attrs.contains('_allow_multiple_values') + if is_flag { + if fields.len > 32 { + p.error('when an enum is used as bit field, it must have a max of 32 fields') + return ast.EnumDecl{} + } + for f in fields { + if f.has_expr { + p.error_with_pos('when an enum is used as a bit field, you can not assign custom values', + f.pos) + return ast.EnumDecl{} + } + } + pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' } + p.scanner.codegen(' +// +[inline] $pubfn ( e &$enum_name) is_empty() bool { return int(*e) == 0 } +[inline] $pubfn ( e &$enum_name) has(flag $enum_name) bool { return (int(*e) & (int(flag))) != 0 } +[inline] $pubfn (mut e $enum_name) set(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) | (int(flag))) } } +[inline] $pubfn (mut e $enum_name) clear(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) & ~(int(flag))) } } +[inline] $pubfn (mut e $enum_name) toggle(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) ^ (int(flag))) } } +// +') + } + idx := p.table.register_type_symbol(ast.TypeSymbol{ + kind: .enum_ + name: name + cname: util.no_dots(name) + mod: p.mod + info: ast.Enum{ + vals: vals + is_flag: is_flag + is_multi_allowed: is_multi_allowed + } + is_public: is_pub + }) + if idx == -1 { + p.error_with_pos('cannot register enum `$name`, another type with this name exists', + end_pos) + } + + enum_decl := ast.EnumDecl{ + name: name + is_pub: is_pub + is_flag: is_flag + is_multi_allowed: is_multi_allowed + fields: fields + pos: start_pos.extend_with_last_line(end_pos, p.prev_tok.line_nr) + attrs: p.attrs + comments: enum_decl_comments + } + + p.table.register_enum_decl(enum_decl) + + return enum_decl +} + +fn (mut p Parser) type_decl() ast.TypeDecl { + start_pos := p.tok.position() + is_pub := p.tok.kind == .key_pub + if is_pub { + p.next() + } + p.check(.key_type) + end_pos := p.tok.position() + decl_pos := start_pos.extend(end_pos) + name_pos := p.tok.position() + name := p.check_name() + if name.len == 1 && name[0].is_capital() { + p.error_with_pos('single letter capital names are reserved for generic template types.', + decl_pos) + return ast.FnTypeDecl{} + } + if name in p.imported_symbols { + p.error_with_pos('cannot register alias `$name`, this type was already imported', + end_pos) + return ast.AliasTypeDecl{} + } + mut sum_variants := []ast.TypeNode{} + generic_types := p.parse_generic_type_list() + decl_pos_with_generics := decl_pos.extend(p.prev_tok.position()) + p.check(.assign) + mut type_pos := p.tok.position() + mut comments := []ast.Comment{} + if p.tok.kind == .key_fn { + // function type: `type mycallback = fn(string, int)` + fn_name := p.prepend_mod(name) + fn_type := p.parse_fn_type(fn_name) + p.table.get_type_symbol(fn_type).is_public = is_pub + type_pos = type_pos.extend(p.tok.position()) + comments = p.eat_comments(same_line: true) + return ast.FnTypeDecl{ + name: fn_name + is_pub: is_pub + typ: fn_type + pos: decl_pos + type_pos: type_pos + comments: comments + } + } + first_type := p.parse_type() // need to parse the first type before we can check if it's `type A = X | Y` + type_alias_pos := p.tok.position() + if p.tok.kind == .pipe { + mut type_end_pos := p.prev_tok.position() + type_pos = type_pos.extend(type_end_pos) + p.next() + sum_variants << ast.TypeNode{ + typ: first_type + pos: type_pos + } + // type SumType = A | B | c + for { + type_pos = p.tok.position() + variant_type := p.parse_type() + // TODO: needs to be its own var, otherwise TCC fails because of a known stack error + prev_tok := p.prev_tok + type_end_pos = prev_tok.position() + type_pos = type_pos.extend(type_end_pos) + sum_variants << ast.TypeNode{ + typ: variant_type + pos: type_pos + } + if p.tok.kind != .pipe { + break + } + p.check(.pipe) + } + variant_types := sum_variants.map(it.typ) + prepend_mod_name := p.prepend_mod(name) + typ := p.table.register_type_symbol(ast.TypeSymbol{ + kind: .sum_type + name: prepend_mod_name + cname: util.no_dots(prepend_mod_name) + mod: p.mod + info: ast.SumType{ + variants: variant_types + is_generic: generic_types.len > 0 + generic_types: generic_types + } + is_public: is_pub + }) + if typ == -1 { + p.error_with_pos('cannot register sum type `$name`, another type with this name exists', + name_pos) + return ast.SumTypeDecl{} + } + comments = p.eat_comments(same_line: true) + return ast.SumTypeDecl{ + name: name + typ: typ + is_pub: is_pub + variants: sum_variants + generic_types: generic_types + pos: decl_pos + comments: comments + } + } + // type MyType = int + if generic_types.len > 0 { + p.error_with_pos('generic type aliases are not yet implemented', decl_pos_with_generics) + return ast.AliasTypeDecl{} + } + parent_type := first_type + parent_sym := p.table.get_type_symbol(parent_type) + pidx := parent_type.idx() + p.check_for_impure_v(parent_sym.language, decl_pos) + prepend_mod_name := p.prepend_mod(name) + idx := p.table.register_type_symbol(ast.TypeSymbol{ + kind: .alias + name: prepend_mod_name + cname: util.no_dots(prepend_mod_name) + mod: p.mod + parent_idx: pidx + info: ast.Alias{ + parent_type: parent_type + language: parent_sym.language + } + is_public: is_pub + }) + type_end_pos := p.prev_tok.position() + if idx == -1 { + p.error_with_pos('cannot register alias `$name`, another type with this name exists', + name_pos) + return ast.AliasTypeDecl{} + } + if idx == pidx { + p.error_with_pos('a type alias can not refer to itself: $name', decl_pos.extend(type_alias_pos)) + return ast.AliasTypeDecl{} + } + comments = p.eat_comments(same_line: true) + return ast.AliasTypeDecl{ + name: name + is_pub: is_pub + parent_type: parent_type + type_pos: type_pos.extend(type_end_pos) + pos: decl_pos + comments: comments + } +} + +fn (mut p Parser) assoc() ast.Assoc { + var_name := p.check_name() + pos := p.tok.position() + mut v := p.scope.find_var(var_name) or { + p.error('unknown variable `$var_name`') + return ast.Assoc{ + scope: 0 + } + } + v.is_used = true + mut fields := []string{} + mut vals := []ast.Expr{} + p.check(.pipe) + for p.tok.kind != .eof { + fields << p.check_name() + p.check(.colon) + expr := p.expr(0) + vals << expr + if p.tok.kind == .comma { + p.next() + } + if p.tok.kind == .rcbr { + break + } + } + return ast.Assoc{ + var_name: var_name + fields: fields + exprs: vals + pos: pos + scope: p.scope + } +} + +fn (p &Parser) new_true_expr() ast.Expr { + return ast.BoolLiteral{ + val: true + pos: p.tok.position() + } +} + +[noreturn] +fn verror(s string) { + util.verror('parser error', s) +} + +fn (mut p Parser) top_level_statement_start() { + if p.comments_mode == .toplevel_comments { + p.scanner.set_is_inside_toplevel_statement(true) + p.rewind_scanner_to_current_token_in_new_mode() + $if debugscanner ? { + eprintln('>> p.top_level_statement_start | tidx:${p.tok.tidx:-5} | p.tok.kind: ${p.tok.kind:-10} | p.tok.lit: $p.tok.lit $p.peek_tok.lit ${p.peek_token(2).lit} ${p.peek_token(3).lit} ...') + } + } +} + +fn (mut p Parser) top_level_statement_end() { + if p.comments_mode == .toplevel_comments { + p.scanner.set_is_inside_toplevel_statement(false) + p.rewind_scanner_to_current_token_in_new_mode() + $if debugscanner ? { + eprintln('>> p.top_level_statement_end | tidx:${p.tok.tidx:-5} | p.tok.kind: ${p.tok.kind:-10} | p.tok.lit: $p.tok.lit $p.peek_tok.lit ${p.peek_token(2).lit} ${p.peek_token(3).lit} ...') + } + } +} + +fn (mut p Parser) rewind_scanner_to_current_token_in_new_mode() { + // Go back and rescan some tokens, ensuring that the parser's + // lookahead buffer p.peek_tok .. p.peek_token(3), will now contain + // the correct tokens (possible comments), for the new mode + // This refilling of the lookahead buffer is needed for the + // .toplevel_comments parsing mode. + tidx := p.tok.tidx + p.scanner.set_current_tidx(tidx - 5) + no_token := token.Token{} + p.prev_tok = no_token + p.tok = no_token + p.peek_tok = no_token // requires 2 calls p.next() or check p.tok.kind != token.Kind.unknown + p.next() + for { + p.next() + // eprintln('rewinding to ${p.tok.tidx:5} | goal: ${tidx:5}') + if tidx == p.tok.tidx { + break + } + } +} + +pub fn (mut p Parser) mark_var_as_used(varname string) bool { + if obj := p.scope.find(varname) { + match mut obj { + ast.Var { + obj.is_used = true + return true + } + else {} + } + } + return false +} + +fn (mut p Parser) unsafe_stmt() ast.Stmt { + mut pos := p.tok.position() + p.next() + if p.tok.kind != .lcbr { + return p.error_with_pos('please use `unsafe {`', p.tok.position()) + } + p.next() + if p.inside_unsafe { + return p.error_with_pos('already inside `unsafe` block', pos) + } + if p.tok.kind == .rcbr { + // `unsafe {}` + pos.update_last_line(p.tok.line_nr) + p.next() + return ast.Block{ + is_unsafe: true + pos: pos + } + } + p.inside_unsafe = true + p.open_scope() // needed in case of `unsafe {stmt}` + defer { + p.inside_unsafe = false + p.close_scope() + } + stmt := p.stmt(false) + if p.tok.kind == .rcbr { + if stmt is ast.ExprStmt { + // `unsafe {expr}` + if stmt.expr.is_expr() { + p.next() + pos.update_last_line(p.prev_tok.line_nr) + ue := ast.UnsafeExpr{ + expr: stmt.expr + pos: pos + } + // parse e.g. `unsafe {expr}.foo()` + expr := p.expr_with_left(ue, 0, p.is_stmt_ident) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + } + } + // unsafe {stmts} + mut stmts := [stmt] + for p.tok.kind != .rcbr { + stmts << p.stmt(false) + } + p.next() + pos.update_last_line(p.tok.line_nr) + return ast.Block{ + stmts: stmts + is_unsafe: true + pos: pos + } +} + +fn (mut p Parser) trace(fbase string, message string) { + if p.file_base == fbase { + println('> p.trace | ${fbase:-10s} | $message') + } +} diff --git a/v_windows/v/vlib/v/parser/sql.v b/v_windows/v/vlib/v/parser/sql.v new file mode 100644 index 0000000..28a7157 --- /dev/null +++ b/v_windows/v/vlib/v/parser/sql.v @@ -0,0 +1,262 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast + +fn (mut p Parser) sql_expr() ast.Expr { + // `sql db {` + pos := p.tok.position() + p.check_name() + db_expr := p.check_expr(0) or { + p.error_with_pos('invalid expression: unexpected $p.tok, expecting database', + p.tok.position()) + } + p.check(.lcbr) + p.check(.key_select) + n := p.check_name() + is_count := n == 'count' + mut typ := ast.void_type + if is_count { + p.check_name() // from + typ = ast.int_type + } + table_pos := p.tok.position() + table_type := p.parse_type() // `User` + mut where_expr := ast.empty_expr() + has_where := p.tok.kind == .name && p.tok.lit == 'where' + mut query_one := false // one object is returned, not an array + if has_where { + p.next() + where_expr = p.expr(0) + // `id == x` means that a single object is returned + if !is_count && where_expr is ast.InfixExpr { + e := where_expr as ast.InfixExpr + if e.op == .eq && e.left is ast.Ident { + if e.left.name == 'id' { + query_one = true + } + } + } + } + mut has_limit := false + mut limit_expr := ast.empty_expr() + mut has_offset := false + mut offset_expr := ast.empty_expr() + mut has_order := false + mut order_expr := ast.empty_expr() + mut has_desc := false + if p.tok.kind == .name && p.tok.lit == 'order' { + p.check_name() // `order` + order_pos := p.tok.position() + if p.tok.kind == .name && p.tok.lit == 'by' { + p.check_name() // `by` + } else { + return p.error_with_pos('use `order by` in ORM queries', order_pos) + } + has_order = true + order_expr = p.expr(0) + if p.tok.kind == .name && p.tok.lit == 'desc' { + p.check_name() // `desc` + has_desc = true + } + } + if p.tok.kind == .name && p.tok.lit == 'limit' { + // `limit 1` means that a single object is returned + p.check_name() // `limit` + if p.tok.kind == .number && p.tok.lit == '1' { + query_one = true + } + has_limit = true + limit_expr = p.expr(0) + } + if p.tok.kind == .name && p.tok.lit == 'offset' { + p.check_name() // `offset` + has_offset = true + offset_expr = p.expr(0) + } + if !query_one && !is_count { + // return an array + typ = ast.new_type(p.table.find_or_register_array(table_type)) + } else if !is_count { + // return a single object + // TODO optional + // typ = table_type.set_flag(.optional) + typ = table_type + } + p.check(.rcbr) + return ast.SqlExpr{ + is_count: is_count + typ: typ + db_expr: db_expr + where_expr: where_expr + has_where: has_where + has_limit: has_limit + limit_expr: limit_expr + has_offset: has_offset + offset_expr: offset_expr + has_order: has_order + order_expr: order_expr + has_desc: has_desc + is_array: !query_one + pos: pos.extend(p.prev_tok.position()) + table_expr: ast.TypeNode{ + typ: table_type + pos: table_pos + } + } +} + +// insert user into User +// update User set nr_oders=nr_orders+1 where id == user_id +fn (mut p Parser) sql_stmt() ast.SqlStmt { + mut pos := p.tok.position() + p.inside_match = true + defer { + p.inside_match = false + } + // `sql db {` + p.check_name() + db_expr := p.check_expr(0) or { + p.error_with_pos('invalid expression: unexpected $p.tok, expecting database', + p.tok.position()) + } + // println(typeof(db_expr)) + p.check(.lcbr) + + mut lines := []ast.SqlStmtLine{} + + for p.tok.kind != .rcbr { + lines << p.parse_sql_stmt_line() + } + + p.next() + pos.last_line = p.prev_tok.line_nr + return ast.SqlStmt{ + pos: pos.extend(p.prev_tok.position()) + db_expr: db_expr + lines: lines + } +} + +fn (mut p Parser) parse_sql_stmt_line() ast.SqlStmtLine { + mut n := p.check_name() // insert + pos := p.tok.position() + mut kind := ast.SqlStmtKind.insert + if n == 'delete' { + kind = .delete + } else if n == 'update' { + kind = .update + } else if n == 'create' { + kind = .create + table := p.check_name() + if table != 'table' { + p.error('expected `table` got `$table`') + return ast.SqlStmtLine{} + } + typ := p.parse_type() + typ_pos := p.tok.position() + return ast.SqlStmtLine{ + kind: kind + pos: pos.extend(p.prev_tok.position()) + table_expr: ast.TypeNode{ + typ: typ + pos: typ_pos + } + } + } else if n == 'drop' { + kind = .drop + table := p.check_name() + if table != 'table' { + p.error('expected `table` got `$table`') + return ast.SqlStmtLine{} + } + typ := p.parse_type() + typ_pos := p.tok.position() + return ast.SqlStmtLine{ + kind: kind + pos: pos.extend(p.prev_tok.position()) + table_expr: ast.TypeNode{ + typ: typ + pos: typ_pos + } + } + } + mut inserted_var_name := '' + mut table_type := ast.Type(0) + if kind != .delete { + if kind == .update { + table_type = p.parse_type() + } else if kind == .insert { + expr := p.expr(0) + if expr is ast.Ident { + inserted_var_name = expr.name + } else { + p.error('can only insert variables') + return ast.SqlStmtLine{} + } + } + } + n = p.check_name() // into + mut updated_columns := []string{} + mut update_exprs := []ast.Expr{cap: 5} + if kind == .insert && n != 'into' { + p.error('expecting `into`') + return ast.SqlStmtLine{} + } else if kind == .update { + if n != 'set' { + p.error('expecting `set`') + return ast.SqlStmtLine{} + } + for { + column := p.check_name() + updated_columns << column + p.check(.assign) + update_exprs << p.expr(0) + if p.tok.kind == .comma { + p.check(.comma) + } else { + break + } + } + } else if kind == .delete && n != 'from' { + p.error('expecting `from`') + return ast.SqlStmtLine{} + } + + mut table_pos := p.tok.position() + mut where_expr := ast.empty_expr() + if kind == .insert { + table_pos = p.tok.position() + table_type = p.parse_type() + } else if kind == .update { + p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } + where_expr = p.expr(0) + } else if kind == .delete { + table_pos = p.tok.position() + table_type = p.parse_type() + p.check_sql_keyword('where') or { return ast.SqlStmtLine{} } + where_expr = p.expr(0) + } + return ast.SqlStmtLine{ + table_expr: ast.TypeNode{ + typ: table_type + pos: table_pos + } + object_var_name: inserted_var_name + pos: pos + updated_columns: updated_columns + update_exprs: update_exprs + kind: kind + where_expr: where_expr + } +} + +fn (mut p Parser) check_sql_keyword(name string) ?bool { + if p.check_name() != name { + p.error('orm: expecting `$name`') + return none + } + return true +} diff --git a/v_windows/v/vlib/v/parser/struct.v b/v_windows/v/vlib/v/parser/struct.v new file mode 100644 index 0000000..7cd4e60 --- /dev/null +++ b/v_windows/v/vlib/v/parser/struct.v @@ -0,0 +1,622 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.ast +import v.token +import v.util + +fn (mut p Parser) struct_decl() ast.StructDecl { + p.top_level_statement_start() + // save attributes, they will be changed later in fields + attrs := p.attrs + p.attrs = [] + start_pos := p.tok.position() + is_pub := p.tok.kind == .key_pub + if is_pub { + p.next() + } + is_union := p.tok.kind == .key_union + if p.tok.kind == .key_struct { + p.next() + } else { + p.check(.key_union) + } + language := if p.tok.lit == 'C' && p.peek_tok.kind == .dot { + ast.Language.c + } else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot { + ast.Language.js + } else { + ast.Language.v + } + if language != .v { + p.next() // C || JS + p.next() // . + } + name_pos := p.tok.position() + p.check_for_impure_v(language, name_pos) + mut name := p.check_name() + // defer { + // if name.contains('App') { + // println('end of struct decl $name') + // } + // } + if name.len == 1 && name[0].is_capital() { + p.error_with_pos('single letter capital names are reserved for generic template types.', + name_pos) + return ast.StructDecl{} + } + generic_types := p.parse_generic_type_list() + no_body := p.tok.kind != .lcbr + if language == .v && no_body { + p.error('`$p.tok.lit` lacks body') + return ast.StructDecl{} + } + if language == .v && !p.builtin_mod && name.len > 0 && !name[0].is_capital() + && !p.pref.translated { + p.error_with_pos('struct name `$name` must begin with capital letter', name_pos) + return ast.StructDecl{} + } + if name.len == 1 { + p.error_with_pos('struct names must have more than one character', name_pos) + return ast.StructDecl{} + } + if name in p.imported_symbols { + p.error_with_pos('cannot register struct `$name`, this type was already imported', + name_pos) + return ast.StructDecl{} + } + mut orig_name := name + if language == .c { + name = 'C.$name' + orig_name = name + } else if language == .js { + name = 'JS.$name' + orig_name = name + } else { + name = p.prepend_mod(name) + } + mut ast_fields := []ast.StructField{} + mut fields := []ast.StructField{} + mut embed_types := []ast.Type{} + mut embeds := []ast.Embed{} + mut embed_field_names := []string{} + mut mut_pos := -1 + mut pub_pos := -1 + mut pub_mut_pos := -1 + mut global_pos := -1 + mut module_pos := -1 + mut is_field_mut := false + mut is_field_pub := false + mut is_field_global := false + mut last_line := p.prev_tok.position().line_nr + 1 + mut end_comments := []ast.Comment{} + if !no_body { + p.check(.lcbr) + for p.tok.kind != .rcbr { + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + if p.tok.kind == .rcbr { + break + } + } + if p.tok.kind == .rcbr { + end_comments = comments.clone() + break + } + if p.tok.kind == .key_pub { + p.next() + if p.tok.kind == .key_mut { + if pub_mut_pos != -1 { + p.error('redefinition of `pub mut` section') + return ast.StructDecl{} + } + p.next() + pub_mut_pos = ast_fields.len + is_field_pub = true + is_field_mut = true + is_field_global = false + } else { + if pub_pos != -1 { + p.error('redefinition of `pub` section') + return ast.StructDecl{} + } + pub_pos = ast_fields.len + is_field_pub = true + is_field_mut = false + is_field_global = false + } + p.check(.colon) + } else if p.tok.kind == .key_mut { + if mut_pos != -1 { + p.error('redefinition of `mut` section') + return ast.StructDecl{} + } + p.next() + p.check(.colon) + mut_pos = ast_fields.len + is_field_pub = false + is_field_mut = true + is_field_global = false + } else if p.tok.kind == .key_global { + if global_pos != -1 { + p.error('redefinition of `global` section') + return ast.StructDecl{} + } + p.next() + p.check(.colon) + global_pos = ast_fields.len + is_field_pub = true + is_field_mut = true + is_field_global = true + } else if p.tok.kind == .key_module { + if module_pos != -1 { + p.error('redefinition of `module` section') + return ast.StructDecl{} + } + p.next() + p.check(.colon) + module_pos = ast_fields.len + is_field_pub = false + is_field_mut = false + is_field_global = false + } + for p.tok.kind == .comment { + comments << p.comment() + if p.tok.kind == .rcbr { + break + } + } + field_start_pos := p.tok.position() + is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) + || p.peek_tok.kind == .dot) && language == .v && p.peek_tok.kind != .key_fn + is_on_top := ast_fields.len == 0 && !(is_field_mut || is_field_global) + mut field_name := '' + mut typ := ast.Type(0) + mut type_pos := token.Position{} + mut field_pos := token.Position{} + if is_embed { + // struct embedding + type_pos = p.tok.position() + typ = p.parse_type() + ecomments := p.eat_comments() + type_pos = type_pos.extend(p.prev_tok.position()) + if !is_on_top { + p.error_with_pos('struct embedding must be declared at the beginning of the struct body', + type_pos) + return ast.StructDecl{} + } + sym := p.table.get_type_symbol(typ) + if typ in embed_types { + p.error_with_pos('cannot embed `$sym.name` more than once', type_pos) + return ast.StructDecl{} + } + field_name = sym.embed_name() + if field_name in embed_field_names { + p.error_with_pos('duplicate field `$field_name`', type_pos) + return ast.StructDecl{} + } + embed_field_names << field_name + embed_types << typ + embeds << ast.Embed{ + typ: typ + pos: type_pos + comments: ecomments + } + } else { + // struct field + field_name = p.check_name() + for p.tok.kind == .comment { + comments << p.comment() + if p.tok.kind == .rcbr { + break + } + } + typ = p.parse_type() + if typ.idx() == 0 { + // error is set in parse_type + return ast.StructDecl{} + } + type_pos = p.prev_tok.position() + field_pos = field_start_pos.extend(type_pos) + } + // Comments after type (same line) + comments << p.eat_comments() + if p.tok.kind == .lsbr { + // attrs are stored in `p.attrs` + p.attributes() + } + mut default_expr := ast.empty_expr() + mut has_default_expr := false + if !is_embed { + if p.tok.kind == .assign { + // Default value + p.next() + default_expr = p.expr(0) + match mut default_expr { + ast.EnumVal { default_expr.typ = typ } + // TODO: implement all types?? + else {} + } + has_default_expr = true + comments << p.eat_comments() + } + ast_fields << ast.StructField{ + name: field_name + typ: typ + pos: field_pos + type_pos: type_pos + comments: comments + default_expr: default_expr + has_default_expr: has_default_expr + attrs: p.attrs + is_pub: is_embed || is_field_pub + is_mut: is_embed || is_field_mut + is_global: is_field_global + } + } + // save embeds as table fields too, it will be used in generation phase + fields << ast.StructField{ + name: field_name + typ: typ + pos: field_pos + type_pos: type_pos + comments: comments + default_expr: default_expr + has_default_expr: has_default_expr + attrs: p.attrs + is_pub: is_embed || is_field_pub + is_mut: is_embed || is_field_mut + is_global: is_field_global + } + p.attrs = [] + } + p.top_level_statement_end() + last_line = p.tok.line_nr + p.check(.rcbr) + } + t := ast.TypeSymbol{ + kind: .struct_ + language: language + name: name + cname: util.no_dots(name) + mod: p.mod + info: ast.Struct{ + embeds: embed_types + fields: fields + is_typedef: attrs.contains('typedef') + is_union: is_union + is_heap: attrs.contains('heap') + is_generic: generic_types.len > 0 + generic_types: generic_types + attrs: attrs + } + is_public: is_pub + } + if p.table.has_deep_child_no_ref(&t, name) { + p.error_with_pos('invalid recursive struct `$orig_name`', name_pos) + return ast.StructDecl{} + } + mut ret := 0 + // println('reg type symbol $name mod=$p.mod') + ret = p.table.register_type_symbol(t) + // allow duplicate c struct declarations + if ret == -1 && language != .c { + p.error_with_pos('cannot register struct `$name`, another type with this name exists', + name_pos) + return ast.StructDecl{} + } + p.expr_mod = '' + return ast.StructDecl{ + name: name + is_pub: is_pub + fields: ast_fields + pos: start_pos.extend_with_last_line(name_pos, last_line) + mut_pos: mut_pos + pub_pos: pub_pos + pub_mut_pos: pub_mut_pos + global_pos: global_pos + module_pos: module_pos + language: language + is_union: is_union + attrs: attrs + end_comments: end_comments + generic_types: generic_types + embeds: embeds + } +} + +fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { + first_pos := (if short_syntax && p.prev_tok.kind == .lcbr { p.prev_tok } else { p.tok }).position() + typ := if short_syntax { ast.void_type } else { p.parse_type() } + p.expr_mod = '' + // sym := p.table.get_type_symbol(typ) + // p.warn('struct init typ=$sym.name') + if !short_syntax { + p.check(.lcbr) + } + pre_comments := p.eat_comments() + mut fields := []ast.StructInitField{} + mut i := 0 + no_keys := p.peek_tok.kind != .colon && p.tok.kind != .rcbr && p.tok.kind != .ellipsis // `Vec{a,b,c} + saved_is_amp := p.is_amp + p.is_amp = false + mut update_expr := ast.empty_expr() + mut update_expr_comments := []ast.Comment{} + mut has_update_expr := false + for p.tok.kind !in [.rcbr, .rpar, .eof] { + mut field_name := '' + mut expr := ast.empty_expr() + mut field_pos := token.Position{} + mut first_field_pos := token.Position{} + mut comments := []ast.Comment{} + mut nline_comments := []ast.Comment{} + is_update_expr := fields.len == 0 && p.tok.kind == .ellipsis + if no_keys { + // name will be set later in checker + expr = p.expr(0) + field_pos = expr.position() + first_field_pos = field_pos + comments = p.eat_comments(same_line: true) + } else if is_update_expr { + // struct updating syntax; f2 := Foo{ ...f, name: 'f2' } + p.check(.ellipsis) + update_expr = p.expr(0) + update_expr_comments << p.eat_comments(same_line: true) + has_update_expr = true + } else { + first_field_pos = p.tok.position() + field_name = p.check_name() + p.check(.colon) + expr = p.expr(0) + comments = p.eat_comments(same_line: true) + last_field_pos := expr.position() + field_len := if last_field_pos.len > 0 { + last_field_pos.pos - first_field_pos.pos + last_field_pos.len + } else { + first_field_pos.len + 1 + } + field_pos = token.Position{ + line_nr: first_field_pos.line_nr + pos: first_field_pos.pos + len: field_len + col: first_field_pos.col + } + } + i++ + if p.tok.kind == .comma { + p.next() + } + comments << p.eat_comments(same_line: true) + nline_comments << p.eat_comments() + if !is_update_expr { + fields << ast.StructInitField{ + name: field_name + expr: expr + pos: field_pos + name_pos: first_field_pos + comments: comments + next_comments: nline_comments + parent_type: typ + } + } + } + if !short_syntax { + p.check(.rcbr) + } + p.is_amp = saved_is_amp + return ast.StructInit{ + unresolved: typ.has_flag(.generic) + typ: typ + fields: fields + update_expr: update_expr + update_expr_comments: update_expr_comments + has_update_expr: has_update_expr + name_pos: first_pos + pos: first_pos.extend(if short_syntax { p.tok.position() } else { p.prev_tok.position() }) + is_short: no_keys + pre_comments: pre_comments + } +} + +fn (mut p Parser) interface_decl() ast.InterfaceDecl { + p.top_level_statement_start() + mut pos := p.tok.position() + is_pub := p.tok.kind == .key_pub + if is_pub { + p.next() + } + p.next() // `interface` + language := if p.tok.lit == 'C' && p.peek_tok.kind == .dot { + ast.Language.c + } else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot { + ast.Language.js + } else { + ast.Language.v + } + if language != .v { + p.next() // C || JS + p.next() // . + } + name_pos := p.tok.position() + p.check_for_impure_v(language, name_pos) + modless_name := p.check_name() + interface_name := p.prepend_mod(modless_name).clone() + generic_types := p.parse_generic_type_list() + // println('interface decl $interface_name') + p.check(.lcbr) + pre_comments := p.eat_comments() + if modless_name in p.imported_symbols { + p.error_with_pos('cannot register interface `$interface_name`, this type was already imported', + name_pos) + return ast.InterfaceDecl{} + } + // Declare the type + reg_idx := p.table.register_type_symbol( + is_public: is_pub + kind: .interface_ + name: interface_name + cname: util.no_dots(interface_name) + mod: p.mod + info: ast.Interface{ + types: [] + is_generic: generic_types.len > 0 + generic_types: generic_types + } + ) + if reg_idx == -1 { + p.error_with_pos('cannot register interface `$interface_name`, another type with this name exists', + name_pos) + return ast.InterfaceDecl{} + } + typ := ast.new_type(reg_idx) + mut ts := p.table.get_type_symbol(typ) + mut info := ts.info as ast.Interface + // if methods were declared before, it's an error, ignore them + ts.methods = []ast.Fn{cap: 20} + // Parse fields or methods + mut fields := []ast.StructField{cap: 20} + mut methods := []ast.FnDecl{cap: 20} + mut is_mut := false + mut mut_pos := -1 + mut ifaces := []ast.InterfaceEmbedding{} + for p.tok.kind != .rcbr && p.tok.kind != .eof { + if p.tok.kind == .name && p.tok.lit.len > 0 && p.tok.lit[0].is_capital() { + iface_pos := p.tok.position() + iface_name := p.tok.lit + iface_type := p.parse_type() + comments := p.eat_comments() + ifaces << ast.InterfaceEmbedding{ + name: iface_name + typ: iface_type + pos: iface_pos + comments: comments + } + if p.tok.kind == .rcbr { + break + } + continue + } + if p.tok.kind == .key_mut { + if is_mut { + p.error_with_pos('redefinition of `mut` section', p.tok.position()) + return ast.InterfaceDecl{} + } + p.next() + p.check(.colon) + is_mut = true + mut_pos = fields.len + } + if p.peek_tok.kind == .lpar { + method_start_pos := p.tok.position() + line_nr := p.tok.line_nr + name := p.check_name() + + if name in ['type_name', 'type_idx'] { + p.error_with_pos('cannot override built-in method `$name`', method_start_pos) + return ast.InterfaceDecl{} + } + if ts.has_method(name) { + p.error_with_pos('duplicate method `$name`', method_start_pos) + return ast.InterfaceDecl{} + } + if language == .v && util.contains_capital(name) { + p.error('interface methods cannot contain uppercase letters, use snake_case instead') + return ast.InterfaceDecl{} + } + // field_names << name + args2, _, is_variadic := p.fn_args() // TODO merge ast.Param and ast.Arg to avoid this + mut args := [ast.Param{ + name: 'x' + is_mut: is_mut + typ: typ + is_hidden: true + }] + args << args2 + mut method := ast.FnDecl{ + name: name + mod: p.mod + params: args + file: p.file_name + return_type: ast.void_type + is_variadic: is_variadic + is_pub: true + pos: method_start_pos.extend(p.prev_tok.position()) + scope: p.scope + } + if p.tok.kind.is_start_of_type() && p.tok.line_nr == line_nr { + method.return_type_pos = p.tok.position() + method.return_type = p.parse_type() + method.return_type_pos = method.return_type_pos.extend(p.tok.position()) + method.pos = method.pos.extend(method.return_type_pos) + } + mcomments := p.eat_comments(same_line: true) + mnext_comments := p.eat_comments() + method.comments = mcomments + method.next_comments = mnext_comments + methods << method + // println('register method $name') + tmethod := ast.Fn{ + name: name + params: args + pos: method.pos + return_type: method.return_type + is_variadic: is_variadic + is_pub: true + } + ts.register_method(tmethod) + info.methods << tmethod + } else { + // interface fields + field_pos := p.tok.position() + field_name := p.check_name() + mut type_pos := p.tok.position() + field_typ := p.parse_type() + type_pos = type_pos.extend(p.prev_tok.position()) + mut comments := []ast.Comment{} + for p.tok.kind == .comment { + comments << p.comment() + if p.tok.kind == .rcbr { + break + } + } + fields << ast.StructField{ + name: field_name + pos: field_pos + type_pos: type_pos + typ: field_typ + comments: comments + is_pub: true + } + info.fields << ast.StructField{ + name: field_name + typ: field_typ + is_pub: true + is_mut: is_mut + } + } + } + info.ifaces = ifaces.map(it.typ) + ts.info = info + p.top_level_statement_end() + p.check(.rcbr) + pos = pos.extend_with_last_line(p.prev_tok.position(), p.prev_tok.line_nr) + res := ast.InterfaceDecl{ + name: interface_name + language: language + typ: typ + fields: fields + methods: methods + ifaces: ifaces + is_pub: is_pub + pos: pos + pre_comments: pre_comments + generic_types: generic_types + mut_pos: mut_pos + name_pos: name_pos + } + p.table.register_interface(res) + return res +} diff --git a/v_windows/v/vlib/v/parser/tests/README.md b/v_windows/v/vlib/v/parser/tests/README.md new file mode 100644 index 0000000..7547a55 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/README.md @@ -0,0 +1 @@ +Put here tests, ensuring that the v's parser errors for certain situations. diff --git a/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.out b/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.out new file mode 100644 index 0000000..fe42d51 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/anon_fn_return_type.vv:7:22: error: expected return type, not string `hi` for anonymous function + 5 | _ = fn (name string) flag.Flag + 6 | _ = fn (name string) flag.Flag {return flag.Flag{}} + 7 | _ = fn (name string) "hi" + name + | ~~~~ + 8 | diff --git a/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.vv b/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.vv new file mode 100644 index 0000000..fc50fa0 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/anon_fn_return_type.vv @@ -0,0 +1,8 @@ +import flag + +_ = fn (name string) +_ = fn (name string) {} +_ = fn (name string) flag.Flag +_ = fn (name string) flag.Flag {return flag.Flag{}} +_ = fn (name string) "hi" + name + diff --git a/v_windows/v/vlib/v/parser/tests/anon_unused_param.out b/v_windows/v/vlib/v/parser/tests/anon_unused_param.out new file mode 100644 index 0000000..a12769b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/anon_unused_param.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/anon_unused_param.vv:1:9: error: use `_` to name an unused parameter + 1 | _ = fn (int){} + | ~~~ + 2 | diff --git a/v_windows/v/vlib/v/parser/tests/anon_unused_param.vv b/v_windows/v/vlib/v/parser/tests/anon_unused_param.vv new file mode 100644 index 0000000..c285cea --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/anon_unused_param.vv @@ -0,0 +1,2 @@ +_ = fn (int){} + diff --git a/v_windows/v/vlib/v/parser/tests/array_init.out b/v_windows/v/vlib/v/parser/tests/array_init.out new file mode 100644 index 0000000..75bdbe3 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/array_init.out @@ -0,0 +1,12 @@ +vlib/v/parser/tests/array_init.vv:2:7: warning: use `x := []Type{}` instead of `x := []Type` + 1 | fn main() { + 2 | _ := []int + | ~~~~~ + 3 | _ := [1]int + 4 | } +vlib/v/parser/tests/array_init.vv:3:7: warning: use e.g. `x := [1]Type{}` instead of `x := [1]Type` + 1 | fn main() { + 2 | _ := []int + 3 | _ := [1]int + | ~~~~~~ + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/array_init.vv b/v_windows/v/vlib/v/parser/tests/array_init.vv new file mode 100644 index 0000000..4bbfb88 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/array_init.vv @@ -0,0 +1,4 @@ +fn main() { + _ := []int + _ := [1]int +} diff --git a/v_windows/v/vlib/v/parser/tests/array_pos_err.out b/v_windows/v/vlib/v/parser/tests/array_pos_err.out new file mode 100644 index 0000000..8593054 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/array_pos_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/array_pos_err.vv:2:2: error: expression evaluated but not used + 1 | fn main() { + 2 | '' in [] + | ~~~~~~~~ + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/array_pos_err.vv b/v_windows/v/vlib/v/parser/tests/array_pos_err.vv new file mode 100644 index 0000000..0988140 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/array_pos_err.vv @@ -0,0 +1,3 @@ +fn main() { + '' in [] +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.out b/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.out new file mode 100644 index 0000000..89972cd --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/c_struct_no_embed.vv:7:1: error: expecting type declaration + 5 | struct C.Unknown { + 6 | Foo + 7 | } + | ^ \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.vv b/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.vv new file mode 100644 index 0000000..020dce7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/c_struct_no_embed.vv @@ -0,0 +1,7 @@ +struct Foo { + x int +} + +struct C.Unknown { + Foo +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/closure_not_declared.out b/v_windows/v/vlib/v/parser/tests/closure_not_declared.out new file mode 100644 index 0000000..a90ceac --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_not_declared.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/closure_not_declared.vv:4:9: error: undefined ident: `a` + 2 | a := 1 + 3 | f := fn () { + 4 | print(a) + | ^ + 5 | } + 6 | f() diff --git a/v_windows/v/vlib/v/parser/tests/closure_not_declared.vv b/v_windows/v/vlib/v/parser/tests/closure_not_declared.vv new file mode 100644 index 0000000..b0ed7a5 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_not_declared.vv @@ -0,0 +1,8 @@ +fn my_fn() { + a := 1 + f := fn () { + print(a) + } + f() + _ = a +} diff --git a/v_windows/v/vlib/v/parser/tests/closure_not_used.out b/v_windows/v/vlib/v/parser/tests/closure_not_used.out new file mode 100644 index 0000000..2cd7a06 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_not_used.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/closure_not_used.vv:3:11: error: unused variable: `a` + 1 | fn my_fn() { + 2 | a := 1 + 3 | f := fn [a] () { + | ^ + 4 | print("hello") + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/closure_not_used.vv b/v_windows/v/vlib/v/parser/tests/closure_not_used.vv new file mode 100644 index 0000000..076f53a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_not_used.vv @@ -0,0 +1,7 @@ +fn my_fn() { + a := 1 + f := fn [a] () { + print("hello") + } + f() +} diff --git a/v_windows/v/vlib/v/parser/tests/closure_undefined_var.out b/v_windows/v/vlib/v/parser/tests/closure_undefined_var.out new file mode 100644 index 0000000..0eeeb82 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_undefined_var.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/closure_undefined_var.vv:2:11: error: undefined ident: `dont_exist` + 1 | fn my_fn() { + 2 | f := fn [dont_exist] () { + | ~~~~~~~~~~ + 3 | print(dont_exist) + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/closure_undefined_var.vv b/v_windows/v/vlib/v/parser/tests/closure_undefined_var.vv new file mode 100644 index 0000000..3edb445 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/closure_undefined_var.vv @@ -0,0 +1,6 @@ +fn my_fn() { + f := fn [dont_exist] () { + print(dont_exist) + } + f() +} diff --git a/v_windows/v/vlib/v/parser/tests/const_index.out b/v_windows/v/vlib/v/parser/tests/const_index.out new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_index.out @@ -0,0 +1 @@ + diff --git a/v_windows/v/vlib/v/parser/tests/const_index.vv b/v_windows/v/vlib/v/parser/tests/const_index.vv new file mode 100644 index 0000000..c629502 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_index.vv @@ -0,0 +1,22 @@ +// must not parse 4[deprecated] as index expression +const x = 4 +[deprecated] +fn g() { + a := [3] + // indexing is currently allowed on next line + _ = a + [0] +} + +const y = 5 +[deprecated] +fn h() {} + +const z = 6 +[typedef] +struct C.Foo{} + +// test implicit main allows indexing on next line +a := [3] +_ := a +[0] diff --git a/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.out b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.out new file mode 100644 index 0000000..e3d0186 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/const_missing_rpar_a.vv:3:1: error: unexpected eof, expecting ´)´ + 1 | const ( + 2 | a = 5 diff --git a/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.vv b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.vv new file mode 100644 index 0000000..38043f9 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_a.vv @@ -0,0 +1,2 @@ +const ( + a = 5 diff --git a/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.out b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.out new file mode 100644 index 0000000..4bef87e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/const_missing_rpar_b.vv:4:1: error: unexpected eof, expecting ´)´ + 2 | a = 5 + 3 | // foo diff --git a/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.vv b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.vv new file mode 100644 index 0000000..dbb66af --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_missing_rpar_b.vv @@ -0,0 +1,3 @@ +const ( + a = 5 + // foo diff --git a/v_windows/v/vlib/v/parser/tests/const_only_keyword.out b/v_windows/v/vlib/v/parser/tests/const_only_keyword.out new file mode 100644 index 0000000..1e3de05 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_only_keyword.out @@ -0,0 +1,2 @@ +vlib/v/parser/tests/const_only_keyword.vv:2:1: error: unexpected eof, expecting name + 1 | const diff --git a/v_windows/v/vlib/v/parser/tests/const_only_keyword.vv b/v_windows/v/vlib/v/parser/tests/const_only_keyword.vv new file mode 100644 index 0000000..aaae4e1 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_only_keyword.vv @@ -0,0 +1 @@ +const diff --git a/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.out b/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.out new file mode 100644 index 0000000..8f90da6 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.out @@ -0,0 +1,2 @@ +vlib/v/parser/tests/const_unexpected_eof.vv:2:1: error: unexpected eof, expecting an expression + 1 | const a = diff --git a/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.vv b/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.vv new file mode 100644 index 0000000..6210a52 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/const_unexpected_eof.vv @@ -0,0 +1 @@ +const a = diff --git a/v_windows/v/vlib/v/parser/tests/dec_use_as_value.out b/v_windows/v/vlib/v/parser/tests/dec_use_as_value.out new file mode 100644 index 0000000..91587e1 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/dec_use_as_value.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/dec_use_as_value.vv:2:16: error: cannot use i-- as value + 1 | fn main() { + 2 | for i := 100; i--; i > 0 { + | ^ + 3 | } + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/dec_use_as_value.vv b/v_windows/v/vlib/v/parser/tests/dec_use_as_value.vv new file mode 100644 index 0000000..4774070 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/dec_use_as_value.vv @@ -0,0 +1,4 @@ +fn main() { + for i := 100; i--; i > 0 { + } +} diff --git a/v_windows/v/vlib/v/parser/tests/defer_propagate.out b/v_windows/v/vlib/v/parser/tests/defer_propagate.out new file mode 100644 index 0000000..93dae37 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_propagate.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/defer_propagate.vv:9:15: error: error propagation not allowed inside `defer` blocks + 7 | mut a := 0 + 8 | defer { + 9 | a = test1() ? + | ^ + 10 | } + 11 | return a diff --git a/v_windows/v/vlib/v/parser/tests/defer_propagate.vv b/v_windows/v/vlib/v/parser/tests/defer_propagate.vv new file mode 100644 index 0000000..ad92bb9 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_propagate.vv @@ -0,0 +1,16 @@ +fn test1() ?int { + a := 3 + return a +} + +fn test2() ?int { + mut a := 0 + defer { + a = test1() ? + } + return a + +fn main() { + x := test2() or { -1 } + println(x) +} diff --git a/v_windows/v/vlib/v/parser/tests/defer_return.out b/v_windows/v/vlib/v/parser/tests/defer_return.out new file mode 100644 index 0000000..2debd10 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_return.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/defer_return.vv:3:3: error: `return` not allowed inside `defer` block + 1 | fn main() { + 2 | defer { + 3 | return + | ~~~~~~ + 4 | } + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/defer_return.vv b/v_windows/v/vlib/v/parser/tests/defer_return.vv new file mode 100644 index 0000000..0bcc740 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_return.vv @@ -0,0 +1,5 @@ +fn main() { + defer { + return + } +} diff --git a/v_windows/v/vlib/v/parser/tests/defer_return2.out b/v_windows/v/vlib/v/parser/tests/defer_return2.out new file mode 100644 index 0000000..54628e4 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_return2.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/defer_return2.vv:3:3: error: `return` not allowed inside `defer` block + 1 | fn test1() int { + 2 | defer { + 3 | return 12 + | ~~~~~~ + 4 | } + 5 | a := 3 diff --git a/v_windows/v/vlib/v/parser/tests/defer_return2.vv b/v_windows/v/vlib/v/parser/tests/defer_return2.vv new file mode 100644 index 0000000..82f62aa --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/defer_return2.vv @@ -0,0 +1,12 @@ +fn test1() int { + defer { + return 12 + } + a := 3 + return a +} + +fn main() { + x := test1() + println(x) +} diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.out b/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.out new file mode 100644 index 0000000..7fc6311 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/duplicate_field_embed_err.vv:8:2: error: duplicate field `ModFileAndFolder` + 6 | struct Bar { + 7 | ModFileAndFolder + 8 | vmod.ModFileAndFolder + | ~~~~~~~~~~~~~~~~~~~~~ + 9 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.vv b/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.vv new file mode 100644 index 0000000..d71112f --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_field_embed_err.vv @@ -0,0 +1,9 @@ +import v.vmod + +struct ModFileAndFolder { + name int = 5 +} +struct Bar { + ModFileAndFolder + vmod.ModFileAndFolder +} diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_type_a.out b/v_windows/v/vlib/v/parser/tests/duplicate_type_a.out new file mode 100644 index 0000000..a2324a4 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_type_a.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/duplicate_type_a.vv:3:11: error: cannot register interface `Foo`, another type with this name exists + 1 | struct Foo {} + 2 | + 3 | interface Foo {} + | ~~~ diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_type_a.vv b/v_windows/v/vlib/v/parser/tests/duplicate_type_a.vv new file mode 100644 index 0000000..f47419b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_type_a.vv @@ -0,0 +1,3 @@ +struct Foo {} + +interface Foo {} diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_type_b.out b/v_windows/v/vlib/v/parser/tests/duplicate_type_b.out new file mode 100644 index 0000000..6831fda --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_type_b.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/duplicate_type_b.vv:3:8: error: cannot register struct `Foo`, another type with this name exists + 1 | interface Foo {} + 2 | + 3 | struct Foo {} + | ~~~ diff --git a/v_windows/v/vlib/v/parser/tests/duplicate_type_b.vv b/v_windows/v/vlib/v/parser/tests/duplicate_type_b.vv new file mode 100644 index 0000000..52aa104 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicate_type_b.vv @@ -0,0 +1,3 @@ +interface Foo {} + +struct Foo {} diff --git a/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.out b/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.out new file mode 100644 index 0000000..e151407 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/duplicated_generic_err.vv:1:12: error: duplicated generic parameter `A` + 1 | fn test() {} + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.vv b/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.vv new file mode 100644 index 0000000..4b8e345 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/duplicated_generic_err.vv @@ -0,0 +1 @@ +fn test() {} diff --git a/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.out b/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.out new file mode 100644 index 0000000..07cfb9b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/empty_name_expr_err.vv:2:9: error: unexpected name `n` + 1 | fn main() { + 2 | return n ?( + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.vv b/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.vv new file mode 100644 index 0000000..61d2740 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/empty_name_expr_err.vv @@ -0,0 +1,3 @@ +fn main() { + return n ?( +} diff --git a/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.out b/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.out new file mode 100644 index 0000000..e378cf7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/expected_type_enum_err.vv:6:12: error: expected type is not an enum (`rune`) + 4 | + 5 | fn main() { + 6 | if `c` == .bar {} + | ~~~~ + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.vv b/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.vv new file mode 100644 index 0000000..7385f14 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expected_type_enum_err.vv @@ -0,0 +1,7 @@ +module main + +enum Test { bar } + +fn main() { + if `c` == .bar {} +} diff --git a/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.out b/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.out new file mode 100644 index 0000000..d273ac5 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/expecting_assign_type_alias.vv:1:10: error: unexpected name `int`, expecting `=` + 1 | type Ttt int + | ~~~ + 2 | + 3 | fn main() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.vv b/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.vv new file mode 100644 index 0000000..441cc40 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expecting_assign_type_alias.vv @@ -0,0 +1,3 @@ +type Ttt int + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/export_interop_func_err.out b/v_windows/v/vlib/v/parser/tests/export_interop_func_err.out new file mode 100644 index 0000000..129e6b2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/export_interop_func_err.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/export_interop_func_err.vv:1:1: error: interop function cannot be exported + 1 | [export: 'test'] + | ~~~~~~~~~~~~~~~~ + 2 | fn C.printf(s string) diff --git a/v_windows/v/vlib/v/parser/tests/export_interop_func_err.vv b/v_windows/v/vlib/v/parser/tests/export_interop_func_err.vv new file mode 100644 index 0000000..b18925b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/export_interop_func_err.vv @@ -0,0 +1,2 @@ +[export: 'test'] +fn C.printf(s string) diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.out new file mode 100644 index 0000000..e83390f --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_a.vv:2:2: error: expression evaluated but not used + 1 | fn main() { + 2 | 'hello' + | ~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.vv new file mode 100644 index 0000000..c4b6515 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_a.vv @@ -0,0 +1,3 @@ +fn main() { + 'hello' +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.out new file mode 100644 index 0000000..437f89a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_b.vv:2:2: error: expression evaluated but not used + 1 | fn main() { + 2 | 22 + | ~~ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.vv new file mode 100644 index 0000000..cac3734 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_b.vv @@ -0,0 +1,3 @@ +fn main() { + 22 +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.out new file mode 100644 index 0000000..f6dbc23 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_c.vv:3:5: error: expression evaluated but not used + 1 | fn main() { + 2 | a := 10 + 3 | `b` + | ~~~ + 4 | println(a) + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.vv new file mode 100644 index 0000000..863ce2f --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_c.vv @@ -0,0 +1,5 @@ +fn main() { + a := 10 + `b` + println(a) +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.out new file mode 100644 index 0000000..03639ea --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_d.vv:4:19: error: expression evaluated but not used + 2 | a := 1 + 3 | b := 2 + 4 | println(a*b), a+b + | ~~~ + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.vv new file mode 100644 index 0000000..6dbacb7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_d.vv @@ -0,0 +1,5 @@ +fn main() { + a := 1 + b := 2 + println(a*b), a+b +} diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.out new file mode 100644 index 0000000..c312124 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_e.vv:3:14: error: expression evaluated but not used + 1 | fn main() { + 2 | mut array := [1, 2, 3] + 3 | array << 4, 5 + | ^ + 4 | println(array) + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.vv new file mode 100644 index 0000000..763624a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_e.vv @@ -0,0 +1,5 @@ +fn main() { + mut array := [1, 2, 3] + array << 4, 5 + println(array) +} diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.out new file mode 100644 index 0000000..f35579a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_if.vv:3:5: error: expression evaluated but not used + 1 | fn main() { + 2 | if true { + 3 | 1 + 1 + | ~~~~~ + 4 | 1 + 1 + 5 | } else { diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.vv new file mode 100644 index 0000000..8cd5a96 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_if.vv @@ -0,0 +1,8 @@ +fn main() { + if true { + 1 + 1 + 1 + 1 + } else { + 1 + 1 + } +} diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.out b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.out new file mode 100644 index 0000000..70b3e7e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/expr_evaluated_but_not_used_or.vv:3:5: error: expression evaluated but not used + 1 | fn main() { + 2 | f() or { + 3 | 0 + | ^ + 4 | 1 + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.vv b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.vv new file mode 100644 index 0000000..4a3a82a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/expr_evaluated_but_not_used_or.vv @@ -0,0 +1,10 @@ +fn main() { + f() or { + 0 + 1 + } +} + +fn f() ?int { + return none +} diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.out b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.out new file mode 100644 index 0000000..c8015ca --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/fn_attributes_duplicate_multiple.vv:2:2: error: duplicate attribute `inline` + 1 | [inline] + 2 | [inline] + | ~~~~~~ + 3 | fn foo() {} + 4 | diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.vv b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.vv new file mode 100644 index 0000000..2e1f223 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_multiple.vv @@ -0,0 +1,7 @@ +[inline] +[inline] +fn foo() {} + +fn main() { + foo() +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.out b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.out new file mode 100644 index 0000000..34a90cd --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/fn_attributes_duplicate_single.vv:1:10: error: duplicate attribute `inline` + 1 | [inline; inline] + | ~~~~~~ + 2 | fn foo() {} + 3 | diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.vv b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.vv new file mode 100644 index 0000000..27d6f55 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_duplicate_single.vv @@ -0,0 +1,6 @@ +[inline; inline] +fn foo() {} + +fn main() { + foo() +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.out b/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.out new file mode 100644 index 0000000..d021aee --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/fn_attributes_empty_err.vv:1:1: error: attributes cannot be empty + 1 | [] fn tt() { + | ~~ + 2 | println('text') + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.vv b/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.vv new file mode 100644 index 0000000..fd584c2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_attributes_empty_err.vv @@ -0,0 +1,6 @@ +[] fn tt() { + println('text') +} +fn main() { + tt() +} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.out b/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.out new file mode 100644 index 0000000..aef33f6 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/fn_decl_unexpected_eof.vv:1:12: error: unexpected eof, expecting `}` + 1 | fn main() { + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.vv b/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.vv new file mode 100644 index 0000000..fd74173 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_decl_unexpected_eof.vv @@ -0,0 +1 @@ +fn main() { \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.out b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.out new file mode 100644 index 0000000..acc1399 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/fn_type_only_args_in_interfaces.vv:22:1: error: `syntax_error` evaluated but not used + 20 | } + 21 | + 22 | syntax_error + | ~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.vv b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.vv new file mode 100644 index 0000000..0a3e839 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_in_interfaces.vv @@ -0,0 +1,22 @@ + +struct Type1 {} +struct Type2 {} +struct Type3 {} + + +pub interface Widget1 { + init(Type1, Type2) +} + +pub interface Widget2 { + init(Type1) + draw(Type2, Type3) +} + +pub interface Widget3 { + fnoparams1() + fnoparams2() + draw(Type1, Type2) +} + +syntax_error diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.out b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.out new file mode 100644 index 0000000..fad0819 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/fn_type_only_args_no_body.vv:2:1: error: functions with type only args can not have bodies + 1 | fn test(int) { + 2 | } + | ^ + 3 | + 4 | fn main() { diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.vv b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.vv new file mode 100644 index 0000000..d661c15 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_no_body.vv @@ -0,0 +1,6 @@ +fn test(int) { +} + +fn main() { + test(0) +} diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.out b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.out new file mode 100644 index 0000000..1f94768 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/fn_type_only_args_unknown_name.vv:2:1: error: functions with type only args can not have bodies + 1 | fn test(param) { + 2 | } + | ^ + 3 | + 4 | fn main() { diff --git a/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.vv b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.vv new file mode 100644 index 0000000..7bb6b78 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_type_only_args_unknown_name.vv @@ -0,0 +1,6 @@ +fn test(param) { +} + +fn main() { + test(0) +} diff --git a/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.out b/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.out new file mode 100644 index 0000000..5e57fe2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/fn_use_builtin_err.vv:1:4: error: cannot redefine builtin function `print` + 1 | fn print(strings ...string) { + | ~~~~~ + 2 | for s in strings { + 3 | println(s) diff --git a/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.vv b/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.vv new file mode 100644 index 0000000..1466e4c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/fn_use_builtin_err.vv @@ -0,0 +1,9 @@ +fn print(strings ...string) { + for s in strings { + println(s) + } +} + +fn main() { + print('text') +} diff --git a/v_windows/v/vlib/v/parser/tests/for.out b/v_windows/v/vlib/v/parser/tests/for.out new file mode 100644 index 0000000..acc089b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/for.vv:1:5: error: cannot declare index variable with range `for` + 1 | for i, k in 0..5 { + | ^ + 2 | } diff --git a/v_windows/v/vlib/v/parser/tests/for.vv b/v_windows/v/vlib/v/parser/tests/for.vv new file mode 100644 index 0000000..1a48f69 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for.vv @@ -0,0 +1,2 @@ +for i, k in 0..5 { +} diff --git a/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.out b/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.out new file mode 100644 index 0000000..f8e5a51 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/for_in_mut_index_of_array.vv:3:6: error: index of array or key of map cannot be mutated + 1 | fn main() { + 2 | mut m := [1, 2, 3] + 3 | for mut i, _ in m { + | ~~~ + 4 | println(i) + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.vv b/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.vv new file mode 100644 index 0000000..eb04d96 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for_in_mut_index_of_array.vv @@ -0,0 +1,6 @@ +fn main() { + mut m := [1, 2, 3] + for mut i, _ in m { + println(i) + } +} diff --git a/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.out b/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.out new file mode 100644 index 0000000..7c159a0 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/for_in_mut_key_of_map.vv:3:6: error: index of array or key of map cannot be mutated + 1 | fn main() { + 2 | mut m := {'foo': 1, 'bar': 2} + 3 | for mut k, _ in m { + | ~~~ + 4 | println(k) + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.vv b/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.vv new file mode 100644 index 0000000..22a5aca --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/for_in_mut_key_of_map.vv @@ -0,0 +1,6 @@ +fn main() { + mut m := {'foo': 1, 'bar': 2} + for mut k, _ in m { + println(k) + } +} diff --git a/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.out b/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.out new file mode 100644 index 0000000..7c69a4b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/function_variadic_arg_non_final.vv:1:6: error: cannot use ...(variadic) with non-final parameter para1 + 1 | fn f(para1 ...int, para2 f32) int { + | ~~~~~ + 2 | return 22 + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.vv b/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.vv new file mode 100644 index 0000000..f7ce72a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/function_variadic_arg_non_final.vv @@ -0,0 +1,7 @@ +fn f(para1 ...int, para2 f32) int { + return 22 +} + +fn main() { + a := f(11, 1.1) +} diff --git a/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.out b/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.out new file mode 100644 index 0000000..3a02413 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/generic_lowercase_err.vv:1:22: error: generic parameter needs to be uppercase + 1 | fn lowercase_generic() {} + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.vv b/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.vv new file mode 100644 index 0000000..4d9bcad --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/generic_lowercase_err.vv @@ -0,0 +1 @@ +fn lowercase_generic() {} diff --git a/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.out b/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.out new file mode 100644 index 0000000..95c9e02 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/generic_type_alias_decl.vv:1:1: error: generic type aliases are not yet implemented + 1 | type Pointer = &T + | ~~~~~~~~~~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.vv b/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.vv new file mode 100644 index 0000000..b2a177a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/generic_type_alias_decl.vv @@ -0,0 +1 @@ +type Pointer = &T diff --git a/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.out b/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.out new file mode 100644 index 0000000..7f2a5b2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/if_guard_redefinition.vv:7:8: error: redefinition of `x` + 5 | fn main() { + 6 | x := 1 + 7 | if x := opt_fn() { + | ^ + 8 | println(x) + 9 | } diff --git a/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.vv b/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.vv new file mode 100644 index 0000000..b54ee90 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/if_guard_redefinition.vv @@ -0,0 +1,10 @@ +fn opt_fn() ?int { + return 2 +} + +fn main() { + x := 1 + if x := opt_fn() { + println(x) + } +} diff --git a/v_windows/v/vlib/v/parser/tests/inc_use_as_value.out b/v_windows/v/vlib/v/parser/tests/inc_use_as_value.out new file mode 100644 index 0000000..11888f8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/inc_use_as_value.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/inc_use_as_value.vv:2:14: error: cannot use i++ as value + 1 | fn main() { + 2 | for i := 0; i++; i < 100 { + | ^ + 3 | } + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/inc_use_as_value.vv b/v_windows/v/vlib/v/parser/tests/inc_use_as_value.vv new file mode 100644 index 0000000..885f6b7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/inc_use_as_value.vv @@ -0,0 +1,4 @@ +fn main() { + for i := 0; i++; i < 100 { + } +} diff --git a/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.out b/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.out new file mode 100644 index 0000000..eaee38e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/interface_duplicate_interface_method.vv:4:2: error: duplicate method `fun` + 2 | interface Abc { + 3 | fun() + 4 | fun() + | ~~~ + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.vv b/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.vv new file mode 100644 index 0000000..4d29383 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interface_duplicate_interface_method.vv @@ -0,0 +1,5 @@ +// duplicate interface methods in decleration +interface Abc { + fun() + fun() +} diff --git a/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.out b/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.out new file mode 100644 index 0000000..f83ad2a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/interface_duplicate_method.vv:5:12: error: duplicate method `foo` + 3 | // duplicate normal method definitions on interface + 4 | fn (a Abc) foo() {} + 5 | fn (a Abc) foo() {} + | ~~~ diff --git a/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.vv b/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.vv new file mode 100644 index 0000000..ab22330 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interface_duplicate_method.vv @@ -0,0 +1,5 @@ +interface Abc {} + +// duplicate normal method definitions on interface +fn (a Abc) foo() {} +fn (a Abc) foo() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/interop_func_body_err.out b/v_windows/v/vlib/v/parser/tests/interop_func_body_err.out new file mode 100644 index 0000000..ebdbc19 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interop_func_body_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/interop_func_body_err.vv:1:23: error: interop functions cannot have a body + 1 | fn C.printf(s string) {} + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/interop_func_body_err.vv b/v_windows/v/vlib/v/parser/tests/interop_func_body_err.vv new file mode 100644 index 0000000..157f6f7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/interop_func_body_err.vv @@ -0,0 +1 @@ +fn C.printf(s string) {} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.out b/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.out new file mode 100644 index 0000000..7ae507c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/invalid_attribute_a.vv:1:9: error: unexpected token `]`, an argument is expected after `:` + 1 | [foobar:] + | ^ + 2 | fn my_fn_with_invalid_attr() { + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.vv b/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.vv new file mode 100644 index 0000000..971dc28 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_a.vv @@ -0,0 +1,3 @@ +[foobar:] +fn my_fn_with_invalid_attr() { +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.out b/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.out new file mode 100644 index 0000000..763de97 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/invalid_attribute_b.vv:1:6: error: unexpected token `:`, an argument is expected after `:` + 1 | [foo::] + | ^ + 2 | fn my_fn_with_invalid_attr() { + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.vv b/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.vv new file mode 100644 index 0000000..d6af445 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_b.vv @@ -0,0 +1,3 @@ +[foo::] +fn my_fn_with_invalid_attr() { +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.out b/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.out new file mode 100644 index 0000000..a30cb42 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/invalid_attribute_c.vv:1:6: error: unexpected token `[`, an argument is expected after `:` + 1 | [bar:[] + | ^ + 2 | fn my_fn_with_invalid_attr() { + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.vv b/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.vv new file mode 100644 index 0000000..527076d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_c.vv @@ -0,0 +1,3 @@ +[bar:[] +fn my_fn_with_invalid_attr() { +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.out b/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.out new file mode 100644 index 0000000..3d05dc2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/invalid_attribute_d.vv:1:9: error: unexpected token `}`, an argument is expected after `:` + 1 | [foobar:} + | ^ + 2 | fn my_fn_with_invalid_attr() { + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.vv b/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.vv new file mode 100644 index 0000000..0b7d2e2 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_attribute_d.vv @@ -0,0 +1,3 @@ +[foobar:} +fn my_fn_with_invalid_attr() { +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.out b/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.out new file mode 100644 index 0000000..aead2d0 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/invalid_fn_decl_script_err.vv:3:4: error: function declarations in script mode should be before all script statements + 1 | mynum := 10 + 2 | + 3 | fn main() { + | ~~~~ + 4 | println(mynum) + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.vv b/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.vv new file mode 100644 index 0000000..196895a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_fn_decl_script_err.vv @@ -0,0 +1,5 @@ +mynum := 10 + +fn main() { + println(mynum) +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.out b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.out new file mode 100644 index 0000000..8cac758 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/invalid_recursive_struct1_err.vv:1:8: error: invalid recursive struct `Human` + 1 | struct Human { + | ~~~~~ + 2 | child Human + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.vv b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.vv new file mode 100644 index 0000000..649ca81 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct1_err.vv @@ -0,0 +1,3 @@ +struct Human { + child Human +} diff --git a/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.out b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.out new file mode 100644 index 0000000..d784e50 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/invalid_recursive_struct2_err.vv:5:8: error: invalid recursive struct `Human` + 3 | } + 4 | + 5 | struct Human { + | ~~~~~ + 6 | child Child + 7 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.vv b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.vv new file mode 100644 index 0000000..0088c8e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/invalid_recursive_struct2_err.vv @@ -0,0 +1,7 @@ +struct Child { + be Human +} + +struct Human { + child Child +} diff --git a/v_windows/v/vlib/v/parser/tests/long_generic_err.out b/v_windows/v/vlib/v/parser/tests/long_generic_err.out new file mode 100644 index 0000000..d27f9f0 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/long_generic_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/long_generic_err.vv:1:17: error: generic parameter name needs to be exactly one char + 1 | fn long_generic() {} + | ~~~ diff --git a/v_windows/v/vlib/v/parser/tests/long_generic_err.vv b/v_windows/v/vlib/v/parser/tests/long_generic_err.vv new file mode 100644 index 0000000..ae3e538 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/long_generic_err.vv @@ -0,0 +1 @@ +fn long_generic() {} diff --git a/v_windows/v/vlib/v/parser/tests/map_init.out b/v_windows/v/vlib/v/parser/tests/map_init.out new file mode 100644 index 0000000..7185266 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/map_init.vv:3:22: error: `}` expected; explicit `map` initialization does not support parameters + 1 | fn main() { + 2 | a := map[string]int{} + 3 | b := map[string]f64{cap: 10} + | ~~~ + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/map_init.vv b/v_windows/v/vlib/v/parser/tests/map_init.vv new file mode 100644 index 0000000..2ab972d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init.vv @@ -0,0 +1,4 @@ +fn main() { + a := map[string]int{} + b := map[string]f64{cap: 10} +} diff --git a/v_windows/v/vlib/v/parser/tests/map_init_void.out b/v_windows/v/vlib/v/parser/tests/map_init_void.out new file mode 100644 index 0000000..fd76d7e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init_void.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/map_init_void.vv:2:18: error: map value type cannot be void + 1 | fn main() { + 2 | m := map[string]{} + | ^ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/map_init_void.vv b/v_windows/v/vlib/v/parser/tests/map_init_void.vv new file mode 100644 index 0000000..5ad2727 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init_void.vv @@ -0,0 +1,3 @@ +fn main() { + m := map[string]{} +} diff --git a/v_windows/v/vlib/v/parser/tests/map_init_void2.out b/v_windows/v/vlib/v/parser/tests/map_init_void2.out new file mode 100644 index 0000000..81031dc --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init_void2.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/map_init_void2.vv:1:19: error: expecting type declaration + 1 | fn f(m map[string]) { + | ^ + 2 | println('illegal function') + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/map_init_void2.vv b/v_windows/v/vlib/v/parser/tests/map_init_void2.vv new file mode 100644 index 0000000..0b9a8e4 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/map_init_void2.vv @@ -0,0 +1,7 @@ +fn f(m map[string]) { + println('illegal function') +} + +fn main() { + println('Hello world') +} diff --git a/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.out b/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.out new file mode 100644 index 0000000..bc815ed --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/match_range_dotdot_err.vv:3:4: error: match only supports inclusive (`...`) ranges, not exclusive (`..`) + 1 | fn test_match() { + 2 | match 5 { + 3 | 0..10 { '0-9' } + | ~~ + 4 | else { 'other' } + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.vv b/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.vv new file mode 100644 index 0000000..0c7f112 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/match_range_dotdot_err.vv @@ -0,0 +1,6 @@ +fn test_match() { + match 5 { + 0..10 { '0-9' } + else { 'other' } + } +} diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.out b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.out new file mode 100644 index 0000000..f01fc00 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/method_decl_on_non_local_array.vv:1:7: error: cannot define new methods on non-local type []int + 1 | fn (a []int) get_number() int { + | ~~~~~ + 2 | return 1 + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.vv b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.vv new file mode 100644 index 0000000..9822ce5 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_array.vv @@ -0,0 +1,3 @@ +fn (a []int) get_number() int { + return 1 +} diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.out b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.out new file mode 100644 index 0000000..d8459da --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/method_decl_on_non_local_map.vv:1:7: error: cannot define new methods on non-local type map[string]string + 1 | fn (a map[string]string) get_number() int { + | ~~~~~~~~~~~~~~~~~ + 2 | return 1 + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.vv b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.vv new file mode 100644 index 0000000..f95e719 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_map.vv @@ -0,0 +1,3 @@ +fn (a map[string]string) get_number() int { + return 1 +} diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.out b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.out new file mode 100644 index 0000000..1b57bbd --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/method_decl_on_non_local_type.vv:1:7: error: cannot define new methods on non-local type int + 1 | fn (a int) get_number() int { + | ~~~ + 2 | return 1 + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.vv b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.vv new file mode 100644 index 0000000..f75565b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/method_decl_on_non_local_type.vv @@ -0,0 +1,3 @@ +fn (a int) get_number() int { + return 1 +} diff --git a/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.out b/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.out new file mode 100644 index 0000000..6ae53a8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/module_multiple_names_err.vv:1:13: error: `module main`, you can only declare one module, unexpected `os` + 1 | module main os + | ~~ + 2 | fn main() { + 3 | println('hello, world') diff --git a/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.vv b/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.vv new file mode 100644 index 0000000..96158af --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/module_multiple_names_err.vv @@ -0,0 +1,4 @@ +module main os +fn main() { + println('hello, world') +} diff --git a/v_windows/v/vlib/v/parser/tests/module_syntax_err.out b/v_windows/v/vlib/v/parser/tests/module_syntax_err.out new file mode 100644 index 0000000..38a1905 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/module_syntax_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/module_syntax_err.vv:1:12: error: `module main`, unexpected `.` after module name + 1 | module main.os + | ^ + 2 | fn main() { + 3 | println('hello, world') diff --git a/v_windows/v/vlib/v/parser/tests/module_syntax_err.vv b/v_windows/v/vlib/v/parser/tests/module_syntax_err.vv new file mode 100644 index 0000000..8f8c91f --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/module_syntax_err.vv @@ -0,0 +1,4 @@ +module main.os +fn main() { + println('hello, world') +} diff --git a/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.out b/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.out new file mode 100644 index 0000000..4e1a64c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/multi_argumented_assign_err.vv:3:10: error: unexpected +=, expecting := or = or comma + 1 | fn main() { + 2 | mut a, mut b, mut c := 0,1, 2 + 3 | a, b, c += 1, 2, 4 + | ~~ + 4 | println('$a $b $c') + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.vv b/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.vv new file mode 100644 index 0000000..26257b8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/multi_argumented_assign_err.vv @@ -0,0 +1,5 @@ +fn main() { + mut a, mut b, mut c := 0,1, 2 + a, b, c += 1, 2, 4 + println('$a $b $c') +} diff --git a/v_windows/v/vlib/v/parser/tests/nested_defer.out b/v_windows/v/vlib/v/parser/tests/nested_defer.out new file mode 100644 index 0000000..c5da8dc --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_defer.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/nested_defer.vv:5:3: error: `defer` blocks cannot be nested + 3 | defer { + 4 | a = 12 + 5 | defer { + | ~~~~~ + 6 | a = 13 + 7 | } diff --git a/v_windows/v/vlib/v/parser/tests/nested_defer.vv b/v_windows/v/vlib/v/parser/tests/nested_defer.vv new file mode 100644 index 0000000..7abc2d3 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_defer.vv @@ -0,0 +1,14 @@ +fn test1() int { + mut a := 0 + defer { + a = 12 + defer { + a = 13 + } + } + return a + +fn main() { + x := test1() + println(x) +} diff --git a/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.out b/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.out new file mode 100644 index 0000000..1d815ed --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/nested_unsafe_expr.vv:4:8: error: already inside `unsafe` block + 2 | a := 0 + 3 | unsafe { + 4 | a += unsafe{2} + | ~~~~~~ + 5 | } + 6 | println(a) diff --git a/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.vv b/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.vv new file mode 100644 index 0000000..1ab098c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_unsafe_expr.vv @@ -0,0 +1,7 @@ +fn main() { + a := 0 + unsafe { + a += unsafe{2} + } + println(a) +} diff --git a/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.out b/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.out new file mode 100644 index 0000000..b76a6e5 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/nested_unsafe_stmt.vv:4:3: error: already inside `unsafe` block + 2 | a := 0 + 3 | unsafe { + 4 | unsafe { + | ~~~~~~ + 5 | a++ + 6 | } diff --git a/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.vv b/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.vv new file mode 100644 index 0000000..283e76d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/nested_unsafe_stmt.vv @@ -0,0 +1,9 @@ +fn main() { + a := 0 + unsafe { + unsafe { + a++ + } + } + println(a) +} diff --git a/v_windows/v/vlib/v/parser/tests/operator_normal_fn.out b/v_windows/v/vlib/v/parser/tests/operator_normal_fn.out new file mode 100644 index 0000000..9313c0a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/operator_normal_fn.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/operator_normal_fn.vv:1:4: error: cannot use operator overloading with normal functions + 1 | fn +(x int) int { + | ^ + 2 | return 5 + x + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/operator_normal_fn.vv b/v_windows/v/vlib/v/parser/tests/operator_normal_fn.vv new file mode 100644 index 0000000..5a82b31 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/operator_normal_fn.vv @@ -0,0 +1,3 @@ +fn +(x int) int { + return 5 + x +} diff --git a/v_windows/v/vlib/v/parser/tests/or_default_missing.out b/v_windows/v/vlib/v/parser/tests/or_default_missing.out new file mode 100644 index 0000000..82cfa59 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/or_default_missing.out @@ -0,0 +1,14 @@ +vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a default value of type `int`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1) + 2 | m := [3, 4, 5] + 3 | el := m[4] or { + 4 | println('error') + | ~~~~~~~~~~~~~~~~ + 5 | } + 6 | println(el) +vlib/v/parser/tests/or_default_missing.vv:16:16: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope + 14 | } + 15 | mut testvar := 0 + 16 | el := m['pp'] or { + | ~~~~ + 17 | testvar = 12 + 18 | } diff --git a/v_windows/v/vlib/v/parser/tests/or_default_missing.vv b/v_windows/v/vlib/v/parser/tests/or_default_missing.vv new file mode 100644 index 0000000..d89db0d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/or_default_missing.vv @@ -0,0 +1,20 @@ +fn test_array_or() { + m := [3, 4, 5] + el := m[4] or { + println('error') + } + println(el) +} + +fn test_map_or() { + m := { + 'as': 3 + 'qw': 4 + 'kl': 5 + } + mut testvar := 0 + el := m['pp'] or { + testvar = 12 + } + println('$el $testvar') +} diff --git a/v_windows/v/vlib/v/parser/tests/postfix_err.out b/v_windows/v/vlib/v/parser/tests/postfix_err.out new file mode 100644 index 0000000..d83ccfa --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/postfix_err.out @@ -0,0 +1,21 @@ +vlib/v/parser/tests/postfix_err.vv:5:10: warning: `++` operator can only be used as a statement + 3 | fn test_postfix() { + 4 | mut x := 1 + 5 | _ = (x++) + | ^ + 6 | x--, x-- // OK + 7 | f(x++) +vlib/v/parser/tests/postfix_err.vv:7:7: warning: `++` operator can only be used as a statement + 5 | _ = (x++) + 6 | x--, x-- // OK + 7 | f(x++) + | ^ + 8 | a := [x] + 9 | _ = a[x--] +vlib/v/parser/tests/postfix_err.vv:9:11: warning: `--` operator can only be used as a statement + 7 | f(x++) + 8 | a := [x] + 9 | _ = a[x--] + | ^ + 10 | } + 11 | diff --git a/v_windows/v/vlib/v/parser/tests/postfix_err.vv b/v_windows/v/vlib/v/parser/tests/postfix_err.vv new file mode 100644 index 0000000..a1ba568 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/postfix_err.vv @@ -0,0 +1,11 @@ +fn f(i int) {} + +fn test_postfix() { + mut x := 1 + _ = (x++) + x--, x-- // OK + f(x++) + a := [x] + _ = a[x--] +} + diff --git a/v_windows/v/vlib/v/parser/tests/postfix_inc.out b/v_windows/v/vlib/v/parser/tests/postfix_inc.out new file mode 100644 index 0000000..189a413 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/postfix_inc.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/postfix_inc.vv:3:1: error: token `++` must be on the same line as the previous token + 1 | mut v := 4 + 2 | _ = v + 3 | ++v + | ~~ diff --git a/v_windows/v/vlib/v/parser/tests/postfix_inc.vv b/v_windows/v/vlib/v/parser/tests/postfix_inc.vv new file mode 100644 index 0000000..3f2fd06 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/postfix_inc.vv @@ -0,0 +1,3 @@ +mut v := 4 +_ = v +++v diff --git a/v_windows/v/vlib/v/parser/tests/prefix_first.out b/v_windows/v/vlib/v/parser/tests/prefix_first.out new file mode 100644 index 0000000..3a4adc6 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/prefix_first.out @@ -0,0 +1,28 @@ +vlib/v/parser/tests/prefix_first.vv:15:3: warning: move infix `-` operator before new line (if infix intended) or use brackets for a prefix expression + 13 | _ = if true { + 14 | v = 1 + 15 | -1 + | ^ + 16 | } else {1} + 17 | _ = p +vlib/v/parser/tests/prefix_first.vv:27:3: warning: move infix `&` operator before new line (if infix intended) or use brackets for a prefix expression + 25 | _ = opt() or { + 26 | _ = 1 + 27 | &v + | ^ + 28 | } + 29 | } +vlib/v/parser/tests/prefix_first.vv:13:6: error: `if` expression requires an expression as the last statement of every branch + 11 | + 12 | // later this should compile correctly + 13 | _ = if true { + | ~~~~~~~ + 14 | v = 1 + 15 | -1 +vlib/v/parser/tests/prefix_first.vv:25:12: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope + 23 | // later this should compile correctly + 24 | v := 3 + 25 | _ = opt() or { + | ~~~~ + 26 | _ = 1 + 27 | &v diff --git a/v_windows/v/vlib/v/parser/tests/prefix_first.vv b/v_windows/v/vlib/v/parser/tests/prefix_first.vv new file mode 100644 index 0000000..83e180c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/prefix_first.vv @@ -0,0 +1,29 @@ +// a prefix op can be parsed as an infix op if there's an expression on the line before +// https://github.com/vlang/v/pull/6491 +fn test_prefix() { + mut v := 1 + mut p := &v + // OK, special workaround + unsafe { + v = 1 + *p = 2 + } + + // later this should compile correctly + _ = if true { + v = 1 + -1 + } else {1} + _ = p +} + +fn opt() ?&int {return none} + +fn test_prefix_or() { + // later this should compile correctly + v := 3 + _ = opt() or { + _ = 1 + &v + } +} diff --git a/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.out b/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.out new file mode 100644 index 0000000..e292e0a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.vv:1:8: error: cannot register struct `Option`, another type with this name exists + 1 | struct Option {} + | ~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.vv b/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.vv new file mode 100644 index 0000000..26669cc --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/prohibit_redeclaration_of_builtin_types.vv @@ -0,0 +1 @@ +struct Option {} diff --git a/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.out b/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.out new file mode 100644 index 0000000..1c02758 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/redeclaration_of_imported_fn.vv:3:4: error: cannot redefine imported function `input` + 1 | import os { input } + 2 | + 3 | fn input() {} + | ~~~~~ + 4 | + 5 | input() diff --git a/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.vv b/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.vv new file mode 100644 index 0000000..45a3b5a --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/redeclaration_of_imported_fn.vv @@ -0,0 +1,5 @@ +import os { input } + +fn input() {} + +input() diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_alias.out b/v_windows/v/vlib/v/parser/tests/register_imported_alias.out new file mode 100644 index 0000000..3cc0365 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_alias.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/register_imported_alias.vv:2:6: error: cannot register alias `Duration`, this type was already imported + 1 | import time { Duration } + 2 | type Duration = bool + | ~~~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_alias.vv b/v_windows/v/vlib/v/parser/tests/register_imported_alias.vv new file mode 100644 index 0000000..0200e5d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_alias.vv @@ -0,0 +1,2 @@ +import time { Duration } +type Duration = bool diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_enum.out b/v_windows/v/vlib/v/parser/tests/register_imported_enum.out new file mode 100644 index 0000000..89f888b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_enum.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/register_imported_enum.vv:2:6: error: cannot register enum `Method`, this type was already imported + 1 | import net.http { Method } + 2 | enum Method { foo bar } + | ~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_enum.vv b/v_windows/v/vlib/v/parser/tests/register_imported_enum.vv new file mode 100644 index 0000000..bb1bb6e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_enum.vv @@ -0,0 +1,2 @@ +import net.http { Method } +enum Method { foo bar } diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_interface.out b/v_windows/v/vlib/v/parser/tests/register_imported_interface.out new file mode 100644 index 0000000..58b2029 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_interface.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/register_imported_interface.vv:2:11: error: cannot register interface `Reader`, this type was already imported + 1 | import io { Reader } + 2 | interface Reader {} + | ~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_interface.vv b/v_windows/v/vlib/v/parser/tests/register_imported_interface.vv new file mode 100644 index 0000000..c659327 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_interface.vv @@ -0,0 +1,2 @@ +import io { Reader } +interface Reader {} diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_struct.out b/v_windows/v/vlib/v/parser/tests/register_imported_struct.out new file mode 100644 index 0000000..259b831 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_struct.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/register_imported_struct.vv:2:8: error: cannot register struct `File`, this type was already imported + 1 | import os { File } + 2 | struct File {} + | ~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/register_imported_struct.vv b/v_windows/v/vlib/v/parser/tests/register_imported_struct.vv new file mode 100644 index 0000000..a9c54db --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/register_imported_struct.vv @@ -0,0 +1,2 @@ +import os { File } +struct File {} diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_1.out b/v_windows/v/vlib/v/parser/tests/select_bad_key_1.out new file mode 100644 index 0000000..a9f946c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_1.out @@ -0,0 +1,14 @@ +vlib/v/parser/tests/select_bad_key_1.vv:19:3: notice: `>` is deprecated and will soon be forbidden - just state the timeout in nanoseconds + 17 | a++ + 18 | } + 19 | > 50 * time.millisecond { + | ^ + 20 | println('timeout') + 21 | } +vlib/v/parser/tests/select_bad_key_1.vv:39:8: error: select key: receive expression expected + 37 | fn f3_bad(ch1 chan St) { + 38 | select { + 39 | b := 17 { + | ~~ + 40 | println(b) + 41 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_1.vv b/v_windows/v/vlib/v/parser/tests/select_bad_key_1.vv new file mode 100644 index 0000000..3e9da5d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_1.vv @@ -0,0 +1,47 @@ +import time + +struct St { + a int +} + +fn f1_good(ch1 chan St, ch2 chan int, ch3 chan int) { + mut a := 5 + select { + a = <- ch3 { + println(a) + } + b := <- ch1 { + println(b.a) + } + ch1 <- a { + a++ + } + > 50 * time.millisecond { + println('timeout') + } + } + println('done') +} + +fn f2_good(ch1 chan St) { + select { + b := <- ch1 { + println(b) + } + else { + println('no channel ready') + } + } +} + +fn f3_bad(ch1 chan St) { + select { + b := 17 { + println(b) + } + } +} + +fn main() {} + + diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_2.out b/v_windows/v/vlib/v/parser/tests/select_bad_key_2.out new file mode 100644 index 0000000..a7a6109 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_2.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_2.vv:7:3: error: invalid type `f64` for timeout - expected integer number of nanoseconds aka `time.Duration` + 5 | println(b) + 6 | } + 7 | a + 7 { + | ~~~~~ + 8 | println("shouldn't get here") + 9 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_2.vv b/v_windows/v/vlib/v/parser/tests/select_bad_key_2.vv new file mode 100644 index 0000000..a3fcda8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_2.vv @@ -0,0 +1,13 @@ +fn f3_bad(ch1 chan int) { + a := 3.75 + select { + b := <-ch1 { + println(b) + } + a + 7 { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_3.out b/v_windows/v/vlib/v/parser/tests/select_bad_key_3.out new file mode 100644 index 0000000..029add6 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_3.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_3.vv:6:3: error: invalid type `void` for timeout - expected integer number of nanoseconds aka `time.Duration` + 4 | println(b) + 5 | } + 6 | println(7) { + | ~~~~~~~~~~ + 7 | println("shouldn't get here") + 8 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_3.vv b/v_windows/v/vlib/v/parser/tests/select_bad_key_3.vv new file mode 100644 index 0000000..c7bf3a6 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_3.vv @@ -0,0 +1,12 @@ +fn f3_bad(ch1 chan int) { + select { + b := <-ch1 { + println(b) + } + println(7) { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_4.out b/v_windows/v/vlib/v/parser/tests/select_bad_key_4.out new file mode 100644 index 0000000..3fac7dd --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_4.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_4.vv:7:8: error: select key: `<-` operator expected + 5 | println(b) + 6 | } + 7 | c := -a { + | ^ + 8 | println("shouldn't get here") + 9 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_bad_key_4.vv b/v_windows/v/vlib/v/parser/tests/select_bad_key_4.vv new file mode 100644 index 0000000..bee7402 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_bad_key_4.vv @@ -0,0 +1,13 @@ +fn f3_bad(ch1 chan int) { + mut a := 5 + select { + a = <-ch1 { + println(b) + } + c := -a { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/select_else_1.out b/v_windows/v/vlib/v/parser/tests/select_else_1.out new file mode 100644 index 0000000..9704fbb --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_else_1.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_else_1.vv:12:3: error: `else` and timeout value are mutually exclusive `select` keys + 10 | println("shouldn't get here") + 11 | } + 12 | 30 * time.millisecond { + | ~~~~~~~~~~~~~~~~~~~~~ + 13 | println('bad') + 14 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_else_1.vv b/v_windows/v/vlib/v/parser/tests/select_else_1.vv new file mode 100644 index 0000000..78077f4 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_else_1.vv @@ -0,0 +1,18 @@ +import time + +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + else { + println("shouldn't get here") + } + 30 * time.millisecond { + println('bad') + } + } +} + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/select_else_2.out b/v_windows/v/vlib/v/parser/tests/select_else_2.out new file mode 100644 index 0000000..f927031 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_else_2.out @@ -0,0 +1,14 @@ +vlib/v/parser/tests/select_else_2.vv:9:3: notice: `>` is deprecated and will soon be forbidden - just state the timeout in nanoseconds + 7 | println(b) + 8 | } + 9 | > 30 * time.millisecond { + | ^ + 10 | println('bad') + 11 | } +vlib/v/parser/tests/select_else_2.vv:12:3: error: timeout `> t` and `else` are mutually exclusive `select` keys + 10 | println('bad') + 11 | } + 12 | else { + | ~~~~ + 13 | println("shouldn't get here") + 14 | } diff --git a/v_windows/v/vlib/v/parser/tests/select_else_2.vv b/v_windows/v/vlib/v/parser/tests/select_else_2.vv new file mode 100644 index 0000000..2666dbc --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/select_else_2.vv @@ -0,0 +1,18 @@ +import time + +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + > 30 * time.millisecond { + println('bad') + } + else { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.out b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.out new file mode 100644 index 0000000..41417ae --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/sql_no_db_expr_a.vv:4:1: error: invalid expression: unexpected token `}` + 2 | // SqlStmt + 3 | sql := + 4 | } + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.vv b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.vv new file mode 100644 index 0000000..5181f2d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_a.vv @@ -0,0 +1,4 @@ +fn x() { + // SqlStmt + sql := +} diff --git a/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.out b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.out new file mode 100644 index 0000000..da86e9c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/sql_no_db_expr_b.vv:3:11: error: invalid expression: unexpected token `:=` + 1 | fn x() { + 2 | // SqlExpr + 3 | x := sql := + | ~~ + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.vv b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.vv new file mode 100644 index 0000000..fa36435 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sql_no_db_expr_b.vv @@ -0,0 +1,4 @@ +fn x() { + // SqlExpr + x := sql := +} diff --git a/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.out b/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.out new file mode 100644 index 0000000..416d415 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/string_invalid_prefix_err.vv:2:12: error: only `c`, `r`, `js` are recognized string prefixes, but you tried to use `w` + 1 | fn main() { + 2 | why := w'why' + | ^ + 3 | println(why) + 4 | } diff --git a/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.vv b/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.vv new file mode 100644 index 0000000..6c8630c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/string_invalid_prefix_err.vv @@ -0,0 +1,4 @@ +fn main() { + why := w'why' + println(why) +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.out b/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.out new file mode 100644 index 0000000..752ed81 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_embed_duplicate.vv:7:2: error: cannot embed `Abc` more than once + 5 | struct Xyz { + 6 | Abc + 7 | Abc + | ~~~ + 8 | bar int + 9 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.vv b/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.vv new file mode 100644 index 0000000..46563a4 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_duplicate.vv @@ -0,0 +1,9 @@ +struct Abc { + foo int = 5 +} + +struct Xyz { + Abc + Abc + bar int +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.out b/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.out new file mode 100644 index 0000000..5d4542c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/struct_embed_unknown_module.vv:2:2: error: unknown module `custom` + 1 | struct WithEmbed { + 2 | custom.Foo + | ~~~~~~ + 3 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.vv b/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.vv new file mode 100644 index 0000000..1e084a8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_unknown_module.vv @@ -0,0 +1,3 @@ +struct WithEmbed { + custom.Foo +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.out b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.out new file mode 100644 index 0000000..a4bc579 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_embed_wrong_pos_long_err.vv:4:2: error: struct embedding must be declared at the beginning of the struct body + 2 | struct Foo2 { + 3 | mut: + 4 | cli.Command + | ~~~~~~~~~~~ + 5 | } + 6 | fn main() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.vv b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.vv new file mode 100644 index 0000000..8dcc62e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_long_err.vv @@ -0,0 +1,6 @@ +import cli +struct Foo2 { +mut: + cli.Command +} +fn main() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.out b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.out new file mode 100644 index 0000000..04240f8 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_embed_wrong_pos_short_err.vv:6:2: error: struct embedding must be declared at the beginning of the struct body + 4 | struct Foo2 { + 5 | mut: + 6 | Foo + | ~~~ + 7 | } + 8 | fn main() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.vv b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.vv new file mode 100644 index 0000000..cc270fa --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_embed_wrong_pos_short_err.vv @@ -0,0 +1,8 @@ +struct Foo { + foo string +} +struct Foo2 { +mut: + Foo +} +fn main() {} \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_expected.out b/v_windows/v/vlib/v/parser/tests/struct_field_expected.out new file mode 100644 index 0000000..0aabfbe --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_expected.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_field_expected.vv:6:4: error: invalid expression: unexpected token `:` + 4 | + 5 | x = Bar{ + 6 | .a: 'Alpha' + | ^ + 7 | .b: 'Beta' + 8 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_expected.vv b/v_windows/v/vlib/v/parser/tests/struct_field_expected.vv new file mode 100644 index 0000000..c177311 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_expected.vv @@ -0,0 +1,9 @@ +enum TestEnum { + a b +} + +x = Bar{ + .a: 'Alpha' + .b: 'Beta' +} + diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.out b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.out new file mode 100644 index 0000000..81ac9db --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/struct_field_unknown_module_a.vv:2:7: error: unknown module `ui` + 1 | struct Inter { + 2 | code ui.KeyCode + | ~~ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.vv b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.vv new file mode 100644 index 0000000..31058d5 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_a.vv @@ -0,0 +1,3 @@ +struct Inter { + code ui.KeyCode +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.out b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.out new file mode 100644 index 0000000..e467979 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/struct_field_unknown_module_b.vv:2:7: error: unknown module `term.unknownmod` + 1 | struct Inter { + 2 | code term.unknownmod.KeyCode + | ~~~~~~~~~~~~~~~ + 3 | } diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.vv b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.vv new file mode 100644 index 0000000..3193a7c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_b.vv @@ -0,0 +1,3 @@ +struct Inter { + code term.unknownmod.KeyCode +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.out b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.out new file mode 100644 index 0000000..84bd41e --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/struct_field_unknown_module_c.vv:4:7: error: unknown module `term.ui`; did you mean `ui`? + 2 | + 3 | struct Inter { + 4 | code term.ui.KeyCode + | ~~~~~~~ + 5 | } diff --git a/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.vv b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.vv new file mode 100644 index 0000000..cce4940 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_field_unknown_module_c.vv @@ -0,0 +1,5 @@ +import term.ui + +struct Inter { + code term.ui.KeyCode +} diff --git a/v_windows/v/vlib/v/parser/tests/struct_module_section.out b/v_windows/v/vlib/v/parser/tests/struct_module_section.out new file mode 100644 index 0000000..3bc03f7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_module_section.out @@ -0,0 +1,12 @@ +vlib/v/parser/tests/struct_module_section.vv:16:3: error: field `i` of struct `S1` is immutable + 14 | + 15 | mut s := S1{} + 16 | s.i++ + | ^ + 17 | mut s2 := S2{} + 18 | s2.j++ +vlib/v/parser/tests/struct_module_section.vv:18:4: error: field `j` of struct `S2` is immutable + 16 | s.i++ + 17 | mut s2 := S2{} + 18 | s2.j++ + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/struct_module_section.vv b/v_windows/v/vlib/v/parser/tests/struct_module_section.vv new file mode 100644 index 0000000..d15a49c --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_module_section.vv @@ -0,0 +1,18 @@ +struct S1 { +pub mut: + v byte +module: + i int +} + +struct S2 { +module: + j int +mut: + v byte +} + +mut s := S1{} +s.i++ +mut s2 := S2{} +s2.j++ diff --git a/v_windows/v/vlib/v/parser/tests/struct_update_err.out b/v_windows/v/vlib/v/parser/tests/struct_update_err.out new file mode 100644 index 0000000..ee72f49 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_update_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_update_err.vv:15:3: error: unexpected token `...`, expecting name + 13 | f2 := Foo{ + 14 | name: 'f2' + 15 | ...f + | ~~~ + 16 | } + 17 | } diff --git a/v_windows/v/vlib/v/parser/tests/struct_update_err.vv b/v_windows/v/vlib/v/parser/tests/struct_update_err.vv new file mode 100644 index 0000000..46aa523 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/struct_update_err.vv @@ -0,0 +1,17 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f := Foo{ + name: 'test' + age: 18 + } + f2 := Foo{ + name: 'f2' + ...f + } +} diff --git a/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.out b/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.out new file mode 100644 index 0000000..99bbeaf --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/sum_type_exists_err.vv:1:6: error: cannot register sum type `Option`, another type with this name exists + 1 | type Option = string | int + | ~~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.vv b/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.vv new file mode 100644 index 0000000..b9634c1 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/sum_type_exists_err.vv @@ -0,0 +1 @@ +type Option = string | int diff --git a/v_windows/v/vlib/v/parser/tests/too_many_generics_err.out b/v_windows/v/vlib/v/parser/tests/too_many_generics_err.out new file mode 100644 index 0000000..82cb5fb --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/too_many_generics_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/too_many_generics_err.vv:1:40: error: cannot have more than 9 generic parameters + 1 | fn too_many() {} + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/too_many_generics_err.vv b/v_windows/v/vlib/v/parser/tests/too_many_generics_err.vv new file mode 100644 index 0000000..a42f2c1 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/too_many_generics_err.vv @@ -0,0 +1 @@ +fn too_many() {} diff --git a/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.out b/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.out new file mode 100644 index 0000000..e3ada57 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/type_alias_existing_type_err.vv:3:6: error: cannot register alias `Foo`, another type with this name exists + 1 | struct Foo{} + 2 | + 3 | type Foo = Foo + | ~~~ + 4 | + 5 | fn main() { diff --git a/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.vv b/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.vv new file mode 100644 index 0000000..c1aeeed --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/type_alias_existing_type_err.vv @@ -0,0 +1,7 @@ +struct Foo{} + +type Foo = Foo + +fn main() { + +} diff --git a/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.out b/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.out new file mode 100644 index 0000000..c5b3797 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/type_alias_same_type_err.vv:1:1: error: a type alias can not refer to itself: Foo + 1 | type Foo = Foo + | ~~~~~~~~~~~~~~ + 2 | + 3 | fn main() { diff --git a/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.vv b/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.vv new file mode 100644 index 0000000..4cf7265 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/type_alias_same_type_err.vv @@ -0,0 +1,5 @@ +type Foo = Foo + +fn main() { + +} diff --git a/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.out b/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.out new file mode 100644 index 0000000..8acb09d --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/uncomplete_module_call_err.vv:7:1: error: unexpected token `}`, expecting name + 5 | fn main() { + 6 | os. + 7 | } + | ^ diff --git a/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.vv b/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.vv new file mode 100644 index 0000000..7a3cb06 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/uncomplete_module_call_err.vv @@ -0,0 +1,7 @@ +module main + +import os + +fn main() { + os. +} diff --git a/v_windows/v/vlib/v/parser/tests/unexpected_expr.out b/v_windows/v/vlib/v/parser/tests/unexpected_expr.out new file mode 100644 index 0000000..46c1610 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unexpected_expr.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/unexpected_expr.vv:1:5: error: invalid expression: unexpected keyword `break` + 1 | _ = break + | ~~~~~ diff --git a/v_windows/v/vlib/v/parser/tests/unexpected_expr.vv b/v_windows/v/vlib/v/parser/tests/unexpected_expr.vv new file mode 100644 index 0000000..25b0690 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unexpected_expr.vv @@ -0,0 +1 @@ +_ = break diff --git a/v_windows/v/vlib/v/parser/tests/unexpected_keyword.out b/v_windows/v/vlib/v/parser/tests/unexpected_keyword.out new file mode 100644 index 0000000..08adaf7 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unexpected_keyword.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/unexpected_keyword.vv:5:12: error: expecting method name + 3 | } + 4 | + 5 | fn (s Abc) import(name string) { + | ~~~~~~ + 6 | println(name) + 7 | } diff --git a/v_windows/v/vlib/v/parser/tests/unexpected_keyword.vv b/v_windows/v/vlib/v/parser/tests/unexpected_keyword.vv new file mode 100644 index 0000000..4d60303 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unexpected_keyword.vv @@ -0,0 +1,12 @@ +struct Abc { + x int +} + +fn (s Abc) import(name string) { + println(name) +} + +fn main() { + s := Abc{} + s.import('lib') +} diff --git a/v_windows/v/vlib/v/parser/tests/unnecessary_mut.out b/v_windows/v/vlib/v/parser/tests/unnecessary_mut.out new file mode 100644 index 0000000..4399c9f --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unnecessary_mut.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/unnecessary_mut.vv:3:5: error: remove unnecessary `mut` + 1 | fn main() { + 2 | mut x := 0 + 3 | if mut x == 0 { + | ~~~ + 4 | println(true) + 5 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/unnecessary_mut.vv b/v_windows/v/vlib/v/parser/tests/unnecessary_mut.vv new file mode 100644 index 0000000..6c7a470 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unnecessary_mut.vv @@ -0,0 +1,7 @@ +fn main() { + mut x := 0 + if mut x == 0 { + println(true) + } + _ = x +} diff --git a/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.out b/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.out new file mode 100644 index 0000000..5f7dfee --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/unnecessary_mut_2.vv:2:9: error: unexpected token `true` + 1 | fn main() { + 2 | if mut true { + | ~~~~ + 3 | println(true) + 4 | } \ No newline at end of file diff --git a/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.vv b/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.vv new file mode 100644 index 0000000..0df3f80 --- /dev/null +++ b/v_windows/v/vlib/v/parser/tests/unnecessary_mut_2.vv @@ -0,0 +1,5 @@ +fn main() { + if mut true { + println(true) + } +} diff --git a/v_windows/v/vlib/v/parser/tmpl.v b/v_windows/v/vlib/v/parser/tmpl.v new file mode 100644 index 0000000..1679e5b --- /dev/null +++ b/v_windows/v/vlib/v/parser/tmpl.v @@ -0,0 +1,238 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module parser + +import v.token +import v.errors +import os +import strings + +const tmpl_str_start = "sb.write_string('" + +const tmpl_str_end = "' ) " + +enum State { + html + css // ' { + state = .html + } else if is_html_open_tag('script', line) { + state = .js + } else if line == '' { + state = .html + } + if line.contains('@header') { + position := line.index('@header') or { 0 } + p.error_with_error(errors.Error{ + message: "Please use @include 'header' instead of @header (deprecated)" + file_path: template_file + pos: token.Position{ + len: '@header'.len + line_nr: tline_number + pos: start_of_line_pos + position + last_line: lines.len + } + reporter: .parser + }) + } else if line.contains('@footer') { + position := line.index('@footer') or { 0 } + p.error_with_error(errors.Error{ + message: "Please use @include 'footer' instead of @footer (deprecated)" + file_path: template_file + pos: token.Position{ + len: '@footer'.len + line_nr: tline_number + pos: start_of_line_pos + position + last_line: lines.len + } + reporter: .parser + }) + } + if line.contains('@include ') { + lines.delete(i) + mut file_name := line.split("'")[1] + mut file_ext := os.file_ext(file_name) + if file_ext == '' { + file_ext = '.html' + } + file_name = file_name.replace(file_ext, '') + // relative path, starting with the current folder + mut templates_folder := os.real_path(basepath) + if file_name.contains('/') && file_name.starts_with('/') { + // an absolute path + templates_folder = '' + } + file_path := os.real_path(os.join_path(templates_folder, '$file_name$file_ext')) + $if trace_tmpl ? { + eprintln('>>> basepath: "$basepath" , template_file: "$template_file" , fn_name: "$fn_name" , @include line: "$line" , file_name: "$file_name" , file_ext: "$file_ext" , templates_folder: "$templates_folder" , file_path: "$file_path"') + } + file_content := os.read_file(file_path) or { + position := line.index('@include ') or { 0 } + '@include '.len + p.error_with_error(errors.Error{ + message: 'Reading file $file_name from path: $file_path failed' + details: "Failed to @include '$file_name'" + file_path: template_file + pos: token.Position{ + len: '@include '.len + file_name.len + line_nr: tline_number + pos: start_of_line_pos + position + last_line: lines.len + } + reporter: .parser + }) + '' + } + file_splitted := file_content.split_into_lines().reverse() + for f in file_splitted { + tline_number-- + lines.insert(i, f) + } + i-- + } else if line.contains('@js ') { + pos := line.index('@js') or { continue } + source.write_string('') + } else if line.contains('@css ') { + pos := line.index('@css') or { continue } + source.write_string('') + } else if line.contains('@if ') { + source.writeln(parser.tmpl_str_end) + pos := line.index('@if') or { continue } + source.writeln('if ' + line[pos + 4..] + '{') + source.writeln(parser.tmpl_str_start) + } else if line.contains('@end') { + // Remove new line byte + source.go_back(1) + source.writeln(parser.tmpl_str_end) + source.writeln('}') + source.writeln(parser.tmpl_str_start) + } else if line.contains('@else') { + // Remove new line byte + source.go_back(1) + source.writeln(parser.tmpl_str_end) + source.writeln(' } else { ') + source.writeln(parser.tmpl_str_start) + } else if line.contains('@for') { + source.writeln(parser.tmpl_str_end) + pos := line.index('@for') or { continue } + source.writeln('for ' + line[pos + 4..] + '{') + source.writeln(parser.tmpl_str_start) + } else if state == .html && line.contains('span.') && line.ends_with('{') { + // `span.header {` => `` + class := line.find_between('span.', '{').trim_space() + source.writeln('') + in_span = true + } else if state == .html && line.contains('.') && line.ends_with('{') { + // `.header {` => `
` + class := line.find_between('.', '{').trim_space() + source.writeln('
') + } else if state == .html && line.contains('#') && line.ends_with('{') { + // `#header {` => `