rhai-1.21.0/.cargo_vcs_info.json0000644000000001360000000000100120400ustar { "git": { "sha1": "dfa4255edcde5b26f12133c832e15b723ff8d12f" }, "path_in_vcs": "" }rhai-1.21.0/Cargo.lock0000644000000565500000000000100100260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", "getrandom", "once_cell", "version_check", "zerocopy", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base-x" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clipboard-win" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ "error-code", ] [[package]] name = "const-random" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] [[package]] name = "const-random-macro" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", "tiny-keccak", ] [[package]] name = "core-error" version = "0.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f" dependencies = [ "version_check", ] [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "derive_arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", ] [[package]] name = "discard" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" [[package]] name = "document-features" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" dependencies = [ "litrs", ] [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "error-code" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "fd-lock" version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", "rustix", "windows-sys 0.52.0", ] [[package]] name = "foldhash" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "instant" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", "js-sys", "stdweb", "wasm-bindgen", "web-sys", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litrs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nibble_vec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ "smallvec", ] [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" dependencies = [ "spin", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" dependencies = [ "portable-atomic", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "portable-atomic" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ "endian-type", "nibble_vec", ] [[package]] name = "rhai" version = "1.21.0" dependencies = [ "ahash", "arbitrary", "bitflags", "core-error", "document-features", "getrandom", "hashbrown", "instant", "libm", "no-std-compat", "num-traits", "once_cell", "rhai_codegen", "rmp-serde", "rust_decimal", "rustyline", "serde", "serde_json", "smallvec", "smartstring", "thin-vec", "unicode-xid", ] [[package]] name = "rhai_codegen" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", ] [[package]] name = "rmp" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder", "num-traits", "paste", ] [[package]] name = "rmp-serde" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder", "rmp", "serde", ] [[package]] name = "rust_decimal" version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arbitrary", "arrayvec", "num-traits", ] [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustyline" version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ "bitflags", "cfg-if", "clipboard-win", "fd-lock", "home", "libc", "log", "memchr", "nix", "radix_trie", "unicode-segmentation", "unicode-width", "utf8parse", "winapi", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", ] [[package]] name = "serde_json" version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "sha1" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" dependencies = [ "sha1_smol", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "smartstring" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ "autocfg", "serde", "static_assertions", "version_check", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stdweb" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", "rustc_version", "serde", "serde_json", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", "wasm-bindgen", ] [[package]] name = "stdweb-derive" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", "serde", "serde_derive", "syn 1.0.109", ] [[package]] name = "stdweb-internal-macros" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", "proc-macro2", "quote", "serde", "serde_derive", "serde_json", "sha1", "syn 1.0.109", ] [[package]] name = "stdweb-internal-runtime" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thin-vec" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" dependencies = [ "serde", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.90", ] rhai-1.21.0/Cargo.toml0000644000000113330000000000100100370ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.66.0" name = "rhai" version = "1.21.0" authors = [ "Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968", ] build = false include = [ "/src/**/*", "/Cargo.toml", "/README.md", "LICENSE*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Embedded scripting for Rust" homepage = "https://rhai.rs" readme = "README.md" keywords = [ "scripting", "scripting-engine", "scripting-language", "embedded", ] categories = [ "no-std", "embedded", "wasm", "parser-implementations", ] license = "MIT OR Apache-2.0" repository = "https://github.com/rhaiscript/rhai" resolver = "2" [package.metadata.docs.rs] features = [ "document-features", "metadata", "serde", "internals", "decimal", "debugging", ] [profile.release] lto = "fat" codegen-units = 1 [lib] name = "rhai" path = "src/lib.rs" [[bin]] name = "rhai-dbg" path = "src/bin/rhai-dbg.rs" required-features = ["debugging"] [[bin]] name = "rhai-repl" path = "src/bin/rhai-repl.rs" required-features = ["rustyline"] [[bin]] name = "rhai-run" path = "src/bin/rhai-run.rs" [dependencies.ahash] version = "0.8.2" features = ["compile-time-rng"] default-features = false [dependencies.arbitrary] version = "1.3.2" features = ["derive"] optional = true [dependencies.bitflags] version = "2.0.0" default-features = false [dependencies.core-error] version = "0.0.0" features = ["alloc"] optional = true default-features = false [dependencies.document-features] version = "0.2.0" optional = true [dependencies.getrandom] version = "0.2.0" optional = true [dependencies.hashbrown] version = "0.15.0" optional = true [dependencies.libm] version = "0.2.0" optional = true default-features = false [dependencies.no-std-compat] version = "0.4.1" features = ["alloc"] optional = true default-features = false [dependencies.num-traits] version = "0.2.0" default-features = false [dependencies.once_cell] version = "1.20.1" features = [ "race", "portable-atomic", "alloc", ] default-features = false [dependencies.rhai_codegen] version = "2.1.0" [dependencies.rust_decimal] version = "1.16.0" features = ["maths"] optional = true default-features = false [dependencies.rustyline] version = "13.0.0" optional = true [dependencies.serde] version = "1.0.96" features = [ "derive", "alloc", ] optional = true default-features = false [dependencies.serde_json] version = "1.0.45" features = ["alloc"] optional = true default-features = false [dependencies.smallvec] version = "1.7.0" features = [ "union", "const_new", "const_generics", ] default-features = false [dependencies.smartstring] version = "1.0.0" default-features = false [dependencies.thin-vec] version = "0.2.13" default-features = false [dependencies.unicode-xid] version = "0.2.0" optional = true default-features = false [dev-dependencies.rmp-serde] version = "1.1.0" [dev-dependencies.serde_json] version = "1.0.45" features = ["alloc"] default-features = false [features] bin-features = [ "decimal", "metadata", "serde", "debugging", "rustyline", ] debugging = ["internals"] decimal = ["rust_decimal"] default = [ "std", "ahash/runtime-rng", ] f32_float = [] fuzz = [ "arbitrary", "rust_decimal/rust-fuzz", "serde", ] internals = [] metadata = [ "serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde", ] no_closure = [] no_custom_syntax = [] no_float = [] no_function = ["no_closure"] no_index = [] no_module = [] no_object = [] no_optimize = [] no_position = [] no_std = [ "no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time", ] no_time = [] only_i32 = [] only_i64 = [] serde = [ "dep:serde", "smartstring/serde", "smallvec/serde", "thin-vec/serde", ] std = [ "once_cell/std", "ahash/std", "num-traits/std", "smartstring/std", ] stdweb = [ "getrandom/js", "instant/stdweb", ] sync = ["no-std-compat/compat_sync"] testing-environ = [] unchecked = [] unicode-xid-ident = ["unicode-xid"] unstable = [] wasm-bindgen = [ "getrandom/js", "instant/wasm-bindgen", ] [target.'cfg(target_family = "wasm")'.dependencies.instant] version = "0.1.10" rhai-1.21.0/Cargo.toml.orig000064400000000000000000000155451046102023000135310ustar 00000000000000[workspace] members = [".", "codegen"] [package] name = "rhai" version = "1.21.0" rust-version = "1.66.0" edition = "2018" resolver = "2" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" homepage = "https://rhai.rs" repository = "https://github.com/rhaiscript/rhai" readme = "README.md" license = "MIT OR Apache-2.0" include = ["/src/**/*", "/Cargo.toml", "/README.md", "LICENSE*"] keywords = ["scripting", "scripting-engine", "scripting-language", "embedded"] categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.7.0", default-features = false, features = ["union", "const_new", "const_generics"] } thin-vec = { version = "0.2.13", default-features = false } ahash = { version = "0.8.2", default-features = false, features = ["compile-time-rng"] } num-traits = { version = "0.2.0", default-features = false } once_cell = { version = "1.20.1", default-features = false, features = ["race", "portable-atomic", "alloc"] } bitflags = { version = "2.0.0", default-features = false } smartstring = { version = "1.0.0", default-features = false } rhai_codegen = { version = "2.1.0", path = "codegen" } no-std-compat = { git = "https://gitlab.com/jD91mZM2/no-std-compat", version = "0.4.1", default-features = false, features = ["alloc"], optional = true } libm = { version = "0.2.0", default-features = false, optional = true } hashbrown = { version = "0.15.0", optional = true } core-error = { version = "0.0.0", default-features = false, features = ["alloc"], optional = true } serde = { version = "1.0.96", default-features = false, features = ["derive", "alloc"], optional = true } serde_json = { version = "1.0.45", default-features = false, features = ["alloc"], optional = true } unicode-xid = { version = "0.2.0", default-features = false, optional = true } rust_decimal = { version = "1.16.0", default-features = false, features = ["maths"], optional = true } getrandom = { version = "0.2.0", optional = true } rustyline = { version = "13.0.0", optional = true } document-features = { version = "0.2.0", optional = true } arbitrary = { version = "1.3.2", optional = true, features = ["derive"] } [dev-dependencies] rmp-serde = "1.1.0" serde_json = { version = "1.0.45", default-features = false, features = ["alloc"] } [features] ## Default features: `std`, uses runtime random numbers for hashing. default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng ## Standard features: uses compile-time random number for hashing. std = ["once_cell/std", "ahash/std", "num-traits/std", "smartstring/std"] #! ### Enable Special Functionalities ## Require that all data types implement `Send + Sync` (for multi-threaded usage). sync = ["no-std-compat/compat_sync"] ## Add support for the [`Decimal`](https://crates.io/crates/rust_decimal) data type (acts as the system floating-point type under `no_float`). decimal = ["rust_decimal"] ## Enable serialization/deserialization of Rhai data types via [`serde`](https://crates.io/crates/serde). serde = ["dep:serde", "smartstring/serde", "smallvec/serde", "thin-vec/serde"] ## Allow [Unicode Standard Annex #31](https://unicode.org/reports/tr31/) for identifiers. unicode-xid-ident = ["unicode-xid"] ## Enable functions metadata (including doc-comments); implies [`serde`](#feature-serde). metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] ## Expose internal data structures (e.g. `AST` nodes). internals = [] ## Enable the debugging interface (implies [`internals`](#feature-internals)). debugging = ["internals"] ## Features and dependencies required by `bin` tools: `decimal`, `metadata`, `serde`, `debugging` and [`rustyline`](https://crates.io/crates/rustyline). bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"] ## Enable fuzzing via the [`arbitrary`](https://crates.io/crates/arbitrary) crate. fuzz = ["arbitrary", "rust_decimal/rust-fuzz", "serde"] #! ### System Configuration Features ## Use `f32` instead of `f64` as the system floating-point number type. f32_float = [] ## Use `i32` instead of `i64` for the system integer number type (useful for 32-bit architectures). ## All other integer types (e.g. `u8`) are disabled. only_i32 = [] ## Disable all integer types (e.g. `u8`) other than `i64`. only_i64 = [] #! ### Disable Language Features ## Remove support for floating-point numbers. no_float = [] ## Remove support for arrays and indexing. no_index = [] ## Remove support for custom types, properties, method-style calls and object maps. no_object = [] ## Remove support for time-stamps. no_time = [] ## Remove support for script-defined functions (implies [`no_closure`](#feature-no_closure)). no_function = ["no_closure"] ## Remove support for capturing external variables in anonymous functions (i.e. closures). no_closure = [] ## Remove support for loading external modules. no_module = [] ## Remove support for custom syntax. no_custom_syntax = [] #! ### Performance-Related Features ## Disable all safety checks. unchecked = [] ## Do not track position when parsing. no_position = [] ## Disable the script optimizer. no_optimize = [] #! ### Compiling for `no-std` ## Turn on `no-std` compilation (nightly only). no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"] #! ### JavaScript Interface for WASM ## Use [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen) as JavaScript interface. wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] ## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface. stdweb = ["getrandom/js", "instant/stdweb"] #! ### Features used in testing environments only ## Compiled with a non-stable compiler (i.e. beta or nightly) unstable = [] ## Running under a testing environment. testing-environ = [] [[bin]] name = "rhai-repl" required-features = ["rustyline"] [[bin]] name = "rhai-run" [[bin]] name = "rhai-dbg" required-features = ["debugging"] [profile.release] lto = "fat" codegen-units = 1 #opt-level = "z" # optimize for size #panic = 'abort' # remove stack backtrace for no-std [target.'cfg(target_family = "wasm")'.dependencies] instant = { version = "0.1.10" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] features = ["document-features", "metadata", "serde", "internals", "decimal", "debugging"] [patch.crates-io] # Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows. # This can be moved to the official version when bracketed paste is added. rustyline = { git = "https://github.com/schungx/rustyline", branch = "v13" } # Patch SmartString to resolve an UB issue. #smartstring = { git = "https://github.com/bodil/smartstring", ref = "refs/pull/34/head" } rhai-1.21.0/LICENSE-APACHE.txt000064400000000000000000000241551046102023000134010ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS rhai-1.21.0/LICENSE-MIT.txt000064400000000000000000000020261046102023000131020ustar 00000000000000Permission 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. rhai-1.21.0/README.md000064400000000000000000000217361046102023000121200ustar 00000000000000Rhai - Embedded Scripting for Rust ================================== ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) [![Stars](https://img.shields.io/github/stars/rhaiscript/rhai?style=flat&logo=github)](https://github.com/rhaiscript/rhai) [![License](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg?logo=docs-rs)](https://docs.rs/rhai/) [![VS Code plugin installs](https://img.shields.io/visual-studio-marketplace/i/rhaiscript.vscode-rhai?logo=visual-studio-code&label=vs%20code)](https://marketplace.visualstudio.com/items?itemName=rhaiscript.vscode-rhai) [![Sublime Text package downloads](https://img.shields.io/packagecontrol/dt/Rhai.svg?logo=sublime-text&label=sublime%20text)](https://packagecontrol.io/packages/Rhai) [![Discord Chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord&label=discord)](https://discord.gg/HquqbYFcZ9) [![Zulip Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip)](https://rhaiscript.zulipchat.com) [![Reddit Channel](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit&label=reddit)](https://www.reddit.com/r/Rhai) [![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg)](https://rhai.rs) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. Targets and builds ------------------ * All CPU and O/S targets supported by Rust, including: * WebAssembly (WASM) * `no-std` * Minimum Rust version 1.66.0 Standard features ----------------- * Simple language similar to JavaScript+Rust with [dynamic](https://rhai.rs/book/language/dynamic.html) typing. * Fairly efficient evaluation - 1 million iterations in 0.14 sec on a single-core 2.6 GHz Linux VM running [this script](https://github.com/rhaiscript/rhai/blob/main/scripts/speed_test.rhai). * Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types](https://rhai.rs/book/rust/custom-types.html), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/methods.html) and [indexers](https://rhai.rs/book/rust/indexers.html). * Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/engine/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html). * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html). * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). * Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`thin-vec`](https://crates.io/crates/thin-vec), [`num-traits`](https://crates.io/crates/num-traits), [`once_cell`](https://crates.io/crates/once_cell), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize) (useful for template-based machine-generated scripts). * Easy custom API development via [plugins](https://rhai.rs/book/plugins) system powered by procedural macros. * [Function overloading](https://rhai.rs/book/language/overload.html) and [operator overloading](https://rhai.rs/book/rust/operators.html). * Dynamic dispatch via [function pointers](https://rhai.rs/book/language/fn-ptr.html) with additional support for [currying](https://rhai.rs/book/language/fn-curry.html). * [Closures](https://rhai.rs/book/language/fn-closure.html) (anonymous functions) that can capture shared values. * Some syntactic support for [object-oriented programming (OOP)](https://rhai.rs/book/patterns/oop.html). * Organize code base with dynamically-loadable [modules](https://rhai.rs/book/language/modules), optionally [overriding the resolution process](https://rhai.rs/book/rust/modules/resolvers.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Support for [minimal builds](https://rhai.rs/book/start/builds/minimal.html) by excluding unneeded language [features](https://rhai.rs/book/start/features.html). * A [debugging](https://rhai.rs/book/engine/debugging) interface. Protected against attacks ------------------------- * [_Don't Panic_](https://rhai.rs/book/safety/index.html#dont-panic-guarantee--any-panic-is-a-bug) guarantee - Any panic is a bug. Rhai subscribes to the motto that a library should never panic the host system, and is coded with this in mind. * [Sand-boxed](https://rhai.rs/book/safety/sandbox.html) - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://rhai.rs/book/patterns/control.html). * Rugged - protected against malicious attacks (such as [stack-overflow](https://rhai.rs/book/safety/max-call-stack.html), [over-sized data](https://rhai.rs/book/safety/max-string-size.html), and [runaway scripts](https://rhai.rs/book/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress](https://rhai.rs/book/safety/progress.html) and manually terminate a script run. * Passes Miri. For those who actually want their own language ---------------------------------------------- * Use as a [DSL](https://rhai.rs/book/engine/dsl.html). * Disable certain [language features](https://rhai.rs/book/engine/options.html#language-features) such as [looping](https://rhai.rs/book/engine/disable-looping.html). * Further restrict the language by surgically [disabling keywords and operators](https://rhai.rs/book/engine/disable-keywords.html). * Define [custom operators](https://rhai.rs/book/engine/custom-op.html). * Extend the language with [custom syntax](https://rhai.rs/book/engine/custom-syntax.html). Example ------- The [`scripts`](https://github.com/rhaiscript/rhai/tree/master/scripts) subdirectory contains sample Rhai scripts. Below is the standard _Fibonacci_ example for scripting languages: ```ts // This Rhai script calculates the n-th Fibonacci number using a // really dumb algorithm to test the speed of the scripting engine. const TARGET = 28; const REPEAT = 5; const ANSWER = 317_811; fn fib(n) { if n < 2 { n } else { fib(n-1) + fib(n-2) } } print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`); print("Ready... Go!"); let result; let now = timestamp(); for n in 0..REPEAT { result = fib(TARGET); } print(`Finished. Run time = ${now.elapsed} seconds.`); print(`Fibonacci number #${TARGET} = ${result}`); if result != ANSWER { print(`The answer is WRONG! Should be ${ANSWER}!`); } ``` Project Site ------------ [`rhai.rs`](https://rhai.rs) Documentation ------------- See [_The Rhai Book_](https://rhai.rs/book) for details on the Rhai scripting engine and language. Playground ---------- An [_Online Playground_](https://rhai.rs/playground) is available with syntax-highlighting editor, powered by WebAssembly. Scripts can be evaluated directly from the editor. License ------- Licensed under either of the following, at your choice: * [Apache License, Version 2.0](https://github.com/rhaiscript/rhai/blob/master/LICENSE-APACHE.txt), or * [MIT license](https://github.com/rhaiscript/rhai/blob/master/LICENSE-MIT.txt) Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions. rhai-1.21.0/src/README.md000064400000000000000000000042101046102023000126730ustar 00000000000000Source Structure ================ Root Sources ------------ | Source file | Description | | -------------- | ------------------------------------------------------------------------------- | | `lib.rs` | Crate root | | `engine.rs` | The scripting engine, defines the `Engine` type | | `tokenizer.rs` | Script tokenizer/lexer | | `parser.rs` | Script parser | | `optimizer.rs` | Script optimizer | | `defer.rs` | Utilities for deferred clean-up of resources | | `reify.rs` | Utilities for making generic types concrete | | `tests.rs` | Unit tests (not integration tests, which are in the main `tests` sub-directory) | Sub-Directories --------------- | Sub-directory | Description | | ------------- | ------------------------------------------------------------------ | | `config` | Configuration | | `types` | Common data types (e.g. `Dynamic`, errors) | | `api` | Public API for the scripting engine | | `ast` | AST definition | | `module` | Support for modules | | `packages` | Pre-defined packages | | `func` | Registering and calling functions (native Rust and script-defined) | | `eval` | AST evaluation | | `serde` | Support for [`serde`](https://crates.io/crates/serde) and metadata | | `bin` | Pre-built CLI binaries | rhai-1.21.0/src/api/build_type.rs000064400000000000000000000271371046102023000147100ustar 00000000000000//! Trait to build a custom type for use with [`Engine`]. use crate::func::SendSync; use crate::module::FuncMetadata; use crate::packages::string_basic::{FUNC_TO_DEBUG, FUNC_TO_STRING}; use crate::types::dynamic::Variant; use crate::{Engine, FuncRegistration, Identifier, RhaiNativeFunc, StaticVec}; use std::marker::PhantomData; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::func::register::Mut; /// Trait to build the API of a custom type for use with an [`Engine`] /// (i.e. register the type and its getters, setters, methods, etc.). /// /// # Example /// /// ``` /// # #[cfg(not(feature = "no_object"))] /// # { /// use rhai::{CustomType, TypeBuilder, Engine}; /// /// #[derive(Debug, Clone, Eq, PartialEq)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// fn update(&mut self, offset: i64) { /// self.field += offset; /// } /// fn get_value(&mut self) -> i64 { /// self.field /// } /// fn set_value(&mut self, value: i64) { /// self.field = value; /// } /// } /// /// impl CustomType for TestStruct { /// fn build(mut builder: TypeBuilder) { /// builder /// // Register pretty-print name of the type /// .with_name("TestStruct") /// // Register display functions /// .on_print(|v| format!("TestStruct({})", v.field)) /// .on_debug(|v| format!("{v:?}")) /// // Register a constructor function /// .with_fn("new_ts", Self::new) /// // Register the 'update' method /// .with_fn("update", Self::update) /// // Register the 'value' property /// .with_get_set("value", Self::get_value, Self::set_value); /// } /// } /// /// # fn main() -> Result<(), Box> { /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine.build_type::(); /// /// assert_eq!( /// engine.eval::("let x = new_ts(); x.update(41); print(x); x")?, /// TestStruct { field: 42 } /// ); /// # Ok(()) /// # } /// # } /// ``` pub trait CustomType: Variant + Clone { /// Builds the custom type for use with the [`Engine`]. /// /// Methods, property getters/setters, indexers etc. should be registered in this function. fn build(builder: TypeBuilder); } impl Engine { /// Build the API of a custom type for use with the [`Engine`]. /// /// The custom type must implement [`CustomType`]. #[inline] pub fn build_type(&mut self) -> &mut Self { T::build(TypeBuilder::new(self)); self } } /// Builder to build the API of a custom type for use with an [`Engine`]. /// /// The type is automatically registered when this builder is dropped. /// /// ## Pretty-Print Name /// /// By default the type is registered with [`Engine::register_type`] (i.e. without a pretty-print name). /// /// To define a pretty-print name, call [`with_name`][`TypeBuilder::with_name`], /// to use [`Engine::register_type_with_name`] instead. pub struct TypeBuilder<'a, T: Variant + Clone> { engine: &'a mut Engine, /// Keep the latest registered function(s) in cache to add additional metadata. hashes: StaticVec, _marker: PhantomData, } impl<'a, T: Variant + Clone> TypeBuilder<'a, T> { /// Create a [`TypeBuilder`] linked to a particular [`Engine`] instance. #[inline(always)] fn new(engine: &'a mut Engine) -> Self { Self { engine, hashes: StaticVec::new_const(), _marker: PhantomData, } } } impl TypeBuilder<'_, T> { /// Set a pretty-print name for the `type_of` function. #[inline(always)] pub fn with_name(&mut self, name: &str) -> &mut Self { self.engine.register_type_with_name::(name); self } /// Set a comments for the type. /// `TypeBuilder::with_name` must be called before this function, otherwise /// the comments will not be registered. /// /// Available with the metadata feature only. #[cfg(feature = "metadata")] #[inline(always)] pub fn with_comments(&mut self, comments: &[&str]) -> &mut Self { let namespace = self.engine.global_namespace_mut(); if let Some(name) = namespace .get_custom_type_raw::() .map(|ty| ty.display_name.clone()) { namespace.set_custom_type_with_comments::(name.as_str(), comments); } self } /// Pretty-print this custom type. #[inline(always)] pub fn on_print( &mut self, on_print: impl Fn(&mut T) -> String + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new(FUNC_TO_STRING).register_into_engine(self.engine, on_print); self.hashes.clear(); self.hashes.push(*hash); self } /// Debug-print this custom type. #[inline(always)] pub fn on_debug( &mut self, on_debug: impl Fn(&mut T) -> String + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new(FUNC_TO_DEBUG).register_into_engine(self.engine, on_debug); self.hashes.clear(); self.hashes.push(*hash); self } /// Register a custom method. #[inline(always)] pub fn with_fn( &mut self, name: impl AsRef + Into, method: impl RhaiNativeFunc + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new(name).register_into_engine(self.engine, method); self.hashes.clear(); self.hashes.push(*hash); self } /// _(metadata)_ Add comments to the last registered function. /// Available under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] pub fn and_comments(&mut self, comments: &[&str]) -> &mut Self { let module = self.engine.global_namespace_mut(); for &hash in &self.hashes { if let Some(f) = module.get_fn_metadata_mut(hash) { f.comments = comments.into_iter().map(|&s| s.into()).collect(); } } self } } impl TypeBuilder<'_, T> where T: Variant + Clone + IntoIterator, ::Item: Variant + Clone, { /// Register a type iterator. /// This is an advanced API. #[inline(always)] pub fn is_iterable(&mut self) -> &mut Self { self.engine.register_iterator::(); self } } #[cfg(not(feature = "no_object"))] impl TypeBuilder<'_, T> { /// Register a getter function. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. #[inline(always)] pub fn with_get( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X, R, F> + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new_getter(name).register_into_engine(self.engine, get_fn); self.hashes.clear(); self.hashes.push(*hash); self } /// Register a setter function. /// /// Not available under `no_object`. #[inline(always)] pub fn with_set( &mut self, name: impl AsRef, set_fn: impl RhaiNativeFunc<(Mut, R), 2, X, (), F> + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new_setter(name).register_into_engine(self.engine, set_fn); self.hashes.clear(); self.hashes.push(*hash); self } /// Short-hand for registering both getter and setter functions. /// /// All function signatures must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. #[inline(always)] pub fn with_get_set< const X1: bool, const X2: bool, R: Variant + Clone, const F1: bool, const F2: bool, >( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X1, R, F1> + SendSync + 'static, set_fn: impl RhaiNativeFunc<(Mut, R), 2, X2, (), F2> + SendSync + 'static, ) -> &mut Self { let hash_1 = FuncRegistration::new_getter(&name) .register_into_engine(self.engine, get_fn) .hash; let hash_2 = FuncRegistration::new_setter(&name) .register_into_engine(self.engine, set_fn) .hash; self.hashes.clear(); self.hashes.push(hash_1); self.hashes.push(hash_2); self } } #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl TypeBuilder<'_, T> { /// Register an index getter. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under both `no_index` and `no_object`. #[inline(always)] pub fn with_indexer_get< IDX: Variant + Clone, const X: bool, R: Variant + Clone, const F: bool, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X, R, F> + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new_index_getter().register_into_engine(self.engine, get_fn); self.hashes.clear(); self.hashes.push(*hash); self } /// Register an index setter. /// /// Not available under both `no_index` and `no_object`. #[inline(always)] pub fn with_indexer_set< IDX: Variant + Clone, const X: bool, R: Variant + Clone, const F: bool, >( &mut self, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X, (), F> + SendSync + 'static, ) -> &mut Self { let FuncMetadata { hash, .. } = FuncRegistration::new_index_setter().register_into_engine(self.engine, set_fn); self.hashes.clear(); self.hashes.push(*hash); self } /// Short-hand for registering both index getter and setter functions. /// /// Not available under both `no_index` and `no_object`. #[inline(always)] pub fn with_indexer_get_set< IDX: Variant + Clone, const X1: bool, const X2: bool, R: Variant + Clone, const F1: bool, const F2: bool, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X1, R, F1> + SendSync + 'static, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X2, (), F2> + SendSync + 'static, ) -> &mut Self { let hash_1 = FuncRegistration::new_index_getter() .register_into_engine(self.engine, get_fn) .hash; let hash_2 = FuncRegistration::new_index_setter() .register_into_engine(self.engine, set_fn) .hash; self.hashes.clear(); self.hashes.push(hash_1); self.hashes.push(hash_2); self } } rhai-1.21.0/src/api/call_fn.rs000064400000000000000000000241211046102023000141340ustar 00000000000000//! Module that defines the `call_fn` API of [`Engine`]. #![cfg(not(feature = "no_function"))] use crate::eval::{Caches, GlobalRuntimeState}; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, FnArgsVec, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::type_name, mem}; /// Options for calling a script-defined function via [`Engine::call_fn_with_options`]. #[derive(Debug, Hash)] #[non_exhaustive] pub struct CallFnOptions<'t> { /// A value for binding to the `this` pointer (if any). Default [`None`]. pub this_ptr: Option<&'t mut Dynamic>, /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. Default [`None`]. pub tag: Option, /// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`. pub eval_ast: bool, /// Rewind the [`Scope`] after the function call? Default `true`. pub rewind_scope: bool, } impl Default for CallFnOptions<'_> { #[inline(always)] fn default() -> Self { Self::new() } } impl<'a> CallFnOptions<'a> { /// Create a default [`CallFnOptions`]. #[inline(always)] #[must_use] pub fn new() -> Self { Self { this_ptr: None, tag: None, eval_ast: true, rewind_scope: true, } } /// Bind to the `this` pointer. #[inline(always)] #[must_use] pub fn bind_this_ptr(mut self, value: &'a mut Dynamic) -> Self { self.this_ptr = Some(value); self } /// Set the custom state of this evaluation run (if any). #[inline(always)] #[must_use] pub fn with_tag(mut self, value: impl Variant + Clone) -> Self { self.tag = Some(Dynamic::from(value)); self } /// Set whether to evaluate the [`AST`] to load necessary modules before calling the function. #[inline(always)] #[must_use] pub const fn eval_ast(mut self, value: bool) -> Self { self.eval_ast = value; self } /// Set whether to rewind the [`Scope`] after the function call. #[inline(always)] #[must_use] pub const fn rewind_scope(mut self, value: bool) -> Self { self.rewind_scope = value; self } } impl Engine { /// Call a script function defined in an [`AST`] with multiple arguments. /// /// Not available under `no_function`. /// /// The [`AST`] is evaluated before calling the function. /// This allows a script to load the necessary modules. /// This is usually desired. If not, use [`call_fn_with_options`][Engine::call_fn_with_options] instead. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile(" /// fn add(x, y) { len(x) + y + foo } /// fn add1(x) { len(x) + 1 + foo } /// fn bar() { foo/2 } /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function /// let result = engine.call_fn::(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; /// assert_eq!(result, 168); /// /// let result = engine.call_fn::(&mut scope, &ast, "add1", ( "abc", ) )?; /// // ^^^^^^^^^^ tuple of one /// assert_eq!(result, 46); /// /// let result = engine.call_fn::(&mut scope, &ast, "bar", () )?; /// assert_eq!(result, 21); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn call_fn( &self, scope: &mut Scope, ast: &AST, name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { self.call_fn_with_options(<_>::default(), scope, ast, name, args) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// /// Options are provided via the [`CallFnOptions`] type. /// This is an advanced API. /// /// Not available under `no_function`. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope, Dynamic, CallFnOptions}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile(" /// fn action(x) { this += x; } // function using 'this' pointer /// fn decl(x) { let hello = x; } // declaring variables /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Binding the 'this' pointer /// let mut value = 1_i64.into(); /// let options = CallFnOptions::new().bind_this_ptr(&mut value); /// /// engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?; /// assert_eq!(value.as_int().unwrap(), 42); /// /// // Do not rewind scope /// let options = CallFnOptions::default().rewind_scope(false); /// /// engine.call_fn_with_options(options, &mut scope, &ast, "decl", ( 42_i64, ))?; /// assert_eq!(scope.get_value::("hello").unwrap(), 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn call_fn_with_options( &self, options: CallFnOptions, scope: &mut Scope, ast: &AST, name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); self._call_fn( options, scope, ast, name.as_ref(), arg_values.as_mut(), &mut self.new_global_runtime_state(), &mut Caches::new(), ) .and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = self.map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => self.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE) .into() }) }) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ /// calling this function. #[inline(always)] pub(crate) fn _call_fn( &self, options: CallFnOptions, scope: &mut Scope, ast: &AST, name: &str, arg_values: &mut [Dynamic], global: &mut GlobalRuntimeState, caches: &mut Caches, ) -> RhaiResult { let statements = ast.statements(); let orig_source = mem::replace(&mut global.source, ast.source_raw().cloned()); let orig_lib_len = global.lib.len(); global.lib.push(ast.shared_lib().clone()); let orig_tag = options.tag.map(|v| mem::replace(&mut global.tag, v)); let mut this_ptr = options.this_ptr; #[cfg(not(feature = "no_module"))] let orig_embedded_module_resolver = std::mem::replace(&mut global.embedded_module_resolver, ast.resolver.clone()); let rewind_scope = options.rewind_scope; defer! { global => move |g| { #[cfg(not(feature = "no_module"))] { g.embedded_module_resolver = orig_embedded_module_resolver; } if let Some(orig_tag) = orig_tag { g.tag = orig_tag; } g.lib.truncate(orig_lib_len); g.source = orig_source; }} let global_result = if options.eval_ast && !statements.is_empty() { defer! { scope if rewind_scope => rewind; let orig_scope_len = scope.len(); } self.eval_global_statements(global, caches, scope, statements, true) } else { Ok(Dynamic::UNIT) }; let result = global_result.and_then(|_| { let args = &mut arg_values.iter_mut().collect::>(); // Check for data race. #[cfg(not(feature = "no_closure"))] crate::func::ensure_no_data_race(name, args, false)?; ast.shared_lib() .get_script_fn(name, args.len()) .map_or_else( || Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()), |fn_def| { self.call_script_fn( global, caches, scope, this_ptr.as_deref_mut(), None, fn_def, args, rewind_scope, Position::NONE, ) }, ) .or_else(|err| match *err { ERR::Exit(out, ..) => Ok(out), _ => Err(err), }) }); #[cfg(feature = "debugging")] if self.is_debugger_registered() { global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); self.dbg(global, caches, scope, this_ptr, node)?; } result.map_err(|err| match *err { ERR::ErrorInFunctionCall(fn_name, _, inner_err, _) if fn_name == name => inner_err, _ => err, }) } } rhai-1.21.0/src/api/compile.rs000064400000000000000000000273001046102023000141700ustar 00000000000000//! Module that defines the public compilation API of [`Engine`]. use crate::parser::{ParseResult, ParseState}; use crate::{Engine, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Compile a string into an [`AST`], which can be used later for evaluation. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; /// /// for _ in 0..42 { /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn compile(&self, script: impl AsRef) -> ParseResult { self.compile_with_scope(&Scope::new(), script) } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. This allows functions /// to be optimized based on dynamic global constants. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_optimize"))] /// # { /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant /// /// // Compile a script to an AST and store it for later evaluation. /// // Notice that `Full` optimization is on, so constants are folded /// // into function calls and operators. /// let ast = engine.compile_with_scope(&mut scope, /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 /// )?; /// /// // Normally this would have failed because no scope is passed into the 'eval_ast' /// // call and so the variable 'x' does not exist. Here, it passes because the script /// // has been optimized and all references to 'x' are already gone. /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// # } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn compile_with_scope(&self, scope: &Scope, script: impl AsRef) -> ParseResult { self.compile_scripts_with_scope(scope, &[script]) } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, /// embedding all imported modules. /// /// Not available under `no_module`. /// /// Modules referred by `import` statements containing literal string paths are eagerly resolved /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved /// [modules][crate::Module] and the resolution process is not performed again. #[cfg(not(feature = "no_module"))] pub fn compile_into_self_contained( &self, scope: &Scope, script: impl AsRef, ) -> crate::RhaiResultOf { use crate::{ ast::{ASTNode, Expr, Stmt}, func::native::shared_take_or_clone, module::resolvers::StaticModuleResolver, }; use std::collections::BTreeSet; fn collect_imports( ast: &AST, resolver: &StaticModuleResolver, imports: &mut BTreeSet, ) { ast._walk(&mut |path| match path.last().unwrap() { // Collect all `import` statements with a string constant path ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 { Expr::StringConstant(ref s, ..) if !resolver.contains_path(s) && (imports.is_empty() || !imports.contains(s.as_str())) => { imports.insert(s.clone().into()); true } _ => true, }, _ => true, }); } let mut ast = self.compile_with_scope(scope, script)?; let mut resolver = StaticModuleResolver::new(); let mut imports = BTreeSet::new(); collect_imports(&ast, &resolver, &mut imports); if !imports.is_empty() { while let Some(path) = imports.pop_first() { let path = path.clone(); match self .module_resolver() .resolve_ast(self, None, &path, crate::Position::NONE) { Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports), Some(err) => return err, None => (), } let module = self.module_resolver() .resolve(self, None, &path, crate::Position::NONE)?; let module = shared_take_or_clone(module); resolver.insert(path, module); } ast.resolver = Some(resolver.into()); } Ok(ast) } /// When passed a list of strings, first join the strings into one large script, and then /// compile them into an [`AST`] using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization when using /// [`OptimizationLevel::Full`][crate::OptimizationLevel::Full]. /// /// ## Note /// /// All strings are simply parsed one after another with nothing inserted in between, not even a /// newline or space. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. This allows functions /// to be optimized based on dynamic global constants. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_optimize"))] /// # { /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant /// /// // Compile a script made up of script segments to an AST and store it for later evaluation. /// // Notice that `Full` optimization is on, so constants are folded /// // into function calls and operators. /// let ast = engine.compile_scripts_with_scope(&mut scope, &[ /// "if x > 40", // all 'x' are replaced with 42 /// "{ x } el", /// "se { 0 }" // segments do not need to be valid scripts! /// ])?; /// /// // Normally this would have failed because no scope is passed into the 'eval_ast' /// // call and so the variable 'x' does not exist. Here, it passes because the script /// // has been optimized and all references to 'x' are already gone. /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// # } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn compile_scripts_with_scope>( &self, scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { self.compile_scripts_with_scope_raw( Some(scope), scripts, #[cfg(not(feature = "no_optimize"))] self.optimization_level, ) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][`crate::OptimizationLevel::None`], constants defined within the scope are propagated /// throughout the script _including_ functions. This allows functions to be optimized based on /// dynamic global constants. #[inline] pub(crate) fn compile_scripts_with_scope_raw>( &self, scope: Option<&Scope>, scripts: impl AsRef<[S]>, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { let (stream, tc) = self.lex(scripts.as_ref()); let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(scope, input, tc.clone(), lib); let mut _ast = self.parse( state, #[cfg(not(feature = "no_optimize"))] optimization_level, )?; #[cfg(feature = "metadata")] { let global_comments = &tc.borrow().global_comments; _ast.doc = global_comments.into(); } Ok(_ast) } /// Compile a string containing an expression into an [`AST`], /// which can be used later for evaluation. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile_expression("40 + 2")?; /// /// for _ in 0..42 { /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn compile_expression(&self, script: impl AsRef) -> ParseResult { self.compile_expression_with_scope(&Scope::new(), script) } /// Compile a string containing an expression into an [`AST`] using own scope, /// which can be used later for evaluation. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_optimize"))] /// # { /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 10_i64); // 'x' is a constant /// /// // Compile a script to an AST and store it for later evaluation. /// let ast = engine.compile_expression_with_scope(&mut scope, /// "2 + (x + x) * 2" // all 'x' are replaced with 10 /// )?; /// /// // Normally this would have failed because no scope is passed into the 'eval_ast' /// // call and so the variable 'x' does not exist. Here, it passes because the script /// // has been optimized and all references to 'x' are already gone. /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// # } /// # Ok(()) /// # } /// ``` #[inline] pub fn compile_expression_with_scope( &self, scope: &Scope, script: impl AsRef, ) -> ParseResult { let scripts = [script]; let (stream, t) = self.lex(&scripts); let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(Some(scope), input, t, lib); self.parse_global_expr( state, |_| {}, #[cfg(not(feature = "no_optimize"))] self.optimization_level, ) } } rhai-1.21.0/src/api/custom_syntax.rs000064400000000000000000000370301046102023000154610ustar 00000000000000//! Module implementing custom syntax for [`Engine`]. #![cfg(not(feature = "no_custom_syntax"))] use crate::ast::Expr; use crate::func::SendSync; use crate::parser::ParseResult; use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_identifier, Token}; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{borrow::Borrow, ops::Deref}; /// Collection of special markers for custom syntax definition. pub mod markers { /// Special marker for matching an expression. pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$"; /// Special marker for matching a statements block. pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$"; /// Special marker for matching a function body. #[cfg(not(feature = "no_function"))] pub const CUSTOM_SYNTAX_MARKER_FUNC: &str = "$func$"; /// Special marker for matching an identifier. pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$"; /// Special marker for matching a single symbol. pub const CUSTOM_SYNTAX_MARKER_SYMBOL: &str = "$symbol$"; /// Special marker for matching a string literal. pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$"; /// Special marker for matching an integer number. pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$"; /// Special marker for matching a floating-point number. #[cfg(not(feature = "no_float"))] pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$"; /// Special marker for matching a boolean value. pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$"; /// Special marker for identifying the custom syntax variant. pub const CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT: &str = "$$"; } /// A general expression evaluation trait object. #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult; /// A general expression evaluation trait object. #[cfg(feature = "sync")] pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult + Send + Sync; /// A general expression parsing trait object. #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxParse = dyn Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult>; /// A general expression parsing trait object. #[cfg(feature = "sync")] pub type FnCustomSyntaxParse = dyn Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult> + Send + Sync; /// An expression sub-tree in an [`AST`][crate::AST]. #[derive(Debug, Clone)] pub struct Expression<'a>(&'a Expr); impl<'a> From<&'a Expr> for Expression<'a> { #[inline(always)] fn from(expr: &'a Expr) -> Self { Self(expr) } } impl Expression<'_> { /// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. #[inline(always)] pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult { context.eval_expression_tree(self) } /// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`]. /// /// The following option is available: /// /// * whether to rewind the [`Scope`][crate::Scope] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # WARNING - Low Level API /// /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn eval_with_context_raw( &self, context: &mut EvalContext, rewind_scope: bool, ) -> RhaiResult { #[allow(deprecated)] context.eval_expression_tree_raw(self, rewind_scope) } /// Get the value of this expression if it is a variable name or a string constant. /// /// Returns [`None`] also if the constant is not of the specified type. #[inline(always)] #[must_use] pub fn get_string_value(&self) -> Option<&str> { match self.0 { #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if !x.2.is_empty() => None, Expr::Variable(x, ..) => Some(&x.1), #[cfg(not(feature = "no_function"))] Expr::ThisPtr(..) => Some(crate::engine::KEYWORD_THIS), Expr::StringConstant(x, ..) => Some(x), _ => None, } } /// Get the position of this expression. #[inline(always)] #[must_use] pub const fn position(&self) -> Position { self.0.position() } /// Get the value of this expression if it is a literal constant. /// /// Supports [`INT`][crate::INT], [`FLOAT`][crate::FLOAT], `()`, `char`, `bool` and /// [`ImmutableString`][crate::ImmutableString]. /// /// Returns [`None`] also if the constant is not of the specified type. #[inline] #[must_use] pub fn get_literal_value(&self) -> Option { // Coded this way in order to maximally leverage potentials for dead-code removal. match self.0 { Expr::DynamicConstant(x, ..) => x.clone().try_cast::(), Expr::IntegerConstant(x, ..) => reify! { *x => Option }, #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x, ..) => reify! { *x => Option }, Expr::CharConstant(x, ..) => reify! { *x => Option }, Expr::StringConstant(x, ..) => reify! { x.clone() => Option }, Expr::Variable(x, ..) => reify! { x.1.clone() => Option }, Expr::BoolConstant(x, ..) => reify! { *x => Option }, Expr::Unit(..) => reify! { () => Option }, _ => None, } } } impl Borrow for Expression<'_> { #[inline(always)] #[must_use] fn borrow(&self) -> &Expr { self.0 } } impl AsRef for Expression<'_> { #[inline(always)] #[must_use] fn as_ref(&self) -> &Expr { self.0 } } impl Deref for Expression<'_> { type Target = Expr; #[inline(always)] #[must_use] fn deref(&self) -> &Self::Target { self.0 } } /// Definition of a custom syntax definition. pub struct CustomSyntax { /// A parsing function to return the next token in a custom syntax based on the /// symbols parsed so far. pub parse: Box, /// Custom syntax implementation function. pub func: Box, /// Any variables added/removed in the scope? pub scope_may_be_changed: bool, } impl Engine { /// Register a custom syntax with the [`Engine`]. /// /// Not available under `no_custom_syntax`. /// /// * `symbols` holds a slice of strings that define the custom syntax. /// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax. /// * `func` is the implementation function. /// /// ## Note on `symbols` /// /// * Whitespaces around symbols are stripped. /// * Symbols that are all-whitespace or empty are ignored. /// * If `symbols` does not contain at least one valid token, then the custom syntax registration /// is simply ignored. /// /// ## Note on `scope_may_be_changed` /// /// If `scope_may_be_changed` is `true`, then _size_ of the current [`Scope`][crate::Scope] /// _may_ be modified by this custom syntax. /// /// Adding new variables and/or removing variables count. /// /// Simply modifying the values of existing variables does NOT count, as the _size_ of the /// current [`Scope`][crate::Scope] is unchanged, so `false` should be passed. /// /// Replacing one variable with another (i.e. adding a new variable and removing one variable at /// the same time so that the total _size_ of the [`Scope`][crate::Scope] is unchanged) also /// does NOT count, so `false` should be passed. pub fn register_custom_syntax + Into>( &mut self, symbols: impl AsRef<[S]>, scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> ParseResult<&mut Self> { #[allow(clippy::wildcard_imports)] use markers::*; let mut segments = Vec::::new(); for s in symbols.as_ref() { let s = s.as_ref().trim(); // Skip empty symbols if s.is_empty() { continue; } let token = Token::lookup_symbol_from_syntax(s).or_else(|| { is_reserved_keyword_or_symbol(s) .0 .then(|| Token::Reserved(Box::new(s.into()))) }); let seg = match s { // Markers not in first position CUSTOM_SYNTAX_MARKER_IDENT | CUSTOM_SYNTAX_MARKER_SYMBOL | CUSTOM_SYNTAX_MARKER_EXPR | CUSTOM_SYNTAX_MARKER_BLOCK | CUSTOM_SYNTAX_MARKER_BOOL | CUSTOM_SYNTAX_MARKER_INT | CUSTOM_SYNTAX_MARKER_STRING if !segments.is_empty() => { s.into() } // Markers not in first position #[cfg(not(feature = "no_function"))] CUSTOM_SYNTAX_MARKER_FUNC if !segments.is_empty() => s.into(), // Markers not in first position #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), // Identifier not in first position _ if !segments.is_empty() && is_valid_identifier(s) => s.into(), // Keyword/symbol not in first position _ if !segments.is_empty() && token.is_some() => { // Make it a custom keyword/symbol if it is disabled or reserved if (self.is_symbol_disabled(s) || token.as_ref().map_or(false, Token::is_reserved)) && !self.custom_keywords.contains_key(s) { self.custom_keywords.insert(s.into(), None); } s.into() } // Standard keyword in first position but not disabled _ if segments.is_empty() && token.as_ref().map_or(false, Token::is_standard_keyword) && !self.is_symbol_disabled(s) => { return Err(LexError::ImproperSymbol( s.to_string(), format!("Improper symbol for custom syntax at position #0: '{s}'"), ) .into_err(Position::NONE)); } // Identifier or symbol in first position _ if segments.is_empty() && (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) => { // Make it a custom keyword/symbol if it is disabled or reserved if self.is_symbol_disabled(s) || (token.as_ref().map_or(false, Token::is_reserved) && !self.custom_keywords.contains_key(s)) { self.custom_keywords.insert(s.into(), None); } s.into() } // Anything else is an error _ => { return Err(LexError::ImproperSymbol( s.to_string(), format!( "Improper symbol for custom syntax at position #{}: '{s}'", segments.len() + 1, ), ) .into_err(Position::NONE)); } }; segments.push(seg); } // If the syntax has nothing, just ignore the registration if segments.is_empty() { return Ok(self); } // The first keyword/symbol is the discriminator let key = segments[0].clone(); self.register_custom_syntax_with_state_raw( key, // Construct the parsing function move |stream, _, _| match stream.len() { len if len >= segments.len() => Ok(None), len => Ok(Some(segments[len].clone())), }, scope_may_be_changed, move |context, expressions, _| func(context, expressions), ); Ok(self) } /// Register a custom syntax with the [`Engine`] with custom user-defined state. /// /// Not available under `no_custom_syntax`. /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// * `scope_may_be_changed` specifies variables have been added/removed by this custom syntax. /// * `parse` is the parsing function. /// * `func` is the implementation function. /// /// All custom keywords used as symbols must be manually registered via [`Engine::register_custom_operator`]. /// Otherwise, they won't be recognized. /// /// # Parsing Function Signature /// /// The parsing function has the following signature: /// /// `Fn(symbols: &[ImmutableString], look_ahead: &str, state: &mut Dynamic) -> Result, ParseError>` /// /// where: /// * `symbols`: a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`; /// `$ident$` and other literal markers are replaced by the actual text /// * `look_ahead`: a string slice containing the next symbol that is about to be read /// * `state`: a [`Dynamic`] value that contains a user-defined state /// /// ## Return value /// /// * `Ok(None)`: parsing complete and there are no more symbols to match. /// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`. /// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`][crate::ParseError]. pub fn register_custom_syntax_with_state_raw( &mut self, key: impl Into, parse: impl Fn(&[ImmutableString], &str, &mut Dynamic) -> ParseResult> + SendSync + 'static, scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult + SendSync + 'static, ) -> &mut Self { self.custom_syntax.insert( key.into(), CustomSyntax { parse: Box::new(parse), func: Box::new(func), scope_may_be_changed, } .into(), ); self } } rhai-1.21.0/src/api/definitions/builtin-functions.d.rhai000064400000000000000000000145521046102023000212550ustar 00000000000000/// Display any data to the standard output. /// /// # Example /// /// ```rhai /// let answer = 42; /// /// print(`The Answer is ${answer}`); /// ``` fn print(data: ?); /// Display any data to the standard output in debug format. /// /// # Example /// /// ```rhai /// let answer = 42; /// /// debug(answer); /// ``` fn debug(data: ?); /// Get the type of a value. /// /// # Example /// /// ```rhai /// let x = "hello, world!"; /// /// print(x.type_of()); // prints "string" /// ``` fn type_of(data: ?) -> String; /// Create a function pointer to a named function. /// /// If the specified name is not a valid function name, an error is raised. /// /// # Example /// /// ```rhai /// let f = Fn("foo"); // function pointer to 'foo' /// /// f.call(42); // call: foo(42) /// ``` fn Fn(fn_name: String) -> FnPtr; /// Call a function pointed to by a function pointer, /// passing following arguments to the function call. /// /// If an appropriate function is not found, an error is raised. /// /// # Example /// /// ```rhai /// let f = Fn("foo"); // function pointer to 'foo' /// /// f.call(1, 2, 3); // call: foo(1, 2, 3) /// ``` fn call(fn_ptr: FnPtr, ...args: ?) -> ?; /// Call a function pointed to by a function pointer, binding the `this` pointer /// to the object of the method call, and passing on following arguments to the function call. /// /// If an appropriate function is not found, an error is raised. /// /// # Example /// /// ```rhai /// fn add(x) { /// this + x /// } /// /// let f = Fn("add"); // function pointer to 'add' /// /// let x = 41; /// /// let r = x.call(f, 1); // call: add(1) with 'this' = 'x' /// /// print(r); // prints 42 /// ``` fn call(obj: ?, fn_ptr: FnPtr, ...args: ?) -> ?; /// Curry a number of arguments into a function pointer and return it as a new function pointer. /// /// # Example /// /// ```rhai /// fn foo(x, y, z) { /// x + y + z /// } /// /// let f = Fn("foo"); /// /// let g = f.curry(1, 2); // curried arguments: 1, 2 /// /// g.call(3); // call: foo(1, 2, 3) /// ``` fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr; /// Return `true` if a script-defined function exists with a specified name and /// number of parameters. /// /// # Example /// /// ```rhai /// fn foo(x) { } /// /// print(is_def_fn("foo", 1)); // prints true /// print(is_def_fn("foo", 2)); // prints false /// print(is_def_fn("foo", 0)); // prints false /// print(is_def_fn("bar", 1)); // prints false /// ``` fn is_def_fn(fn_name: String, num_params: int) -> bool; /// Return `true` if a script-defined function exists with a specified name and /// number of parameters, bound to a specified type for `this`. /// /// # Example /// /// ```rhai /// // A method that requires `this` to be `MyType` /// fn MyType.foo(x) { } /// /// print(is_def_fn("MyType", "foo", 1)); // prints true /// print(is_def_fn("foo", 1)); // prints false /// print(is_def_fn("MyType", "foo", 2)); // prints false /// ``` fn is_def_fn(this_type: String, fn_name: String, num_params: int) -> bool; /// Return `true` if a variable matching a specified name is defined. /// /// # Example /// /// ```rhai /// let x = 42; /// /// print(is_def_var("x")); // prints true /// print(is_def_var("foo")); // prints false /// /// { /// let y = 1; /// print(is_def_var("y")); // prints true /// } /// /// print(is_def_var("y")); // prints false /// ``` fn is_def_var(var_name: String) -> bool; /// Return `true` if the variable is shared. /// /// # Example /// /// ```rhai /// let x = 42; /// /// print(is_shared(x)); // prints false /// /// let f = || x; // capture 'x', making it shared /// /// print(is_shared(x)); // prints true /// ``` fn is_shared(variable: ?) -> bool; /// Evaluate a text script within the current scope. /// /// # Example /// /// ```rhai /// let x = 42; /// /// eval("let y = x; x = 123;"); /// /// print(x); // prints 123 /// print(y); // prints 42 /// ``` fn eval(script: String) -> ?; /// Return `true` if the string contains another string. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let x = "hello world!"; /// /// // The 'in' operator calls 'contains' in the background /// if "world" in x { /// print("found!"); /// } /// ``` fn contains(string: String, find: String) -> bool; /// Return `true` if the string contains a character. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let x = "hello world!"; /// /// // The 'in' operator calls 'contains' in the background /// if 'w' in x { /// print("found!"); /// } /// ``` fn contains(string: String, ch: char) -> bool; /// Return `true` if a value falls within the exclusive range. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let r = 1..100; /// /// // The 'in' operator calls 'contains' in the background /// if 42 in r { /// print("found!"); /// } /// ``` fn contains(range: Range, value: int) -> bool; /// Return `true` if a value falls within the inclusive range. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let r = 1..=100; /// /// // The 'in' operator calls 'contains' in the background /// if 42 in r { /// print("found!"); /// } /// ``` fn contains(range: RangeInclusive, value: int) -> bool; /// Return `true` if a key exists within the object map. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// /// // The 'in' operator calls 'contains' in the background /// if "c" in m { /// print("found!"); /// } /// ``` fn contains(map: Map, string: String) -> bool; /// Return `true` if a value is found within the BLOB. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// // The 'in' operator calls 'contains' in the background /// if 3 in b { /// print("found!"); /// } /// ``` fn contains(blob: Blob, value: int) -> bool; rhai-1.21.0/src/api/definitions/builtin-operators.d.rhai000064400000000000000000000142611046102023000212600ustar 00000000000000op ==(int, int) -> bool; op !=(int, int) -> bool; op >(int, int) -> bool; op >=(int, int) -> bool; op <(int, int) -> bool; op <=(int, int) -> bool; op &(int, int) -> int; op |(int, int) -> int; op ^(int, int) -> int; op ..(int, int) -> Range; op ..=(int, int) -> RangeInclusive; op ==(bool, bool) -> bool; op !=(bool, bool) -> bool; op >(bool, bool) -> bool; op >=(bool, bool) -> bool; op <(bool, bool) -> bool; op <=(bool, bool) -> bool; op &(bool, bool) -> bool; op |(bool, bool) -> bool; op ^(bool, bool) -> bool; op ==((), ()) -> bool; op !=((), ()) -> bool; op >((), ()) -> bool; op >=((), ()) -> bool; op <((), ()) -> bool; op <=((), ()) -> bool; op +(int, int) -> int; op -(int, int) -> int; op *(int, int) -> int; op /(int, int) -> int; op %(int, int) -> int; op **(int, int) -> int; op >>(int, int) -> int; op <<(int, int) -> int; op +(float, float) -> float; op -(float, float) -> float; op *(float, float) -> float; op /(float, float) -> float; op %(float, float) -> float; op **(float, float) -> float; op ==(float, float) -> bool; op !=(float, float) -> bool; op >(float, float) -> bool; op >=(float, float) -> bool; op <(float, float) -> bool; op <=(float, float) -> bool; op +(float, int) -> float; op -(float, int) -> float; op *(float, int) -> float; op /(float, int) -> float; op %(float, int) -> float; op **(float, int) -> float; op ==(float, int) -> bool; op !=(float, int) -> bool; op >(float, int) -> bool; op >=(float, int) -> bool; op <(float, int) -> bool; op <=(float, int) -> bool; op +(int, float) -> float; op -(int, float) -> float; op *(int, float) -> float; op /(int, float) -> float; op %(int, float) -> float; op **(int, float) -> float; op ==(int, float) -> bool; op !=(int, float) -> bool; op >(int, float) -> bool; op >=(int, float) -> bool; op <(int, float) -> bool; op <=(int, float) -> bool; op +(Decimal, Decimal) -> Decimal; op -(Decimal, Decimal) -> Decimal; op *(Decimal, Decimal) -> Decimal; op /(Decimal, Decimal) -> Decimal; op %(Decimal, Decimal) -> Decimal; op **(Decimal, Decimal) -> Decimal; op ==(Decimal, Decimal) -> bool; op !=(Decimal, Decimal) -> bool; op >(Decimal, Decimal) -> bool; op >=(Decimal, Decimal) -> bool; op <(Decimal, Decimal) -> bool; op <=(Decimal, Decimal) -> bool; op +(Decimal, int) -> Decimal; op -(Decimal, int) -> Decimal; op *(Decimal, int) -> Decimal; op /(Decimal, int) -> Decimal; op %(Decimal, int) -> Decimal; op **(Decimal, int) -> Decimal; op ==(Decimal, int) -> bool; op !=(Decimal, int) -> bool; op >(Decimal, int) -> bool; op >=(Decimal, int) -> bool; op <(Decimal, int) -> bool; op <=(Decimal, int) -> bool; op +(int, Decimal) -> Decimal; op -(int, Decimal) -> Decimal; op *(int, Decimal) -> Decimal; op /(int, Decimal) -> Decimal; op %(int, Decimal) -> Decimal; op **(int, Decimal) -> Decimal; op ==(int, Decimal) -> bool; op !=(int, Decimal) -> bool; op >(int, Decimal) -> bool; op >=(int, Decimal) -> bool; op <(int, Decimal) -> bool; op <=(int, Decimal) -> bool; op +(String, String) -> String; op -(String, String) -> String; op ==(String, String) -> bool; op !=(String, String) -> bool; op >(String, String) -> bool; op >=(String, String) -> bool; op <(String, String) -> bool; op <=(String, String) -> bool; op +(char, char) -> String; op ==(char, char) -> bool; op !=(char, char) -> bool; op >(char, char) -> bool; op >=(char, char) -> bool; op <(char, char) -> bool; op <=(char, char) -> bool; op +(char, String) -> String; op ==(char, String) -> bool; op !=(char, String) -> bool; op >(char, String) -> bool; op >=(char, String) -> bool; op <(char, String) -> bool; op <=(char, String) -> bool; op +(String, char) -> String; op -(String, char) -> String; op ==(String, char) -> bool; op !=(String, char) -> bool; op >(String, char) -> bool; op >=(String, char) -> bool; op <(String, char) -> bool; op <=(String, char) -> bool; op +((), String) -> String; op ==((), String) -> bool; op !=((), String) -> bool; op >((), String) -> bool; op >=((), String) -> bool; op <((), String) -> bool; op <=((), String) -> bool; op +(String, ()) -> String; op ==(String, ()) -> bool; op !=(String, ()) -> bool; op >(String, ()) -> bool; op >=(String, ()) -> bool; op <(String, ()) -> bool; op <=(String, ()) -> bool; op +(Blob, Blob) -> Blob; op +(Blob, char) -> Blob; op ==(Blob, Blob) -> bool; op !=(Blob, Blob) -> bool; op ==(Range, RangeInclusive) -> bool; op !=(Range, RangeInclusive) -> bool; op ==(RangeInclusive, Range) -> bool; op !=(RangeInclusive, Range) -> bool; op ==(Range, Range) -> bool; op !=(Range, Range) -> bool; op ==(RangeInclusive, RangeInclusive) -> bool; op !=(RangeInclusive, RangeInclusive) -> bool; op ==(?, ?) -> bool; op !=(?, ?) -> bool; op >(?, ?) -> bool; op >=(?, ?) -> bool; op <(?, ?) -> bool; op <=(?, ?) -> bool; op &=(bool, bool); op |=(bool, bool); op +=(int, int); op -=(int, int); op *=(int, int); op /=(int, int); op %=(int, int); op **=(int, int); op >>=(int, int); op <<=(int, int); op &=(int, int); op |=(int, int); op ^=(int, int); op +=(float, float); op -=(float, float); op *=(float, float); op /=(float, float); op %=(float, float); op **=(float, float); op +=(float, int); op -=(float, int); op *=(float, int); op /=(float, int); op %=(float, int); op **=(float, int); op +=(Decimal, Decimal); op -=(Decimal, Decimal); op *=(Decimal, Decimal); op /=(Decimal, Decimal); op %=(Decimal, Decimal); op **=(Decimal, Decimal); op +=(Decimal, int); op -=(Decimal, int); op *=(Decimal, int); op /=(Decimal, int); op %=(Decimal, int); op **=(Decimal, int); op +=(String, String); op -=(String, String); op +=(String, char); op -=(String, char); op +=(char, String); op +=(char, char); op +=(Array, Array); op +=(Array, ?); op +=(Blob, Blob); op +=(Blob, int); op +=(Blob, char); op +=(Blob, String); op in(?, Array) -> bool; op in(String, String) -> bool; op in(char, String) -> bool; op in(int, Range) -> bool; op in(int, RangeInclusive) -> bool; op in(String, Map) -> bool; op in(int, Blob) -> bool; rhai-1.21.0/src/api/definitions/mod.rs000064400000000000000000000436621046102023000156430ustar 00000000000000//! Module that defines functions to output definition files for [`Engine`]. #![cfg(feature = "internals")] #![cfg(feature = "metadata")] use crate::module::FuncMetadata; use crate::tokenizer::{is_valid_function_name, Token}; use crate::{Engine, FnAccess, FnPtr, Module, Scope, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::type_name, borrow::Cow, cmp::Ordering, fmt}; impl Engine { /// _(metadata, internals)_ Return [`Definitions`] that can be used to generate definition files /// for the [`Engine`]. /// Exported under the `internals` and `metadata` feature only. /// /// # Example /// /// ```no_run /// # use rhai::Engine; /// # fn main() -> std::io::Result<()> { /// let engine = Engine::new(); /// /// engine /// .definitions() /// .write_to_dir(".rhai/definitions")?; /// # Ok(()) /// # } /// ``` #[inline(always)] #[must_use] pub fn definitions(&self) -> Definitions { Definitions { engine: self, scope: None, config: <_>::default(), } } /// _(metadata, internals)_ Return [`Definitions`] that can be used to generate definition files /// for the [`Engine`] and the given [`Scope`]. /// Exported under the `internals` and `metadata` feature only. /// /// # Example /// /// ```no_run /// # use rhai::{Engine, Scope}; /// # fn main() -> std::io::Result<()> { /// let engine = Engine::new(); /// let scope = Scope::new(); /// engine /// .definitions_with_scope(&scope) /// .write_to_dir(".rhai/definitions")?; /// # Ok(()) /// # } /// ``` #[inline(always)] #[must_use] pub fn definitions_with_scope<'e>(&'e self, scope: &'e Scope<'e>) -> Definitions<'e> { Definitions { engine: self, scope: Some(scope), config: <_>::default(), } } } /// Internal configuration for module generation. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[non_exhaustive] pub struct DefinitionsConfig { /// Write `module ...` headers in definition files (default `false`). pub write_headers: bool, /// Include standard packages (default `true`). pub include_standard_packages: bool, } impl Default for DefinitionsConfig { #[inline(always)] #[must_use] fn default() -> Self { Self { write_headers: false, include_standard_packages: true, } } } /// _(metadata, internals)_ Definitions helper type to generate definition files based on the /// contents of an [`Engine`]. /// Exported under the `internals` and `metadata` feature only. #[derive(Debug, Clone)] pub struct Definitions<'e> { /// The [`Engine`]. engine: &'e Engine, /// Optional [`Scope`] to include. scope: Option<&'e Scope<'e>>, config: DefinitionsConfig, } impl Definitions<'_> { /// Write `module ...` headers in separate definitions, default `false`. /// /// Headers are always present in content that is expected to be written to a file /// (i.e. `write_to*` and `*_file` methods). #[inline(always)] #[must_use] pub const fn with_headers(mut self, headers: bool) -> Self { self.config.write_headers = headers; self } /// Include standard packages when writing definition files. #[inline(always)] #[must_use] pub const fn include_standard_packages(mut self, include_standard_packages: bool) -> Self { self.config.include_standard_packages = include_standard_packages; self } /// Get the [`Engine`]. #[inline(always)] #[must_use] pub const fn engine(&self) -> &Engine { self.engine } /// Get the [`Scope`]. #[inline(always)] #[must_use] pub const fn scope(&self) -> Option<&Scope> { self.scope } /// Get the configuration. #[inline(always)] #[must_use] pub(crate) const fn config(&self) -> &DefinitionsConfig { &self.config } } impl Definitions<'_> { /// Output all definition files returned from [`iter_files`][Definitions::iter_files] to a /// specified directory. /// /// This function creates the directories and overrides any existing files if needed. #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] #[inline] pub fn write_to_dir(&self, path: impl AsRef) -> std::io::Result<()> { use std::fs; let path = path.as_ref(); fs::create_dir_all(path)?; for (file_name, content) in self.iter_files() { fs::write(path.join(file_name), content)?; } Ok(()) } /// Output all definitions merged into a single file. /// /// The parent directory must exist but the file will be created or overwritten as needed. #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] #[inline(always)] pub fn write_to_file(&self, path: impl AsRef) -> std::io::Result<()> { std::fs::write(path, self.single_file()) } /// Return all definitions merged into a single file. #[inline] #[must_use] pub fn single_file(&self) -> String { let config = DefinitionsConfig { write_headers: false, ..self.config }; let mut def_file = String::from("module static;\n\n"); if config.include_standard_packages { def_file += &Self::builtin_functions_operators_impl(config); def_file += "\n"; def_file += &Self::builtin_functions_impl(config); def_file += "\n"; } def_file += &self.static_module_impl(config); def_file += "\n"; #[cfg(not(feature = "no_module"))] { use std::fmt::Write; for (module_name, module_def) in self.modules_impl(config) { write!( &mut def_file, "\nmodule {module_name} {{\n{module_def}\n}}\n" ) .unwrap(); } def_file += "\n"; } def_file += &self.scope_items_impl(config); def_file += "\n"; def_file } /// Iterate over generated definition files. /// /// The returned iterator yields all definition files as (filename, content) pairs. #[inline] pub fn iter_files(&self) -> impl Iterator + '_ { let config = DefinitionsConfig { write_headers: true, ..self.config }; if config.include_standard_packages { vec![ ( "__builtin__.d.rhai".to_string(), Self::builtin_functions_impl(config), ), ( "__builtin-operators__.d.rhai".to_string(), Self::builtin_functions_operators_impl(config), ), ] } else { vec![] } .into_iter() .chain(std::iter::once(( "__static__.d.rhai".to_string(), self.static_module_impl(config), ))) .chain(self.scope.iter().map(move |_| { ( "__scope__.d.rhai".to_string(), self.scope_items_impl(config), ) })) .chain( #[cfg(not(feature = "no_module"))] { self.modules_impl(config) .map(|(name, def)| (format!("{name}.d.rhai"), def)) }, #[cfg(feature = "no_module")] { std::iter::empty() }, ) } /// Return definitions for all builtin functions. #[inline(always)] #[must_use] pub fn builtin_functions(&self) -> String { Self::builtin_functions_impl(self.config) } /// Return definitions for all builtin functions. #[must_use] fn builtin_functions_impl(config: DefinitionsConfig) -> String { let def = include_str!("builtin-functions.d.rhai"); if config.write_headers { format!("module static;\n\n{def}") } else { def.to_string() } } /// Return definitions for all builtin operators. #[inline(always)] #[must_use] pub fn builtin_functions_operators(&self) -> String { Self::builtin_functions_operators_impl(self.config) } /// Return definitions for all builtin operators. #[must_use] fn builtin_functions_operators_impl(config: DefinitionsConfig) -> String { let def = include_str!("builtin-operators.d.rhai"); if config.write_headers { format!("module static;\n\n{def}") } else { def.to_string() } } /// Return definitions for all globally available functions and constants. #[inline(always)] #[must_use] pub fn static_module(&self) -> String { self.static_module_impl(self.config) } /// Return definitions for all globally available functions and constants. #[must_use] fn static_module_impl(&self, config: DefinitionsConfig) -> String { let mut s = if config.write_headers { String::from("module static;\n\n") } else { String::new() }; self.engine .global_modules .iter() .filter(|&m| self.config.include_standard_packages || !m.is_standard_lib()) .enumerate() .for_each(|(i, m)| { if i > 0 { s += "\n\n"; } m.write_definition(&mut s, self).unwrap(); }); s } /// Return definitions for all items inside the [`Scope`], if any. #[inline(always)] #[must_use] pub fn scope_items(&self) -> String { self.scope_items_impl(self.config) } /// Return definitions for all items inside the [`Scope`], if any. #[must_use] fn scope_items_impl(&self, config: DefinitionsConfig) -> String { let mut s = if config.write_headers { String::from("module static;\n\n") } else { String::new() }; if let Some(scope) = self.scope { scope.write_definition(&mut s, self).unwrap(); } s } /// Return a (module name, definitions) pair for each registered static [module][Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn modules(&self) -> impl Iterator + '_ { self.modules_impl(self.config) } /// Return a (module name, definitions) pair for each registered static [module][Module]. #[cfg(not(feature = "no_module"))] fn modules_impl( &self, config: DefinitionsConfig, ) -> impl Iterator + '_ { let mut m = self .engine .global_sub_modules .iter() .map(move |(name, module)| { ( name.to_string(), if config.write_headers { format!("module {name};\n\n{}", module.definition(self)) } else { module.definition(self) }, ) }) .collect::>(); m.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); m.into_iter() } } impl Module { /// Return definitions for all items inside the [`Module`]. #[cfg(not(feature = "no_module"))] #[must_use] fn definition(&self, def: &Definitions) -> String { let mut s = String::new(); self.write_definition(&mut s, def).unwrap(); s } /// Output definitions for all items inside the [`Module`]. fn write_definition(&self, writer: &mut dyn fmt::Write, def: &Definitions) -> fmt::Result { let mut first = true; let mut submodules = self.iter_sub_modules().collect::>(); submodules.sort_by(|(a, _), (b, _)| a.cmp(b)); for (submodule_name, submodule) in submodules { if !first { writer.write_str("\n\n")?; } first = false; writeln!(writer, "module {submodule_name} {{")?; submodule.write_definition(writer, def)?; writer.write_str("}")?; } let mut vars = self.iter_var().collect::>(); vars.sort_by(|(a, _), (b, _)| a.cmp(b)); for (name, value) in vars { if !first { writer.write_str("\n\n")?; } first = false; let ty = def_type_name(value.type_name(), def.engine); write!(writer, "const {name}: {ty};")?; } let mut func_infos = self.iter_fn().collect::>(); func_infos.sort_by(|(_, a), (_, b)| match a.name.cmp(&b.name) { Ordering::Equal => match a.num_params.cmp(&b.num_params) { Ordering::Equal => (a.params_info.join("") + a.return_type.as_str()) .cmp(&(b.params_info.join("") + b.return_type.as_str())), o => o, }, o => o, }); for (_, f) in func_infos { if !first { writer.write_str("\n\n")?; } first = false; if f.access != FnAccess::Private { let operator = !f.name.contains('$') && !is_valid_function_name(&f.name); #[cfg(not(feature = "no_custom_syntax"))] let operator = operator || def.engine.custom_keywords.contains_key(&f.name); f.write_definition(writer, def, operator)?; } } Ok(()) } } impl FuncMetadata { /// Output definitions for a function. fn write_definition( &self, writer: &mut dyn fmt::Write, def: &Definitions, operator: bool, ) -> fmt::Result { for comment in &*self.comments { writeln!(writer, "{comment}")?; } if operator { writer.write_str("op ")?; } else { writer.write_str("fn ")?; } if let Some(name) = self.name.strip_prefix("get$") { write!(writer, "get {name}(")?; } else if let Some(name) = self.name.strip_prefix("set$") { write!(writer, "set {name}(")?; } else { write!(writer, "{}(", self.name)?; } let mut first = true; for i in 0..self.num_params { if !first { writer.write_str(", ")?; } first = false; let (param_name, param_type) = self.params_info.get(i).map_or(("_", "?".into()), |s| { let mut s = s.splitn(2, ':'); ( s.next().unwrap_or("_").split(' ').last().unwrap(), s.next() .map_or(Cow::Borrowed("?"), |ty| def_type_name(ty, def.engine)), ) }); if operator { write!(writer, "{param_type}")?; } else { write!(writer, "{param_name}: {param_type}")?; } } write!( writer, ") -> {};", def_type_name(&self.return_type, def.engine) )?; Ok(()) } } /// We have to transform some of the types. /// /// This is highly inefficient and is currently based on trial and error with the core packages. /// /// It tries to flatten types, removing `&` and `&mut`, and paths, while keeping generics. /// /// Associated generic types are also rewritten into regular generic type parameters. #[must_use] fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> { let ty = engine.format_param_type(ty).replace("crate::", ""); let ty = ty.strip_prefix("&mut").unwrap_or(&*ty).trim(); let ty = ty.split("::").last().unwrap(); let ty = ty .strip_prefix("RhaiResultOf<") .and_then(|s| s.strip_suffix('>')) .map_or(ty, str::trim); let ty = ty .replace("Iterator(), "int") .replace("FLOAT", "float") .replace("&str", "String") .replace("ImmutableString", "String"); #[cfg(not(feature = "no_float"))] let ty = ty.replace(type_name::(), "float"); #[cfg(not(feature = "no_index"))] let ty = ty.replace(type_name::(), "Array"); #[cfg(not(feature = "no_index"))] let ty = ty.replace(type_name::(), "Blob"); #[cfg(not(feature = "no_object"))] let ty = ty.replace(type_name::(), "Map"); #[cfg(not(feature = "no_time"))] let ty = ty.replace(type_name::(), "Instant"); let ty = ty.replace(type_name::(), "FnPtr"); ty.into() } impl Scope<'_> { /// _(metadata, internals)_ Return definitions for all items inside the [`Scope`]. fn write_definition(&self, writer: &mut dyn fmt::Write, def: &Definitions) -> fmt::Result { let mut first = true; for (name, constant, value) in self.iter_inner() { if !first { writer.write_str("\n\n")?; } first = false; let kw = if constant { Token::Const } else { Token::Let }; let ty = def_type_name(value.type_name(), def.engine); write!(writer, "{kw} {name}: {ty};")?; } Ok(()) } } rhai-1.21.0/src/api/deprecated.rs000064400000000000000000001405211046102023000146410ustar 00000000000000//! Module containing all deprecated API that will be removed in the next major version. use crate::func::{RhaiFunc, SendSync}; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, FnPtr, FuncRegistration, Identifier, ImmutableString, Module, NativeCallContext, Position, RhaiNativeFunc, RhaiResult, RhaiResultOf, Scope, SharedModule, TypeBuilder, AST, }; use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::func::register::Mut; #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] use std::path::PathBuf; impl Engine { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// Not available under `no_std` or `WASM`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run_file`][Engine::run_file] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_file` instead")] #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] #[inline(always)] pub fn consume_file(&self, path: PathBuf) -> RhaiResultOf<()> { self.run_file(path) } /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// Not available under `no_std` or `WASM`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run_file_with_scope`][Engine::run_file_with_scope] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")] #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] #[inline(always)] pub fn consume_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> { self.run_file_with_scope(scope, path) } /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run`][Engine::run] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run` instead")] #[inline(always)] pub fn consume(&self, script: &str) -> RhaiResultOf<()> { self.run(script) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run_with_scope`][Engine::run_with_scope] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_with_scope` instead")] #[inline(always)] pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { self.run_with_scope(scope, script) } /// Evaluate an [`AST`], but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run_ast`][Engine::run_ast] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_ast` instead")] #[inline(always)] pub fn consume_ast(&self, ast: &AST) -> RhaiResultOf<()> { self.run_ast(ast) } /// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// # Deprecated /// /// This method is deprecated. /// Use [`run_ast_with_scope`][Engine::run_ast_with_scope] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `run_ast_with_scope` instead")] #[inline(always)] pub fn consume_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { self.run_ast_with_scope(scope, ast) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments /// and optionally a value for binding to the `this` pointer. /// /// Not available under `no_function`. /// /// There is an option to evaluate the [`AST`] to load necessary modules before calling the function. /// /// # Deprecated /// /// This method is deprecated. /// Use [`call_fn_with_options`][Engine::call_fn_with_options] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `call_fn_with_options` instead")] #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn call_fn_dynamic( &self, scope: &mut Scope, ast: &AST, eval_ast: bool, name: impl AsRef, this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { #[allow(deprecated)] self.call_fn_raw(scope, ast, eval_ast, true, name, this_ptr, arg_values) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// /// Not available under `no_function`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`call_fn_with_options`][Engine::call_fn_with_options] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.12.0", note = "use `call_fn_with_options` instead")] #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn call_fn_raw( &self, scope: &mut Scope, ast: &AST, eval_ast: bool, rewind_scope: bool, name: impl AsRef, this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { let mut arg_values = arg_values; let options = crate::CallFnOptions { this_ptr, eval_ast, rewind_scope, ..<_>::default() }; self._call_fn( options, scope, ast, name.as_ref(), arg_values.as_mut(), &mut self.new_global_runtime_state(), &mut crate::eval::Caches::new(), ) } /// Register a custom fallible function with the [`Engine`]. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_fn`][Engine::register_fn] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `register_fn` instead")] #[inline(always)] pub fn register_result_fn( &mut self, name: impl AsRef + Into, func: impl RhaiNativeFunc + SendSync + 'static, ) -> &mut Self { self.register_fn(name, func) } /// Register a getter function for a member of a registered type with the [`Engine`]. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_get`][Engine::register_get] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `register_get` instead")] #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn register_get_result( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X, R, true> + SendSync + 'static, ) -> &mut Self { self.register_get(name, get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// /// Not available under `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_set`][Engine::register_set] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `register_set` instead")] #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn register_set_result( &mut self, name: impl AsRef, set_fn: impl RhaiNativeFunc<(Mut, V), 2, X, (), true> + SendSync + 'static, ) -> &mut Self { self.register_set(name, set_fn) } /// Register an index getter for a custom type with the [`Engine`]. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under both `no_index` and `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_indexer_get`][Engine::register_indexer_get] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `register_indexer_get` instead")] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get_result< T: Variant + Clone, IDX: Variant + Clone, R: Variant + Clone, const X: bool, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X, R, true> + SendSync + 'static, ) -> &mut Self { self.register_indexer_get(get_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// /// Not available under both `no_index` and `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_indexer_set`][Engine::register_indexer_set] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `register_indexer_set` instead")] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_set_result< T: Variant + Clone, IDX: Variant + Clone, R: Variant + Clone, const X: bool, >( &mut self, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X, (), true> + SendSync + 'static, ) -> &mut Self { self.register_indexer_set(set_fn) } /// Register a custom syntax with the [`Engine`]. /// /// Not available under `no_custom_syntax`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`register_custom_syntax_with_state_raw`][Engine::register_custom_syntax_with_state_raw] instead. /// /// This method will be removed in the next major version. #[deprecated( since = "1.11.0", note = "use `register_custom_syntax_with_state_raw` instead" )] #[inline(always)] #[cfg(not(feature = "no_custom_syntax"))] pub fn register_custom_syntax_raw( &mut self, key: impl Into, parse: impl Fn(&[ImmutableString], &str) -> crate::parser::ParseResult> + SendSync + 'static, scope_may_be_changed: bool, func: impl Fn(&mut crate::EvalContext, &[crate::Expression]) -> RhaiResult + SendSync + 'static, ) -> &mut Self { self.register_custom_syntax_with_state_raw( key, move |keywords, look_ahead, _| parse(keywords, look_ahead), scope_may_be_changed, move |context, expressions, _| func(context, expressions), ) } /// _(internals)_ Evaluate a list of statements with no `this` pointer. /// Exported under the `internals` feature only. /// /// # Deprecated /// /// This method is deprecated. It will be removed in the next major version. #[cfg(feature = "internals")] #[inline(always)] #[deprecated(since = "1.12.0")] pub fn eval_statements_raw( &self, global: &mut crate::eval::GlobalRuntimeState, caches: &mut crate::eval::Caches, scope: &mut Scope, statements: &[crate::ast::Stmt], ) -> RhaiResult { self.eval_global_statements(global, caches, scope, statements, true) } } impl Dynamic { /// Convert the [`Dynamic`] into a [`String`] and return it. /// If there are other references to the same string, a cloned copy is returned. /// Returns the name of the actual type if the cast fails. /// /// # Deprecated /// /// This method is deprecated. /// Use [`into_string`][Dynamic::into_string] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `into_string` instead")] #[inline(always)] pub fn as_string(self) -> Result { self.into_string() } /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. /// Returns the name of the actual type if the cast fails. /// /// # Deprecated /// /// This method is deprecated. /// Use [`into_immutable_string`][Dynamic::into_immutable_string] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.1.0", note = "use `into_immutable_string` instead")] #[inline(always)] pub fn as_immutable_string(self) -> Result { self.into_immutable_string() } } impl AST { /// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions. /// Exported under the `internals` feature only. /// /// Not available under `no_function`. /// /// # Deprecated /// /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] #[cfg(feature = "internals")] #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn lib(&self) -> &crate::Module { self.shared_lib() } /// Clear the documentation. /// Exported under the `metadata` feature only. /// /// # Deprecated /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0")] #[cfg(feature = "metadata")] #[inline(always)] pub fn clear_doc(&mut self) -> &mut Self { self.doc.clear(); self } } impl NativeCallContext<'_> { /// Create a new [`NativeCallContext`]. /// /// # Unimplemented /// /// This method is deprecated. It is no longer implemented and always panics. /// /// Use [`FnPtr::call`] to call a function pointer directly. /// /// This method will be removed in the next major version. #[deprecated( since = "1.3.0", note = "use `FnPtr::call` to call a function pointer directly." )] #[inline(always)] #[must_use] #[allow(unused_variables)] pub fn new(engine: &Engine, fn_name: &str, lib: &[SharedModule]) -> Self { unimplemented!("`NativeCallContext::new` is deprecated"); } /// Call a function inside the call context. /// /// # Deprecated /// /// This method is deprecated. /// Use [`call_fn_raw`][NativeCallContext::call_fn_raw] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.2.0", note = "use `call_fn_raw` instead")] #[inline(always)] pub fn call_fn_dynamic_raw( &self, fn_name: impl AsRef, is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { self.call_fn_raw(fn_name.as_ref(), is_method_call, is_method_call, args) } } #[allow(useless_deprecated)] #[deprecated(since = "1.2.0", note = "explicitly wrap `EvalAltResult` in `Err`")] impl From for RhaiResultOf { #[inline(always)] fn from(err: EvalAltResult) -> Self { Err(err.into()) } } impl FnPtr { /// Get the number of curried arguments. /// /// # Deprecated /// /// This method is deprecated. /// Use [`curry().len()`][`FnPtr::curry`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.8.0", note = "use `curry().len()` instead")] #[inline(always)] #[must_use] pub fn num_curried(&self) -> usize { self.curry().len() } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. /// /// This method is intended for calling a function pointer that is passed into a native Rust /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the /// function. /// /// # Deprecated /// /// This method is deprecated. /// Use [`call_within_context`][FnPtr::call_within_context] or [`call_raw`][FnPtr::call_raw] instead. /// /// This method will be removed in the next major version. #[deprecated( since = "1.3.0", note = "use `call_within_context` or `call_raw` instead" )] #[inline(always)] pub fn call_dynamic( &self, context: &NativeCallContext, this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { self.call_raw(context, this_ptr, arg_values) } } #[cfg(not(feature = "no_custom_syntax"))] impl crate::Expression<'_> { /// If this expression is a variable name, return it. Otherwise [`None`]. /// /// # Deprecated /// /// This method is deprecated. /// Use [`get_string_value`][crate::Expression::get_string_value] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.4.0", note = "use `get_string_value` instead")] #[inline(always)] #[must_use] pub fn get_variable_name(&self) -> Option<&str> { self.get_string_value() } } impl Position { /// Create a new [`Position`]. /// /// If `line` is zero, then [`None`] is returned. /// /// If `position` is zero, then it is at the beginning of a line. /// /// # Deprecated /// /// This function is deprecated. /// Use [`new`][Position::new] (which panics when `line` is zero) instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.6.0", note = "use `new` instead")] #[inline(always)] #[must_use] pub const fn new_const(line: u16, position: u16) -> Option { if line == 0 { None } else { Some(Self::new(line, position)) } } } #[allow(deprecated)] impl TypeBuilder<'_, T> { /// Register a custom fallible function. /// /// # Deprecated /// /// This method is deprecated. /// Use [`with_fn`][`TypeBuilder::with_fn`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `with_fn` instead")] #[inline(always)] pub fn with_result_fn( &mut self, name: S, method: FUNC, ) -> &mut Self where S: AsRef + Into, R: Variant + Clone, FUNC: RhaiNativeFunc + SendSync + 'static, { self.with_fn(name, method) } /// Register a fallible getter function. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`with_get`][`TypeBuilder::with_get`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `with_get` instead")] #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn with_get_result( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X, R, true> + SendSync + 'static, ) -> &mut Self { self.with_get(name, get_fn) } /// Register a fallible setter function. /// /// Not available under `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`with_set`][`TypeBuilder::with_set`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `with_set` instead")] #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn with_set_result( &mut self, name: impl AsRef, set_fn: impl RhaiNativeFunc<(Mut, R), 2, X, (), true> + SendSync + 'static, ) -> &mut Self { self.with_set(name, set_fn) } /// Register an fallible index getter. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under both `no_index` and `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`with_indexer_get`][`TypeBuilder::with_indexer_get`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `with_indexer_get` instead")] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn with_indexer_get_result( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X, R, true> + SendSync + 'static, ) -> &mut Self { self.with_indexer_get(get_fn) } /// Register an fallible index setter. /// /// Not available under both `no_index` and `no_object`. /// /// # Deprecated /// /// This method is deprecated. /// Use [`with_indexer_set`][`TypeBuilder::with_indexer_set`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.9.1", note = "use `with_indexer_set` instead")] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn with_indexer_set_result( &mut self, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X, (), true> + SendSync + 'static, ) -> &mut Self { self.with_indexer_set(set_fn) } } impl Module { /// Create a new [`Module`] with a pre-sized capacity for functions. /// /// # Deprecated /// /// This method is deprecated. /// Use [`new`][`Module::new`] instead. /// /// This method will be removed in the next major version. #[inline(always)] #[must_use] #[deprecated(since = "1.12.0", note = "use `new` instead")] pub const fn with_capacity(_capacity: usize) -> Self { Self::new() } /// Get the display name of a registered custom type. /// /// # Deprecated /// /// This method is deprecated. /// Use [`get_custom_type_display_by_name`][`Module::get_custom_type_display_by_name`] instead. /// /// This method will be removed in the next major version. #[inline(always)] #[must_use] #[deprecated( since = "1.16.0", note = "use `get_custom_type_display_by_name` instead" )] pub fn get_custom_type(&self, type_name: &str) -> Option<&str> { self.get_custom_type_display_by_name(type_name) } /// Set a native Rust function into the [`Module`], returning a [`u64`] hash key. /// /// If there is an existing Rust function of the same hash, it is replaced. /// /// # Deprecated /// /// This method is deprecated. /// Use the [`FuncRegistration`] API instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use the `FuncRegistration` API instead")] #[inline(always)] pub fn set_fn( &mut self, name: impl Into, namespace: FnNamespace, _access: FnAccess, arg_names: Option<&[&str]>, arg_types: impl AsRef<[TypeId]>, func: RhaiFunc, ) -> u64 { let _arg_names = arg_names; let fx = FuncRegistration::new(name).with_namespace(namespace); #[cfg(feature = "metadata")] let fx = if let Some(arg_names) = _arg_names { fx.with_params_info(arg_names) } else { fx }; fx.set_into_module_raw(self, arg_types, func).hash } /// _(metadata)_ Set a native Rust function into the [`Module`], returning a [`u64`] hash key. /// Exported under the `metadata` feature only. /// /// If there is an existing Rust function of the same hash, it is replaced. /// /// # Deprecated /// /// This method is deprecated. /// Use the [`FuncRegistration`] API instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use the `FuncRegistration` API instead")] #[cfg(feature = "metadata")] #[inline(always)] pub fn set_fn_with_comments>( &mut self, name: impl Into, namespace: FnNamespace, _access: FnAccess, arg_names: Option<&[&str]>, arg_types: impl AsRef<[TypeId]>, comments: impl IntoIterator, func: RhaiFunc, ) -> u64 { FuncRegistration::new(name) .with_namespace(namespace) .with_params_info(arg_names.unwrap_or(&[])) .with_comments(comments) .set_into_module_raw(self, arg_types, func) .hash } /// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function. /// Exported under the `metadata` feature only. /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. /// /// # Deprecated /// /// This method is deprecated. /// Use [`update_fn_metadata_with_comments`][`Module::update_fn_metadata_with_comments`] instead. /// /// This method will be removed in the next major version. #[deprecated( since = "1.17.0", note = "use `update_fn_metadata_with_comments` instead" )] #[cfg(feature = "metadata")] #[inline(always)] #[allow(deprecated)] pub fn update_fn_metadata>( &mut self, hash_fn: u64, arg_names: impl IntoIterator, ) -> &mut Self { self.update_fn_metadata_with_comments(hash_fn, arg_names, [""; 0]) } /// _(metadata)_ Generate signatures for all the non-private functions in the [`Module`]. /// Exported under the `metadata` feature only. /// /// # Deprecated /// /// This method is deprecated. /// Use [`gen_fn_signatures_with_mapper`][`Module::gen_fn_signatures_with_mapper`] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use `gen_fn_signatures_with_mapper` instead")] #[cfg(feature = "metadata")] #[inline(always)] pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { self.gen_fn_signatures_with_mapper(Into::into) } } #[cfg(not(feature = "no_index"))] use crate::plugin::*; #[cfg(not(feature = "no_index"))] #[export_module] pub mod deprecated_array_functions { use crate::packages::array_basic::array_functions::*; use crate::{Array, INT}; /// Iterate through all the elements in the array, applying a function named by `mapper` to each /// element in turn, and return the results as a new array. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.map(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `mapper` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn square(x) { x * x } /// /// fn multiply(x, i) { x * i } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.map("square"); /// /// print(y); // prints "[1, 4, 9, 16, 25]" /// /// let y = x.map("multiply"); /// /// print(y); // prints "[0, 2, 6, 12, 20]" /// ``` #[rhai_fn(name = "map", return_raw)] pub fn map_by_fn_name( ctx: NativeCallContext, array: &mut Array, mapper: &str, ) -> RhaiResultOf { map(ctx, array, FnPtr::new(mapper)?) } /// Iterate through all the elements in the array, applying a function named by `filter` to each /// element in turn, and return a copy of all elements (in order) that return `true` as a new array. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.filter(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn screen(x, i) { x * i >= 10 } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.filter("is_odd"); /// /// print(y); // prints "[1, 3, 5]" /// /// let y = x.filter("screen"); /// /// print(y); // prints "[12, 20]" /// ``` #[rhai_fn(name = "filter", return_raw)] pub fn filter_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter_func: &str, ) -> RhaiResultOf { filter(ctx, array, FnPtr::new(filter_func)?) } /// Iterate through all the elements in the array, applying a function named by `filter` to each /// element in turn, and return the index of the first element that returns `true`. /// If no element returns `true`, `-1` is returned. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.index_of(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn is_special(x) { x > 3 } /// /// fn is_dumb(x) { x > 8 } /// /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of("is_special")); // prints 3 /// /// print(x.index_of("is_dumb")); // prints -1 /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { index_of_filter(ctx, array, FnPtr::new(filter)?) } /// Iterate through all the elements in the array, starting from a particular `start` position, /// applying a function named by `filter` to each element in turn, and return the index of the /// first element that returns `true`. If no element returns `true`, `-1` is returned. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.index_of(Fn("fn_name"), start)` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn plural(x) { x > 1 } /// /// fn singular(x) { x < 2 } /// /// fn screen(x, i) { x * i > 20 } /// /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of("plural", 3)); // prints 5: 2 > 1 /// /// print(x.index_of("singular", 9)); // prints -1: nothing < 2 past index 9 /// /// print(x.index_of("plural", 15)); // prints -1: nothing found past end of array /// /// print(x.index_of("plural", -5)); // prints 9: -5 = start from index 8 /// /// print(x.index_of("plural", -99)); // prints 1: -99 = start from beginning /// /// print(x.index_of("screen", 8)); // prints 10: 3 * 10 > 20 /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_by_fn_name_starting_from( ctx: NativeCallContext, array: &mut Array, filter: &str, start: INT, ) -> RhaiResultOf { index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start) } /// Return `true` if any element in the array that returns `true` when applied a function named /// by `filter`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.some(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn large(x) { x > 3 } /// /// fn huge(x) { x > 10 } /// /// fn screen(x, i) { i > x } /// /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.some("large")); // prints true /// /// print(x.some("huge")); // prints false /// /// print(x.some("screen")); // prints true /// ``` #[rhai_fn(name = "some", return_raw, pure)] pub fn some_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { some(ctx, array, FnPtr::new(filter)?) } /// Return `true` if all elements in the array return `true` when applied a function named by `filter`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.all(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.all(|v| v > 3)); // prints false /// /// print(x.all(|v| v > 1)); // prints true /// /// print(x.all(|v, i| i > v)); // prints false /// ``` #[rhai_fn(name = "all", return_raw, pure)] pub fn all_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { all(ctx, array, FnPtr::new(filter)?) } /// Remove duplicated _consecutive_ elements from the array that return `true` when applied a /// function named by `comparer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.dedup(Fn("fn_name"))` instead. /// /// No element is removed if the correct `comparer` function does not exist. /// /// # Function Parameters /// /// * `element1`: copy of the current array element to compare /// * `element2`: copy of the next array element to compare /// /// ## Return Value /// /// `true` if `element1 == element2`, otherwise `false`. /// /// # Example /// /// ```rhai /// fn declining(a, b) { a >= b } /// /// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1]; /// /// x.dedup("declining"); /// /// print(x); // prints "[1, 2, 3, 4]" /// ``` #[rhai_fn(name = "dedup", return_raw)] pub fn dedup_by_fn_name( ctx: NativeCallContext, array: &mut Array, comparer: &str, ) -> RhaiResultOf<()> { dedup_by_comparer(ctx, array, FnPtr::new(comparer)?); Ok(()) } /// Reduce an array by iterating through all elements while applying a function named by `reducer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.reduce(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `reducer` must exist taking these parameters: /// /// * `result`: accumulated result, initially `()` /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn process(r, x) { /// x + (r ?? 0) /// } /// fn process_extra(r, x, i) { /// x + i + (r ?? 0) /// } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce("process"); /// /// print(y); // prints 15 /// /// let y = x.reduce("process_extra"); /// /// print(y); // prints 25 /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_by_fn_name( ctx: NativeCallContext, array: &mut Array, reducer: &str, ) -> RhaiResult { reduce(ctx, array, FnPtr::new(reducer)?) } /// Reduce an array by iterating through all elements while applying a function named by `reducer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.reduce(Fn("fn_name"), initial)` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `reducer` must exist taking these parameters: /// /// * `result`: accumulated result, starting with the value of `initial` /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn process(r, x) { x + r } /// /// fn process_extra(r, x, i) { x + i + r } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce("process", 5); /// /// print(y); // prints 20 /// /// let y = x.reduce("process_extra", 5); /// /// print(y); // prints 30 /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, initial: Dynamic, ) -> RhaiResult { reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } /// Reduce an array by iterating through all elements, in _reverse_ order, /// while applying a function named by `reducer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.reduce_rev(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `reducer` must exist taking these parameters: /// /// * `result`: accumulated result, initially `()` /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn process(r, x) { /// x + (r ?? 0) /// } /// fn process_extra(r, x, i) { /// x + i + (r ?? 0) /// } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce_rev("process"); /// /// print(y); // prints 15 /// /// let y = x.reduce_rev("process_extra"); /// /// print(y); // prints 25 /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_by_fn_name( ctx: NativeCallContext, array: &mut Array, reducer: &str, ) -> RhaiResult { reduce_rev(ctx, array, FnPtr::new(reducer)?) } /// Reduce an array by iterating through all elements, in _reverse_ order, /// while applying a function named by `reducer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.reduce_rev(Fn("fn_name"), initial)` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `reducer` must exist taking these parameters: /// /// * `result`: accumulated result, starting with the value of `initial` /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn process(r, x) { x + r } /// /// fn process_extra(r, x, i) { x + i + r } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce_rev("process", 5); /// /// print(y); // prints 20 /// /// let y = x.reduce_rev("process_extra", 5); /// /// print(y); // prints 30 /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_by_fn_name_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: &str, initial: Dynamic, ) -> RhaiResult { reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial) } /// Sort the array based on applying a function named by `comparer`. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.sort(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `comparer` must exist taking these parameters: /// /// * `element1`: copy of the current array element to compare /// * `element2`: copy of the next array element to compare /// /// ## Return Value /// /// * Any integer > 0 if `element1 > element2` /// * Zero if `element1 == element2` /// * Any integer < 0 if `element1 < element2` /// /// # Example /// /// ```rhai /// fn reverse(a, b) { /// if a > b { /// -1 /// } else if a < b { /// 1 /// } else { /// 0 /// } /// } /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; /// /// x.sort("reverse"); /// /// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" /// ``` #[rhai_fn(name = "sort", return_raw)] pub fn sort_by_fn_name( ctx: NativeCallContext, array: &mut Array, comparer: &str, ) -> RhaiResultOf<()> { sort(ctx, array, FnPtr::new(comparer)?); Ok(()) } /// Remove all elements in the array that returns `true` when applied a function named by `filter` /// and return them as a new array. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.drain(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn small(x) { x < 3 } /// /// fn screen(x, i) { x + i > 5 } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.drain("small"); /// /// print(x); // prints "[3, 4, 5]" /// /// print(y); // prints "[1, 2]" /// /// let z = x.drain("screen"); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(name = "drain", return_raw)] pub fn drain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { drain(ctx, array, FnPtr::new(filter)?) } /// Remove all elements in the array that do not return `true` when applied a function named by /// `filter` and return them as a new array. /// /// # Deprecated API /// /// This method is deprecated and will be removed from the next major version. /// Use `array.retain(Fn("fn_name"))` instead. /// /// # Function Parameters /// /// A function with the same name as the value of `filter` must exist taking these parameters: /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// fn large(x) { x >= 3 } /// /// fn screen(x, i) { x + i <= 5 } /// /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.retain("large"); /// /// print(x); // prints "[3, 4, 5]" /// /// print(y); // prints "[1, 2]" /// /// let z = x.retain("screen"); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(name = "retain", return_raw)] pub fn retain_by_fn_name( ctx: NativeCallContext, array: &mut Array, filter: &str, ) -> RhaiResultOf { retain(ctx, array, FnPtr::new(filter)?) } } pub mod config { pub mod hashing { /// Set the hashing seed. This is used to hash functions etc. /// /// # Deprecated /// /// This method is deprecated. /// Use [`set_hashing_seed`][crate::config::hashing::set_hashing_seed] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use `set_hashing_seed` instead")] #[inline(always)] pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { crate::config::hashing::set_hashing_seed(new_seed) } /// Get the current hashing Seed. /// /// # Deprecated /// /// This method is deprecated. /// Use [`get_hashing_seed`][crate::config::hashing::get_hashing_seed] instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use `get_hashing_seed` instead")] #[inline] #[must_use] pub fn get_ahash_seed() -> &'static Option<[u64; 4]> { crate::config::hashing::get_hashing_seed() } } } rhai-1.21.0/src/api/eval.rs000064400000000000000000000212651046102023000134730ustar 00000000000000//! Module that defines the public evaluation API of [`Engine`]. use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, mem, }; impl Engine { /// Evaluate a string as a script, returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// assert_eq!(engine.eval::("40 + 2")?, 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn eval(&self, script: &str) -> RhaiResultOf { self.eval_with_scope(&mut Scope::new(), script) } /// Evaluate a string as a script with own scope, returning the result value or an error. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. /// /// This allows functions to be optimized based on dynamic global constants. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_with_scope( &self, scope: &mut Scope, script: &str, ) -> RhaiResultOf { let ast = self.compile_scripts_with_scope_raw( Some(scope), [script], #[cfg(not(feature = "no_optimize"))] self.optimization_level, )?; self.eval_ast_with_scope(scope, &ast) } /// Evaluate a string containing an expression, returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn eval_expression(&self, script: &str) -> RhaiResultOf { self.eval_expression_with_scope(&mut Scope::new(), script) } /// Evaluate a string containing an expression with own scope, returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// /// assert_eq!(engine.eval_expression_with_scope::(&mut scope, "x + 2")?, 42); /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_expression_with_scope( &self, scope: &mut Scope, script: &str, ) -> RhaiResultOf { let scripts = [script]; let ast = { let (stream, tc) = self.lex(&scripts); let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(Some(scope), input, tc, lib); // No need to optimize a lone expression self.parse_global_expr( state, |_| {}, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, )? }; self.eval_ast_with_scope(scope, &ast) } /// Evaluate an [`AST`], returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast)?, 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn eval_ast(&self, ast: &AST) -> RhaiResultOf { self.eval_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope, returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("x += 2; x")?; /// /// // Evaluate it /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_ast_with_scope( &self, scope: &mut Scope, ast: &AST, ) -> RhaiResultOf { let global = &mut self.new_global_runtime_state(); let caches = &mut Caches::new(); let result = self.eval_ast_with_scope_raw(global, caches, scope, ast)?; // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { return Ok(reify! { result => T }); } result.try_cast_result::().map_err(|v| { let typename = match type_name::() { typ if typ.contains("::") => self.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType( typename.into(), self.map_type_name(v.type_name()).into(), Position::NONE, ) .into() }) } /// Evaluate an [`AST`] with own scope, returning the result value or an error. #[inline] pub(crate) fn eval_ast_with_scope_raw( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, ast: &AST, ) -> RhaiResult { let orig_source = mem::replace(&mut global.source, ast.source_raw().cloned()); #[cfg(not(feature = "no_function"))] let orig_lib_len = global.lib.len(); #[cfg(not(feature = "no_function"))] global.lib.push(ast.shared_lib().clone()); #[cfg(not(feature = "no_module"))] let orig_embedded_module_resolver = mem::replace(&mut global.embedded_module_resolver, ast.resolver.clone()); defer! { global => move |g| { #[cfg(not(feature = "no_module"))] { g.embedded_module_resolver = orig_embedded_module_resolver; } #[cfg(not(feature = "no_function"))] g.lib.truncate(orig_lib_len); g.source = orig_source; }} let r = self.eval_global_statements(global, caches, scope, ast.statements(), true)?; #[cfg(feature = "debugging")] if self.is_debugger_registered() { global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); self.dbg(global, caches, scope, None, node)?; } Ok(r) } } /// Evaluate a string as a script, returning the result value or an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// let result: i64 = rhai::eval("40 + 2")?; /// /// assert_eq!(result, 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn eval(script: &str) -> RhaiResultOf { Engine::new().eval(script) } rhai-1.21.0/src/api/events.rs000064400000000000000000000423311046102023000140450ustar 00000000000000//! Module that defines public event handlers for [`Engine`]. use crate::func::SendSync; use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf, VarDefInfo}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Provide a callback that will be invoked before each variable access. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(name: &str, index: usize, context: EvalContext) -> Result, Box>` /// /// where: /// * `name`: name of the variable. /// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the /// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in /// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position /// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position /// and a search through the current [`Scope`][crate::Scope] must be performed. /// * `context`: the current [evaluation context][`EvalContext`]. /// /// ## Return value /// /// * `Ok(None)`: continue with normal variable access. /// * `Ok(Some(Dynamic))`: the variable's value. /// /// ## Raising errors /// /// Return `Err(...)` if there is an error. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register a variable resolver. /// engine.on_var(|name, _, _| { /// match name { /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), /// _ => Ok(None) /// } /// }); /// /// engine.eval::("MYSTIC_NUMBER")?; /// /// # Ok(()) /// # } /// ``` #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn on_var( &mut self, callback: impl Fn(&str, usize, EvalContext) -> RhaiResultOf> + SendSync + 'static, ) -> &mut Self { self.resolve_var = Some(Box::new(callback)); self } /// Provide a callback that will be invoked before the definition of each variable . /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(is_runtime: bool, info: VarInfo, context: EvalContext) -> Result>` /// /// where: /// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation. /// * `info`: information on the variable. /// * `context`: the current [evaluation context][`EvalContext`]. /// /// ## Return value /// /// * `Ok(true)`: continue with normal variable definition. /// * `Ok(false)`: deny the variable definition with an [runtime error][crate::EvalAltResult::ErrorRuntime]. /// /// ## Raising errors /// /// Return `Err(...)` if there is an error. /// /// # Example /// /// ```should_panic /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register a variable definition filter. /// engine.on_def_var(|_, info, _| { /// // Disallow defining MYSTIC_NUMBER as a constant /// if info.name() == "MYSTIC_NUMBER" && info.is_const() { /// Ok(false) /// } else { /// Ok(true) /// } /// }); /// /// // The following runs fine: /// engine.eval::("let MYSTIC_NUMBER = 42;")?; /// /// // The following will cause an error: /// engine.eval::("const MYSTIC_NUMBER = 42;")?; /// /// # Ok(()) /// # } /// ``` #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn on_def_var( &mut self, callback: impl Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { self.def_var_filter = Some(Box::new(callback)); self } /// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens. /// Exported under the `internals` feature only. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` /// /// where: /// * [`token`][crate::tokenizer::Token]: current token parsed /// * [`pos`][`Position`]: location of the token /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer /// /// ## Raising errors /// /// It is possible to raise a parsing error by returning /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Token}; /// /// let mut engine = Engine::new(); /// /// // Register a token mapper. /// # #[allow(deprecated)] /// engine.on_parse_token(|token, _, _| { /// match token { /// // Convert all integer literals to strings /// Token::IntegerConstant(n) => Token::StringConstant(Box::new(n.to_string().into())), /// // Convert 'begin' .. 'end' to '{' .. '}' /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, /// // Pass through all other tokens unchanged /// _ => token /// } /// }); /// /// assert_eq!(engine.eval::("42")?, "42"); /// assert_eq!(engine.eval::("true")?, true); /// assert_eq!(engine.eval::("let x = 42; begin let x = 0; end; x")?, "42"); /// /// # Ok(()) /// # } /// ``` #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[cfg(feature = "internals")] #[inline(always)] pub fn on_parse_token( &mut self, callback: impl Fn( crate::tokenizer::Token, Position, &crate::tokenizer::TokenizeState, ) -> crate::tokenizer::Token + SendSync + 'static, ) -> &mut Self { self.token_mapper = Some(Box::new(callback)); self } /// Register a callback for script evaluation progress. /// /// Not available under `unchecked`. /// /// # Callback Function Signature /// /// `Fn(counter: u64) -> Option` /// /// ## Return value /// /// * `None`: continue running the script. /// * `Some(Dynamic)`: terminate the script with the specified exception value. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # use std::sync::RwLock; /// # use std::sync::Arc; /// use rhai::Engine; /// /// let result = Arc::new(RwLock::new(0_u64)); /// let logger = result.clone(); /// /// let mut engine = Engine::new(); /// /// engine.on_progress(move |ops| { /// if ops > 1000 { /// Some("Over 1,000 operations!".into()) /// } else if ops % 123 == 0 { /// *logger.write().unwrap() = ops; /// None /// } else { /// None /// } /// }); /// /// engine.run("for x in 0..5000 { print(x); }") /// .expect_err("should error"); /// /// assert_eq!(*result.read().unwrap(), 984); /// /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn on_progress( &mut self, callback: impl Fn(u64) -> Option + SendSync + 'static, ) -> &mut Self { self.progress = Some(Box::new(callback)); self } /// Override default action of `print` (print to stdout using [`println!`]) /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # use std::sync::RwLock; /// # use std::sync::Arc; /// use rhai::Engine; /// /// let result = Arc::new(RwLock::new(String::new())); /// /// let mut engine = Engine::new(); /// /// // Override action of 'print' function /// let logger = result.clone(); /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); /// /// engine.run("print(40 + 2);")?; /// /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { self.print = Some(Box::new(callback)); self } /// Override default action of `debug` (print to stdout using [`println!`]) /// /// # Callback Function Signature /// /// The callback function signature passed takes the following form: /// /// `Fn(text: &str, source: Option<&str>, pos: Position)` /// /// where: /// * `text`: the text to display /// * `source`: current source, if any /// * [`pos`][`Position`]: location of the `debug` call /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # use std::sync::RwLock; /// # use std::sync::Arc; /// use rhai::Engine; /// /// let result = Arc::new(RwLock::new(String::new())); /// /// let mut engine = Engine::new(); /// /// // Override action of 'print' function /// let logger = result.clone(); /// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str( /// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s) /// )); /// /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; /// ast.set_source("world"); /// engine.run_ast(&ast)?; /// /// #[cfg(not(feature = "no_position"))] /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); /// #[cfg(feature = "no_position")] /// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn on_debug( &mut self, callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, ) -> &mut Self { self.debug = Some(Box::new(callback)); self } /// _(internals)_ Register a callback for access to [`Map`][crate::Map] properties that do not exist. /// Exported under the `internals` feature only. /// /// Not available under `no_index`. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(array: &mut Array, index: INT) -> Result>` /// /// where: /// * `array`: mutable reference to the [`Array`][crate::Array] instance. /// * `index`: numeric index of the array access. /// /// ## Return value /// /// * `Ok(Target)`: [`Target`][crate::Target] of the indexing access. /// /// ## Raising errors /// /// Return `Err(...)` if there is an error, usually /// [`EvalAltResult::ErrorPropertyNotFound`][crate::EvalAltResult::ErrorPropertyNotFound]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # use rhai::{Engine, Dynamic, EvalAltResult, Position}; /// # use std::convert::TryInto; /// let mut engine = Engine::new(); /// /// engine.on_invalid_array_index(|arr, index, _| match index /// { /// -100 => { /// // The array can be modified in place /// arr.push((42_i64).into()); /// // Return a mutable reference to an element /// let value_ref = arr.last_mut().unwrap(); /// value_ref.try_into() /// } /// 100 => { /// let value = Dynamic::from(100_i64); /// // Return a temporary value (not a reference) /// Ok(value.into()) /// } /// // Return the standard out-of-bounds error /// _ => Err(EvalAltResult::ErrorArrayBounds( /// arr.len(), index, Position::NONE /// ).into()), /// }); /// /// let r = engine.eval::(" /// let a = [1, 2, 3]; /// a[-100] += 1; /// a[3] + a[100] /// ")?; /// /// assert_eq!(r, 143); /// # Ok(()) } /// ``` #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] #[inline(always)] pub fn on_invalid_array_index( &mut self, callback: impl for<'a> Fn( &'a mut crate::Array, crate::INT, EvalContext, ) -> RhaiResultOf> + SendSync + 'static, ) -> &mut Self { self.invalid_array_index = Some(Box::new(callback)); self } /// _(internals)_ Register a callback for access to [`Map`][crate::Map] properties that do not exist. /// Exported under the `internals` feature only. /// /// Not available under `no_object`. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(map: &mut Map, prop: &str) -> Result>` /// /// where: /// * `map`: mutable reference to the [`Map`][crate::Map] instance. /// * `prop`: name of the property that does not exist. /// /// ## Return value /// /// * `Ok(Target)`: [`Target`][crate::Target] of the property access. /// /// ## Raising errors /// /// Return `Err(...)` if there is an error, usually [`EvalAltResult::ErrorPropertyNotFound`][crate::EvalAltResult::ErrorPropertyNotFound]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # use rhai::{Engine, Dynamic, EvalAltResult, Position}; /// # use std::convert::TryInto; /// let mut engine = Engine::new(); /// /// engine.on_map_missing_property(|map, prop, _| match prop /// { /// "x" => { /// // The object-map can be modified in place /// map.insert("y".into(), (42_i64).into()); /// // Return a mutable reference to an element /// let value_ref = map.get_mut("y").unwrap(); /// value_ref.try_into() /// } /// "z" => { /// // Return a temporary value (not a reference) /// let value = Dynamic::from(100_i64); /// Ok(value.into()) /// } /// // Return the standard property-not-found error /// _ => Err(EvalAltResult::ErrorPropertyNotFound( /// prop.to_string(), Position::NONE /// ).into()), /// }); /// /// let r = engine.eval::(" /// let obj = #{ a:1, b:2 }; /// obj.x += 1; /// obj.y + obj.z /// ")?; /// /// assert_eq!(r, 143); /// # Ok(()) } /// ``` #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] #[inline(always)] pub fn on_map_missing_property( &mut self, callback: impl for<'a> Fn(&'a mut crate::Map, &str, EvalContext) -> RhaiResultOf> + SendSync + 'static, ) -> &mut Self { self.missing_map_property = Some(Box::new(callback)); self } /// _(debugging)_ Register a callback for debugging. /// Exported under the `debugging` feature only. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[cfg(feature = "debugging")] #[inline(always)] pub fn register_debugger( &mut self, init: impl Fn(&Self, crate::debugger::Debugger) -> crate::debugger::Debugger + SendSync + 'static, callback: impl Fn( EvalContext, crate::eval::DebuggerEvent, crate::ast::ASTNode, Option<&str>, Position, ) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { self.debugger_interface = Some((Box::new(init), Box::new(callback))); self } } rhai-1.21.0/src/api/files.rs000064400000000000000000000206271046102023000136470ustar 00000000000000//! Module that defines the public file-based API of [`Engine`]. #![cfg(not(feature = "no_std"))] #![cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] use crate::types::dynamic::Variant; use crate::{Engine, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fs::File, io::Read, path::{Path, PathBuf}, }; impl Engine { /// Read the contents of a file into a string. fn read_file(path: impl AsRef) -> RhaiResultOf { let path = path.as_ref(); let mut f = File::open(path).map_err(|err| { ERR::ErrorSystem( format!("Cannot open script file '{}'", path.to_string_lossy()), err.into(), ) })?; let mut contents = String::new(); f.read_to_string(&mut contents).map_err(|err| { ERR::ErrorSystem( format!("Cannot read script file '{}'", path.to_string_lossy()), err.into(), ) })?; if contents.starts_with("#!") { // Remove shebang match contents.find('\n') { Some(n) => { contents.drain(0..n).count(); } None => contents.clear(), } }; Ok(contents) } /// Compile a script file into an [`AST`], which can be used later for evaluation. /// /// Not available under `no_std` or `WASM`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Compile a script file to an AST and store it for later evaluation. /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let ast = engine.compile_file("script.rhai".into())?; /// /// for _ in 0..42 { /// engine.eval_ast::(&ast)?; /// } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn compile_file(&self, path: PathBuf) -> RhaiResultOf { self.compile_file_with_scope(&Scope::new(), path) } /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. /// /// Not available under `no_std` or `WASM`. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. /// /// This allows functions to be optimized based on dynamic global constants. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_optimize"))] /// # { /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant /// /// // Compile a script to an AST and store it for later evaluation. /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let ast = engine.compile_file_with_scope(&scope, "script.rhai".into())?; /// /// let result = engine.eval_ast::(&ast)?; /// # } /// # Ok(()) /// # } /// ``` #[inline] pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf { Self::read_file(&path).and_then(|contents| { let mut ast = self.compile_with_scope(scope, contents)?; ast.set_source(path.to_string_lossy().as_ref()); Ok(ast) }) } /// Evaluate a script file, returning the result value or an error. /// /// Not available under `no_std` or `WASM`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let result = engine.eval_file::("script.rhai".into())?; /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_file(&self, path: PathBuf) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } /// Evaluate a script file with own scope, returning the result value or an error. /// /// Not available under `no_std` or `WASM`. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. /// /// This allows functions to be optimized based on dynamic global constants. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 42_i64); /// /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let result = engine.eval_file_with_scope::(&mut scope, "script.rhai".into())?; /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_file_with_scope( &self, scope: &mut Scope, path: PathBuf, ) -> RhaiResultOf { Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents)) } /// Evaluate a file. /// /// Not available under `no_std` or `WASM`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Notice that a PathBuf is required which can easily be constructed from a string. /// engine.run_file("script.rhai".into())?; /// # Ok(()) /// # } /// ``` #[inline] pub fn run_file(&self, path: PathBuf) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run(&contents)) } /// Evaluate a file with own scope. /// /// Not available under `no_std` or `WASM`. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. /// /// This allows functions to be optimized based on dynamic global constants. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 42_i64); /// /// // Notice that a PathBuf is required which can easily be constructed from a string. /// engine.run_file_with_scope(&mut scope, "script.rhai".into())?; /// # Ok(()) /// # } /// ``` #[inline] pub fn run_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> { Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) } } /// Evaluate a script file, returning the result value or an error. /// /// Not available under `no_std` or `WASM`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// let result = rhai::eval_file::("script.rhai")?; /// # Ok(()) /// # } /// ``` #[inline] pub fn eval_file(path: impl AsRef) -> RhaiResultOf { Engine::read_file(path).and_then(|contents| Engine::new().eval::(&contents)) } /// Evaluate a file. /// /// Not available under `no_std` or `WASM`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// rhai::run_file("script.rhai")?; /// # Ok(()) /// # } /// ``` #[inline] pub fn run_file(path: impl AsRef) -> RhaiResultOf<()> { Engine::read_file(path).and_then(|contents| Engine::new().run(&contents)) } rhai-1.21.0/src/api/formatting.rs000064400000000000000000000245771046102023000147270ustar 00000000000000//! Module that provide formatting services to the [`Engine`]. use crate::packages::iter_basic::{BitRange, CharsStream, StepRange}; use crate::parser::{ParseResult, ParseState}; use crate::{ Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError, SmartString, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Map the name of a standard type into a friendly form. #[inline] #[must_use] pub fn map_std_type_name(name: &str, shorthands: bool) -> &str { let name = name.trim(); match name { "INT" => return type_name::(), #[cfg(not(feature = "no_float"))] "FLOAT" => return type_name::(), _ => (), } if name == type_name::() { return if shorthands { "string" } else { "String" }; } if name == type_name::() || name == "ImmutableString" { return if shorthands { "string" } else { "ImmutableString" }; } if name == type_name::<&str>() { return if shorthands { "string" } else { "&str" }; } #[cfg(feature = "decimal")] if name == type_name::() { return if shorthands { "decimal" } else { "Decimal" }; } if name == type_name::() || name == "FnPtr" { return if shorthands { "Fn" } else { "FnPtr" }; } #[cfg(not(feature = "no_index"))] if name == type_name::() || name == "Array" { return if shorthands { "array" } else { "Array" }; } #[cfg(not(feature = "no_index"))] if name == type_name::() || name == "Blob" { return if shorthands { "blob" } else { "Blob" }; } #[cfg(not(feature = "no_object"))] if name == type_name::() || name == "Map" { return if shorthands { "map" } else { "Map" }; } #[cfg(not(feature = "no_time"))] if name == type_name::() || name == "Instant" { return if shorthands { "timestamp" } else { "Instant" }; } if name == type_name::() || name == "ExclusiveRange" { return if shorthands { "range" } else if cfg!(feature = "only_i32") { "Range" } else { "Range" }; } if name == type_name::() || name == "InclusiveRange" { return if shorthands { "range=" } else if cfg!(feature = "only_i32") { "RangeInclusive" } else { "RangeInclusive" }; } if name == type_name::() { return if shorthands { "range" } else { "BitRange" }; } if name == type_name::() { return if shorthands { "range" } else { "CharStream" }; } let step_range_name = type_name::>(); let step_range_name = &step_range_name[..step_range_name.len() - 3]; if name.starts_with(step_range_name) && name.ends_with('>') { return if shorthands { "range" } else { let step_range_name = step_range_name.split("::").last().unwrap(); &step_range_name[..step_range_name.len() - 1] }; } #[cfg(not(feature = "no_float"))] if name == type_name::>() { return if shorthands { "range" } else { "StepFloatRange" }; } #[cfg(feature = "decimal")] if name == type_name::>() { return if shorthands { "range" } else { "StepDecimalRange" }; } name.strip_prefix("rhai::") .map_or(name, |s| map_std_type_name(s, shorthands)) } /// Format a Rust parameter type to be display-friendly. /// /// * `rhai::` prefix is cleared. /// * `()` is cleared. /// * `&mut` is cleared. /// * `INT` and `FLOAT` are expanded. /// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`][crate::RhaiResultOf] are expanded. #[cfg(feature = "metadata")] pub fn format_param_type_for_display(typ: &str, is_return_type: bool) -> std::borrow::Cow { const RESULT_TYPE: &str = "Result<"; const ERROR_TYPE: &str = ",Box>"; const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box>"; const RHAI_RANGE: &str = "ExclusiveRange"; const RHAI_RANGE_TYPE: &str = "Range<"; const RHAI_RANGE_EXPAND: &str = "Range<{}>"; const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange"; const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<"; const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>"; let typ = typ.trim(); if let Some(x) = typ.strip_prefix("rhai::") { return format_param_type_for_display(x, is_return_type); } else if let Some(x) = typ.strip_prefix("&mut ") { let r = format_param_type_for_display(x, false); return if r == x { typ.into() } else { format!("&mut {r}").into() }; } else if typ.contains(' ') { let typ = typ.replace(' ', ""); let r = format_param_type_for_display(&typ, is_return_type); return r.into_owned().into(); } match typ { "" | "()" if is_return_type => "".into(), "INT" => std::any::type_name::().into(), #[cfg(not(feature = "no_float"))] "FLOAT" => std::any::type_name::().into(), RHAI_RANGE => RHAI_RANGE_EXPAND .replace("{}", std::any::type_name::()) .into(), RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND .replace("{}", std::any::type_name::()) .into(), RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => { let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1]; RHAI_RANGE_EXPAND .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => { let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1]; RHAI_INCLUSIVE_RANGE_EXPAND .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => { let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1]; RHAI_RESULT_OF_TYPE_EXPAND .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty if ty.starts_with(RESULT_TYPE) && ty.ends_with(ERROR_TYPE) => { let inner = &ty[RESULT_TYPE.len()..ty.len() - ERROR_TYPE.len()]; RHAI_RESULT_OF_TYPE_EXPAND .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty => ty.into(), } } impl Engine { /// Pretty-print a type name. /// /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// the type name provided for the registration will be used. #[inline] #[must_use] pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.global_modules .iter() .find_map(|m| m.get_custom_type_display_by_name(name)) .or_else(|| { #[cfg(not(feature = "no_module"))] return self .global_sub_modules .values() .find_map(|m| m.get_custom_type_display_by_name(name)); #[cfg(feature = "no_module")] return None; }) .unwrap_or_else(|| map_std_type_name(name, true)) } /// Format a Rust parameter type. /// /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// the type name provided for the registration will be used. /// /// This method properly handles type names beginning with `&mut`. #[cfg(feature = "metadata")] #[inline] #[must_use] pub(crate) fn format_param_type<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> { if let Some(x) = name.strip_prefix("&mut ") { return match self.format_param_type(x) { r if r == x => name.into(), r => format!("&mut {r}").into(), }; } self.map_type_name(name).into() } /// Make a `Box<`[`EvalAltResult`][ERR::ErrorMismatchDataType]`>`. #[cold] #[inline(never)] #[must_use] pub(crate) fn make_type_mismatch_err(&self, typ: &str, pos: Position) -> RhaiError { ERR::ErrorMismatchDataType(self.map_type_name(type_name::()).into(), typ.into(), pos) .into() } /// Compact a script to eliminate insignificant whitespaces and comments. /// /// This is useful to prepare a script for further compressing. /// /// The output script is semantically identical to the input script, except smaller in size. /// /// Unlike other uglifiers and minifiers, this method does not rename variables nor perform any /// optimization on the input script. #[inline] pub fn compact_script(&self, script: impl AsRef) -> ParseResult { let scripts = [script]; let (mut stream, tc) = self.lex(&scripts); tc.borrow_mut().compressed = Some(String::new()); stream.state.last_token = Some(SmartString::new_const()); let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(None, input, tc.clone(), lib); let mut _ast = self.parse( state, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, )?; let guard = tc.borrow(); Ok(guard.compressed.as_ref().unwrap().into()) } } rhai-1.21.0/src/api/json.rs000064400000000000000000000174031046102023000135140ustar 00000000000000//! Module that defines JSON manipulation functions for [`Engine`]. #![cfg(not(feature = "no_object"))] use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::Token; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, LexError, Map, RhaiResultOf}; use std::fmt::Write; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Parse a JSON string into an [object map][Map]. /// /// This is a light-weight alternative to using, say, [`serde_json`](https://crates.io/crates/serde_json) /// to deserialize the JSON. /// /// Not available under `no_object`. /// /// The JSON string must be an object hash. It cannot be a simple primitive value. /// /// Set `has_null` to `true` in order to map `null` values to `()`. /// Setting it to `false` causes a syntax error for any `null` value. /// /// JSON sub-objects are handled transparently. /// /// This function can be used together with [`format_map_as_json`] to work with JSON texts /// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Map}; /// /// let engine = Engine::new(); /// /// let map = engine.parse_json(r#" /// { /// "a": 123, /// "b": 42, /// "c": { /// "x": false, /// "y": true, /// "z": '$' /// }, /// "d": null /// }"#, true)?; /// /// assert_eq!(map.len(), 4); /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); /// assert_eq!(map["d"].as_unit().expect("d should exist"), ()); /// /// let c = map["c"].as_map_ref().expect("c should exist"); /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); /// assert_eq!(c["y"].as_bool().expect("y should be bool"), true); /// assert_eq!(c["z"].as_char().expect("z should be char"), '$'); /// # Ok(()) /// # } /// ``` #[inline] pub fn parse_json(&self, json: impl AsRef, has_null: bool) -> RhaiResultOf { let scripts = [json.as_ref()]; let (stream, tokenizer_control) = self.lex_raw( &scripts, Some(if has_null { &|token, _, _| { match token { // `null` => `()` Token::Reserved(s) if &*s == "null" => Token::Unit, // `{` => `#{` Token::LeftBrace => Token::MapStart, // Disallowed syntax t @ (Token::Unit | Token::MapStart) => Token::LexError( LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new()) .into(), ), Token::InterpolatedString(..) => Token::LexError( LexError::ImproperSymbol( "interpolated string".to_string(), String::new(), ) .into(), ), // All others _ => token, } } } else { &|token, _, _| { match token { Token::Reserved(s) if &*s == "null" => Token::LexError( LexError::ImproperSymbol("null".to_string(), String::new()).into(), ), // `{` => `#{` Token::LeftBrace => Token::MapStart, // Disallowed syntax t @ (Token::Unit | Token::MapStart) => Token::LexError( LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new()) .into(), ), Token::InterpolatedString(..) => Token::LexError( LexError::ImproperSymbol( "interpolated string".to_string(), String::new(), ) .into(), ), // All others _ => token, } } }), ); let ast = { let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(None, input, tokenizer_control, lib); self.parse_global_expr( state, |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, )? }; self.eval_ast(&ast) } } /// Return the JSON representation of an [object map][Map]. /// /// Not available under `no_std`. /// /// This function can be used together with [`Engine::parse_json`] to work with JSON texts /// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Data types /// /// Only the following data types should be kept inside the object map: [`INT`][crate::INT], /// [`FLOAT`][crate::FLOAT], [`ImmutableString`][crate::ImmutableString], `char`, `bool`, `()`, /// [`Array`][crate::Array], [`Map`]. /// /// # Errors /// /// Data types not supported by JSON serialize into formats that may invalidate the result. #[inline] #[must_use] pub fn format_map_as_json(map: &Map) -> String { let mut result = String::from('{'); for (key, value) in map { if result.len() > 1 { result += ","; } write!(result, "{key:?}").unwrap(); result += ":"; format_dynamic_as_json(&mut result, value); } result += "}"; result } /// Format a [`Dynamic`] value as JSON. fn format_dynamic_as_json(result: &mut String, value: &Dynamic) { match value.0 { Union::Unit(..) => *result += "null", Union::FnPtr(ref f, _, _) if f.is_curried() => { *result += "["; write!(result, "{:?}", f.fn_name()).unwrap(); f.iter_curry().for_each(|value| { *result += ","; format_dynamic_as_json(result, value); }); *result += "]"; } Union::FnPtr(ref f, _, _) => write!(result, "{:?}", f.fn_name()).unwrap(), Union::Map(ref m, ..) => *result += &format_map_as_json(m), #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => { *result += "["; for (i, x) in a.iter().enumerate() { if i > 0 { *result += ","; } format_dynamic_as_json(result, x); } *result += "]"; } #[cfg(not(feature = "no_index"))] Union::Blob(ref b, _, _) => { *result += "["; for (i, x) in b.iter().enumerate() { if i > 0 { *result += ","; } write!(result, "{x}").unwrap(); } *result += "]"; } #[cfg(not(feature = "no_closure"))] Union::Shared(ref v, _, _) => { let value = &*crate::func::locked_read(v).unwrap(); format_dynamic_as_json(result, value) } _ => write!(result, "{value:?}").unwrap(), } } rhai-1.21.0/src/api/limits.rs000064400000000000000000000275511046102023000140510ustar 00000000000000//! Settings for [`Engine`]'s limitations. #![cfg(not(feature = "unchecked"))] use crate::Engine; use std::num::{NonZeroU64, NonZeroUsize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(debug_assertions)] pub mod default_limits { /// Maximum levels of function calls. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub const MAX_CALL_STACK_DEPTH: usize = 8; /// Maximum levels of expressions. pub const MAX_EXPR_DEPTH: usize = 32; /// Maximum levels of expressions in function bodies. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; } #[cfg(not(debug_assertions))] pub mod default_limits { /// Maximum levels of function calls. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub const MAX_CALL_STACK_DEPTH: usize = 64; /// Maximum levels of expressions. pub const MAX_EXPR_DEPTH: usize = 64; /// Maximum levels of expressions in function bodies. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; } /// A type containing all the limits imposed by the [`Engine`]. /// /// Not available under `unchecked`. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Limits { /// Maximum levels of call-stack to prevent infinite recursion. /// /// Set to zero to effectively disable function calls. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub call_stack_depth: usize, /// Maximum depth of statements/expressions at global level. pub expr_depth: Option, /// Maximum depth of statements/expressions in functions. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub function_expr_depth: Option, /// Maximum number of operations allowed to run. pub num_operations: Option, /// Maximum number of variables allowed at any instant. /// /// Set to zero to effectively disable creating variables. pub num_variables: usize, /// Maximum number of scripted functions allowed. /// /// Set to zero to effectively disable defining any function. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub num_functions: usize, /// Maximum number of [modules][crate::Module] allowed to load. /// /// Set to zero to effectively disable loading any [module][crate::Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] pub num_modules: usize, /// Maximum length of a [string][crate::ImmutableString]. pub string_len: Option, /// Maximum length of an [array][crate::Array]. /// /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] pub array_size: Option, /// Maximum number of properties in an [object map][crate::Map]. /// /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] pub map_size: Option, } impl Limits { /// Create a new [`Limits`] with default values. /// /// Not available under `unchecked`. #[inline] pub const fn new() -> Self { Self { #[cfg(not(feature = "no_function"))] call_stack_depth: default_limits::MAX_CALL_STACK_DEPTH, expr_depth: NonZeroUsize::new(default_limits::MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] function_expr_depth: NonZeroUsize::new(default_limits::MAX_FUNCTION_EXPR_DEPTH), num_operations: None, num_variables: usize::MAX, #[cfg(not(feature = "no_function"))] num_functions: usize::MAX, #[cfg(not(feature = "no_module"))] num_modules: usize::MAX, string_len: None, #[cfg(not(feature = "no_index"))] array_size: None, #[cfg(not(feature = "no_object"))] map_size: None, } } } impl Default for Limits { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl Engine { /// Is there a data size limit set? #[inline(always)] pub(crate) const fn has_data_size_limit(&self) -> bool { self.limits.string_len.is_some() || { #[cfg(not(feature = "no_index"))] { self.limits.array_size.is_some() } #[cfg(feature = "no_index")] false } || { #[cfg(not(feature = "no_object"))] { self.limits.map_size.is_some() } #[cfg(feature = "no_object")] false } } /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. /// /// Not available under `unchecked` or `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { self.limits.call_stack_depth = levels; self } /// The maximum levels of function calls allowed for a script. /// /// Not available under `unchecked` or `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub const fn max_call_levels(&self) -> usize { self.limits.call_stack_depth } /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). /// /// Not available under `unchecked`. #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { self.limits.num_operations = NonZeroU64::new(operations); self } /// The maximum number of operations allowed for a script to run (0 for unlimited). /// /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_operations(&self) -> u64 { match self.limits.num_operations { Some(n) => n.get(), None => 0, } } /// Set the maximum number of variables allowed for a script at any instant. /// /// Not available under `unchecked`. #[inline(always)] pub fn set_max_variables(&mut self, variables: usize) -> &mut Self { self.limits.num_variables = variables; self } /// The maximum number of variables allowed for a script at any instant. /// /// Not available under `unchecked`. #[inline(always)] #[must_use] pub const fn max_variables(&self) -> usize { self.limits.num_variables } /// Set the maximum number of scripted functions allowed for a script at any instant. /// /// Not available under `unchecked` or `no_function` #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn set_max_functions(&mut self, functions: usize) -> &mut Self { self.limits.num_functions = functions; self } /// The maximum number of scripted functions allowed for a script at any instant. /// /// Not available under `unchecked` or `no_function` #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub const fn max_functions(&self) -> usize { self.limits.num_functions } /// Set the maximum number of imported [modules][crate::Module] allowed for a script. /// /// Not available under `unchecked` or `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { self.limits.num_modules = modules; self } /// The maximum number of imported [modules][crate::Module] allowed for a script. /// /// Not available under `unchecked` or `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub const fn max_modules(&self) -> usize { self.limits.num_modules } /// Set the depth limits for expressions (0 for unlimited). /// /// Not available under `unchecked`. #[inline(always)] pub fn set_max_expr_depths( &mut self, max_expr_depth: usize, #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, ) -> &mut Self { self.limits.expr_depth = NonZeroUsize::new(max_expr_depth); #[cfg(not(feature = "no_function"))] { self.limits.function_expr_depth = NonZeroUsize::new(max_function_expr_depth); } self } /// The depth limit for expressions (0 for unlimited). /// /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_expr_depth(&self) -> usize { match self.limits.expr_depth { Some(n) => n.get(), None => 0, } } /// The depth limit for expressions in functions (0 for unlimited). /// /// Not available under `unchecked` or `no_function`. #[inline] #[must_use] pub const fn max_function_expr_depth(&self) -> usize { #[cfg(not(feature = "no_function"))] return match self.limits.function_expr_depth { Some(n) => n.get(), None => 0, }; #[cfg(feature = "no_function")] return 0; } /// Set the maximum length, in bytes, of [strings][crate::ImmutableString] (0 for unlimited). /// /// Not available under `unchecked`. #[inline(always)] pub fn set_max_string_size(&mut self, max_len: usize) -> &mut Self { self.limits.string_len = NonZeroUsize::new(max_len); self } /// The maximum length, in bytes, of [strings][crate::ImmutableString] (0 for unlimited). /// /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_string_size(&self) -> usize { match self.limits.string_len { Some(n) => n.get(), None => 0, } } /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). /// /// Not available under `unchecked` or `no_index`. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { self.limits.array_size = NonZeroUsize::new(max_size); self } /// The maximum length of [arrays][crate::Array] (0 for unlimited). /// /// Not available under `unchecked` or `no_index`. #[inline] #[must_use] pub const fn max_array_size(&self) -> usize { #[cfg(not(feature = "no_index"))] return match self.limits.array_size { Some(n) => n.get(), None => 0, }; #[cfg(feature = "no_index")] return 0; } /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// /// Not available under `unchecked` or `no_object`. #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { self.limits.map_size = NonZeroUsize::new(max_size); self } /// The maximum size of [object maps][crate::Map] (0 for unlimited). /// /// Not available under `unchecked` or `no_object`. #[inline] #[must_use] pub const fn max_map_size(&self) -> usize { #[cfg(not(feature = "no_object"))] return match self.limits.map_size { Some(n) => n.get(), None => 0, }; #[cfg(feature = "no_object")] return 0; } } rhai-1.21.0/src/api/mod.rs000064400000000000000000000176751046102023000133350ustar 00000000000000//! Module defining the public API of the Rhai engine. pub mod eval; pub mod run; pub mod compile; pub mod json; pub mod files; pub mod register; pub mod call_fn; pub mod options; pub mod optimize; pub mod limits; pub mod events; pub mod formatting; pub mod custom_syntax; pub mod build_type; #[cfg(feature = "metadata")] pub mod definitions; pub mod deprecated; use crate::func::{locked_read, locked_write}; use crate::types::StringsInterner; use crate::{Dynamic, Engine, Identifier}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Default limits. pub mod default_limits { #[cfg(not(feature = "unchecked"))] #[cfg(feature = "internals")] pub use super::limits::default_limits::*; /// Maximum number of parameters in functions with [`Dynamic`][crate::Dynamic] support. pub const MAX_DYNAMIC_PARAMETERS: usize = 16; /// Maximum number of strings interned. pub const MAX_STRINGS_INTERNED: usize = 256; } impl Engine { /// Set the maximum number of strings to be interned. #[inline(always)] pub fn set_max_strings_interned(&mut self, max: usize) -> &mut Self { if max == 0 { self.interned_strings = None; } else if let Some(ref interner) = self.interned_strings { if let Some(mut guard) = locked_write(interner) { guard.set_max(max); } } else { self.interned_strings = Some(StringsInterner::new(max).into()); } self } /// The maximum number of strings to be interned. #[inline(always)] #[must_use] pub fn max_strings_interned(&self) -> usize { self.interned_strings.as_ref().map_or(0, |interner| { locked_read(interner).map_or(0, |guard| guard.max()) }) } /// The module resolution service used by the [`Engine`]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub fn module_resolver(&self) -> &dyn crate::ModuleResolver { static DUMMY_RESOLVER: crate::module::resolvers::DummyModuleResolver = crate::module::resolvers::DummyModuleResolver; self.module_resolver.as_deref().unwrap_or(&DUMMY_RESOLVER) } /// Set the module resolution service used by the [`Engine`]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn set_module_resolver( &mut self, resolver: impl crate::ModuleResolver + 'static, ) -> &mut Self { self.module_resolver = Some(Box::new(resolver)); self } /// Disable a particular keyword or operator in the language. /// /// # Examples /// /// The following will raise an error during parsing because the `if` keyword is disabled and is /// recognized as a reserved symbol! /// /// ```rust,should_panic /// # fn main() -> Result<(), rhai::ParseError> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// engine.disable_symbol("if"); // disable the 'if' keyword /// /// engine.compile("let x = if true { 42 } else { 0 };")?; /// // ^ 'if' is rejected as a reserved symbol /// # Ok(()) /// # } /// ``` /// /// The following will raise an error during parsing because the `+=` operator is disabled. /// /// ```rust,should_panic /// # fn main() -> Result<(), rhai::ParseError> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// engine.disable_symbol("+="); // disable the '+=' operator /// /// engine.compile("let x = 42; x += 1;")?; /// // ^ unknown operator /// # Ok(()) /// # } /// ``` #[inline] pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { self.disabled_symbols.insert(symbol.into()); self } /// Is a particular keyword or operator disabled? /// /// # Examples /// /// ```rust /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// engine.disable_symbol("if"); // disable the 'if' keyword /// /// assert!(engine.is_symbol_disabled("if")); /// ``` #[inline(always)] #[must_use] pub fn is_symbol_disabled(&self, symbol: &str) -> bool { self.disabled_symbols.contains(symbol) } /// Register a custom operator with a precedence into the language. /// /// Not available under `no_custom_syntax`. /// /// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword. /// /// The precedence cannot be zero. /// /// # Example /// /// ```rust /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register a custom operator called '#' and give it /// // a precedence of 160 (i.e. between +|- and *|/). /// engine.register_custom_operator("#", 160).expect("should succeed"); /// /// // Register a binary function named '#' /// engine.register_fn("#", |x: i64, y: i64| (x * y) - (x + y)); /// /// assert_eq!( /// engine.eval_expression::("1 + 2 * 3 # 4 - 5 / 6")?, /// 15 /// ); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_custom_syntax"))] pub fn register_custom_operator( &mut self, keyword: impl AsRef, precedence: u8, ) -> Result<&mut Self, String> { use crate::tokenizer::Token; let precedence = crate::engine::Precedence::new(precedence) .ok_or_else(|| "precedence cannot be zero".to_string())?; let keyword = keyword.as_ref(); match Token::lookup_symbol_from_syntax(keyword) { // Standard identifiers and reserved keywords are OK None | Some(Token::Reserved(..)) => (), // custom keywords are OK #[cfg(not(feature = "no_custom_syntax"))] Some(Token::Custom(..)) => (), // Active standard keywords cannot be made custom // Disabled keywords are OK Some(token) if token.is_standard_keyword() && !self.is_symbol_disabled(token.literal_syntax()) => { return Err(format!("'{keyword}' is a reserved keyword")) } // Active standard symbols cannot be made custom Some(token) if token.is_standard_symbol() && !self.is_symbol_disabled(token.literal_syntax()) => { return Err(format!("'{keyword}' is a reserved operator")) } // Active standard symbols cannot be made custom Some(token) if !self.is_symbol_disabled(token.literal_syntax()) => { return Err(format!("'{keyword}' is a reserved symbol")) } // Disabled symbols are OK Some(_) => (), } // Add to custom keywords self.custom_keywords .insert(keyword.into(), Some(precedence)); Ok(self) } /// Get the default value of the custom state for each evaluation run. #[inline(always)] pub const fn default_tag(&self) -> &Dynamic { &self.def_tag } /// Get a mutable reference to the default value of the custom state for each evaluation run. #[inline(always)] pub fn default_tag_mut(&mut self) -> &mut Dynamic { &mut self.def_tag } /// Set the default value of the custom state for each evaluation run. #[inline(always)] pub fn set_default_tag(&mut self, value: impl Into) -> &mut Self { self.def_tag = value.into(); self } } rhai-1.21.0/src/api/optimize.rs000064400000000000000000000052021046102023000143750ustar 00000000000000//! Module that defines the script optimization API of [`Engine`]. #![cfg(not(feature = "no_optimize"))] use crate::{Engine, OptimizationLevel, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation. /// /// Not available under `no_optimize`. #[inline(always)] pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self { self.optimization_level = optimization_level; self } /// The current optimization level. /// It controls whether and how the [`Engine`] will optimize an [`AST`] after compilation. /// /// Not available under `no_optimize`. #[inline(always)] #[must_use] pub const fn optimization_level(&self) -> OptimizationLevel { self.optimization_level } /// Optimize the [`AST`] with constants defined in an external Scope. /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// /// Not available under `no_optimize`. /// /// Although optimization is performed by default during compilation, sometimes it is necessary /// to _re_-optimize an [`AST`]. /// /// For example, when working with constants that are passed in via an external scope, /// it will be more efficient to optimize the [`AST`] once again to take advantage of the new constants. /// /// With this method, it is no longer necessary to recompile a large script. /// The script [`AST`] can be compiled just once. /// /// Before evaluation, constants are passed into the [`Engine`] via an external scope /// (i.e. with [`Scope::push_constant`][Scope::push_constant]). /// /// Then, the [`AST`] is cloned and the copy re-optimized before running. #[inline] #[must_use] pub fn optimize_ast( &self, scope: &Scope, ast: AST, optimization_level: OptimizationLevel, ) -> AST { let mut ast = ast; let mut _new_ast = self.optimize_into_ast( Some(scope), std::mem::take(ast.statements_mut()).to_vec().into(), #[cfg(not(feature = "no_function"))] ast.shared_lib() .iter_fn() .map(|(f, _)| f.get_script_fn_def().unwrap()) .cloned() .collect::>(), optimization_level, ); #[cfg(feature = "metadata")] { _new_ast.doc = std::mem::take(&mut ast.doc); } _new_ast } } rhai-1.21.0/src/api/options.rs000064400000000000000000000162031046102023000142330ustar 00000000000000//! Settings for [`Engine`]'s language options. use crate::Engine; use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; bitflags! { /// Bit-flags containing all language options for the [`Engine`]. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct LangOptions: u16 { /// Is `if`-expression allowed? const IF_EXPR = 0b_0000_0000_0001; /// Is `switch` expression allowed? const SWITCH_EXPR = 0b_0000_0000_0010; /// Are loop expressions allowed? const LOOP_EXPR = 0b_0000_0000_0100; /// Is statement-expression allowed? const STMT_EXPR = 0b_0000_0000_1000; /// Is anonymous function allowed? #[cfg(not(feature = "no_function"))] const ANON_FN = 0b_0000_0001_0000; /// Is looping allowed? const LOOPING = 0b_0000_0010_0000; /// Is variables shadowing allowed? const SHADOWING = 0b_0000_0100_0000; /// Strict variables mode? const STRICT_VAR = 0b_0000_1000_0000; /// Raise error if an object map property does not exist? /// Returns `()` if `false`. #[cfg(not(feature = "no_object"))] const FAIL_ON_INVALID_MAP_PROPERTY = 0b_0001_0000_0000; /// Fast operators mode? const FAST_OPS = 0b_0010_0000_0000; } } impl LangOptions { /// Create a new [`LangOptions`] with default values. #[inline(always)] #[must_use] pub const fn new() -> Self { Self::from_bits_truncate( Self::IF_EXPR.bits() | Self::SWITCH_EXPR.bits() | Self::LOOP_EXPR.bits() | Self::STMT_EXPR.bits() | Self::LOOPING.bits() | Self::SHADOWING.bits() | Self::FAST_OPS.bits() | { #[cfg(not(feature = "no_function"))] { Self::ANON_FN.bits() } #[cfg(feature = "no_function")] { Self::empty().bits() } }, ) } } impl Engine { /// Is `if`-expression allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_if_expression(&self) -> bool { self.options.intersects(LangOptions::IF_EXPR) } /// Set whether `if`-expression is allowed. #[inline(always)] pub fn set_allow_if_expression(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::IF_EXPR, enable); self } /// Is `switch` expression allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_switch_expression(&self) -> bool { self.options.intersects(LangOptions::SWITCH_EXPR) } /// Set whether `switch` expression is allowed. #[inline(always)] pub fn set_allow_switch_expression(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::SWITCH_EXPR, enable); self } /// Are loop expressions allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_loop_expressions(&self) -> bool { self.options.intersects(LangOptions::LOOP_EXPR) } /// Set whether loop expressions are allowed. #[inline(always)] pub fn set_allow_loop_expressions(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::LOOP_EXPR, enable); self } /// Is statement-expression allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_statement_expression(&self) -> bool { self.options.intersects(LangOptions::STMT_EXPR) } /// Set whether statement-expression is allowed. #[inline(always)] pub fn set_allow_statement_expression(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::STMT_EXPR, enable); self } /// Is anonymous function allowed? /// Default is `true`. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub const fn allow_anonymous_fn(&self) -> bool { self.options.intersects(LangOptions::ANON_FN) } /// Set whether anonymous function is allowed. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn set_allow_anonymous_fn(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::ANON_FN, enable); self } /// Is looping allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_looping(&self) -> bool { self.options.intersects(LangOptions::LOOPING) } /// Set whether looping is allowed. #[inline(always)] pub fn set_allow_looping(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::LOOPING, enable); self } /// Is variables shadowing allowed? /// Default is `true`. #[inline(always)] #[must_use] pub const fn allow_shadowing(&self) -> bool { self.options.intersects(LangOptions::SHADOWING) } /// Set whether variables shadowing is allowed. #[inline(always)] pub fn set_allow_shadowing(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::SHADOWING, enable); self } /// Is strict variables mode enabled? /// Default is `false`. #[inline(always)] #[must_use] pub const fn strict_variables(&self) -> bool { self.options.intersects(LangOptions::STRICT_VAR) } /// Set whether strict variables mode is enabled. #[inline(always)] pub fn set_strict_variables(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::STRICT_VAR, enable); self } /// Raise error if an object map property does not exist? /// Default is `false`. /// /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] #[inline(always)] #[must_use] pub const fn fail_on_invalid_map_property(&self) -> bool { self.options .intersects(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY) } /// Set whether to raise error if an object map property does not exist. /// /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_fail_on_invalid_map_property(&mut self, enable: bool) -> &mut Self { self.options .set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable); self } /// Is fast operators mode enabled? /// Default is `false`. #[inline(always)] #[must_use] pub const fn fast_operators(&self) -> bool { self.options.intersects(LangOptions::FAST_OPS) } /// Set whether fast operators mode is enabled. #[inline(always)] pub fn set_fast_operators(&mut self, enable: bool) -> &mut Self { self.options.set(LangOptions::FAST_OPS, enable); self } } rhai-1.21.0/src/api/register.rs000064400000000000000000000763161046102023000143770ustar 00000000000000//! Module that defines the public function/module registration API of [`Engine`]. use crate::func::{FnCallArgs, RhaiFunc, RhaiNativeFunc, SendSync}; use crate::module::FuncRegistration; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, Identifier, Module, NativeCallContext, RhaiResultOf, Shared, SharedModule, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::func::register::Mut; impl Engine { /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). #[inline(always)] #[must_use] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { if self.global_modules.is_empty() { let mut global_namespace = Module::new(); global_namespace.set_internal(true); self.global_modules.push(global_namespace.into()); } Shared::get_mut(self.global_modules.first_mut().unwrap()).unwrap() } /// Register a custom function with the [`Engine`]. /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`][`crate::FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _pure_ unless it is a property setter or an index setter. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// // Normal function /// fn add(x: i64, y: i64) -> i64 { /// x + y /// } /// /// let mut engine = Engine::new(); /// /// engine.register_fn("add", add); /// /// assert_eq!(engine.eval::("add(40, 2)")?, 42); /// /// // You can also register a closure. /// engine.register_fn("sub", |x: i64, y: i64| x - y ); /// /// assert_eq!(engine.eval::("sub(44, 2)")?, 42); /// # Ok(()) /// # } /// ``` #[inline] pub fn register_fn< A: 'static, const N: usize, const X: bool, R: Variant + Clone, const F: bool, >( &mut self, name: impl AsRef + Into, func: impl RhaiNativeFunc + SendSync + 'static, ) -> &mut Self { FuncRegistration::new(name.into()).register_into_engine(self, func); self } /// Register a function of the [`Engine`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s /// indicating the actual types of the parameters. /// /// # Arguments /// /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic]. /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. /// /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` /// /// To access an argument value and avoid cloning, use `args[n].take().cast::()`. /// Notice that this will _consume_ the argument, replacing it with `()`. /// /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` #[inline(always)] pub fn register_raw_fn( &mut self, name: impl AsRef + Into, arg_types: impl AsRef<[TypeId]>, func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { let name = name.into(); let arg_types = arg_types.as_ref(); let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] let is_pure = is_pure && (arg_types.len() != 3 || name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] let is_pure = is_pure && (arg_types.len() != 2 || !name.starts_with(crate::engine::FN_SET)); FuncRegistration::new(name) .in_global_namespace() .set_into_module_raw( self.global_namespace_mut(), arg_types, RhaiFunc::Method { func: Shared::new( move |ctx: Option, args: &mut FnCallArgs| { func(ctx.unwrap(), args).map(Dynamic::from) }, ), has_context: true, is_pure, is_volatile: true, }, ); self } /// Register a custom type for use with the [`Engine`]. /// The type must implement [`Clone`]. /// /// # Example /// /// ``` /// #[derive(Debug, Clone, Eq, PartialEq)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// fn update(&mut self, offset: i64) { /// self.field += offset; /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine /// .register_type::() /// .register_fn("new_ts", TestStruct::new) /// // Use `register_fn` to register methods on the type. /// .register_fn("update", TestStruct::update); /// /// # #[cfg(not(feature = "no_object"))] /// assert_eq!( /// engine.eval::("let x = new_ts(); x.update(41); x")?, /// TestStruct { field: 42 } /// ); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn register_type(&mut self) -> &mut Self { self.register_type_with_name::(type_name::()) } /// Register a custom type for use with the [`Engine`], with a pretty-print name /// for the `type_of` function. The type must implement [`Clone`]. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine /// .register_type::() /// .register_fn("new_ts", TestStruct::new); /// /// assert_eq!( /// engine.eval::("let x = new_ts(); type_of(x)")?, /// "rust_out::TestStruct" /// ); /// /// // Re-register the custom type with a name. /// engine.register_type_with_name::("Hello"); /// /// assert_eq!( /// engine.eval::("let x = new_ts(); type_of(x)")?, /// "Hello" /// ); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { self.global_namespace_mut().set_custom_type::(name); self } /// Register a custom type for use with the [`Engine`], with a pretty-print name /// for the `type_of` function. The type must implement [`Clone`]. /// /// # WARNING - Low Level API /// /// This function is low level. #[inline(always)] pub fn register_type_with_name_raw( &mut self, type_path: impl Into, name: impl Into, ) -> &mut Self { // Add the pretty-print type name into the map self.global_namespace_mut() .set_custom_type_raw(type_path, name); self } /// Register a type iterator for an iterable type with the [`Engine`]. /// This is an advanced API. #[inline(always)] pub fn register_iterator(&mut self) -> &mut Self where T: Variant + Clone + IntoIterator, ::Item: Variant + Clone, { self.global_namespace_mut().set_iterable::(); self } /// Register a fallible type iterator for an iterable type with the [`Engine`]. /// This is an advanced API. #[inline(always)] pub fn register_iterator_result(&mut self) -> &mut Self where T: Variant + Clone + IntoIterator>, R: Variant + Clone, { self.global_namespace_mut().set_iterable_result::(); self } /// Register a getter function for a member of a registered type with the [`Engine`]. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { /// self.field /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine /// .register_type::() /// .register_fn("new_ts", TestStruct::new) /// // Register a getter on a property (notice it doesn't have to be the same name). /// .register_get("xyz", TestStruct::get_field); /// /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn register_get( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X, R, F> + SendSync + 'static, ) -> &mut Self { self.register_fn(crate::engine::make_getter(name.as_ref()), get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// /// Not available under `no_object`. /// /// # Example /// /// ``` /// #[derive(Debug, Clone, Eq, PartialEq)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// fn set_field(&mut self, new_val: i64) { /// self.field = new_val; /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine /// .register_type::() /// .register_fn("new_ts", TestStruct::new) /// // Register a setter on a property (notice it doesn't have to be the same name) /// .register_set("xyz", TestStruct::set_field); /// /// // Notice that, with a getter, there is no way to get the property value /// assert_eq!( /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, /// TestStruct { field: 42 } /// ); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn register_set( &mut self, name: impl AsRef, set_fn: impl RhaiNativeFunc<(Mut, R), 2, X, (), F> + SendSync + 'static, ) -> &mut Self { self.register_fn(crate::engine::make_setter(name.as_ref()), set_fn) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. /// /// All function signatures must start with `&mut self` and not `&self`. /// /// Not available under `no_object`. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// field: i64 /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { field: 1 } /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { /// self.field /// } /// fn set_field(&mut self, new_val: i64) { /// self.field = new_val; /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// engine /// .register_type::() /// .register_fn("new_ts", TestStruct::new) /// // Register both a getter and a setter on a property /// // (notice it doesn't have to be the same name) /// .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); /// /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?, 42); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn register_get_set< T: Variant + Clone, const X1: bool, const X2: bool, R: Variant + Clone, const F1: bool, const F2: bool, >( &mut self, name: impl AsRef, get_fn: impl RhaiNativeFunc<(Mut,), 1, X1, R, F1> + SendSync + 'static, set_fn: impl RhaiNativeFunc<(Mut, R), 2, X2, (), F2> + SendSync + 'static, ) -> &mut Self { self.register_get(&name, get_fn).register_set(&name, set_fn) } /// Register an index getter for a custom type with the [`Engine`]. /// /// The function signature must start with `&mut self` and not `&self`. /// /// Not available under both `no_index` and `no_object`. /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// fields: Vec /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { fields: vec![1, 2, 3, 4, 5] } /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { /// self.fields[index as usize] /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine /// .register_fn("new_ts", TestStruct::new) /// // Register an indexer. /// .register_indexer_get(TestStruct::get_field); /// /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); /// # Ok(()) /// # } /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get< T: Variant + Clone, IDX: Variant + Clone, const X: bool, R: Variant + Clone, const F: bool, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X, R, F> + SendSync + 'static, ) -> &mut Self { self.register_fn(crate::engine::FN_IDX_GET, get_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// /// Not available under both `no_index` and `no_object`. /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// fields: Vec /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { fields: vec![1, 2, 3, 4, 5] } /// } /// fn set_field(&mut self, index: i64, value: i64) { /// self.fields[index as usize] = value; /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine /// .register_fn("new_ts", TestStruct::new) /// // Register an indexer. /// .register_indexer_set(TestStruct::set_field); /// /// # #[cfg(not(feature = "no_index"))] /// let result = engine.eval::("let a = new_ts(); a[2] = 42; a")?; /// /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(result.fields[2], 42); /// # Ok(()) /// # } /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_set< T: Variant + Clone, IDX: Variant + Clone, const X: bool, R: Variant + Clone, const F: bool, >( &mut self, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X, (), F> + SendSync + 'static, ) -> &mut Self { self.register_fn(crate::engine::FN_IDX_SET, set_fn) } /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. /// /// Not available under both `no_index` and `no_object`. /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// #[derive(Clone)] /// struct TestStruct { /// fields: Vec /// } /// /// impl TestStruct { /// fn new() -> Self { /// Self { fields: vec![1, 2, 3, 4, 5] } /// } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { /// self.fields[index as usize] /// } /// fn set_field(&mut self, index: i64, value: i64) { /// self.fields[index as usize] = value; /// } /// } /// /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// /// // Register API for the custom type. /// # #[cfg(not(feature = "no_object"))] /// engine.register_type::(); /// /// engine /// .register_fn("new_ts", TestStruct::new) /// // Register an indexer. /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); /// /// # #[cfg(not(feature = "no_index"))] /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); /// # Ok(()) /// # } /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn register_indexer_get_set< T: Variant + Clone, IDX: Variant + Clone, const X1: bool, const X2: bool, R: Variant + Clone, const F1: bool, const F2: bool, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, IDX), 2, X1, R, F1> + SendSync + 'static, set_fn: impl RhaiNativeFunc<(Mut, IDX, R), 3, X2, (), F2> + SendSync + 'static, ) -> &mut Self { self.register_indexer_get(get_fn) .register_indexer_set(set_fn) } /// Register a shared [`Module`] into the global namespace of [`Engine`]. /// /// All functions and type iterators are automatically available to scripts without namespace /// qualifications. /// /// Sub-modules and variables are **ignored**. /// /// When searching for functions, modules loaded later are preferred. In other words, loaded /// modules are searched in reverse order. #[inline(always)] pub fn register_global_module(&mut self, module: SharedModule) -> &mut Self { // Make sure the global namespace is created. let _ = self.global_namespace_mut(); // Insert the module into the front. // The first module is always the global namespace. self.global_modules.insert(1, module); self } /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. /// /// Functions marked [`FnNamespace::Global`][`crate::FnNamespace::Global`] and type iterators are exposed to scripts without /// namespace qualifications. /// /// Not available under `no_module`. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Shared, Module}; /// /// let mut engine = Engine::new(); /// /// // Create the module /// let mut module = Module::new(); /// module.set_native_fn("calc", |x: i64| Ok(x + 1)); /// /// let module: Shared = module.into(); /// /// engine /// // Register the module as a fixed sub-module /// .register_static_module("foo::bar::baz", module.clone()) /// // Multiple registrations to the same partial path is also OK! /// .register_static_module("foo::bar::hello", module.clone()) /// .register_static_module("CalcService", module); /// /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, name: impl AsRef, module: SharedModule, ) -> &mut Self { use std::collections::BTreeMap; fn register_static_module_raw( root: &mut BTreeMap, name: &str, module: SharedModule, ) { let separator = crate::engine::NAMESPACE_SEPARATOR; if name.contains(separator) { let mut iter = name.splitn(2, separator); let sub_module = iter.next().unwrap().trim(); let remainder = iter.next().unwrap().trim(); if root.is_empty() || !root.contains_key(sub_module) { let mut m = Module::new(); register_static_module_raw(m.get_sub_modules_mut(), remainder, module); m.build_index(); root.insert(sub_module.into(), m.into()); } else { let m = root.remove(sub_module).unwrap(); let mut m = crate::func::shared_take_or_clone(m); register_static_module_raw(m.get_sub_modules_mut(), remainder, module); m.build_index(); root.insert(sub_module.into(), m.into()); } } else if module.is_indexed() { root.insert(name.into(), module); } else { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::func::shared_take_or_clone(module); module.build_index(); root.insert(name.into(), module.into()); } } register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module); self } /// _(metadata)_ Generate a list of all registered functions. /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included, in order: /// 1) Functions registered into the global namespace /// 2) Functions in registered sub-modules /// 3) Functions in registered packages /// 4) Functions in standard packages (optional) #[cfg(feature = "metadata")] #[inline] #[must_use] pub fn gen_fn_signatures(&self, include_standard_packages: bool) -> Vec { let mut signatures = Vec::with_capacity(64); if let Some(global_namespace) = self.global_modules.first() { signatures.extend( global_namespace.gen_fn_signatures_with_mapper(|s| self.format_param_type(s)), ); } #[cfg(not(feature = "no_module"))] for (name, m) in &self.global_sub_modules { signatures.extend( m.gen_fn_signatures_with_mapper(|s| self.format_param_type(s)) .map(|f| format!("{name}::{f}")), ); } signatures.extend( self.global_modules .iter() .skip(1) .filter(|m| !m.is_internal() && (include_standard_packages || !m.is_standard_lib())) .flat_map(|m| m.gen_fn_signatures_with_mapper(|s| self.format_param_type(s))), ); signatures } /// Collect the [`FuncInfo`][crate::module::FuncInfo] of all functions, native or script-defined, /// mapping them into any type. /// Exported under the `internals` feature only. /// /// Return [`None`] from the `mapper` to skip a function. /// /// Functions from the following sources are included, in order: /// 1) Functions defined in the current script (if any) /// 2) Functions registered into the global namespace /// 3) Functions in registered packages /// 4) Functions in standard packages (optional) /// 5) Functions defined in modules `import`-ed by the current script (if any) /// 6) Functions in registered sub-modules #[cfg(feature = "internals")] #[inline(always)] pub fn collect_fn_metadata( &self, ctx: Option<&NativeCallContext>, mapper: impl Fn(crate::module::FuncInfo) -> Option + Copy, include_standard_packages: bool, ) -> Vec { self.collect_fn_metadata_impl(ctx, mapper, include_standard_packages) } /// Collect the [`FuncInfo`][crate::module::FuncInfo] of all functions, native or script-defined, /// mapping them into any type. /// /// Return [`None`] from the `mapper` to skip a function. /// /// Functions from the following sources are included, in order: /// 1) Functions defined in the current script (if any) /// 2) Functions registered into the global namespace /// 3) Functions in registered packages /// 4) Functions in standard packages (optional) /// 5) Functions defined in modules `import`-ed by the current script (if any) /// 6) Functions in registered sub-modules #[allow(dead_code)] pub(crate) fn collect_fn_metadata_impl( &self, _ctx: Option<&NativeCallContext>, mapper: impl Fn(crate::module::FuncInfo) -> Option + Copy, include_standard_packages: bool, ) -> Vec { let mut list = Vec::new(); #[cfg(not(feature = "no_function"))] if let Some(ctx) = _ctx { ctx.iter_namespaces() .flat_map(Module::iter_fn) .filter_map(|(func, f)| { mapper(crate::module::FuncInfo { metadata: f, #[cfg(not(feature = "no_module"))] namespace: Identifier::new_const(), script: func.get_script_fn_def().map(|f| (&**f).into()), }) }) .for_each(|v| list.push(v)); } self.global_modules .iter() .filter(|m| !m.is_internal() && (include_standard_packages || !m.is_standard_lib())) .flat_map(|m| m.iter_fn()) .filter_map(|(_func, f)| { mapper(crate::module::FuncInfo { metadata: f, #[cfg(not(feature = "no_module"))] namespace: Identifier::new_const(), #[cfg(not(feature = "no_function"))] script: _func.get_script_fn_def().map(|f| (&**f).into()), }) }) .for_each(|v| list.push(v)); #[cfg(not(feature = "no_module"))] if let Some(ctx) = _ctx { use crate::engine::NAMESPACE_SEPARATOR; use crate::SmartString; // Recursively scan modules for script-defined functions. fn scan_module( list: &mut Vec, namespace: &str, module: &Module, mapper: impl Fn(crate::module::FuncInfo) -> Option + Copy, ) { module .iter_fn() .filter_map(|(_func, f)| { mapper(crate::module::FuncInfo { metadata: f, namespace: namespace.into(), #[cfg(not(feature = "no_function"))] script: _func.get_script_fn_def().map(|f| (&**f).into()), }) }) .for_each(|v| list.push(v)); for (name, m) in module.iter_sub_modules() { use std::fmt::Write; let mut ns = SmartString::new_const(); write!(&mut ns, "{namespace}{NAMESPACE_SEPARATOR}{name}").unwrap(); scan_module(list, &ns, m, mapper); } } for (ns, m) in ctx.global_runtime_state().iter_imports_raw() { scan_module(&mut list, ns, m, mapper); } } #[cfg(not(feature = "no_module"))] self.global_sub_modules .values() .flat_map(|m| m.iter_fn()) .filter_map(|(_func, f)| { mapper(crate::module::FuncInfo { metadata: f, namespace: Identifier::new_const(), #[cfg(not(feature = "no_function"))] script: _func.get_script_fn_def().map(|f| (&**f).into()), }) }) .for_each(|v| list.push(v)); list } } rhai-1.21.0/src/api/run.rs000064400000000000000000000113701046102023000133440ustar 00000000000000//! Module that defines the public evaluation API of [`Engine`]. use crate::eval::Caches; use crate::parser::ParseState; use crate::{Engine, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Evaluate a string as a script. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// engine.run("print(40 + 2);")?; /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn run(&self, script: &str) -> RhaiResultOf<()> { self.run_with_scope(&mut Scope::new(), script) } /// Evaluate a string as a script with own scope. /// /// ## Constants Propagation /// /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within /// the scope are propagated throughout the script _including_ functions. /// /// This allows functions to be optimized based on dynamic global constants. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// /// engine.run_with_scope(&mut scope, "x += 2; print(x);")?; /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 42); /// # Ok(()) /// # } /// ``` #[inline] pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { let scripts = [script]; let ast = { let (stream, tc) = self.lex(&scripts); let input = &mut stream.peekable(); let lib = &mut <_>::default(); let state = ParseState::new(Some(scope), input, tc, lib); self.parse( state, #[cfg(not(feature = "no_optimize"))] self.optimization_level, )? }; self.run_ast_with_scope(scope, &ast) } /// Evaluate an [`AST`]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("print(40 + 2);")?; /// /// // Evaluate it /// engine.run_ast(&ast)?; /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn run_ast(&self, ast: &AST) -> RhaiResultOf<()> { self.run_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an [`AST`] with own scope. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("x += 2; x")?; /// /// // Evaluate it /// engine.run_ast_with_scope(&mut scope, &ast)?; /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 42); /// # Ok(()) /// # } /// ``` #[inline] pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { let caches = &mut Caches::new(); let global = &mut self.new_global_runtime_state(); global.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_function"))] global.lib.push(ast.shared_lib().clone()); #[cfg(not(feature = "no_module"))] global.embedded_module_resolver.clone_from(&ast.resolver); let _ = self.eval_global_statements(global, caches, scope, ast.statements(), true)?; #[cfg(feature = "debugging")] if self.is_debugger_registered() { global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(crate::Position::NONE); self.dbg(global, caches, scope, None, node)?; } Ok(()) } } /// Evaluate a string as a script. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// rhai::run("print(40 + 2);")?; /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn run(script: &str) -> RhaiResultOf<()> { Engine::new().run(script) } rhai-1.21.0/src/ast/ast.rs000064400000000000000000000762531046102023000133600ustar 00000000000000//! Module defining the AST (abstract syntax tree). use super::{ASTFlags, Expr, FnAccess, Stmt}; use crate::{expose_under_internals, Dynamic, FnNamespace, ImmutableString, Position, ThinVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ borrow::Borrow, fmt, hash::Hash, ops::{Add, AddAssign}, ptr, }; /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// # Thread Safety /// /// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Clone)] pub struct AST { /// Source of the [`AST`]. source: Option, /// Global statements. body: ThinVec, /// Script-defined functions. #[cfg(not(feature = "no_function"))] lib: crate::SharedModule, /// Embedded module resolver, if any. #[cfg(not(feature = "no_module"))] pub(crate) resolver: Option>, /// [`AST`] documentation. #[cfg(feature = "metadata")] pub(crate) doc: crate::SmartString, } impl Default for AST { #[inline(always)] #[must_use] fn default() -> Self { Self::empty() } } impl fmt::Debug for AST { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fp = f.debug_struct("AST"); fp.field("source", &self.source); #[cfg(feature = "metadata")] fp.field("doc", &self.doc); #[cfg(not(feature = "no_module"))] fp.field("resolver", &self.resolver); fp.field("body", &self.body); #[cfg(not(feature = "no_function"))] for (.., fn_def) in self.lib.iter_script_fn() { let sig = fn_def.to_string(); fp.field(&sig, &fn_def.body.statements()); } fp.finish() } } impl AST { /// _(internals)_ Create a new [`AST`]. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline] #[must_use] fn new( statements: impl IntoIterator, #[cfg(not(feature = "no_function"))] functions: impl Into, ) -> Self { Self { source: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), body: statements.into_iter().collect(), #[cfg(not(feature = "no_function"))] lib: functions.into(), #[cfg(not(feature = "no_module"))] resolver: None, } } /// _(internals)_ Create a new [`AST`] with a source name. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline] #[must_use] fn new_with_source( statements: impl IntoIterator, #[cfg(not(feature = "no_function"))] functions: impl Into, source: impl Into, ) -> Self { let mut ast = Self::new( statements, #[cfg(not(feature = "no_function"))] functions, ); ast.set_source(source); ast } /// Create an empty [`AST`]. #[inline] #[must_use] pub fn empty() -> Self { Self { source: None, #[cfg(feature = "metadata")] doc: crate::SmartString::new_const(), body: <_>::default(), #[cfg(not(feature = "no_function"))] lib: crate::Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: None, } } /// Get the source, if any. #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { self.source.as_deref() } /// Get a reference to the source. #[inline(always)] #[must_use] pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { self.source.as_ref() } /// Set the source. #[inline] pub fn set_source(&mut self, source: impl Into) -> &mut Self { let source = source.into(); #[cfg(not(feature = "no_function"))] crate::Shared::get_mut(&mut self.lib) .as_mut() .map(|m| m.set_id(source.clone())); self.source = (!source.is_empty()).then_some(source); self } /// Clear the source. #[inline(always)] pub fn clear_source(&mut self) -> &mut Self { self.source = None; self } /// Get the documentation (if any). /// Exported under the `metadata` feature only. /// /// Documentation is a collection of all comment lines beginning with `//!`. /// /// Leading white-spaces are stripped, and each line always starts with `//!`. #[cfg(feature = "metadata")] #[inline(always)] #[must_use] pub fn doc(&self) -> &str { &self.doc } /// _(internals)_ Get the statements. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn statements(&self) -> &[Stmt] { &self.body } /// Get the statements. #[inline(always)] #[must_use] #[allow(dead_code)] pub(crate) fn statements_mut(&mut self) -> &mut ThinVec { &mut self.body } /// Does this [`AST`] contain script-defined functions? /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn has_functions(&self) -> bool { !self.lib.is_empty() } /// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions. /// Exported under the `internals` feature only. /// /// Not available under `no_function`. #[expose_under_internals] #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] const fn shared_lib(&self) -> &crate::SharedModule { &self.lib } /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub const fn resolver( &self, ) -> Option<&crate::Shared> { self.resolver.as_ref() } /// Clone the [`AST`]'s functions into a new [`AST`]. /// No statements are cloned. /// /// Not available under `no_function`. /// /// This operation is cheap because functions are shared. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn clone_functions_only(&self) -> Self { self.clone_functions_only_filtered(|_, _, _, _, _| true) } /// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate. /// No statements are cloned. /// /// Not available under `no_function`. /// /// This operation is cheap because functions are shared. #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub fn clone_functions_only_filtered( &self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { let mut lib = crate::Module::new(); lib.merge_filtered(&self.lib, &filter); Self { source: self.source.clone(), #[cfg(feature = "metadata")] doc: self.doc.clone(), body: <_>::default(), lib: lib.into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), } } /// Clone the [`AST`]'s script statements into a new [`AST`]. /// No functions are cloned. #[inline(always)] #[must_use] pub fn clone_statements_only(&self) -> Self { Self { source: self.source.clone(), #[cfg(feature = "metadata")] doc: self.doc.clone(), body: self.body.clone(), #[cfg(not(feature = "no_function"))] lib: crate::Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), } } /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, /// version is returned. /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then /// the second [`AST`] will essentially be dead code. /// /// All script-defined functions in the second [`AST`] overwrite similarly-named functions /// in the first [`AST`] with the same number of parameters. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let ast1 = engine.compile(" /// fn foo(x) { 42 + x } /// foo(1) /// ")?; /// /// let ast2 = engine.compile(r#" /// fn foo(n) { `hello${n}` } /// foo("!") /// "#)?; /// /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' /// /// // Notice that using the '+' operator also works: /// // let ast = &ast1 + &ast2; /// /// // 'ast' is essentially: /// // /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten /// // foo(1) // <- notice this will be "hello1" instead of 43, /// // // but it is no longer the return value /// // foo("!") // returns "hello!" /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast)?, "hello!"); /// # } /// # Ok(()) /// # } /// ``` #[inline(always)] #[must_use] pub fn merge(&self, other: &Self) -> Self { self.merge_filtered_impl(other, |_, _, _, _, _| true) } /// Combine one [`AST`] with another. The second [`AST`] is consumed. /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then /// the second [`AST`] will essentially be dead code. /// /// All script-defined functions in the second [`AST`] overwrite similarly-named functions /// in the first [`AST`] with the same number of parameters. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let mut ast1 = engine.compile(" /// fn foo(x) { 42 + x } /// foo(1) /// ")?; /// /// let ast2 = engine.compile(r#" /// fn foo(n) { `hello${n}` } /// foo("!") /// "#)?; /// /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' /// /// // Notice that using the '+=' operator also works: /// // ast1 += ast2; /// /// // 'ast1' is essentially: /// // /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten /// // foo(1) // <- notice this will be "hello1" instead of 43, /// // // but it is no longer the return value /// // foo("!") // returns "hello!" /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); /// # } /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn combine(&mut self, other: Self) -> &mut Self { self.combine_filtered_impl(other, |_, _, _, _, _| true) } /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version /// is returned. /// /// Not available under `no_function`. /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then /// the second [`AST`] will essentially be dead code. /// /// All script-defined functions in the second [`AST`] are first selected based on a filter /// predicate, then overwrite similarly-named functions in the first [`AST`] with the /// same number of parameters. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let ast1 = engine.compile(" /// fn foo(x) { 42 + x } /// foo(1) /// ")?; /// /// let ast2 = engine.compile(r#" /// fn foo(n) { `hello${n}` } /// fn error() { 0 } /// foo("!") /// "#)?; /// /// // Merge 'ast2', picking only 'error()' but not 'foo(..)', into 'ast1' /// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params| /// script && name == "error" && params == 0); /// /// // 'ast' is essentially: /// // /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten /// // // because 'ast2::foo' is filtered away /// // foo(1) // <- notice this will be 43 instead of "hello1", /// // // but it is no longer the return value /// // fn error() { 0 } // <- this function passes the filter and is merged /// // foo("!") // <- returns "42!" /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn merge_filtered( &self, other: &Self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { self.merge_filtered_impl(other, filter) } /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version /// is returned. #[inline] #[must_use] fn merge_filtered_impl( &self, other: &Self, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { let merged = match (self.body.as_ref(), other.body.as_ref()) { ([], []) => <_>::default(), (_, []) => self.body.to_vec(), ([], _) => other.body.to_vec(), (body, other) => { let mut body = body.to_vec(); body.extend(other.iter().cloned()); body } }; #[cfg(not(feature = "no_function"))] let lib = { let mut lib = self.lib.as_ref().clone(); lib.merge_filtered(&other.lib, &_filter); lib }; let mut _ast = match other.source { Some(ref source) => Self::new_with_source( merged, #[cfg(not(feature = "no_function"))] lib, source.clone(), ), None => Self::new( merged, #[cfg(not(feature = "no_function"))] lib, ), }; #[cfg(not(feature = "no_module"))] match ( self.resolver.as_deref().map_or(true, |r| r.is_empty()), other.resolver.as_deref().map_or(true, |r| r.is_empty()), ) { (true, true) => (), (false, true) => _ast.resolver.clone_from(&self.resolver), (true, false) => _ast.resolver.clone_from(&other.resolver), (false, false) => { let mut resolver = self.resolver.as_deref().unwrap().clone(); for (k, v) in other.resolver.as_deref().unwrap() { resolver.insert(k.clone(), v.as_ref().clone()); } _ast.resolver = Some(resolver.into()); } } #[cfg(feature = "metadata")] match (other.doc.as_str(), _ast.doc.as_str()) { ("", _) => (), (_, "") => _ast.doc = other.doc.clone(), (_, _) => { _ast.doc.push_str("\n"); _ast.doc.push_str(&other.doc); } } _ast } /// Combine one [`AST`] with another. The second [`AST`] is consumed. /// /// Not available under `no_function`. /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then /// the second [`AST`] will essentially be dead code. /// /// All script-defined functions in the second [`AST`] are first selected based on a filter /// predicate, then overwrite similarly-named functions in the first [`AST`] with the /// same number of parameters. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let mut ast1 = engine.compile(" /// fn foo(x) { 42 + x } /// foo(1) /// ")?; /// /// let ast2 = engine.compile(r#" /// fn foo(n) { `hello${n}` } /// fn error() { 0 } /// foo("!") /// "#)?; /// /// // Combine 'ast2', picking only 'error()' but not 'foo(..)', into 'ast1' /// ast1.combine_filtered(ast2, |_, _, script, name, params| /// script && name == "error" && params == 0); /// /// // 'ast1' is essentially: /// // /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten /// // // because 'ast2::foo' is filtered away /// // foo(1) // <- notice this will be 43 instead of "hello1", /// // // but it is no longer the return value /// // fn error() { 0 } // <- this function passes the filter and is merged /// // foo("!") // <- returns "42!" /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn combine_filtered( &mut self, other: Self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { self.combine_filtered_impl(other, filter) } /// Combine one [`AST`] with another. The second [`AST`] is consumed. fn combine_filtered_impl( &mut self, other: Self, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { #[cfg(not(feature = "no_module"))] match ( self.resolver.as_deref().map_or(true, |r| r.is_empty()), other.resolver.as_deref().map_or(true, |r| r.is_empty()), ) { (_, true) => (), (true, false) => self.resolver.clone_from(&other.resolver), (false, false) => { let resolver = crate::func::shared_make_mut(self.resolver.as_mut().unwrap()); let other_resolver = crate::func::shared_take_or_clone(other.resolver.unwrap()); for (k, v) in other_resolver { resolver.insert(k, crate::func::shared_take_or_clone(v)); } } } match (self.body.as_ref(), other.body.as_ref()) { (_, []) => (), ([], _) => self.body = other.body, (_, _) => self.body.extend(other.body), } #[cfg(not(feature = "no_function"))] if !other.lib.is_empty() { crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter); } #[cfg(feature = "metadata")] match (other.doc.as_str(), self.doc.as_str()) { ("", _) => (), (_, "") => self.doc = other.doc, (_, _) => { self.doc.push_str("\n"); self.doc.push_str(&other.doc); } } self } /// Filter out the functions, retaining only some based on a filter predicate. /// /// Not available under `no_function`. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let mut ast = engine.compile(r#" /// fn foo(n) { n + 1 } /// fn bar() { print("hello"); } /// "#)?; /// /// // Remove all functions except 'foo(..)' /// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1); /// # } /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] #[inline] pub fn retain_functions( &mut self, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { if self.has_functions() { crate::func::shared_make_mut(&mut self.lib).retain_script_functions(filter); } self } /// _(internals)_ Iterate through all function definitions. /// Exported under the `internals` feature only. /// /// Not available under `no_function`. #[expose_under_internals] #[cfg(not(feature = "no_function"))] #[inline] fn iter_fn_def(&self) -> impl Iterator> { self.lib.iter_script_fn().map(|(.., fn_def)| fn_def) } /// Iterate through all function definitions. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] pub fn iter_functions(&self) -> impl Iterator { self.lib .iter_script_fn() .map(|(.., fn_def)| fn_def.as_ref().into()) } /// Clear all function definitions in the [`AST`]. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn clear_functions(&mut self) -> &mut Self { self.lib = crate::Module::new().into(); self } /// Clear all statements in the [`AST`], leaving only function definitions. #[inline(always)] pub fn clear_statements(&mut self) -> &mut Self { self.body = <_>::default(); self } /// Extract all top-level literal constant and/or variable definitions. /// This is useful for extracting all global constants from a script without actually running it. /// /// A literal constant/variable definition takes the form of: /// `const VAR = `_value_`;` and `let VAR = `_value_`;` /// where _value_ is a literal expression or will be optimized into a literal. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile( /// " /// const A = 40 + 2; // constant that optimizes into a literal /// let b = 123; // literal variable /// const B = b * A; // non-literal constant /// const C = 999; // literal constant /// b = A + C; // expression /// /// { // <- new block scope /// const Z = 0; // <- literal constant not at top-level /// /// print(Z); // make sure the block is not optimized away /// } /// ")?; /// /// let mut iter = ast.iter_literal_variables(true, false) /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); /// /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(iter.next(), Some(("A", true, 42))); /// assert_eq!(iter.next(), Some(("C", true, 999))); /// assert_eq!(iter.next(), None); /// /// let mut iter = ast.iter_literal_variables(false, true) /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); /// /// assert_eq!(iter.next(), Some(("b", false, 123))); /// assert_eq!(iter.next(), None); /// /// let mut iter = ast.iter_literal_variables(true, true) /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); /// /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(iter.next(), Some(("A", true, 42))); /// assert_eq!(iter.next(), Some(("b", false, 123))); /// assert_eq!(iter.next(), Some(("C", true, 999))); /// assert_eq!(iter.next(), None); /// /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); /// /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(scope.len(), 2); /// /// Ok(()) /// # } /// ``` pub fn iter_literal_variables( &self, include_constants: bool, include_variables: bool, ) -> impl Iterator { self.statements().iter().filter_map(move |stmt| match stmt { Stmt::Var(x, options, ..) if options.intersects(ASTFlags::CONSTANT) && include_constants || !options.intersects(ASTFlags::CONSTANT) && include_variables => { let (name, expr, ..) = &**x; expr.get_literal_value() .map(|value| (name.as_str(), options.intersects(ASTFlags::CONSTANT), value)) } _ => None, }) } /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). /// Return `false` from the callback to terminate the walk. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] pub fn walk(&self, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized)) -> bool { self._walk(on_node) } /// Recursively walk the [`AST`], including function bodies (if any). /// Return `false` from the callback to terminate the walk. pub(crate) fn _walk(&self, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized)) -> bool { let path = &mut Vec::new(); for stmt in self.statements() { if !stmt.walk(path, on_node) { return false; } } #[cfg(not(feature = "no_function"))] for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) { if !stmt.walk(path, on_node) { return false; } } true } } impl> Add for &AST { type Output = AST; #[inline(always)] fn add(self, rhs: A) -> Self::Output { self.merge(rhs.as_ref()) } } impl> AddAssign for AST { #[inline(always)] fn add_assign(&mut self, rhs: A) { self.combine(rhs.into()); } } impl Borrow<[Stmt]> for AST { #[inline(always)] #[must_use] fn borrow(&self) -> &[Stmt] { self.statements() } } impl AsRef<[Stmt]> for AST { #[inline(always)] #[must_use] fn as_ref(&self) -> &[Stmt] { self.statements() } } #[cfg(not(feature = "no_function"))] impl Borrow for AST { #[inline(always)] #[must_use] fn borrow(&self) -> &crate::Module { self.shared_lib() } } #[cfg(not(feature = "no_function"))] impl AsRef for AST { #[inline(always)] #[must_use] fn as_ref(&self) -> &crate::Module { self.shared_lib().as_ref() } } #[cfg(not(feature = "no_function"))] impl Borrow for AST { #[inline(always)] #[must_use] fn borrow(&self) -> &crate::SharedModule { self.shared_lib() } } #[cfg(not(feature = "no_function"))] impl AsRef for AST { #[inline(always)] #[must_use] fn as_ref(&self) -> &crate::SharedModule { self.shared_lib() } } /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Copy, Hash)] #[non_exhaustive] pub enum ASTNode<'a> { /// A statement ([`Stmt`]). Stmt(&'a Stmt), /// An expression ([`Expr`]). Expr(&'a Expr), } impl<'a> From<&'a Stmt> for ASTNode<'a> { #[inline(always)] fn from(stmt: &'a Stmt) -> Self { Self::Stmt(stmt) } } impl<'a> From<&'a Expr> for ASTNode<'a> { #[inline(always)] fn from(expr: &'a Expr) -> Self { Self::Expr(expr) } } impl PartialEq for ASTNode<'_> { #[inline] fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Stmt(x), Self::Stmt(y)) => ptr::eq(*x, *y), (Self::Expr(x), Self::Expr(y)) => ptr::eq(*x, *y), _ => false, } } } impl Eq for ASTNode<'_> {} impl ASTNode<'_> { /// Is this [`ASTNode`] a [`Stmt`]? #[inline(always)] #[must_use] pub const fn is_stmt(&self) -> bool { matches!(self, Self::Stmt(..)) } /// Is this [`ASTNode`] an [`Expr`]? #[inline(always)] #[must_use] pub const fn is_expr(&self) -> bool { matches!(self, Self::Expr(..)) } /// Get the [`Position`] of this [`ASTNode`]. #[inline] #[must_use] pub fn position(&self) -> Position { match self { Self::Stmt(stmt) => stmt.position(), Self::Expr(expr) => expr.position(), } } } /// _(internals)_ Encapsulated AST environment. /// Exported under the `internals` feature only. /// /// 1) functions defined within the same AST /// 2) the stack of imported [modules][crate::Module] /// 3) global constants #[derive(Debug, Clone)] pub struct EncapsulatedEnviron { /// Functions defined within the same [`AST`][crate::AST]. #[cfg(not(feature = "no_function"))] pub lib: crate::SharedModule, /// Imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] pub imports: crate::ThinVec<(ImmutableString, crate::SharedModule)>, /// Globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub constants: Option, } rhai-1.21.0/src/ast/expr.rs000064400000000000000000000764771046102023000135570ustar 00000000000000//! Module defining script expressions. use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock}; use crate::engine::KEYWORD_FN_PTR; use crate::tokenizer::Token; use crate::types::dynamic::Union; use crate::{ calc_fn_hash, Dynamic, FnArgsVec, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec, ThinVec, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ collections::BTreeMap, fmt, fmt::Write, hash::Hash, iter::once, mem, num::{NonZeroU8, NonZeroUsize}, }; /// _(internals)_ A binary expression. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash, Default)] pub struct BinaryExpr { /// LHS expression. pub lhs: Expr, /// RHS expression. pub rhs: Expr, } /// _(internals)_ A custom syntax expression. /// Exported under the `internals` feature only. /// /// Not available under `no_custom_syntax`. #[cfg(not(feature = "no_custom_syntax"))] #[derive(Debug, Clone, Hash)] pub struct CustomExpr { /// List of keywords. pub inputs: FnArgsVec, /// List of tokens actually parsed. pub tokens: FnArgsVec, /// State value. pub state: Dynamic, /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement /// (e.g. introducing a new variable)? pub scope_may_be_changed: bool, /// Is this custom syntax self-terminated? pub self_terminated: bool, } #[cfg(not(feature = "no_custom_syntax"))] impl CustomExpr { /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? /// /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` #[inline(always)] #[must_use] pub const fn is_self_terminated(&self) -> bool { self.self_terminated } } /// _(internals)_ A set of function call hashes. Exported under the `internals` feature only. /// /// Two separate hashes are pre-calculated because of the following patterns: /// /// ```rhai /// func(a, b, c); // Native: func(a, b, c) - 3 parameters /// // Script: func(a, b, c) - 3 parameters /// /// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters /// // Script: func(b, c) - 2 parameters /// ``` /// /// For normal function calls, the native hash equals the script hash. /// /// For method-style calls, the script hash contains one fewer parameter. /// /// Function call hashes are used in the following manner: /// /// * First, the script hash (if any) is tried, which contains only the called function's name plus /// the number of parameters. /// /// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is /// then used to search for a native function. /// /// In other words, a complete native function call hash always contains the called function's /// name plus the types of the arguments. This is due to possible function overloading for /// different parameter types. #[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct FnCallHashes { /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). #[cfg(not(feature = "no_function"))] script: Option, /// Pre-calculated hash for a native Rust function with no parameter types. native: u64, } impl fmt::Debug for FnCallHashes { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_function"))] return match self.script { Some(script) if script == self.native => fmt::Debug::fmt(&self.native, f), Some(script) => write!(f, "({script}, {})", self.native), None => write!(f, "{} (native only)", self.native), }; #[cfg(feature = "no_function")] return write!(f, "{}", self.native); } } impl FnCallHashes { /// Create a [`FnCallHashes`] from a single hash. #[inline] #[must_use] pub const fn from_hash(hash: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] script: Some(hash), native: hash, } } /// Create a [`FnCallHashes`] with only the native Rust hash. #[inline] #[must_use] pub const fn from_native_only(hash: u64) -> Self { Self { #[cfg(not(feature = "no_function"))] script: None, native: hash, } } /// Create a [`FnCallHashes`] with both script function and native Rust hashes. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub const fn from_script_and_native(script: u64, native: u64) -> Self { Self { script: Some(script), native, } } /// Is this [`FnCallHashes`] native-only? #[inline(always)] #[must_use] pub const fn is_native_only(&self) -> bool { #[cfg(not(feature = "no_function"))] return self.script.is_none(); #[cfg(feature = "no_function")] return true; } /// Get the native hash. /// /// The hash returned is never zero. #[inline(always)] #[must_use] pub const fn native(&self) -> u64 { self.native } /// Get the script hash. /// /// The hash returned is never zero. /// /// # Panics /// /// Panics if this [`FnCallHashes`] is native-only. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn script(&self) -> u64 { self.script.expect("native-only hash") } } /// _(internals)_ A function call. /// Exported under the `internals` feature only. #[derive(Clone, Hash)] pub struct FnCallExpr { /// Namespace of the function, if any. #[cfg(not(feature = "no_module"))] pub namespace: super::Namespace, /// Function name. pub name: ImmutableString, /// Pre-calculated hashes. pub hashes: FnCallHashes, /// List of function call argument expressions. pub args: FnArgsVec, /// Does this function call capture the parent scope? pub capture_parent_scope: bool, /// Is this function call a native operator? pub op_token: Option, } impl fmt::Debug for FnCallExpr { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ff = f.debug_struct("FnCallExpr"); #[cfg(not(feature = "no_module"))] if !self.namespace.is_empty() { ff.field("namespace", &self.namespace); } ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); if self.is_operator_call() { ff.field("op_token", &self.op_token); } if self.capture_parent_scope { ff.field("capture_parent_scope", &self.capture_parent_scope); } ff.finish() } } impl FnCallExpr { /// Does this function call contain a qualified namespace? /// /// Not available under [`no_module`] #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub fn is_qualified(&self) -> bool { !self.namespace.is_empty() } /// Is this function call an operator expression? #[inline(always)] #[must_use] pub fn is_operator_call(&self) -> bool { self.op_token.is_some() } /// Convert this into an [`Expr::FnCall`]. #[inline(always)] #[must_use] pub fn into_fn_call_expr(self, pos: Position) -> Expr { Expr::FnCall(self.into(), pos) } /// Are all arguments constant? #[inline] #[must_use] pub fn constant_args(&self) -> bool { self.args.is_empty() || self.args.iter().all(Expr::is_constant) } } /// _(internals)_ An expression sub-tree. /// Exported under the `internals` feature only. #[derive(Clone, Hash)] #[non_exhaustive] #[allow(clippy::type_complexity)] pub enum Expr { /// Dynamic constant. /// /// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning. /// Primitive data types should use the appropriate variants to avoid an allocation. /// /// The [`Dynamic`] value is boxed in order to avoid bloating the size of [`Expr`]. DynamicConstant(Box, Position), /// Boolean constant. BoolConstant(bool, Position), /// Integer constant. IntegerConstant(INT, Position), /// Floating-point constant. #[cfg(not(feature = "no_float"))] FloatConstant(crate::types::FloatWrapper, Position), /// Character constant. CharConstant(char, Position), /// [String][ImmutableString] constant. StringConstant(ImmutableString, Position), /// An interpolated [string][ImmutableString]. InterpolatedString(ThinVec, Position), /// [ expr, ... ] Array(ThinVec, Position), /// #{ name:expr, ... } Map( Box<(StaticVec<(Ident, Expr)>, BTreeMap)>, Position, ), /// () Unit(Position), /// Variable access - (optional long index, variable name, namespace, namespace hash), optional short index, position /// /// The short index is [`u8`] which is used when the index is <= 255, which should be /// the vast majority of cases (unless there are more than 255 variables defined!). /// This is to avoid reading a pointer redirection during each variable access. Variable( #[cfg(not(feature = "no_module"))] Box<(Option, ImmutableString, super::Namespace, u64)>, #[cfg(feature = "no_module")] Box<(Option, ImmutableString)>, Option, Position, ), /// `this`. ThisPtr(Position), /// Property access - ((getter, hash), (setter, hash), prop) Property( Box<( (ImmutableString, u64), (ImmutableString, u64), ImmutableString, )>, Position, ), /// xxx `.` method `(` expr `,` ... `)` MethodCall(Box, Position), /// { [statement][Stmt] ... } Stmt(Box), /// func `(` expr `,` ... `)` FnCall(Box, Position), /// lhs `.` rhs | lhs `?.` rhs /// /// ### Flags /// /// * [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset) /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset) Dot(Box, ASTFlags, Position), /// lhs `[` rhs `]` /// /// ### Flags /// /// * [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset) /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset) Index(Box, ASTFlags, Position), /// lhs `&&` rhs And(Box, Position), /// lhs `||` rhs Or(Box, Position), /// lhs `??` rhs Coalesce(Box, Position), /// Custom syntax #[cfg(not(feature = "no_custom_syntax"))] Custom(Box, Position), } impl Default for Expr { #[inline(always)] #[must_use] fn default() -> Self { Self::Unit(Position::NONE) } } impl fmt::Debug for Expr { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut display_pos = self.start_position(); match self { Self::DynamicConstant(value, ..) => write!(f, "{value:?}"), Self::BoolConstant(value, ..) => write!(f, "{value:?}"), Self::IntegerConstant(value, ..) => write!(f, "{value:?}"), #[cfg(not(feature = "no_float"))] Self::FloatConstant(value, ..) => write!(f, "{value:?}"), Self::CharConstant(value, ..) => write!(f, "{value:?}"), Self::StringConstant(value, ..) => write!(f, "{value:?}"), Self::Unit(..) => f.write_str("()"), Self::InterpolatedString(x, ..) => { f.write_str("InterpolatedString")?; return f.debug_list().entries(x.iter()).finish(); } Self::Array(x, ..) => { f.write_str("Array")?; f.debug_list().entries(x.iter()).finish() } Self::Map(x, ..) => { f.write_str("Map")?; f.debug_map() .entries(x.0.iter().map(|(k, v)| (k, v))) .finish() } Self::ThisPtr(..) => f.debug_struct("ThisPtr").finish(), Self::Variable(x, i, ..) => { f.write_str("Variable(")?; #[cfg(not(feature = "no_module"))] if !x.2.is_empty() { write!(f, "{}{}", x.1, crate::engine::NAMESPACE_SEPARATOR)?; let pos = x.2.position(); if !pos.is_none() { display_pos = pos; } } f.write_str(&x.1)?; #[cfg(not(feature = "no_module"))] if let Some(n) = x.2.index { write!(f, " #{n}")?; } if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { write!(f, " #{n}")?; } f.write_str(")") } Self::Property(x, ..) => write!(f, "Property({})", x.2), Self::MethodCall(x, ..) => f.debug_tuple("MethodCall").field(x).finish(), Self::Stmt(x) => { let pos = x.span(); if !pos.is_none() { display_pos = pos.start(); } f.write_str("ExprStmtBlock")?; f.debug_list().entries(x.iter()).finish() } Self::FnCall(x, ..) => fmt::Debug::fmt(x, f), Self::Index(x, options, pos) => { if !pos.is_none() { display_pos = *pos; } let mut f = f.debug_struct("Index"); f.field("lhs", &x.lhs).field("rhs", &x.rhs); if !options.is_empty() { f.field("options", options); } f.finish() } Self::Dot(x, options, pos) => { if !pos.is_none() { display_pos = *pos; } let mut f = f.debug_struct("Dot"); f.field("lhs", &x.lhs).field("rhs", &x.rhs); if !options.is_empty() { f.field("options", options); } f.finish() } Self::And(x, pos) | Self::Or(x, pos) | Self::Coalesce(x, pos) => { let op_name = match self { Self::And(..) => "And", Self::Or(..) => "Or", Self::Coalesce(..) => "Coalesce", expr => unreachable!("`And`, `Or` or `Coalesce` expected but gets {:?}", expr), }; if !pos.is_none() { display_pos = *pos; } f.debug_struct(op_name) .field("lhs", &x.lhs) .field("rhs", &x.rhs) .finish() } #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(), }?; write!(f, " @ {display_pos:?}") } } impl Expr { /// Get the [`Dynamic`] value of a literal constant expression. /// /// Returns [`None`] if the expression is not a literal constant. #[inline] #[must_use] pub fn get_literal_value(&self) -> Option { Some(match self { Self::DynamicConstant(x, ..) => x.as_ref().clone(), Self::IntegerConstant(x, ..) => (*x).into(), #[cfg(not(feature = "no_float"))] Self::FloatConstant(x, ..) => (*x).into(), Self::CharConstant(x, ..) => (*x).into(), Self::StringConstant(x, ..) => x.clone().into(), Self::BoolConstant(x, ..) => (*x).into(), Self::Unit(..) => Dynamic::UNIT, #[cfg(not(feature = "no_index"))] Self::Array(x, ..) if self.is_constant() => { let mut arr = crate::Array::with_capacity(x.len()); arr.extend(x.iter().map(|v| v.get_literal_value().unwrap())); Dynamic::from_array(arr) } #[cfg(not(feature = "no_object"))] Self::Map(x, ..) if self.is_constant() => { let mut map = x.1.clone(); for (k, v) in &x.0 { *map.get_mut(k.as_str()).unwrap() = v.get_literal_value().unwrap(); } Dynamic::from_map(map) } // Interpolated string Self::InterpolatedString(x, ..) if self.is_constant() => { let mut s = SmartString::new_const(); for segment in x { let v = segment.get_literal_value().unwrap(); write!(&mut s, "{v}").unwrap(); } s.into() } // Qualified function call #[cfg(not(feature = "no_module"))] Self::FnCall(x, ..) if x.is_qualified() => return None, // Function call Self::FnCall(x, ..) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR => { match x.args[0] { Self::StringConstant(ref s, ..) => FnPtr::new(s.clone()).ok()?.into(), _ => return None, } } // Binary operator call Self::FnCall(x, ..) if x.args.len() == 2 => { pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax(); pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); match x.name.as_str() { // x..y OP_EXCLUSIVE_RANGE => match (&x.args[0], &x.args[1]) { ( Self::IntegerConstant(ref start, ..), Self::IntegerConstant(ref end, ..), ) => (*start..*end).into(), (Self::IntegerConstant(ref start, ..), Self::Unit(..)) => { (*start..INT::MAX).into() } (Self::Unit(..), Self::IntegerConstant(ref start, ..)) => { (0..*start).into() } _ => return None, }, // x..=y OP_INCLUSIVE_RANGE => match (&x.args[0], &x.args[1]) { ( Self::IntegerConstant(ref start, ..), Self::IntegerConstant(ref end, ..), ) => (*start..=*end).into(), (Self::IntegerConstant(ref start, ..), Self::Unit(..)) => { (*start..=INT::MAX).into() } (Self::Unit(..), Self::IntegerConstant(ref start, ..)) => { (0..=*start).into() } _ => return None, }, _ => return None, } } _ => return None, }) } /// Create an [`Expr`] from a [`Dynamic`] value. #[inline] #[must_use] pub fn from_dynamic(value: Dynamic, pos: Position) -> Self { match value.0 { Union::Unit(..) => Self::Unit(pos), Union::Bool(b, ..) => Self::BoolConstant(b, pos), Union::Str(s, ..) => Self::StringConstant(s, pos), Union::Char(c, ..) => Self::CharConstant(c, pos), Union::Int(i, ..) => Self::IntegerConstant(i, pos), #[cfg(feature = "decimal")] Union::Decimal(value, ..) => Self::DynamicConstant(Box::new((*value).into()), pos), #[cfg(not(feature = "no_float"))] Union::Float(f, ..) => Self::FloatConstant(f, pos), #[cfg(not(feature = "no_index"))] Union::Array(a, ..) => Self::DynamicConstant(Box::new((*a).into()), pos), #[cfg(not(feature = "no_object"))] Union::Map(m, ..) => Self::DynamicConstant(Box::new((*m).into()), pos), Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall( FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: super::Namespace::NONE, name: KEYWORD_FN_PTR.into(), hashes: FnCallHashes::from_hash(calc_fn_hash(None, f.fn_name(), 1)), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, op_token: None, } .into(), pos, ), _ => Self::DynamicConstant(value.into(), pos), } } /// Return the variable name if the expression a simple variable access. /// /// `non_qualified` is ignored under `no_module`. #[inline] #[must_use] pub(crate) fn get_variable_name(&self, _non_qualified: bool) -> Option<&str> { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) if _non_qualified && !x.2.is_empty() => None, Self::Variable(x, ..) => Some(&x.1), _ => None, } } /// Get the [options][ASTFlags] of the expression. #[inline] #[must_use] pub const fn options(&self) -> ASTFlags { match self { Self::Index(_, options, _) | Self::Dot(_, options, _) => *options, #[cfg(not(feature = "no_float"))] Self::FloatConstant(..) => ASTFlags::empty(), Self::DynamicConstant(..) | Self::BoolConstant(..) | Self::IntegerConstant(..) | Self::CharConstant(..) | Self::Unit(..) | Self::StringConstant(..) | Self::Array(..) | Self::Map(..) | Self::Variable(..) | Self::ThisPtr(..) | Self::And(..) | Self::Or(..) | Self::Coalesce(..) | Self::FnCall(..) | Self::MethodCall(..) | Self::InterpolatedString(..) | Self::Property(..) | Self::Stmt(..) => ASTFlags::empty(), #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(..) => ASTFlags::empty(), } } /// Get the [position][Position] of the expression. #[inline] #[must_use] pub const fn position(&self) -> Position { match self { #[cfg(not(feature = "no_float"))] Self::FloatConstant(.., pos) => *pos, Self::DynamicConstant(.., pos) | Self::BoolConstant(.., pos) | Self::IntegerConstant(.., pos) | Self::CharConstant(.., pos) | Self::Unit(pos) | Self::StringConstant(.., pos) | Self::Array(.., pos) | Self::Map(.., pos) | Self::Variable(.., pos) | Self::ThisPtr(pos) | Self::And(.., pos) | Self::Or(.., pos) | Self::Coalesce(.., pos) | Self::FnCall(.., pos) | Self::MethodCall(.., pos) | Self::Index(.., pos) | Self::Dot(.., pos) | Self::InterpolatedString(.., pos) | Self::Property(.., pos) => *pos, #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(.., pos) => *pos, Self::Stmt(x) => x.position(), } } /// Get the starting [position][Position] of the expression. /// For a binary expression, this will be the left-most LHS instead of the operator. #[inline] #[must_use] pub fn start_position(&self) -> Position { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) => { if x.2.is_empty() { self.position() } else { x.2.position() } } Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) | Self::Index(x, ..) | Self::Dot(x, ..) => x.lhs.start_position(), Self::FnCall(.., pos) => *pos, _ => self.position(), } } /// Override the [position][Position] of the expression. #[inline] pub fn set_position(&mut self, new_pos: Position) -> &mut Self { match self { #[cfg(not(feature = "no_float"))] Self::FloatConstant(.., pos) => *pos = new_pos, Self::DynamicConstant(.., pos) | Self::BoolConstant(.., pos) | Self::IntegerConstant(.., pos) | Self::CharConstant(.., pos) | Self::Unit(pos) | Self::StringConstant(.., pos) | Self::Array(.., pos) | Self::Map(.., pos) | Self::And(.., pos) | Self::Or(.., pos) | Self::Coalesce(.., pos) | Self::Dot(.., pos) | Self::Index(.., pos) | Self::Variable(.., pos) | Self::ThisPtr(pos) | Self::FnCall(.., pos) | Self::MethodCall(.., pos) | Self::InterpolatedString(.., pos) | Self::Property(.., pos) => *pos = new_pos, #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(.., pos) => *pos = new_pos, Self::Stmt(x) => x.set_position(new_pos, Position::NONE), } self } /// Is the expression pure? /// /// A pure expression has no side effects. #[inline] #[must_use] pub fn is_pure(&self) -> bool { match self { Self::InterpolatedString(x, ..) | Self::Array(x, ..) => x.iter().all(Self::is_pure), Self::Map(x, ..) => x.0.iter().map(|(.., v)| v).all(Self::is_pure), Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => { x.lhs.is_pure() && x.rhs.is_pure() } Self::Stmt(x) => x.iter().all(Stmt::is_pure), Self::Variable(..) => true, _ => self.is_constant(), } } /// Is the expression the unit `()` literal? #[inline(always)] #[must_use] pub const fn is_unit(&self) -> bool { matches!(self, Self::Unit(..)) } /// Is the expression a constant? #[inline] #[must_use] pub fn is_constant(&self) -> bool { match self { #[cfg(not(feature = "no_float"))] Self::FloatConstant(..) => true, Self::DynamicConstant(..) | Self::BoolConstant(..) | Self::IntegerConstant(..) | Self::CharConstant(..) | Self::StringConstant(..) | Self::Unit(..) => true, Self::InterpolatedString(x, ..) | Self::Array(x, ..) => x.iter().all(Self::is_constant), Self::Map(x, ..) => x.0.iter().map(|(.., expr)| expr).all(Self::is_constant), _ => false, } } /// Is a particular [token][Token] allowed as a postfix operator to this expression? #[inline] #[must_use] pub const fn is_valid_postfix(&self, token: &Token) -> bool { match token { #[cfg(not(feature = "no_object"))] Token::Period | Token::Elvis => return true, #[cfg(not(feature = "no_index"))] Token::LeftBracket | Token::QuestionBracket => return true, _ => (), } match self { #[cfg(not(feature = "no_float"))] Self::FloatConstant(..) => false, Self::DynamicConstant(..) | Self::BoolConstant(..) | Self::CharConstant(..) | Self::And(..) | Self::Or(..) | Self::Coalesce(..) | Self::Unit(..) => false, Self::IntegerConstant(..) | Self::StringConstant(..) | Self::InterpolatedString(..) | Self::FnCall(..) | Self::ThisPtr(..) | Self::MethodCall(..) | Self::Stmt(..) | Self::Dot(..) | Self::Index(..) | Self::Array(..) | Self::Map(..) => false, #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(..) => false, Self::Variable(..) => matches!( token, Token::LeftParen | Token::Unit | Token::Bang | Token::DoubleColon ), Self::Property(..) => matches!(token, Token::LeftParen), } } /// Return this [`Expr`], replacing it with [`Expr::Unit`]. #[inline(always)] #[must_use] pub fn take(&mut self) -> Self { mem::take(self) } /// Recursively walk this expression. /// Return `false` from the callback to terminate the walk. pub fn walk<'a>( &'a self, path: &mut Vec>, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized), ) -> bool { // Push the current node onto the path path.push(self.into()); if !on_node(path) { return false; } match self { Self::Stmt(x) => { for s in &**x { if !s.walk(path, on_node) { return false; } } } Self::InterpolatedString(x, ..) | Self::Array(x, ..) => { for e in &**x { if !e.walk(path, on_node) { return false; } } } Self::Map(x, ..) => { for (.., e) in &x.0 { if !e.walk(path, on_node) { return false; } } } Self::Index(x, ..) | Self::Dot(x, ..) | Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => { if !x.lhs.walk(path, on_node) { return false; } if !x.rhs.walk(path, on_node) { return false; } } Self::FnCall(x, ..) => { for e in &*x.args { if !e.walk(path, on_node) { return false; } } } #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(x, ..) => { for e in &*x.inputs { if !e.walk(path, on_node) { return false; } } } _ => (), } path.pop().unwrap(); true } } rhai-1.21.0/src/ast/flags.rs000064400000000000000000000036521046102023000136560ustar 00000000000000//! Module defining script options. use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// A type representing the access mode of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[non_exhaustive] pub enum FnAccess { /// Private function. Private, /// Public function. Public, } impl FnAccess { /// Is this function private? #[inline(always)] #[must_use] pub const fn is_private(self) -> bool { match self { Self::Private => true, Self::Public => false, } } /// Is this function public? #[inline(always)] #[must_use] pub const fn is_public(self) -> bool { match self { Self::Private => false, Self::Public => true, } } } bitflags! { /// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options. /// Exported under the `internals` feature only. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct ASTFlags: u8 { /// The [`AST`][crate::AST] node is read-only. const CONSTANT = 0b_0000_0001; /// The [`AST`][crate::AST] node is exposed to the outside (i.e. public). const EXPORTED = 0b_0000_0010; /// The [`AST`][crate::AST] node is negated (i.e. whatever information is the opposite). const NEGATED = 0b_0000_0100; /// The [`AST`][crate::AST] node breaks out of normal control flow. const BREAK = 0b_0000_1000; } } impl ASTFlags { /// No flags. pub const NONE: Self = Self::empty(); } impl std::fmt::Debug for ASTFlags { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.0) } } rhai-1.21.0/src/ast/ident.rs000064400000000000000000000025411046102023000136610ustar 00000000000000//! Module defining script identifiers. use crate::{ImmutableString, Position}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{borrow::Borrow, fmt, hash::Hash}; /// _(internals)_ An identifier containing a name and a [position][Position]. /// Exported under the `internals` feature only. #[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. pub name: ImmutableString, /// Position. pub pos: Position, } impl fmt::Debug for Ident { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.name)?; if !self.pos.is_none() { write!(f, " @ {:?}", self.pos)?; } Ok(()) } } impl Borrow for Ident { #[inline(always)] #[must_use] fn borrow(&self) -> &str { self.name.as_ref() } } impl AsRef for Ident { #[inline(always)] #[must_use] fn as_ref(&self) -> &str { self.name.as_ref() } } impl Ident { /// Get the name of the identifier as a string slice. #[inline(always)] #[must_use] pub fn as_str(&self) -> &str { &self.name } /// Is the identifier empty? #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.name.is_empty() } } rhai-1.21.0/src/ast/mod.rs000064400000000000000000000017461046102023000133430ustar 00000000000000//! Module defining the AST (abstract syntax tree). #[allow(clippy::module_inception)] pub mod ast; pub mod expr; pub mod flags; pub mod ident; pub mod namespace; pub mod script_fn; pub mod stmt; pub use ast::{ASTNode, EncapsulatedEnviron, AST}; #[cfg(not(feature = "no_custom_syntax"))] pub use expr::CustomExpr; pub use expr::{BinaryExpr, Expr, FnCallExpr, FnCallHashes}; pub use flags::{ASTFlags, FnAccess}; pub use ident::Ident; #[cfg(not(feature = "no_module"))] pub use namespace::Namespace; #[cfg(not(feature = "no_function"))] pub use script_fn::{ScriptFnMetadata, ScriptFuncDef}; pub use stmt::{ CaseBlocksList, FlowControl, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, }; /// _(internals)_ Empty placeholder for a script-defined function. /// Exported under the `internals` feature only. #[cfg(feature = "no_function")] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] pub struct ScriptFuncDef; rhai-1.21.0/src/ast/namespace.rs000064400000000000000000000054661046102023000145230ustar 00000000000000//! Namespace reference type. #![cfg(not(feature = "no_module"))] use crate::ast::Ident; use crate::{Position, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, num::NonZeroUsize}; /// _(internals)_ A chain of [module][crate::Module] names to namespace-qualify a variable or function call. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. /// /// A [`u64`] offset to the current stack of imported [modules][crate::Module] in the /// [global runtime state][crate::GlobalRuntimeState] is cached for quick search purposes. /// /// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only /// one level, and it is wasteful to always allocate a [`Vec`] with one element. #[derive(Clone, Eq, PartialEq, Default, Hash)] #[non_exhaustive] pub struct Namespace { /// Path segments. pub path: StaticVec, /// Cached index into the current stack of imported [modules][crate::Module], if any. pub index: Option, } impl fmt::Debug for Namespace { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return f.write_str("NONE"); } if let Some(index) = self.index { write!(f, "{index} -> ")?; } f.write_str( &self .path .iter() .map(Ident::as_str) .collect::>() .join(crate::engine::NAMESPACE_SEPARATOR), ) } } impl fmt::Display for Namespace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return Ok(()); } f.write_str( &self .path .iter() .map(Ident::as_str) .collect::>() .join(crate::engine::NAMESPACE_SEPARATOR), ) } } impl Namespace { /// Constant for no namespace. pub const NONE: Self = Self { index: None, path: StaticVec::new_const(), }; /// Is this [`Namespace`] empty? #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.path.is_empty() } /// Get the [position][Position] of this [`Namespace`]. /// /// # Panics /// /// Panics if the path is empty. #[inline(always)] #[must_use] pub fn position(&self) -> Position { self.path[0].pos } /// Get the first path segment of this [`Namespace`]. /// /// # Panics /// /// Panics if the path is empty. #[inline(always)] #[must_use] pub fn root(&self) -> &str { &self.path[0].name } } rhai-1.21.0/src/ast/script_fn.rs000064400000000000000000000141521046102023000145460ustar 00000000000000//! Module defining script-defined functions. #![cfg(not(feature = "no_function"))] use super::{FnAccess, StmtBlock}; use crate::{FnArgsVec, ImmutableString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, hash::Hash}; /// _(internals)_ A type containing information on a script-defined function. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] pub struct ScriptFuncDef { /// Function body. pub body: StmtBlock, /// Function name. pub name: ImmutableString, /// Function access mode. pub access: FnAccess, #[cfg(not(feature = "no_object"))] /// Type of `this` pointer, if any. /// Not available under `no_object`. pub this_type: Option, /// Names of function parameters. pub params: FnArgsVec, /// _(metadata)_ Function doc-comments (if any). Exported under the `metadata` feature only. /// /// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`, /// placed immediately before a function definition. /// /// Block doc-comments are kept in a single string with line-breaks within. /// /// Line doc-comments are merged, with line-breaks, into a single string without a termination line-break. /// /// Leading white-spaces are stripped, and each string always starts with the corresponding /// doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments starts with `///`. #[cfg(feature = "metadata")] pub comments: crate::StaticVec, } impl ScriptFuncDef { /// Clone this [`ScriptFuncDef`] but with only signature-related info. /// /// The body of the function is removed, as well as comments (if any). #[allow(dead_code)] pub(crate) fn clone_function_signatures(&self) -> Self { Self { name: self.name.clone(), access: self.access, body: StmtBlock::NONE, #[cfg(not(feature = "no_object"))] this_type: self.this_type.clone(), params: self.params.clone(), #[cfg(feature = "metadata")] comments: <_>::default(), } } } impl fmt::Display for ScriptFuncDef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_object"))] let this_type = self .this_type .as_ref() .map_or(String::new(), |s| format!("{s:?}.")); #[cfg(feature = "no_object")] let this_type = ""; write!( f, "{}{}{}({})", match self.access { FnAccess::Public => "", FnAccess::Private => "private ", }, this_type, self.name, self.params .iter() .map(ImmutableString::as_str) .collect::>() .join(", ") ) } } /// A type containing the metadata of a script-defined function. /// /// Not available under `no_function`. /// /// Created by [`AST::iter_functions`][super::AST::iter_functions]. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[non_exhaustive] pub struct ScriptFnMetadata<'a> { /// Function name. pub name: &'a str, /// Function parameters (if any). #[cfg_attr( feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty") )] pub params: Vec<&'a str>, /// Function access mode. pub access: FnAccess, /// Type of `this` pointer, if any. /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] #[cfg_attr( feature = "serde", serde(default, skip_serializing_if = "Option::is_none") )] pub this_type: Option<&'a str>, /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. /// /// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`, /// placed immediately before a function definition. /// /// Block doc-comments are kept in a single string slice with line-breaks within. /// /// Line doc-comments are merged, with line-breaks, into a single string slice without a termination line-break. /// /// Leading white-spaces are stripped, and each string slice always starts with the /// corresponding doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments starts with `///`. #[cfg(feature = "metadata")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub comments: Vec<&'a str>, } impl fmt::Display for ScriptFnMetadata<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "no_object"))] let this_type = self .this_type .as_ref() .map_or(String::new(), |s| format!("{s:?}.")); #[cfg(feature = "no_object")] let this_type = ""; write!( f, "{}{}{}({})", match self.access { FnAccess::Public => "", FnAccess::Private => "private ", }, this_type, self.name, self.params .iter() .copied() .collect::>() .join(", ") ) } } impl<'a> From<&'a ScriptFuncDef> for ScriptFnMetadata<'a> { #[inline] fn from(value: &'a ScriptFuncDef) -> Self { Self { name: &value.name, params: value.params.iter().map(ImmutableString::as_str).collect(), access: value.access, #[cfg(not(feature = "no_object"))] this_type: value.this_type.as_deref(), #[cfg(feature = "metadata")] comments: value.comments.iter().map(<_>::as_ref).collect(), } } } rhai-1.21.0/src/ast/stmt.rs000064400000000000000000001105511046102023000135460ustar 00000000000000//! Module defining script statements. use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident}; use crate::engine::{KEYWORD_EVAL, OP_EQUALS}; use crate::func::StraightHashMap; use crate::tokenizer::Token; use crate::types::dynamic::Union; use crate::types::Span; use crate::{calc_fn_hash, Dynamic, FnArgsVec, Position, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ borrow::Borrow, fmt, hash::{Hash, Hasher}, mem, num::NonZeroUsize, ops::{Range, RangeInclusive}, }; /// _(internals)_ An op-assignment operator. /// Exported under the `internals` feature only. /// /// This type may hold a straight assignment (i.e. not an op-assignment). #[derive(Clone, PartialEq, Hash)] pub struct OpAssignment { /// Hash of the op-assignment call. hash_op_assign: u64, /// Hash of the underlying operator call (for fallback). hash_op: u64, /// Op-assignment operator. op_assign: Token, /// Syntax of op-assignment operator. op_assign_syntax: &'static str, /// Underlying operator. op: Token, /// Syntax of underlying operator. op_syntax: &'static str, /// [Position] of the op-assignment operator. pos: Position, } impl OpAssignment { /// Create a new [`OpAssignment`] that is only a straight assignment. #[must_use] #[inline(always)] pub const fn new_assignment(pos: Position) -> Self { Self { hash_op_assign: 0, hash_op: 0, op_assign: Token::Equals, op_assign_syntax: OP_EQUALS, op: Token::Equals, op_syntax: OP_EQUALS, pos, } } /// Is this an op-assignment? #[must_use] #[inline(always)] pub const fn is_op_assignment(&self) -> bool { !matches!(self.op, Token::Equals) } /// Get information if this [`OpAssignment`] is an op-assignment. /// /// Returns `( hash_op_assign, hash_op, op_assign, op_assign_syntax, op, op_syntax )`: /// /// * `hash_op_assign`: Hash of the op-assignment call. /// * `hash_op`: Hash of the underlying operator call (for fallback). /// * `op_assign`: Op-assignment operator. /// * `op_assign_syntax`: Syntax of op-assignment operator. /// * `op`: Underlying operator. /// * `op_syntax`: Syntax of underlying operator. #[must_use] #[inline] pub const fn get_op_assignment_info( &self, ) -> Option<(u64, u64, &Token, &'static str, &Token, &'static str)> { if self.is_op_assignment() { Some(( self.hash_op_assign, self.hash_op, &self.op_assign, self.op_assign_syntax, &self.op, self.op_syntax, )) } else { None } } /// Get the [position][Position] of this [`OpAssignment`]. #[must_use] #[inline(always)] pub const fn position(&self) -> Position { self.pos } /// Create a new [`OpAssignment`]. /// /// # Panics /// /// Panics if the name is not an op-assignment operator. #[must_use] #[inline(always)] pub fn new_op_assignment(name: &str, pos: Position) -> Self { let op = Token::lookup_symbol_from_syntax(name) .unwrap_or_else(|| panic!("{} is not an op-assignment operator", name)); Self::new_op_assignment_from_token(op, pos) } /// Create a new [`OpAssignment`] from a [`Token`]. /// /// # Panics /// /// Panics if the token is not an op-assignment operator. #[must_use] pub fn new_op_assignment_from_token(op_assign: Token, pos: Position) -> Self { let op = op_assign .get_base_op_from_assignment() .unwrap_or_else(|| panic!("{:?} is not an op-assignment operator", op_assign)); let op_assign_syntax = op_assign.literal_syntax(); let op_syntax = op.literal_syntax(); Self { hash_op_assign: calc_fn_hash(None, op_assign_syntax, 2), hash_op: calc_fn_hash(None, op_syntax, 2), op_assign, op_assign_syntax, op, op_syntax, pos, } } /// Create a new [`OpAssignment`] from a base operator. /// /// # Panics /// /// Panics if the name is not an operator that can be converted into an op-operator. #[must_use] #[inline(always)] pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self { let op = Token::lookup_symbol_from_syntax(name) .unwrap_or_else(|| panic!("{} cannot be converted into an op-operator", name)); Self::new_op_assignment_from_base_token(&op, pos) } /// Convert a [`Token`] into a new [`OpAssignment`]. /// /// # Panics /// /// Panics if the token is cannot be converted into an op-assignment operator. #[inline(always)] #[must_use] pub fn new_op_assignment_from_base_token(op: &Token, pos: Position) -> Self { Self::new_op_assignment_from_token( op.convert_to_op_assignment() .unwrap_or_else(|| panic!("{:?} cannot be converted into an op-operator", op)), pos, ) } } impl fmt::Debug for OpAssignment { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_op_assignment() { f.debug_struct("OpAssignment") .field("hash_op_assign", &self.hash_op_assign) .field("hash_op", &self.hash_op) .field("op_assign", &self.op_assign) .field("op_assign_syntax", &self.op_assign_syntax) .field("op", &self.op) .field("op_syntax", &self.op_syntax) .field("pos", &self.pos) .finish() } else { write!(f, "{} @ {:?}", Token::Equals, self.pos) } } } /// _(internals)_ A type containing a range case for a `switch` statement. /// Exported under the `internals` feature only. #[derive(Clone, Hash)] pub enum RangeCase { /// Exclusive range. ExclusiveInt(Range, usize), /// Inclusive range. InclusiveInt(RangeInclusive, usize), } impl fmt::Debug for RangeCase { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ExclusiveInt(r, n) => write!(f, "{}..{} => {n}", r.start, r.end), Self::InclusiveInt(r, n) => write!(f, "{}..={} => {n}", *r.start(), *r.end()), } } } impl From> for RangeCase { #[inline(always)] fn from(value: Range) -> Self { Self::ExclusiveInt(value, usize::MAX) } } impl From> for RangeCase { #[inline(always)] fn from(value: RangeInclusive) -> Self { Self::InclusiveInt(value, usize::MAX) } } impl IntoIterator for RangeCase { type Item = INT; type IntoIter = Box>; #[inline] #[must_use] fn into_iter(self) -> Self::IntoIter { match self { Self::ExclusiveInt(r, ..) => Box::new(r), Self::InclusiveInt(r, ..) => Box::new(r), } } } impl RangeCase { /// Returns `true` if the range contains no items. #[inline] #[must_use] pub fn is_empty(&self) -> bool { match self { Self::ExclusiveInt(r, ..) => r.is_empty(), Self::InclusiveInt(r, ..) => r.is_empty(), } } /// Size of the range. #[inline] #[must_use] pub fn len(&self) -> INT { match self { Self::ExclusiveInt(r, ..) if r.is_empty() => 0, Self::ExclusiveInt(r, ..) => r.end - r.start, Self::InclusiveInt(r, ..) if r.is_empty() => 0, Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1, } } /// Is the specified value within this range? #[inline] #[must_use] pub fn contains(&self, value: &Dynamic) -> bool { match value { Dynamic(Union::Int(v, ..)) => self.contains_int(*v), #[cfg(not(feature = "no_float"))] Dynamic(Union::Float(v, ..)) => self.contains_float(**v), #[cfg(feature = "decimal")] Dynamic(Union::Decimal(v, ..)) => self.contains_decimal(**v), _ => false, } } /// Is the specified number within this range? #[inline] #[must_use] pub fn contains_int(&self, n: INT) -> bool { match self { Self::ExclusiveInt(r, ..) => r.contains(&n), Self::InclusiveInt(r, ..) => r.contains(&n), } } /// Is the specified floating-point number within this range? #[cfg(not(feature = "no_float"))] #[inline] #[must_use] pub fn contains_float(&self, n: crate::FLOAT) -> bool { use crate::FLOAT; match self { Self::ExclusiveInt(r, ..) => ((r.start as FLOAT)..(r.end as FLOAT)).contains(&n), Self::InclusiveInt(r, ..) => ((*r.start() as FLOAT)..=(*r.end() as FLOAT)).contains(&n), } } /// Is the specified decimal number within this range? #[cfg(feature = "decimal")] #[inline] #[must_use] pub fn contains_decimal(&self, n: rust_decimal::Decimal) -> bool { use rust_decimal::Decimal; match self { Self::ExclusiveInt(r, ..) => { (Into::::into(r.start)..Into::::into(r.end)).contains(&n) } Self::InclusiveInt(r, ..) => { (Into::::into(*r.start())..=Into::::into(*r.end())).contains(&n) } } } /// Is the specified range inclusive? #[inline(always)] #[must_use] pub const fn is_inclusive(&self) -> bool { match self { Self::ExclusiveInt(..) => false, Self::InclusiveInt(..) => true, } } /// Get the index to the list of expressions. #[inline(always)] #[must_use] pub const fn index(&self) -> usize { match self { Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n, } } /// Set the index to the list of expressions. #[inline(always)] pub fn set_index(&mut self, index: usize) { match self { Self::ExclusiveInt(.., n) | Self::InclusiveInt(.., n) => *n = index, } } } pub type CaseBlocksList = smallvec::SmallVec<[usize; 2]>; /// _(internals)_ A type containing all cases for a `switch` statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] pub struct SwitchCasesCollection { /// List of conditional expressions: LHS = condition, RHS = expression. pub expressions: FnArgsVec, /// Dictionary mapping value hashes to [`CaseBlocksList`]'s. pub cases: StraightHashMap, /// List of range cases. pub ranges: StaticVec, /// Statements block for the default case (there can be no condition for the default case). pub def_case: Option, } impl Hash for SwitchCasesCollection { #[inline(always)] fn hash(&self, state: &mut H) { self.expressions.hash(state); self.cases.len().hash(state); self.cases.iter().for_each(|kv| kv.hash(state)); self.ranges.hash(state); self.def_case.hash(state); } } /// Number of items to keep inline for [`StmtBlockContainer`]. #[cfg(not(feature = "no_std"))] const STMT_BLOCK_INLINE_SIZE: usize = 8; /// _(internals)_ The underlying container type for [`StmtBlock`]. /// Exported under the `internals` feature only. /// /// A [`SmallVec`](https://crates.io/crates/smallvec) containing up to 8 items inline is used to /// hold a statements block, with the assumption that most program blocks would container fewer than /// 8 statements, and those that do have a lot more statements. #[cfg(not(feature = "no_std"))] pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; STMT_BLOCK_INLINE_SIZE]>; /// _(internals)_ The underlying container type for [`StmtBlock`]. /// Exported under the `internals` feature only. #[cfg(feature = "no_std")] pub type StmtBlockContainer = crate::StaticVec; /// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. #[derive(Clone, Hash, Default)] pub struct StmtBlock { /// List of [statements][Stmt]. block: StmtBlockContainer, /// [Position] of the statements block. span: Span, } impl StmtBlock { /// A [`StmtBlock`] that does not exist. pub const NONE: Self = Self::empty(Position::NONE); /// Create a new [`StmtBlock`]. #[inline(always)] #[must_use] pub fn new( statements: impl IntoIterator, start_pos: Position, end_pos: Position, ) -> Self { Self::new_with_span(statements, Span::new(start_pos, end_pos)) } /// Create a new [`StmtBlock`]. #[must_use] pub fn new_with_span(statements: impl IntoIterator, span: Span) -> Self { let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect(); statements.shrink_to_fit(); Self { block: statements, span, } } /// Create an empty [`StmtBlock`]. #[inline(always)] #[must_use] pub const fn empty(pos: Position) -> Self { Self { block: StmtBlockContainer::new_const(), span: Span::new(pos, pos), } } /// Returns `true` if this statements block contains no statements. #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.block.is_empty() } /// Number of statements in this statements block. #[inline(always)] #[must_use] pub fn len(&self) -> usize { self.block.len() } /// Get the statements of this statements block. #[inline(always)] #[must_use] pub fn statements(&self) -> &[Stmt] { &self.block } /// Get the statements of this statements block. #[inline(always)] #[must_use] pub fn statements_mut(&mut self) -> &mut StmtBlockContainer { &mut self.block } /// Get an iterator over the statements of this statements block. #[inline(always)] pub fn iter(&self) -> impl Iterator { self.block.iter() } /// Get the start position (location of the beginning `{`) of this statements block. #[inline(always)] #[must_use] pub const fn position(&self) -> Position { (self.span).start() } /// Get the end position (location of the ending `}`) of this statements block. #[inline(always)] #[must_use] pub const fn end_position(&self) -> Position { (self.span).end() } /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block. #[inline(always)] #[must_use] pub const fn span(&self) -> Span { self.span } /// Get the positions (locations of the beginning `{` and ending `}`) of this statements block /// or a default. #[inline(always)] #[must_use] pub const fn span_or_else(&self, def_start_pos: Position, def_end_pos: Position) -> Span { Span::new( (self.span).start().or_else(def_start_pos), (self.span).end().or_else(def_end_pos), ) } /// Set the positions of this statements block. #[inline(always)] pub fn set_position(&mut self, start_pos: Position, end_pos: Position) { self.span = Span::new(start_pos, end_pos); } } impl Borrow<[Stmt]> for StmtBlock { #[inline(always)] #[must_use] fn borrow(&self) -> &[Stmt] { &self.block } } impl AsRef<[Stmt]> for StmtBlock { #[inline(always)] #[must_use] fn as_ref(&self) -> &[Stmt] { &self.block } } impl AsMut<[Stmt]> for StmtBlock { #[inline(always)] #[must_use] fn as_mut(&mut self) -> &mut [Stmt] { &mut self.block } } impl fmt::Debug for StmtBlock { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Block")?; fmt::Debug::fmt(&self.block, f)?; if !self.span.is_none() { write!(f, " @ {:?}", self.span())?; } Ok(()) } } impl From for StmtBlock { #[inline] fn from(stmt: Stmt) -> Self { match stmt { Stmt::Block(block) => *block, Stmt::Noop(pos) => Self { block: StmtBlockContainer::new_const(), span: Span::new(pos, pos), }, _ => { let pos = stmt.position(); Self { block: vec![stmt].into(), span: Span::new(pos, Position::NONE), } } } } } impl IntoIterator for StmtBlock { type Item = Stmt; #[cfg(not(feature = "no_std"))] type IntoIter = smallvec::IntoIter<[Stmt; STMT_BLOCK_INLINE_SIZE]>; #[cfg(feature = "no_std")] type IntoIter = smallvec::IntoIter<[Stmt; crate::STATIC_VEC_INLINE_SIZE]>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.block.into_iter() } } impl<'a> IntoIterator for &'a StmtBlock { type Item = &'a Stmt; type IntoIter = std::slice::Iter<'a, Stmt>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.block.iter() } } impl Extend for StmtBlock { #[inline(always)] fn extend>(&mut self, iter: T) { self.block.extend(iter); } } /// _(internals)_ A flow control block containing: /// * an expression, /// * a statements body /// * an alternate statements body /// /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] pub struct FlowControl { /// Flow control expression. pub expr: Expr, /// Main body. pub body: StmtBlock, /// Branch body. pub branch: StmtBlock, } /// _(internals)_ A statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] #[non_exhaustive] #[allow(clippy::type_complexity)] pub enum Stmt { /// No-op. Noop(Position), /// `if` expr `{` stmt `}` `else` `{` stmt `}` If(Box, Position), /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}` /// /// ### Data Structure /// /// 0) Hash table for (condition, block) /// 1) Default block /// 2) List of ranges: (start, end, inclusive, condition, statement) Switch(Box<(Expr, SwitchCasesCollection)>, Position), /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. While(Box, Position), /// `do` `{` stmt `}` `while`|`until` expr /// /// ### Flags /// /// * [`NONE`][ASTFlags::NONE] = `while` /// * [`NEGATED`][ASTFlags::NEGATED] = `until` Do(Box, ASTFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Box<(Ident, Option, FlowControl)>, Position), /// \[`export`\] `let`|`const` id `=` expr /// /// ### Flags /// /// * [`EXPORTED`][ASTFlags::EXPORTED] = `export` /// * [`CONSTANT`][ASTFlags::CONSTANT] = `const` Var(Box<(Ident, Expr, Option)>, ASTFlags, Position), /// expr op`=` expr Assignment(Box<(OpAssignment, BinaryExpr)>), /// func `(` expr `,` ... `)` /// /// This is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single /// function call forming one statement. FnCall(Box, Position), /// `{` stmt`;` ... `}` Block(Box), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` TryCatch(Box, Position), /// [expression][Expr] Expr(Box), /// `continue`/`break` expr /// /// ### Flags /// /// * [`NONE`][ASTFlags::NONE] = `continue` /// * [`BREAK`][ASTFlags::BREAK] = `break` BreakLoop(Option>, ASTFlags, Position), /// `return`/`throw` expr /// /// ### Flags /// /// * [`NONE`][ASTFlags::NONE] = `return` /// * [`BREAK`][ASTFlags::BREAK] = `throw` Return(Option>, ASTFlags, Position), /// `import` expr `as` alias /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] Import(Box<(Expr, Ident)>, Position), /// `export` var `as` alias /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] Export(Box<(Ident, Ident)>, Position), /// Convert a list of variables to shared. /// /// Not available under `no_closure`. /// /// # Notes /// /// This variant does not map to any language structure. It is currently only used only to /// convert normal variables into shared variables when they are _captured_ by a closure. #[cfg(not(feature = "no_closure"))] Share(Box)>>), } impl Default for Stmt { #[inline(always)] #[must_use] fn default() -> Self { Self::Noop(Position::NONE) } } impl Stmt { /// Is this statement [`Noop`][Stmt::Noop]? #[inline(always)] #[must_use] pub const fn is_noop(&self) -> bool { matches!(self, Self::Noop(..)) } /// Get the [options][ASTFlags] of this statement. #[inline] #[must_use] pub const fn options(&self) -> ASTFlags { match self { Self::Do(_, options, _) | Self::Var(_, options, _) | Self::BreakLoop(_, options, _) | Self::Return(_, options, _) => *options, Self::Noop(..) | Self::If(..) | Self::Switch(..) | Self::Block(..) | Self::Expr(..) | Self::FnCall(..) | Self::While(..) | Self::For(..) | Self::TryCatch(..) | Self::Assignment(..) => ASTFlags::empty(), #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => ASTFlags::empty(), #[cfg(not(feature = "no_closure"))] Self::Share(..) => ASTFlags::empty(), } } /// Get the [position][Position] of this statement. #[must_use] pub fn position(&self) -> Position { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) | Self::Switch(.., pos) | Self::While(.., pos) | Self::Do(.., pos) | Self::For(.., pos) | Self::Return(.., pos) | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos, Self::Assignment(x) => x.0.pos, Self::Block(x) => x.position(), Self::Expr(x) => x.start_position(), #[cfg(not(feature = "no_module"))] Self::Import(.., pos) => *pos, #[cfg(not(feature = "no_module"))] Self::Export(.., pos) => *pos, #[cfg(not(feature = "no_closure"))] Self::Share(x) => x[0].0.pos, } } /// Override the [position][Position] of this statement. pub fn set_position(&mut self, new_pos: Position) -> &mut Self { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) | Self::Switch(.., pos) | Self::While(.., pos) | Self::Do(.., pos) | Self::For(.., pos) | Self::Return(.., pos) | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos = new_pos, Self::Assignment(x) => x.0.pos = new_pos, Self::Block(x) => x.set_position(new_pos, x.end_position()), Self::Expr(x) => { x.set_position(new_pos); } #[cfg(not(feature = "no_module"))] Self::Import(.., pos) => *pos = new_pos, #[cfg(not(feature = "no_module"))] Self::Export(.., pos) => *pos = new_pos, #[cfg(not(feature = "no_closure"))] Self::Share(x) => x.iter_mut().for_each(|(x, _)| x.pos = new_pos), } self } /// Does this statement return a value? #[must_use] pub const fn returns_value(&self) -> bool { match self { Self::If(..) | Self::Switch(..) | Self::Block(..) | Self::Expr(..) | Self::FnCall(..) => true, Self::Noop(..) | Self::While(..) | Self::Do(..) | Self::For(..) | Self::TryCatch(..) => false, Self::Var(..) | Self::Assignment(..) | Self::BreakLoop(..) | Self::Return(..) => false, #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => false, #[cfg(not(feature = "no_closure"))] Self::Share(..) => false, } } /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? #[must_use] pub const fn is_self_terminated(&self) -> bool { match self { Self::If(..) | Self::Switch(..) | Self::While(..) | Self::For(..) | Self::Block(..) | Self::TryCatch(..) => true, // A No-op requires a semicolon in order to know it is an empty statement! Self::Noop(..) => false, Self::Expr(e) => match &**e { #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(x, ..) if x.is_self_terminated() => true, _ => false, }, Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) | Self::Do(..) | Self::BreakLoop(..) | Self::Return(..) => false, #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => false, #[cfg(not(feature = "no_closure"))] Self::Share(..) => false, } } /// Is this statement _pure_? /// /// A pure statement has no side effects. #[must_use] pub fn is_pure(&self) -> bool { match self { Self::Noop(..) => true, Self::Expr(expr) => expr.is_pure(), Self::If(x, ..) => { x.expr.is_pure() && x.body.iter().all(Self::is_pure) && x.branch.iter().all(Self::is_pure) } Self::Switch(x, ..) => { let (expr, sw) = &**x; expr.is_pure() && sw.cases.values().flat_map(|cases| cases.iter()).all(|&c| { let block = &sw.expressions[c]; block.lhs.is_pure() && block.rhs.is_pure() }) && sw.ranges.iter().all(|r| { let block = &sw.expressions[r.index()]; block.lhs.is_pure() && block.rhs.is_pure() }) && sw.def_case.is_some() && sw.expressions[sw.def_case.unwrap()].rhs.is_pure() } // Loops that exit can be pure because it can never be infinite. Self::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => true, Self::Do(x, options, ..) if matches!(x.expr, Expr::BoolConstant(..)) => match x.expr { Expr::BoolConstant(cond, ..) if cond == options.intersects(ASTFlags::NEGATED) => { x.body.iter().all(Self::is_pure) } _ => false, }, // Loops are never pure since they can be infinite - and that's a side effect. Self::While(..) | Self::Do(..) => false, // For loops can be pure because if the iterable is pure, it is finite, // so infinite loops can never occur. Self::For(x, ..) => x.2.expr.is_pure() && x.2.body.iter().all(Self::is_pure), Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false, Self::Block(block, ..) => block.iter().all(Self::is_pure), Self::BreakLoop(..) | Self::Return(..) => false, Self::TryCatch(x, ..) => { x.expr.is_pure() && x.body.iter().all(Self::is_pure) && x.branch.iter().all(Self::is_pure) } #[cfg(not(feature = "no_module"))] Self::Import(..) => false, #[cfg(not(feature = "no_module"))] Self::Export(..) => false, #[cfg(not(feature = "no_closure"))] Self::Share(..) => false, } } /// Does this statement's behavior depend on its containing block? /// /// A statement that depends on its containing block behaves differently when promoted to an /// upper block. /// /// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements, /// and `eval` calls (which may in turn define variables) fall under this category. #[inline] #[must_use] pub fn is_block_dependent(&self) -> bool { match self { Self::Var(..) => true, Self::Expr(e) => match &**e { Expr::Stmt(s) => s.iter().all(Self::is_block_dependent), #[cfg(not(feature = "no_module"))] Expr::FnCall(x, ..) if x.is_qualified() => false, Expr::FnCall(x, ..) => x.name == KEYWORD_EVAL, _ => false, }, #[cfg(not(feature = "no_module"))] Self::FnCall(x, ..) if x.is_qualified() => false, Self::FnCall(x, ..) => x.name == KEYWORD_EVAL, #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => true, _ => false, } } /// Is this statement _pure_ within the containing block? /// /// An internally pure statement only has side effects that disappear outside the block. /// /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` /// statements are internally pure, other than pure expressions. #[inline] #[must_use] pub fn is_internally_pure(&self) -> bool { match self { Self::Var(x, ..) => x.1.is_pure(), Self::Expr(e) => match &**e { Expr::Stmt(s) => s.iter().all(Self::is_internally_pure), _ => self.is_pure(), }, #[cfg(not(feature = "no_module"))] Self::Import(x, ..) => x.0.is_pure(), #[cfg(not(feature = "no_module"))] Self::Export(..) => true, _ => self.is_pure(), } } /// Does this statement break the current control flow through the containing block? /// /// Currently this is only true for `return`, `throw`, `break` and `continue`. /// /// All statements following this statement will essentially be dead code. #[inline] #[must_use] pub const fn is_control_flow_break(&self) -> bool { matches!(self, Self::Return(..) | Self::BreakLoop(..)) } /// Return this [`Stmt`], replacing it with [`Stmt::Noop`]. #[inline(always)] #[must_use] pub fn take(&mut self) -> Self { mem::take(self) } /// Recursively walk this statement. /// Return `false` from the callback to terminate the walk. pub fn walk<'a>( &'a self, path: &mut Vec>, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized), ) -> bool { // Push the current node onto the path path.push(self.into()); if !on_node(path) { return false; } match self { Self::Var(x, ..) => { if !x.1.walk(path, on_node) { return false; } } Self::If(x, ..) => { if !x.expr.walk(path, on_node) { return false; } for s in &x.body { if !s.walk(path, on_node) { return false; } } for s in &x.branch { if !s.walk(path, on_node) { return false; } } } Self::Switch(x, ..) => { let (expr, sw) = &**x; if !expr.walk(path, on_node) { return false; } for (.., blocks) in &sw.cases { for &b in blocks { let block = &sw.expressions[b]; if !block.lhs.walk(path, on_node) { return false; } if !block.rhs.walk(path, on_node) { return false; } } } for r in &sw.ranges { let block = &sw.expressions[r.index()]; if !block.lhs.walk(path, on_node) { return false; } if !block.rhs.walk(path, on_node) { return false; } } if let Some(index) = sw.def_case { if !sw.expressions[index].lhs.walk(path, on_node) { return false; } } } Self::While(x, ..) | Self::Do(x, ..) => { if !x.expr.walk(path, on_node) { return false; } for s in x.body.statements() { if !s.walk(path, on_node) { return false; } } } Self::For(x, ..) => { if !x.2.expr.walk(path, on_node) { return false; } for s in &x.2.body { if !s.walk(path, on_node) { return false; } } } Self::Assignment(x, ..) => { if !x.1.lhs.walk(path, on_node) { return false; } if !x.1.rhs.walk(path, on_node) { return false; } } Self::FnCall(x, ..) => { for s in &*x.args { if !s.walk(path, on_node) { return false; } } } Self::Block(x, ..) => { for s in x.statements() { if !s.walk(path, on_node) { return false; } } } Self::TryCatch(x, ..) => { for s in &x.body { if !s.walk(path, on_node) { return false; } } for s in &x.branch { if !s.walk(path, on_node) { return false; } } } Self::Expr(e) => { if !e.walk(path, on_node) { return false; } } Self::Return(Some(e), ..) => { if !e.walk(path, on_node) { return false; } } #[cfg(not(feature = "no_module"))] Self::Import(x, ..) => { if !x.0.walk(path, on_node) { return false; } } _ => (), } path.pop().unwrap(); true } } rhai-1.21.0/src/bin/README.md000064400000000000000000000031351046102023000134500ustar 00000000000000Rhai Tools ========== Tools for working with Rhai scripts. | Tool | Required feature(s) | Description | | -------------------------------------------------------------------------------- | :-----------------: | ----------------------------------------------------- | | [`rhai-run`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-run.rs) | | runs each filename passed to it as a Rhai script | | [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | a simple REPL that interactively evaluates statements | | [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ | For convenience, a feature named `bin-features` is available which is a combination of the following: * `decimal` – support for decimal numbers * `metadata` – access functions metadata * `serde` – export functions metadata to JSON * `debugging` – required by `rhai-dbg` * `rustyline` – required by `rhai-repl` How to Run ---------- ```sh cargo run --features bin-features --bin sample_app_to_run ``` How to Install -------------- To install these all tools (with full features), use the following command: ```sh cargo install --path . --bins --features bin-features ``` or specifically: ```sh cargo install --path . --bin sample_app_to_run --features bin-features ``` rhai-1.21.0/src/bin/rhai-dbg.rs000064400000000000000000000630431046102023000142200ustar 00000000000000use rhai::debugger::{BreakPoint, DebuggerCommand, DebuggerEvent}; use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope, INT}; use std::{ env, fs::File, io::{stdin, stdout, Read, Write}, path::Path, process::exit, }; /// Pretty-print source line. fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, usize)) { if pos.is_none() { // No position println!(); return; } let line = pos.line().unwrap() - 1; let start = if line >= window.0 { line - window.0 } else { 0 }; let end = usize::min(line + window.1, lines.len() - 1); let line_no_len = end.to_string().len(); // Print error position if start >= end { println!("{}: {}", start + 1, lines[start]); if let Some(pos) = pos.position() { println!("{0:>1$}", "^", pos + offset + line_no_len + 2); } } else { for (n, s) in lines.iter().enumerate().take(end + 1).skip(start) { let marker = if n == line { "> " } else { " " }; println!( "{0}{1}{2:>3$}{5}│ {0}{4}{5}", if n == line { "\x1b[33m" } else { "" }, marker, n + 1, line_no_len, s, if n == line { "\x1b[39m" } else { "" }, ); if n == line { if let Some(pos) = pos.position() { let shift = offset + line_no_len + marker.len() + 2; println!("{0:>1$}{2:>3$}", "│ ", shift, "\x1b[36m^\x1b[39m", pos + 10); } } } } } fn print_current_source( context: &mut rhai::EvalContext, source: Option<&str>, pos: Position, lines: &[String], window: (usize, usize), ) { let current_source = &mut *context .global_runtime_state_mut() .debugger_mut() .state_mut() .as_immutable_string_mut() .unwrap(); let src = source.unwrap_or(""); if src != current_source { println!( "\x1b[34m>>> Source => {}\x1b[39m", source.unwrap_or("main script") ); *current_source = src.into(); } if !src.is_empty() { // Print just a line number for imported modules println!("{src} @ {pos:?}"); } else { // Print the current source line print_source(lines, pos, 0, window); } } /// Pretty-print error. fn print_error(input: &str, mut err: EvalAltResult) { // Do not use `line` because it "eats" the last empty line if the script ends with a newline. let lines: Vec<_> = input.split('\n').collect(); let pos = err.take_position(); let line_no = if lines.len() > 1 { if pos.is_none() { String::new() } else { format!("{}: ", pos.line().unwrap()) } } else { String::new() }; // Print error position if pos.is_none() { // No position println!("\x1b[31m{err}\x1b[39m"); } else { // Specific position - print line text println!("{line_no}{}", lines[pos.line().unwrap() - 1]); for (i, err_line) in err.to_string().lines().enumerate() { // Display position marker println!( "\x1b[31m{0:>1$}{err_line}\x1b[39m", if i > 0 { "| " } else { "^ " }, line_no.len() + pos.position().unwrap() + 1, ); } } } /// Print debug help. fn print_debug_help() { println!("help, h => print this help"); println!("quit, q, exit, kill => quit"); println!("scope => print the scope"); println!("operations => print the total operations performed"); println!("source => print the current source"); println!("print, p => print all variables de-duplicated"); println!("print/p this => print the `this` pointer"); println!("print/p => print the current value of a variable"); #[cfg(not(feature = "no_module"))] println!("imports => print all imported modules"); println!("node => print the current AST node"); println!("list, l => print the current source line"); println!("list/l => print a source line"); println!("backtrace, bt => print the current call-stack"); println!("info break, i b => print all break-points"); println!("enable/en => enable a break-point"); println!("disable/dis => disable a break-point"); println!("delete, d => delete all break-points"); println!("delete/d => delete a break-point"); #[cfg(not(feature = "no_position"))] println!("break, b => set a new break-point at the current position"); #[cfg(not(feature = "no_position"))] println!("break/b => set a new break-point at a line number"); #[cfg(not(feature = "no_object"))] println!("break/b . => set a new break-point for a property access"); println!("break/b => set a new break-point for a function call"); println!( "break/b <#args> => set a new break-point for a function call with #args arguments" ); println!("throw => throw a runtime exception"); println!("throw => throw an exception with string data"); println!("throw <#> => throw an exception with numeric data"); println!("run, r => restart the script evaluation from beginning"); println!("step, s => go to the next expression, diving into functions"); println!("over, o => go to the next expression, skipping oer functions"); println!("next, n, => go to the next statement, skipping over functions"); println!("finish, f => continue until the end of the current function call"); println!("continue, c => continue normal execution"); println!(); } // Load script to debug. fn load_script(engine: &Engine) -> (rhai::AST, String) { if let Some(filename) = env::args().nth(1) { let mut contents = String::new(); let filename = match Path::new(&filename).canonicalize() { Err(err) => { eprintln!("\x1b[31mError script file path: {filename}\n{err}\x1b[39m"); exit(1); } Ok(f) => { match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) { Ok(f) => f.into(), _ => f, } } }; let mut f = match File::open(&filename) { Err(err) => { eprintln!( "\x1b[31mError reading script file: {}\n{}\x1b[39m", filename.to_string_lossy(), err ); exit(1); } Ok(f) => f, }; if let Err(err) = f.read_to_string(&mut contents) { println!( "Error reading script file: {}\n{}", filename.to_string_lossy(), err ); exit(1); } let script = if contents.starts_with("#!") { // Skip shebang &contents[contents.find('\n').unwrap_or(0)..] } else { &contents[..] }; let ast = match engine .compile(script) .map_err(Into::>::into) { Err(err) => { print_error(script, *err); exit(1); } Ok(ast) => ast, }; println!("Script '{}' loaded.", filename.to_string_lossy()); (ast, contents) } else { eprintln!("\x1b[31mNo script file specified.\x1b[39m"); exit(1); } } // Main callback for debugging. fn debug_callback( mut context: rhai::EvalContext, event: DebuggerEvent, node: rhai::ASTNode, source: Option<&str>, pos: Position, lines: &[String], ) -> Result> { // Check event match event { DebuggerEvent::Start if source.is_some() => { println!("\x1b[32m! Script '{}' start\x1b[39m", source.unwrap()) } DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"), DebuggerEvent::End if source.is_some() => { println!("\x1b[31m! Script '{}' end\x1b[39m", source.unwrap()) } DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"), DebuggerEvent::Step => (), DebuggerEvent::BreakPoint(n) => { match context.global_runtime_state().debugger().break_points()[n] { #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { .. } => (), BreakPoint::AtFunctionName { ref name, .. } | BreakPoint::AtFunctionCall { ref name, .. } => { println!("! Call to function {name}.") } #[cfg(not(feature = "no_object"))] BreakPoint::AtProperty { ref name, .. } => { println!("! Property {name} accessed.") } _ => unreachable!(), } } DebuggerEvent::FunctionExitWithValue(r) => { println!( "! Return from function call '{}' => {:?}", context .global_runtime_state() .debugger() .call_stack() .last() .unwrap() .fn_name, r ) } DebuggerEvent::FunctionExitWithError(err) => { println!( "! Return from function call '{}' with error: {}", context .global_runtime_state() .debugger() .call_stack() .last() .unwrap() .fn_name, err ) } _ => unreachable!(), } // Print current source line print_current_source(&mut context, source, pos, lines, (0, 0)); // Read stdin for commands let mut input = String::new(); loop { print!("dbg> "); stdout().flush().expect("couldn't flush stdout"); input.clear(); match stdin().read_line(&mut input) { Ok(0) => break Ok(DebuggerCommand::Continue), Ok(_) => match input.split_whitespace().collect::>().as_slice() { ["help" | "h"] => print_debug_help(), ["exit" | "quit" | "q" | "kill", ..] => { println!("Script terminated. Bye!"); exit(0); } ["node"] => { if pos.is_none() { println!("{node:?}"); } else { match source { Some(source) => println!("{node:?} {source} @ {pos:?}"), None => println!("{node:?} @ {pos:?}"), } } println!(); } ["operations"] => { println!("{}", context.global_runtime_state().num_operations) } ["source"] => { println!("{}", context.global_runtime_state().source().unwrap_or("")) } ["list" | "l"] => print_current_source(&mut context, source, pos, lines, (3, 6)), ["list" | "l", n] if n.parse::().is_ok() => { let num = n.parse::().unwrap(); if num == 0 || num > lines.len() { eprintln!("\x1b[31mInvalid line: {num}\x1b[39m"); } else { let pos = Position::new(num as u16, 0); print_current_source(&mut context, source, pos, lines, (3, 6)); } } ["continue" | "c"] => break Ok(DebuggerCommand::Continue), ["finish" | "f"] => break Ok(DebuggerCommand::FunctionExit), [] | ["step" | "s"] => break Ok(DebuggerCommand::StepInto), ["over" | "o"] => break Ok(DebuggerCommand::StepOver), ["next" | "n"] => break Ok(DebuggerCommand::Next), ["scope"] => println!("{}", context.scope()), ["print" | "p", "this"] => match context.this_ptr() { Some(value) => println!("=> {value:?}"), None => println!("`this` pointer is unbound."), }, ["print" | "p", var_name] => match context.scope().get_value::(var_name) { Some(value) => println!("=> {value:?}"), None => eprintln!("Variable not found: {var_name}"), }, ["print" | "p"] => { println!("{}", context.scope().clone_visible()); if let Some(value) = context.this_ptr() { println!("this = {value:?}"); } } #[cfg(not(feature = "no_module"))] ["imports"] => { for (i, (name, module)) in context .global_runtime_state() .scan_imports_raw() .enumerate() { println!( "[{}] {} = {}", i + 1, name, module.id().unwrap_or("") ); } println!(); } #[cfg(not(feature = "no_function"))] ["backtrace" | "bt"] => { for frame in context .global_runtime_state() .debugger() .call_stack() .iter() .rev() { println!("{frame}") } } ["info" | "i", "break" | "b"] => Iterator::for_each( context .global_runtime_state() .debugger() .break_points() .iter() .enumerate(), |(i, bp)| match bp { #[cfg(not(feature = "no_position"))] rhai::debugger::BreakPoint::AtPosition { pos, .. } => { let line_num = format!("[{}] line ", i + 1); print!("{line_num}"); print_source(lines, *pos, line_num.len(), (0, 0)); } _ => println!("[{}] {bp}", i + 1), }, ), ["enable" | "en", n] => { if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .get_mut(n - 1) .unwrap() .enable(true); println!("Break-point #{n} enabled.") } else { eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["disable" | "dis", n] => { if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .get_mut(n - 1) .unwrap() .enable(false); println!("Break-point #{n} disabled.") } else { eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["delete" | "d", n] => { if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .remove(n - 1); println!("Break-point #{n} deleted.") } else { eprintln!("\x1b[31mInvalid break-point: {n}\x1b[39m"); } } else { eprintln!("\x1b[31mInvalid break-point: '{n}'\x1b[39m"); } } ["delete" | "d"] => { context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .clear(); println!("All break-points deleted."); } ["break" | "b", fn_name, args] => { if let Ok(args) = args.parse::() { let bp = rhai::debugger::BreakPoint::AtFunctionCall { name: fn_name.trim().into(), args, enabled: true, }; println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .push(bp); } else { eprintln!("\x1b[31mInvalid number of arguments: '{args}'\x1b[39m"); } } // Property name #[cfg(not(feature = "no_object"))] ["break" | "b", param] if param.starts_with('.') && param.len() > 1 => { let bp = rhai::debugger::BreakPoint::AtProperty { name: param[1..].into(), enabled: true, }; println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .push(bp); } // Numeric parameter #[cfg(not(feature = "no_position"))] ["break" | "b", param] if param.parse::().is_ok() => { let n = param.parse::().unwrap(); let range = if source.is_none() { 1..=lines.len() } else { 1..=(u16::MAX as usize) }; if range.contains(&n) { let bp = rhai::debugger::BreakPoint::AtPosition { source: source.map(|s| s.into()), pos: Position::new(n as u16, 0), enabled: true, }; println!("Break-point added {bp}"); context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .push(bp); } else { eprintln!("\x1b[31mInvalid line number: '{n}'\x1b[39m"); } } // Function name parameter ["break" | "b", param] => { let bp = rhai::debugger::BreakPoint::AtFunctionName { name: param.trim().into(), enabled: true, }; println!("Break-point added for {bp}"); context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .push(bp); } #[cfg(not(feature = "no_position"))] ["break" | "b"] => { let bp = rhai::debugger::BreakPoint::AtPosition { source: source.map(|s| s.into()), pos, enabled: true, }; println!("Break-point added {bp}"); context .global_runtime_state_mut() .debugger_mut() .break_points_mut() .push(bp); } ["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()), ["throw", num] if num.trim().parse::().is_ok() => { let value = num.trim().parse::().unwrap().into(); break Err(EvalAltResult::ErrorRuntime(value, pos).into()); } #[cfg(not(feature = "no_float"))] ["throw", num] if num.trim().parse::().is_ok() => { let value = num.trim().parse::().unwrap().into(); break Err(EvalAltResult::ErrorRuntime(value, pos).into()); } ["throw", ..] => { let msg = input.trim().split_once(' ').map(|(_, x)| x).unwrap_or(""); break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into()); } ["run" | "r"] => { println!("Terminating current run..."); break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); } _ => eprintln!( "\x1b[31mInvalid debugger command: '{}'\x1b[39m", input.trim() ), }, Err(err) => panic!("input error: {}", err), } } } fn main() { let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION")); println!("{title}"); println!("{0:=<1$}", "", title.len()); // Initialize scripting engine let mut engine = Engine::new(); #[cfg(not(feature = "no_optimize"))] engine.set_optimization_level(rhai::OptimizationLevel::None); let (ast, script) = load_script(&engine); // Hook up debugger let lines: Vec<_> = script.trim().lines().map(|s| s.to_string()).collect(); #[allow(deprecated)] engine.register_debugger( // Store the current source in the debugger state |engine, mut debugger| { debugger.set_state(engine.const_empty_string()); debugger }, // Main debugging interface move |context, event, node, source, pos| { debug_callback(context, event, node, source, pos, &lines) }, ); // Set a file module resolver without caching #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] { let mut resolver = rhai::module_resolvers::FileModuleResolver::new(); resolver.enable_cache(false); engine.set_module_resolver(resolver); } println!("Type 'help' for commands list."); println!(); // Evaluate while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) { match *err { // Loop back to restart EvalAltResult::ErrorTerminated(..) => { println!("Restarting script..."); } // Break evaluation _ => { print_error(&script, *err); println!(); break; } } } println!("Script terminated. Bye!"); } rhai-1.21.0/src/bin/rhai-repl.rs000064400000000000000000000513511046102023000144250ustar 00000000000000use rhai::plugin::*; use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT}; use rustyline::config::Builder; use rustyline::error::ReadlineError; use rustyline::history::{History, SearchDirection}; use rustyline::{Cmd, DefaultEditor, Event, EventHandler, KeyCode, KeyEvent, Modifiers, Movement}; use std::{env, fs::File, io::Read, path::Path, process::exit}; const HISTORY_FILE: &str = ".rhai-repl-history"; /// Pretty-print error. fn print_error(input: &str, mut err: EvalAltResult) { // Do not use `line` because it "eats" the last empty line if the script ends with a newline. let lines: Vec<_> = input.split('\n').collect(); let pos = err.take_position(); let line_no = if lines.len() > 1 { if pos.is_none() { String::new() } else { format!("{}: ", pos.line().unwrap()) } } else { String::new() }; // Print error position if pos.is_none() { // No position println!("{err}"); } else { // Specific position - print line text println!("{line_no}{}", lines[pos.line().unwrap() - 1]); for (i, err_line) in err.to_string().lines().enumerate() { // Display position marker println!( "{0:>1$}{err_line}", if i > 0 { "| " } else { "^ " }, line_no.len() + pos.position().unwrap() + 1, ); } } } /// Print help text. fn print_help() { println!("help => print this help"); println!("quit, exit => quit"); println!("keys => print list of key bindings"); println!("history => print lines history"); println!("!! => repeat the last history line"); println!("!<#> => repeat a particular history line"); println!("! => repeat the last history line starting with some text"); println!("!? => repeat the last history line containing some text"); println!("scope => print all variables in the scope"); println!("strict => toggle on/off Strict Variables Mode"); #[cfg(not(feature = "no_optimize"))] println!("optimize => toggle on/off script optimization"); #[cfg(feature = "metadata")] println!("functions => print all functions defined"); #[cfg(feature = "metadata")] println!("json => output all functions to `metadata.json`"); println!("ast => print the last AST (optimized)"); #[cfg(not(feature = "no_optimize"))] println!("astu => print the last raw, un-optimized AST"); println!(); println!("press Ctrl-Enter or end a line with `\\`"); println!("to continue to the next line."); println!(); } /// Print key bindings. fn print_keys() { println!("Home => move to beginning of line"); println!("Ctrl-Home => move to beginning of input"); println!("End => move to end of line"); println!("Ctrl-End => move to end of input"); println!("Left => move left"); println!("Ctrl-Left => move left by one word"); println!("Right => move right by one word"); println!("Ctrl-Right => move right"); println!("Up => previous line or history"); println!("Ctrl-Up => previous history"); println!("Down => next line or history"); println!("Ctrl-Down => next history"); println!("Ctrl-R => reverse search history"); println!(" (Ctrl-S forward, Ctrl-G cancel)"); println!("Ctrl-L => clear screen"); #[cfg(target_family = "windows")] println!("Escape => clear all input"); println!("Ctrl-C => exit"); println!("Ctrl-D => EOF (when line empty)"); println!("Ctrl-H, Backspace => backspace"); println!("Ctrl-D, Del => delete character"); println!("Ctrl-U => delete from start"); println!("Ctrl-W => delete previous word"); println!("Ctrl-T => transpose characters"); println!("Ctrl-V => insert special character"); println!("Ctrl-Y => paste yank"); #[cfg(target_family = "unix")] println!("Ctrl-Z => suspend"); #[cfg(target_family = "windows")] println!("Ctrl-Z => undo"); println!("Ctrl-_ => undo"); println!("Enter => run code"); println!("Shift-Ctrl-Enter => continue to next line"); println!(); println!("Plus all standard Emacs key bindings"); println!(); } // Load script files specified in the command line. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] fn load_script_files(engine: &mut Engine) { // Load init scripts let mut contents = String::new(); let mut has_init_scripts = false; for filename in env::args().skip(1) { let filename = match Path::new(&filename).canonicalize() { Err(err) => { eprintln!("Error script file path: {filename}\n{err}"); exit(1); } Ok(f) => { match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) { Ok(f) => f.into(), _ => f, } } }; contents.clear(); let mut f = match File::open(&filename) { Err(err) => { eprintln!( "Error reading script file: {}\n{}", filename.to_string_lossy(), err ); exit(1); } Ok(f) => f, }; if let Err(err) = f.read_to_string(&mut contents) { println!( "Error reading script file: {}\n{}", filename.to_string_lossy(), err ); exit(1); } let module = match engine .compile(&contents) .map_err(|err| err.into()) .and_then(|mut ast| { ast.set_source(filename.to_string_lossy().to_string()); Module::eval_ast_as_new(Scope::new(), &ast, engine) }) { Err(err) => { let filename = filename.to_string_lossy(); eprintln!("{:=<1$}", "", filename.len()); eprintln!("{filename}"); eprintln!("{:=<1$}", "", filename.len()); eprintln!(); print_error(&contents, *err); exit(1); } Ok(m) => m, }; engine.register_global_module(module.into()); has_init_scripts = true; println!("Script '{}' loaded.", filename.to_string_lossy()); } if has_init_scripts { println!(); } } // Setup the Rustyline editor. fn setup_editor() -> DefaultEditor { //env_logger::init(); let config = Builder::new() .tab_stop(4) .indent_size(4) .bracketed_paste(true) .build(); let mut rl = DefaultEditor::with_config(config).unwrap(); // Bind more keys // On Windows, Esc clears the input buffer #[cfg(target_family = "windows")] rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Esc, Modifiers::empty())]), EventHandler::Simple(Cmd::Kill(Movement::WholeBuffer)), ); // On Windows, Ctrl-Z is undo #[cfg(target_family = "windows")] rl.bind_sequence( Event::KeySeq(vec![KeyEvent::ctrl('z')]), EventHandler::Simple(Cmd::Undo(1)), ); // Map Ctrl-Enter to insert a new line - bypass need for `\` continuation rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Char('J'), Modifiers::CTRL)]), EventHandler::Simple(Cmd::Newline), ); rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Enter, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Newline), ); // Map Ctrl-Home and Ctrl-End for beginning/end of input rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Home, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Move(Movement::BeginningOfBuffer)), ); rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::End, Modifiers::CTRL)]), EventHandler::Simple(Cmd::Move(Movement::EndOfBuffer)), ); // Map Ctrl-Up and Ctrl-Down to skip up/down the history, even through multi-line histories rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Down, Modifiers::CTRL)]), EventHandler::Simple(Cmd::NextHistory), ); rl.bind_sequence( Event::KeySeq(vec![KeyEvent(KeyCode::Up, Modifiers::CTRL)]), EventHandler::Simple(Cmd::PreviousHistory), ); // Load the history file if rl.load_history(HISTORY_FILE).is_err() { eprintln!("! No previous lines history!"); } rl } /// Module containing sample functions. #[export_module] mod sample_functions { /// My super-string type. pub type MyType = String; /// This is a sample function. /// /// It takes two numbers and prints them to a string. /// /// # Example /// /// ```rhai /// let result = test(42, 123); /// /// print(result); // prints "42 123" /// ``` pub fn test(x: INT, y: INT) -> MyType { format!("{x} {y}") } /// This is a sample method for integers. /// /// # Example /// /// ```rhai /// let x = 42; /// /// x.test(123, "hello"); /// /// print(x); // prints 170 /// ``` #[rhai_fn(name = "test")] pub fn test2(x: &mut INT, y: INT, z: &str) { *x += y + (z.len() as INT); println!("{x} {y} {z}"); } } fn main() { let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); println!("{title}"); println!("{0:=<1$}", "", title.len()); #[cfg(not(feature = "no_optimize"))] let mut optimize_level = rhai::OptimizationLevel::Full; // Initialize scripting engine let mut engine = Engine::new(); #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] load_script_files(&mut engine); // Setup Engine #[cfg(not(feature = "no_optimize"))] engine.set_optimization_level(rhai::OptimizationLevel::None); // Set a file module resolver without caching #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] { let mut resolver = rhai::module_resolvers::FileModuleResolver::new(); resolver.enable_cache(false); engine.set_module_resolver(resolver); } // Register sample functions engine.register_global_module(exported_module!(sample_functions).into()); // Create scope let mut scope = Scope::new(); // REPL line editor setup let mut rl = setup_editor(); // REPL loop let mut input = String::new(); let mut replacement = None; let mut replacement_index = 0; let mut history_offset = 1; let mut main_ast = AST::empty(); #[cfg(not(feature = "no_optimize"))] let mut ast_u = AST::empty(); let mut ast = AST::empty(); print_help(); 'main_loop: loop { if let Some(replace) = replacement.take() { input = replace; if rl .add_history_entry(input.clone()) .expect("Failed to add history entry") { history_offset += 1; } if input.contains('\n') { println!("[{replacement_index}] ~~~~"); println!("{input}"); println!("~~~~"); } else { println!("[{replacement_index}] {input}"); } replacement_index = 0; } else { input.clear(); loop { let prompt = if input.is_empty() { "repl> " } else { " > " }; match rl.readline(prompt) { // Line continuation Ok(mut line) if line.ends_with('\\') => { line.pop(); input += &line; input += "\n"; } Ok(line) => { input += &line; let cmd = input.trim(); if !cmd.is_empty() && !cmd.starts_with('!') && cmd.trim() != "history" && rl .add_history_entry(input.clone()) .expect("Failed to add history entry") { history_offset += 1; } break; } Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop, Err(err) => { eprintln!("Error: {err:?}"); break 'main_loop; } } } } let cmd = input.trim(); if cmd.is_empty() { continue; } // Implement standard commands match cmd { "help" => { print_help(); continue; } "keys" => { print_keys(); continue; } "exit" | "quit" => break, // quit "history" => { for (i, h) in rl.history().iter().enumerate() { match &h.lines().collect::>()[..] { [line] => println!("[{}] {line}", history_offset + i), lines => { for (x, line) in lines.iter().enumerate() { let number = format!("[{}]", history_offset + i); if x == 0 { println!("{number} {}", line.trim_end()); } else { println!("{0:>1$} {2}", "", number.len(), line.trim_end()); } } } } } continue; } "strict" if engine.strict_variables() => { engine.set_strict_variables(false); println!("Strict Variables Mode turned OFF."); continue; } "strict" => { engine.set_strict_variables(true); println!("Strict Variables Mode turned ON."); continue; } #[cfg(not(feature = "no_optimize"))] "optimize" => { match optimize_level { rhai::OptimizationLevel::Full => { optimize_level = rhai::OptimizationLevel::None; println!("Script optimization turned OFF."); } rhai::OptimizationLevel::Simple => { optimize_level = rhai::OptimizationLevel::Full; println!("Script optimization turned to FULL."); } _ => { optimize_level = rhai::OptimizationLevel::Simple; println!("Script optimization turned to SIMPLE."); } } continue; } "scope" => { println!("{scope}"); continue; } #[cfg(not(feature = "no_optimize"))] "astu" => { // print the last un-optimized AST println!("{ast_u:#?}\n"); continue; } "ast" => { // print the last AST println!("{ast:#?}\n"); continue; } #[cfg(feature = "metadata")] "functions" => { // print a list of all registered functions for f in engine.gen_fn_signatures(false) { println!("{f}") } #[cfg(not(feature = "no_function"))] for f in main_ast.iter_functions() { println!("{f}") } println!(); continue; } #[cfg(feature = "metadata")] "json" => { use std::io::Write; let json = engine .gen_fn_metadata_with_ast_to_json(&main_ast, false) .expect("Unable to generate JSON"); let mut f = std::fs::File::create("metadata.json") .expect("Unable to create `metadata.json`"); f.write_all(json.as_bytes()).expect("Unable to write data"); println!("Functions metadata written to `metadata.json`."); continue; } "!!" => { match rl.history().iter().last() { Some(line) => { replacement = Some(line.clone()); replacement_index = history_offset + rl.history().len() - 1; } None => eprintln!("No lines history!"), } continue; } _ if cmd.starts_with("!?") => { let text = cmd[2..].trim(); let history = rl .history() .iter() .rev() .enumerate() .find(|(.., h)| h.contains(text)); match history { Some((n, line)) => { replacement = Some(line.clone()); replacement_index = history_offset + (rl.history().len() - 1 - n); } None => eprintln!("History line not found: {text}"), } continue; } _ if cmd.starts_with('!') => { if let Ok(num) = cmd[1..].parse::() { if num >= history_offset { if let Some(line) = rl .history() .get(num - history_offset, SearchDirection::Forward) .expect("Failed to get history entry") { replacement = Some(line.entry.into()); replacement_index = num; continue; } } } else { let prefix = cmd[1..].trim(); if let Some((n, line)) = rl .history() .iter() .rev() .enumerate() .find(|(.., h)| h.trim_start().starts_with(prefix)) { replacement = Some(line.clone()); replacement_index = history_offset + (rl.history().len() - 1 - n); continue; } } eprintln!("History line not found: {}", &cmd[1..]); continue; } _ => (), } match engine .compile_with_scope(&scope, &input) .map_err(Into::into) .and_then(|r| { #[cfg(not(feature = "no_optimize"))] { ast_u = r.clone(); ast = engine.optimize_ast(&scope, r, optimize_level); } #[cfg(feature = "no_optimize")] { ast = r; } // Merge the AST into the main main_ast += ast.clone(); // Evaluate engine.eval_ast_with_scope::(&mut scope, &main_ast) }) { Ok(result) if !result.is_unit() => { println!("=> {result:?}"); println!(); } Ok(_) => (), Err(err) => { println!(); print_error(&input, *err); println!(); } } // Throw away all the statements, leaving only the functions main_ast.clear_statements(); } rl.save_history(HISTORY_FILE) .expect("Failed to save history"); println!("Bye!"); } rhai-1.21.0/src/bin/rhai-run.rs000064400000000000000000000063061046102023000142670ustar 00000000000000use rhai::{Engine, EvalAltResult, Position}; use std::{env, fs::File, io::Read, path::Path, process::exit}; fn eprint_error(input: &str, mut err: EvalAltResult) { fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { let line = pos.line().unwrap(); let line_no = format!("{line}: "); eprintln!("{line_no}{}", lines[line - 1]); for (i, err_line) in err_msg.to_string().lines().enumerate() { // Display position marker println!( "{0:>1$}{err_line}", if i > 0 { "| " } else { "^ " }, line_no.len() + pos.position().unwrap() + 1, ); } eprintln!(); } // Do not use `line` because it "eats" the last empty line if the script ends with a newline. let lines: Vec<_> = input.split('\n').collect(); // Print error let pos = err.take_position(); if pos.is_none() { // No position eprintln!("{err}"); } else { // Specific position eprint_line(&lines, pos, &err.to_string()) } } fn main() { let mut contents = String::new(); for filename in env::args().skip(1) { let filename = match Path::new(&filename).canonicalize() { Err(err) => { eprintln!("Error script file path: {filename}\n{err}"); exit(1); } Ok(f) => match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) { Ok(f) => f.into(), _ => f, }, }; // Initialize scripting engine #[allow(unused_mut)] let mut engine = Engine::new(); #[cfg(not(feature = "no_optimize"))] engine.set_optimization_level(rhai::OptimizationLevel::Simple); let mut f = match File::open(&filename) { Err(err) => { eprintln!( "Error reading script file: {}\n{}", filename.to_string_lossy(), err ); exit(1); } Ok(f) => f, }; contents.clear(); if let Err(err) = f.read_to_string(&mut contents) { eprintln!( "Error reading script file: {}\n{}", filename.to_string_lossy(), err ); exit(1); } let contents = if contents.starts_with("#!") { // Skip shebang &contents[contents.find('\n').unwrap_or(0)..] } else { &contents[..] }; if let Err(err) = engine .compile(contents) .map_err(|err| err.into()) .and_then(|mut ast| { ast.set_source(filename.to_string_lossy().to_string()); engine.run_ast(&ast) }) { let filename = filename.to_string_lossy(); eprintln!("{:=<1$}", "", filename.len()); eprintln!("{filename}"); eprintln!("{:=<1$}", "", filename.len()); eprintln!(); eprint_error(contents, *err); } } } rhai-1.21.0/src/config/hashing.rs000064400000000000000000000045241046102023000146600ustar 00000000000000//! Fixed hashing seeds for stable hashing. //! //! Set to [`None`] to disable stable hashing. //! //! See [`rhai::config::hashing::set_hashing_seed`][set_hashing_seed]. //! //! # Example //! //! ```rust //! // Set the hashing seed to [1, 2, 3, 4] //! rhai::config::hashing::set_hashing_seed(Some([1, 2, 3, 4])).unwrap(); //! ``` //! Alternatively, set this at compile time via the `RHAI_HASHING_SEED` environment variable. //! //! # Example //! //! ```sh //! env RHAI_HASHING_SEED ="[236,800,954,213]" //! ``` use super::hashing_env; use crate::OnceCell; static HASHING_SEED: OnceCell> = OnceCell::new(); #[allow(deprecated)] pub use crate::api::deprecated::config::hashing::{get_ahash_seed, set_ahash_seed}; /// Set the hashing seed. This is used to hash functions etc. /// /// This is a static global value and affects every Rhai instance. /// This should not be used _unless_ you know you need it. /// /// Set the hashing seed to all zeros effectively disables stable hashing. /// /// # Warning /// /// * You can only call this function **ONCE** for the entire duration of program execution. /// * You **MUST** call this before performing **ANY** Rhai operation (e.g. creating an [`Engine`][crate::Engine]). /// /// # Error /// /// Returns an error containing the existing hashing seed if already set. /// /// # Example /// /// ```rust /// # use rhai::Engine; /// // Set the hashing seed to [1, 2, 3, 4] /// rhai::config::hashing::set_hashing_seed(Some([1, 2, 3, 4])).unwrap(); /// /// // Use Rhai AFTER setting the hashing seed /// let engine = Engine::new(); /// ``` #[inline(always)] pub fn set_hashing_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { #[cfg(feature = "std")] return HASHING_SEED.set(new_seed); #[cfg(not(feature = "std"))] return HASHING_SEED.set(new_seed.into()).map_err(|err| *err); } /// Get the current hashing Seed. /// /// If the seed is not yet defined, the `RHAI_HASHING_SEED` environment variable (if any) is used. /// /// Otherwise, the hashing seed is randomized to protect against DOS attacks. /// /// See [`rhai::config::hashing::set_hashing_seed`][set_hashing_seed] for more. #[inline] #[must_use] pub fn get_hashing_seed() -> &'static Option<[u64; 4]> { HASHING_SEED.get().unwrap_or(&hashing_env::HASHING_SEED) } rhai-1.21.0/src/config/hashing_env.rs000064400000000000000000000002251046102023000155220ustar 00000000000000//! This file is automatically recreated during build time by `build.rs` from `build.template`. pub const HASHING_SEED: Option<[u64; 4]> = None; rhai-1.21.0/src/config/mod.rs000064400000000000000000000001031046102023000140030ustar 00000000000000//! Configuration for Rhai. pub mod hashing; mod hashing_env; rhai-1.21.0/src/defer.rs000064400000000000000000000067421046102023000130630ustar 00000000000000//! Facility to run state restoration logic at the end of scope. use std::ops::{Deref, DerefMut}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Automatically restore state at the end of the scope. macro_rules! defer { (let $temp:ident = $var:ident . $prop:ident; $code:stmt) => { defer!(let $temp = $var.$prop; $code => move |v| v.$prop = $temp); }; (let $temp:ident = $var:ident . $prop:ident; $code:stmt => $restore:expr) => { let $temp = $var.$prop; $code defer!($var => $restore); }; ($var:ident => $restore:ident; let $temp:ident = $save:expr;) => { defer!($var => $restore; let $temp = $save; {}); }; ($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr;) => { defer!($var if $guard => $restore; let $temp = $save; {}); }; ($var:ident => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => { let $temp = $save; $code defer!($var => move |v| { v.$restore($temp); }); }; ($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => { let $temp = $save; $code defer!($var if $guard => move |v| { v.$restore($temp); }); }; ($var:ident => $restore:expr) => { defer!($var = $var => $restore); }; ($var:ident = $value:expr => $restore:expr) => { let $var = &mut *crate::Deferred::lock($value, $restore); }; ($var:ident if Some($guard:ident) => $restore:expr) => { defer!($var = ($var) if Some($guard) => $restore); }; ($var:ident = ( $value:expr ) if Some($guard:ident) => $restore:expr) => { let mut __rx__; let $var = match $guard { Some($guard) => { __rx__ = crate::Deferred::lock($value, $restore); &mut *__rx__ } _ => &mut *$value }; }; ($var:ident if $guard:expr => $restore:expr) => { defer!($var = ($var) if $guard => $restore); }; ($var:ident = ( $value:expr ) if $guard:expr => $restore:expr) => { let mut __rx__; let $var = if $guard { __rx__ = crate::Deferred::lock($value, $restore); &mut *__rx__ } else { &mut *$value }; }; } /// Run custom restoration logic upon the end of scope. #[must_use] pub struct Deferred<'a, T: ?Sized, R: FnOnce(&mut T)> { lock: &'a mut T, defer: Option, } impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deferred<'a, T, R> { /// Create a new [`Deferred`] that locks a mutable reference and runs restoration logic at /// the end of scope. /// /// Beware that the end of scope means the end of its lifetime, not necessarily waiting until /// the current block scope is exited. #[inline(always)] pub fn lock(value: &'a mut T, restore: R) -> Self { Self { lock: value, defer: Some(restore), } } } impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for Deferred<'a, T, R> { #[inline(always)] fn drop(&mut self) { self.defer.take().unwrap()(self.lock); } } impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deref for Deferred<'a, T, R> { type Target = T; #[inline(always)] fn deref(&self) -> &Self::Target { self.lock } } impl<'a, T: ?Sized, R: FnOnce(&mut T)> DerefMut for Deferred<'a, T, R> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { self.lock } } rhai-1.21.0/src/engine.rs000064400000000000000000000334711046102023000132420ustar 00000000000000//! Main module defining the script evaluation [`Engine`]. use crate::api::default_limits::MAX_STRINGS_INTERNED; use crate::api::options::LangOptions; use crate::func::native::{ locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, }; use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; use crate::types::StringsInterner; use crate::{Dynamic, Identifier, ImmutableString, Locked, SharedModule}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{collections::BTreeSet, fmt, num::NonZeroU8}; pub type Precedence = NonZeroU8; pub const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_DEBUG: &str = "debug"; pub const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; #[cfg(not(feature = "no_closure"))] pub const KEYWORD_IS_SHARED: &str = "is_shared"; pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var"; #[cfg(not(feature = "no_function"))] pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn"; #[cfg(not(feature = "no_function"))] pub const KEYWORD_THIS: &str = "this"; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] pub const KEYWORD_GLOBAL: &str = "global"; #[cfg(not(feature = "no_object"))] pub const FN_GET: &str = "get$"; #[cfg(not(feature = "no_object"))] pub const FN_SET: &str = "set$"; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub const FN_IDX_GET: &str = "index$get$"; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub const FN_IDX_SET: &str = "index$set$"; #[cfg(not(feature = "no_function"))] pub const FN_ANONYMOUS: &str = "anon$"; /// Standard equality comparison operator. /// /// Some standard functions (e.g. searching an [`Array`][crate::Array]) implicitly call this /// function to compare two [`Dynamic`] values. pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax(); /// Standard containment testing function. /// /// The `in` operator is implemented as a call to this function. pub const OP_CONTAINS: &str = "contains"; /// Standard not operator. pub const OP_NOT: &str = Token::Bang.literal_syntax(); /// Separator for namespaces. #[cfg(not(feature = "no_module"))] pub const NAMESPACE_SEPARATOR: &str = Token::DoubleColon.literal_syntax(); /// Rhai main scripting engine. /// /// # Thread Safety /// /// [`Engine`] is re-entrant. /// /// Currently, [`Engine`] is neither [`Send`] nor [`Sync`]. /// Use the `sync` feature to make it [`Send`] `+` [`Sync`]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::Engine; /// /// let engine = Engine::new(); /// /// let result = engine.eval::("40 + 2")?; /// /// println!("Answer: {result}"); // prints 42 /// # Ok(()) /// # } /// ``` pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. pub(crate) global_modules: Vec, /// A collection of all sub-modules directly loaded into the Engine. #[cfg(not(feature = "no_module"))] pub(crate) global_sub_modules: std::collections::BTreeMap, /// A module resolution service. #[cfg(not(feature = "no_module"))] pub(crate) module_resolver: Option>, /// Strings interner. pub(crate) interned_strings: Option>, /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, /// A map containing custom keywords and precedence to recognize. #[cfg(not(feature = "no_custom_syntax"))] pub(crate) custom_keywords: std::collections::BTreeMap>, /// Custom syntax. #[cfg(not(feature = "no_custom_syntax"))] pub(crate) custom_syntax: std::collections::BTreeMap>, /// Callback closure for filtering variable definition. pub(crate) def_var_filter: Option>, /// Callback closure for resolving variable access. pub(crate) resolve_var: Option>, /// Callback closure to remap tokens during parsing. pub(crate) token_mapper: Option>, /// Callback closure when a [`Array`][crate::Array] property accessed does not exist. #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] pub(crate) invalid_array_index: Option>, /// Callback closure when a [`Map`][crate::Map] property accessed does not exist. #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] pub(crate) missing_map_property: Option>, /// Callback closure for implementing the `print` command. pub(crate) print: Option>, /// Callback closure for implementing the `debug` command. pub(crate) debug: Option>, /// Callback closure for progress reporting. #[cfg(not(feature = "unchecked"))] pub(crate) progress: Option>, /// Language options. pub(crate) options: LangOptions, /// Default value for the custom state. pub(crate) def_tag: Dynamic, /// Script optimization level. #[cfg(not(feature = "no_optimize"))] pub(crate) optimization_level: crate::OptimizationLevel, /// Max limits. #[cfg(not(feature = "unchecked"))] pub(crate) limits: crate::api::limits::Limits, /// Callback closure for debugging. #[cfg(feature = "debugging")] pub(crate) debugger_interface: Option<( Box, Box, )>, } impl fmt::Debug for Engine { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("Engine"); f.field("global_modules", &self.global_modules); #[cfg(not(feature = "no_module"))] f.field("global_sub_modules", &self.global_sub_modules); f.field("disabled_symbols", &self.disabled_symbols); #[cfg(not(feature = "no_custom_syntax"))] f.field("custom_keywords", &self.custom_keywords).field( "custom_syntax", &self .custom_syntax .keys() .map(crate::SmartString::as_str) .collect::(), ); f.field("def_var_filter", &self.def_var_filter.is_some()) .field("resolve_var", &self.resolve_var.is_some()) .field("token_mapper", &self.token_mapper.is_some()); #[cfg(not(feature = "unchecked"))] f.field("progress", &self.progress.is_some()); f.field("options", &self.options) .field("default_tag", &self.def_tag); #[cfg(not(feature = "no_optimize"))] f.field("optimization_level", &self.optimization_level); #[cfg(not(feature = "unchecked"))] f.field("limits", &self.limits); #[cfg(feature = "debugging")] f.field("debugger_interface", &self.debugger_interface.is_some()); f.finish() } } impl Default for Engine { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } /// Make getter function #[cfg(not(feature = "no_object"))] #[inline(always)] #[must_use] pub fn make_getter(id: &str) -> Identifier { let mut buf = Identifier::new_const(); buf.push_str(FN_GET); buf.push_str(id); buf } /// Make setter function #[cfg(not(feature = "no_object"))] #[inline(always)] #[must_use] pub fn make_setter(id: &str) -> Identifier { let mut buf = Identifier::new_const(); buf.push_str(FN_SET); buf.push_str(id); buf } impl Engine { /// An empty raw [`Engine`]. pub const RAW: Self = Self { global_modules: Vec::new(), #[cfg(not(feature = "no_module"))] global_sub_modules: std::collections::BTreeMap::new(), #[cfg(not(feature = "no_module"))] module_resolver: None, interned_strings: None, disabled_symbols: BTreeSet::new(), #[cfg(not(feature = "no_custom_syntax"))] custom_keywords: std::collections::BTreeMap::new(), #[cfg(not(feature = "no_custom_syntax"))] custom_syntax: std::collections::BTreeMap::new(), def_var_filter: None, resolve_var: None, token_mapper: None, #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] invalid_array_index: None, #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] missing_map_property: None, print: None, debug: None, #[cfg(not(feature = "unchecked"))] progress: None, options: LangOptions::new(), def_tag: Dynamic::UNIT, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel::Simple, #[cfg(not(feature = "unchecked"))] limits: crate::api::limits::Limits::new(), #[cfg(feature = "debugging")] debugger_interface: None, }; /// Create a new [`Engine`]. #[inline] #[must_use] pub fn new() -> Self { // Create the new scripting Engine let mut engine = Self::new_raw(); #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] { engine.module_resolver = Some(Box::new(crate::module::resolvers::FileModuleResolver::new())); } // Turn on the strings interner engine.set_max_strings_interned(MAX_STRINGS_INTERNED); // default print/debug implementations #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] { engine.print = Some(Box::new(|s| println!("{s}"))); engine.debug = Some(Box::new(|s, source, pos| match (source, pos) { (Some(source), crate::Position::NONE) => println!("{source} | {s}"), #[cfg(not(feature = "no_position"))] (Some(source), pos) => println!("{source} @ {pos:?} | {s}"), (None, crate::Position::NONE) => println!("{s}"), #[cfg(not(feature = "no_position"))] (None, pos) => println!("{pos:?} | {s}"), })); } // Register the standard package engine.register_global_module(StandardPackage::new().as_shared_module()); engine } /// Create a new [`Engine`] with minimal built-in functions. /// It returns a copy of [`Engine::RAW`]. /// /// This is useful for creating a custom scripting engine with only the functions you need. /// /// Use [`register_global_module`][Engine::register_global_module] to add packages of functions. #[inline] #[must_use] pub const fn new_raw() -> Self { Self::RAW } /// Get an interned [string][ImmutableString]. /// /// [`Engine`] keeps a cache of [`ImmutableString`] instances and tries to avoid new allocations /// and save memory when an existing instance is found. /// /// It is usually a good idea to intern strings if they are used frequently. #[inline] #[must_use] pub fn get_interned_string( &self, string: impl AsRef + Into, ) -> ImmutableString { match self.interned_strings { Some(ref interner) => match locked_write(interner) { Some(mut cache) => cache.get(string), None => string.into(), }, None => string.into(), } } /// Get an interned property getter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] #[inline] #[must_use] pub(crate) fn get_interned_getter( &self, text: impl AsRef + Into, ) -> ImmutableString { match self.interned_strings { Some(ref interner) => match locked_write(interner) { Some(mut cache) => { cache.get_with_mapper(b'g', |s| make_getter(s.as_ref()).into(), text) } None => make_getter(text.as_ref()).into(), }, None => make_getter(text.as_ref()).into(), } } /// Get an interned property setter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] #[inline] #[must_use] pub(crate) fn get_interned_setter( &self, text: impl AsRef + Into, ) -> ImmutableString { match self.interned_strings { Some(ref interner) => match locked_write(interner) { Some(mut cache) => { cache.get_with_mapper(b's', |s| make_setter(s.as_ref()).into(), text) } None => make_setter(text.as_ref()).into(), }, None => make_setter(text.as_ref()).into(), } } /// Get an empty [`ImmutableString`] which refers to a shared instance. #[inline(always)] #[must_use] pub fn const_empty_string(&self) -> ImmutableString { self.get_interned_string("") } /// Is there a debugger interface registered with this [`Engine`]? #[cfg(feature = "debugging")] #[inline(always)] #[must_use] pub(crate) const fn is_debugger_registered(&self) -> bool { self.debugger_interface.is_some() } } rhai-1.21.0/src/eval/cache.rs000064400000000000000000000057141046102023000137660ustar 00000000000000//! System caches. use crate::func::{RhaiFunc, StraightHashMap}; use crate::types::BloomFilterU64; use crate::{ImmutableString, StaticVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// _(internals)_ An entry in a function resolution cache. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] pub struct FnResolutionCacheEntry { /// Function. pub func: RhaiFunc, /// Optional source. pub source: Option, } /// _(internals)_ A function resolution cache with a bloom filter. /// Exported under the `internals` feature only. /// /// The [bloom filter][`BloomFilterU64`] is used to rapidly check whether a function hash has never been encountered. /// It enables caching a hash only during the second encounter to avoid "one-hit wonders". #[derive(Debug, Clone, Default)] pub struct FnResolutionCache { /// Hash map containing cached functions. pub dict: StraightHashMap>, /// Bloom filter to avoid caching "one-hit wonders". pub bloom_filter: BloomFilterU64, } impl FnResolutionCache { /// Clear the [`FnResolutionCache`]. #[inline(always)] #[allow(dead_code)] pub fn clear(&mut self) { self.dict.clear(); self.bloom_filter.clear(); } } /// _(internals)_ A type containing system-wide caches. /// Exported under the `internals` feature only. /// /// The following caches are contained inside this type: /// * A stack of [function resolution caches][FnResolutionCache] #[derive(Debug, Clone)] pub struct Caches { fn_resolution: StaticVec, } impl Caches { /// Create an empty [`Caches`]. #[inline(always)] #[must_use] pub const fn new() -> Self { Self { fn_resolution: StaticVec::new_const(), } } /// Get the number of function resolution cache(s) in the stack. #[inline(always)] #[must_use] pub fn fn_resolution_caches_len(&self) -> usize { self.fn_resolution.len() } /// Get a mutable reference to the current function resolution cache. /// /// A new function resolution cache is pushed onto the stack if the stack is empty. #[inline] #[must_use] pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { // Push a new function resolution cache if the stack is empty if self.fn_resolution.is_empty() { self.push_fn_resolution_cache(); } self.fn_resolution.last_mut().unwrap() } /// Push an empty function resolution cache onto the stack and make it current. #[inline(always)] pub fn push_fn_resolution_cache(&mut self) { self.fn_resolution.push(<_>::default()); } /// Rewind the function resolution caches stack to a particular size. #[inline(always)] pub fn rewind_fn_resolution_caches(&mut self, len: usize) { self.fn_resolution.truncate(len); } } rhai-1.21.0/src/eval/chaining.rs000064400000000000000000001601301046102023000144750ustar 00000000000000//! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use super::{Caches, GlobalRuntimeState, Target}; use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment}; use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::types::dynamic::Union; use crate::{ calc_fn_hash, Dynamic, Engine, ExclusiveRange, FnArgsVec, InclusiveRange, OnceCell, Position, RhaiResult, RhaiResultOf, Scope, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{convert::TryInto, hash::Hash}; /// Function call hashes to index getters and setters. static INDEXER_HASHES: OnceCell<(u64, u64)> = OnceCell::new(); /// Get the pre-calculated index getter/setter hashes. #[inline] #[must_use] fn hash_idx() -> (u64, u64) { *INDEXER_HASHES.get_or_init(|| { #[allow(clippy::useless_conversion)] ( calc_fn_hash(None, FN_IDX_GET, 2), calc_fn_hash(None, FN_IDX_SET, 3), ) .into() }) } /// Method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum ChainType { /// Indexing. #[cfg(not(feature = "no_index"))] Indexing, /// Dotting. #[cfg(not(feature = "no_object"))] Dotting, } impl From<&Expr> for ChainType { #[inline(always)] fn from(expr: &Expr) -> Self { match expr { #[cfg(not(feature = "no_index"))] Expr::Index(..) => Self::Indexing, #[cfg(not(feature = "no_object"))] Expr::Dot(..) => Self::Dotting, expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), } } } impl Engine { /// Call a get indexer. #[inline] fn call_indexer_get( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, target: &mut Dynamic, idx: &mut Dynamic, pos: Position, ) -> RhaiResultOf { defer! { let orig_level = global.level; global.level += 1 } let hash = hash_idx().0; let args = &mut [target, idx]; self.exec_native_fn_call( global, caches, FN_IDX_GET, None, hash, args, true, false, pos, ) .map(|(r, ..)| r) } /// Call a set indexer. #[inline] fn call_indexer_set( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, target: &mut Dynamic, idx: &mut Dynamic, new_val: &mut Dynamic, is_ref_mut: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { defer! { let orig_level = global.level; global.level += 1 } let hash = hash_idx().1; let args = &mut [target, idx, new_val]; self.exec_native_fn_call( global, caches, FN_IDX_SET, None, hash, args, is_ref_mut, false, pos, ) } /// Get the value at the indexed position of a base type. /// /// # Panics /// /// Panics if the target object is shared. /// /// Shared objects should be handled (dereferenced) before calling this method. fn get_indexed_mut<'t>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, _scope: &mut Scope, _this_ptr: Option<&mut Dynamic>, target: &'t mut Dynamic, idx: &mut Dynamic, idx_pos: Position, op_pos: Position, _add_if_not_found: bool, use_indexers: bool, ) -> RhaiResultOf> { self.track_operation(global, Position::NONE)?; match target { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(arr, ..)) => { // val_array[idx] let index = idx .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; let len = arr.len(); let arr_idx = match super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, idx_pos).into() }) { Ok(idx) => idx, Err(err) => { #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] if let Some(ref cb) = self.invalid_array_index { let context = super::EvalContext::new(self, global, caches, _scope, _this_ptr); return cb(arr, index, context) .map_err(|err| err.fill_position(idx_pos)); } return Err(err); } }; arr.get_mut(arr_idx).unwrap().try_into() } #[cfg(not(feature = "no_index"))] Dynamic(Union::Blob(arr, ..)) => { // val_blob[idx] let index = idx .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; let len = arr.len(); let arr_idx = super::calc_index(len, index, true, || { ERR::ErrorArrayBounds(len, index, idx_pos).into() })?; let value = arr.get(arr_idx).map(|&v| (v as crate::INT).into()).unwrap(); Ok(Target::BlobByte { source: target, value, index: arr_idx, }) } #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map, ..)) => { // val_map[idx] let index = idx.read_lock::().ok_or_else(|| { self.make_type_mismatch_err::(idx.type_name(), idx_pos) })?; #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] if let Some(ref cb) = self.missing_map_property { if !map.contains_key(index.as_str()) { let context = super::EvalContext::new(self, global, caches, _scope, _this_ptr); return cb(map, index.as_str(), context) .map_err(|err| err.fill_position(idx_pos)); } } if _add_if_not_found && (map.is_empty() || !map.contains_key(index.as_str())) { map.insert(index.clone().into(), Dynamic::UNIT); } if let Some(value) = map.get_mut(index.as_str()) { value.try_into() } else if self.fail_on_invalid_map_property() { Err(ERR::ErrorPropertyNotFound(index.to_string(), idx_pos).into()) } else { Ok(Target::from(Dynamic::UNIT)) } } #[cfg(not(feature = "no_index"))] Dynamic(Union::Int(value, ..)) if idx.is::() || idx.is::() => { // val_int[range] let (shift, mask) = if let Some(range) = idx.read_lock::() { let start = range.start; let end = if range.end == crate::INT::MAX { crate::INT_BITS as crate::INT } else { range.end }; let start = super::calc_index(crate::INT_BITS, start, false, || { ERR::ErrorBitFieldBounds(crate::INT_BITS, start, idx_pos).into() })?; let end = super::calc_index(crate::INT_BITS, end, false, || { if end >= 0 && end < crate::MAX_USIZE_INT && (end as usize) <= crate::INT_BITS { // Handle largest value Ok(end as usize) } else { ERR::ErrorBitFieldBounds(crate::INT_BITS, end, idx_pos).into() } })?; #[allow(clippy::cast_possible_truncation)] if end <= start { (0, 0) } else if end == crate::INT_BITS && start == 0 { // -1 = all bits set (0, -1) } else { ( start as u8, // 2^bits - 1 (((2 as crate::UNSIGNED_INT).pow((end - start) as u32) - 1) as crate::INT) << start, ) } } else if let Some(range) = idx.read_lock::() { let start = *range.start(); let end = if *range.end() == crate::INT::MAX { (crate::INT_BITS - 1) as crate::INT } else { *range.end() }; let start = super::calc_index(crate::INT_BITS, start, false, || { ERR::ErrorBitFieldBounds(crate::INT_BITS, start, idx_pos).into() })?; let end = super::calc_index(crate::INT_BITS, end, false, || { ERR::ErrorBitFieldBounds(crate::INT_BITS, end, idx_pos).into() })?; #[allow(clippy::cast_possible_truncation)] if end < start { (0, 0) } else if end == crate::INT_BITS - 1 && start == 0 { // -1 = all bits set (0, -1) } else { ( start as u8, // 2^bits - 1 (((2 as crate::UNSIGNED_INT).pow((end - start + 1) as u32) - 1) as crate::INT) << start, ) } } else { unreachable!("Range or RangeInclusive expected but gets {:?}", idx); }; let field_value = (*value & mask) >> shift; Ok(Target::BitField { source: target, value: field_value.into(), mask, shift, }) } #[cfg(not(feature = "no_index"))] Dynamic(Union::Int(value, ..)) => { // val_int[idx] let index = idx .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; let bit = super::calc_index(crate::INT_BITS, index, true, || { ERR::ErrorBitFieldBounds(crate::INT_BITS, index, idx_pos).into() })?; let bit_value = (*value & (1 << bit)) != 0; #[allow(clippy::cast_possible_truncation)] let bit = bit as u8; Ok(Target::Bit { source: target, value: bit_value.into(), bit, }) } #[cfg(not(feature = "no_index"))] Dynamic(Union::Str(s, ..)) => { // Numeric index - character match idx.as_int() { Ok(index) => { let (ch, offset) = if index >= 0 { #[allow(clippy::absurd_extreme_comparisons)] if index >= crate::MAX_USIZE_INT { return Err(ERR::ErrorStringBounds( s.chars().count(), index, idx_pos, ) .into()); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let offset = index as usize; ( s.chars().nth(offset).ok_or_else(|| { ERR::ErrorStringBounds(s.chars().count(), index, idx_pos) })?, offset, ) } else { let abs_index = index.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_index as u64 > usize::MAX as u64 { return Err(ERR::ErrorStringBounds( s.chars().count(), index, idx_pos, ) .into()); } #[allow(clippy::cast_possible_truncation)] let offset = abs_index as usize; ( // Count from end if negative s.chars().rev().nth(offset - 1).ok_or_else(|| { ERR::ErrorStringBounds(s.chars().count(), index, idx_pos) })?, offset, ) }; Ok(Target::StringChar { source: target, value: ch.into(), index: offset, }) } // Range index on empty string - empty slice Err(typ) if (typ == std::any::type_name::() || typ == std::any::type_name::()) && s.is_empty() => { let value = s.clone().into(); Ok(Target::StringSlice { source: target, value, start: 0, end: 0, exclusive: true, }) } // Range index - slice Err(typ) if typ == std::any::type_name::() => { // val_str[range] let range = idx.read_lock::().unwrap().clone(); let chars_count = s.chars().count(); let start = if range.start >= 0 { range.start as usize } else { super::calc_index(chars_count, range.start, true, || { ERR::ErrorStringBounds(chars_count, range.start, idx_pos).into() })? }; let end = if range.end >= 0 { range.end as usize } else { super::calc_index(chars_count, range.end, true, || { ERR::ErrorStringBounds(chars_count, range.end, idx_pos).into() }) .unwrap_or(0) }; let value = if start == 0 && end >= chars_count { s.clone().into() } else { let take = if end > start { end - start } else { 0 }; s.chars().skip(start).take(take).collect::().into() }; Ok(Target::StringSlice { source: target, value, start, end, exclusive: true, }) } Err(typ) if typ == std::any::type_name::() => { // val_str[range] let range = idx.read_lock::().unwrap().clone(); let chars_count = s.chars().count(); let start = if *range.start() >= 0 { *range.start() as usize } else { super::calc_index(chars_count, *range.start(), true, || { ERR::ErrorStringBounds(chars_count, *range.start(), idx_pos).into() })? }; let end = if *range.end() >= 0 { *range.end() as usize } else { super::calc_index(chars_count, *range.end(), true, || { ERR::ErrorStringBounds(chars_count, *range.end(), idx_pos).into() }) .unwrap_or(0) }; let value = if start == 0 && end >= chars_count - 1 { s.clone().into() } else { let take = if end > start { end - start + 1 } else { 0 }; s.chars().skip(start).take(take).collect::().into() }; Ok(Target::StringSlice { source: target, value, start, end, exclusive: false, }) } // Unsupported index type Err(typ) => Err(self.make_type_mismatch_err::(typ, idx_pos)), } } #[cfg(not(feature = "no_closure"))] Dynamic(Union::Shared(..)) => { unreachable!("`get_indexed_mut` cannot handle shared values") } _ if use_indexers => self .call_indexer_get(global, caches, target, idx, op_pos) .map(Into::into), _ => Err(ERR::ErrorIndexingType( format!( "{} [{}]", self.map_type_name(target.type_name()), self.map_type_name(idx.type_name()) ), op_pos, ) .into()), } } /// Evaluate a dot/index chain. pub(crate) fn eval_dot_index_chain( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, expr: &Expr, new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let BinaryExpr { lhs, rhs } = match expr { #[cfg(not(feature = "no_index"))] Expr::Index(x, ..) => &**x, #[cfg(not(feature = "no_object"))] Expr::Dot(x, ..) => &**x, expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), }; let idx_values = &mut FnArgsVec::new_const(); match (rhs, ChainType::from(expr)) { // Short-circuit for simple property access: {expr}.prop #[cfg(not(feature = "no_object"))] (Expr::Property(..), ChainType::Dotting) => (), #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] (Expr::Property(..), ChainType::Indexing) => { unreachable!("unexpected Expr::Property for indexing") } // Short-circuit for indexing with literal: {expr}[1] #[cfg(not(feature = "no_index"))] (_, ChainType::Indexing) if rhs.get_literal_value().is_some() => { idx_values.push(rhs.get_literal_value().unwrap()) } // Short-circuit for simple method call: {expr}.func() #[cfg(not(feature = "no_object"))] (Expr::MethodCall(x, ..), ChainType::Dotting) if x.args.is_empty() => (), // All other patterns - evaluate the arguments chain _ => { let this_ptr = this_ptr.as_deref_mut(); self.eval_dot_index_chain_arguments( global, caches, scope, this_ptr, expr, rhs, idx_values, )? } } match (lhs, new_val) { // this.??? or this[???] (Expr::ThisPtr(var_pos), new_val) => { self.track_operation(global, *var_pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), lhs)?; this_ptr.map_or_else( || Err(ERR::ErrorUnboundThis(*var_pos).into()), |this_ptr| { let target = &mut this_ptr.try_into()?; let scope = Some(scope); self.eval_dot_index_chain_raw( global, caches, scope, None, lhs, expr, target, rhs, idx_values, new_val, ) }, ) } // id.??? or id[???] (Expr::Variable(.., var_pos), new_val) => { self.track_operation(global, *var_pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), lhs)?; let target = &mut self.search_namespace(global, caches, scope, this_ptr, lhs)?; self.eval_dot_index_chain_raw( global, caches, None, None, lhs, expr, target, rhs, idx_values, new_val, ) } // {expr}.??? = ??? or {expr}[???] = ??? (_, Some(..)) => unreachable!("cannot assign to an expression"), // {expr}.??? or {expr}[???] (lhs_expr, None) => { let value = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs_expr)? .flatten(); let item_ptr = &mut value.into(); let scope = Some(scope); self.eval_dot_index_chain_raw( global, caches, scope, this_ptr, lhs_expr, expr, item_ptr, rhs, idx_values, None, ) } } .map(|(v, ..)| v) } /// Evaluate a chain of indexes and store the results in a [`FnArgsVec`]. fn eval_dot_index_chain_arguments( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, parent: &Expr, expr: &Expr, idx_values: &mut FnArgsVec, ) -> RhaiResultOf<()> { self.track_operation(global, expr.position())?; match (expr, ChainType::from(parent)) { #[cfg(not(feature = "no_object"))] (Expr::MethodCall(x, ..), ChainType::Dotting) => { #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" ); for expr in &*x.args { let arg_value = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; idx_values.push(arg_value.0.flatten()); } } #[cfg(not(feature = "no_object"))] (Expr::Property(..), ChainType::Dotting) => (), (Expr::Index(x, ..) | Expr::Dot(x, ..), chain_type) if !parent.options().intersects(ASTFlags::BREAK) => { let BinaryExpr { lhs, rhs, .. } = &**x; let mut _arg_values = FnArgsVec::new_const(); // Evaluate in left-to-right order match (lhs, chain_type) { #[cfg(not(feature = "no_object"))] (Expr::Property(..), ChainType::Dotting) => (), #[cfg(not(feature = "no_object"))] (Expr::MethodCall(x, ..), ChainType::Dotting) => { #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" ); for expr in &*x.args { let tp = this_ptr.as_deref_mut(); let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?; _arg_values.push(arg_value.0.flatten()); } } #[cfg(not(feature = "no_index"))] (_, ChainType::Indexing) => { _arg_values.push( self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs)? .flatten(), ); } #[allow(unreachable_patterns)] (expr, chain_type) => { unreachable!("unknown {:?} expression: {:?}", chain_type, expr) } } // Push in reverse order self.eval_dot_index_chain_arguments( global, caches, scope, this_ptr, expr, rhs, idx_values, )?; idx_values.extend(_arg_values); } #[cfg(not(feature = "no_index"))] (_, ChainType::Indexing) => idx_values.push( self.eval_expr(global, caches, scope, this_ptr, expr)? .flatten(), ), #[allow(unreachable_patterns)] (expr, chain_type) => unreachable!("unknown {:?} expression: {:?}", chain_type, expr), } Ok(()) } /// Chain-evaluate a dot/index chain. fn eval_dot_index_chain_raw( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: Option<&mut Scope>, mut this_ptr: Option<&mut Dynamic>, root: &Expr, parent: &Expr, target: &mut Target, rhs: &Expr, idx_values: &mut FnArgsVec, new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let mut b; let mut s = scope; macro_rules! x { ($var:ident, $base:ident) => {{ if $var.is_none() { $base = Scope::new(); $var = Some(&mut $base); } $var.as_deref_mut().unwrap() }}; } let is_ref_mut = target.is_ref(); let op_pos = parent.position(); match ChainType::from(parent) { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { // Check for existence with the null conditional operator if parent.options().intersects(ASTFlags::NEGATED) && target.as_ref().is_unit() { return Ok((Dynamic::UNIT, false)); } let pos = rhs.start_position(); match (rhs, new_val) { // xxx[idx].expr... | xxx[idx][expr]... (Expr::Dot(x, ..) | Expr::Index(x, ..), new_val) if !parent.options().intersects(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr.as_deref_mut(), parent)?; let idx_val = &mut idx_values.pop().unwrap(); let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); let (try_setter, result) = { let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let tp = this_ptr.as_deref_mut(); let new_scope = x!(s, b); let mut item = self.get_indexed_mut( global, caches, new_scope, tp, obj, idx_val, idx_pos, op_pos, false, true, )?; let is_item_temp_val = item.is_temp_value(); let item_ptr = &mut item; match self.eval_dot_index_chain_raw( global, caches, s, this_ptr, root, rhs, item_ptr, &x.rhs, idx_values, new_val, ) { Ok((result, true)) if is_item_temp_val => { (Some(item.take_or_clone()), (result, true)) } Ok(result) => (None, result), Err(err) => return Err(err), } }; if let Some(mut new_val) = try_setter { // Try to call index setter if value is changed let target = target.as_mut(); let idx = &mut idx_val_for_setter; let new_val = &mut new_val; // The return value of a indexer setter (usually `()`) is thrown away and not used. let _ = self .call_indexer_set( global, caches, target, idx, new_val, is_ref_mut, op_pos, ) .or_else(|e| match *e { ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), _ => Err(e), })?; } Ok(result) } // xxx[rhs] op= new_val (_, Some((new_val, op_info))) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr.as_deref_mut(), parent)?; let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let new_scope = x!(s, b); let idx_val = &mut idx_values.pop().unwrap(); let idx = &mut idx_val.clone(); let try_setter = match self.get_indexed_mut( global, caches, new_scope, this_ptr, obj, idx, pos, op_pos, true, false, ) { // Indexed value is not a temp value - update directly Ok(ref mut item_ptr) => { self.eval_op_assignment( global, caches, op_info, root, item_ptr, new_val, )?; self.check_data_size(item_ptr.as_ref(), op_info.position())?; None } // Indexed value cannot be referenced - use indexer #[cfg(not(feature = "no_index"))] Err(err) if matches!(*err, ERR::ErrorIndexingType(..)) => Some(new_val), // Any other error Err(err) => return Err(err), }; if let Some(mut new_val) = try_setter { // Is this an op-assignment? if op_info.is_op_assignment() { let idx = &mut idx_val.clone(); // Call the index getter to get the current value if let Ok(val) = self.call_indexer_get(global, caches, obj, idx, op_pos) { let mut val = val.into(); // Run the op-assignment self.eval_op_assignment( global, caches, op_info, root, &mut val, new_val, )?; // Replace new value new_val = val.take_or_clone(); self.check_data_size(&new_val, op_info.position())?; } } // Try to call index setter let new_val = &mut new_val; // The return value of a indexer setter (usually `()`) is thrown away and not used. let _ = self.call_indexer_set( global, caches, obj, idx_val, new_val, is_ref_mut, op_pos, )?; } Ok((Dynamic::UNIT, true)) } // xxx[rhs] (_, None) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr.as_deref_mut(), parent)?; let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let new_scope = x!(s, b); let idx_val = &mut idx_values.pop().unwrap(); self.get_indexed_mut( global, caches, new_scope, this_ptr, obj, idx_val, pos, op_pos, false, true, ) .map(|v| (v.take_or_clone(), false)) } } } #[cfg(not(feature = "no_object"))] ChainType::Dotting => { // Check for existence with the Elvis operator if parent.options().intersects(ASTFlags::NEGATED) && target.as_ref().is_unit() { return Ok((Dynamic::UNIT, false)); } match (rhs, new_val, target.as_ref().is_map()) { // xxx.fn_name(...) = ??? (Expr::MethodCall(..), Some(..), ..) => { unreachable!("method call cannot be assigned to") } // xxx.fn_name(arg_expr_list) (Expr::MethodCall(x, pos), None, ..) => { #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" ); #[cfg(feature = "debugging")] let reset = self.dbg_reset(global, caches, x!(s, b), this_ptr, rhs)?; #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; // Truncate the index values upon exit defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let arg1_pos = args.first().map_or(Position::NONE, Expr::position); self.make_method_call( global, caches, name, *hashes, target, call_args, arg1_pos, *pos, ) } // {xxx:map}.id op= ??? (Expr::Property(x, pos), Some((new_val, op_info)), true) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr.as_deref_mut(), rhs)?; let index = &mut x.2.clone().into(); { let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let new_scope = x!(s, b); let item = &mut self.get_indexed_mut( global, caches, new_scope, this_ptr, obj, index, *pos, op_pos, true, false, )?; self.eval_op_assignment(global, caches, op_info, root, item, new_val)?; } self.check_data_size(target.source(), op_info.position())?; Ok((Dynamic::UNIT, true)) } // {xxx:map}.id (Expr::Property(x, pos), None, true) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr.as_deref_mut(), rhs)?; let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let index = &mut x.2.clone().into(); let new_scope = x!(s, b); let item = self.get_indexed_mut( global, caches, new_scope, this_ptr, obj, index, *pos, op_pos, false, false, )?; Ok((item.take_or_clone(), false)) } // xxx.id op= ??? (Expr::Property(x, pos), Some((mut new_val, op_info)), false) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr, rhs)?; let ((getter, hash_get), (setter, hash_set), name) = &**x; if op_info.is_op_assignment() { let args = &mut [target.as_mut()]; let (mut orig_val, ..) = self .exec_native_fn_call( global, caches, getter, None, *hash_get, args, is_ref_mut, false, *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let target = target.as_mut(); let mut prop = name.into(); self.call_indexer_get( global, caches, target, &mut prop, op_pos, ) .map(|r| (r, false)) .map_err(|e| { match *e { ERR::ErrorIndexingType(..) => err, _ => e, } }) } _ => Err(err), })?; { let orig_val = &mut (&mut orig_val).try_into()?; self.eval_op_assignment( global, caches, op_info, root, orig_val, new_val, )?; } new_val = orig_val; } let args = &mut [target.as_mut(), &mut new_val]; self.exec_native_fn_call( global, caches, setter, None, *hash_set, args, is_ref_mut, false, *pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let target = target.as_mut(); let idx = &mut name.into(); let new_val = &mut new_val; self.call_indexer_set( global, caches, target, idx, new_val, is_ref_mut, op_pos, ) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, _ => e, }) } _ => Err(err), }) } // xxx.id (Expr::Property(x, pos), None, false) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), this_ptr, rhs)?; let ((getter, hash_get), _, name) = &**x; let args = &mut [target.as_mut()]; self.exec_native_fn_call( global, caches, getter, None, *hash_get, args, is_ref_mut, false, *pos, ) .map_or_else( |err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let target = target.as_mut(); let mut prop = name.into(); self.call_indexer_get(global, caches, target, &mut prop, op_pos) .map(|r| (r, false)) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, _ => e, }) } _ => Err(err), }, // Assume getters are always pure |(v, ..)| Ok((v, false)), ) } // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr (Expr::Index(x, ..) | Expr::Dot(x, ..), new_val, true) => { let _node = &x.lhs; let mut _tp = this_ptr.as_deref_mut(); #[cfg(not(feature = "no_closure"))] let mut target_guard; let item = &mut match x.lhs { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), _tp.as_deref_mut(), _node)?; let obj = target.as_mut(); #[cfg(not(feature = "no_closure"))] let obj = if obj.is_shared() { target_guard = obj.write_lock::().unwrap(); &mut *target_guard } else { obj }; let new_scope = x!(s, b); let index = &mut p.2.clone().into(); self.get_indexed_mut( global, caches, new_scope, _tp, obj, index, pos, op_pos, false, true, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) => { #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" ); #[cfg(feature = "debugging")] let reset = self.dbg_reset(global, caches, x!(s, b), _tp, _node)?; #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } let crate::ast::FnCallExpr { name, hashes, args, .. } = &**x; // Truncate the index values upon exit defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let arg1_pos = args.first().map_or(Position::NONE, Expr::position); self.make_method_call( global, caches, name, *hashes, target, call_args, arg1_pos, pos, )? .0 .into() } // Others - syntax error ref expr => unreachable!("invalid dot expression: {:?}", expr), }; self.eval_dot_index_chain_raw( global, caches, s, this_ptr, root, rhs, item, &x.rhs, idx_values, new_val, ) } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr (Expr::Index(x, ..) | Expr::Dot(x, ..), new_val, ..) => { let _node = &x.lhs; let mut _this_ptr = this_ptr; let _tp = _this_ptr.as_deref_mut(); match x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.dbg(global, caches, x!(s, b), _tp, _node)?; let ((getter, hash_get), (setter, hash_set), name) = &**p; let args = &mut [target.as_mut()]; // Assume getters are always pure let (mut val, ..) = self .exec_native_fn_call( global, caches, getter, None, *hash_get, args, is_ref_mut, false, pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let target = target.as_mut(); let mut prop = name.into(); self.call_indexer_get( global, caches, target, &mut prop, op_pos, ) .map(|r| (r, false)) .map_err( |e| match *e { ERR::ErrorIndexingType(..) => err, _ => e, }, ) } _ => Err(err), })?; let val = &mut (&mut val).try_into()?; let (result, may_be_changed) = self.eval_dot_index_chain_raw( global, caches, s, _this_ptr, root, rhs, val, &x.rhs, idx_values, new_val, )?; // Feed the value back via a setter just in case it has been updated if may_be_changed { // Re-use args because the first &mut parameter will not be consumed let args = &mut [target.as_mut(), val.as_mut()]; // The return value is thrown away and not used. let _ = self .exec_native_fn_call( global, caches, setter, None, *hash_set, args, is_ref_mut, false, pos, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let target = target.as_mut(); let idx = &mut name.into(); let new_val = val.as_mut(); self.call_indexer_set( global, caches, target, idx, new_val, is_ref_mut, op_pos, ) .or_else(|e| match *e { // If there is no setter, no need to feed it // back because the property is read-only ERR::ErrorIndexingType(..) => { Ok((Dynamic::UNIT, false)) } _ => Err(e), }) } _ => Err(err), })?; } Ok((result, may_be_changed)) } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::MethodCall(ref f, pos) => { #[cfg(not(feature = "no_module"))] debug_assert!( !f.is_qualified(), "method call in dot chain should not be namespace-qualified" ); let val = { #[cfg(feature = "debugging")] let reset = self.dbg_reset(global, caches, x!(s, b), _tp, _node)?; #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } let crate::ast::FnCallExpr { name, hashes, args, .. } = &**f; // Truncate the index values upon exit defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); } let call_args = &mut idx_values[offset..]; let pos1 = args.first().map_or(Position::NONE, Expr::position); self.make_method_call( global, caches, name, *hashes, target, call_args, pos1, pos, )? .0 }; let val = &mut val.into(); self.eval_dot_index_chain_raw( global, caches, s, _this_ptr, root, rhs, val, &x.rhs, idx_values, new_val, ) } // Others - syntax error ref expr => unreachable!("invalid dot expression: {:?}", expr), } } // Syntax error (expr, ..) => unreachable!("invalid chaining expression: {:?}", expr), } } } } } rhai-1.21.0/src/eval/data_check.rs000064400000000000000000000151501046102023000147640ustar 00000000000000//! Data size checks during evaluation. #![cfg(not(feature = "unchecked"))] use super::GlobalRuntimeState; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR}; use std::borrow::Borrow; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Recursively calculate the sizes of an array. /// /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`. /// /// # Panics /// /// Panics if any interior data is shared (should never happen). #[cfg(not(feature = "no_index"))] #[inline] pub fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) { let (mut ax, mut mx, mut sx) = (0, 0, 0); for value in array { ax += 1; match value.0 { Union::Array(ref a, ..) => { let (a, m, s) = calc_array_sizes(a); ax += a; mx += m; sx += s; } Union::Blob(ref a, ..) => ax += 1 + a.len(), #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => { let (a, m, s) = calc_map_sizes(m); ax += a; mx += m; sx += s; } Union::Str(ref s, ..) => sx += s.len(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { unreachable!("shared values discovered within data") } _ => (), } } (ax, mx, sx) } /// Recursively calculate the sizes of a map. /// /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`. /// /// # Panics /// /// Panics if any interior data is shared (should never happen). #[cfg(not(feature = "no_object"))] #[inline] pub fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) { let (mut ax, mut mx, mut sx) = (0, 0, 0); for value in map.values() { mx += 1; match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => { let (a, m, s) = calc_array_sizes(a); ax += a; mx += m; sx += s; } #[cfg(not(feature = "no_index"))] Union::Blob(ref a, ..) => ax += 1 + a.len(), Union::Map(ref m, ..) => { let (a, m, s) = calc_map_sizes(m); ax += a; mx += m; sx += s; } Union::Str(ref s, ..) => sx += s.len(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { unreachable!("shared values discovered within data") } _ => (), } } (ax, mx, sx) } /// Recursively calculate the sizes of a value. /// /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`. /// /// # Panics /// /// Panics if any interior data is shared (should never happen). #[inline] pub fn calc_data_sizes(value: &Dynamic, _top: bool) -> (usize, usize, usize) { match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => calc_array_sizes(arr), #[cfg(not(feature = "no_index"))] Union::Blob(ref blob, ..) => (blob.len(), 0, 0), #[cfg(not(feature = "no_object"))] Union::Map(ref map, ..) => calc_map_sizes(map), Union::Str(ref s, ..) => (0, 0, s.len()), #[cfg(not(feature = "no_closure"))] Union::Shared(..) if _top => calc_data_sizes(&value.read_lock::().unwrap(), true), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { unreachable!("shared values discovered within data: {}", value) } _ => (0, 0, 0), } } impl Engine { /// Raise an error if any data size exceeds limit. /// /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE] /// and should be set afterwards. #[cfg(not(feature = "unchecked"))] pub(crate) fn throw_on_size(&self, (_arr, _map, s): (usize, usize, usize)) -> RhaiResultOf<()> { if self.limits.string_len.map_or(false, |max| s > max.get()) { return Err( ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), ); } #[cfg(not(feature = "no_index"))] if self.limits.array_size.map_or(false, |max| _arr > max.get()) { return Err( ERR::ErrorDataTooLarge("Size of array/BLOB".to_string(), Position::NONE).into(), ); } #[cfg(not(feature = "no_object"))] if self.limits.map_size.map_or(false, |max| _map > max.get()) { return Err( ERR::ErrorDataTooLarge("Size of object map".to_string(), Position::NONE).into(), ); } Ok(()) } /// Check whether the size of a [`Dynamic`] is within limits. #[cfg(not(feature = "unchecked"))] #[inline] pub(crate) fn check_data_size>( &self, value: T, pos: Position, ) -> RhaiResultOf { // If no data size limits, just return if !self.has_data_size_limit() { return Ok(value); } let sizes = calc_data_sizes(value.borrow(), true); self.throw_on_size(sizes) .map_err(|err| err.fill_position(pos))?; Ok(value) } /// Raise an error if the size of a [`Dynamic`] is out of limits (if any). /// /// Not available under `unchecked`. #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> { self.check_data_size(value, Position::NONE).map(|_| ()) } /// Check if the number of operations stay within limit. #[inline(always)] pub(crate) fn track_operation( &self, global: &mut GlobalRuntimeState, pos: Position, ) -> RhaiResultOf<()> { global.num_operations += 1; // Guard against too many operations #[cfg(not(feature = "unchecked"))] if self.max_operations() > 0 && global.num_operations > self.max_operations() { return Err(ERR::ErrorTooManyOperations(pos).into()); } self.progress .as_ref() .and_then(|progress| { progress(global.num_operations) .map(|token| Err(ERR::ErrorTerminated(token, pos).into())) }) .unwrap_or(Ok(())) } } rhai-1.21.0/src/eval/debugger.rs000064400000000000000000000453301046102023000145050ustar 00000000000000//! Module defining the debugging interface. #![cfg(feature = "debugging")] use super::{Caches, EvalContext, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; use crate::{ Dynamic, Engine, EvalAltResult, ImmutableString, Position, RhaiResultOf, Scope, ThinVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, iter::repeat, mem}; /// Callback function to initialize the debugger. #[cfg(not(feature = "sync"))] pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger; /// Callback function to initialize the debugger. #[cfg(feature = "sync")] pub type OnDebuggingInit = dyn Fn(&Engine, Debugger) -> Debugger + Send + Sync; /// Callback function for debugging. #[cfg(not(feature = "sync"))] pub type OnDebuggerCallback = dyn Fn( EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position, ) -> RhaiResultOf; /// Callback function for debugging. #[cfg(feature = "sync")] pub type OnDebuggerCallback = dyn Fn(EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position) -> RhaiResultOf + Send + Sync; /// A command for the debugger on the next iteration. #[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)] #[non_exhaustive] pub enum DebuggerCommand { /// Continue normal execution. #[default] Continue, /// Step into the next expression, diving into functions. StepInto, /// Run to the next expression or statement, stepping over functions. StepOver, /// Run to the next statement, skipping over functions. Next, /// Run to the end of the current function call. FunctionExit, } /// The debugger status. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[non_exhaustive] pub enum DebuggerStatus { // Script evaluation starts. Init, // Stop at the next statement or expression. Next(bool, bool), // Run to the end of the current level of function call. FunctionExit(usize), // Script evaluation ends. Terminate, } impl DebuggerStatus { pub const CONTINUE: Self = Self::Next(false, false); pub const STEP: Self = Self::Next(true, true); pub const NEXT: Self = Self::Next(true, false); pub const INTO: Self = Self::Next(false, true); } /// A event that triggers the debugger. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum DebuggerEvent<'a> { /// Script evaluation starts. Start, /// Break on next step. Step, /// Break on break-point. BreakPoint(usize), /// Return from a function with a value. FunctionExitWithValue(&'a Dynamic), /// Return from a function with a value. FunctionExitWithError(&'a EvalAltResult), /// Script evaluation ends. End, } /// A break-point for debugging. #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[non_exhaustive] pub enum BreakPoint { /// Break at a particular position under a particular source. /// /// Not available under `no_position`. #[cfg(not(feature = "no_position"))] AtPosition { /// Source (empty if not available) of the break-point. source: Option, /// [Position] of the break-point. pos: Position, /// Is the break-point enabled? enabled: bool, }, /// Break at a particular function call. AtFunctionName { /// Function name. name: ImmutableString, /// Is the break-point enabled? enabled: bool, }, /// Break at a particular function call with a particular number of arguments. AtFunctionCall { /// Function name. name: ImmutableString, /// Number of arguments. args: usize, /// Is the break-point enabled? enabled: bool, }, /// Break at a particular property . /// /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] AtProperty { /// Property name. name: ImmutableString, /// Is the break-point enabled? enabled: bool, }, } impl fmt::Display for BreakPoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { #[cfg(not(feature = "no_position"))] Self::AtPosition { source, pos, enabled, } => { if let Some(ref source) = source { write!(f, "{source} ")?; } write!(f, "@ {pos:?}")?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } Self::AtFunctionName { name, enabled } => { write!(f, "{name} (...)")?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } Self::AtFunctionCall { name, args, enabled, } => { write!( f, "{name} ({})", repeat("_").take(*args).collect::>().join(", ") )?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } #[cfg(not(feature = "no_object"))] Self::AtProperty { name, enabled } => { write!(f, ".{name}")?; if !*enabled { f.write_str(" (disabled)")?; } Ok(()) } } } } impl BreakPoint { /// Is this [`BreakPoint`] enabled? #[inline(always)] #[must_use] pub const fn is_enabled(&self) -> bool { match self { #[cfg(not(feature = "no_position"))] Self::AtPosition { enabled, .. } => *enabled, Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => *enabled, #[cfg(not(feature = "no_object"))] Self::AtProperty { enabled, .. } => *enabled, } } /// Enable/disable this [`BreakPoint`]. #[inline(always)] pub fn enable(&mut self, value: bool) { match self { #[cfg(not(feature = "no_position"))] Self::AtPosition { enabled, .. } => *enabled = value, Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => { *enabled = value } #[cfg(not(feature = "no_object"))] Self::AtProperty { enabled, .. } => *enabled = value, } } } /// A function call. #[derive(Debug, Clone, Hash)] pub struct CallStackFrame { /// Function name. pub fn_name: ImmutableString, /// Copies of function call arguments, if any. pub args: ThinVec, /// Source of the function. pub source: Option, /// [Position][`Position`] of the function call. pub pos: Position, } impl fmt::Display for CallStackFrame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fp = f.debug_tuple(&self.fn_name); for arg in &self.args { fp.field(arg); } fp.finish()?; if !self.pos.is_none() { if let Some(ref source) = self.source { write!(f, ": {source}")?; } write!(f, " @ {:?}", self.pos)?; } Ok(()) } } /// A type providing debugging facilities. #[derive(Debug, Clone, Hash)] pub struct Debugger { /// The current status command. pub(crate) status: DebuggerStatus, /// The current set of break-points. break_points: Vec, /// The current function call stack. call_stack: Vec, /// The current state. state: Dynamic, } impl Debugger { /// Create a new [`Debugger`]. #[inline(always)] #[must_use] pub fn new(status: DebuggerStatus) -> Self { Self { status, break_points: Vec::new(), call_stack: Vec::new(), state: Dynamic::UNIT, } } /// Get the current call stack. #[inline(always)] #[must_use] pub fn call_stack(&self) -> &[CallStackFrame] { &self.call_stack } /// Rewind the function call stack to a particular depth. #[inline(always)] pub(crate) fn rewind_call_stack(&mut self, len: usize) { self.call_stack.truncate(len); } /// Add a new frame to the function call stack. #[inline(always)] pub(crate) fn push_call_stack_frame( &mut self, fn_name: ImmutableString, args: impl IntoIterator, source: Option, pos: Position, ) { self.call_stack.push(CallStackFrame { fn_name, args: args.into_iter().collect(), source, pos, }); } /// Change the current status to [`CONTINUE`][DebuggerStatus::CONTINUE] and return the previous status. pub(crate) fn clear_status_if( &mut self, filter: impl FnOnce(&DebuggerStatus) -> bool, ) -> Option { if filter(&self.status) { Some(mem::replace(&mut self.status, DebuggerStatus::CONTINUE)) } else { None } } /// Override the status of this [`Debugger`] if the current status is /// [`CONTINUE`][DebuggerStatus::CONTINUE]. #[inline(always)] pub(crate) fn reset_status(&mut self, status: DebuggerStatus) { if self.status == DebuggerStatus::CONTINUE { self.status = status; } } /// Returns the first break-point triggered by a particular [`AST` Node][ASTNode]. #[must_use] pub fn is_break_point(&self, src: Option<&str>, node: ASTNode) -> Option { let _src = src; self.break_points() .iter() .enumerate() .filter(|(.., bp)| bp.is_enabled()) .find(|(.., bp)| match bp { #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { pos, .. } if pos.is_none() => false, #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => { node.position().line().unwrap_or(0) == pos.line().unwrap() && _src == source.as_deref() } #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } => { node.position() == *pos && _src == source.as_deref() } BreakPoint::AtFunctionName { name, .. } => match node { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { x.name == *name } ASTNode::Stmt(Stmt::Expr(e)) => match &**e { Expr::FnCall(x, ..) => x.name == *name, _ => false, }, _ => false, }, BreakPoint::AtFunctionCall { name, args, .. } => match node { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { x.args.len() == *args && x.name == *name } ASTNode::Stmt(Stmt::Expr(e)) => match &**e { Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name, _ => false, }, _ => false, }, #[cfg(not(feature = "no_object"))] BreakPoint::AtProperty { name, .. } => match node { ASTNode::Expr(Expr::Property(x, ..)) => x.2 == *name, _ => false, }, }) .map(|(i, ..)| i) } /// Get a slice of all [`BreakPoint`]'s. #[inline(always)] #[must_use] pub fn break_points(&self) -> &[BreakPoint] { &self.break_points } /// Get the underlying [`Vec`] holding all [`BreakPoint`]'s. #[inline(always)] #[must_use] pub fn break_points_mut(&mut self) -> &mut Vec { &mut self.break_points } /// Get the custom state. #[inline(always)] pub const fn state(&self) -> &Dynamic { &self.state } /// Get a mutable reference to the custom state. #[inline(always)] pub fn state_mut(&mut self) -> &mut Dynamic { &mut self.state } /// Set the custom state. #[inline(always)] pub fn set_state(&mut self, state: impl Into) { self.state = state.into(); } } impl Engine { /// Run the debugger callback if there is a debugging interface registered. #[inline(always)] pub(crate) fn dbg<'a>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, this_ptr: Option<&mut Dynamic>, node: impl Into>, ) -> RhaiResultOf<()> { if self.is_debugger_registered() { if let Some(cmd) = self.dbg_reset_raw(global, caches, scope, this_ptr, node)? { global.debugger_mut().status = cmd; } } Ok(()) } /// Run the debugger callback if there is a debugging interface registered. /// /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline(always)] pub(crate) fn dbg_reset<'a>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, this_ptr: Option<&mut Dynamic>, node: impl Into>, ) -> RhaiResultOf> { if self.is_debugger_registered() { self.dbg_reset_raw(global, caches, scope, this_ptr, node) } else { Ok(None) } } /// Run the debugger callback. /// /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn dbg_reset_raw<'a>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, this_ptr: Option<&mut Dynamic>, node: impl Into>, ) -> RhaiResultOf> { let node = node.into(); // Skip transitive nodes match node { ASTNode::Expr(Expr::Stmt(..)) | ASTNode::Stmt(Stmt::Expr(..)) => return Ok(None), _ => (), } match global.debugger { Some(ref dbg) => { let event = match dbg.status { DebuggerStatus::Init => Some(DebuggerEvent::Start), DebuggerStatus::NEXT if node.is_stmt() => Some(DebuggerEvent::Step), DebuggerStatus::INTO if node.is_expr() => Some(DebuggerEvent::Step), DebuggerStatus::STEP => Some(DebuggerEvent::Step), DebuggerStatus::Terminate => Some(DebuggerEvent::End), _ => None, }; let event = match event { Some(e) => e, None => match dbg.is_break_point(global.source(), node) { Some(bp) => DebuggerEvent::BreakPoint(bp), None => return Ok(None), }, }; self.dbg_raw(global, caches, scope, this_ptr, node, event) } None => Ok(None), } } /// Run the debugger callback unconditionally. /// /// Returns [`Some`] if the debugger needs to be reactivated at the end of the block, statement or /// function call. /// /// It is up to the [`Engine`] to reactivate the debugger. #[inline] pub(crate) fn dbg_raw( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, this_ptr: Option<&mut Dynamic>, node: ASTNode, event: DebuggerEvent, ) -> Result, Box> { match self.debugger_interface { Some(ref x) => { let orig_scope_len = scope.len(); let src = global.source_raw().cloned(); let context = EvalContext::new(self, global, caches, scope, this_ptr); let (.., ref on_debugger) = *x; let command = on_debugger(context, event, node, src.as_deref(), node.position()); if orig_scope_len != scope.len() { // The scope is changed, always search from now on global.always_search_scope = true; } match command? { DebuggerCommand::Continue => { global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(None) } DebuggerCommand::Next => { global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(Some(DebuggerStatus::NEXT)) } DebuggerCommand::StepOver => { global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(Some(DebuggerStatus::STEP)) } DebuggerCommand::StepInto => { global.debugger_mut().status = DebuggerStatus::STEP; Ok(None) } DebuggerCommand::FunctionExit => { // Bump a level if it is a function call let level = match node { ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { global.level + 1 } ASTNode::Stmt(Stmt::Expr(e)) if matches!(**e, Expr::FnCall(..)) => { global.level + 1 } _ => global.level, }; global.debugger_mut().status = DebuggerStatus::FunctionExit(level); Ok(None) } } } None => Ok(None), } } } rhai-1.21.0/src/eval/eval_context.rs000064400000000000000000000342751046102023000154220ustar 00000000000000//! Evaluation context. use super::{Caches, GlobalRuntimeState}; use crate::ast::FnCallHashes; use crate::tokenizer::{is_valid_function_name, Token}; use crate::types::dynamic::Variant; use crate::{ calc_fn_hash, expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Context of a script evaluation process. #[allow(dead_code)] pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> { /// The current [`Engine`]. engine: &'a Engine, /// The current [`GlobalRuntimeState`]. global: &'g mut GlobalRuntimeState, /// The current [caches][Caches], if available. caches: &'c mut Caches, /// The current [`Scope`]. scope: &'s mut Scope<'ps>, /// The current bound `this` pointer, if any. this_ptr: Option<&'t mut Dynamic>, } impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { /// Create a new [`EvalContext`]. #[expose_under_internals] #[inline(always)] #[must_use] fn new( engine: &'a Engine, global: &'g mut GlobalRuntimeState, caches: &'c mut Caches, scope: &'s mut Scope<'ps>, this_ptr: Option<&'t mut Dynamic>, ) -> Self { Self { engine, global, caches, scope, this_ptr, } } /// The current [`Engine`]. #[inline(always)] #[must_use] pub const fn engine(&self) -> &'a Engine { self.engine } /// The current source. #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { self.global.source() } /// The current [`Scope`]. #[inline(always)] #[must_use] pub const fn scope(&self) -> &Scope<'ps> { self.scope } /// Get a mutable reference to the current [`Scope`]. #[inline(always)] #[must_use] pub fn scope_mut(&mut self) -> &mut Scope<'ps> { self.scope } /// Get an iterator over the current set of modules imported via `import` statements, /// in reverse order (i.e. modules imported last come first). #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn iter_imports(&self) -> impl Iterator { self.global.iter_imports() } /// Custom state kept in a [`Dynamic`]. #[inline(always)] pub const fn tag(&self) -> &Dynamic { &self.global.tag } /// Mutable reference to the custom state kept in a [`Dynamic`]. #[inline(always)] pub fn tag_mut(&mut self) -> &mut Dynamic { &mut self.global.tag } /// _(internals)_ The current [`GlobalRuntimeState`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] #[must_use] pub const fn global_runtime_state(&self) -> &GlobalRuntimeState { self.global } /// _(internals)_ Get a mutable reference to the current [`GlobalRuntimeState`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] #[must_use] pub fn global_runtime_state_mut(&mut self) -> &mut GlobalRuntimeState { self.global } /// Get an iterator over the namespaces containing definition of all script-defined functions. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] pub fn iter_namespaces(&self) -> impl Iterator { self.global.lib.iter().map(<_>::as_ref) } /// _(internals)_ The current set of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "internals")] #[inline(always)] #[must_use] pub fn namespaces(&self) -> &[crate::SharedModule] { &self.global.lib } /// The current bound `this` pointer, if any. #[inline(always)] #[must_use] pub fn this_ptr(&self) -> Option<&Dynamic> { self.this_ptr.as_deref() } /// Mutable reference to the current bound `this` pointer, if any. #[inline(always)] #[must_use] pub fn this_ptr_mut(&mut self) -> Option<&mut Dynamic> { self.this_ptr.as_deref_mut() } /// The current nesting level of function calls. #[inline(always)] #[must_use] pub const fn call_level(&self) -> usize { self.global.level } /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] pub fn eval_expression_tree(&mut self, expr: &crate::Expression) -> crate::RhaiResult { #[allow(deprecated)] self.eval_expression_tree_raw(expr, true) } /// Evaluate an [expression tree][crate::Expression] within this [evaluation context][`EvalContext`]. /// /// The following option is available: /// /// * whether to rewind the [`Scope`] after evaluation if the expression is a [`StmtBlock`][crate::ast::StmtBlock] /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # WARNING - Low Level API /// /// This function is _extremely_ low level. It evaluates an expression from an [`AST`][crate::AST]. #[cfg(not(feature = "no_custom_syntax"))] #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline] pub fn eval_expression_tree_raw( &mut self, expr: &crate::Expression, rewind_scope: bool, ) -> crate::RhaiResult { let expr: &crate::ast::Expr = expr; let this_ptr = self.this_ptr.as_deref_mut(); match expr { crate::ast::Expr::Stmt(stmts) => self.engine.eval_stmt_block( self.global, self.caches, self.scope, this_ptr, stmts.statements(), rewind_scope, ), _ => self .engine .eval_expr(self.global, self.caches, self.scope, this_ptr, expr), } } /// Call a function inside the [evaluation context][`EvalContext`] with the provided arguments. pub fn call_fn( &mut self, fn_name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let engine = self.engine(); let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let args = &mut arg_values.iter_mut().collect::>(); let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() { args.insert(0, this_ptr); true } else { false }; _call_fn_raw( engine, self.global, self.caches, self.scope, fn_name, args, false, is_ref_mut, false, ) .and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = engine.map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => engine.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE) .into() }) }) } /// Call a registered native Rust function inside the [evaluation context][`EvalContext`] with /// the provided arguments. /// /// This is often useful because Rust functions typically only want to cross-call other /// registered Rust functions and not have to worry about scripted functions hijacking the /// process unknowingly (or deliberately). pub fn call_native_fn( &mut self, fn_name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let engine = self.engine(); let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let args = &mut arg_values.iter_mut().collect::>(); let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() { args.insert(0, this_ptr); true } else { false }; _call_fn_raw( engine, self.global, self.caches, self.scope, fn_name, args, true, is_ref_mut, false, ) .and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = engine.map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => engine.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE) .into() }) }) } /// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`]. /// /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for /// a script-defined function (or the object of a method call). /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// # Arguments /// /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them /// _before_ calling this function. /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. #[inline(always)] pub fn call_fn_raw( &mut self, fn_name: impl AsRef, is_ref_mut: bool, is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { let name = fn_name.as_ref(); let native_only = !is_valid_function_name(name); #[cfg(not(feature = "no_function"))] let native_only = native_only && !crate::parser::is_anonymous_fn(name); _call_fn_raw( self.engine(), self.global, self.caches, self.scope, fn_name, args, native_only, is_ref_mut, is_method_call, ) } /// Call a registered native Rust function inside the [evaluation context][`EvalContext`]. /// /// This is often useful because Rust functions typically only want to cross-call other /// registered Rust functions and not have to worry about scripted functions hijacking the /// process unknowingly (or deliberately). /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// # Arguments /// /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them /// _before_ calling this function. /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. #[inline(always)] pub fn call_native_fn_raw( &mut self, fn_name: impl AsRef, is_ref_mut: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { _call_fn_raw( self.engine(), self.global, self.caches, self.scope, fn_name, args, true, is_ref_mut, false, ) } } /// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`]. fn _call_fn_raw( engine: &Engine, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, fn_name: impl AsRef, args: &mut [&mut Dynamic], native_only: bool, is_ref_mut: bool, is_method_call: bool, ) -> RhaiResult { defer! { let orig_level = global.level; global.level += 1 } let fn_name = fn_name.as_ref(); let op_token = Token::lookup_symbol_from_syntax(fn_name); let op_token = op_token.as_ref(); let args_len = args.len(); if native_only { let hash = calc_fn_hash(None, fn_name, args_len); return engine .exec_native_fn_call( global, caches, fn_name, op_token, hash, args, is_ref_mut, false, Position::NONE, ) .map(|(r, ..)| r); } // Native or script let hash = match is_method_call { #[cfg(not(feature = "no_function"))] true => FnCallHashes::from_script_and_native( calc_fn_hash(None, fn_name, args_len - 1), calc_fn_hash(None, fn_name, args_len), ), #[cfg(feature = "no_function")] true => FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args_len)), _ => FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args_len)), }; engine .exec_fn_call( global, caches, Some(scope), fn_name, op_token, hash, args, is_ref_mut, is_method_call, Position::NONE, ) .map(|(r, ..)| r) } rhai-1.21.0/src/eval/expr.rs000064400000000000000000000407161046102023000137020ustar 00000000000000//! Module defining functions for evaluating an expression. use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::Expr; use crate::packages::string_basic::{print_with_func, FUNC_TO_STRING}; use crate::types::dynamic::AccessMode; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{convert::TryInto, fmt::Write, num::NonZeroUsize}; impl Engine { /// Search for a module within an imports stack. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub(crate) fn search_imports( &self, global: &GlobalRuntimeState, namespace: &crate::ast::Namespace, ) -> Option { debug_assert!(!namespace.is_empty()); let root = namespace.root(); // Qualified - check if the root module is directly indexed if !global.always_search_scope { if let Some(index) = namespace.index { let offset = global.num_imports() - index.get(); if let m @ Some(_) = global.get_shared_import(offset) { return m; } } } // Do a text-match search if the index doesn't work global.find_import(root).map_or_else( || self.global_sub_modules.get(root).cloned(), |offset| global.get_shared_import(offset), ) } /// Search for a variable within the scope /// /// # Panics /// /// Panics if `expr` is not [`Expr::Variable`]. pub(crate) fn search_scope_only<'s>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &'s mut Scope, this_ptr: Option<&'s mut Dynamic>, expr: &Expr, ) -> RhaiResultOf> { // Make sure that the pointer indirection is taken only when absolutely necessary. let index = match expr { // Check if the variable is `this` Expr::ThisPtr(..) => unreachable!("Expr::ThisPtr should have been handled outside"), _ if global.always_search_scope => 0, Expr::Variable(_, Some(i), ..) => i.get() as usize, Expr::Variable(v, None, ..) => { #[cfg(not(feature = "no_module"))] debug_assert!(v.2.is_empty(), "variable should not be namespace-qualified"); // Scripted function with the same name #[cfg(not(feature = "no_function"))] if let Some(fn_def) = global .lib .iter() .flat_map(|m| m.iter_script_fn()) .find_map(|(_, _, f, _, func)| if f == v.1 { Some(func) } else { None }) { let val: Dynamic = crate::FnPtr { name: v.1.clone(), curry: <_>::default(), env: None, typ: crate::types::fn_ptr::FnPtrType::Script(fn_def.clone()), } .into(); return Ok(val.into()); } v.0.map_or(0, NonZeroUsize::get) } _ => unreachable!("Expr::Variable expected but gets {:?}", expr), }; // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { let orig_scope_len = scope.len(); let context = EvalContext::new(self, global, caches, scope, this_ptr); let var_name = expr.get_variable_name(true).unwrap(); let resolved_var = resolve_var(var_name, index, context); if orig_scope_len != scope.len() { // The scope is changed, always search from now on global.always_search_scope = true; } match resolved_var { Ok(Some(mut result)) => { result.set_access_mode(AccessMode::ReadOnly); return Ok(result.into()); } Ok(None) => (), Err(err) => return Err(err.fill_position(expr.position())), } } let index = if index > 0 { scope.len() - index } else { // Find the variable in the scope let var_name = expr.get_variable_name(true).unwrap(); match scope.search(var_name) { Some(index) => index, None => { return self .global_modules .iter() .find_map(|m| m.get_var(var_name)) .map_or_else( || { Err(ERR::ErrorVariableNotFound( var_name.to_string(), expr.position(), ) .into()) }, |val| Ok(val.into()), ) } } }; let val = scope.get_mut_by_index(index); val.try_into() } /// Search for a variable within the scope or within imports, /// depending on whether the variable name is namespace-qualified. pub(crate) fn search_namespace<'s>( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &'s mut Scope, this_ptr: Option<&'s mut Dynamic>, expr: &Expr, ) -> RhaiResultOf> { match expr { Expr::Variable(_, Some(_), _) => { self.search_scope_only(global, caches, scope, this_ptr, expr) } Expr::Variable(v, None, ..) => match &**v { // Qualified variable access #[cfg(not(feature = "no_module"))] (_, var_name, ns, hash_var) if !ns.is_empty() => { // foo:bar::baz::VARIABLE if let Some(module) = self.search_imports(global, ns) { return module.get_qualified_var(*hash_var).map_or_else( || { let sep = crate::engine::NAMESPACE_SEPARATOR; Err(ERR::ErrorVariableNotFound( format!("{ns}{sep}{var_name}"), ns.position(), ) .into()) }, |mut target| { // Module variables are constant target.set_access_mode(AccessMode::ReadOnly); Ok(target.into()) }, ); } // global::VARIABLE #[cfg(not(feature = "no_function"))] if ns.path.len() == 1 && ns.root() == crate::engine::KEYWORD_GLOBAL { if let Some(ref constants) = global.constants { if let Some(value) = crate::func::locked_write(constants) .unwrap() .get_mut(var_name.as_str()) { let mut target: Target = value.clone().into(); // Module variables are constant target.as_mut().set_access_mode(AccessMode::ReadOnly); return Ok(target); } } let sep = crate::engine::NAMESPACE_SEPARATOR; return Err(ERR::ErrorVariableNotFound( format!("{ns}{sep}{var_name}"), ns.position(), ) .into()); } Err(ERR::ErrorModuleNotFound(ns.to_string(), ns.position()).into()) } // Normal variable access _ => self.search_scope_only(global, caches, scope, this_ptr, expr), }, _ => unreachable!("Expr::Variable expected but gets {:?}", expr), } } /// Evaluate an expression. pub(crate) fn eval_expr( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, expr: &Expr, ) -> RhaiResult { self.track_operation(global, expr.position())?; #[cfg(feature = "debugging")] let reset = self.dbg_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?; #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } match expr { // Constants Expr::IntegerConstant(x, ..) => Ok((*x).into()), Expr::StringConstant(x, ..) => Ok(x.clone().into()), Expr::BoolConstant(x, ..) => Ok((*x).into()), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x, ..) => Ok((*x).into()), Expr::CharConstant(x, ..) => Ok((*x).into()), Expr::Unit(..) => Ok(Dynamic::UNIT), Expr::DynamicConstant(x, ..) => Ok(x.as_ref().clone()), Expr::FnCall(x, pos) => { self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos) } Expr::ThisPtr(var_pos) => this_ptr .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) .cloned(), Expr::Variable(..) => self .search_namespace(global, caches, scope, this_ptr, expr) .map(Target::take_or_clone), Expr::InterpolatedString(x, _) => { let mut concat = SmartString::new_const(); for expr in &**x { let item = &mut self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .flatten(); let pos = expr.position(); if item.is_string() { write!(concat, "{item}").unwrap(); } else { let source = global.source(); let context = &(self, FUNC_TO_STRING, source, &*global, pos).into(); let display = print_with_func(FUNC_TO_STRING, context, item); write!(concat, "{display}").unwrap(); } #[cfg(not(feature = "unchecked"))] self.throw_on_size((0, 0, concat.len())) .map_err(|err| err.fill_position(pos))?; } Ok(self.get_interned_string(concat).into()) } #[cfg(not(feature = "no_index"))] Expr::Array(x, ..) => { let mut array = crate::Array::with_capacity(x.len()); #[cfg(not(feature = "unchecked"))] let mut total_data_sizes = (0, 0, 0); for item_expr in &**x { let value = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), item_expr)? .flatten(); #[cfg(not(feature = "unchecked"))] if self.has_data_size_limit() { let val_sizes = crate::eval::calc_data_sizes(&value, true); total_data_sizes = ( total_data_sizes.0 + val_sizes.0 + 1, total_data_sizes.1 + val_sizes.1, total_data_sizes.2 + val_sizes.2, ); self.throw_on_size(total_data_sizes) .map_err(|err| err.fill_position(item_expr.position()))?; } array.push(value); } Ok(Dynamic::from_array(array)) } #[cfg(not(feature = "no_object"))] Expr::Map(x, ..) => { let mut map = x.1.clone(); #[cfg(not(feature = "unchecked"))] let mut total_data_sizes = (0, 0, 0); for (key, value_expr) in &x.0 { let value = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)? .flatten(); #[cfg(not(feature = "unchecked"))] if self.has_data_size_limit() { let delta = crate::eval::calc_data_sizes(&value, true); total_data_sizes = ( total_data_sizes.0 + delta.0, total_data_sizes.1 + delta.1 + 1, total_data_sizes.2 + delta.2, ); self.throw_on_size(total_data_sizes) .map_err(|err| err.fill_position(value_expr.position()))?; } *map.get_mut(key.as_str()).unwrap() = value; } Ok(Dynamic::from_map(map)) } Expr::And(x, ..) => Ok((self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? && self .eval_expr(global, caches, scope, this_ptr, &x.rhs)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) .into()), Expr::Or(x, ..) => Ok((self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.lhs.position()))? || self .eval_expr(global, caches, scope, this_ptr, &x.rhs)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, x.rhs.position()))?) .into()), Expr::Coalesce(x, ..) => { let value = self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?; if value.is_unit() { self.eval_expr(global, caches, scope, this_ptr, &x.rhs) } else { Ok(value) } } #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(custom, pos) => { let expressions: crate::StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); // The first token acts as the custom syntax's key let key_token = custom.tokens.first().unwrap(); // The key should exist, unless the AST is compiled in a different Engine let custom_def = self.custom_syntax.get(key_token.as_str()).ok_or_else(|| { Box::new(ERR::ErrorCustomSyntax( format!("Invalid custom syntax prefix: {key_token}"), custom.tokens.iter().map(<_>::to_string).collect(), *pos, )) })?; let mut context = EvalContext::new(self, global, caches, scope, this_ptr); (custom_def.func)(&mut context, &expressions, &custom.state) .and_then(|r| self.check_data_size(r, expr.start_position())) } Expr::Stmt(x) => { self.eval_stmt_block(global, caches, scope, this_ptr, x.statements(), true) } #[cfg(not(feature = "no_index"))] Expr::Index(..) => { self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None) } #[cfg(not(feature = "no_object"))] Expr::Dot(..) => self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None), #[allow(unreachable_patterns)] _ => unreachable!("expression cannot be evaluated: {:?}", expr), } } } rhai-1.21.0/src/eval/global_state.rs000064400000000000000000000304051046102023000153560ustar 00000000000000//! Global runtime state. use crate::{expose_under_internals, Dynamic, Engine, ImmutableString}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Collection of globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub type SharedGlobalConstants = crate::Shared>>; /// _(internals)_ Global runtime states. /// Exported under the `internals` feature only. // // # Implementation Notes // // This implementation for imported [modules][crate::Module] splits the module names from the shared // modules to improve data locality. // // Most usage will be looking up a particular key from the list and then getting the module that // corresponds to that key. #[derive(Clone)] pub struct GlobalRuntimeState { /// Names of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] imports: crate::ThinVec, /// Stack of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] modules: crate::ThinVec, /// The current stack of loaded [modules][crate::Module] containing script-defined functions. #[cfg(not(feature = "no_function"))] pub lib: crate::ThinVec, /// Source of the current context. /// /// No source if the string is empty. pub source: Option, /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. #[cfg(not(feature = "no_module"))] pub num_modules_loaded: usize, /// The current nesting level of function calls. pub level: usize, /// Level of the current scope. /// /// The global (root) level is zero, a new block (or function call) is one level higher, and so on. pub scope_level: usize, /// Force a [`Scope`][crate::Scope] search by name. /// /// Normally, access to variables are parsed with a relative offset into the /// [`Scope`][crate::Scope] to avoid a lookup. /// /// In some situation, e.g. after running an `eval` statement, or after a custom syntax /// statement, subsequent offsets may become mis-aligned. /// /// When that happens, this flag is turned on. pub always_search_scope: bool, /// Embedded [module][crate::Module] resolver. #[cfg(not(feature = "no_module"))] pub embedded_module_resolver: Option>, /// Cache of globally-defined constants. /// /// Interior mutability is needed because it is shared in order to aid in cloning. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub constants: Option, /// Custom state that can be used by the external host. pub tag: Dynamic, /// Debugging interface. #[cfg(feature = "debugging")] pub(crate) debugger: Option>, } impl Engine { /// _(internals)_ Create a new [`GlobalRuntimeState`] based on an [`Engine`]. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn new_global_runtime_state(&self) -> GlobalRuntimeState { GlobalRuntimeState { #[cfg(not(feature = "no_module"))] imports: crate::ThinVec::new(), #[cfg(not(feature = "no_module"))] modules: crate::ThinVec::new(), #[cfg(not(feature = "no_function"))] lib: crate::ThinVec::new(), source: None, num_operations: 0, #[cfg(not(feature = "no_module"))] num_modules_loaded: 0, scope_level: 0, level: 0, always_search_scope: false, #[cfg(not(feature = "no_module"))] embedded_module_resolver: None, #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] constants: None, tag: self.default_tag().clone(), #[cfg(feature = "debugging")] debugger: self.debugger_interface.as_ref().map(|x| { let dbg = crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init); (x.0)(self, dbg).into() }), } } } impl GlobalRuntimeState { /// Get the length of the stack of globally-imported [modules][crate::Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub fn num_imports(&self) -> usize { self.modules.len() } /// Get the globally-imported [module][crate::Module] at a particular index. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub fn get_shared_import(&self, index: usize) -> Option { self.modules.get(index).cloned() } /// Get the index of a globally-imported [module][crate::Module] by name. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub fn find_import(&self, name: &str) -> Option { self.imports.iter().rposition(|key| key == name) } /// Push an imported [module][crate::Module] onto the stack. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn push_import( &mut self, name: impl Into, module: impl Into, ) { self.imports.push(name.into()); self.modules.push(module.into()); } /// Truncate the stack of globally-imported [modules][crate::Module] to a particular length. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn truncate_imports(&mut self, size: usize) { self.imports.truncate(size); self.modules.truncate(size); } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports(&self) -> impl Iterator { self.imports .iter() .rev() .zip(self.modules.iter().rev()) .map(|(name, module)| (name.as_str(), &**module)) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports_raw( &self, ) -> impl Iterator { self.imports.iter().rev().zip(self.modules.iter().rev()) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn scan_imports_raw( &self, ) -> impl Iterator { self.imports.iter().zip(self.modules.iter()) } /// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of /// globally-imported [modules][crate::Module]? /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { self.modules .iter() .any(|m| m.may_contain_dynamic_fn(hash_script)) } /// Does the specified function hash key exist in the stack of globally-imported /// [modules][crate::Module]? /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[allow(dead_code)] #[inline] #[must_use] pub fn contains_qualified_fn(&self, hash: u64) -> bool { self.modules.iter().any(|m| m.contains_qualified_fn(hash)) } /// Get the specified function via its hash key from the stack of globally-imported /// [modules][crate::Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub fn get_qualified_fn( &self, hash: u64, global_namespace_only: bool, ) -> Option<(&crate::func::RhaiFunc, Option<&ImmutableString>)> { if global_namespace_only { self.modules .iter() .rev() .filter(|&m| m.contains_indexed_global_functions()) .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } else { self.modules .iter() .rev() .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of /// globally-imported [modules][crate::Module]? /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[allow(dead_code)] #[inline] #[must_use] pub fn contains_iter(&self, id: std::any::TypeId) -> bool { self.modules.iter().any(|m| m.contains_qualified_iter(id)) } /// Get the specified [`TypeId`][std::any::TypeId] iterator from the stack of globally-imported /// [modules][crate::Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub fn get_iter(&self, id: std::any::TypeId) -> Option<&crate::func::FnIterator> { self.modules .iter() .rev() .find_map(|m| m.get_qualified_iter(id)) } /// Get the current source. #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { self.source.as_deref() } /// Get the current source. #[inline(always)] #[must_use] #[allow(dead_code)] pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { self.source.as_ref() } /// Return a reference to the debugging interface. /// /// # Panics /// /// Panics if the debugging interface is not set. #[cfg(feature = "debugging")] #[must_use] pub fn debugger(&self) -> &super::Debugger { self.debugger.as_ref().unwrap() } /// Return a mutable reference to the debugging interface. /// /// # Panics /// /// Panics if the debugging interface is not set. #[cfg(feature = "debugging")] #[must_use] pub fn debugger_mut(&mut self) -> &mut super::Debugger { self.debugger.as_deref_mut().unwrap() } } #[cfg(not(feature = "no_module"))] impl, M: Into> Extend<(K, M)> for GlobalRuntimeState { #[inline] fn extend>(&mut self, iter: T) { for (k, m) in iter { self.imports.push(k.into()); self.modules.push(m.into()); } } } impl fmt::Debug for GlobalRuntimeState { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("GlobalRuntimeState"); #[cfg(not(feature = "no_module"))] f.field("imports", &self.scan_imports_raw().collect::>()) .field("num_modules_loaded", &self.num_modules_loaded) .field("embedded_module_resolver", &self.embedded_module_resolver); #[cfg(not(feature = "no_function"))] f.field("lib", &self.lib); f.field("source", &self.source) .field("num_operations", &self.num_operations) .field("level", &self.level) .field("scope_level", &self.scope_level) .field("always_search_scope", &self.always_search_scope); #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] f.field("constants", &self.constants); f.field("tag", &self.tag); #[cfg(feature = "debugging")] f.field("debugger", &self.debugger); f.finish() } } rhai-1.21.0/src/eval/mod.rs000064400000000000000000000036101046102023000134730ustar 00000000000000mod cache; mod chaining; mod data_check; mod debugger; mod eval_context; mod expr; mod global_state; mod stmt; mod target; #[allow(unused_imports)] pub use cache::FnResolutionCache; pub use cache::{Caches, FnResolutionCacheEntry}; #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] pub use data_check::calc_array_sizes; #[cfg(not(feature = "unchecked"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub use data_check::calc_data_sizes; #[cfg(feature = "debugging")] #[cfg(not(feature = "no_function"))] pub use debugger::CallStackFrame; #[cfg(feature = "debugging")] pub use debugger::{ BreakPoint, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus, OnDebuggerCallback, OnDebuggingInit, }; pub use eval_context::EvalContext; pub use global_state::GlobalRuntimeState; #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub use global_state::SharedGlobalConstants; #[cfg(not(feature = "no_index"))] pub use target::calc_offset_len; pub use target::{calc_index, Target}; #[cfg(feature = "unchecked")] mod unchecked { use crate::{eval::GlobalRuntimeState, Dynamic, Engine, Position, RhaiResultOf}; use std::borrow::Borrow; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Check if the number of operations stay within limit. #[inline(always)] pub(crate) const fn track_operation( &self, _: &GlobalRuntimeState, _: Position, ) -> RhaiResultOf<()> { Ok(()) } /// Check whether the size of a [`Dynamic`] is within limits. #[inline(always)] pub(crate) const fn check_data_size>( &self, value: T, _: Position, ) -> RhaiResultOf { Ok(value) } } } rhai-1.21.0/src/eval/stmt.rs000064400000000000000000001271131046102023000137100ustar 00000000000000//! Module defining functions for evaluating a statement. use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::{ ASTFlags, BinaryExpr, Expr, FlowControl, OpAssignment, Stmt, SwitchCasesCollection, }; use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::tokenizer::Token; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT}; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// If the value is a string, intern it. #[inline(always)] fn intern_string(&self, value: Dynamic) -> Dynamic { match value.0 { Union::Str(s, ..) => self.get_interned_string(s).into(), _ => value, } } /// Evaluate a statements block. pub(crate) fn eval_stmt_block( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, statements: &[Stmt], restore_orig_state: bool, ) -> RhaiResult { if statements.is_empty() { return Ok(Dynamic::UNIT); } // Restore scope at end of block if necessary defer! { scope if restore_orig_state => rewind; let orig_scope_len = scope.len(); } // Restore global state at end of block if necessary let orig_always_search_scope = global.always_search_scope; #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); if restore_orig_state { global.scope_level += 1; } defer! { global if restore_orig_state => move |g| { g.scope_level -= 1; #[cfg(not(feature = "no_module"))] g.truncate_imports(orig_imports_len); // The impact of new local variables goes away at the end of a block // because any new variables introduced will go out of scope g.always_search_scope = orig_always_search_scope; }} // Pop new function resolution caches at end of block defer! { caches => rewind_fn_resolution_caches; let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); } // Run the statements statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { let this_ptr = this_ptr.as_deref_mut(); #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); let result = self.eval_stmt(global, caches, scope, this_ptr, stmt, restore_orig_state)?; #[cfg(not(feature = "no_module"))] if matches!(stmt, Stmt::Import(..)) { // Get the extra modules - see if any functions are marked global. // Without global functions, the extra modules never affect function resolution. if global .scan_imports_raw() .skip(orig_imports_len) .any(|(.., m)| m.contains_indexed_global_functions()) { // Different scenarios where the cache must be cleared - notice that this is // expensive as all function resolutions must start again if caches.fn_resolution_caches_len() > orig_fn_resolution_caches_len { // When new module is imported with global functions and there is already // a new cache, just clear it caches.fn_resolution_cache_mut().clear(); } else if restore_orig_state { // When new module is imported with global functions, push a new cache caches.push_fn_resolution_cache(); } else { // When the block is to be evaluated in-place, just clear the current cache caches.fn_resolution_cache_mut().clear(); } } } Ok(result) }) } /// Evaluate an op-assignment statement. pub(crate) fn eval_op_assignment( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, op_info: &OpAssignment, root: &Expr, target: &mut Target, mut new_val: Dynamic, ) -> RhaiResultOf<()> { // Assignment to constant variable? if target.as_ref().is_read_only() { let name = root.get_variable_name(false).unwrap_or_default(); let pos = root.start_position(); return Err(ERR::ErrorAssignmentToConstant(name.to_string(), pos).into()); } let pos = op_info.position(); if let Some((hash_x, hash, op_x, op_x_str, op, op_str)) = op_info.get_op_assignment_info() { let mut lock_guard = target.as_mut().write_lock::().unwrap(); let mut done = false; // Short-circuit built-in op-assignments if under Fast Operators mode if self.fast_operators() { #[allow(clippy::wildcard_imports)] use Token::*; done = true; // For extremely simple primary data operations, do it directly // to avoid the overhead of calling a function. match (&mut lock_guard.0, &mut new_val.0) { (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_x { AndAssign => *b1 = *b1 && *b2, OrAssign => *b1 = *b1 || *b2, XOrAssign => *b1 ^= *b2, _ => done = false, }, (Union::Int(n1, ..), Union::Int(n2, ..)) => { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] match op_x { PlusAssign => { *n1 = add(*n1, *n2).map_err(|err| err.fill_position(pos))? } MinusAssign => { *n1 = subtract(*n1, *n2).map_err(|err| err.fill_position(pos))? } MultiplyAssign => { *n1 = multiply(*n1, *n2).map_err(|err| err.fill_position(pos))? } DivideAssign => { *n1 = divide(*n1, *n2).map_err(|err| err.fill_position(pos))? } ModuloAssign => { *n1 = modulo(*n1, *n2).map_err(|err| err.fill_position(pos))? } _ => done = false, } #[cfg(feature = "unchecked")] match op_x { PlusAssign => *n1 += *n2, MinusAssign => *n1 -= *n2, MultiplyAssign => *n1 *= *n2, DivideAssign => *n1 /= *n2, ModuloAssign => *n1 %= *n2, _ => done = false, } } #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Float(f2, ..)) => match op_x { PlusAssign => **f1 += **f2, MinusAssign => **f1 -= **f2, MultiplyAssign => **f1 *= **f2, DivideAssign => **f1 /= **f2, ModuloAssign => **f1 %= **f2, _ => done = false, }, #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Int(n2, ..)) => match op_x { PlusAssign => **f1 += *n2 as crate::FLOAT, MinusAssign => **f1 -= *n2 as crate::FLOAT, MultiplyAssign => **f1 *= *n2 as crate::FLOAT, DivideAssign => **f1 /= *n2 as crate::FLOAT, ModuloAssign => **f1 %= *n2 as crate::FLOAT, _ => done = false, }, _ => done = false, } if !done { if let Some((func, need_context)) = get_builtin_op_assignment_fn(op_x, &lock_guard, &new_val) { // We may not need to bump the level because built-in's do not need it. //defer! { let orig_level = global.level; global.level += 1 } let args = &mut [&mut *lock_guard, &mut new_val]; let context = need_context .then(|| (self, op_x_str, global.source(), &*global, pos).into()); let _ = func(context, args).map_err(|err| err.fill_position(pos))?; done = true; } } } if !done { let opx = Some(op_x); let args = &mut [&mut *lock_guard, &mut new_val]; match self.exec_native_fn_call( global, caches, op_x_str, opx, hash_x, args, true, false, pos, ) { Ok(_) => (), Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) => { // Expand to `var = var op rhs` let op = Some(op); *args[0] = self .exec_native_fn_call( global, caches, op_str, op, hash, args, true, false, pos, )? .0; } Err(err) => return Err(err), } self.check_data_size(&*args[0], root.position())?; } } else { // Normal assignment match target { // Lock it again just in case it is shared Target::RefMut(_) | Target::TempValue(_) => { *target.as_mut().write_lock::().unwrap() = new_val } #[allow(unreachable_patterns)] _ => *target.as_mut() = new_val, } } target.propagate_changed_value(pos) } /// Evaluate a statement. pub(crate) fn eval_stmt( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, stmt: &Stmt, rewind_scope: bool, ) -> RhaiResult { self.track_operation(global, stmt.position())?; #[cfg(feature = "debugging")] let reset = self.dbg_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?; #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } match stmt { // No-op Stmt::Noop(..) => Ok(Dynamic::UNIT), // Expression as statement Stmt::Expr(expr) => self .eval_expr(global, caches, scope, this_ptr, expr) .map(Dynamic::flatten), // Block scope Stmt::Block(stmts, ..) => { if stmts.is_empty() { Ok(Dynamic::UNIT) } else { self.eval_stmt_block(global, caches, scope, this_ptr, stmts.statements(), true) } } // Function call Stmt::FnCall(x, pos) => { self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos) } // Assignment Stmt::Assignment(x, ..) => { let (op_info, BinaryExpr { lhs, rhs }) = &**x; if let Expr::ThisPtr(..) = lhs { if this_ptr.is_none() { return Err(ERR::ErrorUnboundThis(lhs.position()).into()); } #[cfg(not(feature = "no_function"))] { use std::convert::TryInto; let rhs_val = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)? .flatten(); self.track_operation(global, lhs.position())?; let target = &mut this_ptr.unwrap().try_into()?; self.eval_op_assignment(global, caches, op_info, lhs, target, rhs_val)?; } #[cfg(feature = "no_function")] unreachable!(); } else if let Expr::Variable(x, ..) = lhs { let rhs_val = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)? .flatten(); self.track_operation(global, lhs.position())?; let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let is_temp_result = !target.is_ref(); #[cfg(not(feature = "no_closure"))] // Also handle case where target is a `Dynamic` shared value // (returned by a variable resolver, for example) let is_temp_result = is_temp_result && !target.is_shared(); // Cannot assign to temp result from expression if is_temp_result { return Err(ERR::ErrorAssignmentToConstant( x.1.to_string(), lhs.position(), ) .into()); } self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?; } else { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] { let rhs_val = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)? .flatten(); let _new_val = Some((self.intern_string(rhs_val), op_info)); // Must be either `var[index] op= val` or `var.prop op= val`. // The return value of any op-assignment (should be `()`) is thrown away and not used. let _ = match lhs { // this op= rhs (handled above) Expr::ThisPtr(..) => { unreachable!("Expr::ThisPtr case is already handled") } // name op= rhs (handled above) Expr::Variable(..) => { unreachable!("Expr::Variable case is already handled") } // idx_lhs[idx_expr] op= rhs #[cfg(not(feature = "no_index"))] Expr::Index(..) => self.eval_dot_index_chain( global, caches, scope, this_ptr, lhs, _new_val, ), // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] Expr::Dot(..) => self.eval_dot_index_chain( global, caches, scope, this_ptr, lhs, _new_val, ), _ => unreachable!("cannot assign to expression: {:?}", lhs), }?; } } Ok(Dynamic::UNIT) } // Variable definition Stmt::Var(x, options, pos) => { if !self.allow_shadowing() && scope.contains(x.0.as_str()) { return Err(ERR::ErrorVariableExists(x.0.as_str().to_string(), *pos).into()); } // Let/const statement let (var_name, expr, index) = &**x; let access = if options.intersects(ASTFlags::CONSTANT) { AccessMode::ReadOnly } else { AccessMode::ReadWrite }; let export = options.intersects(ASTFlags::EXPORTED); // Check variable definition filter if let Some(ref filter) = self.def_var_filter { let will_shadow = scope.contains(var_name.as_str()); let is_const = access == AccessMode::ReadOnly; let info = VarDefInfo::new( var_name.as_str(), is_const, global.scope_level, will_shadow, ); let orig_scope_len = scope.len(); let context = EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut()); let filter_result = filter(true, info, context); if orig_scope_len != scope.len() { // The scope is changed, always search from now on global.always_search_scope = true; } if !filter_result? { return Err(ERR::ErrorForbiddenVariable( var_name.as_str().to_string(), *pos, ) .into()); } } // Guard against too many variables #[cfg(not(feature = "unchecked"))] if index.is_none() && scope.len() >= self.max_variables() { return Err(ERR::ErrorTooManyVariables(*pos).into()); } // Evaluate initial value let value = self .eval_expr(global, caches, scope, this_ptr, expr)? .flatten(); let mut value = self.intern_string(value); let _alias = if !rewind_scope { // Put global constants into global module #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] if global.scope_level == 0 && access == AccessMode::ReadOnly && global.lib.iter().any(|m| !m.is_empty()) { crate::func::locked_write(global.constants.get_or_insert_with(|| { crate::Shared::new( crate::Locked::new(std::collections::BTreeMap::new()), ) })) .unwrap() .insert(var_name.name.clone(), value.clone()); } export.then_some(var_name) } else if !export { None } else { unreachable!("exported variable not on global level"); }; match index { Some(index) => { value.set_access_mode(access); *scope.get_mut_by_index(scope.len() - index.get()) = value; } _ => { scope.push_entry(var_name.name.clone(), access, value); } } #[cfg(not(feature = "no_module"))] if let Some(alias) = _alias { scope.add_alias_by_index(scope.len() - 1, alias.as_str().into()); } Ok(Dynamic::UNIT) } // If statement Stmt::If(x, ..) => { let FlowControl { expr, body, branch } = &**x; let guard_val = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; if guard_val && !body.is_empty() { self.eval_stmt_block(global, caches, scope, this_ptr, body.statements(), true) } else if !guard_val && !branch.is_empty() { self.eval_stmt_block(global, caches, scope, this_ptr, branch.statements(), true) } else { Ok(Dynamic::UNIT) } } // Switch statement Stmt::Switch(x, ..) => { let ( expr, SwitchCasesCollection { expressions, cases, def_case, ranges, }, ) = &**x; let mut result = None; let value = self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?; if value.is_hashable() { let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); // First check hashes if let Some(case_blocks_list) = cases.get(&hash) { debug_assert!(!case_blocks_list.is_empty()); for &index in case_blocks_list { let block = &expressions[index]; let cond_result = match block.lhs { Expr::BoolConstant(b, ..) => b, ref c => self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)? .as_bool() .map_err(|typ| { self.make_type_mismatch_err::(typ, c.position()) })?, }; if cond_result { result = Some(&block.rhs); break; } } } else if !ranges.is_empty() { // Then check integer ranges for r in ranges.iter().filter(|r| r.contains(&value)) { let BinaryExpr { lhs, rhs } = &expressions[r.index()]; let cond_result = match lhs { Expr::BoolConstant(b, ..) => *b, c => self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)? .as_bool() .map_err(|typ| { self.make_type_mismatch_err::(typ, c.position()) })?, }; if cond_result { result = Some(rhs); break; } } } } result .or_else(|| def_case.as_ref().map(|&index| &expressions[index].rhs)) .map_or(Ok(Dynamic::UNIT), |expr| { self.eval_expr(global, caches, scope, this_ptr, expr) }) } // Loop Stmt::While(x, ..) if matches!(x.expr, Expr::Unit(..) | Expr::BoolConstant(true, ..)) => { let FlowControl { body, .. } = &**x; if body.is_empty() { loop { self.track_operation(global, body.position())?; } } loop { let this_ptr = this_ptr.as_deref_mut(); let statements = body.statements(); match self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) { Ok(..) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => (), ERR::LoopBreak(true, value, ..) => break Ok(value), _ => break Err(err), }, } } } // While loop Stmt::While(x, ..) => { let FlowControl { expr, body, .. } = &**x; loop { let condition = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; if !condition { break Ok(Dynamic::UNIT); } if body.is_empty() { continue; } let this_ptr = this_ptr.as_deref_mut(); let statements = body.statements(); match self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) { Ok(..) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => (), ERR::LoopBreak(true, value, ..) => break Ok(value), _ => break Err(err), }, } } } // Do loop Stmt::Do(x, options, ..) => { let FlowControl { expr, body, .. } = &**x; let is_while = !options.intersects(ASTFlags::NEGATED); loop { if !body.is_empty() { let this_ptr = this_ptr.as_deref_mut(); let statements = body.statements(); match self .eval_stmt_block(global, caches, scope, this_ptr, statements, true) { Ok(..) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => continue, ERR::LoopBreak(true, value, ..) => break Ok(value), _ => break Err(err), }, } } let condition = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; if condition ^ is_while { break Ok(Dynamic::UNIT); } } } // For loop Stmt::For(x, ..) => { let (var_name, counter, FlowControl { expr, body, .. }) = &**x; // Guard against too many variables #[cfg(not(feature = "unchecked"))] if scope.len() >= self.max_variables() - usize::from(counter.is_some()) { return Err(ERR::ErrorTooManyVariables(var_name.pos).into()); } let iter_obj = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .flatten(); let iter_type = iter_obj.type_id(); // lib should only contain scripts, so technically they cannot have iterators // Search order: // 1) Global namespace - functions registered via Engine::register_XXX // 2) Global modules - packages // 3) Imported modules - functions marked with global namespace // 4) Global sub-modules - functions marked with global namespace let iter_func = self .global_modules .iter() .find_map(|m| m.get_iter(iter_type)); #[cfg(not(feature = "no_module"))] let iter_func = iter_func .or_else(|| global.get_iter(iter_type)) .or_else(|| { self.global_sub_modules .values() .find_map(|m| m.get_qualified_iter(iter_type)) }); let iter_func = iter_func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?; // Restore scope at end of statement defer! { scope => rewind; let orig_scope_len = scope.len(); } // Add the loop variables let counter_index = counter.as_ref().map(|counter| { scope.push(counter.name.clone(), 0 as INT); scope.len() - 1 }); scope.push(var_name.name.clone(), ()); let index = scope.len() - 1; let mut result = Dynamic::UNIT; if body.is_empty() { for _ in iter_func(iter_obj) { self.track_operation(global, body.position())?; } } else { for (i, iter_value) in iter_func(iter_obj).enumerate() { // Increment counter if let Some(counter_index) = counter_index { // As the variable increments from 0, this should always work // since any overflow will first be caught below. let index_value = i as INT; #[cfg(not(feature = "unchecked"))] #[allow(clippy::absurd_extreme_comparisons)] if index_value > crate::MAX_USIZE_INT { return Err(ERR::ErrorArithmetic( format!("for-loop counter overflow: {i}"), counter.as_ref().unwrap().pos, ) .into()); } *scope.get_mut_by_index(counter_index).write_lock().unwrap() = Dynamic::from_int(index_value); } // Set loop value let value = iter_value .map_err(|err| err.fill_position(expr.position()))? .flatten(); *scope.get_mut_by_index(index).write_lock().unwrap() = value; // Run block let this_ptr = this_ptr.as_deref_mut(); let statements = body.statements(); match self .eval_stmt_block(global, caches, scope, this_ptr, statements, true) { Ok(_) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => (), ERR::LoopBreak(true, value, ..) => { result = value; break; } _ => return Err(err), }, } } } Ok(result) } // Continue/Break statement Stmt::BreakLoop(expr, options, pos) => { let is_break = options.intersects(ASTFlags::BREAK); let value = match expr { Some(ref expr) => self.eval_expr(global, caches, scope, this_ptr, expr)?, None => Dynamic::UNIT, }; Err(ERR::LoopBreak(is_break, value, *pos).into()) } // Try/Catch statement Stmt::TryCatch(x, ..) => { let FlowControl { body, expr: catch_var, branch, } = &**x; match self.eval_stmt_block( global, caches, scope, this_ptr.as_deref_mut(), body.statements(), true, ) { r @ Ok(_) => r, Err(err) if err.is_pseudo_error() => Err(err), Err(err) if !err.is_catchable() => Err(err), Err(mut err) => { let err_value = match err.unwrap_inner() { // No error variable _ if catch_var.is_unit() => Dynamic::UNIT, ERR::ErrorRuntime(x, ..) => x.clone(), #[cfg(feature = "no_object")] _ => { let _ = err.take_position(); err.to_string().into() } #[cfg(not(feature = "no_object"))] _ => { let mut err_map = crate::Map::new(); let err_pos = err.take_position(); err_map.insert("message".into(), err.to_string().into()); if let Some(ref source) = global.source { err_map.insert("source".into(), source.into()); } if !err_pos.is_none() { err_map.insert( "line".into(), (err_pos.line().unwrap() as INT).into(), ); err_map.insert( "position".into(), (err_pos.position().unwrap_or(0) as INT).into(), ); } err.dump_fields(&mut err_map); err_map.into() } }; // Restore scope at end of block defer! { scope if !catch_var.is_unit() => rewind; let orig_scope_len = scope.len(); } if let Expr::Variable(x, ..) = catch_var { // Guard against too many variables #[cfg(not(feature = "unchecked"))] if scope.len() >= self.max_variables() { return Err(ERR::ErrorTooManyVariables(catch_var.position()).into()); } scope.push(x.1.clone(), err_value); } let this_ptr = this_ptr.as_deref_mut(); let statements = branch.statements(); self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) .map(|_| Dynamic::UNIT) .map_err(|result_err| match *result_err { // Re-throw exception ERR::ErrorRuntime(v, pos) if v.is_unit() => { err.set_position(pos); err } _ => result_err, }) } } } // Throw value Stmt::Return(Some(expr), options, pos) if options.intersects(ASTFlags::BREAK) => self .eval_expr(global, caches, scope, this_ptr, expr) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw Stmt::Return(None, options, pos) if options.intersects(ASTFlags::BREAK) => { Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into()) } // Return value Stmt::Return(Some(expr), .., pos) => self .eval_expr(global, caches, scope, this_ptr, expr) .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), // Empty return Stmt::Return(None, .., pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()), // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x, _pos) => { use crate::ModuleResolver; let (expr, export) = &**x; // Guard against too many modules #[cfg(not(feature = "unchecked"))] if global.num_modules_loaded >= self.max_modules() { return Err(ERR::ErrorTooManyModules(*_pos).into()); } let v = self.eval_expr(global, caches, scope, this_ptr, expr)?; let path = v.try_cast_result::().map_err(|v| { self.make_type_mismatch_err::( v.type_name(), expr.position(), ) })?; let path_pos = expr.start_position(); let resolver = global.embedded_module_resolver.clone(); let module = resolver .as_ref() .and_then( |r| match r.resolve_raw(self, global, scope, &path, path_pos) { Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, result => Some(result), }, ) .or_else(|| { Some( self.module_resolver() .resolve_raw(self, global, scope, &path, path_pos), ) }) .unwrap_or_else(|| { Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) })?; let (export, must_be_indexed) = if export.is_empty() { (self.const_empty_string(), false) } else { (export.name.clone(), true) }; if !must_be_indexed || module.is_indexed() { global.push_import(export, module); } else { // Index the module (making a clone copy if necessary) if it is not indexed let mut m = crate::func::shared_take_or_clone(module); m.build_index(); global.push_import(export, m); } global.num_modules_loaded += 1; Ok(Dynamic::UNIT) } // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(x, ..) => { use crate::ast::Ident; let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x; // Mark scope variables as public scope.search(name).map_or_else( || Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()), |index| { let alias = if alias.is_empty() { name } else { alias }; scope.add_alias_by_index(index, alias.clone()); Ok(Dynamic::UNIT) }, ) } // Share statement #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { for (var, index) in &**x { // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { let orig_scope_len = scope.len(); let context = EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut()); let resolved_var = resolve_var( &var.name, index.map_or(0, core::num::NonZeroUsize::get), context, ); if orig_scope_len != scope.len() { // The scope is changed, always search from now on global.always_search_scope = true; } match resolved_var { // If resolved to a variable, skip it (because we cannot make it shared) Ok(Some(_)) => continue, Ok(None) => (), Err(err) => return Err(err.fill_position(var.pos)), } } // Search the scope let index = index .map(|n| scope.len() - n.get()) .or_else(|| scope.search(&var.name)) .ok_or_else(|| { Box::new(ERR::ErrorVariableNotFound(var.name.to_string(), var.pos)) })?; let val = scope.get_mut_by_index(index); if !val.is_shared() { // Replace the variable with a shared value. *val = val.take().into_shared(); } } Ok(Dynamic::UNIT) } #[allow(unreachable_patterns)] _ => unreachable!("statement cannot be evaluated: {:?}", stmt), } } /// Evaluate a list of statements with no `this` pointer. /// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body. #[inline(always)] pub(crate) fn eval_global_statements( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, statements: &[Stmt], map_exit_to_return_value: bool, ) -> RhaiResult { self.eval_stmt_block(global, caches, scope, None, statements, false) .or_else(|err| match *err { ERR::Return(out, ..) => Ok(out), ERR::Exit(out, ..) if map_exit_to_return_value => Ok(out), ERR::LoopBreak(..) => { unreachable!("no outer loop scope to break out of") } _ => Err(err), }) } } rhai-1.21.0/src/eval/target.rs000064400000000000000000000421161046102023000142060ustar 00000000000000//! Type to hold a mutable reference to the target of an evaluation. use crate::{Dynamic, Position, RhaiError, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ borrow::{Borrow, BorrowMut}, convert::TryFrom, }; /// Calculate an offset+len pair given an actual length of the underlying array. /// /// Negative starting positions count from the end. /// /// Values going over bounds are limited to the actual length. #[cfg(not(feature = "no_index"))] #[inline] #[allow( clippy::cast_sign_loss, clippy::absurd_extreme_comparisons, clippy::cast_possible_truncation )] pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (usize, usize) { let start = if start < 0 { let abs_start = start.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_start as u64 > crate::MAX_USIZE_INT as u64 { 0 } else { length - usize::min(abs_start as usize, length) } } else if start > crate::MAX_USIZE_INT || start as usize >= length { return (length, 0); } else { start as usize }; let len = if len <= 0 { 0 } else if len > crate::MAX_USIZE_INT || len as usize > length - start { length - start } else { len as usize }; (start, len) } /// Calculate an offset+len pair given an actual length of the underlying array. /// /// Negative starting positions count from the end. /// /// Values going over bounds call the provided closure to return a default value or an error. #[inline] #[allow(dead_code)] #[allow( clippy::cast_sign_loss, clippy::cast_possible_truncation, clippy::absurd_extreme_comparisons )] pub fn calc_index( length: usize, index: crate::INT, negative_count_from_end: bool, err_func: impl FnOnce() -> Result, ) -> Result { if length == 0 { // Empty array, do nothing } else if index < 0 { if negative_count_from_end { let abs_index = index.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_index as u64 <= crate::MAX_USIZE_INT as u64 && (abs_index as usize) <= length { return Ok(length - (abs_index as usize)); } } } else if index <= crate::MAX_USIZE_INT && (index as usize) < length { return Ok(index as usize); } err_func() } /// _(internals)_ A type that encapsulates a mutation target for an expression with side effects. /// Exported under the `internals` feature only. /// /// This type is typically used to hold a mutable reference to the target of an indexing or property /// access operation. /// /// # Example /// /// ``` /// # #[cfg(feature = "internals")] { /// # use rhai::{Dynamic, Target}; /// // Normal `Dynamic` value /// let mut value = Dynamic::from(42_i64); /// let target: Target = value.into(); /// /// // Mutable reference to a `Dynamic` value /// let mut value = Dynamic::from(42_i64); /// let value_ref = &mut value; /// let target: Target = value.into(); /// # } /// ``` #[derive(Debug)] #[must_use] pub enum Target<'a> { /// The target is a mutable reference to a [`Dynamic`]. RefMut(&'a mut Dynamic), /// The target is a mutable reference to a _shared_ [`Dynamic`]. #[cfg(not(feature = "no_closure"))] SharedValue { /// Lock guard to the shared [`Dynamic`]. guard: crate::types::dynamic::DynamicWriteLock<'a, Dynamic>, /// Copy of the shared value. shared_value: Dynamic, }, /// The target is a temporary [`Dynamic`] value (i.e. its mutation can cause no side effects). TempValue(Dynamic), /// The target is a bit inside an [`INT`][crate::INT]. /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. #[cfg(not(feature = "no_index"))] Bit { /// Mutable reference to the source [`Dynamic`]. source: &'a mut Dynamic, /// Copy of the boolean bit, as a [`Dynamic`]. value: Dynamic, /// Bit offset. bit: u8, }, /// The target is a range of bits inside an [`INT`][crate::INT]. /// This is necessary because directly pointing to a range of bits inside an [`INT`][crate::INT] is impossible. #[cfg(not(feature = "no_index"))] BitField { /// Mutable reference to the source [`Dynamic`]. source: &'a mut Dynamic, /// Copy of the integer value of the bits, as a [`Dynamic`]. value: Dynamic, /// Bitmask to apply to the source value (i.e. shifted) mask: crate::INT, /// Number of bits to right-shift the source value. shift: u8, }, /// The target is a byte inside a [`Blob`][crate::Blob]. /// This is necessary because directly pointing to a [byte][u8] inside a BLOB is impossible. #[cfg(not(feature = "no_index"))] BlobByte { /// Mutable reference to the source [`Dynamic`]. source: &'a mut Dynamic, /// Copy of the byte at the index, as a [`Dynamic`]. value: Dynamic, /// Offset index. index: usize, }, /// The target is a character inside a string. /// This is necessary because directly pointing to a [`char`] inside a [`String`] is impossible. #[cfg(not(feature = "no_index"))] StringChar { /// Mutable reference to the source [`Dynamic`]. source: &'a mut Dynamic, /// Copy of the character at the offset, as a [`Dynamic`]. value: Dynamic, /// Offset index. index: usize, }, /// The target is a slice of a string. /// This is necessary because directly pointing to a [`char`] inside a [`String`] is impossible. #[cfg(not(feature = "no_index"))] StringSlice { /// Mutable reference to the source [`Dynamic`]. source: &'a mut Dynamic, /// Copy of the character at the offset, as a [`Dynamic`]. value: Dynamic, /// Offset index. start: usize, /// End index. end: usize, /// Is exclusive? exclusive: bool, }, } impl<'a> Target<'a> { /// Is the [`Target`] a reference pointing to other data? #[allow(dead_code)] #[inline] #[must_use] pub const fn is_ref(&self) -> bool { match self { Self::RefMut(..) => true, #[cfg(not(feature = "no_closure"))] Self::SharedValue { .. } => true, Self::TempValue(..) => false, #[cfg(not(feature = "no_index"))] Self::Bit { .. } | Self::BitField { .. } | Self::BlobByte { .. } | Self::StringChar { .. } | Self::StringSlice { .. } => false, } } /// Is the [`Target`] a temp value? #[inline] #[must_use] pub const fn is_temp_value(&self) -> bool { match self { Self::RefMut(..) => false, #[cfg(not(feature = "no_closure"))] Self::SharedValue { .. } => false, Self::TempValue(..) => true, #[cfg(not(feature = "no_index"))] Self::Bit { .. } | Self::BitField { .. } | Self::BlobByte { .. } | Self::StringChar { .. } | Self::StringSlice { .. } => false, } } /// Is the [`Target`] a shared value? #[inline] #[must_use] pub fn is_shared(&self) -> bool { #[cfg(not(feature = "no_closure"))] return match self { Self::RefMut(r) => r.is_shared(), Self::SharedValue { .. } => true, Self::TempValue(value) => value.is_shared(), #[cfg(not(feature = "no_index"))] Self::Bit { .. } | Self::BitField { .. } | Self::BlobByte { .. } | Self::StringChar { .. } | Self::StringSlice { .. } => false, }; #[cfg(feature = "no_closure")] return false; } /// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary. #[inline] pub fn take_or_clone(self) -> Dynamic { match self { Self::RefMut(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_closure"))] Self::SharedValue { shared_value, .. } => shared_value, // Original shared value is simply taken Self::TempValue(value) => value, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::Bit { value, .. } | Self::BitField { value, .. } | Self::BlobByte { value, .. } | Self::StringChar { value, .. } | Self::StringSlice { value, .. } => value, // Intermediate value is simply taken } } /// Take a `&mut Dynamic` reference from the `Target`. #[inline(always)] #[must_use] pub fn take_ref(self) -> Option<&'a mut Dynamic> { match self { Self::RefMut(r) => Some(r), _ => None, } } /// Convert a shared or reference [`Target`] into a target with an owned value. #[inline(always)] pub fn into_owned(self) -> Self { match self { Self::RefMut(r) => Self::TempValue(r.clone()), #[cfg(not(feature = "no_closure"))] Self::SharedValue { shared_value, .. } => Self::TempValue(shared_value), _ => self, } } /// Get the source [`Dynamic`] of the [`Target`]. #[allow(dead_code)] #[inline] pub fn source(&self) -> &Dynamic { match self { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { guard, .. } => guard, Self::TempValue(value) => value, #[cfg(not(feature = "no_index"))] Self::Bit { source, .. } | Self::BitField { source, .. } | Self::BlobByte { source, .. } | Self::StringChar { source, .. } | Self::StringSlice { source, .. } => source, } } /// Propagate a changed value back to the original source. /// This has no effect for direct references. #[inline] pub fn propagate_changed_value(&mut self, _pos: Position) -> RhaiResultOf<()> { match self { Self::RefMut(..) | Self::TempValue(..) => (), #[cfg(not(feature = "no_closure"))] Self::SharedValue { .. } => (), #[cfg(not(feature = "no_index"))] Self::Bit { source, value, bit } => { // Replace the bit at the specified index position let new_bit = value.as_bool().map_err(|err| { Box::new(crate::ERR::ErrorMismatchDataType( "bool".to_string(), err.to_string(), _pos, )) })?; let value = &mut *source.write_lock::().unwrap(); let index = *bit; let mask = 1 << index; if new_bit { *value |= mask; } else { *value &= !mask; } } #[cfg(not(feature = "no_index"))] Self::BitField { source, value, mask, shift, } => { let shift = *shift; let mask = *mask; // Replace the bit at the specified index position let new_value = value.as_int().map_err(|err| { Box::new(crate::ERR::ErrorMismatchDataType( "integer".to_string(), err.to_string(), _pos, )) })?; let new_value = (new_value << shift) & mask; let value = &mut *source.write_lock::().unwrap(); *value &= !mask; *value |= new_value; } #[cfg(not(feature = "no_index"))] Self::BlobByte { source, value, index, } => { // Replace the byte at the specified index position let new_byte = value.as_int().map_err(|err| { Box::new(crate::ERR::ErrorMismatchDataType( "INT".to_string(), err.to_string(), _pos, )) })?; let value = &mut *source.write_lock::().unwrap(); #[allow(clippy::cast_sign_loss)] { value[*index] = (new_byte & 0x00ff) as u8; } } #[cfg(not(feature = "no_index"))] Self::StringChar { source, value, index, } => { // Replace the character at the specified index position let new_ch = value.as_char().map_err(|err| { Box::new(crate::ERR::ErrorMismatchDataType( "char".to_string(), err.to_string(), _pos, )) })?; let s = &mut *source.write_lock::().unwrap(); *s = s .chars() .enumerate() .map(|(i, ch)| if i == *index { new_ch } else { ch }) .collect(); } #[cfg(not(feature = "no_index"))] Self::StringSlice { source, ref start, ref end, ref value, exclusive, } => { let s = &mut *source.write_lock::().unwrap(); let n = s.chars().count(); let vs = s.chars().take(*start); let ve = if *exclusive { let end = if *end > n { n } else { *end }; s.chars().skip(end) } else if n == 0 { s.chars().skip(usize::MAX) } else { let end = if *end >= n { n - 1 } else { *end }; s.chars().skip(end + 1) }; *s = vs.chain(value.to_string().chars()).chain(ve).collect(); } } Ok(()) } } impl<'a> TryFrom<&'a mut Dynamic> for Target<'a> { type Error = RhaiError; #[inline] fn try_from(value: &'a mut Dynamic) -> Result { #[cfg(not(feature = "no_closure"))] if value.is_shared() { // Cloning is cheap for a shared value let shared_value = value.clone(); let Some(guard) = value.write_lock::() else { return Err( crate::EvalAltResult::ErrorDataRace(String::new(), Position::NONE).into(), ); }; return Ok(Self::SharedValue { guard, shared_value, }); } Ok(Self::RefMut(value)) } } impl AsRef for Target<'_> { #[inline] fn as_ref(&self) -> &Dynamic { match self { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { guard, .. } => guard, Self::TempValue(ref value) => value, #[cfg(not(feature = "no_index"))] Self::StringSlice { ref value, .. } => value, #[cfg(not(feature = "no_index"))] Self::Bit { ref value, .. } | Self::BitField { ref value, .. } | Self::BlobByte { ref value, .. } | Self::StringChar { ref value, .. } => value, } } } impl Borrow for Target<'_> { #[inline(always)] fn borrow(&self) -> &Dynamic { self.as_ref() } } impl AsMut for Target<'_> { #[inline] fn as_mut(&mut self) -> &mut Dynamic { match self { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { guard, .. } => &mut *guard, Self::TempValue(ref mut value) => value, #[cfg(not(feature = "no_index"))] Self::StringSlice { ref mut value, .. } => value, #[cfg(not(feature = "no_index"))] Self::Bit { ref mut value, .. } | Self::BitField { ref mut value, .. } | Self::BlobByte { ref mut value, .. } | Self::StringChar { ref mut value, .. } => value, } } } impl BorrowMut for Target<'_> { #[inline(always)] fn borrow_mut(&mut self) -> &mut Dynamic { self.as_mut() } } impl> From for Target<'_> { #[inline(always)] fn from(value: T) -> Self { Self::TempValue(value.into()) } } rhai-1.21.0/src/func/builtin.rs000064400000000000000000001300721046102023000143710ustar 00000000000000//! Built-in implementations for common operators. #![allow(clippy::float_cmp)] use super::call::FnCallArgs; use super::native::FnBuiltin; #[allow(clippy::enum_glob_use)] use crate::tokenizer::{Token, Token::*}; use crate::{ Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, NativeCallContext, RhaiResult, SmartString, INT, }; use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] use crate::FLOAT; #[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] use num_traits::Float; #[cfg(feature = "decimal")] use rust_decimal::Decimal; /// The `unchecked` feature is not active. const CHECKED_BUILD: bool = cfg!(not(feature = "unchecked")); /// A function that returns `true`. #[inline(always)] #[allow(clippy::unnecessary_wraps)] fn const_true_fn(_: Option, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::TRUE) } /// A function that returns `false`. #[inline(always)] #[allow(clippy::unnecessary_wraps)] fn const_false_fn(_: Option, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::FALSE) } /// Returns true if the type is numeric. #[inline(always)] fn is_numeric(typ: TypeId) -> bool { if typ == TypeId::of::() { return true; } #[cfg(not(feature = "no_float"))] if typ == TypeId::of::() || typ == TypeId::of::() { return true; } #[cfg(feature = "decimal")] if typ == TypeId::of::() { return true; } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() || typ == TypeId::of::() { return true; } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] if typ == TypeId::of::() || typ == TypeId::of::() { return true; } false } /// Build in common binary operator implementations to avoid the cost of calling a registered function. /// /// The return function will be registered as a _method_, so the first parameter cannot be consumed. #[must_use] pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); macro_rules! impl_op { ($xx:ident $op:tt $yy:ident) => { Some((|_, args| { let x = &*args[0].read_lock::<$xx>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap(); Ok((x $op y).into()) }, false)) }; ($xx:ident . $func:ident ( $yy:ty )) => { Some((|_, args| { let x = &*args[0].read_lock::<$xx>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap(); Ok(x.$func(y).into()) }, false)) }; ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { Some((|_, args| { let x = &*args[0].read_lock::<$xx>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap(); Ok(x.$func(y.$yyy()).into()) }, false)) }; ($func:ident ( $op:tt )) => { Some((|_, args| { let (x, y) = $func(args); Ok((x $op y).into()) }, false)) }; ($base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; Ok((x $op y).into()) }, false)) }; ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { Some((|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; Ok(x.$func(y as $yyy).into()) }, false)) }; ($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; Ok($func(x, y).into()) }, false)) }; ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; $func(x, y).map(Into::into) }, false)) }; (from $base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); Ok((x $op y).into()) }, false)) }; (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); Ok(x.$func(y).into()) }, false)) }; (from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); Ok($func(x, y).into()) }, false)) }; (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); $func(x, y).map(Into::into) }, false)) }; } // Check for common patterns if type1 == type2 { if type1 == TypeId::of::() { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] match op { Plus => return impl_op!(INT => add(as_int, as_int)), Minus => return impl_op!(INT => subtract(as_int, as_int)), Multiply => return impl_op!(INT => multiply(as_int, as_int)), Divide => return impl_op!(INT => divide(as_int, as_int)), Modulo => return impl_op!(INT => modulo(as_int, as_int)), PowerOf => return impl_op!(INT => power(as_int, as_int)), RightShift => return impl_op!(INT => Ok(shift_right(as_int, as_int))), LeftShift => return impl_op!(INT => Ok(shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { Plus => return impl_op!(INT => as_int + as_int), Minus => return impl_op!(INT => as_int - as_int), Multiply => return impl_op!(INT => as_int * as_int), Divide => return impl_op!(INT => as_int / as_int), Modulo => return impl_op!(INT => as_int % as_int), PowerOf => return impl_op!(INT => as_int.pow(as_int as u32)), RightShift => { return Some(( |_, args| { let x = args[0].as_int().unwrap(); let y = args[1].as_int().unwrap(); Ok((if y < 0 { x << -y } else { x >> y }).into()) }, false, )) } LeftShift => { return Some(( |_, args| { let x = args[0].as_int().unwrap(); let y = args[1].as_int().unwrap(); Ok((if y < 0 { x >> -y } else { x << y }).into()) }, false, )) } _ => (), } return match op { EqualsTo => impl_op!(INT => as_int == as_int), NotEqualsTo => impl_op!(INT => as_int != as_int), GreaterThan => impl_op!(INT => as_int > as_int), GreaterThanEqualsTo => impl_op!(INT => as_int >= as_int), LessThan => impl_op!(INT => as_int < as_int), LessThanEqualsTo => impl_op!(INT => as_int <= as_int), Ampersand => impl_op!(INT => as_int & as_int), Pipe => impl_op!(INT => as_int | as_int), XOr => impl_op!(INT => as_int ^ as_int), ExclusiveRange => impl_op!(INT => as_int .. as_int), InclusiveRange => impl_op!(INT => as_int ..= as_int), _ => None, }; } if type1 == TypeId::of::() { return match op { EqualsTo => impl_op!(bool => as_bool == as_bool), NotEqualsTo => impl_op!(bool => as_bool != as_bool), GreaterThan => impl_op!(bool => as_bool > as_bool), GreaterThanEqualsTo => impl_op!(bool => as_bool >= as_bool), LessThan => impl_op!(bool => as_bool < as_bool), LessThanEqualsTo => impl_op!(bool => as_bool <= as_bool), Ampersand => impl_op!(bool => as_bool & as_bool), Pipe => impl_op!(bool => as_bool | as_bool), XOr => impl_op!(bool => as_bool ^ as_bool), _ => None, }; } if type1 == TypeId::of::() { return match op { Plus => Some(( |_ctx, args| { let s1 = &*args[0].as_immutable_string_ref().unwrap(); let s2 = &*args[1].as_immutable_string_ref().unwrap(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((0, 0, s1.len() + s2.len()))?; Ok((s1 + s2).into()) }, CHECKED_BUILD, )), Minus => impl_op!(ImmutableString - ImmutableString), EqualsTo => impl_op!(ImmutableString == ImmutableString), NotEqualsTo => impl_op!(ImmutableString != ImmutableString), GreaterThan => impl_op!(ImmutableString > ImmutableString), GreaterThanEqualsTo => impl_op!(ImmutableString >= ImmutableString), LessThan => impl_op!(ImmutableString < ImmutableString), LessThanEqualsTo => impl_op!(ImmutableString <= ImmutableString), _ => None, }; } if type1 == TypeId::of::() { return match op { Plus => Some(( |_ctx, args| { let x = args[0].as_char().unwrap(); let y = args[1].as_char().unwrap(); let mut result = SmartString::new_const(); result.push(x); result.push(y); #[cfg(not(feature = "unchecked"))] _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?; Ok(result.into()) }, CHECKED_BUILD, )), EqualsTo => impl_op!(char => as_char == as_char), NotEqualsTo => impl_op!(char => as_char != as_char), GreaterThan => impl_op!(char => as_char > as_char), GreaterThanEqualsTo => impl_op!(char => as_char >= as_char), LessThan => impl_op!(char => as_char < as_char), LessThanEqualsTo => impl_op!(char => as_char <= as_char), _ => None, }; } #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { use crate::Blob; return match op { Plus => Some(( |_ctx, args| { let b2 = &*args[1].as_blob_ref().unwrap(); if b2.is_empty() { return Ok(args[0].flatten_clone()); } let b1 = &*args[0].as_blob_ref().unwrap(); if b1.is_empty() { return Ok(args[1].flatten_clone()); } #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((b1.len() + b2.len(), 0, 0))?; let mut blob = b1.clone(); blob.extend(b2); Ok(Dynamic::from_blob(blob)) }, CHECKED_BUILD, )), EqualsTo => impl_op!(Blob == Blob), NotEqualsTo => impl_op!(Blob != Blob), _ => None, }; } if type1 == TypeId::of::<()>() { return match op { EqualsTo => Some((const_true_fn, false)), NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { Some((const_false_fn, false)) } _ => None, }; } // Handle ranges here because ranges are implemented as custom type if type1 == TypeId::of::() { return match op { EqualsTo => impl_op!(ExclusiveRange == ExclusiveRange), NotEqualsTo => impl_op!(ExclusiveRange != ExclusiveRange), _ => None, }; } if type1 == TypeId::of::() { return match op { EqualsTo => impl_op!(InclusiveRange == InclusiveRange), NotEqualsTo => impl_op!(InclusiveRange != InclusiveRange), _ => None, }; } } #[cfg(not(feature = "no_float"))] macro_rules! impl_float { ($x:ty, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { Plus => impl_op!(FLOAT => $xx + $yy), Minus => impl_op!(FLOAT => $xx - $yy), Multiply => impl_op!(FLOAT => $xx * $yy), Divide => impl_op!(FLOAT => $xx / $yy), Modulo => impl_op!(FLOAT => $xx % $yy), PowerOf => impl_op!(FLOAT => $xx.powf($yy as FLOAT)), #[cfg(feature = "unchecked")] EqualsTo => impl_op!(FLOAT => $xx == $yy), #[cfg(not(feature = "unchecked"))] EqualsTo => Some((|_, args| { let x = args[0].$xx().unwrap() as FLOAT; let y = args[1].$yy().unwrap() as FLOAT; Ok(((x - y).abs() <= FLOAT::EPSILON).into()) }, false)), #[cfg(feature = "unchecked")] NotEqualsTo => impl_op!(FLOAT => $xx != $yy), #[cfg(not(feature = "unchecked"))] NotEqualsTo => Some((|_, args| { let x = args[0].$xx().unwrap() as FLOAT; let y = args[1].$yy().unwrap() as FLOAT; Ok(((x - y).abs() > FLOAT::EPSILON).into()) }, false)), GreaterThan => impl_op!(FLOAT => $xx > $yy), GreaterThanEqualsTo => impl_op!(FLOAT => $xx >= $yy), LessThan => impl_op!(FLOAT => $xx < $yy), LessThanEqualsTo => impl_op!(FLOAT => $xx <= $yy), _ => None, }; } }; } #[cfg(not(feature = "no_float"))] { impl_float!(FLOAT, as_float, FLOAT, as_float); impl_float!(FLOAT, as_float, INT, as_int); impl_float!(INT, as_int, FLOAT, as_float); } #[cfg(feature = "decimal")] macro_rules! impl_decimal { ($x:ty, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] match op { Plus => return impl_op!(from Decimal => add($xx, $yy)), Minus => return impl_op!(from Decimal => subtract($xx, $yy)), Multiply => return impl_op!(from Decimal => multiply($xx, $yy)), Divide => return impl_op!(from Decimal => divide($xx, $yy)), Modulo => return impl_op!(from Decimal => modulo($xx, $yy)), PowerOf => return impl_op!(from Decimal => power($xx, $yy)), _ => () } #[cfg(feature = "unchecked")] use rust_decimal::MathematicalOps; #[cfg(feature = "unchecked")] match op { Plus => return impl_op!(from Decimal => $xx + $yy), Minus => return impl_op!(from Decimal => $xx - $yy), Multiply => return impl_op!(from Decimal => $xx * $yy), Divide => return impl_op!(from Decimal => $xx / $yy), Modulo => return impl_op!(from Decimal => $xx % $yy), PowerOf => return impl_op!(from Decimal => $xx.powd($yy)), _ => () } return match op { EqualsTo => impl_op!(from Decimal => $xx == $yy), NotEqualsTo => impl_op!(from Decimal => $xx != $yy), GreaterThan => impl_op!(from Decimal => $xx > $yy), GreaterThanEqualsTo => impl_op!(from Decimal => $xx >= $yy), LessThan => impl_op!(from Decimal => $xx < $yy), LessThanEqualsTo => impl_op!(from Decimal => $xx <= $yy), _ => None }; } }; } #[cfg(feature = "decimal")] { impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); impl_decimal!(Decimal, as_decimal, INT, as_int); impl_decimal!(INT, as_int, Decimal, as_decimal); } // Ranges if *op == ExclusiveRange && type1 == TypeId::of::() && type2 == TypeId::of::<()>() { return Some(( |_ctx, args| Ok((args[0].as_int().unwrap()..INT::MAX).into()), false, )); } else if *op == ExclusiveRange && type1 == TypeId::of::<()>() && type2 == TypeId::of::() { return Some(( |_ctx, args| Ok((0..args[1].as_int().unwrap()).into()), false, )); } else if *op == ExclusiveRange && type1 == TypeId::of::() && type2 == TypeId::of::<()>() { return Some(( |_ctx, args| Ok((args[0].as_int().unwrap()..=INT::MAX).into()), false, )); } else if *op == ExclusiveRange && type1 == TypeId::of::<()>() && type2 == TypeId::of::() { return Some(( |_ctx, args| Ok((0..=args[1].as_int().unwrap()).into()), false, )); } // char op string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { fn get_s1s2(args: &FnCallArgs) -> ([Option; 2], [Option; 2]) { let x = args[0].as_char().unwrap(); let y = &*args[1].as_immutable_string_ref().unwrap(); let s1 = [Some(x), None]; let mut y = y.chars(); let s2 = [y.next(), y.next()]; (s1, s2) } return match op { Plus => Some(( |_ctx, args| { let x = args[0].as_char().unwrap(); let y = &*args[1].as_immutable_string_ref().unwrap(); let mut result = SmartString::new_const(); result.push(x); result.push_str(y); #[cfg(not(feature = "unchecked"))] _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?; Ok(result.into()) }, CHECKED_BUILD, )), EqualsTo => impl_op!(get_s1s2(==)), NotEqualsTo => impl_op!(get_s1s2(!=)), GreaterThan => impl_op!(get_s1s2(>)), GreaterThanEqualsTo => impl_op!(get_s1s2(>=)), LessThan => impl_op!(get_s1s2(<)), LessThanEqualsTo => impl_op!(get_s1s2(<=)), _ => None, }; } // string op char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { fn get_s1s2(args: &FnCallArgs) -> ([Option; 2], [Option; 2]) { let x = &*args[0].as_immutable_string_ref().unwrap(); let y = args[1].as_char().unwrap(); let mut x = x.chars(); let s1 = [x.next(), x.next()]; let s2 = [Some(y), None]; (s1, s2) } return match op { Plus => Some(( |_ctx, args| { let x = &*args[0].as_immutable_string_ref().unwrap(); let y = args[1].as_char().unwrap(); let result = x + y; #[cfg(not(feature = "unchecked"))] _ctx.unwrap().engine().throw_on_size((0, 0, result.len()))?; Ok(result.into()) }, CHECKED_BUILD, )), Minus => Some(( |_, args| { let x = &*args[0].as_immutable_string_ref().unwrap(); let y = args[1].as_char().unwrap(); Ok((x - y).into()) }, false, )), EqualsTo => impl_op!(get_s1s2(==)), NotEqualsTo => impl_op!(get_s1s2(!=)), GreaterThan => impl_op!(get_s1s2(>)), GreaterThanEqualsTo => impl_op!(get_s1s2(>=)), LessThan => impl_op!(get_s1s2(<)), LessThanEqualsTo => impl_op!(get_s1s2(<=)), _ => None, }; } // () op string if (type1, type2) == (TypeId::of::<()>(), TypeId::of::()) { return match op { Plus => Some((|_, args| Ok(args[1].clone()), false)), EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { Some((const_false_fn, false)) } NotEqualsTo => Some((const_true_fn, false)), _ => None, }; } // string op () if (type1, type2) == (TypeId::of::(), TypeId::of::<()>()) { return match op { Plus => Some((|_, args| Ok(args[0].clone()), false)), EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { Some((const_false_fn, false)) } NotEqualsTo => Some((const_true_fn, false)), _ => None, }; } // blob #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { if type2 == TypeId::of::() { return match op { Plus => Some(( |_ctx, args| { let mut blob = args[0].as_blob_ref().unwrap().clone(); let mut buf = [0_u8; 4]; let x = args[1].as_char().unwrap().encode_utf8(&mut buf); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((blob.len() + x.len(), 0, 0))?; blob.extend(x.as_bytes()); Ok(Dynamic::from_blob(blob)) }, CHECKED_BUILD, )), _ => None, }; } } // Non-compatible ranges if (type1, type2) == ( TypeId::of::(), TypeId::of::(), ) || (type1, type2) == ( TypeId::of::(), TypeId::of::(), ) { return match op { NotEqualsTo => Some((const_true_fn, false)), Equals => Some((const_false_fn, false)), _ => None, }; } // Default comparison operators for different, non-numeric types if type2 != type1 { return match op { NotEqualsTo if !is_numeric(type1) || !is_numeric(type2) => Some((const_true_fn, false)), EqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo if !is_numeric(type1) || !is_numeric(type2) => { Some((const_false_fn, false)) } _ => None, }; } // Beyond here, type1 == type2 None } /// Build in common operator assignment implementations to avoid the cost of calling a registered function. /// /// The return function is registered as a _method_, so the first parameter cannot be consumed. #[must_use] pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option { let type1 = x.type_id(); let type2 = y.type_id(); macro_rules! impl_op { ($x:ty = x $op:tt $yy:ident) => { Some((|_, args| { let x = args[0].$yy().unwrap(); let y = args[1].$yy().unwrap() as $x; Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into()) }, false)) }; ($x:ident $op:tt $yy:ident) => { Some((|_, args| { let y = args[1].$yy().unwrap() as $x; Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) }, false)) }; ($x:ident $op:tt $yy:ident as $yyy:ty) => { Some((|_, args| { let y = args[1].$yy().unwrap() as $yyy; Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) }, false)) }; ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = args[1].$yy().unwrap() as $x; Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) }, false)) }; ($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = args[1].$yy().unwrap() as $x; let v: Dynamic = $func(x, y).into(); Ok((*args[0].write_lock().unwrap() = v).into()) }, false)) }; ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = args[1].$yy().unwrap() as $x; Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) }, false)) }; (from $x:ident $op:tt $yy:ident) => { Some((|_, args| { let y = <$x>::from(args[1].$yy().unwrap()); Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) }, false)) }; (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = <$x>::from(args[1].$yy().unwrap()); Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) }, false)) }; (from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = <$x>::from(args[1].$yy().unwrap()); Ok((*args[0].write_lock().unwrap() = $func(x, y).into()).into()) }, false)) }; (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| { let x = args[0].$xx().unwrap(); let y = <$x>::from(args[1].$yy().unwrap()); Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) }, false)) }; } // Check for common patterns if type1 == type2 { if type1 == TypeId::of::() { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] match op { PlusAssign => return impl_op!(INT => add(as_int, as_int)), MinusAssign => return impl_op!(INT => subtract(as_int, as_int)), MultiplyAssign => return impl_op!(INT => multiply(as_int, as_int)), DivideAssign => return impl_op!(INT => divide(as_int, as_int)), ModuloAssign => return impl_op!(INT => modulo(as_int, as_int)), PowerOfAssign => return impl_op!(INT => power(as_int, as_int)), RightShiftAssign => return impl_op!(INT => Ok(shift_right(as_int, as_int))), LeftShiftAssign => return impl_op!(INT => Ok(shift_left(as_int, as_int))), _ => (), } #[cfg(feature = "unchecked")] match op { PlusAssign => return impl_op!(INT += as_int), MinusAssign => return impl_op!(INT -= as_int), MultiplyAssign => return impl_op!(INT *= as_int), DivideAssign => return impl_op!(INT /= as_int), ModuloAssign => return impl_op!(INT %= as_int), PowerOfAssign => return impl_op!(INT => as_int.pow(as_int as u32)), RightShiftAssign => { return Some(( |_, args| { let x = args[0].as_int().unwrap(); let y = args[1].as_int().unwrap(); let v = if y < 0 { x << -y } else { x >> y }; *args[0].write_lock::().unwrap() = v.into(); Ok(Dynamic::UNIT) }, false, )) } LeftShiftAssign => { return Some(( |_, args| { let x = args[0].as_int().unwrap(); let y = args[1].as_int().unwrap(); let v = if y < 0 { x >> -y } else { x << y }; *args[0].write_lock::().unwrap() = v.into(); Ok(Dynamic::UNIT) }, false, )) } _ => (), } return match op { AndAssign => impl_op!(INT &= as_int), OrAssign => impl_op!(INT |= as_int), XOrAssign => impl_op!(INT ^= as_int), _ => None, }; } if type1 == TypeId::of::() { return match op { AndAssign => impl_op!(bool = x && as_bool), OrAssign => impl_op!(bool = x || as_bool), XOrAssign => impl_op!(bool = x ^ as_bool), _ => None, }; } if type1 == TypeId::of::() { return match op { PlusAssign => Some(( |_, args| { let y = args[1].as_char().unwrap(); let x = &mut *args[0].write_lock::().unwrap(); let mut buf = SmartString::new_const(); buf.push(x.as_char().unwrap()); buf.push(y); *x = buf.into(); Ok(Dynamic::UNIT) }, false, )), _ => None, }; } if type1 == TypeId::of::() { return match op { PlusAssign => Some(( |_ctx, args| { let (first, second) = args.split_first_mut().unwrap(); let x = &mut *first.as_immutable_string_mut().unwrap(); let y = &*second[0].as_immutable_string_ref().unwrap(); #[cfg(not(feature = "unchecked"))] if !x.is_empty() && !y.is_empty() { let total_len = x.len() + y.len(); _ctx.unwrap().engine().throw_on_size((0, 0, total_len))?; } *x += y; Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), MinusAssign => Some(( |_, args| { let (first, second) = args.split_first_mut().unwrap(); let x = &mut *first.as_immutable_string_mut().unwrap(); let y = &*second[0].as_immutable_string_ref().unwrap(); *x -= y; Ok(Dynamic::UNIT) }, false, )), _ => None, }; } #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { #[allow(clippy::wildcard_imports)] use crate::packages::array_basic::array_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { let x = args[1].take().into_array().unwrap(); if x.is_empty() { return Ok(Dynamic::UNIT); } #[cfg(not(feature = "unchecked"))] if !args[0].as_array_ref().unwrap().is_empty() { _ctx.unwrap().engine().check_data_size( &*args[0].read_lock().unwrap(), crate::Position::NONE, )?; } let array = &mut *args[0].as_array_mut().unwrap(); append(array, x); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { let blob2 = args[1].take().into_blob().unwrap(); let blob1 = &mut *args[0].as_blob_mut().unwrap(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((blob1.len() + blob2.len(), 0, 0))?; append(blob1, blob2); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } } #[cfg(not(feature = "no_float"))] macro_rules! impl_float { ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { PlusAssign => impl_op!($x += $yy), MinusAssign => impl_op!($x -= $yy), MultiplyAssign => impl_op!($x *= $yy), DivideAssign => impl_op!($x /= $yy), ModuloAssign => impl_op!($x %= $yy), PowerOfAssign => impl_op!($x => $xx.powf($yy as $x)), _ => None, }; } } } #[cfg(not(feature = "no_float"))] { impl_float!(FLOAT, as_float, FLOAT, as_float); impl_float!(FLOAT, as_float, INT, as_int); } #[cfg(feature = "decimal")] macro_rules! impl_decimal { ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] return match op { PlusAssign => impl_op!(from $x => add($xx, $yy)), MinusAssign => impl_op!(from $x => subtract($xx, $yy)), MultiplyAssign => impl_op!(from $x => multiply($xx, $yy)), DivideAssign => impl_op!(from $x => divide($xx, $yy)), ModuloAssign => impl_op!(from $x => modulo($xx, $yy)), PowerOfAssign => impl_op!(from $x => power($xx, $yy)), _ => None, }; #[cfg(feature = "unchecked")] use rust_decimal::MathematicalOps; #[cfg(feature = "unchecked")] return match op { PlusAssign => impl_op!(from $x += $yy), MinusAssign => impl_op!(from $x -= $yy), MultiplyAssign => impl_op!(from $x *= $yy), DivideAssign => impl_op!(from $x /= $yy), ModuloAssign => impl_op!(from $x %= $yy), PowerOfAssign => impl_op!(from $x => $xx.powd($yy)), _ => None, }; } }; } #[cfg(feature = "decimal")] { impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); impl_decimal!(Decimal, as_decimal, INT, as_int); } // string op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { PlusAssign => Some(( |_ctx, args| { let mut buf = [0_u8; 4]; let ch = &*args[1].as_char().unwrap().encode_utf8(&mut buf); let mut x = args[0].as_immutable_string_mut().unwrap(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((0, 0, x.len() + ch.len()))?; *x += ch; Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), MinusAssign => impl_op!(ImmutableString -= as_char as char), _ => None, }; } // char op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { return match op { PlusAssign => Some(( |_ctx, args| { let ch = { let s = &*args[1].as_immutable_string_ref().unwrap(); if s.is_empty() { return Ok(Dynamic::UNIT); } let mut ch = args[0].as_char().unwrap().to_string(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((0, 0, ch.len() + s.len()))?; ch += s; ch }; *args[0].write_lock::().unwrap() = ch.into(); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } // array op= any #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { #[allow(clippy::wildcard_imports)] use crate::packages::array_basic::array_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { { let x = args[1].take(); let array = &mut *args[0].as_array_mut().unwrap(); push(array, x); } #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .check_data_size(&*args[0].read_lock().unwrap(), crate::Position::NONE)?; Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } #[cfg(not(feature = "no_index"))] { use crate::Blob; // blob op= int if (type1, type2) == (TypeId::of::(), TypeId::of::()) { #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { let x = args[1].as_int().unwrap(); let blob = &mut *args[0].as_blob_mut().unwrap(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((blob.len() + 1, 0, 0))?; push(blob, x); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } // blob op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { let x = args[1].as_char().unwrap(); let blob = &mut *args[0].as_blob_mut().unwrap(); #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((blob.len() + 1, 0, 0))?; append_char(blob, x); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } // blob op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { PlusAssign => Some(( |_ctx, args| { let (first, second) = args.split_first_mut().unwrap(); let blob = &mut *first.as_blob_mut().unwrap(); let s = &*second[0].as_immutable_string_ref().unwrap(); if s.is_empty() { return Ok(Dynamic::UNIT); } #[cfg(not(feature = "unchecked"))] _ctx.unwrap() .engine() .throw_on_size((blob.len() + s.len(), 0, 0))?; append_str(blob, s); Ok(Dynamic::UNIT) }, CHECKED_BUILD, )), _ => None, }; } } None } rhai-1.21.0/src/func/call.rs000064400000000000000000002470221046102023000136420ustar 00000000000000//! Implement function-calling mechanism for [`Engine`]. use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn, RhaiFunc}; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; use crate::ast::{Expr, FnCallExpr, FnCallHashes}; use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; use crate::tokenizer::{is_valid_function_name, Token}; use crate::types::{dynamic::Union, fn_ptr::FnPtrType}; use crate::{ calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position, RhaiResult, RhaiResultOf, Scope, Shared, SmartString, ERR, }; #[cfg(feature = "no_std")] use hashbrown::hash_map::Entry; #[cfg(not(feature = "no_std"))] use std::collections::hash_map::Entry; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, convert::TryFrom, mem, }; #[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] use num_traits::Float; #[cfg(not(feature = "no_float"))] use crate::FLOAT; /// Arguments to a function call, which is a list of [`&mut Dynamic`][Dynamic]. pub type FnCallArgs<'a> = [&'a mut Dynamic]; /// A type that temporarily stores a mutable reference to a `Dynamic`, /// replacing it with a cloned copy. #[derive(Debug)] struct ArgBackup<'a> { orig_mut: Option<&'a mut Dynamic>, value_copy: Dynamic, } impl<'a> ArgBackup<'a> { /// Create a new `ArgBackup`. #[inline(always)] pub fn new() -> Self { Self { orig_mut: None, value_copy: Dynamic::UNIT, } } /// This function replaces the first argument of a method call with a clone copy. /// This is to prevent a pure function unintentionally consuming the first argument. /// /// `restore_first_arg` must be called before the end of the scope to prevent the shorter /// lifetime from leaking. /// /// # Safety /// /// This method blindly casts a reference to another lifetime, which saves allocation and /// string cloning. /// /// As long as `restore_first_arg` is called before the end of the scope, the shorter lifetime /// will not leak. /// /// # Panics /// /// Panics when `args` is empty. #[inline(always)] pub fn change_first_arg_to_copy(&mut self, args: &mut FnCallArgs<'a>) { // Clone the original value. self.value_copy = args[0].clone(); // Replace the first reference with a reference to the clone, force-casting the lifetime. // Must remember to restore it later with `restore_first_arg`. // // SAFETY: // // Blindly casting a reference to another lifetime saves allocation and string cloning, // but must be used with the utmost care. // // We can do this here because, before the end of this scope, we'd restore the original // reference via `restore_first_arg`. Therefore this shorter lifetime does not leak. self.orig_mut = Some(mem::replace(&mut args[0], unsafe { mem::transmute(&mut self.value_copy) })); } /// This function restores the first argument that was replaced by `change_first_arg_to_copy`. /// /// # Safety /// /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ /// exiting the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. #[inline(always)] pub fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { args[0] = self.orig_mut.take().unwrap(); } } impl Drop for ArgBackup<'_> { #[inline(always)] fn drop(&mut self) { // Panic if the shorter lifetime leaks. assert!( self.orig_mut.is_none(), "ArgBackup::restore_first_arg has not been called prior to existing this scope" ); } } // Ensure no data races in function call arguments. #[cfg(not(feature = "no_closure"))] #[inline] pub fn ensure_no_data_race(fn_name: &str, args: &FnCallArgs, is_ref_mut: bool) -> RhaiResultOf<()> { args.iter() .skip(usize::from(is_ref_mut)) .position(|a| a.is_locked()) .map_or(Ok(()), |n| { Err(ERR::ErrorDataRace( format!("argument #{} of function '{fn_name}'", n + 1), Position::NONE, ) .into()) }) } /// Is a function name an anonymous function? #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub fn is_anonymous_fn(name: &str) -> bool { name.starts_with(crate::engine::FN_ANONYMOUS) } impl Engine { /// Generate the signature for a function call. #[inline] #[must_use] fn gen_fn_call_signature(&self, fn_name: &str, args: &[&mut Dynamic]) -> String { format!( "{fn_name} ({})", args.iter() .map(|a| if a.is_string() { "&str | ImmutableString | String" } else { self.map_type_name(a.type_name()) }) .collect::>() .join(", ") ) } /// Resolve a normal (non-qualified) function call. /// /// Search order: /// 1) AST - script functions in the AST /// 2) Global namespace - functions registered via `Engine::register_XXX` /// 3) Global registered modules - packages /// 4) Imported modules - functions marked with global namespace /// 5) Static registered modules #[must_use] fn resolve_fn<'s>( &self, _global: &GlobalRuntimeState, caches: &'s mut Caches, local_entry: &'s mut Option, op_token: Option<&Token>, hash_base: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, ) -> Option<&'s FnResolutionCacheEntry> { let mut hash = args.as_deref().map_or(hash_base, |args| { calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id())) }); let cache = caches.fn_resolution_cache_mut(); match cache.dict.entry(hash) { Entry::Occupied(entry) => entry.into_mut().as_ref(), Entry::Vacant(entry) => { let num_args = args.as_deref().map_or(0, FnCallArgs::len); let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. // Set later when a specific matching function is not found. let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` loop { // First check scripted functions in the AST or embedded environments #[cfg(not(feature = "no_function"))] let func = _global .lib .iter() .rev() .find_map(|m| m.get_fn(hash).map(|f| (f, m.id_raw()))); #[cfg(feature = "no_function")] let func = None; // Then check the global namespace let func = func.or_else(|| { self.global_modules .iter() .find_map(|m| m.get_fn(hash).map(|f| (f, m.id_raw()))) }); // Then check imported modules for global functions, then global sub-modules for global functions #[cfg(not(feature = "no_module"))] let func = func .or_else(|| _global.get_qualified_fn(hash, true)) .or_else(|| { self.global_sub_modules .values() .filter(|m| m.contains_indexed_global_functions()) .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) }); if let Some((f, s)) = func { // Specific version found let new_entry = FnResolutionCacheEntry { func: f.clone(), source: s.cloned(), }; return if cache.bloom_filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" *local_entry = Some(new_entry); local_entry.as_ref() } else { // Cache entry entry.insert(Some(new_entry)).as_ref() }; } // Check `Dynamic` parameters for functions with parameters let max_dynamic_count = usize::min(num_args, MAX_DYNAMIC_PARAMETERS); if allow_dynamic && max_bitmask == 0 && num_args > 0 { let has_dynamic = self .global_modules .iter() .any(|m| m.may_contain_dynamic_fn(hash_base)); #[cfg(not(feature = "no_function"))] let has_dynamic = has_dynamic || _global .lib .iter() .any(|m| m.may_contain_dynamic_fn(hash_base)); #[cfg(not(feature = "no_module"))] let has_dynamic = has_dynamic || _global.may_contain_dynamic_fn(hash_base) || self .global_sub_modules .values() .any(|m| m.may_contain_dynamic_fn(hash_base)); // Set maximum bitmask when there are dynamic versions of the function if has_dynamic { max_bitmask = 1usize << max_dynamic_count; } } // Stop when all permutations are exhausted if bitmask >= max_bitmask { if num_args != 2 { return None; } // Try to find a built-in version let builtin = args.and_then(|args| match op_token { None => None, Some(token) if token.is_op_assignment() => { let (first_arg, rest_args) = args.split_first().unwrap(); get_builtin_op_assignment_fn(token, first_arg, rest_args[0]) .map(|(f, has_context)| FnResolutionCacheEntry { func: RhaiFunc::Method { func: Shared::new(f), has_context, is_pure: false, is_volatile: false, }, source: None, }) } Some(token) => get_builtin_binary_op_fn(token, args[0], args[1]) .map(|(f, has_context)| FnResolutionCacheEntry { func: RhaiFunc::Method { func: Shared::new(f), has_context, is_pure: true, is_volatile: false, }, source: None, }), }); return if cache.bloom_filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" *local_entry = builtin; local_entry.as_ref() } else { // Cache entry entry.insert(builtin).as_ref() }; } // Try all permutations with `Dynamic` wildcards hash = calc_fn_hash_full( hash_base, args.as_ref().unwrap().iter().enumerate().map(|(i, a)| { if i < max_dynamic_count && bitmask & (1usize << (max_dynamic_count - i - 1)) != 0 { // Replace with `Dynamic` TypeId::of::() } else { a.type_id() } }), ); bitmask += 1; } } } } /// # Main Entry-Point (Native by Name) /// /// Call a native Rust function registered with the [`Engine`] by name. /// /// # WARNING /// /// Function call arguments be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// /// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn exec_native_fn_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, name: &str, op_token: Option<&Token>, hash: u64, args: &mut FnCallArgs, is_ref_mut: bool, non_volatile_only: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { self.track_operation(global, pos)?; // Check if function access already in the cache let local_entry = &mut None; let a = Some(&mut *args); let func = self.resolve_fn(global, caches, local_entry, op_token, hash, a, true); if let Some(FnResolutionCacheEntry { func, source }) = func { debug_assert!(func.is_native()); if non_volatile_only && func.is_volatile() { let gen_fn_call_signature = self.gen_fn_call_signature(name, args); return Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature, pos).into()); } let is_method = func.is_method(); // Push a new call stack frame #[cfg(feature = "debugging")] let orig_call_stack_len = global .debugger .as_ref() .map_or(0, |dbg| dbg.call_stack().len()); let backup = &mut ArgBackup::new(); // Calling non-method function but the first argument is a reference? let swap = is_ref_mut && !is_method && !args.is_empty(); if swap { // Clone the first argument backup.change_first_arg_to_copy(args); } #[cfg(feature = "debugging")] if self.is_debugger_registered() { let source = source.clone().or_else(|| global.source.clone()); global.debugger_mut().push_call_stack_frame( self.get_interned_string(name), args.iter().map(|v| (*v).clone()), source, pos, ); } // Run external function let context = func .has_context() .then(|| (self, name, source.as_deref(), &*global, pos).into()); let mut _result = match func { // If function is not pure, there must be at least one argument f if !f.is_pure() && !args.is_empty() && args[0].is_read_only() => { Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) } RhaiFunc::Plugin { func } => func.call(context, args), RhaiFunc::Pure { func, .. } | RhaiFunc::Method { func, .. } => func(context, args), _ => unreachable!("non-native function"), } .and_then(|r| self.check_data_size(r, pos)) .map_err(|err| err.fill_position(pos)); if swap { backup.restore_first_arg(args); } #[cfg(feature = "debugging")] if self.is_debugger_registered() { use crate::eval::{DebuggerEvent, DebuggerStatus}; let trigger = match global.debugger().status { DebuggerStatus::FunctionExit(n) => n >= global.level, DebuggerStatus::Next(.., true) => true, _ => false, }; if trigger { let scope = &mut Scope::new(); let node = crate::ast::Stmt::Noop(pos); let node = (&node).into(); let event = match _result { Ok(ref r) => DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => DebuggerEvent::FunctionExitWithError(err), }; match self.dbg_raw(global, caches, scope, None, node, event) { Ok(..) => (), Err(err) => _result = Err(err), } } // Pop the call stack global.debugger_mut().rewind_call_stack(orig_call_stack_len); } let result = _result?; // Check the data size of any `&mut` object, which may be changed. #[cfg(not(feature = "unchecked"))] if is_ref_mut && !args.is_empty() { self.check_data_size(&*args[0], pos)?; } // See if the function match print/debug (which requires special processing) return Ok(match name { KEYWORD_PRINT => { if let Some(ref print) = self.print { let text = result.into_immutable_string().map_err(|typ| { let t = self.map_type_name(type_name::()).into(); ERR::ErrorMismatchOutputType(t, typ.into(), pos) })?; print(&text); } (Dynamic::UNIT, false) } KEYWORD_DEBUG => { if let Some(ref debug) = self.debug { let text = result.into_immutable_string().map_err(|typ| { let t = self.map_type_name(type_name::()).into(); ERR::ErrorMismatchOutputType(t, typ.into(), pos) })?; debug(&text, global.source(), pos); } (Dynamic::UNIT, false) } _ => (result, is_method), }); } // Error handling match name { // index getter function not found? #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] crate::engine::FN_IDX_GET => { debug_assert_eq!(args.len(), 2); let t0 = self.map_type_name(args[0].type_name()); let t1 = self.map_type_name(args[1].type_name()); Err(ERR::ErrorIndexingType(format!("{t0} [{t1}]"), pos).into()) } // index setter function not found? #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] crate::engine::FN_IDX_SET => { debug_assert_eq!(args.len(), 3); let t0 = self.map_type_name(args[0].type_name()); let t1 = self.map_type_name(args[1].type_name()); let t2 = self.map_type_name(args[2].type_name()); Err(ERR::ErrorIndexingType(format!("{t0} [{t1}] = {t2}"), pos).into()) } // Getter function not found? #[cfg(not(feature = "no_object"))] _ if name.starts_with(crate::engine::FN_GET) => { debug_assert_eq!(args.len(), 1); let prop = &name[crate::engine::FN_GET.len()..]; let t0 = self.map_type_name(args[0].type_name()); Err(ERR::ErrorDotExpr( format!( "Unknown property '{prop}' - a getter is not registered for type '{t0}'" ), pos, ) .into()) } // Setter function not found? #[cfg(not(feature = "no_object"))] _ if name.starts_with(crate::engine::FN_SET) => { debug_assert_eq!(args.len(), 2); let prop = &name[crate::engine::FN_SET.len()..]; let t0 = self.map_type_name(args[0].type_name()); let t1 = self.map_type_name(args[1].type_name()); Err(ERR::ErrorDotExpr( format!( "No writable property '{prop}' - a setter is not registered for type '{t0}' to handle '{t1}'" ), pos, ) .into()) } // Raise error _ => { Err(ERR::ErrorFunctionNotFound(self.gen_fn_call_signature(name, args), pos).into()) } } } /// # Main Entry-Point (By Name) /// /// Perform an actual function call, native Rust or scripted, by name, taking care of special functions. /// /// # WARNING /// /// Function call arguments may be _consumed_ when the function requires them to be passed by /// value. All function arguments not in the first position are always passed by value and thus consumed. /// /// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn exec_fn_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, _scope: Option<&mut Scope>, fn_name: &str, op_token: Option<&Token>, hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, _is_method_call: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { // These may be redirected from method style calls. if hashes.is_native_only() && match fn_name { // Handle type_of() KEYWORD_TYPE_OF if args.len() == 1 => { let typ = self.get_interned_string(self.map_type_name(args[0].type_name())); return Ok((typ.into(), false)); } #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { return Ok((args[0].is_shared().into(), false)) } #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED => true, #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN => true, KEYWORD_TYPE_OF | KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, _ => false, } { let sig = self.gen_fn_call_signature(fn_name, args); return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); } // Check for data race. #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; defer! { let orig_level = global.level; global.level += 1 } // Script-defined function call? #[cfg(not(feature = "no_function"))] if !hashes.is_native_only() { let hash = hashes.script(); let local_entry = &mut None; let mut resolved = None; #[cfg(not(feature = "no_object"))] if _is_method_call && !args.is_empty() { let typed_hash = crate::calc_typed_method_hash(hash, self.map_type_name(args[0].type_name())); resolved = self.resolve_fn(global, caches, local_entry, None, typed_hash, None, false); } if resolved.is_none() { resolved = self.resolve_fn(global, caches, local_entry, None, hash, None, false); } if let Some(FnResolutionCacheEntry { func, source }) = resolved.cloned() { let RhaiFunc::Script { fn_def, env } = func else { unreachable!("Script function expected"); }; let fn_def = &*fn_def; let env = env.as_deref(); if fn_def.body.is_empty() { return Ok((Dynamic::UNIT, false)); } let mut empty_scope; let scope = if let Some(scope) = _scope { scope } else { empty_scope = Scope::new(); &mut empty_scope }; let orig_source = mem::replace(&mut global.source, source); defer! { global => move |g| g.source = orig_source } return if _is_method_call { // Method call of script function - map first argument to `this` let (first_arg, args) = args.split_first_mut().unwrap(); let this_ptr = Some(&mut **first_arg); self.call_script_fn( global, caches, scope, this_ptr, env, fn_def, args, true, pos, ) } else { // Normal call of script function let backup = &mut ArgBackup::new(); // The first argument is a reference? let swap = is_ref_mut && !args.is_empty(); if swap { backup.change_first_arg_to_copy(args); } defer! { args = (args) if swap => move |a| backup.restore_first_arg(a) } self.call_script_fn(global, caches, scope, None, env, fn_def, args, true, pos) } .map(|r| (r, false)); } } // Native function call let hash = hashes.native(); self.exec_native_fn_call( global, caches, fn_name, op_token, hash, args, is_ref_mut, false, pos, ) } /// Evaluate an argument. #[inline] pub(crate) fn get_arg_value( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, this_ptr: Option<&mut Dynamic>, arg_expr: &Expr, ) -> RhaiResultOf<(Dynamic, Position)> { // Literal values if let Some(value) = arg_expr.get_literal_value() { self.track_operation(global, arg_expr.start_position())?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr, arg_expr)?; return Ok((value, arg_expr.start_position())); } // Do not match function exit for arguments #[cfg(feature = "debugging")] let reset = global.debugger.as_deref_mut().and_then(|dbg| { dbg.clear_status_if(|status| { matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) }) }); #[cfg(feature = "debugging")] defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } self.eval_expr(global, caches, scope, this_ptr, arg_expr) .map(|r| (r, arg_expr.start_position())) } /// Call a dot method. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, fn_name: &str, mut hash: FnCallHashes, target: &mut crate::eval::Target, call_args: &mut [Dynamic], first_arg_pos: Position, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { let (result, updated) = match fn_name { // Handle fn_ptr.call(...) KEYWORD_FN_PTR_CALL if target.as_ref().is_fnptr() => { let fn_ptr = target.as_ref().read_lock::().unwrap(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Arguments are passed as-is, adding the curried arguments let mut args = curry .iter_mut() .chain(call_args.iter_mut()) .collect::>(); match fn_ptr.typ { // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] FnPtrType::Script(ref fn_def) if fn_def.params.len() == args.len() => { let fn_ptr = target.as_ref().read_lock::().unwrap(); let scope = &mut Scope::new(); let env = fn_ptr.env.as_ref().map(<_>::as_ref); defer! { let orig_level = global.level; global.level += 1 } self.call_script_fn( global, caches, scope, None, env, &*fn_def, &mut args, true, pos, ) .map(|v| (v, false)) } // Native function - short-circuit FnPtrType::Native(ref func) => { defer! { let orig_level = global.level; global.level += 1 } let context = (self, fn_name, None, &*global, pos).into(); func(context, &mut args) .and_then(|r| self.check_data_size(r, pos)) .map(|v| (v, false)) .map_err(|err| err.fill_position(pos)) } // Normal call _ => { let fn_name = fn_ptr.fn_name(); let _is_anon = false; #[cfg(not(feature = "no_function"))] let _is_anon = fn_ptr.is_anonymous(); // Recalculate hashes let new_hash = if !_is_anon && !is_valid_function_name(fn_name) { FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args.len())) } else { FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args.len())) }; // Map it to name(args) in function-call style self.exec_fn_call( global, caches, None, fn_name, None, new_hash, &mut args, false, false, pos, ) } } } // Handle obj.call(fn_ptr, ...) KEYWORD_FN_PTR_CALL => { if call_args.is_empty() { return Err(self.make_type_mismatch_err::( self.map_type_name(target.as_ref().type_name()), pos, )); } // FnPtr call on object let fn_ptr = call_args[0] .take() .try_cast_result::() .map_err(|v| { self.make_type_mismatch_err::( self.map_type_name(v.type_name()), first_arg_pos, ) })?; let _is_anon = false; #[cfg(not(feature = "no_function"))] let _is_anon = fn_ptr.is_anonymous(); let FnPtr { name, curry, #[cfg(not(feature = "no_function"))] env, typ, } = fn_ptr; // Adding the curried arguments and the remaining arguments let mut curry = curry.into_iter().collect::>(); let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len()); args.extend(curry.iter_mut()); args.extend(call_args.iter_mut().skip(1)); match typ { // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] FnPtrType::Script(fn_def) if fn_def.params.len() == args.len() => { // Check for data race. #[cfg(not(feature = "no_closure"))] ensure_no_data_race(&fn_def.name, args, false)?; let scope = &mut Scope::new(); let this_ptr = Some(target.as_mut()); let env = env.as_deref(); defer! { let orig_level = global.level; global.level += 1 } self.call_script_fn( global, caches, scope, this_ptr, env, &fn_def, args, true, pos, ) .map(|v| (v, false)) } // Native function - short-circuit FnPtrType::Native(ref func) => { defer! { let orig_level = global.level; global.level += 1 } let context = (self, fn_name, None, &*global, pos).into(); // Add the first argument with the object pointer args.insert(0, target.as_mut()); func(context, args) .and_then(|r| self.check_data_size(r, pos)) .map(|v| (v, false)) .map_err(|err| err.fill_position(pos)) } // Normal call _ => { let is_ref_mut = target.is_ref(); // Add the first argument with the object pointer args.insert(0, target.as_mut()); // Recalculate hash let num_args = args.len(); let new_hash = if !_is_anon && !is_valid_function_name(&name) { FnCallHashes::from_native_only(calc_fn_hash(None, &name, num_args)) } else { #[cfg(not(feature = "no_function"))] { FnCallHashes::from_script_and_native( calc_fn_hash(None, &name, num_args - 1), calc_fn_hash(None, &name, num_args), ) } #[cfg(feature = "no_function")] { FnCallHashes::from_native_only(calc_fn_hash(None, &name, num_args)) } }; // Map it to name(args) in function-call style self.exec_fn_call( global, caches, None, &name, None, new_hash, args, is_ref_mut, true, pos, ) } } } // Handle fn_ptr.curry(...) KEYWORD_FN_PTR_CURRY => { let typ = target.as_ref().type_name(); let mut fn_ptr = target .as_ref() .read_lock::() .ok_or_else(|| { self.make_type_mismatch_err::(self.map_type_name(typ), pos) })? .clone(); // Append the new curried arguments to the existing list. fn_ptr.extend(call_args.iter_mut().map(mem::take)); Ok((fn_ptr.into(), false)) } // Handle var.is_shared() #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if call_args.is_empty() => { return Ok((target.is_shared().into(), false)); } _ => { let mut fn_name = fn_name; let _redirected; let mut _linked = None; let mut _arg_values; let mut call_args = call_args; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] if let Ok(map) = target.as_ref().as_map_ref() { if let Some(val) = map.get(fn_name) { if let Some(fn_ptr) = val.read_lock::() { // Remap the function name _redirected = fn_ptr.fn_name_raw().clone(); fn_name = &_redirected; // Add curried arguments if fn_ptr.is_curried() { _arg_values = fn_ptr .curry() .iter() .cloned() .chain(call_args.iter_mut().map(mem::take)) .collect::>(); call_args = &mut _arg_values; } match fn_ptr.typ { // Linked to scripted function #[cfg(not(feature = "no_function"))] FnPtrType::Script(ref fn_def) if fn_def.params.len() == call_args.len() => { _linked = Some((Some(fn_def.clone()), None, fn_ptr.env.clone())) } FnPtrType::Native(ref func) => { _linked = Some(( #[cfg(not(feature = "no_function"))] None, #[cfg(feature = "no_function")] Option::<()>::None, Some(func.clone()), #[cfg(not(feature = "no_function"))] fn_ptr.env.clone(), #[cfg(feature = "no_function")] Option::<()>::None, )) } _ => { let _is_anon = false; #[cfg(not(feature = "no_function"))] let _is_anon = fn_ptr.is_anonymous(); // Recalculate the hash based on the new function name and new arguments let num_args = call_args.len() + 1; hash = if !_is_anon && !is_valid_function_name(fn_name) { FnCallHashes::from_native_only(calc_fn_hash( None, fn_name, num_args, )) } else { #[cfg(not(feature = "no_function"))] { FnCallHashes::from_script_and_native( calc_fn_hash(None, fn_name, num_args - 1), calc_fn_hash(None, fn_name, num_args), ) } #[cfg(feature = "no_function")] { FnCallHashes::from_native_only(calc_fn_hash( None, fn_name, num_args, )) } }; } } } } } match _linked { // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] Some((Some(fn_def), None, env)) => { let scope = &mut Scope::new(); let env = env.as_deref(); let this_ptr = Some(target.as_mut()); let args = &mut call_args.iter_mut().collect::>(); defer! { let orig_level = global.level; global.level += 1 } self.call_script_fn( global, caches, scope, this_ptr, env, &fn_def, args, true, pos, ) .map(|v| (v, false)) } // Native function - short-circuit Some((None, Some(func), ..)) => { // Attached object pointer in front of the arguments let args = &mut std::iter::once(target.as_mut()) .chain(call_args.iter_mut()) .collect::>(); defer! { let orig_level = global.level; global.level += 1 } let context = (self, fn_name, None, &*global, pos).into(); func(context, args) .and_then(|r| self.check_data_size(r, pos)) .map(|v| (v, false)) .map_err(|err| err.fill_position(pos)) } // Normal call None => { let is_ref_mut = target.is_ref(); // Attached object pointer in front of the arguments let args = &mut std::iter::once(target.as_mut()) .chain(call_args.iter_mut()) .collect::>(); self.exec_fn_call( global, caches, None, fn_name, None, hash, args, is_ref_mut, true, pos, ) } _ => unreachable!(), } } }?; // Propagate the changed value back to the source if necessary if updated { target.propagate_changed_value(pos)?; } Ok((result, updated)) } /// Call a function in normal function-call style. pub(crate) fn make_function_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, fn_name: &str, op_token: Option<&Token>, first_arg: Option<&Expr>, args_expr: &[Expr], hashes: FnCallHashes, capture_scope: bool, pos: Position, ) -> RhaiResult { let mut first_arg = first_arg; let mut args_expr = args_expr; let mut num_args = usize::from(first_arg.is_some()) + args_expr.len(); let mut curry = FnArgsVec::new_const(); let mut fn_name = fn_name; let mut hashes = hashes; let redirected; // Handle call() - Redirect function call match fn_name { _ if op_token.is_some() => (), // Handle call(fn_ptr, ...) KEYWORD_FN_PTR_CALL if num_args >= 1 => { let arg = first_arg.unwrap(); let (first_arg_value, first_arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; let fn_ptr = first_arg_value.try_cast_result::().map_err(|v| { self.make_type_mismatch_err::( self.map_type_name(v.type_name()), first_arg_pos, ) })?; let _is_anon = false; #[cfg(not(feature = "no_function"))] let _is_anon = fn_ptr.is_anonymous(); let FnPtr { name, curry: extra_curry, #[cfg(not(feature = "no_function"))] env, typ, } = fn_ptr; curry.extend(extra_curry); match typ { // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] FnPtrType::Script(fn_def) if fn_def.params.len() == curry.len() + args_expr.len() => { // Evaluate arguments let mut arg_values = FnArgsVec::with_capacity(curry.len() + args_expr.len()); arg_values.extend(curry); for expr in args_expr { let this_ptr = this_ptr.as_deref_mut(); let (value, _) = self.get_arg_value(global, caches, scope, this_ptr, expr)?; arg_values.push(value); } let args = &mut arg_values.iter_mut().collect::>(); let scope = &mut Scope::new(); let env = env.as_deref(); defer! { let orig_level = global.level; global.level += 1 } return self.call_script_fn( global, caches, scope, None, env, &fn_def, args, true, pos, ); } // Native function - short-circuit FnPtrType::Native(ref func) => { // Evaluate arguments let mut arg_values = FnArgsVec::with_capacity(curry.len() + args_expr.len()); arg_values.extend(curry); for expr in args_expr { let this_ptr = this_ptr.as_deref_mut(); let (value, _) = self.get_arg_value(global, caches, scope, this_ptr, expr)?; arg_values.push(value); } let args = &mut arg_values.iter_mut().collect::>(); defer! { let orig_level = global.level; global.level += 1 } let context = (self, fn_name, None, &*global, pos).into(); return func(context, args) .and_then(|r| self.check_data_size(r, pos)) .map_err(|err| err.fill_position(pos)); } // Normal function call _ => (), } // Redirect function name redirected = name; fn_name = &redirected; // Shift the arguments first_arg = args_expr.first(); if !args_expr.is_empty() { args_expr = &args_expr[1..]; } num_args -= 1; // Recalculate hash let args_len = num_args + curry.len(); hashes = if !_is_anon && !is_valid_function_name(fn_name) { FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args_len)) } else { FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args_len)) }; } // Handle Fn(fn_name) KEYWORD_FN_PTR if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, arg)?; // Fn - only in function call style return arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos)) .and_then(FnPtr::try_from) .map(Into::into) .map_err(|err| err.fill_position(arg_pos)); } // Handle curry(x, ...) KEYWORD_FN_PTR_CURRY if num_args > 1 => { let first = first_arg.unwrap(); let (first_arg_value, first_arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; let mut fn_ptr = first_arg_value.try_cast_result::().map_err(|v| { self.make_type_mismatch_err::( self.map_type_name(v.type_name()), first_arg_pos, ) })?; // Append the new curried arguments to the existing list. for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; fn_ptr.add_curry(value); } return Ok(fn_ptr.into()); } // Handle is_shared(var) #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; return Ok(arg_value.is_shared().into()); } // Handle is_def_fn(fn_name, arity) #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN if num_args == 2 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, &args_expr[0])?; let num_params = arg_value .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; return Ok(if num_params > crate::MAX_USIZE_INT { Dynamic::FALSE } else if num_params >= 0 { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); self.has_script_fn(global, caches, hash_script).into() } else { Dynamic::FALSE }); } // Handle is_def_fn(this_type, fn_name, arity) #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] crate::engine::KEYWORD_IS_DEF_FN if num_args == 3 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; let this_type = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = self.get_arg_value( global, caches, scope, this_ptr.as_deref_mut(), &args_expr[0], )?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, &args_expr[1])?; let num_params = arg_value .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; return Ok(if num_params > crate::MAX_USIZE_INT { Dynamic::FALSE } else if num_params >= 0 { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let hash_script = crate::calc_typed_method_hash( calc_fn_hash(None, &fn_name, num_params as usize), &this_type, ); self.has_script_fn(global, caches, hash_script).into() } else { Dynamic::FALSE }); } // Handle is_def_var(fn_name) KEYWORD_IS_DEF_VAR if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, arg)?; let var_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; return Ok(scope.contains(&var_name).into()); } // Handle eval(script) KEYWORD_EVAL if num_args == 1 => { // eval - only in function call style let orig_scope_len = scope.len(); #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); let arg = first_arg.unwrap(); let (arg_value, pos) = self.get_arg_value(global, caches, scope, this_ptr, arg)?; let s = &arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let orig_level = global.level; global.level += 1; let result = self.eval_script_expr_in_place(global, caches, scope, s, pos); // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. // The same is true for imports. let scope_changed = scope.len() != orig_scope_len; #[cfg(not(feature = "no_module"))] let scope_changed = scope_changed || global.num_imports() != orig_imports_len; if scope_changed { global.always_search_scope = true; } global.level = orig_level; return result.map_err(|err| match *err { ERR::Exit(..) => err, _ => ERR::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), global.source().unwrap_or("").to_string(), err, pos, ) .into(), }); } _ => (), } // Normal function call - except for Fn, curry, call and eval (handled above) let mut arg_values = FnArgsVec::with_capacity(num_args); let mut args = FnArgsVec::with_capacity(num_args + curry.len()); let mut is_ref_mut = false; // Capture parent scope? // // If so, do it separately because we cannot convert the first argument (if it is a simple // variable access) to &mut because `scope` is needed. if capture_scope && !scope.is_empty() { for expr in first_arg.iter().copied().chain(args_expr.iter()) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } args.extend(curry.iter_mut()); args.extend(arg_values.iter_mut()); // Use parent scope let scope = Some(scope); return self .exec_fn_call( global, caches, scope, fn_name, op_token, hashes, &mut args, is_ref_mut, false, pos, ) .map(|(v, ..)| v); } // Call with blank scope #[cfg(not(feature = "no_closure"))] let has_non_shared_this_ptr = this_ptr.as_ref().map_or(false, |v| !v.is_shared()); #[cfg(feature = "no_closure")] let has_non_shared_this_ptr = this_ptr.is_some(); // If the first argument is a variable, and there are no curried arguments, // convert to method-call style in order to leverage potential &mut first argument // and avoid cloning the value. match first_arg { Some(_first @ Expr::ThisPtr(pos)) if curry.is_empty() && has_non_shared_this_ptr => { // Turn it into a method call only if the object is not shared self.track_operation(global, *pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), _first)?; // func(x, ...) -> x.func(...) for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } is_ref_mut = true; args.push(this_ptr.unwrap()); } Some(first @ Expr::Variable(.., pos)) if curry.is_empty() => { self.track_operation(global, *pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), first)?; // func(x, ...) -> x.func(...) for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } let mut target = self.search_namespace(global, caches, scope, this_ptr, first)?; if target.as_ref().is_read_only() { target = target.into_owned(); } if target.is_shared() || target.is_temp_value() { arg_values.insert(0, target.take_or_clone().flatten()); } else { // Turn it into a method call only if the object is not shared and not a simple value is_ref_mut = true; let obj_ref = target.take_ref().unwrap(); args.push(obj_ref); } } _ => { // func(..., ...) for expr in first_arg.into_iter().chain(args_expr.iter()) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } args.extend(curry.iter_mut()); } } args.extend(arg_values.iter_mut()); self.exec_fn_call( global, caches, None, fn_name, op_token, hashes, &mut args, is_ref_mut, false, pos, ) .map(|(v, ..)| v) } /// Call a namespace-qualified function in normal function-call style. #[cfg(not(feature = "no_module"))] pub(crate) fn make_qualified_function_call( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, namespace: &crate::ast::Namespace, fn_name: &str, args_expr: &[Expr], hash: u64, pos: Position, ) -> RhaiResult { let mut arg_values = FnArgsVec::with_capacity(args_expr.len()); let args = &mut FnArgsVec::with_capacity(args_expr.len()); let mut first_arg_value = None; #[cfg(not(feature = "no_closure"))] let has_non_shared_this_ptr = this_ptr.as_ref().map_or(false, |v| !v.is_shared()); #[cfg(feature = "no_closure")] let has_non_shared_this_ptr = this_ptr.is_some(); // See if the first argument is a variable. // If so, convert to method-call style in order to leverage potential // &mut first argument and avoid cloning the value. match args_expr.first() { Some(_first @ Expr::ThisPtr(pos)) if has_non_shared_this_ptr => { self.track_operation(global, *pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), _first)?; // The first value is a placeholder (for later if it needs to be cloned) arg_values.push(Dynamic::UNIT); for expr in args_expr.iter().skip(1) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } // func(x, ...) -> x.func(...) let (first, rest) = arg_values.split_first_mut().unwrap(); first_arg_value = Some(first); args.push(this_ptr.unwrap()); args.extend(rest.iter_mut()); } Some(first @ Expr::Variable(.., pos)) => { self.track_operation(global, *pos)?; #[cfg(feature = "debugging")] self.dbg(global, caches, scope, this_ptr.as_deref_mut(), first)?; // The first value is a placeholder (for later if it needs to be cloned) arg_values.push(Dynamic::UNIT); for expr in args_expr.iter().skip(1) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } let target = self.search_namespace(global, caches, scope, this_ptr, first)?; if target.is_shared() || target.is_temp_value() { arg_values[0] = target.take_or_clone().flatten(); args.extend(arg_values.iter_mut()); } else { // Turn it into a method call only if the object is not shared and not a simple value // func(x, ...) -> x.func(...) let (first, rest) = arg_values.split_first_mut().unwrap(); first_arg_value = Some(first); let obj_ref = target.take_ref().unwrap(); args.push(obj_ref); args.extend(rest.iter_mut()); } } Some(_) => { // func(..., ...) or func(mod::x, ...) for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); } args.extend(arg_values.iter_mut()); } None => (), } // Search for the root namespace let module = self .search_imports(global, namespace) .ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?; // First search script-defined functions in namespace (can override built-in) let mut func = module.get_qualified_fn(hash).or_else(|| { // Then search native Rust functions let hash_qualified_fn = calc_fn_hash_full(hash, args.iter().map(|a| a.type_id())); module.get_qualified_fn(hash_qualified_fn) }); // Check for `Dynamic` parameters. // // Note - This is done during every function call mismatch without cache, // so hopefully the number of arguments should not be too many // (expected because closures cannot be qualified). if func.is_none() && !args.is_empty() { let num_args = args.len(); let max_dynamic_count = usize::min(num_args, MAX_DYNAMIC_PARAMETERS); let max_bitmask = 1usize << max_dynamic_count; let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` // Try all permutations with `Dynamic` wildcards while bitmask < max_bitmask { let hash_qualified_fn = calc_fn_hash_full( hash, args.iter().enumerate().map(|(i, a)| { if i < max_dynamic_count && bitmask & (1usize << (max_dynamic_count - i - 1)) != 0 { // Replace with `Dynamic` TypeId::of::() } else { a.type_id() } }), ); if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { func = Some(f); break; } bitmask += 1; } } // Clone first argument if the function is not a method after-all if !func.map_or(true, RhaiFunc::is_method) { if let Some(first) = first_arg_value { *first = args[0].clone(); args[0] = first; } } defer! { let orig_level = global.level; global.level += 1 } match func { #[cfg(not(feature = "no_function"))] Some(RhaiFunc::Script { fn_def, env }) => { let env = env.as_deref(); let scope = &mut Scope::new(); let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); defer! { global => move |g| g.source = orig_source } self.call_script_fn(global, caches, scope, None, env, fn_def, args, true, pos) } Some(f) if !f.is_pure() && args[0].is_read_only() => { // If function is not pure, there must be at least one argument Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) } Some(RhaiFunc::Plugin { func }) => { let context = func .has_context() .then(|| (self, fn_name, module.id(), &*global, pos).into()); func.call(context, args) .and_then(|r| self.check_data_size(r, pos)) } Some( RhaiFunc::Pure { func, has_context, .. } | RhaiFunc::Method { func, has_context, .. }, ) => { let context = has_context.then(|| (self, fn_name, module.id(), &*global, pos).into()); func(context, args).and_then(|r| self.check_data_size(r, pos)) } Some(RhaiFunc::Iterator { .. }) => { unreachable!("iterator functions should not occur here") } None => Err(ERR::ErrorFunctionNotFound( if namespace.is_empty() { self.gen_fn_call_signature(fn_name, args) } else { format!( "{namespace}{}{}", crate::engine::NAMESPACE_SEPARATOR, self.gen_fn_call_signature(fn_name, args) ) }, pos, ) .into()), } } /// Evaluate a text script in place - used primarily for 'eval'. pub(crate) fn eval_script_expr_in_place( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, script: &str, _pos: Position, ) -> RhaiResult { self.track_operation(global, _pos)?; let script = script.trim(); if script.is_empty() { return Ok(Dynamic::UNIT); } // Compile the script text // No optimizations because we only run it once let ast = self.compile_scripts_with_scope_raw( None, [script], #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, )?; // If new functions are defined within the eval string, it is an error #[cfg(not(feature = "no_function"))] if ast.has_functions() { return Err(crate::PERR::WrongFnDefinition.into()); } let statements = ast.statements(); if statements.is_empty() { return Ok(Dynamic::UNIT); } // Evaluate the AST self.eval_global_statements(global, caches, scope, statements, false) } /// # Main Entry-Point (`FnCallExpr`) /// /// Evaluate a function call expression. /// /// This method tries to short-circuit function resolution under Fast Operators mode if the /// function call is an operator. pub(crate) fn eval_fn_call_expr( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, expr: &FnCallExpr, pos: Position, ) -> RhaiResult { let FnCallExpr { #[cfg(not(feature = "no_module"))] namespace, name, hashes, args, op_token, capture_parent_scope: capture, .. } = expr; let op_token = op_token.as_ref(); // Short-circuit native unary operator call if under Fast Operators mode if self.fast_operators() && args.len() == 1 && op_token == Some(&Token::Bang) { let mut value = self .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .0 .flatten(); return if let Union::Bool(b, ..) = value.0 { Ok((!b).into()) } else { let operand = &mut [&mut value]; self.exec_fn_call( global, caches, None, name, op_token, *hashes, operand, false, false, pos, ) .map(|(v, ..)| v) }; } // Short-circuit native binary operator call if under Fast Operators mode if self.fast_operators() && args.len() == 2 && op_token.is_some() { #[allow(clippy::wildcard_imports)] use Token::*; let mut lhs = self .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .0 .flatten(); let mut rhs = self .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[1])? .0 .flatten(); #[allow(clippy::unnecessary_unwrap)] let op_token = op_token.unwrap(); // For extremely simple primary data operations, do it directly // to avoid the overhead of calling a function. match (&lhs.0, &rhs.0) { (Union::Unit(..), Union::Unit(..)) => match op_token { EqualsTo => return Ok(Dynamic::TRUE), NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => return Ok(Dynamic::FALSE), _ => (), }, (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_token { EqualsTo => return Ok((b1 == b2).into()), NotEqualsTo => return Ok((b1 != b2).into()), GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { return Ok(Dynamic::FALSE) } Pipe => return Ok((*b1 || *b2).into()), Ampersand => return Ok((*b1 && *b2).into()), _ => (), }, (Union::Int(n1, ..), Union::Int(n2, ..)) => { #[cfg(not(feature = "unchecked"))] #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] match op_token { EqualsTo => return Ok((n1 == n2).into()), NotEqualsTo => return Ok((n1 != n2).into()), GreaterThan => return Ok((n1 > n2).into()), GreaterThanEqualsTo => return Ok((n1 >= n2).into()), LessThan => return Ok((n1 < n2).into()), LessThanEqualsTo => return Ok((n1 <= n2).into()), Plus => return add(*n1, *n2).map(Into::into), Minus => return subtract(*n1, *n2).map(Into::into), Multiply => return multiply(*n1, *n2).map(Into::into), Divide => return divide(*n1, *n2).map(Into::into), Modulo => return modulo(*n1, *n2).map(Into::into), _ => (), } #[cfg(feature = "unchecked")] match op_token { EqualsTo => return Ok((n1 == n2).into()), NotEqualsTo => return Ok((n1 != n2).into()), GreaterThan => return Ok((n1 > n2).into()), GreaterThanEqualsTo => return Ok((n1 >= n2).into()), LessThan => return Ok((n1 < n2).into()), LessThanEqualsTo => return Ok((n1 <= n2).into()), Plus => return Ok((n1 + n2).into()), Minus => return Ok((n1 - n2).into()), Multiply => return Ok((n1 * n2).into()), Divide => return Ok((n1 / n2).into()), Modulo => return Ok((n1 % n2).into()), _ => (), } } #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Float(f2, ..)) => match op_token { #[cfg(feature = "unchecked")] EqualsTo => return Ok((**f1 == **f2).into()), #[cfg(not(feature = "unchecked"))] EqualsTo => return Ok(((**f1 - **f2).abs() <= FLOAT::EPSILON).into()), #[cfg(feature = "unchecked")] NotEqualsTo => return Ok((**f1 != **f2).into()), #[cfg(not(feature = "unchecked"))] NotEqualsTo => return Ok(((**f1 - **f2).abs() > FLOAT::EPSILON).into()), GreaterThan => return Ok((**f1 > **f2).into()), GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()), LessThan => return Ok((**f1 < **f2).into()), LessThanEqualsTo => return Ok((**f1 <= **f2).into()), Plus => return Ok((**f1 + **f2).into()), Minus => return Ok((**f1 - **f2).into()), Multiply => return Ok((**f1 * **f2).into()), Divide => return Ok((**f1 / **f2).into()), Modulo => return Ok((**f1 % **f2).into()), _ => (), }, #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Int(n2, ..)) => match op_token { #[cfg(feature = "unchecked")] EqualsTo => return Ok((**f1 == (*n2 as FLOAT)).into()), #[cfg(not(feature = "unchecked"))] EqualsTo => return Ok(((**f1 - (*n2 as FLOAT)).abs() <= FLOAT::EPSILON).into()), #[cfg(feature = "unchecked")] NotEqualsTo => return Ok((**f1 != (*n2 as FLOAT)).into()), #[cfg(not(feature = "unchecked"))] NotEqualsTo => { return Ok(((**f1 - (*n2 as FLOAT)).abs() > FLOAT::EPSILON).into()) } GreaterThan => return Ok((**f1 > (*n2 as FLOAT)).into()), GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as FLOAT)).into()), LessThan => return Ok((**f1 < (*n2 as FLOAT)).into()), LessThanEqualsTo => return Ok((**f1 <= (*n2 as FLOAT)).into()), Plus => return Ok((**f1 + (*n2 as FLOAT)).into()), Minus => return Ok((**f1 - (*n2 as FLOAT)).into()), Multiply => return Ok((**f1 * (*n2 as FLOAT)).into()), Divide => return Ok((**f1 / (*n2 as FLOAT)).into()), Modulo => return Ok((**f1 % (*n2 as FLOAT)).into()), _ => (), }, #[cfg(not(feature = "no_float"))] (Union::Int(n1, ..), Union::Float(f2, ..)) => match op_token { #[cfg(feature = "unchecked")] EqualsTo => return Ok(((*n1 as FLOAT) == **f2).into()), #[cfg(not(feature = "unchecked"))] EqualsTo => return Ok((((*n1 as FLOAT) - **f2).abs() <= FLOAT::EPSILON).into()), #[cfg(feature = "unchecked")] NotEqualsTo => return Ok(((*n1 as FLOAT) != **f2).into()), #[cfg(not(feature = "unchecked"))] NotEqualsTo => { return Ok((((*n1 as FLOAT) - **f2).abs() > FLOAT::EPSILON).into()) } GreaterThan => return Ok(((*n1 as FLOAT) > **f2).into()), GreaterThanEqualsTo => return Ok(((*n1 as FLOAT) >= **f2).into()), LessThan => return Ok(((*n1 as FLOAT) < **f2).into()), LessThanEqualsTo => return Ok(((*n1 as FLOAT) <= **f2).into()), Plus => return Ok(((*n1 as FLOAT) + **f2).into()), Minus => return Ok(((*n1 as FLOAT) - **f2).into()), Multiply => return Ok(((*n1 as FLOAT) * **f2).into()), Divide => return Ok(((*n1 as FLOAT) / **f2).into()), Modulo => return Ok(((*n1 as FLOAT) % **f2).into()), _ => (), }, (Union::Str(s1, ..), Union::Str(s2, ..)) => match op_token { EqualsTo => return Ok((s1 == s2).into()), NotEqualsTo => return Ok((s1 != s2).into()), GreaterThan => return Ok((s1 > s2).into()), GreaterThanEqualsTo => return Ok((s1 >= s2).into()), LessThan => return Ok((s1 < s2).into()), LessThanEqualsTo => return Ok((s1 <= s2).into()), Plus => { #[cfg(not(feature = "unchecked"))] self.throw_on_size((0, 0, s1.len() + s2.len()))?; return Ok((s1 + s2).into()); } Minus => return Ok((s1 - s2).into()), _ => (), }, (Union::Char(c1, ..), Union::Char(c2, ..)) => match op_token { EqualsTo => return Ok((c1 == c2).into()), NotEqualsTo => return Ok((c1 != c2).into()), GreaterThan => return Ok((c1 > c2).into()), GreaterThanEqualsTo => return Ok((c1 >= c2).into()), LessThan => return Ok((c1 < c2).into()), LessThanEqualsTo => return Ok((c1 <= c2).into()), Plus => { let mut result = SmartString::new_const(); result.push(*c1); result.push(*c2); #[cfg(not(feature = "unchecked"))] self.throw_on_size((0, 0, result.len()))?; return Ok(result.into()); } _ => (), }, (Union::Variant(..), _) | (_, Union::Variant(..)) => (), _ => { if let Some((func, need_context)) = get_builtin_binary_op_fn(op_token, &lhs, &rhs) { // We may not need to bump the level because built-in's do not need it. //defer! { let orig_level = global.level; global.level += 1 } let context = need_context.then(|| (self, name.as_str(), None, &*global, pos).into()); return func(context, &mut [&mut lhs, &mut rhs]); } } } let operands = &mut [&mut lhs, &mut rhs]; let op_token = Some(op_token); return self .exec_fn_call( global, caches, None, name, op_token, *hashes, operands, false, false, pos, ) .map(|(v, ..)| v); } #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { // Qualified function call let hash = hashes.native(); return self.make_qualified_function_call( global, caches, scope, this_ptr, namespace, name, args, hash, pos, ); } // Normal function call let (first_arg, rest_args) = args.split_first().map_or_else( || (None, args.as_ref()), |(first, rest)| (Some(first), rest), ); self.make_function_call( global, caches, scope, this_ptr, name, op_token, first_arg, rest_args, *hashes, *capture, pos, ) } } rhai-1.21.0/src/func/func_args.rs000064400000000000000000000057121046102023000146740ustar 00000000000000//! Helper module which defines [`FuncArgs`] to make function calling easier. #![allow(non_snake_case)] use crate::types::dynamic::Variant; use crate::Dynamic; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Trait that parses arguments to a function call. /// /// Any data type can implement this trait in order to pass arguments to /// [`Engine::call_fn`][crate::Engine::call_fn]. pub trait FuncArgs { /// Parse function call arguments into a container. /// /// # Example /// /// ``` /// use rhai::{Engine, Dynamic, FuncArgs, Scope}; /// /// // A struct containing function arguments /// struct Options { /// pub foo: bool, /// pub bar: String, /// pub baz: i64, /// } /// /// impl FuncArgs for Options { /// fn parse>(self, args: &mut ARGS) { /// args.extend(Some(self.foo.into())); /// args.extend(Some(self.bar.into())); /// args.extend(Some(self.baz.into())); /// } /// } /// /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// let options = Options { foo: false, bar: "world".to_string(), baz: 42 }; /// /// let engine = Engine::new(); /// let mut scope = Scope::new(); /// /// let ast = engine.compile( /// " /// fn hello(x, y, z) { /// if x { `hello ${y}` } else { y + z } /// } /// ")?; /// /// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; /// /// assert_eq!(result, "world42"); /// # } /// # Ok(()) /// # } /// ``` fn parse>(self, args: &mut ARGS); } impl FuncArgs for Vec { #[inline] fn parse>(self, args: &mut ARGS) { args.extend(self.into_iter().map(Dynamic::from)); } } impl FuncArgs for [T; N] { #[inline] fn parse>(self, args: &mut ARGS) { args.extend(IntoIterator::into_iter(self).map(Dynamic::from)); } } /// Macro to implement [`FuncArgs`] for tuples of standard types (each can be converted into a [`Dynamic`]). macro_rules! impl_args { ($($p:ident),*) => { impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { #[inline] #[allow(unused_variables)] fn parse>(self, args: &mut ARGS) { let ($($p,)*) = self; $(args.extend(Some(Dynamic::from($p)));)* } } impl_args!(@pop $($p),*); }; (@pop) => { }; (@pop $head:ident) => { impl_args!(); }; (@pop $head:ident $(, $tail:ident)+) => { impl_args!($($tail),*); }; } impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); rhai-1.21.0/src/func/func_trait.rs000064400000000000000000000113661046102023000150650ustar 00000000000000//! Module which defines the function registration mechanism. #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] use crate::parser::ParseResult; use crate::types::dynamic::Variant; use crate::{Engine, RhaiResultOf, Scope, SmartString, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Trait to create a Rust closure from a script. /// /// Not available under `no_function`. pub trait Func { /// The closure's output type. type Output; /// Create a Rust closure from an [`AST`]. /// /// The [`Engine`] and [`AST`] are consumed and basically embedded into the closure. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Func}; // use 'Func' for 'create_from_ast' /// /// let engine = Engine::new(); // create a new 'Engine' just for this /// /// let ast = engine.compile("fn calc(x, y) { x + len(y) < 42 }")?; /// /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, &str), bool>::create_from_ast( /// // ^^^^^^^^^^^ function parameter types in tuple /// /// engine, // the 'Engine' is consumed into the closure /// ast, // the 'AST' /// "calc" // the entry-point function name /// ); /// /// func(123, "hello")? == false; // call the anonymous function /// # Ok(()) /// # } #[must_use] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; /// Create a Rust closure from a script. /// /// The [`Engine`] is consumed and basically embedded into the closure. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' /// /// let engine = Engine::new(); // create a new 'Engine' just for this /// /// let script = "fn calc(x, y) { x + len(y) < 42 }"; /// /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, &str), bool>::create_from_script( /// // ^^^^^^^^^^^ function parameter types in tuple /// /// engine, // the 'Engine' is consumed into the closure /// script, // the script, notice number of parameters must match /// "calc" // the entry-point function name /// )?; /// /// func(123, "hello")? == false; // call the anonymous function /// # Ok(()) /// # } /// ``` fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult; } macro_rules! def_anonymous_fn { () => { def_anonymous_fn!(imp); }; (imp $($par:ident),*) => { impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine { #[cfg(feature = "sync")] type Output = Box RhaiResultOf + Send + Sync>; #[cfg(not(feature = "sync"))] type Output = Box RhaiResultOf>; #[inline] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { let fn_name: SmartString = entry_point.into(); Box::new(move |$($par,)*| self.call_fn(&mut Scope::new(), &ast, &fn_name, ($($par,)*))) } #[inline] fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult { let ast = self.compile(script)?; Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) } } }; ($p0:ident $(, $p:ident)*) => { def_anonymous_fn!(imp $p0 $(, $p)*); def_anonymous_fn!($($p),*); }; } def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); rhai-1.21.0/src/func/function.rs000064400000000000000000000250201046102023000145440ustar 00000000000000//! Module defining the standard Rhai function type. use super::native::{FnAny, FnIterator, FnPlugin, SendSync}; use crate::ast::{EncapsulatedEnviron, FnAccess}; use crate::plugin::PluginFunc; use crate::Shared; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// _(internals)_ A type encapsulating a function callable by Rhai. /// Exported under the `internals` feature only. #[derive(Clone)] #[non_exhaustive] pub enum RhaiFunc { /// A pure native Rust function with all arguments passed by value. Pure { /// Shared function pointer. func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, /// This is a dummy field and is not used. is_pure: bool, /// Is this function volatile? /// /// A volatile function does not guarantee the same result for the same input(s). is_volatile: bool, }, /// A native Rust object method with the first argument passed by reference, /// and the rest passed by value. Method { /// Shared function pointer. func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, /// Allow operating on constants? is_pure: bool, /// Is this function volatile? /// /// A volatile function does not guarantee the same result for the same input(s). is_volatile: bool, }, /// An iterator function. Iterator { /// Shared function pointer. func: Shared, }, /// A plugin function, Plugin { /// Shared function pointer. func: Shared, }, /// A script-defined function. #[cfg(not(feature = "no_function"))] Script { /// Shared reference to the [`ScriptFuncDef`][crate::ast::ScriptFuncDef] function definition. fn_def: Shared, /// Encapsulated environment, if any. env: Option>, }, } impl fmt::Debug for RhaiFunc { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Pure { .. } => f.write_str("NativePureFunction"), Self::Method { .. } => f.write_str("NativeMethod"), Self::Iterator { .. } => f.write_str("NativeIterator"), Self::Plugin { .. } => f.write_str("PluginFunction"), #[cfg(not(feature = "no_function"))] Self::Script { fn_def, .. } => fmt::Debug::fmt(fn_def, f), } } } impl fmt::Display for RhaiFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Pure { .. } => f.write_str("NativePureFunction"), Self::Method { .. } => f.write_str("NativeMethod"), Self::Iterator { .. } => f.write_str("NativeIterator"), Self::Plugin { .. } => f.write_str("PluginFunction"), #[cfg(not(feature = "no_function"))] Self::Script { fn_def, .. } => fmt::Display::fmt(fn_def, f), } } } impl RhaiFunc { /// Is this a pure native Rust function? #[inline] #[must_use] pub fn is_pure(&self) -> bool { match self { Self::Pure { .. } => true, Self::Method { is_pure, .. } => *is_pure, Self::Iterator { .. } => true, Self::Plugin { func, .. } => func.is_pure(), #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, } } /// Is this a native Rust method function? #[inline] #[must_use] pub fn is_method(&self) -> bool { match self { Self::Method { .. } => true, Self::Pure { .. } | Self::Iterator { .. } => false, Self::Plugin { func, .. } => func.is_method_call(), #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, } } /// Is this an iterator function? #[inline] #[must_use] pub const fn is_iter(&self) -> bool { match self { Self::Iterator { .. } => true, Self::Pure { .. } | Self::Method { .. } | Self::Plugin { .. } => false, #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, } } /// Is this a script-defined function? #[inline] #[must_use] pub const fn is_script(&self) -> bool { #[cfg(feature = "no_function")] return false; #[cfg(not(feature = "no_function"))] match self { Self::Script { .. } => true, Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } | Self::Plugin { .. } => false, } } /// Is this a plugin function? #[inline] #[must_use] pub const fn is_plugin_fn(&self) -> bool { match self { Self::Plugin { .. } => true, Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } => false, #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, } } /// Is this a native Rust function? #[inline] #[must_use] pub const fn is_native(&self) -> bool { #[cfg(feature = "no_function")] return true; #[cfg(not(feature = "no_function"))] match self { Self::Pure { .. } | Self::Method { .. } | Self::Plugin { .. } | Self::Iterator { .. } => true, Self::Script { .. } => false, } } /// Is there a [`NativeCallContext`][crate::NativeCallContext] parameter? #[inline] #[must_use] pub fn has_context(&self) -> bool { match self { Self::Pure { has_context, .. } | Self::Method { has_context, .. } => *has_context, Self::Plugin { func, .. } => func.has_context(), Self::Iterator { .. } => false, #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, } } /// Is this function volatile? /// /// A volatile function does not guarantee the same result for the same input(s). #[inline] #[must_use] pub fn is_volatile(&self) -> bool { match self { Self::Pure { is_volatile, .. } => *is_volatile, Self::Method { is_volatile, .. } => *is_volatile, Self::Iterator { .. } => true, Self::Plugin { func, .. } => func.is_volatile(), // Scripts are assumed to be volatile -- it can be calling volatile native functions. #[cfg(not(feature = "no_function"))] Self::Script { .. } => true, } } /// Get the access mode. #[inline] #[must_use] pub fn access(&self) -> FnAccess { #[cfg(feature = "no_function")] return FnAccess::Public; #[cfg(not(feature = "no_function"))] match self { Self::Plugin { .. } | Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } => FnAccess::Public, Self::Script { fn_def, .. } => fn_def.access, } } /// Get a shared reference to a native Rust function. #[inline] #[must_use] pub fn get_native_fn(&self) -> Option<&Shared> { match self { Self::Pure { func, .. } | Self::Method { func, .. } => Some(func), Self::Iterator { .. } | Self::Plugin { .. } => None, #[cfg(not(feature = "no_function"))] Self::Script { .. } => None, } } /// Get a shared reference to a script-defined function definition. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub const fn get_script_fn_def(&self) -> Option<&Shared> { match self { Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } | Self::Plugin { .. } => None, Self::Script { fn_def, .. } => Some(fn_def), } } /// Get a reference to the shared encapsulated environment of the function definition. /// /// Not available under `no_function` or `no_module`. #[inline] #[must_use] pub fn get_encapsulated_environ(&self) -> Option<&EncapsulatedEnviron> { match self { Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } | Self::Plugin { .. } => None, #[cfg(not(feature = "no_function"))] Self::Script { env, .. } => env.as_deref(), } } /// Get a reference to an iterator function. #[inline] #[must_use] pub fn get_iter_fn(&self) -> Option<&FnIterator> { match self { Self::Iterator { func, .. } => Some(&**func), Self::Pure { .. } | Self::Method { .. } | Self::Plugin { .. } => None, #[cfg(not(feature = "no_function"))] Self::Script { .. } => None, } } /// Get a shared reference to a plugin function. #[inline] #[must_use] pub fn get_plugin_fn(&self) -> Option<&Shared> { match self { Self::Plugin { func, .. } => Some(func), Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } => None, #[cfg(not(feature = "no_function"))] Self::Script { .. } => None, } } } #[cfg(not(feature = "no_function"))] impl From for RhaiFunc { #[inline(always)] fn from(fn_def: crate::ast::ScriptFuncDef) -> Self { Self::Script { fn_def: fn_def.into(), env: None, } } } #[cfg(not(feature = "no_function"))] impl From> for RhaiFunc { #[inline(always)] fn from(fn_def: Shared) -> Self { Self::Script { fn_def, env: None } } } impl From for RhaiFunc { #[inline(always)] fn from(func: T) -> Self { Self::Plugin { func: Shared::new(func), } } } impl From> for RhaiFunc { #[inline(always)] fn from(func: Shared) -> Self { Self::Plugin { func } } } rhai-1.21.0/src/func/hashing.rs000064400000000000000000000111371046102023000143440ustar 00000000000000//! Module containing utilities to hash functions and function calls. use crate::config; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::TypeId, hash::{BuildHasher, Hash, Hasher}, }; #[cfg(feature = "no_std")] pub type StraightHashMap = hashbrown::HashMap; #[cfg(not(feature = "no_std"))] pub type StraightHashMap = std::collections::HashMap; /// A hasher that only takes one single [`u64`] and returns it as a hash key. /// /// # Panics /// /// Panics when hashing any data type other than a [`u64`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct StraightHasher(u64); impl Hasher for StraightHasher { #[inline(always)] #[must_use] fn finish(&self) -> u64 { self.0 } #[cold] #[inline(never)] fn write(&mut self, _bytes: &[u8]) { unreachable!("StraightHasher can only hash u64 values"); } #[inline(always)] fn write_u64(&mut self, i: u64) { self.0 = i; } } /// A hash builder for `StraightHasher`. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub struct StraightHasherBuilder; impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; #[inline(always)] #[must_use] fn build_hasher(&self) -> Self::Hasher { StraightHasher(0) } } /// Create an instance of the default hasher. #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { match config::hashing::get_hashing_seed() { Some([seed1, seed2, seed3, seed4]) if (seed1 | seed2 | seed3 | seed4) != 0 => { ahash::RandomState::with_seeds(*seed1, *seed2, *seed3, *seed4).build_hasher() } _ => <_>::default(), } } /// Calculate a [`u64`] hash key from a namespace-qualified variable name. /// /// Module names are passed in via `&str` references from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator. /// /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] pub fn calc_var_hash<'a>(namespace: impl IntoIterator, var_name: &str) -> u64 { let s = &mut get_hasher(); s.write_u8(b'V'); // hash a discriminant let mut count = 0; // We always skip the first module namespace.into_iter().for_each(|m| { // We always skip the first module if count > 0 { m.hash(s); } count += 1; }); s.write_usize(count); var_name.hash(s); s.finish() } /// Calculate a [`u64`] hash key from a namespace-qualified function name /// and the number of parameters, but no parameter types. /// /// Module names making up the namespace are passed in via `&str` references from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator. /// /// If the function is not namespace-qualified, pass [`None`] as the namespace. /// /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] pub fn calc_fn_hash<'a>( namespace: impl IntoIterator, fn_name: &str, num: usize, ) -> u64 { let s = &mut get_hasher(); s.write_u8(b'F'); // hash a discriminant let mut count = 0; namespace.into_iter().for_each(|m| { // We always skip the first module if count > 0 { m.hash(s); } count += 1; }); s.write_usize(count); fn_name.hash(s); s.write_usize(num); s.finish() } /// Calculate a [`u64`] hash key from a base [`u64`] hash key and a list of parameter types. /// /// Parameter types are passed in via [`TypeId`] values from an iterator. #[inline] #[must_use] pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator) -> u64 { let s = &mut get_hasher(); s.write_u8(b'A'); // hash a discriminant let mut count = 0; params.into_iter().for_each(|t| { t.hash(s); count += 1; }); s.write_usize(count); s.finish() ^ base } /// Calculate a [`u64`] hash key from a base [`u64`] hash key and the type of the `this` pointer. #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub fn calc_typed_method_hash(base: u64, this_type: &str) -> u64 { let s = &mut get_hasher(); s.write_u8(b'T'); // hash a discriminant this_type.hash(s); s.finish() ^ base } rhai-1.21.0/src/func/mod.rs000064400000000000000000000022721046102023000135020ustar 00000000000000//! Module defining mechanisms to handle function calls in Rhai. pub mod builtin; pub mod call; pub mod func_args; #[allow(clippy::module_inception)] pub mod func_trait; pub mod function; pub mod hashing; pub mod native; pub mod plugin; pub mod register; pub mod script; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; #[cfg(not(feature = "no_closure"))] pub use call::ensure_no_data_race; #[cfg(not(feature = "no_function"))] pub use call::is_anonymous_fn; pub use call::FnCallArgs; pub use func_args::FuncArgs; #[cfg(not(feature = "no_function"))] pub use func_trait::Func; pub use function::RhaiFunc; #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_function"))] pub use hashing::calc_typed_method_hash; pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap}; #[cfg(feature = "internals")] #[allow(deprecated)] pub use native::NativeCallContextStore; #[allow(unused_imports)] pub use native::{ locked_read, locked_write, shared_get_mut, shared_make_mut, shared_take, shared_take_or_clone, FnIterator, Locked, NativeCallContext, SendSync, Shared, }; pub use register::RhaiNativeFunc; rhai-1.21.0/src/func/native.rs000064400000000000000000000610571046102023000142170ustar 00000000000000//! Module defining interfaces to native-Rust functions. use super::call::FnCallArgs; use crate::ast::FnCallHashes; use crate::eval::{Caches, GlobalRuntimeState}; use crate::plugin::PluginFunc; use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ calc_fn_hash, expose_under_internals, Dynamic, Engine, EvalContext, FnArgsVec, FuncArgs, Position, RhaiResult, RhaiResultOf, StaticVec, VarDefInfo, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] impl SendSync for T {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] pub trait SendSync {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] impl SendSync for T {} /// Immutable reference-counted container. #[cfg(not(feature = "sync"))] // TODO: Further audit no_std compatibility // When building with no_std + sync features, explicit imports from alloc // are needed despite using no_std_compat. This fixed compilation errors // around missing trait implementations for some users. pub use alloc::rc::Rc as Shared; /// Immutable reference-counted container. #[cfg(feature = "sync")] // TODO: Further audit no_std compatibility // While no_std_compat should map std::sync::Arc to alloc::sync::Arc, // there appear to be cases where this mapping fails. pub use alloc::sync::Arc as Shared; /// Synchronized shared object. #[cfg(not(feature = "sync"))] pub use std::cell::RefCell as Locked; /// Read-only lock guard for synchronized shared object. #[cfg(not(feature = "sync"))] pub type LockGuard<'a, T> = std::cell::Ref<'a, T>; /// Mutable lock guard for synchronized shared object. #[cfg(not(feature = "sync"))] pub type LockGuardMut<'a, T> = std::cell::RefMut<'a, T>; /// Synchronized shared object. #[cfg(feature = "sync")] #[allow(dead_code)] pub use std::sync::RwLock as Locked; /// Read-only lock guard for synchronized shared object. #[cfg(feature = "sync")] #[allow(dead_code)] pub type LockGuard<'a, T> = std::sync::RwLockReadGuard<'a, T>; /// Mutable lock guard for synchronized shared object. #[cfg(feature = "sync")] #[allow(dead_code)] pub type LockGuardMut<'a, T> = std::sync::RwLockWriteGuard<'a, T>; /// Context of a native Rust function call. #[derive(Debug)] pub struct NativeCallContext<'a> { /// The current [`Engine`]. engine: &'a Engine, /// Name of function called. fn_name: &'a str, /// Function source, if any. source: Option<&'a str>, /// The current [`GlobalRuntimeState`], if any. global: &'a GlobalRuntimeState, /// [Position] of the function call. pos: Position, } /// _(internals)_ Context of a native Rust function call, intended for persistence. /// Exported under the `internals` feature only. /// /// # WARNING - Volatile Type /// /// This type is volatile and may change in the future. #[deprecated = "This type is NOT deprecated, but it is considered volatile and may change in the future."] #[cfg(feature = "internals")] #[derive(Debug, Clone)] pub struct NativeCallContextStore { /// Name of function called. pub fn_name: String, /// Function source, if any. pub source: Option, /// The current [`GlobalRuntimeState`], if any. pub global: GlobalRuntimeState, /// [Position] of the function call. pub pos: Position, } #[cfg(feature = "internals")] #[allow(deprecated)] impl NativeCallContextStore { /// Create a [`NativeCallContext`] from a [`NativeCallContextStore`]. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] #[must_use] pub fn create_context<'a>(&'a self, engine: &'a Engine) -> NativeCallContext<'a> { NativeCallContext::from_stored_data(engine, self) } } impl<'a> From<( &'a Engine, &'a str, Option<&'a str>, &'a GlobalRuntimeState, Position, )> for NativeCallContext<'a> { #[inline(always)] fn from( value: ( &'a Engine, &'a str, Option<&'a str>, &'a GlobalRuntimeState, Position, ), ) -> Self { Self { engine: value.0, fn_name: value.1, source: value.2, global: value.3, pos: value.4, } } } impl<'a> NativeCallContext<'a> { /// _(internals)_ Create a new [`NativeCallContext`]. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub const fn new_with_all_fields( engine: &'a Engine, fn_name: &'a str, source: Option<&'a str>, global: &'a GlobalRuntimeState, pos: Position, ) -> Self { Self { engine, fn_name, source, global, pos, } } /// _(internals)_ Create a [`NativeCallContext`] from a [`NativeCallContextStore`]. /// Exported under the `internals` feature only. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[cfg(feature = "internals")] #[inline] #[must_use] #[allow(deprecated)] pub fn from_stored_data(engine: &'a Engine, context: &'a NativeCallContextStore) -> Self { Self { engine, fn_name: &context.fn_name, source: context.source.as_deref(), global: &context.global, pos: context.pos, } } /// _(internals)_ Store this [`NativeCallContext`] into a [`NativeCallContextStore`]. /// Exported under the `internals` feature only. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[cfg(feature = "internals")] #[inline] #[must_use] #[allow(deprecated)] pub fn store_data(&self) -> NativeCallContextStore { NativeCallContextStore { fn_name: self.fn_name.to_string(), source: self.source.map(ToString::to_string), global: self.global.clone(), pos: self.pos, } } /// The current [`Engine`]. #[inline(always)] #[must_use] pub const fn engine(&self) -> &Engine { self.engine } /// Name of the function called. #[inline(always)] #[must_use] pub const fn fn_name(&self) -> &str { self.fn_name } /// [Position] of the function call. #[inline(always)] #[must_use] pub const fn position(&self) -> Position { self.pos } /// Current nesting level of function calls. #[inline(always)] #[must_use] pub const fn call_level(&self) -> usize { self.global.level } /// The current source. #[inline(always)] #[must_use] pub const fn source(&self) -> Option<&str> { self.source } /// Custom state kept in a [`Dynamic`]. #[inline(always)] #[must_use] pub const fn tag(&self) -> Option<&Dynamic> { Some(&self.global.tag) } /// Get an iterator over the current set of modules imported via `import` statements /// in reverse order. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[inline] pub fn iter_imports(&self) -> impl Iterator { self.global.iter_imports() } /// _(internals)_ The current [`GlobalRuntimeState`], if any. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. #[expose_under_internals] #[inline(always)] #[must_use] const fn global_runtime_state(&self) -> &GlobalRuntimeState { self.global } /// Get an iterator over the namespaces containing definitions of all script-defined functions /// in reverse order (i.e. parent namespaces are iterated after child namespaces). /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] pub fn iter_namespaces(&self) -> impl Iterator { self.global.lib.iter().map(<_>::as_ref) } /// _(internals)_ The current stack of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "internals")] #[inline(always)] #[must_use] pub fn namespaces(&self) -> &[crate::SharedModule] { &self.global.lib } /// Call a function inside the call context with the provided arguments. #[inline] pub fn call_fn( &self, fn_name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let args = &mut arg_values.iter_mut().collect::>(); self._call_fn_raw(fn_name, args, false, false, false) .and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = self.engine().map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => self.engine.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType( cast_type.into(), result_type.into(), self.position(), ) .into() }) }) } /// Call a registered native Rust function inside the call context with the provided arguments. /// /// This is often useful because Rust functions typically only want to cross-call other /// registered Rust functions and not have to worry about scripted functions hijacking the /// process unknowingly (or deliberately). #[inline] pub fn call_native_fn( &self, fn_name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let args = &mut arg_values.iter_mut().collect::>(); self._call_fn_raw(fn_name, args, true, false, false) .and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = self.engine().map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => self.engine.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType( cast_type.into(), result_type.into(), self.position(), ) .into() }) }) } /// Call a function (native Rust or scripted) inside the call context. /// /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for /// a script-defined function (or the object of a method call). /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// # Arguments /// /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them /// _before_ calling this function. /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. #[inline(always)] pub fn call_fn_raw( &self, fn_name: impl AsRef, is_ref_mut: bool, is_method_call: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { let name = fn_name.as_ref(); let native_only = !is_valid_function_name(name); #[cfg(not(feature = "no_function"))] let native_only = native_only && !crate::parser::is_anonymous_fn(name); self._call_fn_raw(fn_name, args, native_only, is_ref_mut, is_method_call) } /// Call a registered native Rust function inside the call context. /// /// This is often useful because Rust functions typically only want to cross-call other /// registered Rust functions and not have to worry about scripted functions hijacking the /// process unknowingly (or deliberately). /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// # Arguments /// /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them /// _before_ calling this function. /// /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is /// not consumed. #[inline(always)] pub fn call_native_fn_raw( &self, fn_name: impl AsRef, is_ref_mut: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { self._call_fn_raw(fn_name, args, true, is_ref_mut, false) } /// Call a function (native Rust or scripted) inside the call context. fn _call_fn_raw( &self, fn_name: impl AsRef, args: &mut [&mut Dynamic], native_only: bool, is_ref_mut: bool, is_method_call: bool, ) -> RhaiResult { let global = &mut self.global.clone(); global.level += 1; let caches = &mut Caches::new(); let fn_name = fn_name.as_ref(); let op_token = Token::lookup_symbol_from_syntax(fn_name); let args_len = args.len(); if native_only { return self .engine() .exec_native_fn_call( global, caches, fn_name, op_token.as_ref(), calc_fn_hash(None, fn_name, args_len), args, is_ref_mut, false, self.position(), ) .map(|(r, ..)| r); } // Native or script let hash = match is_method_call { #[cfg(not(feature = "no_function"))] true => FnCallHashes::from_script_and_native( calc_fn_hash(None, fn_name, args_len - 1), calc_fn_hash(None, fn_name, args_len), ), #[cfg(feature = "no_function")] true => FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args_len)), _ => FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args_len)), }; self.engine() .exec_fn_call( global, caches, None, fn_name, op_token.as_ref(), hash, args, is_ref_mut, is_method_call, self.position(), ) .map(|(r, ..)| r) } } /// Return a mutable reference to the wrapped value of a [`Shared`] resource. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn shared_make_mut(value: &mut Shared) -> &mut T { Shared::make_mut(value) } /// Return a mutable reference to the wrapped value of a [`Shared`] resource. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn shared_get_mut(value: &mut Shared) -> Option<&mut T> { Shared::get_mut(value) } /// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise. #[inline] #[must_use] #[allow(dead_code)] pub fn shared_take_or_clone(value: Shared) -> T { shared_try_take(value).unwrap_or_else(|v| v.as_ref().clone()) } /// Consume a [`Shared`] resource if is unique (i.e. not shared). #[inline(always)] #[allow(dead_code)] pub fn shared_try_take(value: Shared) -> Result> { Shared::try_unwrap(value) } /// Consume a [`Shared`] resource, assuming that it is unique (i.e. not shared). /// /// # Panics /// /// Panics if the resource is shared (i.e. has other outstanding references). #[inline] #[must_use] #[allow(dead_code)] pub fn shared_take(value: Shared) -> T { shared_try_take(value) .ok() .unwrap_or_else(|| panic!("`value` is shared (i.e. has outstanding references)")) } /// _(internals)_ Lock a [`Locked`] resource for immutable access. /// Exported under the `internals` feature only. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn locked_read(value: &Locked) -> Option> { #[cfg(not(feature = "sync"))] return value.try_borrow().ok(); #[cfg(feature = "sync")] #[cfg(not(feature = "no_std"))] { #[cfg(feature = "unchecked")] return value.read().ok(); #[cfg(not(feature = "unchecked"))] { // Spin-lock for a short while before giving up for _ in 0..5 { match value.try_read() { Ok(guard) => return Some(guard), Err(std::sync::TryLockError::WouldBlock) => { std::thread::sleep(std::time::Duration::from_millis(10)) } Err(_) => return None, } } return None; } } #[cfg(feature = "sync")] #[cfg(feature = "no_std")] { #[cfg(feature = "unchecked")] return Some(value.read()); #[cfg(not(feature = "unchecked"))] return value.try_read(); } } /// _(internals)_ Lock a [`Locked`] resource for mutable access. /// Exported under the `internals` feature only. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn locked_write(value: &Locked) -> Option> { #[cfg(not(feature = "sync"))] return value.try_borrow_mut().ok(); #[cfg(feature = "sync")] #[cfg(not(feature = "no_std"))] { #[cfg(feature = "unchecked")] return value.write().ok(); #[cfg(not(feature = "unchecked"))] { // Spin-lock for a short while before giving up for _ in 0..5 { match value.try_write() { Ok(guard) => return Some(guard), Err(std::sync::TryLockError::WouldBlock) => { std::thread::sleep(std::time::Duration::from_millis(10)) } Err(_) => return None, } } return None; } } #[cfg(feature = "sync")] #[cfg(feature = "no_std")] { #[cfg(feature = "unchecked")] return Some(value.write()); #[cfg(not(feature = "unchecked"))] return value.try_write(); } } /// General Rust function trail object. #[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(Option, &mut FnCallArgs) -> RhaiResult; /// General Rust function trail object. #[cfg(feature = "sync")] pub type FnAny = dyn Fn(Option, &mut FnCallArgs) -> RhaiResult + Send + Sync; /// Built-in function trait object. pub type FnBuiltin = ( fn(Option, &mut FnCallArgs) -> RhaiResult, bool, ); /// Function that gets an iterator from a type. #[cfg(not(feature = "sync"))] pub type FnIterator = dyn Fn(Dynamic) -> Box>>; /// Function that gets an iterator from a type. #[cfg(feature = "sync")] pub type FnIterator = dyn Fn(Dynamic) -> Box>> + Send + Sync; /// Plugin function trait object. #[cfg(not(feature = "sync"))] pub type FnPlugin = dyn PluginFunc; /// Plugin function trait object. #[cfg(feature = "sync")] pub type FnPlugin = dyn PluginFunc + Send + Sync; /// Callback function for progress reporting. #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "sync"))] pub type OnProgressCallback = dyn Fn(u64) -> Option; /// Callback function for progress reporting. #[cfg(not(feature = "unchecked"))] #[cfg(feature = "sync")] pub type OnProgressCallback = dyn Fn(u64) -> Option + Send + Sync; /// Callback function for printing. #[cfg(not(feature = "sync"))] pub type OnPrintCallback = dyn Fn(&str); /// Callback function for printing. #[cfg(feature = "sync")] pub type OnPrintCallback = dyn Fn(&str) + Send + Sync; /// Callback function for debugging. #[cfg(not(feature = "sync"))] pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position); /// Callback function for debugging. #[cfg(feature = "sync")] pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position) + Send + Sync; /// _(internals)_ Callback function when a property accessed is not found in a [`Map`][crate::Map]. /// Exported under the `internals` feature only. #[cfg(not(feature = "sync"))] #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] pub type OnInvalidArrayIndexCallback = dyn for<'a> Fn( &'a mut crate::Array, crate::INT, EvalContext, ) -> RhaiResultOf>; /// Callback function when a property accessed is not found in a [`Map`][crate::Map]. /// Exported under the `internals` feature only. #[cfg(feature = "sync")] #[cfg(not(feature = "no_index"))] #[cfg(feature = "internals")] pub type OnInvalidArrayIndexCallback = dyn for<'a> Fn(&'a mut crate::Array, crate::INT, EvalContext) -> RhaiResultOf> + Send + Sync; /// _(internals)_ Callback function when a property accessed is not found in a [`Map`][crate::Map]. /// Exported under the `internals` feature only. #[cfg(not(feature = "sync"))] #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] pub type OnMissingMapPropertyCallback = dyn for<'a> Fn(&'a mut crate::Map, &str, EvalContext) -> RhaiResultOf>; /// Callback function when a property accessed is not found in a [`Map`][crate::Map]. /// Exported under the `internals` feature only. #[cfg(feature = "sync")] #[cfg(not(feature = "no_object"))] #[cfg(feature = "internals")] pub type OnMissingMapPropertyCallback = dyn for<'a> Fn(&'a mut crate::Map, &str, EvalContext) -> RhaiResultOf> + Send + Sync; /// Callback function for mapping tokens during parsing. #[cfg(not(feature = "sync"))] pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token; /// Callback function for mapping tokens during parsing. #[cfg(feature = "sync")] pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync; /// Callback function for variable access. #[cfg(not(feature = "sync"))] pub type OnVarCallback = dyn Fn(&str, usize, EvalContext) -> RhaiResultOf>; /// Callback function for variable access. #[cfg(feature = "sync")] pub type OnVarCallback = dyn Fn(&str, usize, EvalContext) -> RhaiResultOf> + Send + Sync; /// Callback function for variable definition. #[cfg(not(feature = "sync"))] pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf; /// Callback function for variable definition. #[cfg(feature = "sync")] pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf + Send + Sync; rhai-1.21.0/src/func/plugin.rs000064400000000000000000000032651046102023000142240ustar 00000000000000//! Module defining macros for developing _plugins_. use super::FnCallArgs; pub use super::RhaiFunc; pub use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, FuncRegistration, ImmutableString, Module, NativeCallContext, Position, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; pub use std::{any::TypeId, mem}; /// Result of a Rhai function. pub type RhaiResult = crate::RhaiResult; /// Re-export the codegen namespace. pub use rhai_codegen::*; /// Trait implemented by a _plugin function_. /// /// This trait should not be used directly. pub trait PluginFunc { /// Call the plugin function with the arguments provided. fn call(&self, context: Option, args: &mut FnCallArgs) -> RhaiResult; /// Is this plugin function a method? #[must_use] fn is_method_call(&self) -> bool; /// Does this plugin function contain a [`NativeCallContext`] parameter? #[must_use] fn has_context(&self) -> bool; /// Is this plugin function pure? /// /// Defaults to `true` such that any old implementation that has constant-checking code inside /// the function itself will continue to work. #[inline(always)] #[must_use] fn is_pure(&self) -> bool { true } /// Is this plugin function volatile? /// /// A volatile function is not guaranteed to return the same result for the same input(s). /// /// Defaults to `true` such that any old implementation that has constant-checking code inside /// the function itself will continue to work. #[inline(always)] #[must_use] fn is_volatile(&self) -> bool { true } } rhai-1.21.0/src/func/register.rs000064400000000000000000000272021046102023000145470ustar 00000000000000//! Module which defines the function registration mechanism. #![allow(non_snake_case)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_variables)] use super::call::FnCallArgs; use super::function::RhaiFunc; use super::native::{SendSync, Shared}; use crate::types::dynamic::{DynamicWriteLock, Union, Variant}; use crate::{Dynamic, Identifier, NativeCallContext, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, mem, }; /// These types are used to build a unique _marker_ tuple type for each combination /// of function parameter types in order to make each trait implementation unique. /// /// That is because stable Rust currently does not allow distinguishing implementations /// based purely on parameter types of traits (`Fn`, `FnOnce` and `FnMut`). /// /// # Examples /// /// `RhaiNativeFunc<(Mut, B, Ref), 3, false, R, false>` = `Fn(&mut A, B, &C) -> R` /// /// `RhaiNativeFunc<(Mut, B, Ref), 3, true, R, false>` = `Fn(NativeCallContext, &mut A, B, &C) -> R` /// /// `RhaiNativeFunc<(Mut, B, Ref), 3, false, R, true>` = `Fn(&mut A, B, &C) -> Result>` /// /// `RhaiNativeFunc<(Mut, B, Ref), 3, true, R, true>` = `Fn(NativeCallContext, &mut A, B, &C) -> Result>` /// /// These types are not actually used anywhere. pub struct Mut(T); //pub struct Ref(T); /// Dereference into [`DynamicWriteLock`] #[inline(always)] pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. data.write_lock::().unwrap() } /// Dereference into value. #[inline(always)] #[must_use] pub fn by_value(data: &mut Dynamic) -> T { if TypeId::of::() == TypeId::of::<&str>() { // If T is `&str`, data must be `ImmutableString`, so map directly to it *data = data.take().flatten(); let ref_str = match data.0 { Union::Str(ref s, ..) => s.as_str(), _ => unreachable!(), }; // SAFETY: We already checked that `T` is `&str`, so it is safe to cast here. return unsafe { mem::transmute_copy::<_, T>(&ref_str) }; } if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it return reify! { data.take().into_string().unwrap() => !!! T }; } // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. data.take().cast::() } /// Trait to register custom Rust functions. /// /// # Type Parameters /// /// * `A` - a tuple containing parameter types, with `&mut T` represented by `Mut`. /// * `N` - a constant generic containing the number of parameters, must be consistent with `ARGS`. /// * `X` - a constant boolean generic indicating whether there is a `NativeCallContext` parameter. /// * `R` - return type of the function; if the function returns `Result`, it is the unwrapped inner value type. /// * `F` - a constant boolean generic indicating whether the function is fallible (i.e. returns `Result>`). pub trait RhaiNativeFunc { /// Convert this function into a [`RhaiFunc`]. #[must_use] fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc; /// Get the type ID's of this function's parameters. #[must_use] fn param_types() -> [TypeId; N]; /// Get the number of parameters for this function. #[inline(always)] #[must_use] fn num_params() -> usize { N } /// Is there a [`NativeCallContext`] parameter for this function? #[inline(always)] #[must_use] fn has_context() -> bool { X } /// _(metadata)_ Get the type names of this function's parameters. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[must_use] fn param_names() -> [&'static str; N]; /// _(metadata)_ Get the type ID of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] #[must_use] fn return_type() -> TypeId { if F { TypeId::of::>() } else { TypeId::of::() } } /// _(metadata)_ Get the type name of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] #[must_use] fn return_type_name() -> &'static str { type_name::() } } macro_rules! def_register { () => { def_register!(imp Pure : 0;); }; (imp $abi:ident : $n:expr ; $($par:ident => $arg:expr => $mark:ty => $param:ty => $clone:expr),*) => { // ^ function ABI type // ^ number of parameters // ^ function parameter generic type name (A, B, C etc.) // ^ call argument(like A, *B, &mut C etc) // ^ function parameter marker type (A, Ref or Mut) // ^ function parameter actual type (A, &B or &mut C) // ^ parameter access function (by_value or by_ref) impl< FN: Fn($($param),*) -> RET + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone, > RhaiNativeFunc<($($mark,)*), $n, false, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[inline(always)] fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc { RhaiFunc::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value let r = self($($arg),*); // Map the result Ok(Dynamic::from(r)) }), has_context: false, is_pure, is_volatile } } } impl< FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RET + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone, > RhaiNativeFunc<($($mark,)*), $n, true, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[inline(always)] fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc { RhaiFunc::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value let r = self(ctx, $($arg),*); // Map the result Ok(Dynamic::from(r)) }), has_context: true, is_pure, is_volatile } } } impl< FN: Fn($($param),*) -> RhaiResultOf + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone > RhaiNativeFunc<($($mark,)*), $n, false, RET, true> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } #[inline(always)] fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc { RhaiFunc::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self($($arg),*).map(Dynamic::from) }), has_context: false, is_pure, is_volatile } } } impl< FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RhaiResultOf + SendSync + 'static, $($par: Variant + Clone,)* RET: Variant + Clone > RhaiNativeFunc<($($mark,)*), $n, true, RET, true> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } #[inline(always)] fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc { RhaiFunc::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self(ctx, $($arg),*).map(Dynamic::from) }), has_context: true, is_pure, is_volatile } } } //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident:$n0:expr $(, $p:ident: $n:expr)*) => { def_register!(imp Pure : $n0 ; $p0 => $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => $p => by_value)*); def_register!(imp Method : $n0 ; $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => $p => by_value)*); // ^ RhaiFunc constructor // ^ number of arguments ^ first parameter passed through // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished //def_register!(imp $p0 => Ref<$p0> => &$p0 => by_ref $(, $p => $p => $p => by_value)*); def_register!($($p: $n),*); }; } def_register!(A:20, B:19, C:18, D:17, E:16, F:15, G:14, H:13, J:12, K:11, L:10, M:9, N:8, P:7, Q:6, R:5, S:4, T:3, U:2, V:1); rhai-1.21.0/src/func/script.rs000064400000000000000000000214011046102023000142220ustar 00000000000000//! Implement script function-calling mechanism for [`Engine`]. #![cfg(not(feature = "no_function"))] use super::call::FnCallArgs; use crate::ast::{EncapsulatedEnviron, ScriptFuncDef}; use crate::eval::{Caches, GlobalRuntimeState}; use crate::{Dynamic, Engine, Position, RhaiResult, Scope, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// # Main Entry-Point /// /// Call a script-defined function. /// /// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not. /// /// # WARNING /// /// Function call arguments may be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// /// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`! pub(crate) fn call_script_fn( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, scope: &mut Scope, mut this_ptr: Option<&mut Dynamic>, _env: Option<&EncapsulatedEnviron>, fn_def: &ScriptFuncDef, args: &mut FnCallArgs, rewind_scope: bool, pos: Position, ) -> RhaiResult { debug_assert_eq!(fn_def.params.len(), args.len()); self.track_operation(global, pos)?; // Check for stack overflow #[cfg(not(feature = "unchecked"))] if global.level > self.max_call_levels() { return Err(ERR::ErrorStackOverflow(pos).into()); } #[cfg(feature = "debugging")] if self.debugger_interface.is_none() && fn_def.body.is_empty() { return Ok(Dynamic::UNIT); } #[cfg(not(feature = "debugging"))] if fn_def.body.is_empty() { return Ok(Dynamic::UNIT); } let orig_scope_len = scope.len(); let orig_lib_len = global.lib.len(); #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); #[cfg(feature = "debugging")] let orig_call_stack_len = global .debugger .as_ref() .map_or(0, |dbg| dbg.call_stack().len()); // Guard against too many variables #[cfg(not(feature = "unchecked"))] if scope.len() + fn_def.params.len() > self.max_variables() { return Err(ERR::ErrorTooManyVariables(pos).into()); } // Put arguments into scope as variables scope.extend(fn_def.params.iter().cloned().zip(args.iter_mut().map(|v| { // Actually consume the arguments instead of cloning them v.take() }))); // Push a new call stack frame #[cfg(feature = "debugging")] if self.is_debugger_registered() { let fn_name = fn_def.name.clone(); let args = scope .iter_inner() .skip(orig_scope_len) .map(|(.., v)| v.flatten_clone()); let source = global.source.clone(); global .debugger_mut() .push_call_stack_frame(fn_name, args, source, pos); } // Merge in encapsulated environment, if any let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); #[cfg(not(feature = "no_module"))] let orig_constants = _env.map(|environ| { let EncapsulatedEnviron { lib, imports, constants, } = environ; imports .iter() .cloned() .for_each(|(n, m)| global.push_import(n, m)); global.lib.push(lib.clone()); std::mem::replace(&mut global.constants, constants.clone()) }); #[cfg(feature = "debugging")] if self.is_debugger_registered() { let node = crate::ast::Stmt::Noop(fn_def.body.position()); self.dbg(global, caches, scope, this_ptr.as_deref_mut(), &node)?; } // Evaluate the function let mut _result: RhaiResult = self .eval_stmt_block( global, caches, scope, this_ptr.as_deref_mut(), fn_def.body.statements(), rewind_scope, ) .or_else(|err| match *err { // Convert return statement to return value ERR::Return(x, ..) => Ok(x), // Exit value is passed straight-through mut err @ ERR::Exit(..) => { err.set_position(pos); Err(err.into()) } // System errors are passed straight-through mut err if err.is_system_exception() => { err.set_position(pos); Err(err.into()) } // Other errors are wrapped in `ErrorInFunctionCall` _ => Err(ERR::ErrorInFunctionCall( fn_def.name.to_string(), #[cfg(not(feature = "no_module"))] _env.and_then(|env| env.lib.id()) .unwrap_or_else(|| global.source().unwrap_or("")) .to_string(), #[cfg(feature = "no_module")] global.source().unwrap_or("").to_string(), err, pos, ) .into()), }); #[cfg(feature = "debugging")] if self.is_debugger_registered() { let trigger = match global.debugger_mut().status { crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level, crate::eval::DebuggerStatus::Next(.., true) => true, _ => false, }; if trigger { let node = crate::ast::Stmt::Noop(fn_def.body.end_position().or_else(pos)); let node = (&node).into(); let event = match _result { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; match self.dbg_raw(global, caches, scope, this_ptr, node, event) { Ok(_) => (), Err(err) => _result = Err(err), } } // Pop the call stack global .debugger .as_mut() .unwrap() .rewind_call_stack(orig_call_stack_len); } // Remove all local variables and imported modules if rewind_scope { scope.rewind(orig_scope_len); } else if !args.is_empty() { // Remove arguments only, leaving new variables in the scope scope.remove_range(orig_scope_len, args.len()); } global.lib.truncate(orig_lib_len); #[cfg(not(feature = "no_module"))] global.truncate_imports(orig_imports_len); // Restore constants #[cfg(not(feature = "no_module"))] if let Some(constants) = orig_constants { global.constants = constants; } // Restore state caches.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); _result } // Does a script-defined function exist? /// /// # Note /// /// If the scripted function is not found, this information is cached for future look-ups. #[must_use] pub(crate) fn has_script_fn( &self, global: &GlobalRuntimeState, caches: &mut Caches, hash_script: u64, ) -> bool { let cache = caches.fn_resolution_cache_mut(); if let Some(result) = cache.dict.get(&hash_script).map(Option::is_some) { return result; } // First check script-defined functions let res = global.lib.iter().any(|m| m.contains_fn(hash_script)) // Then check the global namespace and packages || self.global_modules.iter().any(|m| m.contains_fn(hash_script)); #[cfg(not(feature = "no_module"))] let res = res || // Then check imported modules global.contains_qualified_fn(hash_script) // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); if !res && !cache.bloom_filter.is_absent_and_set(hash_script) { // Do not cache "one-hit wonders" cache.dict.insert(hash_script, None); } res } } rhai-1.21.0/src/lib.rs000064400000000000000000000424151046102023000125410ustar 00000000000000//! # Rhai - embedded scripting for Rust //! //! ![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg) //! //! Rhai is a tiny, simple and fast embedded scripting language for Rust //! that gives you a safe and easy way to add scripting to your applications. //! //! It provides a familiar syntax based on JavaScript+Rust and a simple Rust interface. //! //! # A Quick Example //! //! ## Contents of `my_script.rhai` //! //! ```rhai //! /// Brute force factorial function //! fn factorial(x) { //! if x == 1 { return 1; } //! x * factorial(x - 1) //! } //! //! // Calling an external function 'compute' //! compute(factorial(10)) //! ``` //! //! ## The Rust part //! //! ```no_run //! use rhai::{Engine, EvalAltResult}; //! //! fn main() -> Result<(), Box> //! { //! // Define external function //! fn compute_something(x: i64) -> bool { //! (x % 40) == 0 //! } //! //! // Create scripting engine //! let mut engine = Engine::new(); //! //! // Register external function as 'compute' //! engine.register_fn("compute", compute_something); //! //! # #[cfg(not(feature = "no_std"))] //! # #[cfg(not(target_family = "wasm"))] //! # //! // Evaluate the script, expecting a 'bool' result //! let result: bool = engine.eval_file("my_script.rhai".into())?; //! //! assert_eq!(result, true); //! //! Ok(()) //! } //! ``` //! //! # Features //! #![cfg_attr(feature = "document-features", doc = document_features::document_features!( feature_label = "**`{feature}`**" ))] //! //! # On-Line Documentation //! //! See [The Rhai Book](https://rhai.rs/book) for details on the Rhai scripting engine and language. // Map `no_std` feature. #![cfg_attr(feature = "no_std", no_std)] // // Clippy lints. // #![deny(missing_docs)] // #![warn(clippy::all)] // #![warn(clippy::pedantic)] // #![warn(clippy::nursery)] #![warn(clippy::cargo)] #![warn(clippy::undocumented_unsafe_blocks)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::used_underscore_binding)] #![allow(clippy::inline_always)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::negative_feature_names)] #![allow(clippy::box_collection)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::match_same_arms)] #![allow(clippy::unnecessary_box_returns)] // The lints below are turned off to reduce signal/noise ratio #![allow(clippy::cognitive_complexity)] // Hey, this is a scripting engine with a compiler... #![allow(clippy::too_many_lines)] // Same... #![allow(clippy::too_many_arguments)] // Same... #![allow(clippy::absurd_extreme_comparisons)] // On `only_i32`, `MAX_USIZE_INT` < `INT::MAX` because `usize` == `u32` and `INT` == `i64` #![allow(clippy::wildcard_imports)] // Wildcard imports are used to import the plugins prelude #![allow(clippy::enum_glob_use)] // Sometimes useful to import all `Tokens` etc. #![allow(clippy::no_effect_underscore_binding)] // Underscored variables may be used by code within feature guards #![allow(clippy::semicolon_if_nothing_returned)] // One-liner `match` cases are sometimes formatted as multi-line blocks // TODO: Further audit no_std compatibility // When building with no_std + sync features, explicit imports from alloc // are needed despite using no_std_compat. This fixed compilation errors // in `native.rs` around missing trait implementations for some users. //#[cfg(feature = "no_std")] extern crate alloc; #[cfg(feature = "no_std")] extern crate no_std_compat as std; #[cfg(feature = "no_std")] use std::prelude::v1::*; // Internal modules #[macro_use] mod reify; #[macro_use] mod defer; mod api; mod ast; pub mod config; mod engine; mod eval; mod func; mod module; mod optimizer; pub mod packages; mod parser; #[cfg(feature = "serde")] pub mod serde; mod tests; mod tokenizer; mod types; /// Error encountered when parsing a script. type PERR = ParseErrorType; /// Evaluation result. type ERR = EvalAltResult; /// General evaluation error for Rhai scripts. type RhaiError = Box; /// Generic [`Result`] type for Rhai functions. type RhaiResultOf = Result; /// General [`Result`] type for Rhai functions returning [`Dynamic`] values. type RhaiResult = RhaiResultOf; /// The system integer type. It is defined as [`i64`]. /// /// If the `only_i32` feature is enabled, this will be [`i32`] instead. #[cfg(not(feature = "only_i32"))] pub type INT = i64; /// The system integer type. /// It is defined as [`i32`] since the `only_i32` feature is used. /// /// If the `only_i32` feature is not used, this will be `i64` instead. #[cfg(feature = "only_i32")] pub type INT = i32; /// The unsigned system base integer type. It is defined as [`u64`]. /// /// If the `only_i32` feature is enabled, this will be [`u32`] instead. #[cfg(not(feature = "only_i32"))] #[allow(non_camel_case_types)] type UNSIGNED_INT = u64; /// The unsigned system integer base type. /// It is defined as [`u32`] since the `only_i32` feature is used. /// /// If the `only_i32` feature is not used, this will be `u64` instead. #[cfg(feature = "only_i32")] #[allow(non_camel_case_types)] type UNSIGNED_INT = u32; /// The maximum integer that can fit into a [`usize`]. #[cfg(not(target_pointer_width = "32"))] const MAX_USIZE_INT: INT = INT::MAX; /// The maximum integer that can fit into a [`usize`]. #[cfg(not(feature = "only_i32"))] #[cfg(target_pointer_width = "32")] const MAX_USIZE_INT: INT = usize::MAX as INT; /// The maximum integer that can fit into a [`usize`]. #[cfg(feature = "only_i32")] #[cfg(target_pointer_width = "32")] const MAX_USIZE_INT: INT = INT::MAX; /// Number of bits in [`INT`]. /// /// It is 64 unless the `only_i32` feature is enabled when it will be 32. const INT_BITS: usize = std::mem::size_of::() * 8; /// Number of bytes that make up an [`INT`]. /// /// It is 8 unless the `only_i32` feature is enabled when it will be 4. #[cfg(not(feature = "no_index"))] const INT_BYTES: usize = std::mem::size_of::(); /// The system floating-point type. It is defined as [`f64`]. /// /// Not available under `no_float`. /// /// If the `f32_float` feature is enabled, this will be [`f32`] instead. #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] pub type FLOAT = f64; /// The system floating-point type. /// It is defined as [`f32`] since the `f32_float` feature is used. /// /// Not available under `no_float`. /// /// If the `f32_float` feature is not used, this will be `f64` instead. #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] pub type FLOAT = f32; /// Number of bytes that make up a [`FLOAT`]. /// /// It is 8 unless the `f32_float` feature is enabled when it will be 4. #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_index"))] const FLOAT_BYTES: usize = std::mem::size_of::(); /// An exclusive integer range. type ExclusiveRange = std::ops::Range; /// An inclusive integer range. type InclusiveRange = std::ops::RangeInclusive; #[cfg(feature = "std")] use once_cell::sync::OnceCell; #[cfg(not(feature = "std"))] use once_cell::race::OnceBox as OnceCell; pub use api::build_type::{CustomType, TypeBuilder}; #[cfg(not(feature = "no_custom_syntax"))] pub use api::custom_syntax::Expression; #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] pub use api::files::{eval_file, run_file}; pub use api::{eval::eval, run::run}; pub use ast::{FnAccess, AST}; use defer::Deferred; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; pub use eval::EvalContext; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] use func::calc_typed_method_hash; use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash}; pub use func::{plugin, FuncArgs, NativeCallContext, RhaiNativeFunc}; pub use module::{FnNamespace, FuncRegistration, Module}; pub use packages::string_basic::{FUNC_TO_DEBUG, FUNC_TO_STRING}; pub use rhai_codegen::*; #[cfg(not(feature = "no_time"))] pub use types::Instant; pub use types::{ Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, VarDefInfo, }; /// _(debugging)_ Module containing types for debugging. /// Exported under the `debugging` feature only. #[cfg(feature = "debugging")] pub mod debugger { #[cfg(not(feature = "no_function"))] pub use super::eval::CallStackFrame; pub use super::eval::{BreakPoint, Debugger, DebuggerCommand, DebuggerEvent}; } /// _(internals)_ An identifier in Rhai. /// Exported under the `internals` feature only. /// /// Identifiers are assumed to be all-ASCII and short with few exceptions. /// /// [`SmartString`](https://crates.io/crates/smartstring) is used as the underlying storage type /// because most identifiers can be stored inline. #[expose_under_internals] type Identifier = SmartString; /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. pub use func::Shared; /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. pub use func::Locked; /// A shared [`Module`]. type SharedModule = Shared; #[cfg(not(feature = "no_function"))] pub use func::Func; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; #[cfg(not(feature = "no_function"))] pub use api::call_fn::CallFnOptions; /// Variable-sized array of [`Dynamic`] values. /// /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] pub type Array = Vec; /// Variable-sized array of [`u8`] values (byte array). /// /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] pub type Blob = Vec; /// A dictionary of [`Dynamic`] values with string keys. /// /// Not available under `no_object`. /// /// [`SmartString`](https://crates.io/crates/smartstring) is used as the key type because most /// property names are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(not(feature = "no_object"))] pub type Map = std::collections::BTreeMap; #[cfg(not(feature = "no_object"))] pub use api::json::format_map_as_json; #[cfg(not(feature = "no_module"))] pub use module::ModuleResolver; /// Module containing all built-in _module resolvers_ available to Rhai. #[cfg(not(feature = "no_module"))] pub use module::resolvers as module_resolvers; #[cfg(not(feature = "no_optimize"))] pub use optimizer::OptimizationLevel; // Expose internal data structures. #[cfg(feature = "internals")] pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}; #[cfg(feature = "internals")] pub use module::{FuncInfo, FuncMetadata}; #[cfg(feature = "internals")] #[cfg(not(feature = "no_float"))] pub use types::FloatWrapper; #[cfg(feature = "internals")] pub use types::{BloomFilterU64, CustomTypeInfo, Span, StringsInterner}; #[cfg(feature = "internals")] pub use tokenizer::{ get_next_token, is_valid_function_name, is_valid_identifier, parse_raw_string_literal, parse_string_literal, InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl, TokenizerControlBlock, }; #[cfg(feature = "internals")] pub use parser::ParseState; #[cfg(feature = "internals")] pub use api::default_limits; #[cfg(feature = "internals")] pub use ast::{ ASTFlags, ASTNode, BinaryExpr, EncapsulatedEnviron, Expr, FlowControl, FnCallExpr, FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFuncDef, Stmt, StmtBlock, SwitchCasesCollection, }; #[cfg(feature = "internals")] #[cfg(not(feature = "no_custom_syntax"))] pub use ast::CustomExpr; #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] pub use ast::Namespace; #[cfg(feature = "internals")] pub use eval::{Caches, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeState, Target}; #[cfg(feature = "internals")] #[allow(deprecated)] pub use func::{locked_read, locked_write, NativeCallContextStore, RhaiFunc}; #[cfg(feature = "internals")] #[cfg(feature = "metadata")] pub use api::definitions::Definitions; /// Number of items to keep inline for [`StaticVec`]. const STATIC_VEC_INLINE_SIZE: usize = 3; /// _(internals)_ Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), /// which is a [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored. /// Exported under the `internals` feature only. /// /// # History /// /// And Saint Attila raised the `SmallVec` up on high, saying, "O Lord, bless this Thy `SmallVec` /// that, with it, Thou mayest blow Thine allocation costs to tiny bits in Thy mercy." /// /// And the Lord did grin, and the people did feast upon the lambs and sloths and carp and anchovies /// and orangutans and breakfast cereals and fruit bats and large chu... /// /// And the Lord spake, saying, "First shalt thou depend on the [`smallvec`](https://crates.io/crates/smallvec) crate. /// Then, shalt thou keep three inline. No more. No less. Three shalt be the number thou shalt keep inline, /// and the number to keep inline shalt be three. Four shalt thou not keep inline, nor either keep inline /// thou two, excepting that thou then proceed to three. Five is right out. Once the number three, /// being the third number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who, /// being slow and cache-naughty in My sight, shall snuff it." /// /// # Why Three /// /// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in /// order to improve cache friendliness and reduce indirections. /// /// The number 3, other than being the holy number, is carefully chosen for a balance between /// storage space and reduce allocations. That is because most function calls (and most functions, /// for that matter) contain fewer than 4 arguments, the exception being closures that capture a /// large number of external variables. /// /// In addition, most script blocks either contain many statements, or just one or two lines; /// most scripts load fewer than 4 external modules; most module paths contain fewer than 4 levels /// (e.g. `std::collections::map::HashMap` is 4 levels and it is just about as long as they get). #[expose_under_internals] type StaticVec = smallvec::SmallVec<[T; STATIC_VEC_INLINE_SIZE]>; /// _(internals)_ A smaller [`Vec`] alternative. Exported under the `internals` feature only. /// Exported under the `internals` feature only. /// /// The standard [`Vec`] type uses three machine words (i.e. 24 bytes on 64-bit). /// /// [`ThinVec`](https://crates.io/crates/thin-vec) only uses one machine word, storing other /// information inline together with the data. /// /// This is primarily used in places where a few bytes affect the size of the type /// -- e.g. in `enum`'s. #[expose_under_internals] type ThinVec = thin_vec::ThinVec; /// Number of items to keep inline for [`FnArgsVec`]. #[cfg(not(feature = "no_closure"))] const FN_ARGS_VEC_INLINE_SIZE: usize = 5; /// _(internals)_ Inline arguments storage for function calls. /// Exported under the `internals` feature only. /// /// Not available under `no_closure`. /// /// # Notes /// /// Since most usage of this is during a function call to gather up arguments, this is mostly /// allocated on the stack, so we can tolerate a larger number of values stored inline. /// /// Most functions have few parameters, but closures with a lot of captured variables can /// potentially have many. Having a larger inline storage for arguments reduces allocations in /// scripts with heavy closure usage. /// /// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead. #[expose_under_internals] #[cfg(not(feature = "no_closure"))] type FnArgsVec = smallvec::SmallVec<[T; FN_ARGS_VEC_INLINE_SIZE]>; /// Inline arguments storage for function calls. /// This type aliases to [`StaticVec`][crate::StaticVec]. #[expose_under_internals] #[cfg(feature = "no_closure")] type FnArgsVec = crate::StaticVec; type SmartString = smartstring::SmartString; // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] #[cfg(feature = "f32_float")] compile_error!("`f32_float` cannot be used with `no_float`"); #[cfg(feature = "only_i32")] #[cfg(feature = "only_i64")] compile_error!("`only_i32` and `only_i64` cannot be used together"); #[cfg(feature = "no_std")] #[cfg(feature = "wasm-bindgen")] compile_error!("`wasm-bindgen` cannot be used with `no-std`"); #[cfg(feature = "no_std")] #[cfg(feature = "stdweb")] compile_error!("`stdweb` cannot be used with `no-std`"); #[cfg(target_family = "wasm")] #[cfg(feature = "no_std")] compile_error!("`no_std` cannot be used for WASM target"); #[cfg(not(target_family = "wasm"))] #[cfg(feature = "wasm-bindgen")] compile_error!("`wasm-bindgen` cannot be used for non-WASM target"); #[cfg(not(target_family = "wasm"))] #[cfg(feature = "stdweb")] compile_error!("`stdweb` cannot be used non-WASM target"); #[cfg(feature = "wasm-bindgen")] #[cfg(feature = "stdweb")] compile_error!("`wasm-bindgen` and `stdweb` cannot be used together"); rhai-1.21.0/src/module/mod.rs000064400000000000000000002710301046102023000140340ustar 00000000000000//! Module defining external-loaded modules for Rhai. #[cfg(feature = "metadata")] use crate::api::formatting::format_param_type_for_display; use crate::ast::FnAccess; use crate::func::{ shared_take_or_clone, FnIterator, RhaiFunc, RhaiNativeFunc, SendSync, StraightHashMap, }; use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypeInfo, CustomTypesCollection}; use crate::{ calc_fn_hash, calc_fn_hash_full, expose_under_internals, Dynamic, Engine, FnArgsVec, Identifier, ImmutableString, RhaiResultOf, Shared, SharedModule, SmartString, }; use bitflags::bitflags; #[cfg(feature = "no_std")] use hashbrown::hash_map::Entry; #[cfg(not(feature = "no_std"))] use std::collections::hash_map::Entry; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, collections::BTreeMap, fmt, ops::{Add, AddAssign}, }; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::func::register::Mut; /// Initial capacity of the hashmap for functions. const FN_MAP_SIZE: usize = 16; /// A type representing the namespace of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr( feature = "metadata", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[non_exhaustive] pub enum FnNamespace { /// Module namespace only. /// /// Ignored under `no_module`. Internal, /// Expose to global namespace. Global, } impl FnNamespace { /// Is this a module namespace? #[inline(always)] #[must_use] pub const fn is_module_namespace(self) -> bool { match self { Self::Internal => true, Self::Global => false, } } /// Is this a global namespace? #[inline(always)] #[must_use] pub const fn is_global_namespace(self) -> bool { match self { Self::Internal => false, Self::Global => true, } } } /// _(internals)_ A type containing the metadata of a single registered function. /// Exported under the `internals` features only. #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[non_exhaustive] pub struct FuncMetadata { /// Hash value. pub hash: u64, /// Function namespace. pub namespace: FnNamespace, /// Function access mode. pub access: FnAccess, /// Function name. pub name: Identifier, /// Number of parameters. pub num_params: usize, /// Parameter types (if applicable). pub param_types: FnArgsVec, /// Parameter names and types (if available). /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] pub params_info: FnArgsVec, /// Return type name. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] pub return_type: Identifier, /// Comments. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] pub comments: crate::StaticVec, } impl FuncMetadata { /// _(metadata)_ Generate a signature of the function. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[must_use] pub fn gen_signature<'a>( &'a self, type_mapper: impl Fn(&'a str) -> std::borrow::Cow<'a, str>, ) -> String { let mut signature = format!("{}(", self.name); let return_type = format_param_type_for_display(&self.return_type, true); if self.params_info.is_empty() { for x in 0..self.num_params { signature += "_"; if x < self.num_params - 1 { signature += ", "; } } } else { let params = self .params_info .iter() .map(|param| { let mut segment = param.splitn(2, ':'); let name = match segment.next().unwrap().trim() { "" => "_", s => s, }; let result: std::borrow::Cow<_> = segment.next().map_or_else( || name.into(), |typ| { format!( "{name}: {}", format_param_type_for_display(&type_mapper(typ), false) ) .into() }, ); result }) .collect::>(); signature += ¶ms.join(", "); } signature += ")"; if !return_type.is_empty() { signature += " -> "; signature += &return_type; } signature } } /// Information about a function, native or scripted. /// /// Exported under the `internals` feature only. #[allow(dead_code)] pub struct FuncInfo<'a> { /// Function metadata. pub metadata: &'a FuncMetadata, /// Function namespace. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] pub namespace: Identifier, /// Metadata if the function is scripted. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub script: Option>, } /// _(internals)_ Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types. /// Exported under the `internals` feature only. /// /// Module names are passed in via `&str` references from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator. /// /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] pub fn calc_native_fn_hash<'a>( modules: impl IntoIterator>, fn_name: &str, params: &[TypeId], ) -> u64 { calc_fn_hash_full( calc_fn_hash(modules, fn_name, params.len()), params.iter().copied(), ) } /// Type for fine-tuned module function registration. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FuncRegistration { /// Function metadata. metadata: FuncMetadata, /// Is the function pure? purity: Option, /// Is the function volatile? volatility: Option, } impl FuncRegistration { /// Create a new [`FuncRegistration`]. /// /// # Defaults /// /// * **Accessibility**: The function namespace is [`FnNamespace::Internal`]. /// /// * **Purity**: The function is assumed to be _pure_ unless it is a property setter or an index setter. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. /// /// ``` /// # use rhai::{Module, FuncRegistration, FnNamespace}; /// let mut module = Module::new(); /// /// fn inc(x: i64) -> i64 { x + 1 } /// /// let f = FuncRegistration::new("inc") /// .in_global_namespace() /// .set_into_module(&mut module, inc); /// /// let hash = f.hash; /// /// assert!(module.contains_fn(hash)); /// ``` #[must_use] pub fn new(name: impl Into) -> Self { Self { metadata: FuncMetadata { hash: 0, name: name.into(), namespace: FnNamespace::Internal, access: FnAccess::Public, num_params: 0, param_types: <_>::default(), #[cfg(feature = "metadata")] params_info: <_>::default(), #[cfg(feature = "metadata")] return_type: "".into(), #[cfg(feature = "metadata")] comments: <_>::default(), }, purity: None, volatility: None, } } /// Create a new [`FuncRegistration`] for a property getter. /// /// Not available under `no_object`. /// /// # Defaults /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _pure_. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. #[cfg(not(feature = "no_object"))] #[inline(always)] #[must_use] pub fn new_getter(prop: impl AsRef) -> Self { Self::new(crate::engine::make_getter(prop.as_ref())).in_global_namespace() } /// Create a new [`FuncRegistration`] for a property setter. /// /// Not available under `no_object`. /// /// # Defaults /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _no-pure_. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. #[cfg(not(feature = "no_object"))] #[inline(always)] #[must_use] pub fn new_setter(prop: impl AsRef) -> Self { Self::new(crate::engine::make_setter(prop.as_ref())) .in_global_namespace() .with_purity(false) } /// Create a new [`FuncRegistration`] for an index getter. /// /// Not available under both `no_index` and `no_object`. /// /// # Defaults /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _pure_. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] #[must_use] pub fn new_index_getter() -> Self { Self::new(crate::engine::FN_IDX_GET).in_global_namespace() } /// Create a new [`FuncRegistration`] for an index setter. /// /// Not available under both `no_index` and `no_object`. /// /// # Defaults /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _no-pure_. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] #[must_use] pub fn new_index_setter() -> Self { Self::new(crate::engine::FN_IDX_SET) .in_global_namespace() .with_purity(false) } /// Set the [namespace][`FnNamespace`] of the function. #[must_use] pub const fn with_namespace(mut self, namespace: FnNamespace) -> Self { self.metadata.namespace = namespace; self } /// Set the function to the [global namespace][`FnNamespace::Global`]. #[must_use] pub const fn in_global_namespace(mut self) -> Self { self.metadata.namespace = FnNamespace::Global; self } /// Set the function to the [internal namespace][`FnNamespace::Internal`]. #[must_use] pub const fn in_internal_namespace(mut self) -> Self { self.metadata.namespace = FnNamespace::Internal; self } /// Set whether the function is _pure_. /// A pure function has no side effects. #[must_use] pub const fn with_purity(mut self, pure: bool) -> Self { self.purity = Some(pure); self } /// Set whether the function is _volatile_. /// A volatile function does not guarantee the same result for the same input(s). #[must_use] pub const fn with_volatility(mut self, volatile: bool) -> Self { self.volatility = Some(volatile); self } /// _(metadata)_ Set the function's parameter names and/or types. /// Exported under the `metadata` feature only. /// /// The input is a list of strings, each of which is either a parameter name or a parameter name/type pair. /// /// The _last_ string should be the _type_ of the return value. /// /// # Parameter Examples /// /// `"foo: &str"` <- parameter name = `foo`, type = `&str` /// `"bar"` <- parameter name = `bar`, type unknown /// `"_: i64"` <- parameter name unknown, type = `i64` /// `"MyType"` <- parameter name unknown, type = `MyType` #[cfg(feature = "metadata")] #[must_use] pub fn with_params_info>(mut self, params: impl IntoIterator) -> Self { self.metadata.params_info = params.into_iter().map(|s| s.as_ref().into()).collect(); self } /// _(metadata)_ Set the function's doc-comments. /// Exported under the `metadata` feature only. /// /// The input is a list of strings, each of which is either a block of single-line doc-comments /// or a single block doc-comment. /// /// ## Comments /// /// Single-line doc-comments typically start with `///` and should be merged, with line-breaks, /// into a single string without a final termination line-break. /// /// Block doc-comments typically start with `/**` and end with `*/` and should be kept in a /// separate string. /// /// Leading white-spaces should be stripped, and each string should always start with the /// corresponding doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments should start with `///`. #[cfg(feature = "metadata")] #[must_use] pub fn with_comments>(mut self, comments: impl IntoIterator) -> Self { self.metadata.comments = comments.into_iter().map(|s| s.as_ref().into()).collect(); self } /// Register the function into the specified [`Engine`]. #[inline] pub fn register_into_engine( self, engine: &mut Engine, func: FUNC, ) -> &FuncMetadata where R: Variant + Clone, FUNC: RhaiNativeFunc + SendSync + 'static, { #[cfg(feature = "metadata")] { // Do not update parameter information if `with_params_info` was called previously. if self.metadata.params_info.is_empty() { let mut param_type_names = FUNC::param_names() .iter() .map(|ty| format!("_: {}", engine.format_param_type(ty))) .collect::>(); if FUNC::return_type() != TypeId::of::<()>() { param_type_names .push(engine.format_param_type(FUNC::return_type_name()).into()); } let param_type_names = param_type_names .iter() .map(String::as_str) .collect::>(); self.with_params_info(param_type_names) } else { self } // Duplicate of code without metadata feature because it would // require to set self as mut, which would trigger a warning without // the metadata feature. .in_global_namespace() .set_into_module(engine.global_namespace_mut(), func) } #[cfg(not(feature = "metadata"))] self.in_global_namespace() .set_into_module(engine.global_namespace_mut(), func) } /// Register the function into the specified [`Module`]. #[inline] pub fn set_into_module( self, module: &mut Module, func: FUNC, ) -> &FuncMetadata where R: Variant + Clone, FUNC: RhaiNativeFunc + SendSync + 'static, { let is_pure = self.purity.unwrap_or_else(|| { // default to pure unless specified let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] let is_pure = is_pure && (FUNC::num_params() != 3 || self.metadata.name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] let is_pure = is_pure && (FUNC::num_params() != 2 || !self.metadata.name.starts_with(crate::engine::FN_SET)); is_pure }); let is_volatile = self.volatility.unwrap_or(false); let func = func.into_rhai_function(is_pure, is_volatile); // Clear flags let mut reg = self; reg.purity = None; reg.volatility = None; reg.set_into_module_raw(module, FUNC::param_types(), func) } /// Register the function into the specified [`Module`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s /// indicating the actual types of the parameters. /// /// # Panics /// /// Panics if the type of the first parameter is [`Array`][crate::Array], [`Map`][crate::Map], /// [`String`], [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT] and /// the function name indicates that it is an index getter or setter. /// /// Indexers for arrays, object maps, strings and integers cannot be registered. #[inline] pub fn set_into_module_raw( self, module: &mut Module, arg_types: impl AsRef<[TypeId]>, func: RhaiFunc, ) -> &FuncMetadata { // Make sure that conflicting flags should not be set. debug_assert!(self.purity.is_none()); debug_assert!(self.volatility.is_none()); let mut f = self.metadata; f.num_params = arg_types.as_ref().len(); f.param_types.extend(arg_types.as_ref().iter().copied()); #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] if (f.name == crate::engine::FN_IDX_GET && f.num_params == 2) || (f.name == crate::engine::FN_IDX_SET && f.num_params == 3) { if let Some(&type_id) = f.param_types.first() { #[cfg(not(feature = "no_index"))] assert!( type_id != TypeId::of::(), "Cannot register indexer for arrays." ); #[cfg(not(feature = "no_object"))] assert!( type_id != TypeId::of::(), "Cannot register indexer for object maps." ); assert!( type_id != TypeId::of::() && type_id != TypeId::of::<&str>() && type_id != TypeId::of::(), "Cannot register indexer for strings." ); assert!( type_id != TypeId::of::(), "Cannot register indexer for integers." ); } } let is_method = func.is_method(); f.param_types .iter_mut() .enumerate() .for_each(|(i, type_id)| *type_id = Module::map_type(!is_method || i > 0, *type_id)); let is_dynamic = f .param_types .iter() .any(|&type_id| type_id == TypeId::of::()); #[cfg(feature = "metadata")] if f.params_info.len() > f.param_types.len() { f.return_type = f.params_info.pop().unwrap(); } let hash_base = calc_fn_hash(None, &f.name, f.param_types.len()); let hash_fn = calc_fn_hash_full(hash_base, f.param_types.iter().copied()); f.hash = hash_fn; // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] if let Some(fx) = module.functions.as_ref().and_then(|f| f.get(&hash_base)) { unreachable!( "Hash {} already exists when registering function {}:\n{:#?}", hash_base, f.name, fx ); } if is_dynamic { module.dynamic_functions_filter.mark(hash_base); } module .flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); let entry = match module .functions .get_or_insert_with(|| new_hash_map(FN_MAP_SIZE)) .entry(hash_fn) { Entry::Occupied(mut entry) => { entry.insert((func, f.into())); entry.into_mut() } Entry::Vacant(entry) => entry.insert((func, f.into())), }; &entry.1 } } bitflags! { /// Bit-flags containing all status for [`Module`]. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] struct ModuleFlags: u8 { /// Is the [`Module`] internal? const INTERNAL = 0b0000_0001; /// Is the [`Module`] part of a standard library? const STANDARD_LIB = 0b0000_0010; /// Is the [`Module`] indexed? const INDEXED = 0b0000_0100; /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? const INDEXED_GLOBAL_FUNCTIONS = 0b0000_1000; } } /// A module which may contain variables, sub-modules, external Rust functions, /// and/or script-defined functions. #[derive(Clone)] pub struct Module { /// ID identifying the module. id: Option, /// Module documentation. #[cfg(feature = "metadata")] doc: SmartString, /// Custom types. custom_types: CustomTypesCollection, /// Sub-modules. modules: BTreeMap, /// [`Module`] variables. variables: BTreeMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: Option>, /// Functions (both native Rust and scripted). functions: Option)>>, /// Flattened collection of all functions, native Rust and scripted. /// including those in sub-modules. all_functions: Option>, /// Bloom filter on native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. dynamic_functions_filter: BloomFilterU64, /// Iterator functions, keyed by the type producing the iterator. type_iterators: BTreeMap>, /// Flattened collection of iterator functions, including those in sub-modules. all_type_iterators: BTreeMap>, /// Flags. flags: ModuleFlags, } impl Default for Module { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl fmt::Debug for Module { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); d.field("id", &self.id) .field( "custom_types", &self.custom_types.iter().map(|(k, _)| k).collect::>(), ) .field( "modules", &self .modules .keys() .map(SmartString::as_str) .collect::>(), ) .field("vars", &self.variables) .field( "functions", &self .iter_fn() .map(|(_f, _m)| { #[cfg(not(feature = "metadata"))] return _f.to_string(); #[cfg(feature = "metadata")] return _m.gen_signature(Into::into); }) .collect::>(), ) .field("flags", &self.flags); #[cfg(feature = "metadata")] d.field("doc", &self.doc); d.finish() } } #[cfg(not(feature = "no_function"))] impl>> From for Module { fn from(iter: T) -> Self { let mut module = Self::new(); iter.into_iter().for_each(|fn_def| { module.set_script_fn(fn_def); }); module } } #[cfg(not(feature = "no_function"))] impl>> Extend for Module { fn extend>(&mut self, iter: ITER) { iter.into_iter().for_each(|fn_def| { self.set_script_fn(fn_def); }); } } impl> Add for &Module { type Output = Module; #[inline] fn add(self, rhs: M) -> Self::Output { let mut module = self.clone(); module.merge(rhs.as_ref()); module } } impl> Add for Module { type Output = Self; #[inline(always)] fn add(mut self, rhs: M) -> Self::Output { self.merge(rhs.as_ref()); self } } impl> AddAssign for Module { #[inline(always)] fn add_assign(&mut self, rhs: M) { self.combine(rhs.into()); } } #[inline(always)] fn new_hash_map(size: usize) -> StraightHashMap { StraightHashMap::with_capacity_and_hasher(size, <_>::default()) } impl Module { /// Create a new [`Module`]. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").expect("answer should exist"), 42); /// ``` #[inline(always)] #[must_use] pub const fn new() -> Self { Self { id: None, #[cfg(feature = "metadata")] doc: SmartString::new_const(), custom_types: CustomTypesCollection::new(), modules: BTreeMap::new(), variables: BTreeMap::new(), all_variables: None, functions: None, all_functions: None, dynamic_functions_filter: BloomFilterU64::new(), type_iterators: BTreeMap::new(), all_type_iterators: BTreeMap::new(), flags: ModuleFlags::INDEXED, } } /// Get the ID of the [`Module`], if any. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_id("hello"); /// assert_eq!(module.id(), Some("hello")); /// ``` #[inline] #[must_use] pub fn id(&self) -> Option<&str> { self.id.as_deref() } /// Get the ID of the [`Module`] as an [`Identifier`], if any. #[inline(always)] #[must_use] pub(crate) const fn id_raw(&self) -> Option<&ImmutableString> { self.id.as_ref() } /// Set the ID of the [`Module`]. /// /// If the string is empty, it is equivalent to clearing the ID. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_id("hello"); /// assert_eq!(module.id(), Some("hello")); /// ``` #[inline(always)] pub fn set_id(&mut self, id: impl Into) -> &mut Self { let id = id.into(); self.id = (!id.is_empty()).then_some(id); self } /// Clear the ID of the [`Module`]. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_id("hello"); /// assert_eq!(module.id(), Some("hello")); /// module.clear_id(); /// assert_eq!(module.id(), None); /// ``` #[inline(always)] pub fn clear_id(&mut self) -> &mut Self { self.id = None; self } /// Get the documentation of the [`Module`], if any. /// Exported under the `metadata` feature only. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_doc("//! This is my special module."); /// assert_eq!(module.doc(), "//! This is my special module."); /// ``` #[cfg(feature = "metadata")] #[inline(always)] #[must_use] pub fn doc(&self) -> &str { &self.doc } /// Set the documentation of the [`Module`]. /// Exported under the `metadata` feature only. /// /// If the string is empty, it is equivalent to clearing the documentation. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_doc("//! This is my special module."); /// assert_eq!(module.doc(), "//! This is my special module."); /// ``` #[cfg(feature = "metadata")] #[inline(always)] pub fn set_doc(&mut self, doc: impl Into) -> &mut Self { self.doc = doc.into(); self } /// Clear the documentation of the [`Module`]. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_doc("//! This is my special module."); /// assert_eq!(module.doc(), "//! This is my special module."); /// module.clear_doc(); /// assert_eq!(module.doc(), ""); /// ``` #[cfg(feature = "metadata")] #[inline(always)] pub fn clear_doc(&mut self) -> &mut Self { self.doc.clear(); self } /// Clear the [`Module`]. #[inline(always)] pub fn clear(&mut self) { #[cfg(feature = "metadata")] self.doc.clear(); self.custom_types.clear(); self.modules.clear(); self.variables.clear(); self.all_variables = None; self.functions = None; self.all_functions = None; self.dynamic_functions_filter.clear(); self.type_iterators.clear(); self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); } /// Map a custom type to a friendly display name. /// /// # Example /// /// ``` /// # use rhai::Module; /// #[derive(Clone)] /// struct TestStruct; /// /// let name = std::any::type_name::(); /// /// let mut module = Module::new(); /// /// module.set_custom_type::("MyType"); /// /// assert_eq!(module.get_custom_type_display_by_name(name), Some("MyType")); /// ``` #[inline(always)] pub fn set_custom_type(&mut self, name: &str) -> &mut Self { self.custom_types.add_type::(name); self } /// Map a custom type to a friendly display name. /// Exported under the `metadata` feature only. /// /// ## Comments /// /// Block doc-comments should be kept in a separate string slice. /// /// Line doc-comments should be merged, with line-breaks, into a single string slice without a final termination line-break. /// /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding /// doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments should start with `///`. #[cfg(feature = "metadata")] #[inline(always)] pub fn set_custom_type_with_comments(&mut self, name: &str, comments: &[&str]) -> &mut Self { self.custom_types .add_type_with_comments::(name, comments); self } /// Map a custom type to a friendly display name. /// /// ``` /// # use rhai::Module; /// #[derive(Clone)] /// struct TestStruct; /// /// let name = std::any::type_name::(); /// /// let mut module = Module::new(); /// /// module.set_custom_type_raw(name, "MyType"); /// /// assert_eq!(module.get_custom_type_display_by_name(name), Some("MyType")); /// ``` #[inline(always)] pub fn set_custom_type_raw( &mut self, type_name: impl Into, display_name: impl Into, ) -> &mut Self { self.custom_types.add(type_name, display_name); self } /// Map a custom type to a friendly display name. /// Exported under the `metadata` feature only. /// /// ## Comments /// /// Block doc-comments should be kept in a separate string slice. /// /// Line doc-comments should be merged, with line-breaks, into a single string slice without a final termination line-break. /// /// Leading white-spaces should be stripped, and each string slice always starts with the corresponding /// doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments should start with `///`. #[cfg(feature = "metadata")] #[inline(always)] pub fn set_custom_type_with_comments_raw>( &mut self, type_name: impl Into, display_name: impl Into, comments: impl IntoIterator, ) -> &mut Self { self.custom_types .add_with_comments(type_name, display_name, comments); self } /// Get the display name of a registered custom type. /// /// # Example /// /// ``` /// # use rhai::Module; /// #[derive(Clone)] /// struct TestStruct; /// /// let name = std::any::type_name::(); /// /// let mut module = Module::new(); /// /// module.set_custom_type::("MyType"); /// /// assert_eq!(module.get_custom_type_display_by_name(name), Some("MyType")); /// ``` #[inline] #[must_use] pub fn get_custom_type_display_by_name(&self, type_name: &str) -> Option<&str> { self.get_custom_type_by_name_raw(type_name) .map(|typ| typ.display_name.as_str()) } /// Get the display name of a registered custom type. /// /// # Example /// /// ``` /// # use rhai::Module; /// #[derive(Clone)] /// struct TestStruct; /// /// let name = std::any::type_name::(); /// /// let mut module = Module::new(); /// /// module.set_custom_type::("MyType"); /// /// assert_eq!(module.get_custom_type_display::(), Some("MyType")); /// ``` #[inline(always)] #[must_use] pub fn get_custom_type_display(&self) -> Option<&str> { self.get_custom_type_display_by_name(type_name::()) } /// _(internals)_ Get a registered custom type . /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn get_custom_type_raw(&self) -> Option<&CustomTypeInfo> { self.get_custom_type_by_name_raw(type_name::()) } /// _(internals)_ Get a registered custom type by its type name. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn get_custom_type_by_name_raw(&self, type_name: &str) -> Option<&CustomTypeInfo> { self.custom_types.get(type_name) } /// Returns `true` if this [`Module`] contains no items. /// /// # Example /// /// ``` /// # use rhai::Module; /// let module = Module::new(); /// assert!(module.is_empty()); /// ``` #[inline] #[must_use] pub fn is_empty(&self) -> bool { !self.flags.intersects(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS) && self .functions .as_ref() .map_or(true, StraightHashMap::is_empty) && self.variables.is_empty() && self.modules.is_empty() && self.type_iterators.is_empty() && self .all_functions .as_ref() .map_or(true, StraightHashMap::is_empty) && self .all_variables .as_ref() .map_or(true, StraightHashMap::is_empty) && self.all_type_iterators.is_empty() } /// Is the [`Module`] indexed? /// /// A module must be indexed before it can be used in an `import` statement. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// assert!(module.is_indexed()); /// /// module.set_native_fn("foo", |x: &mut i64, y: i64| { *x = y; Ok(()) }); /// assert!(!module.is_indexed()); /// /// # #[cfg(not(feature = "no_module"))] /// # { /// module.build_index(); /// assert!(module.is_indexed()); /// # } /// ``` #[inline(always)] #[must_use] pub const fn is_indexed(&self) -> bool { self.flags.intersects(ModuleFlags::INDEXED) } /// Is the [`Module`] an internal Rhai system module? #[inline(always)] #[must_use] pub const fn is_internal(&self) -> bool { self.flags.intersects(ModuleFlags::INTERNAL) } /// Set whether the [`Module`] is a Rhai internal system module. #[inline(always)] pub(crate) fn set_internal(&mut self, value: bool) { self.flags.set(ModuleFlags::INTERNAL, value) } /// Is the [`Module`] a Rhai standard library module? #[inline(always)] #[must_use] pub const fn is_standard_lib(&self) -> bool { self.flags.intersects(ModuleFlags::STANDARD_LIB) } /// Set whether the [`Module`] is a Rhai standard library module. #[inline(always)] pub(crate) fn set_standard_lib(&mut self, value: bool) { self.flags.set(ModuleFlags::STANDARD_LIB, value) } /// _(metadata)_ Generate signatures for all the non-private functions in the [`Module`]. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline] pub fn gen_fn_signatures_with_mapper<'a>( &'a self, type_mapper: impl Fn(&'a str) -> std::borrow::Cow<'a, str> + 'a, ) -> impl Iterator + 'a { self.iter_fn() .map(|(_, m)| m) .filter(|&f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) .map(move |m| m.gen_signature(&type_mapper)) } /// Does a variable exist in the [`Module`]? /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_var("answer", 42_i64); /// assert!(module.contains_var("answer")); /// ``` #[inline(always)] #[must_use] pub fn contains_var(&self, name: &str) -> bool { self.variables.contains_key(name) } /// Get the value of a [`Module`] variable. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").expect("answer should exist"), 42); /// ``` #[inline] #[must_use] pub fn get_var_value(&self, name: &str) -> Option { self.get_var(name).and_then(Dynamic::try_cast::) } /// Get a [`Module`] variable as a [`Dynamic`]. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var("answer").expect("answer should exist").cast::(), 42); /// ``` #[inline(always)] #[must_use] pub fn get_var(&self, name: &str) -> Option { self.variables.get(name).cloned() } /// Set a variable into the [`Module`]. /// /// If there is an existing variable of the same name, it is replaced. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// module.set_var("answer", 42_i64); /// assert_eq!(module.get_var_value::("answer").expect("answer should exist"), 42); /// ``` #[inline] pub fn set_var( &mut self, name: impl Into, value: impl Variant + Clone, ) -> &mut Self { let ident = name.into(); let value = Dynamic::from(value); if self.is_indexed() { let hash_var = crate::calc_var_hash(Some(""), &ident); // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] assert!( self.all_variables .as_ref() .map_or(true, |map| !map.contains_key(&hash_var)), "Hash {} already exists when registering variable {}", hash_var, ident ); self.all_variables .get_or_insert_with(Default::default) .insert(hash_var, value.clone()); } self.variables.insert(ident, value); self } /// Get a namespace-qualified [`Module`] variable as a [`Dynamic`]. #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Option { self.all_variables .as_ref() .and_then(|c| c.get(&hash_var).cloned()) } /// Set a script-defined function into the [`Module`]. /// /// If there is an existing function of the same name and number of arguments, it is replaced. #[cfg(not(feature = "no_function"))] #[inline] pub fn set_script_fn(&mut self, fn_def: impl Into>) -> u64 { let fn_def = fn_def.into(); // None + function name + number of arguments. let namespace = FnNamespace::Internal; let num_params = fn_def.params.len(); let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params); #[cfg(not(feature = "no_object"))] let (hash_script, namespace) = fn_def .this_type .as_ref() .map_or((hash_script, namespace), |this_type| { ( crate::calc_typed_method_hash(hash_script, this_type), FnNamespace::Global, ) }); // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] if let Some(f) = self.functions.as_ref().and_then(|f| f.get(&hash_script)) { unreachable!( "Hash {} already exists when registering function {:#?}:\n{:#?}", hash_script, fn_def, f ); } let metadata = FuncMetadata { hash: hash_script, name: fn_def.name.as_str().into(), namespace, access: fn_def.access, num_params, param_types: FnArgsVec::new_const(), #[cfg(feature = "metadata")] params_info: fn_def.params.iter().map(Into::into).collect(), #[cfg(feature = "metadata")] return_type: <_>::default(), #[cfg(feature = "metadata")] comments: crate::StaticVec::new_const(), }; self.functions .get_or_insert_with(|| new_hash_map(FN_MAP_SIZE)) .insert(hash_script, (fn_def.into(), metadata.into())); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); hash_script } /// Get a shared reference to the script-defined function in the [`Module`] based on name /// and number of parameters. #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub fn get_script_fn( &self, name: impl AsRef, num_params: usize, ) -> Option<&Shared> { self.functions.as_ref().and_then(|lib| { let name = name.as_ref(); lib.values() .find(|(_, f)| f.num_params == num_params && f.name == name) .and_then(|(f, _)| f.get_script_fn_def()) }) } /// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules, /// creating one if empty. /// /// # WARNING /// /// By taking a mutable reference, it is assumed that some sub-modules will be modified. /// Thus the [`Module`] is automatically set to be non-indexed. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub(crate) fn get_sub_modules_mut(&mut self) -> &mut BTreeMap { // We must assume that the user has changed the sub-modules // (otherwise why take a mutable reference?) self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); &mut self.modules } /// Does a sub-module exist in the [`Module`]? /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let sub_module = Module::new(); /// module.set_sub_module("question", sub_module); /// assert!(module.contains_sub_module("question")); /// ``` #[inline(always)] #[must_use] pub fn contains_sub_module(&self, name: &str) -> bool { self.modules.contains_key(name) } /// Get a sub-module in the [`Module`]. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let sub_module = Module::new(); /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module("question").is_some()); /// ``` #[inline] #[must_use] pub fn get_sub_module(&self, name: &str) -> Option<&Self> { self.modules.get(name).map(|m| &**m) } /// Set a sub-module into the [`Module`]. /// /// If there is an existing sub-module of the same name, it is replaced. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let sub_module = Module::new(); /// module.set_sub_module("question", sub_module); /// assert!(module.get_sub_module("question").is_some()); /// ``` #[inline] pub fn set_sub_module( &mut self, name: impl Into, sub_module: impl Into, ) -> &mut Self { self.modules.insert(name.into(), sub_module.into()); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); self } /// Does the particular Rust function exist in the [`Module`]? /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let hash = module.set_native_fn("calc", |x: i64| Ok(42 + x)); /// assert!(module.contains_fn(hash)); /// ``` #[inline] #[must_use] pub fn contains_fn(&self, hash_fn: u64) -> bool { self.functions .as_ref() .map_or(false, |m| m.contains_key(&hash_fn)) } /// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a registered function. /// Exported under the `metadata` feature only. /// /// # Deprecated /// /// This method is deprecated. /// Use the [`FuncRegistration`] API instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use the `FuncRegistration` API instead")] #[cfg(feature = "metadata")] #[inline] pub fn update_fn_metadata_with_comments, C: Into>( &mut self, hash_fn: u64, arg_names: impl IntoIterator, comments: impl IntoIterator, ) -> &mut Self { let mut params_info = arg_names .into_iter() .map(Into::into) .collect::>(); if let Some((_, f)) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { let (params_info, return_type_name) = if params_info.len() > f.num_params { let return_type = params_info.pop().unwrap(); (params_info, return_type) } else { (params_info, crate::SmartString::new_const()) }; f.params_info = params_info; f.return_type = return_type_name; f.comments = comments.into_iter().map(Into::into).collect(); } self } /// Update the namespace of a registered function. /// /// # Deprecated /// /// This method is deprecated. /// Use the [`FuncRegistration`] API instead. /// /// This method will be removed in the next major version. #[deprecated(since = "1.17.0", note = "use the `FuncRegistration` API instead")] #[inline] pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { if let Some((_, f)) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { f.namespace = namespace; self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); } self } /// Get a registered function's metadata. #[inline] #[allow(dead_code)] pub(crate) fn get_fn_metadata_mut(&mut self, hash_fn: u64) -> Option<&mut FuncMetadata> { self.functions .as_mut() .and_then(|m| m.get_mut(&hash_fn)) .map(|(_, f)| f.as_mut()) } /// Remap type ID. #[inline] #[must_use] fn map_type(map: bool, type_id: TypeId) -> TypeId { if !map { return type_id; } if type_id == TypeId::of::<&str>() { // Map &str to ImmutableString return TypeId::of::(); } if type_id == TypeId::of::() { // Map String to ImmutableString return TypeId::of::(); } type_id } /// Set a native Rust function into the [`Module`] based on a [`FuncRegistration`]. /// /// # WARNING - Low Level API /// /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s /// indicating the actual types of the parameters. #[inline(always)] pub fn set_fn_raw_with_options( &mut self, options: FuncRegistration, arg_types: impl AsRef<[TypeId]>, func: RhaiFunc, ) -> &FuncMetadata { options.set_into_module_raw(self, arg_types, func) } /// Set a native Rust function into the [`Module`], returning a [`u64`] hash key. /// /// If there is a similar existing Rust function, it is replaced. /// /// # Use `FuncRegistration` API /// /// It is recommended that the [`FuncRegistration`] API be used instead. /// /// Essentially, this method is a shortcut for: /// /// ```text /// FuncRegistration::new(name) /// .in_internal_namespace() /// .with_purity(true) /// .with_volatility(false) /// .set_into_module(module, func) /// .hash /// ``` /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Internal`]. /// /// * **Purity**: The function is assumed to be _pure_ unless it is a property setter or an index setter. /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. /// /// To change these assumptions, use the [`FuncRegistration`] API instead. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let hash = module.set_native_fn("calc", |x: i64| Ok(42 + x)); /// assert!(module.contains_fn(hash)); /// ``` #[inline] pub fn set_native_fn( &mut self, name: impl Into, func: FUNC, ) -> u64 where R: Variant + Clone, FUNC: RhaiNativeFunc + SendSync + 'static, { FuncRegistration::new(name) .in_internal_namespace() .with_purity(true) .with_volatility(false) .set_into_module(self, func) .hash } /// Set a Rust getter function taking one mutable parameter, returning a [`u64`] hash key. /// This function is automatically exposed to the global namespace. /// /// If there is a similar existing Rust getter function, it is replaced. /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _pure_ (so it can be called on constants). /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. /// /// To change these assumptions, use the [`FuncRegistration`] API instead. /// /// # Example /// /// ``` /// # use rhai::Module; /// let mut module = Module::new(); /// let hash = module.set_getter_fn("value", |x: &mut i64| Ok(*x)); /// assert!(module.contains_fn(hash)); /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_getter_fn( &mut self, name: impl AsRef, func: FUNC, ) -> u64 where A: Variant + Clone, R: Variant + Clone, FUNC: RhaiNativeFunc<(Mut,), 1, X, R, true> + SendSync + 'static, { FuncRegistration::new(crate::engine::make_getter(name.as_ref())) .in_global_namespace() .with_purity(true) .with_volatility(false) .set_into_module(self, func) .hash } /// Set a Rust setter function taking two parameters (the first one mutable) into the [`Module`], /// returning a [`u64`] hash key. /// This function is automatically exposed to the global namespace. /// /// If there is a similar existing setter Rust function, it is replaced. /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _non-pure_ (so it cannot be called on constants). /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. /// /// To change these assumptions, use the [`FuncRegistration`] API instead. /// /// # Example /// /// ``` /// use rhai::{Module, ImmutableString}; /// /// let mut module = Module::new(); /// let hash = module.set_setter_fn("value", |x: &mut i64, y: ImmutableString| { /// *x = y.len() as i64; Ok(()) /// }); /// assert!(module.contains_fn(hash)); /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_setter_fn( &mut self, name: impl AsRef, func: FUNC, ) -> u64 where A: Variant + Clone, R: Variant + Clone, FUNC: RhaiNativeFunc<(Mut, R), 2, X, (), true> + SendSync + 'static, { FuncRegistration::new(crate::engine::make_setter(name.as_ref())) .in_global_namespace() .with_purity(false) .with_volatility(false) .set_into_module(self, func) .hash } /// Set a pair of Rust getter and setter functions into the [`Module`], returning both [`u64`] hash keys. /// This is a short-hand for [`set_getter_fn`][Module::set_getter_fn] and [`set_setter_fn`][Module::set_setter_fn]. /// /// These function are automatically exposed to the global namespace. /// /// If there are similar existing Rust functions, they are replaced. /// /// To change these assumptions, use the [`FuncRegistration`] API instead. /// /// # Example /// /// ``` /// use rhai::{Module, ImmutableString}; /// /// let mut module = Module::new(); /// let (hash_get, hash_set) = /// module.set_getter_setter_fn("value", /// |x: &mut i64| Ok(x.to_string().into()), /// |x: &mut i64, y: ImmutableString| { *x = y.len() as i64; Ok(()) } /// ); /// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_set)); /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_getter_setter_fn< A: Variant + Clone, const X1: bool, const X2: bool, R: Variant + Clone, >( &mut self, name: impl AsRef, getter: impl RhaiNativeFunc<(Mut,), 1, X1, R, true> + SendSync + 'static, setter: impl RhaiNativeFunc<(Mut, R), 2, X2, (), true> + SendSync + 'static, ) -> (u64, u64) { ( self.set_getter_fn(name.as_ref(), getter), self.set_setter_fn(name.as_ref(), setter), ) } /// Set a Rust index getter taking two parameters (the first one mutable) into the [`Module`], /// returning a [`u64`] hash key. /// This function is automatically exposed to the global namespace. /// /// If there is a similar existing setter Rust function, it is replaced. /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _pure_ (so it can be called on constants). /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// * **Metadata**: No metadata for the function is registered. /// /// To change these assumptions, use the [`FuncRegistration`] API instead. /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// use rhai::{Module, ImmutableString}; /// /// #[derive(Clone)] /// struct TestStruct(i64); /// /// let mut module = Module::new(); /// /// let hash = module.set_indexer_get_fn( /// |x: &mut TestStruct, y: ImmutableString| Ok(x.0 + y.len() as i64) /// ); /// /// assert!(module.contains_fn(hash)); /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline] pub fn set_indexer_get_fn(&mut self, func: FUNC) -> u64 where A: Variant + Clone, B: Variant + Clone, R: Variant + Clone, FUNC: RhaiNativeFunc<(Mut, B), 2, X, R, true> + SendSync + 'static, { FuncRegistration::new(crate::engine::FN_IDX_GET) .in_global_namespace() .with_purity(true) .with_volatility(false) .set_into_module(self, func) .hash } /// Set a Rust index setter taking three parameters (the first one mutable) into the [`Module`], /// returning a [`u64`] hash key. /// This function is automatically exposed to the global namespace. /// /// If there is a similar existing Rust function, it is replaced. /// /// # Assumptions /// /// * **Accessibility**: The function namespace is [`FnNamespace::Global`]. /// /// * **Purity**: The function is assumed to be _non-pure_ (so it cannot be called on constants). /// /// * **Volatility**: The function is assumed to be _non-volatile_ -- i.e. it guarantees the same result for the same input(s). /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// use rhai::{Module, ImmutableString}; /// /// #[derive(Clone)] /// struct TestStruct(i64); /// /// let mut module = Module::new(); /// /// let hash = module.set_indexer_set_fn(|x: &mut TestStruct, y: ImmutableString, value: i64| { /// *x = TestStruct(y.len() as i64 + value); /// Ok(()) /// }); /// /// assert!(module.contains_fn(hash)); /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline] pub fn set_indexer_set_fn(&mut self, func: FUNC) -> u64 where A: Variant + Clone, B: Variant + Clone, R: Variant + Clone, FUNC: RhaiNativeFunc<(Mut, B, R), 3, X, (), true> + SendSync + 'static, { FuncRegistration::new(crate::engine::FN_IDX_SET) .in_global_namespace() .with_purity(false) .with_volatility(false) .set_into_module(self, func) .hash } /// Set a pair of Rust index getter and setter functions into the [`Module`], returning both [`u64`] hash keys. /// This is a short-hand for [`set_indexer_get_fn`][Module::set_indexer_get_fn] and /// [`set_indexer_set_fn`][Module::set_indexer_set_fn]. /// /// These functions are automatically exposed to the global namespace. /// /// If there are similar existing Rust functions, they are replaced. /// /// # Panics /// /// Panics if the type is [`Array`][crate::Array], [`Map`][crate::Map], [`String`], /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. /// /// Indexers for arrays, object maps, strings and integers cannot be registered. /// /// # Example /// /// ``` /// use rhai::{Module, ImmutableString}; /// /// #[derive(Clone)] /// struct TestStruct(i64); /// /// let mut module = Module::new(); /// /// let (hash_get, hash_set) = module.set_indexer_get_set_fn( /// |x: &mut TestStruct, y: ImmutableString| Ok(x.0 + y.len() as i64), /// |x: &mut TestStruct, y: ImmutableString, value: i64| { *x = TestStruct(y.len() as i64 + value); Ok(()) } /// ); /// /// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_set)); /// ``` #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] pub fn set_indexer_get_set_fn< A: Variant + Clone, B: Variant + Clone, const X1: bool, const X2: bool, R: Variant + Clone, >( &mut self, get_fn: impl RhaiNativeFunc<(Mut, B), 2, X1, R, true> + SendSync + 'static, set_fn: impl RhaiNativeFunc<(Mut, B, R), 3, X2, (), true> + SendSync + 'static, ) -> (u64, u64) { ( self.set_indexer_get_fn(get_fn), self.set_indexer_set_fn(set_fn), ) } /// Look up a native Rust function by hash. #[inline] #[must_use] pub(crate) fn get_fn(&self, hash_native: u64) -> Option<&RhaiFunc> { self.functions .as_ref() .and_then(|m| m.get(&hash_native)) .map(|(f, _)| f) } /// Can the particular function with [`Dynamic`] parameter(s) exist in the [`Module`]? /// /// A `true` return value does not automatically imply that the function _must_ exist. #[inline(always)] #[must_use] pub(crate) const fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { !self.dynamic_functions_filter.is_absent(hash_script) } /// Does the particular namespace-qualified function exist in the [`Module`]? /// /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. #[inline(always)] #[must_use] pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { self.all_functions .as_ref() .map_or(false, |m| m.contains_key(&hash_fn)) } /// Get a namespace-qualified function. /// /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&RhaiFunc> { self.all_functions .as_ref() .and_then(|m| m.get(&hash_qualified_fn)) } /// Combine another [`Module`] into this [`Module`]. /// The other [`Module`] is _consumed_ to merge into this [`Module`]. #[inline] pub fn combine(&mut self, other: Self) -> &mut Self { self.modules.extend(other.modules); self.variables.extend(other.variables); match self.functions { Some(ref mut m) if other.functions.is_some() => m.extend(other.functions.unwrap()), Some(_) => (), None => self.functions = other.functions, } self.dynamic_functions_filter += other.dynamic_functions_filter; self.type_iterators.extend(other.type_iterators); self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); #[cfg(feature = "metadata")] { if !self.doc.is_empty() { self.doc.push_str("\n"); } self.doc.push_str(&other.doc); } self } /// Combine another [`Module`] into this [`Module`]. /// The other [`Module`] is _consumed_ to merge into this [`Module`]. /// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level. #[inline] pub fn combine_flatten(&mut self, other: Self) -> &mut Self { for m in other.modules.into_values() { self.combine_flatten(shared_take_or_clone(m)); } self.variables.extend(other.variables); match self.functions { Some(ref mut m) if other.functions.is_some() => m.extend(other.functions.unwrap()), Some(_) => (), None => self.functions = other.functions, } self.dynamic_functions_filter += other.dynamic_functions_filter; self.type_iterators.extend(other.type_iterators); self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); #[cfg(feature = "metadata")] { if !self.doc.is_empty() { self.doc.push_str("\n"); } self.doc.push_str(&other.doc); } self } /// Polyfill this [`Module`] with another [`Module`]. /// Only items not existing in this [`Module`] are added. #[inline] pub fn fill_with(&mut self, other: &Self) -> &mut Self { for (k, v) in &other.modules { if !self.modules.contains_key(k) { self.modules.insert(k.clone(), v.clone()); } } for (k, v) in &other.variables { if !self.variables.contains_key(k) { self.variables.insert(k.clone(), v.clone()); } } if let Some(ref functions) = other.functions { let others_len = functions.len(); for (&k, f) in functions { let map = self .functions .get_or_insert_with(|| new_hash_map(FN_MAP_SIZE)); map.reserve(others_len); map.entry(k).or_insert_with(|| f.clone()); } } self.dynamic_functions_filter += &other.dynamic_functions_filter; for (&k, v) in &other.type_iterators { self.type_iterators.entry(k).or_insert_with(|| v.clone()); } self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); #[cfg(feature = "metadata")] { if !self.doc.is_empty() { self.doc.push_str("\n"); } self.doc.push_str(&other.doc); } self } /// Merge another [`Module`] into this [`Module`]. #[inline(always)] pub fn merge(&mut self, other: &Self) -> &mut Self { self.merge_filtered(other, |_, _, _, _, _| true) } /// Merge another [`Module`] into this [`Module`] based on a filter predicate. pub(crate) fn merge_filtered( &mut self, other: &Self, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, ) -> &mut Self { for (k, v) in &other.modules { let mut m = Self::new(); m.merge_filtered(v, _filter); self.set_sub_module(k.clone(), m); } #[cfg(feature = "no_function")] self.modules.extend(other.modules.clone()); self.variables.extend(other.variables.clone()); if let Some(ref functions) = other.functions { match self.functions { Some(ref mut m) => m.extend( functions .iter() .filter(|(.., (f, m))| { _filter(m.namespace, m.access, f.is_script(), &m.name, m.num_params) }) .map(|(&k, f)| (k, f.clone())), ), None => self.functions.clone_from(&other.functions), } } self.dynamic_functions_filter += &other.dynamic_functions_filter; self.type_iterators.extend(other.type_iterators.clone()); self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); #[cfg(feature = "metadata")] { if !self.doc.is_empty() { self.doc.push_str("\n"); } self.doc.push_str(&other.doc); } self } /// Filter out the functions, retaining only some script-defined functions based on a filter predicate. #[cfg(not(feature = "no_function"))] #[inline] pub(crate) fn retain_script_functions( &mut self, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { self.functions = std::mem::take(&mut self.functions).map(|m| { m.into_iter() .filter(|(.., (f, m))| { if f.is_script() { filter(m.namespace, m.access, &m.name, m.num_params) } else { false } }) .collect() }); self.dynamic_functions_filter.clear(); self.all_functions = None; self.all_variables = None; self.all_type_iterators.clear(); self.flags .remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS); self } /// Get the number of variables, functions and type iterators in the [`Module`]. #[inline(always)] #[must_use] pub fn count(&self) -> (usize, usize, usize) { ( self.variables.len(), self.functions.as_ref().map_or(0, StraightHashMap::len), self.type_iterators.len(), ) } /// Get an iterator to the sub-modules in the [`Module`]. #[inline(always)] pub fn iter_sub_modules(&self) -> impl Iterator { self.iter_sub_modules_raw().map(|(k, m)| (k.as_str(), m)) } /// Get an iterator to the sub-modules in the [`Module`]. #[inline(always)] pub(crate) fn iter_sub_modules_raw( &self, ) -> impl Iterator { self.modules.iter() } /// Get an iterator to the variables in the [`Module`]. #[inline(always)] pub fn iter_var(&self) -> impl Iterator { self.iter_var_raw().map(|(k, v)| (k.as_str(), v)) } /// Get an iterator to the variables in the [`Module`]. #[inline(always)] pub(crate) fn iter_var_raw(&self) -> impl Iterator { self.variables.iter() } /// Get an iterator to the custom types in the [`Module`]. #[inline(always)] #[allow(dead_code)] pub(crate) fn iter_custom_types(&self) -> impl Iterator { self.custom_types.iter() } /// Get an iterator to the functions in the [`Module`]. #[inline] #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { self.functions .iter() .flat_map(StraightHashMap::values) .map(|(f, m)| (f, &**m)) } /// Get an iterator over all script-defined functions in the [`Module`]. /// /// Function metadata includes: /// 1) Namespace ([`FnNamespace::Global`] or [`FnNamespace::Internal`]). /// 2) Access mode ([`FnAccess::Public`] or [`FnAccess::Private`]). /// 3) Function name (as string slice). /// 4) Number of parameters. /// 5) Shared reference to function definition [`ScriptFuncDef`][crate::ast::ScriptFuncDef]. #[cfg(not(feature = "no_function"))] #[inline] pub(crate) fn iter_script_fn( &self, ) -> impl Iterator< Item = ( FnNamespace, FnAccess, &str, usize, &Shared, ), > + '_ { self.iter_fn().filter(|(f, _)| f.is_script()).map(|(f, m)| { ( m.namespace, m.access, m.name.as_str(), m.num_params, f.get_script_fn_def().expect("`ScriptFuncDef`"), ) }) } /// _(internals)_ Get an iterator over all script-defined functions in the [`Module`]. /// Exported under the `internals` feature only. /// /// Function metadata includes: /// 1) Namespace ([`FnNamespace::Global`] or [`FnNamespace::Internal`]). /// 2) Access mode ([`FnAccess::Public`] or [`FnAccess::Private`]). /// 3) Function name (as string slice). /// 4) Number of parameters. /// 5) _(internals)_ Shared reference to function definition [`ScriptFuncDef`][crate::ast::ScriptFuncDef]. #[expose_under_internals] #[cfg(not(feature = "no_function"))] #[inline(always)] fn iter_script_fn_info( &self, ) -> impl Iterator< Item = ( FnNamespace, FnAccess, &str, usize, &Shared, ), > { self.iter_script_fn() } /// Create a new [`Module`] by evaluating an [`AST`][crate::AST]. /// /// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions to /// cross-call each other. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Module, Scope}; /// /// let engine = Engine::new(); /// let ast = engine.compile("let answer = 42; export answer;")?; /// let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; /// assert!(module.contains_var("answer")); /// assert_eq!(module.get_var_value::("answer").expect("answer should exist"), 42); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn eval_ast_as_new( scope: crate::Scope, ast: &crate::AST, engine: &crate::Engine, ) -> RhaiResultOf { let mut scope = scope; let global = &mut engine.new_global_runtime_state(); Self::eval_ast_as_new_raw(engine, &mut scope, global, ast) } /// Create a new [`Module`] by evaluating an [`AST`][crate::AST]. /// /// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions to /// cross-call each other. /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// In particular, the [`global`][crate::GlobalRuntimeState] parameter allows the entire /// calling environment to be encapsulated, including automatic global constants. #[cfg(not(feature = "no_module"))] pub fn eval_ast_as_new_raw( engine: &crate::Engine, scope: &mut crate::Scope, global: &mut crate::eval::GlobalRuntimeState, ast: &crate::AST, ) -> RhaiResultOf { // Save global state let orig_scope_len = scope.len(); let orig_imports_len = global.num_imports(); let orig_source = global.source.clone(); #[cfg(not(feature = "no_function"))] let orig_lib_len = global.lib.len(); #[cfg(not(feature = "no_function"))] let orig_constants = std::mem::take(&mut global.constants); // Run the script let caches = &mut crate::eval::Caches::new(); let result = engine.eval_ast_with_scope_raw(global, caches, scope, ast); // Create new module let mut module = Self::new(); // Extra modules left become sub-modules let mut imports = crate::ThinVec::new(); if result.is_ok() { global .scan_imports_raw() .skip(orig_imports_len) .for_each(|(k, m)| { imports.push((k.clone(), m.clone())); module.set_sub_module(k.clone(), m.clone()); }); } // Restore global state #[cfg(not(feature = "no_function"))] let constants = std::mem::replace(&mut global.constants, orig_constants); global.truncate_imports(orig_imports_len); #[cfg(not(feature = "no_function"))] global.lib.truncate(orig_lib_len); global.source = orig_source; // The return value is thrown away and not used let _ = result?; // Encapsulated environment #[cfg(not(feature = "no_function"))] let env = Shared::new(crate::ast::EncapsulatedEnviron { #[cfg(not(feature = "no_function"))] lib: ast.shared_lib().clone(), imports, #[cfg(not(feature = "no_function"))] constants, }); // Variables with an alias left in the scope become module variables let mut i = scope.len(); while i > 0 { i -= 1; let (mut _value, mut aliases) = if i >= orig_scope_len { let (_, v, a) = scope.pop_entry().unwrap(); (v, a) } else { let (_, v, a) = scope.get_entry_by_index(i); (v.clone(), a.to_vec()) }; #[cfg(not(feature = "no_function"))] _value.deep_scan(|v| { if let Some(fn_ptr) = v.downcast_mut::() { fn_ptr.env = Some(env.clone()); } }); match aliases.len() { 0 => (), 1 => { let alias = aliases.pop().unwrap(); if !module.contains_var(&alias) { module.set_var(alias, _value); } } _ => { // Avoid cloning the last value let mut first_alias = None; for alias in aliases { if module.contains_var(&alias) { continue; } if first_alias.is_none() { first_alias = Some(alias); } else { module.set_var(alias, _value.clone()); } } if let Some(alias) = first_alias { module.set_var(alias, _value); } } } } // Non-private functions defined become module functions #[cfg(not(feature = "no_function"))] ast.iter_fn_def() .filter(|&f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) .for_each(|f| { let hash = module.set_script_fn(f.clone()); if let (RhaiFunc::Script { env: ref mut e, .. }, _) = module.functions.as_mut().unwrap().get_mut(&hash).unwrap() { // Encapsulate AST environment *e = Some(env.clone()); } }); module.id = ast.source_raw().cloned(); #[cfg(feature = "metadata")] module.set_doc(ast.doc()); module.build_index(); Ok(module) } /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? /// /// # Panics /// /// Panics if the [`Module`] is not yet indexed via [`build_index`][Module::build_index]. #[inline(always)] #[must_use] pub const fn contains_indexed_global_functions(&self) -> bool { self.flags.intersects(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS) } /// Scan through all the sub-modules in the [`Module`] and build a hash index of all /// variables and functions as one flattened namespace. /// /// If the [`Module`] is already indexed, this method has no effect. pub fn build_index(&mut self) -> &mut Self { // Collect a particular module. fn index_module<'a>( module: &'a Module, path: &mut Vec<&'a str>, variables: &mut StraightHashMap, functions: &mut StraightHashMap, type_iterators: &mut BTreeMap>, ) -> bool { let mut contains_indexed_global_functions = false; for (name, m) in &module.modules { // Index all the sub-modules first. path.push(name); if index_module(m, path, variables, functions, type_iterators) { contains_indexed_global_functions = true; } path.pop(); } // Index all variables for (var_name, value) in &module.variables { let hash_var = crate::calc_var_hash(path.iter().copied(), var_name); // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] assert!( !variables.contains_key(&hash_var), "Hash {} already exists when indexing variable {}", hash_var, var_name ); variables.insert(hash_var, value.clone()); } // Index all type iterators for (&type_id, func) in &module.type_iterators { type_iterators.insert(type_id, func.clone()); } // Index all functions for (&hash, (f, m)) in module.functions.iter().flatten() { match m.namespace { FnNamespace::Global => { // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] if let Some(fx) = functions.get(&hash) { unreachable!( "Hash {} already exists when indexing function {:#?}:\n{:#?}", hash, f, fx ); } // Flatten all functions with global namespace functions.insert(hash, f.clone()); contains_indexed_global_functions = true; } FnNamespace::Internal => (), } match m.access { FnAccess::Public => (), FnAccess::Private => continue, // Do not index private functions } if f.is_script() { #[cfg(not(feature = "no_function"))] { let hash_script = crate::calc_fn_hash(path.iter().copied(), &m.name, m.num_params); #[cfg(not(feature = "no_object"))] let hash_script = f .get_script_fn_def() .unwrap() .this_type .as_ref() .map_or(hash_script, |this_type| { crate::calc_typed_method_hash(hash_script, this_type) }); // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] if let Some(fx) = functions.get(&hash_script) { unreachable!( "Hash {} already exists when indexing function {:#?}:\n{:#?}", hash_script, f, fx ); } functions.insert(hash_script, f.clone()); } } else { let hash_fn = calc_native_fn_hash(path.iter().copied(), &m.name, &m.param_types); // Catch hash collisions in testing environment only. #[cfg(feature = "testing-environ")] if let Some(fx) = functions.get(&hash_fn) { unreachable!( "Hash {} already exists when indexing function {:#?}:\n{:#?}", hash_fn, f, fx ); } functions.insert(hash_fn, f.clone()); } } contains_indexed_global_functions } if !self.is_indexed() { let mut path = Vec::with_capacity(4); let mut variables = new_hash_map(self.variables.len()); let mut functions = new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len)); let mut type_iterators = BTreeMap::new(); path.push(""); let has_global_functions = index_module( self, &mut path, &mut variables, &mut functions, &mut type_iterators, ); self.flags .set(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS, has_global_functions); self.all_variables = (!variables.is_empty()).then_some(variables); self.all_functions = (!functions.is_empty()).then_some(functions); self.all_type_iterators = type_iterators; self.flags |= ModuleFlags::INDEXED; } self } /// Does a type iterator exist in the entire module tree? #[inline(always)] #[must_use] pub fn contains_qualified_iter(&self, id: TypeId) -> bool { self.all_type_iterators.contains_key(&id) } /// Does a type iterator exist in the module? #[inline(always)] #[must_use] pub fn contains_iter(&self, id: TypeId) -> bool { self.type_iterators.contains_key(&id) } /// Set a type iterator into the [`Module`]. #[inline(always)] pub fn set_iter( &mut self, type_id: TypeId, func: impl Fn(Dynamic) -> Box> + SendSync + 'static, ) -> &mut Self { self.set_iter_result(type_id, move |x| { Box::new(func(x).map(Ok)) as Box>> }) } /// Set a fallible type iterator into the [`Module`]. #[inline] pub fn set_iter_result( &mut self, type_id: TypeId, func: impl Fn(Dynamic) -> Box>> + SendSync + 'static, ) -> &mut Self { let func = Shared::new(func); if self.is_indexed() { self.all_type_iterators.insert(type_id, func.clone()); } self.type_iterators.insert(type_id, func); self } /// Set a type iterator into the [`Module`]. #[inline(always)] pub fn set_iterable(&mut self) -> &mut Self where T: Variant + Clone + IntoIterator, ::Item: Variant + Clone, { self.set_iter(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().into_iter().map(Dynamic::from)) }) } /// Set a fallible type iterator into the [`Module`]. #[inline(always)] pub fn set_iterable_result(&mut self) -> &mut Self where T: Variant + Clone + IntoIterator>, X: Variant + Clone, { self.set_iter_result(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().into_iter().map(|v| v.map(Dynamic::from))) }) } /// Set an iterator type into the [`Module`] as a type iterator. #[inline(always)] pub fn set_iterator(&mut self) -> &mut Self where T: Variant + Clone + Iterator, ::Item: Variant + Clone, { self.set_iter(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().map(Dynamic::from)) }) } /// Set a iterator type into the [`Module`] as a fallible type iterator. #[inline(always)] pub fn set_iterator_result(&mut self) -> &mut Self where T: Variant + Clone + Iterator>, X: Variant + Clone, { self.set_iter_result(TypeId::of::(), |obj: Dynamic| { Box::new(obj.cast::().map(|v| v.map(Dynamic::from))) }) } /// Get the specified type iterator. #[cfg(not(feature = "no_module"))] #[inline] #[must_use] pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&FnIterator> { self.all_type_iterators.get(&id).map(|f| &**f) } /// Get the specified type iterator. #[inline] #[must_use] pub(crate) fn get_iter(&self, id: TypeId) -> Option<&FnIterator> { self.type_iterators.get(&id).map(|f| &**f) } } /// Module containing all built-in [module resolvers][ModuleResolver]. #[cfg(not(feature = "no_module"))] pub mod resolvers; #[cfg(not(feature = "no_module"))] pub use resolvers::ModuleResolver; rhai-1.21.0/src/module/resolvers/collection.rs000064400000000000000000000124771046102023000174440ustar 00000000000000use crate::{ Engine, ModuleResolver, Position, RhaiResultOf, SharedModule, StaticVec, ERR, STATIC_VEC_INLINE_SIZE, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ops::AddAssign, slice::Iter}; /// [Module][crate::Module] resolution service that holds a collection of module resolvers, /// to be searched in sequential order. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; /// /// let mut collection = ModuleResolversCollection::new(); /// /// let resolver = StaticModuleResolver::new(); /// collection.push(resolver); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(collection); /// ``` #[derive(Default)] pub struct ModuleResolversCollection(StaticVec>); impl ModuleResolversCollection { /// Create a new [`ModuleResolversCollection`]. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection}; /// /// let mut collection = ModuleResolversCollection::new(); /// /// let resolver = StaticModuleResolver::new(); /// collection.push(resolver); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(collection); /// ``` #[inline(always)] #[must_use] pub const fn new() -> Self { Self(StaticVec::new_const()) } /// Append a [module resolver][ModuleResolver] to the end. #[inline(always)] pub fn push(&mut self, resolver: impl ModuleResolver + 'static) -> &mut Self { self.0.push(Box::new(resolver)); self } /// Insert a [module resolver][ModuleResolver] to an offset index. /// /// # Panics /// /// Panics if the index is out of bounds. #[inline(always)] pub fn insert(&mut self, index: usize, resolver: impl ModuleResolver + 'static) -> &mut Self { self.0.insert(index, Box::new(resolver)); self } /// Remove the last [module resolver][ModuleResolver] from the end, if any. #[inline(always)] pub fn pop(&mut self) -> Option> { self.0.pop() } /// Remove a [module resolver][ModuleResolver] at an offset index. /// /// # Panics /// /// Panics if the index is out of bounds. #[inline(always)] pub fn remove(&mut self, index: usize) -> Box { self.0.remove(index) } /// Get an iterator of all the [module resolvers][ModuleResolver]. #[inline] pub fn iter(&self) -> impl Iterator { self.0.iter().map(<_>::as_ref) } /// Remove all [module resolvers][ModuleResolver]. #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.0.clear(); self } /// Returns `true` if this [`ModuleResolversCollection`] contains no module resolvers. #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Get the number of [module resolvers][ModuleResolver] in this [`ModuleResolversCollection`]. #[inline(always)] #[must_use] pub fn len(&self) -> usize { self.0.len() } /// Add another [`ModuleResolversCollection`] to the end of this collection. /// The other [`ModuleResolversCollection`] is consumed. #[inline] pub fn append(&mut self, other: Self) -> &mut Self { self.0.extend(other.0); self } } impl IntoIterator for ModuleResolversCollection { type Item = Box; type IntoIter = smallvec::IntoIter<[Box; STATIC_VEC_INLINE_SIZE]>; #[inline(always)] #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a> IntoIterator for &'a ModuleResolversCollection { type Item = &'a Box; type IntoIter = Iter<'a, Box>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.0.iter() } } impl Extend> for ModuleResolversCollection { #[inline(always)] fn extend>>(&mut self, iter: T) { self.0.extend(iter); } } impl AddAssign for ModuleResolversCollection { #[inline(always)] fn add_assign(&mut self, rhs: M) { self.push(rhs); } } impl ModuleResolver for ModuleResolversCollection { fn resolve( &self, engine: &Engine, source_path: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf { for resolver in &self.0 { match resolver.resolve(engine, source_path, path, pos) { Ok(module) => return Ok(module), Err(err) => match *err { ERR::ErrorModuleNotFound(..) => continue, ERR::ErrorInModule(_, err, _) => return Err(err), _ => unreachable!("ModuleResolver::resolve returns error that is not ErrorModuleNotFound or ErrorInModule"), }, } } Err(ERR::ErrorModuleNotFound(path.into(), pos).into()) } } rhai-1.21.0/src/module/resolvers/dummy.rs000064400000000000000000000025331046102023000164340ustar 00000000000000use crate::{Engine, ModuleResolver, Position, RhaiResultOf, SharedModule, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Empty/disabled [module][crate::Module] resolution service that acts as a dummy. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::DummyModuleResolver; /// /// let resolver = DummyModuleResolver::new(); /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Copy, Eq, PartialEq, Clone, Default, Hash)] pub struct DummyModuleResolver; impl DummyModuleResolver { /// Create a new [`DummyModuleResolver`]. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::DummyModuleResolver; /// /// let resolver = DummyModuleResolver::new(); /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub const fn new() -> Self { Self } } impl ModuleResolver for DummyModuleResolver { #[inline(always)] fn resolve( &self, _: &Engine, _: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf { Err(ERR::ErrorModuleNotFound(path.into(), pos).into()) } } rhai-1.21.0/src/module/resolvers/file.rs000064400000000000000000000303431046102023000162200ustar 00000000000000#![cfg(not(feature = "no_std"))] #![cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] use crate::eval::GlobalRuntimeState; use crate::func::{locked_read, locked_write}; use crate::{ Engine, Identifier, Locked, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, SharedModule, ERR, }; use std::{ collections::BTreeMap, io::Error as IoError, path::{Path, PathBuf}, }; pub const RHAI_SCRIPT_EXTENSION: &str = "rhai"; /// A [module][Module] resolution service that loads [module][Module] script files from the file system. /// /// ## Caching /// /// Resolved [Modules][Module] are cached internally so script files are not reloaded and recompiled /// for subsequent requests. /// /// Use [`clear_cache`][FileModuleResolver::clear_cache] or /// [`clear_cache_for_path`][FileModuleResolver::clear_cache_for_path] to clear the internal cache. /// /// ## Namespace /// /// When a function within a script file module is called, all functions defined within the same /// script are available, evan `private` ones. In other words, functions defined in a module script /// can always cross-call each other. /// /// # Example /// /// ``` /// use rhai::Engine; /// use rhai::module_resolvers::FileModuleResolver; /// /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory /// // with file extension '.x'. /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); /// /// let mut engine = Engine::new(); /// /// engine.set_module_resolver(resolver); /// ``` #[derive(Debug)] pub struct FileModuleResolver { /// Base path of the directory holding script files. base_path: Option, /// File extension of script files, default `.rhai`. extension: Identifier, /// Is the cache enabled? cache_enabled: bool, /// [`Scope`] holding variables for compiling scripts. scope: Scope<'static>, /// Internal cache of resolved modules. /// /// The cache is wrapped in interior mutability because [`resolve`][FileModuleResolver::resolve] /// is immutable. cache: Locked>, } impl Default for FileModuleResolver { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl FileModuleResolver { /// Create a new [`FileModuleResolver`] with the current directory as base path. /// /// The default extension is `.rhai`. /// /// # Example /// /// ``` /// use rhai::Engine; /// use rhai::module_resolvers::FileModuleResolver; /// /// // Create a new 'FileModuleResolver' loading scripts from the current directory /// // with file extension '.rhai' (the default). /// let resolver = FileModuleResolver::new(); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub fn new() -> Self { Self::new_with_extension(RHAI_SCRIPT_EXTENSION) } /// Create a new [`FileModuleResolver`] with a specific base path. /// /// The default extension is `.rhai`. /// /// # Example /// /// ``` /// use rhai::Engine; /// use rhai::module_resolvers::FileModuleResolver; /// /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory /// // with file extension '.rhai' (the default). /// let resolver = FileModuleResolver::new_with_path("./scripts"); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub fn new_with_path(path: impl Into) -> Self { Self::new_with_path_and_extension(path, RHAI_SCRIPT_EXTENSION) } /// Create a new [`FileModuleResolver`] with a file extension. /// /// # Example /// /// ``` /// use rhai::Engine; /// use rhai::module_resolvers::FileModuleResolver; /// /// // Create a new 'FileModuleResolver' loading scripts with file extension '.rhai' (the default). /// let resolver = FileModuleResolver::new_with_extension("rhai"); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub fn new_with_extension(extension: impl Into) -> Self { Self { base_path: None, extension: extension.into(), cache_enabled: true, cache: BTreeMap::new().into(), scope: Scope::new(), } } /// Create a new [`FileModuleResolver`] with a specific base path and file extension. /// /// # Example /// /// ``` /// use rhai::Engine; /// use rhai::module_resolvers::FileModuleResolver; /// /// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory /// // with file extension '.x'. /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub fn new_with_path_and_extension( path: impl Into, extension: impl Into, ) -> Self { Self { base_path: Some(path.into()), extension: extension.into(), cache_enabled: true, cache: BTreeMap::new().into(), scope: Scope::new(), } } /// Get the base path for script files. #[inline(always)] #[must_use] pub fn base_path(&self) -> Option<&Path> { self.base_path.as_deref() } /// Set the base path for script files. #[inline(always)] pub fn set_base_path(&mut self, path: impl Into) -> &mut Self { self.base_path = Some(path.into()); self } /// Get the script file extension. #[inline(always)] #[must_use] pub fn extension(&self) -> &str { &self.extension } /// Set the script file extension. #[inline(always)] pub fn set_extension(&mut self, extension: impl Into) -> &mut Self { self.extension = extension.into(); self } /// Get a reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. #[inline(always)] #[must_use] pub const fn scope(&self) -> &Scope { &self.scope } /// Set the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. #[inline(always)] pub fn set_scope(&mut self, scope: Scope<'static>) { self.scope = scope; } /// Get a mutable reference to the file module resolver's [scope][Scope]. /// /// The [scope][Scope] is used for compiling module scripts. #[inline(always)] #[must_use] pub fn scope_mut(&mut self) -> &mut Scope<'static> { &mut self.scope } /// Enable/disable the cache. #[inline(always)] pub fn enable_cache(&mut self, enable: bool) -> &mut Self { self.cache_enabled = enable; self } /// Is the cache enabled? #[inline(always)] #[must_use] pub const fn is_cache_enabled(&self) -> bool { self.cache_enabled } /// Is a particular path cached? #[inline] #[must_use] pub fn is_cached(&self, path: impl AsRef) -> bool { if !self.cache_enabled { return false; } locked_read(&self.cache) .unwrap() .contains_key(path.as_ref()) } /// Empty the internal cache. #[inline] pub fn clear_cache(&mut self) -> &mut Self { locked_write(&self.cache).unwrap().clear(); self } /// Remove the specified path from internal cache. /// /// The next time this path is resolved, the script file will be loaded once again. #[inline] #[must_use] pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option { locked_write(&self.cache) .unwrap() .remove_entry(path.as_ref()) .map(|(.., v)| v) } /// Construct a full file path. #[must_use] pub fn get_file_path(&self, path: &str, source_path: Option<&Path>) -> PathBuf { let path = Path::new(path); let mut file_path; if path.is_relative() { file_path = self .base_path .clone() .or_else(|| source_path.map(Into::into)) .unwrap_or_default(); file_path.push(path); } else { file_path = path.into(); } file_path.set_extension(self.extension.as_str()); // Force extension file_path } /// Resolve a module based on a path. fn impl_resolve( &self, engine: &Engine, global: &mut GlobalRuntimeState, scope: &mut Scope, source: Option<&str>, path: &str, pos: Position, ) -> Result> { // Load relative paths from source if there is no base path specified let source_path = global .source() .or(source) .and_then(|p| Path::new(p).parent()); let file_path = self.get_file_path(path, source_path); if self.is_cache_enabled() { if let Some(module) = locked_read(&self.cache).unwrap().get(&file_path) { return Ok(module.clone()); } } let mut ast = engine .compile_file_with_scope(&self.scope, file_path.clone()) .map_err(|err| match *err { ERR::ErrorSystem(.., err) if err.is::() => { Box::new(ERR::ErrorModuleNotFound(path.to_string(), pos)) } _ => Box::new(ERR::ErrorInModule(path.to_string(), err, pos)), })?; ast.set_source(path); let m: Shared<_> = Module::eval_ast_as_new_raw(engine, scope, global, &ast) .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? .into(); if self.is_cache_enabled() { locked_write(&self.cache) .unwrap() .insert(file_path, m.clone()); } Ok(m) } } impl ModuleResolver for FileModuleResolver { fn resolve_raw( &self, engine: &Engine, global: &mut GlobalRuntimeState, scope: &mut Scope, path: &str, pos: Position, ) -> RhaiResultOf { self.impl_resolve(engine, global, scope, None, path, pos) } #[inline(always)] fn resolve( &self, engine: &Engine, source: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf { let global = &mut engine.new_global_runtime_state(); let scope = &mut Scope::new(); self.impl_resolve(engine, global, scope, source, path, pos) } /// Resolve an `AST` based on a path string. /// /// The file system is accessed during each call; the internal cache is by-passed. fn resolve_ast( &self, engine: &Engine, source_path: Option<&str>, path: &str, pos: Position, ) -> Option> { // Construct the script file path let file_path = self.get_file_path(path, source_path.map(Path::new)); // Load the script file and compile it Some( engine .compile_file(file_path) .map(|mut ast| { ast.set_source(path); ast }) .map_err(|err| match *err { ERR::ErrorSystem(.., err) if err.is::() => { ERR::ErrorModuleNotFound(path.to_string(), pos).into() } _ => ERR::ErrorInModule(path.to_string(), err, pos).into(), }), ) } } rhai-1.21.0/src/module/resolvers/mod.rs000064400000000000000000000036571046102023000160700ustar 00000000000000use crate::eval::GlobalRuntimeState; use crate::func::SendSync; use crate::{Engine, Position, RhaiResultOf, Scope, SharedModule, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; mod collection; mod dummy; mod file; mod stat; pub use collection::ModuleResolversCollection; pub use dummy::DummyModuleResolver; #[cfg(not(feature = "no_std"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] pub use file::FileModuleResolver; pub use stat::StaticModuleResolver; /// Trait that encapsulates a module resolution service. pub trait ModuleResolver: SendSync { /// Resolve a module based on a path string. fn resolve( &self, engine: &Engine, source: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf; /// Resolve a module based on a path string, given a [`GlobalRuntimeState`] and the current [`Scope`]. /// /// # WARNING - Low Level API /// /// This function is very low level. #[allow(unused_variables)] fn resolve_raw( &self, engine: &Engine, global: &mut GlobalRuntimeState, scope: &mut Scope, path: &str, pos: Position, ) -> RhaiResultOf { self.resolve(engine, global.source(), path, pos) } /// Resolve an `AST` based on a path string. /// /// Returns [`None`] (default) if such resolution is not supported /// (e.g. if the module is Rust-based). /// /// # WARNING - Low Level API /// /// Override the default implementation of this method if the module resolver /// serves modules based on compiled Rhai scripts. #[allow(unused_variables)] #[must_use] fn resolve_ast( &self, engine: &Engine, source: Option<&str>, path: &str, pos: Position, ) -> Option> { None } } rhai-1.21.0/src/module/resolvers/stat.rs000064400000000000000000000114151046102023000162530ustar 00000000000000use crate::{ Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, SharedModule, SmartString, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ collections::btree_map::{IntoIter, Iter}, collections::BTreeMap, ops::AddAssign, }; /// A static [module][Module] resolution service that serves [modules][Module] added into it. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::StaticModuleResolver; /// /// let mut resolver = StaticModuleResolver::new(); /// /// let module = Module::new(); /// resolver.insert("hello", module); /// /// let mut engine = Engine::new(); /// /// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Clone, Default)] pub struct StaticModuleResolver(BTreeMap); impl StaticModuleResolver { /// Create a new [`StaticModuleResolver`]. /// /// # Example /// /// ``` /// use rhai::{Engine, Module}; /// use rhai::module_resolvers::StaticModuleResolver; /// /// let mut resolver = StaticModuleResolver::new(); /// /// let module = Module::new(); /// resolver.insert("hello", module); /// /// let mut engine = Engine::new(); /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] #[must_use] pub const fn new() -> Self { Self(BTreeMap::new()) } /// Add a [module][Module] keyed by its path. #[inline] pub fn insert(&mut self, path: impl Into, mut module: Module) { let path = path.into(); if module.id().is_none() { module.set_id(path.clone()); } module.build_index(); self.0.insert(path, module.into()); } /// Remove a [module][Module] given its path. #[inline(always)] pub fn remove(&mut self, path: &str) -> Option { self.0.remove(path) } /// Does the path exist? #[inline(always)] #[must_use] pub fn contains_path(&self, path: &str) -> bool { self.0.contains_key(path) } /// Get an iterator of all the [modules][Module]. #[inline] pub fn iter(&self) -> impl Iterator { self.0.iter().map(|(k, v)| (k.as_str(), v)) } /// Get a mutable iterator of all the [modules][Module]. #[inline] pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) } /// Get an iterator of all the [module][Module] paths. #[inline] pub fn paths(&self) -> impl Iterator { self.0.keys().map(SmartString::as_str) } /// Get an iterator of all the [modules][Module]. #[inline(always)] pub fn values(&self) -> impl Iterator { self.0.values() } /// Remove all [modules][Module]. #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.0.clear(); self } /// Returns `true` if this [`StaticModuleResolver`] contains no module resolvers. #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Get the number of [modules][Module] in this [`StaticModuleResolver`]. #[inline(always)] #[must_use] pub fn len(&self) -> usize { self.0.len() } /// Merge another [`StaticModuleResolver`] into this. /// The other [`StaticModuleResolver`] is consumed. /// /// Existing modules of the same path name are overwritten. #[inline] pub fn merge(&mut self, other: Self) -> &mut Self { self.0.extend(other.0); self } } impl IntoIterator for StaticModuleResolver { type Item = (Identifier, SharedModule); type IntoIter = IntoIter; #[inline(always)] #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a> IntoIterator for &'a StaticModuleResolver { type Item = (&'a Identifier, &'a SharedModule); type IntoIter = Iter<'a, SmartString, SharedModule>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.0.iter() } } impl AddAssign for StaticModuleResolver { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.merge(rhs); } } impl ModuleResolver for StaticModuleResolver { #[inline] fn resolve( &self, _: &Engine, _: Option<&str>, path: &str, pos: Position, ) -> RhaiResultOf { self.0 .get(path) .cloned() .ok_or_else(|| ERR::ErrorModuleNotFound(path.into(), pos).into()) } } rhai-1.21.0/src/optimizer.rs000064400000000000000000001613211046102023000140130ustar 00000000000000//! Module implementing the [`AST`] optimizer. #![cfg(not(feature = "no_optimize"))] use crate::ast::{ ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, }; use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, KEYWORD_TYPE_OF, OP_NOT, }; use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; use crate::tokenizer::Token; use crate::{ calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position, Scope, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::TypeId, borrow::Cow, convert::TryFrom, hash::{Hash, Hasher}, mem, }; /// Level of optimization performed. /// /// Not available under `no_optimize`. #[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] pub enum OptimizationLevel { /// No optimization performed. None, /// Only perform simple optimizations without evaluating functions. #[default] Simple, /// Full optimizations performed, including evaluating functions. /// Take care that this may cause side effects as it essentially assumes that all functions are pure. Full, } /// Mutable state throughout an optimization pass. #[derive(Debug, Clone)] struct OptimizerState<'a> { /// Has the [`AST`] been changed during this pass? is_dirty: bool, /// Stack of variables/constants for constants propagation and strict variables checking. variables: Vec<(ImmutableString, Option>)>, /// Activate constants propagation? propagate_constants: bool, /// [`Engine`] instance for eager function evaluation. engine: &'a Engine, /// Optional [`Scope`]. scope: Option<&'a Scope<'a>>, /// The global runtime state. global: GlobalRuntimeState, /// Function resolution caches. caches: Caches, /// Optimization level. optimization_level: OptimizationLevel, } impl<'a> OptimizerState<'a> { /// Create a new [`OptimizerState`]. #[inline(always)] pub fn new( engine: &'a Engine, lib: &'a [crate::SharedModule], scope: Option<&'a Scope<'a>>, optimization_level: OptimizationLevel, ) -> Self { let mut _global = engine.new_global_runtime_state(); let _lib = lib; #[cfg(not(feature = "no_function"))] { _global.lib = _lib.into(); } Self { is_dirty: false, variables: Vec::new(), propagate_constants: true, engine, scope, global: _global, caches: Caches::new(), optimization_level, } } /// Set the [`AST`] state to be dirty (i.e. changed). #[inline(always)] pub fn set_dirty(&mut self) { self.is_dirty = true; } /// Set the [`AST`] state to be not dirty (i.e. unchanged). #[inline(always)] pub fn clear_dirty(&mut self) { self.is_dirty = false; } /// Is the [`AST`] dirty (i.e. changed)? #[inline(always)] pub const fn is_dirty(&self) -> bool { self.is_dirty } /// Rewind the variables stack back to a specified size. #[inline(always)] pub fn rewind_var(&mut self, len: usize) { self.variables.truncate(len); } /// Add a new variable to the stack. /// /// `Some(value)` if literal constant (which can be used for constants propagation), `None` otherwise. #[inline(always)] pub fn push_var<'x: 'a>(&mut self, name: ImmutableString, value: Option>) { self.variables.push((name, value)); } /// Look up a literal constant from the variables stack. #[inline] pub fn find_literal_constant(&self, name: &str) -> Option<&Dynamic> { self.variables .iter() .rev() .find(|(n, _)| n == name) .and_then(|(_, value)| value.as_deref()) } /// Call a registered function #[inline] pub fn call_fn_with_const_args( &mut self, fn_name: &str, op_token: Option<&Token>, arg_values: &mut [Dynamic], ) -> Option { self.engine .exec_native_fn_call( &mut self.global, &mut self.caches, fn_name, op_token, calc_fn_hash(None, fn_name, arg_values.len()), &mut arg_values.iter_mut().collect::>(), false, true, Position::NONE, ) .ok() .map(|(v, ..)| v) } } /// Optimize a block of [statements][Stmt]. fn optimize_stmt_block( mut statements: StmtBlockContainer, state: &mut OptimizerState, preserve_result: bool, is_internal: bool, reduce_return: bool, ) -> StmtBlockContainer { if statements.is_empty() { return statements; } let mut is_dirty = state.is_dirty(); let is_pure = if is_internal { Stmt::is_internally_pure } else { Stmt::is_pure }; // Flatten blocks while let Some(n) = statements.iter().position( |s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)), ) { let (first, second) = statements.split_at_mut(n); let mut stmt = second[0].take(); let stmts = match stmt { Stmt::Block(ref mut block, ..) => block.statements_mut(), _ => unreachable!("Stmt::Block expected but gets {:?}", stmt), }; statements = first .iter_mut() .map(mem::take) .chain(stmts.iter_mut().map(mem::take)) .chain(second.iter_mut().skip(1).map(mem::take)) .collect(); is_dirty = true; } // Optimize loop { state.clear_dirty(); let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later let orig_propagate_constants = state.propagate_constants; // Remove everything following control flow breaking statements let mut dead_code = false; statements.retain(|stmt| { if dead_code { state.set_dirty(); false } else if stmt.is_control_flow_break() { dead_code = true; true } else { true } }); // Optimize each statement in the block statements.iter_mut().for_each(|stmt| { match stmt { Stmt::Var(x, options, ..) => { optimize_expr(&mut x.1, state, false); let value = if options.intersects(ASTFlags::CONSTANT) && x.1.is_constant() { // constant literal Some(Cow::Owned(x.1.get_literal_value().unwrap())) } else { // variable None }; state.push_var(x.0.name.clone(), value); } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), } }); // Remove all pure statements except the last one let mut index = 0; let mut first_non_constant = statements .iter() .rev() .position(|stmt| match stmt { stmt if !is_pure(stmt) => true, Stmt::Var(x, ..) if x.1.is_constant() => true, Stmt::Expr(e) if !e.is_constant() => true, #[cfg(not(feature = "no_module"))] Stmt::Import(x, ..) if !x.0.is_constant() => true, _ => false, }) .map_or(0, |n| statements.len() - n - 1); while index < statements.len() { if preserve_result && index >= statements.len() - 1 { break; } match statements[index] { ref stmt if is_pure(stmt) && index >= first_non_constant => { state.set_dirty(); statements.remove(index); } ref stmt if stmt.is_pure() => { state.set_dirty(); if index < first_non_constant { first_non_constant -= 1; } statements.remove(index); } _ => index += 1, } } // Remove all pure statements that do not return values at the end of a block. // We cannot remove anything for non-pure statements due to potential side-effects. if preserve_result { loop { match statements[..] { // { return; } -> {} [Stmt::Return(None, options, ..)] if reduce_return && !options.intersects(ASTFlags::BREAK) => { state.set_dirty(); statements.clear(); } [ref stmt] if !stmt.returns_value() && is_pure(stmt) => { state.set_dirty(); statements.clear(); } // { ...; return; } -> { ... } [.., ref last_stmt, Stmt::Return(None, options, ..)] if reduce_return && !options.intersects(ASTFlags::BREAK) && !last_stmt.returns_value() => { state.set_dirty(); statements.pop().unwrap(); } // { ...; return val; } -> { ...; val } [.., Stmt::Return(ref mut expr, options, pos)] if reduce_return && !options.intersects(ASTFlags::BREAK) => { state.set_dirty(); *statements.last_mut().unwrap() = expr .as_mut() .map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e))); } // { ...; stmt; noop } -> done [.., ref second_last_stmt, Stmt::Noop(..)] if second_last_stmt.returns_value() => { break } // { ...; stmt_that_returns; pure_non_value_stmt } -> { ...; stmt_that_returns; noop } // { ...; stmt; pure_non_value_stmt } -> { ...; stmt } [.., ref second_last_stmt, ref last_stmt] if !last_stmt.returns_value() && is_pure(last_stmt) => { state.set_dirty(); if second_last_stmt.returns_value() { *statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position()); } else { statements.pop().unwrap(); } } _ => break, } } } else { loop { match statements[..] { [ref stmt] if is_pure(stmt) => { state.set_dirty(); statements.clear(); } // { ...; return; } -> { ... } [.., Stmt::Return(None, options, ..)] if reduce_return && !options.intersects(ASTFlags::BREAK) => { state.set_dirty(); statements.pop().unwrap(); } // { ...; return pure_val; } -> { ... } [.., Stmt::Return(Some(ref expr), options, ..)] if reduce_return && !options.intersects(ASTFlags::BREAK) && expr.is_pure() => { state.set_dirty(); statements.pop().unwrap(); } [.., ref last_stmt] if is_pure(last_stmt) => { state.set_dirty(); statements.pop().unwrap(); } _ => break, } } } // Pop the stack and remove all the local constants state.rewind_var(orig_constants_len); state.propagate_constants = orig_propagate_constants; if !state.is_dirty() { break; } is_dirty = true; } if is_dirty { state.set_dirty(); } statements.shrink_to_fit(); statements } impl StmtBlock { #[inline(always)] #[must_use] fn take_statements(&mut self) -> StmtBlockContainer { mem::take(self.statements_mut()) } } /// Is this [`Expr`] a constant that is hashable? #[inline(always)] fn is_hashable_constant(expr: &Expr) -> bool { match expr { _ if !expr.is_constant() => false, Expr::DynamicConstant(v, ..) => v.is_hashable(), _ => false, } } /// Optimize a [statement][Stmt]. fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) { #[inline(always)] #[must_use] fn is_variable_access(expr: &Expr, _non_qualified: bool) -> bool { match expr { #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if _non_qualified && !x.2.is_empty() => false, Expr::Variable(..) => true, _ => false, } } match stmt { // var = var op expr => var op= expr Stmt::Assignment(x, ..) if !x.0.is_op_assignment() && is_variable_access(&x.1.lhs, true) && matches!(&x.1.rhs, Expr::FnCall(x2, ..) if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment()) && x2.args.len() == 2 && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true) ) => { match x.1.rhs { Expr::FnCall(ref mut x2, pos) => { state.set_dirty(); x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos); x.1.rhs = x2.args[1].take(); } ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), } } // expr op= expr Stmt::Assignment(x, ..) => { if !is_variable_access(&x.1.lhs, false) { optimize_expr(&mut x.1.lhs, state, false); } optimize_expr(&mut x.1.rhs, state, false); } // if expr {} Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => { let condition = &mut x.expr; state.set_dirty(); let pos = condition.start_position(); let mut expr = condition.take(); optimize_expr(&mut expr, state, false); *stmt = if preserve_result { // -> { expr, Noop } Stmt::Block( StmtBlock::new( [Stmt::Expr(expr.into()), Stmt::Noop(pos)], pos, Position::NONE, ) .into(), ) } else { // -> expr Stmt::Expr(expr.into()) }; } // if false { if_block } -> Noop Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() => { match x.expr { Expr::BoolConstant(false, pos) => { state.set_dirty(); *stmt = Stmt::Noop(pos); } _ => unreachable!("`Expr::BoolConstant`"), } } // if false { if_block } else { else_block } -> else_block Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => { state.set_dirty(); let body = x.branch.take_statements(); *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.branch.position()), statements => { Stmt::Block(StmtBlock::new_with_span(statements, x.branch.span()).into()) } } } // if true { if_block } else { else_block } -> if_block Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => { state.set_dirty(); let body = x.body.take_statements(); *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.body.position()), statements => { Stmt::Block(StmtBlock::new_with_span(statements, x.body.span()).into()) } } } // if expr { if_block } else { else_block } Stmt::If(x, ..) => { let FlowControl { expr, body, branch } = &mut **x; optimize_expr(expr, state, false); *body.statements_mut() = optimize_stmt_block(body.take_statements(), state, preserve_result, true, false); *branch.statements_mut() = optimize_stmt_block( branch.take_statements(), state, preserve_result, true, false, ); } // switch const { ... } Stmt::Switch(x, pos) if is_hashable_constant(&x.0) => { let ( match_expr, SwitchCasesCollection { expressions, cases, ranges, def_case, }, ) = &mut **x; let value = match_expr.get_literal_value().unwrap(); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); // First check hashes if let Some(case_blocks_list) = cases.get(&hash) { match &case_blocks_list[..] { [] => (), [index] => { let mut b = mem::take(&mut expressions[*index]); cases.clear(); if matches!(b.lhs, Expr::BoolConstant(true, ..)) { // Promote the matched case let mut statements = Stmt::Expr(b.rhs.take().into()); optimize_stmt(&mut statements, state, true); *stmt = statements; } else { // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut b.lhs, state, false); let branch = def_case.map_or(StmtBlock::NONE, |index| { let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into()); optimize_stmt(&mut def_stmt, state, true); def_stmt.into() }); let body = Stmt::Expr(b.rhs.take().into()).into(); let expr = b.lhs.take(); *stmt = Stmt::If( FlowControl { expr, body, branch }.into(), match_expr.start_position(), ); } state.set_dirty(); return; } _ => { for &index in case_blocks_list { let mut b = mem::take(&mut expressions[index]); if matches!(b.lhs, Expr::BoolConstant(true, ..)) { // Promote the matched case let mut statements = Stmt::Expr(b.rhs.take().into()); optimize_stmt(&mut statements, state, true); *stmt = statements; state.set_dirty(); return; } } } } } // Then check ranges if !ranges.is_empty() { // Only one range or all ranges without conditions if ranges.len() == 1 || ranges .iter() .all(|r| matches!(expressions[r.index()].lhs, Expr::BoolConstant(true, ..))) { if let Some(r) = ranges.iter().find(|r| r.contains(&value)) { let range_block = &mut expressions[r.index()]; if matches!(range_block.lhs, Expr::BoolConstant(true, ..)) { // Promote the matched case let block = &mut expressions[r.index()]; let mut statements = Stmt::Expr(block.rhs.take().into()); optimize_stmt(&mut statements, state, true); *stmt = statements; } else { let mut expr = range_block.lhs.take(); // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut expr, state, false); let branch = def_case.map_or(StmtBlock::NONE, |index| { let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into()); optimize_stmt(&mut def_stmt, state, true); def_stmt.into() }); let body = Stmt::Expr(expressions[r.index()].rhs.take().into()).into(); *stmt = Stmt::If( FlowControl { expr, body, branch }.into(), match_expr.start_position(), ); } state.set_dirty(); return; } } else { // Multiple ranges - clear the table and just keep the right ranges if !cases.is_empty() { state.set_dirty(); cases.clear(); } let old_ranges_len = ranges.len(); ranges.retain(|r| r.contains(&value)); if ranges.len() != old_ranges_len { state.set_dirty(); } for r in ranges { let b = &mut expressions[r.index()]; optimize_expr(&mut b.lhs, state, false); optimize_expr(&mut b.rhs, state, false); } return; } } // Promote the default case state.set_dirty(); match def_case { Some(index) => { let mut def_stmt = Stmt::Expr(expressions[*index].rhs.take().into()); optimize_stmt(&mut def_stmt, state, true); *stmt = def_stmt; } _ => *stmt = Stmt::Block(StmtBlock::empty(*pos).into()), } } // switch Stmt::Switch(x, ..) => { let ( match_expr, SwitchCasesCollection { expressions, cases, ranges, def_case, .. }, ) = &mut **x; optimize_expr(match_expr, state, false); // Optimize blocks for b in &mut *expressions { optimize_expr(&mut b.lhs, state, false); optimize_expr(&mut b.rhs, state, false); if matches!(b.lhs, Expr::BoolConstant(false, ..)) && !b.rhs.is_unit() { b.rhs = Expr::Unit(b.rhs.position()); state.set_dirty(); } } // Remove false cases cases.retain(|_, list| { // Remove all entries that have false conditions list.retain(|index| { if matches!(expressions[*index].lhs, Expr::BoolConstant(false, ..)) { state.set_dirty(); false } else { true } }); // Remove all entries after a `true` condition if let Some(n) = list.iter().position(|&index| { matches!(expressions[index].lhs, Expr::BoolConstant(true, ..)) }) { if n + 1 < list.len() { state.set_dirty(); list.truncate(n + 1); } } // Remove if no entry left if list.is_empty() { state.set_dirty(); false } else { true } }); // Remove false ranges ranges.retain(|r| { if matches!(expressions[r.index()].lhs, Expr::BoolConstant(false, ..)) { state.set_dirty(); false } else { true } }); if let Some(index) = def_case { optimize_expr(&mut expressions[*index].rhs, state, false); } // Remove unused block statements expressions.iter_mut().enumerate().for_each(|(index, b)| { if *def_case != Some(index) && cases.values().flat_map(|c| c.iter()).all(|&n| n != index) && ranges.iter().all(|r| r.index() != index) && !b.rhs.is_unit() { b.rhs = Expr::Unit(b.rhs.position()); state.set_dirty(); } }); } // while false { block } -> Noop Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr { Expr::BoolConstant(false, pos) => { state.set_dirty(); *stmt = Stmt::Noop(pos); } _ => unreachable!("`Expr::BoolConstant"), }, // while expr { block } Stmt::While(x, ..) => { let FlowControl { expr, body, .. } = &mut **x; optimize_expr(expr, state, false); if let Expr::BoolConstant(true, pos) = expr { *expr = Expr::Unit(*pos); } *body.statements_mut() = optimize_stmt_block(body.take_statements(), state, false, true, false); } // do { block } while|until expr Stmt::Do(x, ..) => { optimize_expr(&mut x.expr, state, false); *x.body.statements_mut() = optimize_stmt_block(x.body.take_statements(), state, false, true, false); } // for id in expr { block } Stmt::For(x, ..) => { optimize_expr(&mut x.2.expr, state, false); *x.2.body.statements_mut() = optimize_stmt_block(x.2.body.take_statements(), state, false, true, false); } // let id = expr; Stmt::Var(x, options, ..) if !options.intersects(ASTFlags::CONSTANT) => { optimize_expr(&mut x.1, state, false); } // import expr as var; #[cfg(not(feature = "no_module"))] Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false), // { block } Stmt::Block(block) => { let mut stmts = optimize_stmt_block(block.take_statements(), state, preserve_result, true, false); match stmts.as_mut_slice() { [] => { state.set_dirty(); *stmt = Stmt::Noop(block.span().start()); } // Only one statement which is not block-dependent - promote [s] if !s.is_block_dependent() => { state.set_dirty(); *stmt = s.take(); } _ => *block.statements_mut() = stmts, } } // try { pure try_block } catch ( var ) { catch_block } -> try_block Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); let statements = x.body.take_statements(); let block = StmtBlock::new_with_span( optimize_stmt_block(statements, state, false, true, false), x.body.span(), ); *stmt = Stmt::Block(block.into()); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, ..) => { *x.body.statements_mut() = optimize_stmt_block(x.body.take_statements(), state, false, true, false); *x.branch.statements_mut() = optimize_stmt_block(x.branch.take_statements(), state, false, true, false); } // expr(stmt) Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => { state.set_dirty(); match expr.as_mut() { Expr::Stmt(block) if !block.is_empty() => { let mut stmts_blk = mem::take(block.as_mut()); *stmts_blk.statements_mut() = optimize_stmt_block(stmts_blk.take_statements(), state, true, true, false); *stmt = Stmt::Block(stmts_blk.into()); } Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()), _ => unreachable!("`Expr::Stmt`"), } } // expr(func()) Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => { state.set_dirty(); match expr.take() { Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos), _ => unreachable!(), } } Stmt::Expr(expr) => optimize_expr(expr, state, false), // func(...) Stmt::FnCall(..) => match stmt.take() { Stmt::FnCall(x, pos) => { let mut expr = Expr::FnCall(x, pos); optimize_expr(&mut expr, state, false); *stmt = match expr { Expr::FnCall(x, pos) => Stmt::FnCall(x, pos), _ => Stmt::Expr(expr.into()), } } _ => unreachable!(), }, // break expr; Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false), // return expr; Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false), // Share nothing #[cfg(not(feature = "no_closure"))] Stmt::Share(x) if x.is_empty() => { state.set_dirty(); *stmt = Stmt::Noop(Position::NONE); } // Share constants #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { let orig_len = x.len(); if state.propagate_constants { x.retain(|(v, _)| state.find_literal_constant(v.as_str()).is_none()); if x.len() != orig_len { state.set_dirty(); } } } // All other statements - skip _ => (), } } // Convert a constant argument into [`Expr::DynamicConstant`]. fn move_constant_arg(arg_expr: &mut Expr) -> bool { match arg_expr { Expr::DynamicConstant(..) | Expr::Unit(..) | Expr::StringConstant(..) | Expr::CharConstant(..) | Expr::BoolConstant(..) | Expr::IntegerConstant(..) => false, #[cfg(not(feature = "no_float"))] Expr::FloatConstant(..) => false, _ => { if let Some(value) = arg_expr.get_literal_value() { *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position()); true } else { false } } } } /// Optimize an [expression][Expr]. fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ KEYWORD_PRINT, // side effects KEYWORD_DEBUG, // side effects KEYWORD_EVAL, // arbitrary scripts ]; match expr { // {} Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => { state.set_dirty(); match x.take_statements().remove(0) { Stmt::Expr(mut e) => { optimize_expr(&mut e, state, false); *expr = *e; } _ => unreachable!("`Expr::Stmt`") } } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array Expr::Stmt(x) => { *x.statements_mut() = optimize_stmt_block(x.take_statements(), state, true, true, false); // { Stmt(Expr) } - promote if let [ Stmt::Expr(e) ] = x.statements_mut().as_mut() { state.set_dirty(); *expr = e.take(); } } // ()?.rhs #[cfg(not(feature = "no_object"))] Expr::Dot(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => { state.set_dirty(); *expr = x.lhs.take(); } // lhs.rhs #[cfg(not(feature = "no_object"))] Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == p.2) .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr }); } // var.rhs or this.rhs (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true), // const.type_of() (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == KEYWORD_TYPE_OF && x.args.is_empty() => { if let Some(value) = lhs.get_literal_value() { state.set_dirty(); let typ = state.engine.map_type_name(value.type_name()).into(); *expr = Expr::from_dynamic(typ, *pos); } } // const.is_shared() #[cfg(not(feature = "no_closure"))] (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == crate::engine::KEYWORD_IS_SHARED && x.args.is_empty() => { if lhs.get_literal_value().is_some() { state.set_dirty(); *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); } } // lhs.rhs (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); } } // ....lhs.rhs #[cfg(not(feature = "no_object"))] Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } // ()?[rhs] #[cfg(not(feature = "no_index"))] Expr::Index(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => { state.set_dirty(); *expr = x.lhs.take(); } // lhs[rhs] #[cfg(not(feature = "no_index"))] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // array[int] (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let mut result = a[*i as usize].take(); result.set_position(*pos); *expr = result; } // array[-int] #[allow(clippy::unnecessary_cast)] (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= a.len() as u64 && a.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let index = a.len() - i.unsigned_abs() as usize; let mut result = a[index].take(); result.set_position(*pos); *expr = result; } // map[string] (Expr::Map(m, pos), Expr::StringConstant(s, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == s) .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr }); } // int[int] (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < crate::INT_BITS => { // Bit-field literal indexing - get the bit state.set_dirty(); *expr = Expr::BoolConstant((*n & (1 << (*i as usize))) != 0, *pos); } // int[-int] #[allow(clippy::unnecessary_cast)] (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= crate::INT_BITS as u64 => { // Bit-field literal indexing - get the bit state.set_dirty(); *expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - i.unsigned_abs() as usize))) != 0, *pos); } // string[int] #[allow(clippy::unnecessary_cast)] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < s.chars().count() => { // String literal indexing - get the character state.set_dirty(); *expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos); } // string[-int] #[allow(clippy::unnecessary_cast)] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= s.chars().count() as u64 => { // String literal indexing - get the character state.set_dirty(); *expr = Expr::CharConstant(s.chars().rev().nth(i.unsigned_abs() as usize - 1).unwrap(), *pos); } // var[rhs] or this[rhs] (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true), // lhs[rhs] (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); } }, // ...[lhs][rhs] #[cfg(not(feature = "no_index"))] Expr::Index(x, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } // `` Expr::InterpolatedString(x, pos) if x.is_empty() => { state.set_dirty(); *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos); } // `... ${const} ...` Expr::InterpolatedString(..) if expr.is_constant() => { state.set_dirty(); *expr = Expr::StringConstant(expr.get_literal_value().unwrap().cast::(), expr.position()); } // `... ${ ... } ...` Expr::InterpolatedString(x, ..) => { x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); let mut n = 0; // Merge consecutive strings while n < x.len() - 1 { match (x[n].take(),x[n+1].take()) { (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); } (expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); } (Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); } (expr1, Expr::StringConstant(s, ..)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); } (Expr::StringConstant(s, ..), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); } (expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; } } } x.shrink_to_fit(); } // [ constant .. ] #[cfg(not(feature = "no_index"))] Expr::Array(..) if expr.is_constant() => { state.set_dirty(); *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position()); } // [ items .. ] #[cfg(not(feature = "no_index"))] Expr::Array(x, ..) => x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)), // #{ key:constant, .. } #[cfg(not(feature = "no_object"))] Expr::Map(..) if expr.is_constant() => { state.set_dirty(); *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position()); } // #{ key:value, .. } #[cfg(not(feature = "no_object"))] Expr::Map(x, ..) => x.0.iter_mut().for_each(|(.., expr)| optimize_expr(expr, state, false)), // lhs && rhs Expr::And(x, ..) => match (&mut x.lhs, &mut x.rhs) { // true && rhs -> rhs (Expr::BoolConstant(true, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); } // false && rhs -> false (Expr::BoolConstant(false, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); } // lhs && true -> lhs (lhs, Expr::BoolConstant(true, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); } // lhs && rhs (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } }, // lhs || rhs Expr::Or(ref mut x, ..) => match (&mut x.lhs, &mut x.rhs) { // false || rhs -> rhs (Expr::BoolConstant(false, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); } // true || rhs -> true (Expr::BoolConstant(true, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); } // lhs || false (lhs, Expr::BoolConstant(false, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); } // lhs || rhs (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } }, // () ?? rhs -> rhs Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => { state.set_dirty(); *expr = x.rhs.take(); }, // lhs:constant ?? rhs -> lhs Expr::Coalesce(x, ..) if x.lhs.is_constant() => { state.set_dirty(); *expr = x.lhs.take(); }, // !true or !false Expr::FnCall(x,..) if x.name == OP_NOT && x.args.len() == 1 && matches!(x.args[0], Expr::BoolConstant(..)) => { state.set_dirty(); match x.args[0] { Expr::BoolConstant(b, pos) => *expr = Expr::BoolConstant(!b, pos), _ => unreachable!(), } } // nnn::id(args ..) -> optimize function call arguments #[cfg(not(feature = "no_module"))] Expr::FnCall(x, ..) if x.is_qualified() => x.args.iter_mut().for_each(|arg_expr| { optimize_expr(arg_expr, state, false); if move_constant_arg(arg_expr) { state.set_dirty(); } }), // eval! Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => { state.propagate_constants = false; } // Fn Expr::FnCall(x, pos) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR && x.constant_args() => { let fn_name = match x.args[0] { Expr::StringConstant(ref s, ..) => s.clone().into(), _ => Dynamic::UNIT }; if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(Into::into).and_then(FnPtr::try_from) { state.set_dirty(); *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); } else { optimize_expr(&mut x.args[0], state, false); } } // curry(FnPtr, constants...) Expr::FnCall(x, pos) if x.args.len() >= 2 && x.name == KEYWORD_FN_PTR_CURRY && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr()) && x.constant_args() => { let mut fn_ptr = x.args[0].get_literal_value().unwrap().cast::(); fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value().unwrap())); state.set_dirty(); *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); } // Do not call some special keywords that may have side effects Expr::FnCall(x, ..) if DONT_EVAL_KEYWORDS.contains(&x.name.as_str()) => { x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false)); } // Call built-in operators Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.constant_args() // all arguments are constants => { let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::>(); let arg_types = arg_values.iter().map(Dynamic::type_id).collect::>(); match x.name.as_str() { KEYWORD_TYPE_OF if arg_values.len() == 1 => { state.set_dirty(); let typ = state.engine.map_type_name(arg_values[0].type_name()).into(); *expr = Expr::from_dynamic(typ, *pos); return; } #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => { state.set_dirty(); *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); return; } // Overloaded operators can override built-in. _ if x.args.len() == 2 && x.is_operator_call() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => { if let Some((f, ctx)) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) { let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into()); let (first, second) = arg_values.split_first_mut().unwrap(); if let Ok(result) = f(context, &mut [ first, &mut second[0] ]) { state.set_dirty(); *expr = Expr::from_dynamic(result, *pos); return; } } } _ => () } x.args.iter_mut().for_each(|arg_expr| { optimize_expr(arg_expr, state, false); if move_constant_arg(arg_expr) { state.set_dirty(); } }); } // Eagerly call functions Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations && x.constant_args() // all arguments are constants => { // First search for script-defined functions (can override built-in) let _has_script_fn = false; #[cfg(not(feature = "no_function"))] let _has_script_fn = !x.hashes.is_native_only() && state.global.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some(); if !_has_script_fn { let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::>>().unwrap(); let result = match x.name.as_str() { KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), _ => state.call_fn_with_const_args(&x.name, x.op_token.as_ref(), arg_values) }; if let Some(r) = result { state.set_dirty(); *expr = Expr::from_dynamic(r, *pos); return; } } x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); } // id(args ..) or xxx.id(args ..) -> optimize function call arguments Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg_expr| { optimize_expr(arg_expr, state, false); if move_constant_arg(arg_expr) { state.set_dirty(); } }), // constant-name #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if !x.2.is_empty() => (), Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.1).is_some() => { // Replace constant with value *expr = Expr::from_dynamic(state.find_literal_constant(&x.1).unwrap().clone(), *pos); state.set_dirty(); } // Custom syntax #[cfg(not(feature = "no_custom_syntax"))] Expr::Custom(x, ..) => { if x.scope_may_be_changed { state.propagate_constants = false; } // Do not optimize custom syntax expressions as you won't know how they would be called } // All other expressions - skip _ => (), } } impl Engine { /// Has a system function a Rust-native override? fn has_native_fn_override(&self, hash_script: u64, arg_types: impl AsRef<[TypeId]>) -> bool { let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied()); // First check the global namespace and packages, but skip modules that are standard because // they should never conflict with system functions. if self .global_modules .iter() .filter(|m| !m.is_standard_lib()) .any(|m| m.contains_fn(hash)) { return true; } // Then check sub-modules #[cfg(not(feature = "no_module"))] if self .global_sub_modules .values() .any(|m| m.contains_qualified_fn(hash)) { return true; } false } /// Optimize a block of [statements][Stmt] at top level. /// /// Constants and variables from the scope are added. fn optimize_top_level( &self, statements: StmtBlockContainer, scope: Option<&Scope>, lib: &[crate::SharedModule], optimization_level: OptimizationLevel, ) -> StmtBlockContainer { let mut statements = statements; // If optimization level is None then skip optimizing if optimization_level == OptimizationLevel::None { statements.shrink_to_fit(); return statements; } // Set up the state let mut state = OptimizerState::new(self, lib, scope, optimization_level); // Add constants from global modules self.global_modules .iter() .rev() .flat_map(|m| m.iter_var()) .for_each(|(name, value)| state.push_var(name.into(), Some(Cow::Borrowed(value)))); // Add constants and variables from the scope state .scope .into_iter() .flat_map(Scope::iter_inner) .for_each(|(name, constant, value)| { state.push_var( name.into(), if constant { Some(Cow::Borrowed(value)) } else { None }, ); }); optimize_stmt_block(statements, &mut state, true, false, true) } /// Optimize a collection of statements and functions into an [`AST`]. pub(crate) fn optimize_into_ast( &self, scope: Option<&Scope>, statements: StmtBlockContainer, #[cfg(not(feature = "no_function"))] functions: impl IntoIterator> + AsRef<[crate::Shared]>, optimization_level: OptimizationLevel, ) -> AST { let mut statements = statements; #[cfg(not(feature = "no_function"))] let lib: crate::Shared<_> = if optimization_level == OptimizationLevel::None { crate::Module::from(functions).into() } else { // We only need the script library's signatures for optimization purposes let lib2 = crate::Module::from( functions .as_ref() .iter() .map(|fn_def| fn_def.clone_function_signatures().into()), ); let lib2 = &[lib2.into()]; crate::Module::from(functions.into_iter().map(|fn_def| { // Optimize the function body let mut fn_def = crate::func::shared_take_or_clone(fn_def); let statements = fn_def.body.take_statements(); *fn_def.body.statements_mut() = self.optimize_top_level(statements, scope, lib2, optimization_level); fn_def.into() })) .into() }; #[cfg(feature = "no_function")] let lib: crate::Shared<_> = crate::Module::new().into(); statements.shrink_to_fit(); AST::new( match optimization_level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { self.optimize_top_level(statements, scope, &[lib.clone()], optimization_level) } }, #[cfg(not(feature = "no_function"))] lib, ) } } rhai-1.21.0/src/packages/arithmetic.rs000064400000000000000000000471761046102023000157130ustar 00000000000000use crate::plugin::*; use crate::{def_package, Position, RhaiError, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::Float; #[cold] #[inline(never)] pub fn make_err(msg: impl Into) -> RhaiError { ERR::ErrorArithmetic(msg.into(), Position::NONE).into() } macro_rules! gen_arithmetic_functions { ($root:ident => $($arg_type:ident),+) => { #[allow(non_snake_case)] pub mod $root { $(pub mod $arg_type { use super::super::*; #[export_module] pub mod functions { #[rhai_fn(name = "+", return_raw)] pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) } else { Ok(x + y) } } #[rhai_fn(name = "-", return_raw)] pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) } else { Ok(x - y) } } #[rhai_fn(name = "*", return_raw)] pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) } else { Ok(x * y) } } #[rhai_fn(name = "/", return_raw)] pub fn divide(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { // Detect division by zero if y == 0 { Err(make_err(format!("Division by zero: {x} / {y}"))) } else { x.checked_div(y).ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } } else { Ok(x / y) } } #[rhai_fn(name = "%", return_raw)] pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) } else { Ok(x % y) } } #[rhai_fn(name = "**", return_raw)] pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { Err(make_err(format!("Exponential overflow: {x} ** {y}"))) } else if y < 0 { Err(make_err(format!("Integer raised to a negative power: {x} ** {y}"))) } else { x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) } } else { Ok(x.pow(y as u32)) } } #[rhai_fn(name = "<<")] pub fn shift_left(x: $arg_type, y: INT) -> $arg_type { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { 0 } else if y < 0 { shift_right(x, y.checked_abs().unwrap_or(INT::MAX)) } else { x.checked_shl(y as u32).unwrap_or_else(|| 0) } } else if y < 0 { x >> -y } else { x << y } } #[rhai_fn(name = ">>")] pub fn shift_right(x: $arg_type, y: INT) -> $arg_type { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { x.wrapping_shr(u32::MAX) } else if y < 0 { shift_left(x, y.checked_abs().unwrap_or(INT::MAX)) } else { x.checked_shr(y as u32).unwrap_or_else(|| x.wrapping_shr(u32::MAX)) } } else if y < 0 { x << -y } else { x >> y } } #[rhai_fn(name = "&")] pub const fn binary_and(x: $arg_type, y: $arg_type) -> $arg_type { x & y } #[rhai_fn(name = "|")] pub const fn binary_or(x: $arg_type, y: $arg_type) -> $arg_type { x | y } #[rhai_fn(name = "^")] pub const fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { x ^ y } /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub const fn is_zero(x: $arg_type) -> bool { x == 0 } /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub const fn is_odd(x: $arg_type) -> bool { x % 2 != 0 } /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub const fn is_even(x: $arg_type) -> bool { x % 2 == 0 } } })* } } } macro_rules! gen_signed_functions { ($root:ident => $($arg_type:ident),+) => { #[allow(non_snake_case)] pub mod $root { $(pub mod $arg_type { use super::super::*; #[export_module] pub mod functions { #[rhai_fn(name = "-", return_raw)] pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{x}"))) } else { Ok(-x) } } #[rhai_fn(name = "+")] pub const fn plus(x: $arg_type) -> $arg_type { x } /// Return the absolute value of the number. #[rhai_fn(return_raw)] pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> { if cfg!(not(feature = "unchecked")) { x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{x}"))) } else { Ok(x.abs()) } } /// Return the sign (as an integer) of the number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative pub const fn sign(x: $arg_type) -> INT { x.signum() as INT } } })* } } } macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( combine_with_exported_module!($mod_name, "arithmetic", $root::$arg_type::functions); )* } } def_package! { /// Basic arithmetic package. pub ArithmeticPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "int", int_functions); reg_functions!(lib += signed_basic; INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64); reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64); gen_signed_functions!(signed_numbers => i8, i16, i32); reg_functions!(lib += signed_numbers; i8, i16, i32); #[cfg(not(target_family = "wasm"))] { gen_arithmetic_functions!(arith_numbers => i128, u128); reg_functions!(lib += arith_numbers; i128, u128); gen_signed_functions!(signed_numbers => i128); reg_functions!(lib += signed_numbers; i128); } } // Basic arithmetic for floating-point #[cfg(not(feature = "no_float"))] { combine_with_exported_module!(lib, "f32", f32_functions); combine_with_exported_module!(lib, "f64", f64_functions); } // Decimal functions #[cfg(feature = "decimal")] combine_with_exported_module!(lib, "decimal", decimal_functions); } } #[export_module] mod int_functions { /// Return true if the number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub const fn is_zero(x: INT) -> bool { x == 0 } /// Return true if the number is odd. #[rhai_fn(get = "is_odd", name = "is_odd")] pub const fn is_odd(x: INT) -> bool { x % 2 != 0 } /// Return true if the number is even. #[rhai_fn(get = "is_even", name = "is_even")] pub const fn is_even(x: INT) -> bool { x % 2 == 0 } } gen_arithmetic_functions!(arith_basic => INT); gen_signed_functions!(signed_basic => INT); #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { #[cfg(not(feature = "f32_float"))] #[allow(clippy::cast_precision_loss)] pub mod basic_arithmetic { #[rhai_fn(name = "+")] pub fn add(x: f32, y: f32) -> f32 { x + y } #[rhai_fn(name = "-")] pub fn subtract(x: f32, y: f32) -> f32 { x - y } #[rhai_fn(name = "*")] pub fn multiply(x: f32, y: f32) -> f32 { x * y } #[rhai_fn(name = "/")] pub fn divide(x: f32, y: f32) -> f32 { x / y } #[rhai_fn(name = "%")] pub fn modulo(x: f32, y: f32) -> f32 { x % y } #[rhai_fn(name = "**")] pub fn pow_f_f(x: f32, y: f32) -> f32 { x.powf(y) } #[rhai_fn(name = "+")] pub fn add_if(x: INT, y: f32) -> f32 { (x as f32) + y } #[rhai_fn(name = "+")] pub fn add_fi(x: f32, y: INT) -> f32 { x + (y as f32) } #[rhai_fn(name = "-")] pub fn subtract_if(x: INT, y: f32) -> f32 { (x as f32) - y } #[rhai_fn(name = "-")] pub fn subtract_fi(x: f32, y: INT) -> f32 { x - (y as f32) } #[rhai_fn(name = "*")] pub fn multiply_if(x: INT, y: f32) -> f32 { (x as f32) * y } #[rhai_fn(name = "*")] pub fn multiply_fi(x: f32, y: INT) -> f32 { x * (y as f32) } #[rhai_fn(name = "/")] pub fn divide_if(x: INT, y: f32) -> f32 { (x as f32) / y } #[rhai_fn(name = "/")] pub fn divide_fi(x: f32, y: INT) -> f32 { x / (y as f32) } #[rhai_fn(name = "%")] pub fn modulo_if(x: INT, y: f32) -> f32 { (x as f32) % y } #[rhai_fn(name = "%")] pub fn modulo_fi(x: f32, y: INT) -> f32 { x % (y as f32) } } #[rhai_fn(name = "-")] pub fn neg(x: f32) -> f32 { -x } #[rhai_fn(name = "+")] pub const fn plus(x: f32) -> f32 { x } /// Return the absolute value of the floating-point number. pub fn abs(x: f32) -> f32 { x.abs() } /// Return the sign (as an integer) of the floating-point number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f32) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), x => Ok(x as INT), } } /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f32) -> bool { x == 0.0 } #[rhai_fn(name = "**", return_raw)] pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { Err(make_err(format!( "Number raised to too large an index: {x} ** {y}" ))) } else { #[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)] Ok(x.powi(y as i32)) } } } #[cfg(not(feature = "no_float"))] #[export_module] mod f64_functions { #[cfg(feature = "f32_float")] pub mod basic_arithmetic { #[rhai_fn(name = "+")] pub fn add(x: f64, y: f64) -> f64 { x + y } #[rhai_fn(name = "-")] pub fn subtract(x: f64, y: f64) -> f64 { x - y } #[rhai_fn(name = "*")] pub fn multiply(x: f64, y: f64) -> f64 { x * y } #[rhai_fn(name = "/")] pub fn divide(x: f64, y: f64) -> f64 { x / y } #[rhai_fn(name = "%")] pub fn modulo(x: f64, y: f64) -> f64 { x % y } #[rhai_fn(name = "**")] pub fn pow_f_f(x: f64, y: f64) -> f64 { x.powf(y) } #[rhai_fn(name = "+")] pub fn add_if(x: INT, y: f64) -> f64 { (x as f64) + y } #[rhai_fn(name = "+")] pub fn add_fi(x: f64, y: INT) -> f64 { x + (y as f64) } #[rhai_fn(name = "-")] pub fn subtract_if(x: INT, y: f64) -> f64 { (x as f64) - y } #[rhai_fn(name = "-")] pub fn subtract_fi(x: f64, y: INT) -> f64 { x - (y as f64) } #[rhai_fn(name = "*")] pub fn multiply_if(x: INT, y: f64) -> f64 { (x as f64) * y } #[rhai_fn(name = "*")] pub fn multiply_fi(x: f64, y: INT) -> f64 { x * (y as f64) } #[rhai_fn(name = "/")] pub fn divide_if(x: INT, y: f64) -> f64 { (x as f64) / y } #[rhai_fn(name = "/")] pub fn divide_fi(x: f64, y: INT) -> f64 { x / (y as f64) } #[rhai_fn(name = "%")] pub fn modulo_if(x: INT, y: f64) -> f64 { (x as f64) % y } #[rhai_fn(name = "%")] pub fn modulo_fi(x: f64, y: INT) -> f64 { x % (y as f64) } } #[rhai_fn(name = "-")] pub fn neg(x: f64) -> f64 { -x } #[rhai_fn(name = "+")] pub const fn plus(x: f64) -> f64 { x } /// Return the absolute value of the floating-point number. pub fn abs(x: f64) -> f64 { x.abs() } /// Return the sign (as an integer) of the floating-point number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative #[rhai_fn(return_raw)] pub fn sign(x: f64) -> RhaiResultOf { match x.signum() { _ if x == 0.0 => Ok(0), x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), x => Ok(x as INT), } } /// Return true if the floating-point number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub fn is_zero(x: f64) -> bool { x == 0.0 } } #[cfg(feature = "decimal")] #[export_module] pub mod decimal_functions { use rust_decimal::{prelude::Zero, Decimal}; #[cfg(not(feature = "unchecked"))] pub mod builtin { use rust_decimal::MathematicalOps; #[rhai_fn(return_raw)] pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_add(y) .ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) } #[rhai_fn(return_raw)] pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_sub(y) .ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) } #[rhai_fn(return_raw)] pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_mul(y) .ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) } #[rhai_fn(return_raw)] pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { // Detect division by zero if y == Decimal::zero() { Err(make_err(format!("Division by zero: {x} / {y}"))) } else { x.checked_div(y) .ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } } #[rhai_fn(return_raw)] pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_rem(y) .ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) } #[rhai_fn(return_raw)] pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { // Raising to a very large power can take exponential time, so limit it to 1 million. // TODO: Remove this limit when `rust-decimal` is updated with the fix. if std::convert::TryInto::::try_into(y.round()).map_or(true, |v| v > 1_000_000) { return Err(make_err(format!("Exponential overflow: {x} ** {y}"))); } x.checked_powd(y) .ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) } } #[rhai_fn(name = "-")] pub fn neg(x: Decimal) -> Decimal { -x } #[rhai_fn(name = "+")] pub const fn plus(x: Decimal) -> Decimal { x } /// Return the absolute value of the decimal number. pub fn abs(x: Decimal) -> Decimal { x.abs() } /// Return the sign (as an integer) of the decimal number according to the following: /// /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative pub fn sign(x: Decimal) -> INT { if x == Decimal::zero() { 0 } else if x.is_sign_negative() { -1 } else { 1 } } /// Return true if the decimal number is zero. #[rhai_fn(get = "is_zero", name = "is_zero")] pub const fn is_zero(x: Decimal) -> bool { x.is_zero() } } rhai-1.21.0/src/packages/array_basic.rs000064400000000000000000001774041046102023000160370ustar 00000000000000#![cfg(not(feature = "no_index"))] use crate::api::deprecated::deprecated_array_functions; use crate::engine::OP_EQUALS; use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::types::fn_ptr::FnPtrType; use crate::{ def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, Position, RhaiResultOf, ERR, INT, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, cmp::Ordering, mem}; def_package! { /// Package of basic array utilities. pub BasicArrayPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "array", array_functions); combine_with_exported_module!(lib, "deprecated_array", deprecated_array_functions); // Register array iterator lib.set_iterable::(); } } #[export_module] pub mod array_functions { /// Number of elements in the array. #[rhai_fn(name = "len", get = "len", pure)] pub fn len(array: &mut Array) -> INT { array.len() as INT } /// Return true if the array is empty. #[rhai_fn(name = "is_empty", get = "is_empty", pure)] pub fn is_empty(array: &mut Array) -> bool { array.len() == 0 } /// Get a copy of the element at the `index` position in the array. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). /// * If `index` < -length of array, `()` is returned. /// * If `index` ≥ length of array, `()` is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// print(x.get(0)); // prints 1 /// /// print(x.get(-1)); // prints 3 /// /// print(x.get(99)); // prints empty (for '()') /// ``` pub fn get(array: &mut Array, index: INT) -> Dynamic { if array.is_empty() { return Dynamic::UNIT; } let (index, ..) = calc_offset_len(array.len(), index, 0); if index >= array.len() { return Dynamic::UNIT; } array[index].clone() } /// Set the element at the `index` position in the array to a new `value`. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). /// * If `index` < -length of array, the array is not modified. /// * If `index` ≥ length of array, the array is not modified. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// x.set(0, 42); /// /// print(x); // prints "[42, 2, 3]" /// /// x.set(-3, 0); /// /// print(x); // prints "[0, 2, 3]" /// /// x.set(99, 123); /// /// print(x); // prints "[0, 2, 3]" /// ``` pub fn set(array: &mut Array, index: INT, value: Dynamic) { if array.is_empty() { return; } let (index, ..) = calc_offset_len(array.len(), index, 0); if index >= array.len() { return; } array[index] = value; } /// Add a new element, which is not another array, to the end of the array. /// /// If `item` is `Array`, then `append` is more specific and will be called instead. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// x.push("hello"); /// /// print(x); // prints [1, 2, 3, "hello"] /// ``` pub fn push(array: &mut Array, item: Dynamic) { array.push(item); } /// Add all the elements of another array to the end of the array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// let y = [true, 'x']; /// /// x.append(y); /// /// print(x); // prints "[1, 2, 3, true, 'x']" /// ``` pub fn append(array: &mut Array, new_array: Array) { if new_array.is_empty() { return; } if array.is_empty() { *array = new_array; } else { array.extend(new_array); } } /// Combine two arrays into a new array and return it. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// let y = [true, 'x']; /// /// print(x + y); // prints "[1, 2, 3, true, 'x']" /// /// print(x); // prints "[1, 2, 3" /// ``` #[rhai_fn(name = "+")] pub fn concat(array1: Array, array2: Array) -> Array { if array2.is_empty() { return array1; } if array1.is_empty() { return array2; } let mut array = array1; array.extend(array2); array } /// Add a new element into the array at a particular `index` position. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). /// * If `index` < -length of array, the element is added to the beginning of the array. /// * If `index` ≥ length of array, the element is appended to the end of the array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// x.insert(0, "hello"); /// /// x.insert(2, true); /// /// x.insert(-2, 42); /// /// print(x); // prints ["hello", 1, true, 2, 42, 3] /// ``` pub fn insert(array: &mut Array, index: INT, item: Dynamic) { if array.is_empty() { array.push(item); return; } let (index, ..) = calc_offset_len(array.len(), index, 0); if index >= array.len() { array.push(item); } else { array.insert(index, item); } } /// Pad the array to at least the specified length with copies of a specified element. /// /// If `len` ≤ length of array, no padding is done. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// x.pad(5, 42); /// /// print(x); // prints "[1, 2, 3, 42, 42]" /// /// x.pad(3, 123); /// /// print(x); // prints "[1, 2, 3, 42, 42]" /// ``` #[rhai_fn(return_raw)] pub fn pad( ctx: NativeCallContext, array: &mut Array, len: INT, item: Dynamic, ) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; if len <= array.len() { return Ok(()); } let _ctx = ctx; // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 { let pad = len - array.len(); let (a, m, s) = crate::eval::calc_array_sizes(array); let (ax, mx, sx) = crate::eval::calc_data_sizes(&item, true); _ctx.engine() .throw_on_size((a + pad + ax * pad, m + mx * pad, s + sx * pad))?; } array.resize(len, item); Ok(()) } /// Remove the last element from the array and return it. /// /// If the array is empty, `()` is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// print(x.pop()); // prints 3 /// /// print(x); // prints "[1, 2]" /// ``` pub fn pop(array: &mut Array) -> Dynamic { if array.is_empty() { return Dynamic::UNIT; } array.pop().unwrap_or(Dynamic::UNIT) } /// Remove the first element from the array and return it. /// /// If the array is empty, `()` is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// print(x.shift()); // prints 1 /// /// print(x); // prints "[2, 3]" /// ``` pub fn shift(array: &mut Array) -> Dynamic { if array.is_empty() { return Dynamic::UNIT; } array.remove(0) } /// Remove the element at the specified `index` from the array and return it. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). /// * If `index` < -length of array, `()` is returned. /// * If `index` ≥ length of array, `()` is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3]; /// /// print(x.remove(1)); // prints 2 /// /// print(x); // prints "[1, 3]" /// /// print(x.remove(-2)); // prints 1 /// /// print(x); // prints "[3]" /// ``` pub fn remove(array: &mut Array, index: INT) -> Dynamic { let Ok(index) = calc_index(array.len(), index, true, || Err(())) else { return Dynamic::UNIT; }; array.remove(index) } /// Clear the array. pub fn clear(array: &mut Array) { if array.is_empty() { return; } array.clear(); } /// Cut off the array at the specified length. /// /// * If `len` ≤ 0, the array is cleared. /// * If `len` ≥ length of array, the array is not truncated. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// x.truncate(3); /// /// print(x); // prints "[1, 2, 3]" /// /// x.truncate(10); /// /// print(x); // prints "[1, 2, 3]" /// ``` pub fn truncate(array: &mut Array, len: INT) { if len <= 0 { array.clear(); return; } if array.is_empty() { return; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; if len > 0 { array.truncate(len); } else { array.clear(); } } /// Cut off the head of the array, leaving a tail of the specified length. /// /// * If `len` ≤ 0, the array is cleared. /// * If `len` ≥ length of array, the array is not modified. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// x.chop(3); /// /// print(x); // prints "[3, 4, 5]" /// /// x.chop(10); /// /// print(x); // prints "[3, 4, 5]" /// ``` pub fn chop(array: &mut Array, len: INT) { if len <= 0 { array.clear(); return; } if array.is_empty() { return; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; if len == 0 { array.clear(); } else if len < array.len() { array.drain(0..array.len() - len); } } /// Reverse all the elements in the array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// x.reverse(); /// /// print(x); // prints "[5, 4, 3, 2, 1]" /// ``` pub fn reverse(array: &mut Array) { if array.is_empty() { return; } array.reverse(); } /// Replace an exclusive range of the array with another array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [7, 8, 9, 10]; /// /// x.splice(1..3, y); /// /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(array: &mut Array, range: ExclusiveRange, replace: Array) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(array, start, end - start, replace); } /// Replace an inclusive range of the array with another array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [7, 8, 9, 10]; /// /// x.splice(1..=3, y); /// /// print(x); // prints "[1, 7, 8, 9, 10, 5]" /// ``` #[rhai_fn(name = "splice")] pub fn splice_inclusive_range(array: &mut Array, range: InclusiveRange, replace: Array) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(array, start, end - start + 1, replace); } /// Replace a portion of the array with another array. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, the other array is appended to the end of the array. /// * If `len` ≤ 0, the other array is inserted into the array at the `start` position without replacing any element. /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is replaced. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [7, 8, 9, 10]; /// /// x.splice(1, 2, y); /// /// print(x); // prints "[1, 7, 8, 9, 10, 4, 5]" /// /// x.splice(-5, 4, y); /// /// print(x); // prints "[1, 7, 7, 8, 9, 10, 5]" /// ``` pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) { if array.is_empty() { *array = replace; return; } let (start, len) = calc_offset_len(array.len(), start, len); if start >= array.len() { array.extend(replace); } else { array.splice(start..start + len, replace); } } /// Copy an exclusive range of the array and return it as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// print(x.extract(1..3)); // prints "[2, 3]" /// /// print(x); // prints "[1, 2, 3, 4, 5]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(array, start, end - start) } /// Copy an inclusive range of the array and return it as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// print(x.extract(1..=3)); // prints "[2, 3, 4]" /// /// print(x); // prints "[1, 2, 3, 4, 5]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(array, start, end - start + 1) } /// Copy a portion of the array and return it as a new array. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, an empty array is returned. /// * If `len` ≤ 0, an empty array is returned. /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is copied and returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// print(x.extract(1, 3)); // prints "[2, 3, 4]" /// /// print(x.extract(-3, 2)); // prints "[3, 4]" /// /// print(x); // prints "[1, 2, 3, 4, 5]" /// ``` pub fn extract(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); } let (start, len) = calc_offset_len(array.len(), start, len); if len == 0 { return Array::new(); } array[start..start + len].to_vec() } /// Copy a portion of the array beginning at the `start` position till the end and return it as /// a new array. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, the entire array is copied and returned. /// * If `start` ≥ length of array, an empty array is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// print(x.extract(2)); // prints "[3, 4, 5]" /// /// print(x.extract(-3)); // prints "[3, 4, 5]" /// /// print(x); // prints "[1, 2, 3, 4, 5]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_tail(array: &mut Array, start: INT) -> Array { extract(array, start, INT::MAX) } /// Cut off the array at `index` and return it as a new array. /// /// * If `index` < 0, position counts from the end of the array (`-1` is the last element). /// * If `index` is zero, the entire array is cut and returned. /// * If `index` < -length of array, the entire array is cut and returned. /// * If `index` ≥ length of array, nothing is cut from the array and an empty array is returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.split(2); /// /// print(y); // prints "[3, 4, 5]" /// /// print(x); // prints "[1, 2]" /// ``` #[rhai_fn(name = "split")] pub fn split_at(array: &mut Array, index: INT) -> Array { if array.is_empty() { return Array::new(); } let (start, len) = calc_offset_len(array.len(), index, INT::MAX); if start >= array.len() { return Array::new(); } if start == 0 { if len >= array.len() { return mem::take(array); } let mut result = Array::new(); result.extend(array.drain(array.len() - len..)); return result; } let mut result = Array::new(); result.extend(array.drain(start..)); result } /// Iterate through all the elements in the array, applying a `process` function to each element in turn. /// Each element is bound to `this` before calling the function. /// /// # Function Parameters /// /// * `this`: bound to array element (mutable) /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// x.for_each(|| this *= this); /// /// print(x); // prints "[1, 4, 9, 16, 25]" /// /// x.for_each(|i| this *= i); /// /// print(x); // prints "[0, 2, 6, 12, 20]" /// ``` #[rhai_fn(return_raw)] pub fn for_each(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf<()> { if array.is_empty() { return Ok(()); } for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; let _ = map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, None)?; } Ok(()) } /// Iterate through all the elements in the array, applying a `mapper` function to each element /// in turn, and return the results as a new array. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `mapper` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.map(|v| v * v); /// /// print(y); // prints "[1, 4, 9, 16, 25]" /// /// let y = x.map(|v, i| v * i); /// /// print(y); // prints "[0, 2, 6, 12, 20]" /// ``` #[rhai_fn(return_raw, pure)] pub fn map(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } let mut ar = Array::with_capacity(array.len()); for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, Some(0))?); } Ok(ar) } /// Iterate through all the elements in the array, applying a `filter` function to each element /// in turn, and return a copy of all elements (in order) that return `true` as a new array. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.filter(|v| v >= 3); /// /// print(y); // prints "[3, 4, 5]" /// /// let y = x.filter(|v, i| v * i >= 10); /// /// print(y); // prints "[12, 20]" /// ``` #[rhai_fn(return_raw, pure)] pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } let mut ar = Array::new(); for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if filter .call_raw_with_extra_args("filter", &ctx, Some(item), [], ex, Some(0))? .as_bool() .unwrap_or(false) { ar.push(item.clone()); } } Ok(ar) } /// Return `true` if the array contains an element that equals `value`. /// /// The operator `==` is used to compare elements with `value` and must be defined, /// otherwise `false` is assumed. /// /// This function also drives the `in` operator. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// // The 'in' operator calls 'contains' in the background /// if 4 in x { /// print("found!"); /// } /// ``` #[rhai_fn(return_raw, pure)] pub fn contains( ctx: NativeCallContext, array: &mut Array, value: Dynamic, ) -> RhaiResultOf { if array.is_empty() { return Ok(false); } for item in array { if ctx .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { // No default when comparing same type Err(err) } else { Ok(Dynamic::FALSE) } } _ => Err(err), })? .as_bool() .unwrap_or(false) { return Ok(true); } } Ok(false) } /// Find the first element in the array that equals a particular `value` and return its index. /// If no element equals `value`, `-1` is returned. /// /// The operator `==` is used to compare elements with `value` and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of(4)); // prints 3 (first index) /// /// print(x.index_of(9)); // prints -1 /// /// print(x.index_of("foo")); // prints -1: strings do not equal numbers /// ``` #[rhai_fn(return_raw, pure)] pub fn index_of( ctx: NativeCallContext, array: &mut Array, value: Dynamic, ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } index_of_starting_from(ctx, array, value, 0) } /// Find the first element in the array, starting from a particular `start` position, that /// equals a particular `value` and return its index. If no element equals `value`, `-1` is returned. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// /// The operator `==` is used to compare elements with `value` and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of(4, 2)); // prints 3 /// /// print(x.index_of(4, 5)); // prints 7 /// /// print(x.index_of(4, 15)); // prints -1: nothing found past end of array /// /// print(x.index_of(4, -5)); // prints 11: -5 = start from index 8 /// /// print(x.index_of(9, 1)); // prints -1: nothing equals 9 /// /// print(x.index_of("foo", 1)); // prints -1: strings do not equal numbers /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_starting_from( ctx: NativeCallContext, array: &mut Array, value: Dynamic, start: INT, ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } let (start, ..) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter_mut().enumerate().skip(start) { if ctx .call_native_fn_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if item.type_id() == value.type_id() { // No default when comparing same type Err(err) } else { Ok(Dynamic::FALSE) } } _ => Err(err), })? .as_bool() .unwrap_or(false) { return Ok(i as INT); } } Ok(-1 as INT) } /// Iterate through all the elements in the array, applying a `filter` function to each element /// in turn, and return the index of the first element that returns `true`. /// If no element returns `true`, `-1` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of(|v| v > 3)); // prints 3: 4 > 3 /// /// print(x.index_of(|v| v > 8)); // prints -1: nothing is > 8 /// /// print(x.index_of(|v, i| v * i > 20)); // prints 7: 4 * 7 > 20 /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_filter( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } index_of_filter_starting_from(ctx, array, filter, 0) } /// Iterate through all the elements in the array, starting from a particular `start` position, /// applying a `filter` function to each element in turn, and return the index of the first /// element that returns `true`. If no element returns `true`, `-1` is returned. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.index_of(|v| v > 1, 3)); // prints 5: 2 > 1 /// /// print(x.index_of(|v| v < 2, 9)); // prints -1: nothing < 2 past index 9 /// /// print(x.index_of(|v| v > 1, 15)); // prints -1: nothing found past end of array /// /// print(x.index_of(|v| v > 1, -5)); // prints 9: -5 = start from index 8 /// /// print(x.index_of(|v| v > 1, -99)); // prints 1: -99 = start from beginning /// /// print(x.index_of(|v, i| v * i > 20, 8)); // prints 10: 3 * 10 > 20 /// ``` #[rhai_fn(name = "index_of", return_raw, pure)] pub fn index_of_filter_starting_from( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, start: INT, ) -> RhaiResultOf { if array.is_empty() { return Ok(-1); } let (start, ..) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter_mut().enumerate().skip(start) { let ex = [(i as INT).into()]; if filter .call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex, Some(0))? .as_bool() .unwrap_or(false) { return Ok(i as INT); } } Ok(-1 as INT) } /// Iterate through all the elements in the array, applying a `filter` function to each element /// in turn, and return a copy of the first element that returns `true`. If no element returns /// `true`, `()` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 5, 8, 13]; /// /// print(x.find(|v| v > 3)); // prints 5: 5 > 3 /// /// print(x.find(|v| v > 13) ?? "not found"); // prints "not found": nothing is > 13 /// /// print(x.find(|v, i| v * i > 13)); // prints 5: 3 * 5 > 13 /// ``` #[rhai_fn(return_raw, pure)] pub fn find(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult { find_starting_from(ctx, array, filter, 0) } /// Iterate through all the elements in the array, starting from a particular `start` position, /// applying a `filter` function to each element in turn, and return a copy of the first element /// that returns `true`. If no element returns `true`, `()` is returned. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 5, 8, 13]; /// /// print(x.find(|v| v > 1, 2)); // prints 3: 3 > 1 /// /// print(x.find(|v| v < 2, 3) ?? "not found"); // prints "not found": nothing < 2 past index 3 /// /// print(x.find(|v| v > 1, 8) ?? "not found"); // prints "not found": nothing found past end of array /// /// print(x.find(|v| v > 1, -3)); // prints 5: -3 = start from index 4 /// /// print(x.find(|v| v > 0, -99)); // prints 1: -99 = start from beginning /// /// print(x.find(|v, i| v * i > 6, 3)); // prints 5: 5 * 4 > 6 /// ``` #[rhai_fn(name = "find", return_raw, pure)] pub fn find_starting_from( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, start: INT, ) -> RhaiResult { let index = index_of_filter_starting_from(ctx, array, filter, start)?; if index < 0 { return Ok(Dynamic::UNIT); } Ok(get(array, index)) } /// Iterate through all the elements in the array, applying a `mapper` function to each element /// in turn, and return the first result that is not `()`. Otherwise, `()` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `mapper` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}]; /// /// print(x.find_map(|v| v.alice)); // prints 1 /// /// print(x.find_map(|v| v.dave) ?? "not found"); // prints "not found" /// /// print(x.find_map(|| this.dave) ?? "not found"); // prints "not found" /// ``` #[rhai_fn(return_raw, pure)] pub fn find_map(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult { find_map_starting_from(ctx, array, filter, 0) } /// Iterate through all the elements in the array, starting from a particular `start` position, /// applying a `mapper` function to each element in turn, and return the first result that is not `()`. /// Otherwise, `()` is returned. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `mapper` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [#{alice: 1}, #{bob: 2}, #{bob: 3}, #{clara: 3}, #{alice: 0}, #{clara: 5}]; /// /// print(x.find_map(|v| v.alice, 2)); // prints 0 /// /// print(x.find_map(|v| v.bob, 4) ?? "not found"); // prints "not found" /// /// print(x.find_map(|v| v.alice, 8) ?? "not found"); // prints "not found" /// /// print(x.find_map(|| this.alice, 8) ?? "not found"); // prints "not found" /// /// print(x.find_map(|v| v.bob, -4)); // prints 3: -4 = start from index 2 /// /// print(x.find_map(|v| v.alice, -99)); // prints 1: -99 = start from beginning /// /// print(x.find_map(|| this.alice, -99)); // prints 1: -99 = start from beginning /// ``` #[rhai_fn(name = "find_map", return_raw, pure)] pub fn find_map_starting_from( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, start: INT, ) -> RhaiResult { if array.is_empty() { return Ok(Dynamic::UNIT); } let (start, ..) = calc_offset_len(array.len(), start, 0); for (i, item) in array.iter_mut().enumerate().skip(start) { let ex = [(i as INT).into()]; let value = filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex, Some(0))?; if !value.is_unit() { return Ok(value); } } Ok(Dynamic::UNIT) } /// Return `true` if any element in the array that returns `true` when applied the `filter` function. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.some(|v| v > 3)); // prints true /// /// print(x.some(|v| v > 10)); // prints false /// /// print(x.some(|v, i| i > v)); // prints true /// ``` #[rhai_fn(return_raw, pure)] pub fn some(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(false); } for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if filter .call_raw_with_extra_args("some", &ctx, Some(item), [], ex, Some(0))? .as_bool() .unwrap_or(false) { return Ok(true); } } Ok(false) } /// Return `true` if all elements in the array return `true` when applied the `filter` function. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// This method is marked _pure_; the `filter` function should not mutate array elements. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5]; /// /// print(x.all(|v| v > 3)); // prints false /// /// print(x.all(|v| v > 1)); // prints true /// /// print(x.all(|v, i| i > v)); // prints false /// ``` #[rhai_fn(return_raw, pure)] pub fn all(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(true); } for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if !filter .call_raw_with_extra_args("all", &ctx, Some(item), [], ex, Some(0))? .as_bool() .unwrap_or(false) { return Ok(false); } } Ok(true) } /// Remove duplicated _consecutive_ elements from the array. /// /// The operator `==` is used to compare elements and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let x = [1, 2, 2, 2, 3, 4, 3, 3, 2, 1]; /// /// x.dedup(); /// /// print(x); // prints "[1, 2, 3, 4, 3, 2, 1]" /// ``` pub fn dedup(ctx: NativeCallContext, array: &mut Array) { let comparer = FnPtr { name: ctx.engine().get_interned_string(OP_EQUALS), curry: <_>::default(), #[cfg(not(feature = "no_function"))] env: None, typ: FnPtrType::Normal, }; dedup_by_comparer(ctx, array, comparer); } /// Remove duplicated _consecutive_ elements from the array that return `true` when applied the /// `comparer` function. /// /// No element is removed if the correct `comparer` function does not exist. /// /// # Function Parameters /// /// * `element1`: copy of the current array element to compare /// * `element2`: copy of the next array element to compare /// /// ## Return Value /// /// `true` if `element1 == element2`, otherwise `false`. /// /// # Example /// /// ```rhai /// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1]; /// /// x.dedup(|a, b| a >= b); /// /// print(x); // prints "[1, 2, 3, 4]" /// ``` #[rhai_fn(name = "dedup")] pub fn dedup_by_comparer(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) { if array.is_empty() { return; } array.dedup_by(|x, y| { comparer .call_raw(&ctx, None, [y.clone(), x.clone()]) .unwrap_or(Dynamic::FALSE) .as_bool() .unwrap_or(false) }); } /// Reduce an array by iterating through all elements while applying the `reducer` function. /// /// # Function Parameters /// /// * `result`: accumulated result, initially `()` /// * `element`: copy of array element, or bound to `this` if omitted /// * `index` _(optional)_: current index in the array /// /// This method is marked _pure_; the `reducer` function should not mutate array elements. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce(|r, v| v + (r ?? 0)); /// /// print(y); // prints 15 /// /// let y = x.reduce(|r, v, i| v + i + (r ?? 0)); /// /// print(y); // prints 25 /// ``` #[rhai_fn(return_raw, pure)] pub fn reduce(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_with_initial(ctx, array, reducer, Dynamic::UNIT) } /// Reduce an array by iterating through all elements while applying the `reducer` function. /// /// # Function Parameters /// /// * `result`: accumulated result, starting with the value of `initial` /// * `element`: copy of array element, or bound to `this` if omitted /// * `index` _(optional)_: current index in the array /// /// This method is marked _pure_; the `reducer` function should not mutate array elements. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce(|r, v| v + r, 5); /// /// print(y); // prints 20 /// /// let y = x.reduce(|r, v, i| v + i + r, 5); /// /// print(y); // prints 30 /// ``` #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: FnPtr, initial: Dynamic, ) -> RhaiResult { if array.is_empty() { return Ok(initial); } array .iter_mut() .enumerate() .try_fold(initial, |result, (i, item)| { let ex = [(i as INT).into()]; reducer.call_raw_with_extra_args("reduce", &ctx, Some(item), [result], ex, Some(1)) }) } /// Reduce an array by iterating through all elements, in _reverse_ order, /// while applying the `reducer` function. /// /// # Function Parameters /// /// * `result`: accumulated result, initially `()` /// * `element`: copy of array element, or bound to `this` if omitted /// * `index` _(optional)_: current index in the array /// /// This method is marked _pure_; the `reducer` function should not mutate array elements. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce_rev(|r, v| v + (r ?? 0)); /// /// print(y); // prints 15 /// /// let y = x.reduce_rev(|r, v, i| v + i + (r ?? 0)); /// /// print(y); // prints 25 /// ``` #[rhai_fn(return_raw, pure)] pub fn reduce_rev(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult { reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT) } /// Reduce an array by iterating through all elements, in _reverse_ order, /// while applying the `reducer` function. /// /// # Function Parameters /// /// * `result`: accumulated result, starting with the value of `initial` /// * `element`: copy of array element, or bound to `this` if omitted /// * `index` _(optional)_: current index in the array /// /// This method is marked _pure_; the `reducer` function should not mutate array elements. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.reduce_rev(|r, v| v + r, 5); /// /// print(y); // prints 20 /// /// let y = x.reduce_rev(|r, v, i| v + i + r, 5); /// /// print(y); // prints 30 /// ``` #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_with_initial( ctx: NativeCallContext, array: &mut Array, reducer: FnPtr, initial: Dynamic, ) -> RhaiResult { if array.is_empty() { return Ok(initial); } let len = array.len(); array .iter_mut() .rev() .enumerate() .try_fold(initial, |result, (i, item)| { let ex = [((len - 1 - i) as INT).into()]; reducer.call_raw_with_extra_args( "reduce_rev", &ctx, Some(item), [result], ex, Some(1), ) }) } /// Iterate through all elements in two arrays, applying a `mapper` function to them, /// and return a new array containing the results. /// /// # Function Parameters /// /// * `array1`: First array /// * `array2`: Second array /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [9, 8, 7, 6]; /// /// let z = x.zip(y, |a, b| a + b); /// /// print(z); // prints [10, 10, 10, 10] /// /// let z = x.zip(y, |a, b, i| a + b + i); /// /// print(z); // prints [10, 11, 12, 13] /// ``` #[rhai_fn(return_raw, pure)] pub fn zip( ctx: NativeCallContext, array1: &mut Array, array2: Array, map: FnPtr, ) -> RhaiResultOf { if array1.is_empty() && array2.is_empty() { return Ok(Array::new()); } array1 .iter_mut() .zip(array2) .enumerate() .map(|(i, (x, y))| { map.call_raw_with_extra_args( "zip", &ctx, None, [x.clone(), y], [(i as INT).into()], None, ) }) .collect() } /// Sort the array based on applying the `comparer` function. /// /// # Function Parameters /// /// * `element1`: copy of the current array element to compare /// * `element2`: copy of the next array element to compare /// /// ## Return Value /// /// An integer number: /// /// * Any positive integer if `element1 > element2` /// * 0 if `element1 == element2` /// * Any negative integer if `element1 < element2` /// /// or a boolean value: /// /// * `true` if `element1 <= element2` /// * `false` if `element1 > element2` /// /// Any other return value type will yield unpredictable order. /// /// # Example /// /// ```rhai /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; /// /// // Do comparisons in reverse /// x.sort(|a, b| if a > b { -1 } else if a < b { 1 } else { 0 }); /// /// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]" /// ``` pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) { if array.len() <= 1 { return; } array.sort_by(|x, y| { comparer .call_raw(&ctx, None, [x.clone(), y.clone()]) .ok() .and_then(|v| { v.as_int() .or_else(|_| v.as_bool().map(|v| if v { -1 } else { 1 })) .ok() }) .map_or_else( || x.type_id().cmp(&y.type_id()), |v| match v { v if v > 0 => Ordering::Greater, v if v < 0 => Ordering::Less, 0 => Ordering::Equal, _ => unreachable!("v is {}", v), }, ) }); } /// Sort the array. /// /// All elements in the array must be of the same data type. /// /// # Supported Data Types /// /// * integer numbers /// * floating-point numbers /// * decimal numbers /// * characters /// * strings /// * booleans /// * `()` /// /// # Example /// /// ```rhai /// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]; /// /// x.sort(); /// /// print(x); // prints "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" /// ``` #[rhai_fn(name = "sort", return_raw)] pub fn sort_with_builtin(array: &mut Array) -> RhaiResultOf<()> { if array.len() <= 1 { return Ok(()); } let type_id = array[0].type_id(); if array.iter().any(|a| a.type_id() != type_id) { return Err(ERR::ErrorFunctionNotFound( "sort() cannot be called with elements of different types".into(), Position::NONE, ) .into()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = a.as_int().unwrap(); let b = b.as_int().unwrap(); a.cmp(&b) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = a.as_char().unwrap(); let b = b.as_char().unwrap(); a.cmp(&b) }); return Ok(()); } #[cfg(not(feature = "no_float"))] if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = a.as_float().unwrap(); let b = b.as_float().unwrap(); a.partial_cmp(&b).unwrap_or(Ordering::Equal) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = &*a.as_immutable_string_ref().unwrap(); let b = &*b.as_immutable_string_ref().unwrap(); a.cmp(b) }); return Ok(()); } #[cfg(feature = "decimal")] if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = a.as_decimal().unwrap(); let b = b.as_decimal().unwrap(); a.cmp(&b) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { let a = a.as_bool().unwrap(); let b = b.as_bool().unwrap(); a.cmp(&b) }); return Ok(()); } if type_id == TypeId::of::<()>() { return Ok(()); } Ok(()) } /// Remove all elements in the array that returns `true` when applied the `filter` function and /// return them as a new array. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.drain(|v| v < 3); /// /// print(x); // prints "[3, 4, 5]" /// /// print(y); // prints "[1, 2]" /// /// let z = x.drain(|v, i| v + i > 5); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(return_raw)] pub fn drain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } let mut drained = Array::with_capacity(array.len()); let mut i = 0; let mut x = 0; while x < array.len() { let ex = [(i as INT).into()]; if filter .call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex, Some(0))? .as_bool() .unwrap_or(false) { drained.push(array.remove(x)); } else { x += 1; } i += 1; } Ok(drained) } /// Remove all elements in the array within an exclusive `range` and return them as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.drain(1..3); /// /// print(x); // prints "[1, 4, 5]" /// /// print(y); // prints "[2, 3]" /// /// let z = x.drain(2..3); /// /// print(x); // prints "[1, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(name = "drain")] pub fn drain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); drain_range(array, start, end - start) } /// Remove all elements in the array within an inclusive `range` and return them as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.drain(1..=2); /// /// print(x); // prints "[1, 4, 5]" /// /// print(y); // prints "[2, 3]" /// /// let z = x.drain(2..=2); /// /// print(x); // prints "[1, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(name = "drain")] pub fn drain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); drain_range(array, start, end - start + 1) } /// Remove all elements within a portion of the array and return them as a new array. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, no element is removed and an empty array is returned. /// * If `len` ≤ 0, no element is removed and an empty array is returned. /// * If `start` position + `len` ≥ length of array, entire portion of the array after the `start` position is removed and returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.drain(1, 2); /// /// print(x); // prints "[1, 4, 5]" /// /// print(y); // prints "[2, 3]" /// /// let z = x.drain(-1, 1); /// /// print(x); // prints "[1, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(name = "drain")] pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); } let (start, len) = calc_offset_len(array.len(), start, len); if len == 0 { return Array::new(); } array.drain(start..start + len).collect() } /// Remove all elements in the array that do not return `true` when applied the `filter` /// function and return them as a new array. /// /// # No Function Parameter /// /// Array element (mutable) is bound to `this`. /// /// # Function Parameters /// /// * `element`: copy of array element /// * `index` _(optional)_: current index in the array /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.retain(|v| v >= 3); /// /// print(x); // prints "[3, 4, 5]" /// /// print(y); // prints "[1, 2]" /// /// let z = x.retain(|v, i| v + i <= 5); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[5]" /// ``` #[rhai_fn(return_raw)] pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { return Ok(Array::new()); } let mut drained = Array::new(); let mut i = 0; let mut x = 0; while x < array.len() { let ex = [(i as INT).into()]; if filter .call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex, Some(0))? .as_bool() .unwrap_or(false) { x += 1; } else { drained.push(array.remove(x)); } i += 1; } Ok(drained) } /// Remove all elements in the array not within an exclusive `range` and return them as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.retain(1..4); /// /// print(x); // prints "[2, 3, 4]" /// /// print(y); // prints "[1, 5]" /// /// let z = x.retain(1..3); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[1]" /// ``` #[rhai_fn(name = "retain")] pub fn retain_exclusive_range(array: &mut Array, range: ExclusiveRange) -> Array { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); retain_range(array, start, end - start) } /// Remove all elements in the array not within an inclusive `range` and return them as a new array. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.retain(1..=3); /// /// print(x); // prints "[2, 3, 4]" /// /// print(y); // prints "[1, 5]" /// /// let z = x.retain(1..=2); /// /// print(x); // prints "[3, 4]" /// /// print(z); // prints "[1]" /// ``` #[rhai_fn(name = "retain")] pub fn retain_inclusive_range(array: &mut Array, range: InclusiveRange) -> Array { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); retain_range(array, start, end - start + 1) } /// Remove all elements not within a portion of the array and return them as a new array. /// /// * If `start` < 0, position counts from the end of the array (`-1` is the last element). /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, all elements are removed returned. /// * If `len` ≤ 0, all elements are removed and returned. /// * If `start` position + `len` ≥ length of array, entire portion of the array before the `start` position is removed and returned. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// /// let y = x.retain(1, 2); /// /// print(x); // prints "[2, 3]" /// /// print(y); // prints "[1, 4, 5]" /// /// let z = x.retain(-1, 1); /// /// print(x); // prints "[3]" /// /// print(z); // prints "[2]" /// ``` #[rhai_fn(name = "retain")] pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { if array.is_empty() || len <= 0 { return Array::new(); } let (start, len) = calc_offset_len(array.len(), start, len); if len == 0 { return Array::new(); } let mut drained: Array = array.drain(..start).collect(); drained.extend(array.drain(len..)); drained } /// Return `true` if two arrays are equal (i.e. all elements are equal and in the same order). /// /// The operator `==` is used to compare elements and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [1, 2, 3, 4, 5]; /// let z = [1, 2, 3, 4]; /// /// print(x == y); // prints true /// /// print(x == z); // prints false /// ``` #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf { if array1.len() != array2.len() { return Ok(false); } if array1.is_empty() { return Ok(true); } let mut array2 = array2; for (a1, a2) in array1.iter_mut().zip(array2.iter_mut()) { if !ctx .call_native_fn_raw(OP_EQUALS, true, &mut [a1, a2]) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(ref fn_sig, ..) if fn_sig.starts_with(OP_EQUALS) => { if a1.type_id() == a2.type_id() { // No default when comparing same type Err(err) } else { Ok(Dynamic::FALSE) } } _ => Err(err), })? .as_bool() .unwrap_or(false) { return Ok(false); } } Ok(true) } /// Return `true` if two arrays are not-equal (i.e. any element not equal or not in the same order). /// /// The operator `==` is used to compare elements and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// let y = [1, 2, 3, 4, 5]; /// let z = [1, 2, 3, 4]; /// /// print(x != y); // prints false /// /// print(x != z); // prints true /// ``` #[rhai_fn(name = "!=", return_raw, pure)] pub fn not_equals( ctx: NativeCallContext, array1: &mut Array, array2: Array, ) -> RhaiResultOf { equals(ctx, array1, array2).map(|r| !r) } } rhai-1.21.0/src/packages/bit_field.rs000064400000000000000000000163321046102023000154710ustar 00000000000000use crate::eval::calc_index; use crate::plugin::*; use crate::{ def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT, INT_BITS, UNSIGNED_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; def_package! { /// Package of basic bit-field utilities. pub BitFieldPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "bit_field", bit_field_functions); } } #[export_module] mod bit_field_functions { /// Return `true` if the specified `bit` in the number is set. /// /// If `bit` < 0, position counts from the MSB (Most Significant Bit). /// /// # Example /// /// ```rhai /// let x = 123456; /// /// print(x.get_bit(5)); // prints false /// /// print(x.get_bit(6)); // prints true /// /// print(x.get_bit(-48)); // prints true on 64-bit /// ``` #[rhai_fn(return_raw)] pub fn get_bit(value: INT, bit: INT) -> RhaiResultOf { let bit = calc_index(INT_BITS, bit, true, || { ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into() })?; Ok((value & (1 << bit)) != 0) } /// Set the specified `bit` in the number if the new value is `true`. /// Clear the `bit` if the new value is `false`. /// /// If `bit` < 0, position counts from the MSB (Most Significant Bit). /// /// # Example /// /// ```rhai /// let x = 123456; /// /// x.set_bit(5, true); /// /// print(x); // prints 123488 /// /// x.set_bit(6, false); /// /// print(x); // prints 123424 /// /// x.set_bit(-48, false); /// /// print(x); // prints 57888 on 64-bit /// ``` #[rhai_fn(return_raw)] pub fn set_bit(value: &mut INT, bit: INT, new_value: bool) -> RhaiResultOf<()> { let bit = calc_index(INT_BITS, bit, true, || { ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into() })?; let mask = 1 << bit; if new_value { *value |= mask; } else { *value &= !mask; } Ok(()) } /// Return an exclusive range of bits in the number as a new number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// print(x.get_bits(5..10)); // print 18 /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); get_bits(value, from, to - from) } /// Return an inclusive range of bits in the number as a new number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// print(x.get_bits(5..=9)); // print 18 /// ``` #[rhai_fn(name = "get_bits", return_raw)] pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); get_bits(value, from, to - from + 1) } /// Return a portion of bits in the number as a new number. /// /// * If `start` < 0, position counts from the MSB (Most Significant Bit). /// * If `bits` ≤ 0, zero is returned. /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are returned. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// print(x.get_bits(5, 8)); // print 18 /// ``` #[rhai_fn(return_raw)] pub fn get_bits(value: INT, start: INT, bits: INT) -> RhaiResultOf { if bits <= 0 { return Ok(0); } let bit = calc_index(INT_BITS, start, true, || { ERR::ErrorBitFieldBounds(INT_BITS, start, Position::NONE).into() })?; #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let bits = if bit + bits as usize > INT_BITS { INT_BITS - bit } else { bits as usize }; if bit == 0 && bits == INT_BITS { return Ok(value); } // 2^bits - 1 #[allow(clippy::cast_possible_truncation)] let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; Ok(((value & (mask << bit)) >> bit) & mask) } /// Replace an exclusive range of bits in the number with a new value. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// x.set_bits(5..10, 42); /// /// print(x); // print 123200 /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range( value: &mut INT, range: ExclusiveRange, new_value: INT, ) -> RhaiResultOf<()> { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); set_bits(value, from, to - from, new_value) } /// Replace an inclusive range of bits in the number with a new value. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// x.set_bits(5..=9, 42); /// /// print(x); // print 123200 /// ``` #[rhai_fn(name = "set_bits", return_raw)] pub fn set_bits_range_inclusive( value: &mut INT, range: InclusiveRange, new_value: INT, ) -> RhaiResultOf<()> { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); set_bits(value, from, to - from + 1, new_value) } /// Replace a portion of bits in the number with a new value. /// /// * If `start` < 0, position counts from the MSB (Most Significant Bit). /// * If `bits` ≤ 0, the number is not modified. /// * If `start` position + `bits` ≥ total number of bits, the bits after the `start` position are replaced. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// x.set_bits(5, 8, 42); /// /// print(x); // prints 124224 /// /// x.set_bits(-16, 10, 42); /// /// print(x); // prints 11821949021971776 on 64-bit /// ``` #[rhai_fn(return_raw)] pub fn set_bits(value: &mut INT, bit: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> { if bits <= 0 { return Ok(()); } let bit = calc_index(INT_BITS, bit, true, || { ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into() })?; #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let bits = if bit + bits as usize > INT_BITS { INT_BITS - bit } else { bits as usize }; if bit == 0 && bits == INT_BITS { *value = new_value; return Ok(()); } // 2^bits - 1 #[allow(clippy::cast_possible_truncation)] let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; *value &= !(mask << bit); *value |= (new_value & mask) << bit; Ok(()) } } rhai-1.21.0/src/packages/blob_basic.rs000064400000000000000000001622271046102023000156340ustar 00000000000000#![cfg(not(feature = "no_index"))] use crate::eval::{calc_index, calc_offset_len}; use crate::plugin::*; use crate::{ def_package, Array, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, RhaiResultOf, INT, INT_BYTES, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, borrow::Cow, mem}; #[cfg(not(feature = "no_float"))] use crate::{FLOAT, FLOAT_BYTES}; def_package! { /// Package of basic BLOB utilities. pub BasicBlobPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "blob", blob_functions); combine_with_exported_module!(lib, "parse_int", parse_int_functions); combine_with_exported_module!(lib, "write_int", write_int_functions); combine_with_exported_module!(lib, "write_string", write_string_functions); #[cfg(not(feature = "no_float"))] { combine_with_exported_module!(lib, "parse_float", parse_float_functions); combine_with_exported_module!(lib, "write_float", write_float_functions); } // Register blob iterator lib.set_iterable::(); } } #[export_module] pub mod blob_functions { /// Return a new, empty BLOB. pub const fn blob() -> Blob { Blob::new() } /// Return a new BLOB of the specified length, filled with zeros. /// /// If `len` ≤ 0, an empty BLOB is returned. /// /// # Example /// /// ```rhai /// let b = blob(10); /// /// print(b); // prints "[0000000000000000 0000]" /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf { blob_with_capacity_and_value(ctx, len, 0) } /// Return a new BLOB of the specified length, filled with copies of the initial `value`. /// /// If `len` ≤ 0, an empty BLOB is returned. /// /// Only the lower 8 bits of the initial `value` are used; all other bits are ignored. /// /// # Example /// /// ```rhai /// let b = blob(10, 0x42); /// /// print(b); // prints "[4242424242424242 4242]" /// ``` #[rhai_fn(name = "blob", return_raw)] pub fn blob_with_capacity_and_value( ctx: NativeCallContext, len: INT, value: INT, ) -> RhaiResultOf { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.clamp(0, MAX_USIZE_INT) as usize; let _ctx = ctx; // Check if blob will be over max size limit #[cfg(not(feature = "unchecked"))] _ctx.engine().throw_on_size((len, 0, 0))?; let mut blob = Blob::new(); #[allow(clippy::cast_sign_loss)] blob.resize(len, (value & 0x0000_00ff) as u8); Ok(blob) } /// Convert the BLOB into an array of integers. /// /// # Example /// /// ```rhai /// let b = blob(5, 0x42); /// /// let x = b.to_array(); /// /// print(x); // prints "[66, 66, 66, 66, 66]" /// ``` #[rhai_fn(pure)] pub fn to_array(blob: &mut Blob) -> Array { blob.iter().map(|&ch| (ch as INT).into()).collect() } /// Convert the BLOB into a string. /// /// The byte stream must be valid UTF-8, otherwise an error is raised. /// /// # Example /// /// ```rhai /// let b = blob(5, 0x42); /// /// let x = b.as_string(); /// /// print(x); // prints "FFFFF" /// ``` pub fn as_string(blob: Blob) -> String { let s = String::from_utf8_lossy(&blob); match s { Cow::Borrowed(_) => String::from_utf8(blob).unwrap(), Cow::Owned(_) => s.into_owned(), } } /// Return the length of the BLOB. /// /// # Example /// /// ```rhai /// let b = blob(10, 0x42); /// /// print(b); // prints "[4242424242424242 4242]" /// /// print(b.len()); // prints 10 /// ``` #[rhai_fn(name = "len", get = "len", pure)] pub fn len(blob: &mut Blob) -> INT { blob.len() as INT } /// Return true if the BLOB is empty. #[rhai_fn(name = "is_empty", get = "is_empty", pure)] pub fn is_empty(blob: &mut Blob) -> bool { blob.len() == 0 } /// Return `true` if the BLOB contains a specified byte value. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.contains('h')); // prints true /// /// print(text.contains('x')); // prints false /// ``` #[rhai_fn(name = "contains")] pub fn contains(blob: &mut Blob, value: INT) -> bool { #[allow(clippy::cast_sign_loss)] blob.contains(&((value & 0x0000_00ff) as u8)) } /// Get the byte value at the `index` position in the BLOB. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element). /// * If `index` < -length of BLOB, zero is returned. /// * If `index` ≥ length of BLOB, zero is returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.get(0)); // prints 1 /// /// print(b.get(-1)); // prints 5 /// /// print(b.get(99)); // prints 0 /// ``` pub fn get(blob: &mut Blob, index: INT) -> INT { if blob.is_empty() { return 0; } let (index, ..) = calc_offset_len(blob.len(), index, 0); if index >= blob.len() { return 0; } blob[index] as INT } /// Set the particular `index` position in the BLOB to a new byte `value`. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `index` < -length of BLOB, the BLOB is not modified. /// * If `index` ≥ length of BLOB, the BLOB is not modified. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// b.set(0, 0x42); /// /// print(b); // prints "[4202030405]" /// /// b.set(-3, 0); /// /// print(b); // prints "[4202000405]" /// /// b.set(99, 123); /// /// print(b); // prints "[4202000405]" /// ``` pub fn set(blob: &mut Blob, index: INT, value: INT) { if blob.is_empty() { return; } let (index, ..) = calc_offset_len(blob.len(), index, 0); if index >= blob.len() { return; } #[allow(clippy::cast_sign_loss)] { blob[index] = (value & 0x0000_00ff) as u8; } } /// Add a new byte `value` to the end of the BLOB. /// /// Only the lower 8 bits of the `value` are used; all other bits are ignored. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b.push(0x42); /// /// print(b); // prints "[42]" /// ``` #[rhai_fn(name = "push", name = "append")] pub fn push(blob: &mut Blob, value: INT) { #[allow(clippy::cast_sign_loss)] blob.push((value & 0x0000_00ff) as u8); } /// Add another BLOB to the end of the BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(5, 0x42); /// let b2 = blob(3, 0x11); /// /// b1.push(b2); /// /// print(b1); // prints "[4242424242111111]" /// ``` pub fn append(blob1: &mut Blob, blob2: Blob) { if blob2.is_empty() { return; } if blob1.is_empty() { *blob1 = blob2; } else { blob1.extend(blob2); } } /// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB /// /// # Example /// /// ```rhai /// let b = blob(5, 0x42); /// /// b.append("hello"); /// /// print(b); // prints "[424242424268656c 6c6f]" /// ``` #[rhai_fn(name = "append")] pub fn append_str(blob: &mut Blob, string: &str) { if string.is_empty() { return; } blob.extend(string.as_bytes()); } /// Add a character (as UTF-8 encoded byte-stream) to the end of the BLOB /// /// # Example /// /// ```rhai /// let b = blob(5, 0x42); /// /// b.append('!'); /// /// print(b); // prints "[424242424221]" /// ``` #[rhai_fn(name = "append")] pub fn append_char(blob: &mut Blob, character: char) { let mut buf = [0_u8; 4]; let x = character.encode_utf8(&mut buf); blob.extend(x.as_bytes()); } /// Add a byte `value` to the BLOB at a particular `index` position. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `index` < -length of BLOB, the byte value is added to the beginning of the BLOB. /// * If `index` ≥ length of BLOB, the byte value is appended to the end of the BLOB. /// /// Only the lower 8 bits of the `value` are used; all other bits are ignored. /// /// # Example /// /// ```rhai /// let b = blob(5, 0x42); /// /// b.insert(2, 0x18); /// /// print(b); // prints "[4242184242]" /// ``` pub fn insert(blob: &mut Blob, index: INT, value: INT) { #[allow(clippy::cast_sign_loss)] let value = (value & 0x0000_00ff) as u8; if blob.is_empty() { blob.push(value); return; } let (index, ..) = calc_offset_len(blob.len(), index, 0); if index >= blob.len() { blob.push(value); } else { blob.insert(index, value); } } /// Pad the BLOB to at least the specified length with copies of a specified byte `value`. /// /// If `len` ≤ length of BLOB, no padding is done. /// /// Only the lower 8 bits of the `value` are used; all other bits are ignored. /// /// # Example /// /// ```rhai /// let b = blob(3, 0x42); /// /// b.pad(5, 0x18) /// /// print(b); // prints "[4242421818]" /// /// b.pad(3, 0xab) /// /// print(b); // prints "[4242421818]" /// ``` #[rhai_fn(return_raw)] pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, value: INT) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; #[allow(clippy::cast_sign_loss)] let value = (value & 0x0000_00ff) as u8; let _ctx = ctx; // Check if blob will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { return Err(crate::ERR::ErrorDataTooLarge( "Size of BLOB".to_string(), crate::Position::NONE, ) .into()); } if len > blob.len() { blob.resize(len, value); } Ok(()) } /// Remove the last byte from the BLOB and return it. /// /// If the BLOB is empty, zero is returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.pop()); // prints 5 /// /// print(b); // prints "[01020304]" /// ``` pub fn pop(blob: &mut Blob) -> INT { if blob.is_empty() { return 0; } blob.pop().map_or_else(|| 0, |v| v as INT) } /// Remove the first byte from the BLOB and return it. /// /// If the BLOB is empty, zero is returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.shift()); // prints 1 /// /// print(b); // prints "[02030405]" /// ``` pub fn shift(blob: &mut Blob) -> INT { if blob.is_empty() { return 0; } blob.remove(0) as INT } /// Remove the byte at the specified `index` from the BLOB and return it. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `index` < -length of BLOB, zero is returned. /// * If `index` ≥ length of BLOB, zero is returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(x.remove(1)); // prints 2 /// /// print(x); // prints "[01030405]" /// /// print(x.remove(-2)); // prints 4 /// /// print(x); // prints "[010305]" /// ``` pub fn remove(blob: &mut Blob, index: INT) -> INT { let Ok(index) = calc_index(blob.len(), index, true, || Err(())) else { return 0; }; blob.remove(index) as INT } /// Clear the BLOB. pub fn clear(blob: &mut Blob) { if blob.is_empty() { return; } blob.clear(); } /// Cut off the BLOB at the specified length. /// /// * If `len` ≤ 0, the BLOB is cleared. /// * If `len` ≥ length of BLOB, the BLOB is not truncated. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// b.truncate(3); /// /// print(b); // prints "[010203]" /// /// b.truncate(10); /// /// print(b); // prints "[010203]" /// ``` pub fn truncate(blob: &mut Blob, len: INT) { if len <= 0 { blob.clear(); return; } if blob.is_empty() { return; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; if len > 0 { blob.truncate(len); } else { blob.clear(); } } /// Cut off the head of the BLOB, leaving a tail of the specified length. /// /// * If `len` ≤ 0, the BLOB is cleared. /// * If `len` ≥ length of BLOB, the BLOB is not modified. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// b.chop(3); /// /// print(b); // prints "[030405]" /// /// b.chop(10); /// /// print(b); // prints "[030405]" /// ``` #[allow( clippy::cast_sign_loss, clippy::needless_pass_by_value, clippy::cast_possible_truncation )] pub fn chop(blob: &mut Blob, len: INT) { if blob.is_empty() { return; } if len > MAX_USIZE_INT { // len > BLOB length return; } if len <= 0 { blob.clear(); } else if (len as usize) < blob.len() { blob.drain(0..blob.len() - len as usize); } } /// Reverse the BLOB. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b); // prints "[0102030405]" /// /// b.reverse(); /// /// print(b); // prints "[0504030201]" /// ``` pub fn reverse(blob: &mut Blob) { if blob.is_empty() { return; } blob.reverse(); } /// Replace an exclusive `range` of the BLOB with another BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(10, 0x42); /// let b2 = blob(5, 0x18); /// /// b1.splice(1..4, b2); /// /// print(b1); // prints "[4218181818184242 42424242]" /// ``` #[rhai_fn(name = "splice")] pub fn splice_range(blob: &mut Blob, range: ExclusiveRange, replace: Blob) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); splice(blob, start, end - start, replace); } /// Replace an inclusive `range` of the BLOB with another BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(10, 0x42); /// let b2 = blob(5, 0x18); /// /// b1.splice(1..=4, b2); /// /// print(b1); // prints "[4218181818184242 424242]" /// ``` #[rhai_fn(name = "splice")] pub fn splice_range_inclusive(blob: &mut Blob, range: InclusiveRange, replace: Blob) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); splice(blob, start, end - start + 1, replace); } /// Replace a portion of the BLOB with another BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, the other BLOB is appended to the end of the BLOB. /// * If `len` ≤ 0, the other BLOB is inserted into the BLOB at the `start` position without replacing anything. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is replaced. /// /// # Example /// /// ```rhai /// let b1 = blob(10, 0x42); /// let b2 = blob(5, 0x18); /// /// b1.splice(1, 3, b2); /// /// print(b1); // prints "[4218181818184242 42424242]" /// /// b1.splice(-5, 4, b2); /// /// print(b1); // prints "[4218181818184218 1818181842]" /// ``` pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { if blob.is_empty() { *blob = replace; return; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { blob.extend(replace); } else { blob.splice(start..start + len, replace); } } /// Copy an exclusive `range` of the BLOB and return it as a new BLOB. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.extract(1..3)); // prints "[0203]" /// /// print(b); // prints "[0102030405]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); extract(blob, start, end - start) } /// Copy an inclusive `range` of the BLOB and return it as a new BLOB. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.extract(1..=3)); // prints "[020304]" /// /// print(b); // prints "[0102030405]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); extract(blob, start, end - start + 1) } /// Copy a portion of the BLOB and return it as a new BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, an empty BLOB is returned. /// * If `len` ≤ 0, an empty BLOB is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is copied and returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.extract(1, 3)); // prints "[020303]" /// /// print(b.extract(-3, 2)); // prints "[0304]" /// /// print(b); // prints "[0102030405]" /// ``` pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return Blob::new(); } blob[start..start + len].to_vec() } /// Copy a portion of the BLOB beginning at the `start` position till the end and return it as /// a new BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, the entire BLOB is copied and returned. /// * If `start` ≥ length of BLOB, an empty BLOB is returned. /// /// # Example /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// print(b.extract(2)); // prints "[030405]" /// /// print(b.extract(-3)); // prints "[030405]" /// /// print(b); // prints "[0102030405]" /// ``` #[rhai_fn(name = "extract")] pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { extract(blob, start, INT::MAX) } /// Cut off the BLOB at `index` and return it as a new BLOB. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `index` is zero, the entire BLOB is cut and returned. /// * If `index` < -length of BLOB, the entire BLOB is cut and returned. /// * If `index` ≥ length of BLOB, nothing is cut from the BLOB and an empty BLOB is returned. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.split(2); /// /// print(b2); // prints "[030405]" /// /// print(b1); // prints "[0102]" /// ``` #[rhai_fn(name = "split")] pub fn split_at(blob: &mut Blob, index: INT) -> Blob { if blob.is_empty() { return Blob::new(); } let (index, len) = calc_offset_len(blob.len(), index, INT::MAX); if index >= blob.len() { return Blob::new(); } if index == 0 { if len > blob.len() { return mem::take(blob); } let mut result = Blob::new(); result.extend(blob.drain(blob.len() - len..)); return result; } let mut result = Blob::new(); result.extend(blob.drain(index..)); result } /// Remove all bytes in the BLOB within an exclusive `range` and return them as a new BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.drain(1..3); /// /// print(b1); // prints "[010405]" /// /// print(b2); // prints "[0203]" /// /// let b3 = b1.drain(2..3); /// /// print(b1); // prints "[0104]" /// /// print(b3); // prints "[05]" /// ``` #[rhai_fn(name = "drain")] pub fn drain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); drain(blob, start, end - start) } /// Remove all bytes in the BLOB within an inclusive `range` and return them as a new BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.drain(1..=2); /// /// print(b1); // prints "[010405]" /// /// print(b2); // prints "[0203]" /// /// let b3 = b1.drain(2..=2); /// /// print(b1); // prints "[0104]" /// /// print(b3); // prints "[05]" /// ``` #[rhai_fn(name = "drain")] pub fn drain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); drain(blob, start, end - start + 1) } /// Remove all bytes within a portion of the BLOB and return them as a new BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, nothing is removed and an empty BLOB is returned. /// * If `len` ≤ 0, nothing is removed and an empty BLOB is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is removed and returned. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.drain(1, 2); /// /// print(b1); // prints "[010405]" /// /// print(b2); // prints "[0203]" /// /// let b3 = b1.drain(-1, 1); /// /// print(b3); // prints "[0104]" /// /// print(z); // prints "[5]" /// ``` pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return Blob::new(); } blob.drain(start..start + len).collect() } /// Remove all bytes in the BLOB not within an exclusive `range` and return them as a new BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.retain(1..4); /// /// print(b1); // prints "[020304]" /// /// print(b2); // prints "[0105]" /// /// let b3 = b1.retain(1..3); /// /// print(b1); // prints "[0304]" /// /// print(b2); // prints "[01]" /// ``` #[rhai_fn(name = "retain")] pub fn retain_range(blob: &mut Blob, range: ExclusiveRange) -> Blob { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); retain(blob, start, end - start) } /// Remove all bytes in the BLOB not within an inclusive `range` and return them as a new BLOB. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.retain(1..=3); /// /// print(b1); // prints "[020304]" /// /// print(b2); // prints "[0105]" /// /// let b3 = b1.retain(1..=2); /// /// print(b1); // prints "[0304]" /// /// print(b2); // prints "[01]" /// ``` #[rhai_fn(name = "retain")] pub fn retain_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> Blob { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); retain(blob, start, end - start + 1) } /// Remove all bytes not within a portion of the BLOB and return them as a new BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, all elements are removed returned. /// * If `len` ≤ 0, all elements are removed and returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB before the `start` position is removed and returned. /// /// # Example /// /// ```rhai /// let b1 = blob(); /// /// b1 += 1; b1 += 2; b1 += 3; b1 += 4; b1 += 5; /// /// let b2 = b1.retain(1, 2); /// /// print(b1); // prints "[0203]" /// /// print(b2); // prints "[010405]" /// /// let b3 = b1.retain(-1, 1); /// /// print(b1); // prints "[03]" /// /// print(b3); // prints "[02]" /// ``` pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob { if blob.is_empty() || len <= 0 { return Blob::new(); } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return mem::take(blob); } let mut drained: Blob = blob.drain(..start).collect(); drained.extend(blob.drain(len..)); drained } } #[export_module] mod parse_int_functions { #[inline] fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT { if blob.is_empty() || len <= 0 { return 0; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return 0; } let len = usize::min(len, INT_BYTES); let mut buf = [0_u8; INT_BYTES]; buf[..len].copy_from_slice(&blob[start..][..len]); if is_le { INT::from_le_bytes(buf) } else { INT::from_be_bytes(buf) } } /// Parse the bytes within an exclusive `range` in the BLOB as an `INT` /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_le_int(1..3); // parse two bytes /// /// print(x.to_hex()); // prints "0302" /// ``` #[rhai_fn(name = "parse_le_int")] pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_le_int(blob, start, end - start) } /// Parse the bytes within an inclusive `range` in the BLOB as an `INT` /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_le_int(1..=3); // parse three bytes /// /// print(x.to_hex()); // prints "040302" /// ``` #[rhai_fn(name = "parse_le_int")] pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_le_int(blob, start, end - start + 1) } /// Parse the bytes beginning at the `start` position in the BLOB as an `INT` /// in little-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_le_int(1, 2); /// /// print(x.to_hex()); // prints "0302" /// ``` pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, true) } /// Parse the bytes within an exclusive `range` in the BLOB as an `INT` /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_be_int(1..3); // parse two bytes /// /// print(x.to_hex()); // prints "02030000...00" /// ``` #[rhai_fn(name = "parse_be_int")] pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_be_int(blob, start, end - start) } /// Parse the bytes within an inclusive `range` in the BLOB as an `INT` /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_be_int(1..=3); // parse three bytes /// /// print(x.to_hex()); // prints "0203040000...00" /// ``` #[rhai_fn(name = "parse_be_int")] pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_be_int(blob, start, end - start + 1) } /// Parse the bytes beginning at the `start` position in the BLOB as an `INT` /// in big-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. /// /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; /// /// let x = b.parse_be_int(1, 2); /// /// print(x.to_hex()); // prints "02030000...00" /// ``` pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { parse_int(blob, start, len, false) } } #[cfg(not(feature = "no_float"))] #[export_module] mod parse_float_functions { #[inline] fn parse_float(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> FLOAT { if blob.is_empty() || len <= 0 { return 0.0; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return 0.0; } let len = usize::min(len, FLOAT_BYTES); let mut buf = [0_u8; FLOAT_BYTES]; buf[..len].copy_from_slice(&blob[start..][..len]); if is_le { FLOAT::from_le_bytes(buf) } else { FLOAT::from_be_bytes(buf) } } /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_le_float")] pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_le_float(blob, start, end - start) } /// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT` /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_le_float")] pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_le_float(blob, start, end - start + 1) } /// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT` /// in little-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored. pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, true) } /// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT` /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_be_float")] pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); parse_be_float(blob, start, end - start) } /// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT` /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored. #[rhai_fn(name = "parse_be_float")] pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); parse_be_float(blob, start, end - start + 1) } /// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT` /// in big-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded. /// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored. pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { parse_float(blob, start, len, false) } } #[export_module] mod write_int_functions { #[inline] fn write_int(blob: &mut Blob, start: INT, len: INT, value: INT, is_le: bool) { if blob.is_empty() || len <= 0 { return; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return; } let len = usize::min(len, INT_BYTES); let buf = if is_le { value.to_le_bytes() } else { value.to_be_bytes() }; blob[start..][..len].copy_from_slice(&buf[..len]); } /// Write an `INT` value to the bytes within an exclusive `range` in the BLOB /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1..3, 0x12345678); /// /// print(b); // prints "[0078560000000000]" /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_int(blob, start, end - start, value); } /// Write an `INT` value to the bytes within an inclusive `range` in the BLOB /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1..=3, 0x12345678); /// /// print(b); // prints "[0078563400000000]" /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_int(blob, start, end - start + 1, value); } /// Write an `INT` value to the bytes beginning at the `start` position in the BLOB /// in little-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1, 3, 0x12345678); /// /// print(b); // prints "[0078563400000000]" /// ``` #[rhai_fn(name = "write_le")] pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, true); } /// Write an `INT` value to the bytes within an exclusive `range` in the BLOB /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1..3, 0x99); /// /// print(b); // prints "[4200004242424242]" /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_int(blob, start, end - start, value); } /// Write an `INT` value to the bytes within an inclusive `range` in the BLOB /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1..=3, 0x99); /// /// print(b); // prints "[4200000042424242]" /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_int(blob, start, end - start + 1, value); } /// Write an `INT` value to the bytes beginning at the `start` position in the BLOB /// in big-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1, 3, 0x99); /// /// print(b); // prints "[4200000042424242]" /// ``` #[rhai_fn(name = "write_be")] pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { write_int(blob, start, len, value, false); } } #[cfg(not(feature = "no_float"))] #[export_module] mod write_float_functions { #[inline] fn write_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT, is_le: bool) { if blob.is_empty() || len <= 0 { return; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return; } let len = usize::min(len, FLOAT_BYTES); let buf = if is_le { value.to_le_bytes() } else { value.to_be_bytes() }; blob[start..][..len].copy_from_slice(&buf[..len]); } /// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_le_float(blob, start, end - start, value); } /// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB /// in little-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_le_float(blob, start, end - start + 1, value); } /// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB /// in little-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_le")] pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, true); } /// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_be_float(blob, start, end - start, value); } /// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB /// in big-endian byte order. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_be_float(blob, start, end - start + 1, value); } /// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB /// in big-endian byte order. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, zero is returned. /// * If `len` ≤ 0, zero is returned. /// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed. /// /// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written. /// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified. #[rhai_fn(name = "write_be")] pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { write_float(blob, start, len, value, false); } } #[export_module] mod write_string_functions { #[inline] fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) { if len <= 0 || blob.is_empty() || string.is_empty() { return; } let (start, len) = calc_offset_len(blob.len(), start, len); if len == 0 { return; } let len = usize::min(len, string.len()); if ascii_only { string .chars() .filter(char::is_ascii) .take(len) .map(|ch| ch as u8) .enumerate() .for_each(|(i, x)| blob[start + i] = x); } else { blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]); } } /// Write a string to the bytes within an exclusive `range` in the BLOB in UTF-8 encoding. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1..5, "朝には紅顔ありて夕べには白骨となる"); /// /// print(b); // prints "[00e69c9de3000000]" /// ``` #[rhai_fn(name = "write_utf8")] pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_string(blob, start, end - start, string, false); } /// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1..=5, "朝には紅顔ありて夕べには白骨となる"); /// /// print(b); // prints "[00e69c9de3810000]" /// ``` #[rhai_fn(name = "write_utf8")] pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_string(blob, start, end - start + 1, string, false); } /// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, the BLOB is not modified. /// * If `len` ≤ 0, the BLOB is not modified. /// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1, 5, "朝には紅顔ありて夕べには白骨となる"); /// /// print(b); // prints "[00e69c9de3810000]" /// ``` #[rhai_fn(name = "write_utf8")] pub fn write_utf8_string(blob: &mut Blob, start: INT, len: INT, string: &str) { write_string(blob, start, len, string, false); } /// Write an ASCII string to the bytes within an exclusive `range` in the BLOB. /// /// Each ASCII character encodes to one single byte in the BLOB. /// Non-ASCII characters are ignored. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1..5, "hello, world!"); /// /// print(b); // prints "[0068656c6c000000]" /// ``` #[rhai_fn(name = "write_ascii")] pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); write_string(blob, start, end - start, string, true); } /// Write an ASCII string to the bytes within an inclusive `range` in the BLOB. /// /// Each ASCII character encodes to one single byte in the BLOB. /// Non-ASCII characters are ignored. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1..=5, "hello, world!"); /// /// print(b); // prints "[0068656c6c6f0000]" /// ``` #[rhai_fn(name = "write_ascii")] pub fn write_ascii_string_range_inclusive( blob: &mut Blob, range: InclusiveRange, string: &str, ) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); write_string(blob, start, end - start + 1, string, true); } /// Write an ASCII string to the bytes within an exclusive `range` in the BLOB. /// /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte). /// * If `start` < -length of BLOB, position counts from the beginning of the BLOB. /// * If `start` ≥ length of BLOB, the BLOB is not modified. /// * If `len` ≤ 0, the BLOB is not modified. /// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified. /// /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1, 5, "hello, world!"); /// /// print(b); // prints "[0068656c6c6f0000]" /// ``` #[rhai_fn(name = "write_ascii")] pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) { write_string(blob, start, len, string, true); } } rhai-1.21.0/src/packages/debugging.rs000064400000000000000000000063561046102023000155100ustar 00000000000000#![cfg(feature = "debugging")] use crate::def_package; use crate::plugin::*; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] use crate::{Array, Dynamic, NativeCallContext}; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] use crate::Map; def_package! { /// Package of basic debugging utilities. pub DebuggingPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "debugging", debugging_functions); } } #[export_module] mod debugging_functions { /// Get an array of object maps containing the function calls stack. /// /// If there is no debugging interface registered, an empty array is returned. /// /// An array of strings is returned under `no_object`. #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] pub fn back_trace(ctx: NativeCallContext) -> Array { use crate::debugger::CallStackFrame; ctx.global_runtime_state() .debugger .as_ref() .map_or_else(Array::new, |debugger| { debugger .call_stack() .iter() .rev() .filter(|CallStackFrame { fn_name, args, .. }| { fn_name != "back_trace" || !args.is_empty() }) .map( |frame @ CallStackFrame { fn_name: _fn_name, args: _args, source: _source, pos: _pos, }| { let display = frame.to_string(); #[cfg(not(feature = "no_object"))] { use crate::INT; let mut map = Map::new(); map.insert("display".into(), display.into()); map.insert("fn_name".into(), _fn_name.into()); if !_args.is_empty() { map.insert("args".into(), _args.iter().cloned().collect()); } if let Some(source) = _source { map.insert("source".into(), source.into()); } if !_pos.is_none() { map.insert("line".into(), (_pos.line().unwrap() as INT).into()); map.insert( "position".into(), (_pos.position().unwrap_or(0) as INT).into(), ); } Dynamic::from_map(map) } #[cfg(feature = "no_object")] display.into() }, ) .collect() }) } } rhai-1.21.0/src/packages/fn_basic.rs000064400000000000000000000023231046102023000153070ustar 00000000000000use crate::plugin::*; use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; #[cfg(feature = "no_std")] use std::prelude::v1::*; def_package! { /// Package of basic function pointer utilities. pub BasicFnPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); } } #[export_module] mod fn_ptr_functions { /// Return the name of the function. /// /// # Example /// /// ```rhai /// fn double(x) { x * 2 } /// /// let f = Fn("double"); /// /// print(f.name); // prints "double" /// ``` #[rhai_fn(name = "name", get = "name", pure)] pub fn name(fn_ptr: &mut FnPtr) -> ImmutableString { fn_ptr.fn_name_raw().clone() } /// Return `true` if the function is an anonymous function. /// /// # Example /// /// ```rhai /// let f = |x| x * 2; /// /// print(f.is_anonymous); // prints true /// ``` #[cfg(not(feature = "no_function"))] #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] pub fn is_anonymous(fn_ptr: &mut FnPtr) -> bool { fn_ptr.is_anonymous() } } rhai-1.21.0/src/packages/iter_basic.rs000064400000000000000000000516601046102023000156570ustar 00000000000000use crate::eval::calc_index; use crate::plugin::*; use crate::FuncRegistration; use crate::{ def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, ERR, INT, INT_BITS, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::type_name, cmp::Ordering, fmt::Debug, iter::{ExactSizeIterator, FusedIterator}, ops::{Range, RangeInclusive}, vec::IntoIter, }; #[cfg(not(feature = "no_float"))] use crate::FLOAT; #[cfg(feature = "decimal")] use rust_decimal::Decimal; #[cfg(not(feature = "unchecked"))] #[inline(always)] #[allow(clippy::needless_pass_by_value)] fn std_add(x: T, y: T) -> Option where T: num_traits::CheckedAdd, { x.checked_add(&y) } #[inline(always)] #[allow(dead_code)] #[allow(clippy::unnecessary_wraps, clippy::needless_pass_by_value)] fn regular_add(x: T, y: T) -> Option where T: std::ops::Add, { Some(x + y) } // Range iterator with step #[derive(Clone, Hash, Eq, PartialEq)] pub struct StepRange { /// Start of the range. pub from: T, /// End of the range (exclusive). pub to: T, /// Step value. pub step: T, /// Increment function. pub add: fn(T, T) -> Option, /// Direction of iteration. /// > 0 = forward, < 0 = backward, 0 = done. pub dir: i8, } impl Debug for StepRange { #[cold] #[inline(never)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple(&format!("StepRange<{}>", type_name::())) .field(&self.from) .field(&self.to) .field(&self.step) .finish() } } impl StepRange { /// Create a new [`StepRange`]. pub fn new(from: T, to: T, step: T, add: fn(T, T) -> Option) -> RhaiResultOf { let mut dir = 0; if let Some(n) = add(from, step) { #[cfg(not(feature = "unchecked"))] if n == from { return Err(ERR::ErrorInFunctionCall( "range".to_string(), String::new(), ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE) .into(), Position::NONE, ) .into()); } match from.partial_cmp(&to).unwrap_or(Ordering::Equal) { Ordering::Less if n > from => dir = 1, Ordering::Greater if n < from => dir = -1, _ => (), } } Ok(Self { from, to, step, add, dir, }) } } impl Iterator for StepRange { type Item = T; fn next(&mut self) -> Option { if self.dir == 0 { return None; } let v = self.from; self.from = (self.add)(self.from, self.step)?; match self.dir.cmp(&0) { Ordering::Greater if self.from >= self.to => self.dir = 0, Ordering::Less if self.from <= self.to => self.dir = 0, Ordering::Equal => unreachable!("`dir` != 0"), _ => (), } Some(v) } } impl FusedIterator for StepRange {} /// Bit-field iterator with step. /// /// Values are the base number and the number of bits to iterate. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct BitRange(INT, usize); impl BitRange { /// Create a new [`BitRange`]. pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { let from = calc_index(INT_BITS, from, true, || { ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into() })?; #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = if len < 0 { 0 } else if from + (len as usize) > INT_BITS { INT_BITS - from } else { len as usize }; Ok(Self(value >> from, len)) } } impl Iterator for BitRange { type Item = bool; fn next(&mut self) -> Option { if self.1 == 0 { None } else { let r = (self.0 & 0x0001) != 0; self.0 >>= 1; self.1 -= 1; Some(r) } } #[inline(always)] fn size_hint(&self) -> (usize, Option) { (self.1, Some(self.1)) } } impl FusedIterator for BitRange {} impl ExactSizeIterator for BitRange { #[inline(always)] fn len(&self) -> usize { self.1 } } // String iterator over characters. #[derive(Debug, Clone)] pub struct CharsStream(IntoIter); impl CharsStream { /// Create a new [`CharsStream`]. #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn new(string: &str, from: INT, len: INT) -> Self { if len <= 0 || from > MAX_USIZE_INT { return Self(Vec::new().into_iter()); } let len = len.min(MAX_USIZE_INT) as usize; if from >= 0 { return Self( string .chars() .skip(from as usize) .take(len) .collect::>() .into_iter(), ); } let abs_from = from.unsigned_abs() as usize; let num_chars = string.chars().count(); let offset = if num_chars < abs_from { 0 } else { num_chars - abs_from }; Self( string .chars() .skip(offset) .take(len) .collect::>() .into_iter(), ) } } impl Iterator for CharsStream { type Item = char; #[inline(always)] fn next(&mut self) -> Option { self.0.next() } #[inline(always)] fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } impl FusedIterator for CharsStream {} impl ExactSizeIterator for CharsStream { #[inline(always)] fn len(&self) -> usize { self.0.len() } } macro_rules! reg_range { ($lib:ident => $( $arg_type:ty ),*) => { $({ $lib.set_iterator::>(); #[export_module] mod range_function { /// Return an iterator over the exclusive range of `from..to`. /// The value `to` is never included. /// /// # Example /// /// ```rhai /// // prints all values from 8 to 17 /// for n in range(8, 18) { /// print(n); /// } /// ``` pub const fn range (from: $arg_type, to: $arg_type) -> Range<$arg_type> { from..to } } combine_with_exported_module!($lib, stringify!($arg_type), range_function); $lib.set_iterator::>(); })* }; ($lib:ident |> $( $arg_type:ty ),*) => { #[cfg(not(feature = "unchecked"))] reg_range!($lib |> std_add => $( $arg_type ),*); #[cfg(feature = "unchecked")] reg_range!($lib |> regular_add => $( $arg_type ),*); }; ($lib:ident |> $add:ident => $( $arg_type:ty ),*) => { $({ $lib.set_iterator::>(); #[export_module] mod range_functions { /// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`. /// The value `to` is never included. /// /// If `from` > `to` and `step` < 0, iteration goes backwards. /// /// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned. /// /// # Example /// /// ```rhai /// // prints all values from 8 to 17 in steps of 3 /// for n in range(8, 18, 3) { /// print(n); /// } /// /// // prints all values down from 18 to 9 in steps of -3 /// for n in range(18, 8, -3) { /// print(n); /// } /// ``` #[rhai_fn(name = "range", return_raw)] pub fn range_from_to_stepped (from: $arg_type, to: $arg_type, step: $arg_type) -> RhaiResultOf> { StepRange::new(from, to, step, $add) } /// Return an iterator over an exclusive range, each iteration increasing by `step`. /// /// If `range` is reversed and `step` < 0, iteration goes backwards. /// /// Otherwise, if `range` is empty, an empty iterator is returned. /// /// # Example /// /// ```rhai /// // prints all values from 8 to 17 in steps of 3 /// for n in range(8..18, 3) { /// print(n); /// } /// /// // prints all values down from 18 to 9 in steps of -3 /// for n in range(18..8, -3) { /// print(n); /// } /// ``` #[rhai_fn(name = "range", return_raw)] pub fn range_stepped (range: std::ops::Range<$arg_type>, step: $arg_type) -> RhaiResultOf> { StepRange::new(range.start, range.end, step, $add) } } combine_with_exported_module!($lib, stringify!($arg_type), range_functions); })* }; } def_package! { /// Package of basic range iterators pub BasicIteratorPackage(lib) { lib.set_standard_lib(true); // Register iterators for standard types. #[cfg(not(feature = "no_index"))] { lib.set_iterable::(); lib.set_iterable::(); } lib.set_iter(TypeId::of::(), |value| Box::new( CharsStream::new(value.cast::().as_str(), 0, MAX_USIZE_INT).map(Into::into) )); // Register iterator types. lib.set_iterator::(); lib.set_iterator::(); // Register range functions. reg_range!(lib => INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { reg_range!(lib => i8, u8, i16, u16, i32, u32, i64, u64); #[cfg(not(target_family = "wasm"))] reg_range!(lib => i128, u128); } reg_range!(lib |> INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { reg_range!(lib |> i8, u8, i16, u16, i32, u32, i64, u64); #[cfg(not(target_family = "wasm"))] reg_range!(lib |> i128, u128); } #[cfg(not(feature = "no_float"))] reg_range!(lib |> regular_add => FLOAT); #[cfg(feature = "decimal")] reg_range!(lib |> Decimal); // Register iterator functions combine_with_exported_module!(lib, "iterator", iterator_functions); combine_with_exported_module!(lib, "range", range_functions); } } #[export_module] mod iterator_functions { /// Return an iterator over an exclusive range of characters in the string. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars(2..5) { /// print(ch); /// } /// ``` #[rhai_fn(name = "chars")] pub fn chars_from_exclusive_range(string: &str, range: ExclusiveRange) -> CharsStream { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); CharsStream::new(string, from, to - from) } /// Return an iterator over an inclusive range of characters in the string. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars(2..=6) { /// print(ch); /// } /// ``` #[rhai_fn(name = "chars")] pub fn chars_from_inclusive_range(string: &str, range: InclusiveRange) -> CharsStream { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); CharsStream::new(string, from, to - from + 1) } /// Return an iterator over a portion of characters in the string. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, an empty iterator is returned. /// * If `len` ≤ 0, an empty iterator is returned. /// * If `start` position + `len` ≥ length of string, all characters of the string after the `start` position are iterated. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars(2, 4) { /// print(ch); /// } /// ``` #[rhai_fn(name = "chars")] pub fn chars_from_start_len(string: &str, start: INT, len: INT) -> CharsStream { CharsStream::new(string, start, len) } /// Return an iterator over the characters in the string starting from the `start` position. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, an empty iterator is returned. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars(2) { /// print(ch); /// } /// ``` #[rhai_fn(name = "chars")] pub fn chars_from_start(string: &str, start: INT) -> CharsStream { CharsStream::new(string, start, INT::MAX) } /// Return an iterator over the characters in the string. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars() { /// print(ch); /// } /// ``` #[rhai_fn(name = "chars")] pub fn chars(string: &str) -> CharsStream { CharsStream::new(string, 0, INT::MAX) } /// Return an iterator over all the characters in the string. /// /// # Example /// /// ```rhai /// for ch in "hello, world!".chars {" /// print(ch); /// } /// ``` #[cfg(not(feature = "no_object"))] #[rhai_fn(get = "chars")] pub fn get_chars(string: &str) -> CharsStream { CharsStream::new(string, 0, INT::MAX) } /// Return an iterator over an exclusive range of bits in the number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits(10..24) { /// print(bit); /// } /// ``` #[rhai_fn(name = "bits", return_raw)] pub fn bits_from_exclusive_range(value: INT, range: ExclusiveRange) -> RhaiResultOf { let from = INT::max(range.start, 0); let to = INT::max(range.end, from); BitRange::new(value, from, to - from) } /// Return an iterator over an inclusive range of bits in the number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits(10..=23) { /// print(bit); /// } /// ``` #[rhai_fn(name = "bits", return_raw)] pub fn bits_from_inclusive_range(value: INT, range: InclusiveRange) -> RhaiResultOf { let from = INT::max(*range.start(), 0); let to = INT::max(*range.end(), from - 1); // It is OK to use `INT::MAX` as the length to avoid an addition overflow // even though it is an off-by-one error because there cannot be so many bits anyway. #[cfg(not(feature = "unchecked"))] return BitRange::new(value, from, (to - from).checked_add(1).unwrap_or(INT::MAX)); #[cfg(feature = "unchecked")] return BitRange::new(value, from, to - from + 1); } /// Return an iterator over a portion of bits in the number. /// /// * If `start` < 0, position counts from the MSB (Most Significant Bit)>. /// * If `len` ≤ 0, an empty iterator is returned. /// * If `start` position + `len` ≥ length of string, all bits of the number after the `start` position are iterated. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits(10, 8) { /// print(bit); /// } /// ``` #[rhai_fn(name = "bits", return_raw)] pub fn bits_from_start_and_len(value: INT, from: INT, len: INT) -> RhaiResultOf { BitRange::new(value, from, len) } /// Return an iterator over the bits in the number starting from the specified `start` position. /// /// If `start` < 0, position counts from the MSB (Most Significant Bit)>. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits(10) { /// print(bit); /// } /// ``` #[rhai_fn(name = "bits", return_raw)] pub fn bits_from_start(value: INT, from: INT) -> RhaiResultOf { BitRange::new(value, from, INT::MAX) } /// Return an iterator over all the bits in the number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits() { /// print(bit); /// } /// ``` #[rhai_fn(name = "bits", return_raw)] pub fn bits(value: INT) -> RhaiResultOf { BitRange::new(value, 0, INT::MAX) } /// Return an iterator over all the bits in the number. /// /// # Example /// /// ```rhai /// let x = 123456; /// /// for bit in x.bits { /// print(bit); /// } /// ``` #[cfg(not(feature = "no_object"))] #[rhai_fn(get = "bits", return_raw)] pub fn get_bits(value: INT) -> RhaiResultOf { BitRange::new(value, 0, INT::MAX) } } #[export_module] mod range_functions { /// Return the start of the exclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start(range: &mut ExclusiveRange) -> INT { range.start } /// Return the end of the exclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end(range: &mut ExclusiveRange) -> INT { range.end } /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive(range: &mut ExclusiveRange) -> bool { let _ = range; false } /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive(range: &mut ExclusiveRange) -> bool { let _ = range; true } /// Return true if the range contains no items. #[rhai_fn(get = "is_empty", name = "is_empty", pure)] #[allow(unstable_name_collisions)] pub fn is_empty_exclusive(range: &mut ExclusiveRange) -> bool { range.is_empty() } /// Return `true` if the range contains a specified value. #[rhai_fn(name = "contains")] pub fn contains_exclusive(range: &mut ExclusiveRange, value: INT) -> bool { range.contains(&value) } /// Return the start of the inclusive range. #[rhai_fn(get = "start", name = "start", pure)] pub fn start_inclusive(range: &mut InclusiveRange) -> INT { *range.start() } /// Return the end of the inclusive range. #[rhai_fn(get = "end", name = "end", pure)] pub fn end_inclusive(range: &mut InclusiveRange) -> INT { *range.end() } /// Return `true` if the range is inclusive. #[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)] pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool { let _ = range; true } /// Return `true` if the range is exclusive. #[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)] pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool { let _ = range; false } /// Return true if the range contains no items. #[rhai_fn(get = "is_empty", name = "is_empty", pure)] pub fn is_empty_inclusive(range: &mut InclusiveRange) -> bool { range.is_empty() } /// Return `true` if the range contains a specified value. #[rhai_fn(name = "contains")] pub fn contains_inclusive(range: &mut InclusiveRange, value: INT) -> bool { range.contains(&value) } } rhai-1.21.0/src/packages/lang_core.rs000064400000000000000000000230321046102023000154740ustar 00000000000000use crate::def_package; use crate::plugin::*; use crate::types::dynamic::Tag; use crate::{Dynamic, RhaiResult, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_std"))] use crate::FLOAT; def_package! { /// Package of core language features. pub LanguageCorePackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "core", core_functions); #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] combine_with_exported_module!(lib, "reflection", reflection_functions); } } #[export_module] mod core_functions { /// Exit the script evaluation immediately with a value. /// /// # Example /// ```rhai /// exit(42); /// ``` #[rhai_fn(name = "exit", volatile, return_raw)] pub fn exit_with_value(value: Dynamic) -> RhaiResult { Err(ERR::Exit(value, Position::NONE).into()) } /// Exit the script evaluation immediately with `()` as exit value. /// /// # Example /// ```rhai /// exit(); /// ``` #[rhai_fn(volatile, return_raw)] pub fn exit() -> RhaiResult { Err(ERR::Exit(Dynamic::UNIT, Position::NONE).into()) } /// Take ownership of the data in a `Dynamic` value and return it. /// The data is _NOT_ cloned. /// /// The original value is replaced with `()`. /// /// # Example /// /// ```rhai /// let x = 42; /// /// print(take(x)); // prints 42 /// /// print(x); // prints () /// ``` #[rhai_fn(return_raw)] pub fn take(value: &mut Dynamic) -> RhaiResult { if value.is_read_only() { return Err( ERR::ErrorNonPureMethodCallOnConstant("take".to_string(), Position::NONE).into(), ); } Ok(std::mem::take(value)) } /// Return the _tag_ of a `Dynamic` value. /// /// # Example /// /// ```rhai /// let x = "hello, world!"; /// /// x.tag = 42; /// /// print(x.tag); // prints 42 /// ``` #[rhai_fn(name = "tag", get = "tag", pure)] pub fn get_tag(value: &mut Dynamic) -> INT { value.tag() as INT } /// Set the _tag_ of a `Dynamic` value. /// /// # Example /// /// ```rhai /// let x = "hello, world!"; /// /// x.tag = 42; /// /// print(x.tag); // prints 42 /// ``` #[rhai_fn(name = "set_tag", set = "tag", return_raw)] pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> { const TAG_MIN: Tag = Tag::MIN; const TAG_MAX: Tag = Tag::MAX; if tag < TAG_MIN as INT { return Err(ERR::ErrorArithmetic( format!( "{tag} is too small to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})" ), Position::NONE, ) .into()); } if tag > TAG_MAX as INT { return Err(ERR::ErrorArithmetic( format!( "{tag} is too large to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})" ), Position::NONE, ) .into()); } value.set_tag(tag as Tag); Ok(()) } /// Block the current thread for a particular number of `seconds`. /// /// # Example /// /// ```rhai /// // Do nothing for 10 seconds! /// sleep(10.0); /// ``` #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_std"))] #[rhai_fn(name = "sleep", volatile)] pub fn sleep_float(seconds: FLOAT) { if !seconds.is_normal() || seconds.is_sign_negative() { return; } #[cfg(not(feature = "f32_float"))] std::thread::sleep(std::time::Duration::from_secs_f64(seconds)); #[cfg(feature = "f32_float")] std::thread::sleep(std::time::Duration::from_secs_f32(seconds)); } /// Block the current thread for a particular number of `seconds`. /// /// # Example /// /// ```rhai /// // Do nothing for 10 seconds! /// sleep(10); /// ``` #[cfg(not(feature = "no_std"))] #[rhai_fn(volatile)] pub fn sleep(seconds: INT) { if seconds <= 0 { return; } #[allow(clippy::cast_sign_loss)] std::thread::sleep(std::time::Duration::from_secs(seconds as u64)); } /// Parse a JSON string into a value. /// /// # Example /// /// ```rhai /// let m = parse_json(`{"a":1, "b":2, "c":3}`); /// /// print(m); // prints #{"a":1, "b":2, "c":3} /// ``` #[cfg(not(feature = "no_object"))] #[rhai_fn(return_raw)] pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf { #[cfg(feature = "metadata")] let out = serde_json::from_str(json).map_err(|err| err.to_string().into()); #[cfg(not(feature = "metadata"))] let out = _ctx .engine() .parse_json(json, true) .map(|map_object| Dynamic::from(map_object)); out } } #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] #[export_module] mod reflection_functions { use crate::module::FuncInfo; use crate::{Array, Map, ScriptFnMetadata}; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] fn collect( ctx: NativeCallContext, filter: impl Fn(FnNamespace, FnAccess, &str, usize, &ScriptFnMetadata) -> bool, ) -> Array { let engine = ctx.engine(); engine.collect_fn_metadata_impl( Some(&ctx), |FuncInfo { metadata, #[cfg(not(feature = "no_module"))] namespace, script, }| -> Option { let Some(ref func) = script else { return None }; if !filter( metadata.namespace, func.access, func.name, func.params.len(), func, ) { return None; } let mut map = Map::new(); #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { map.insert( "namespace".into(), engine.get_interned_string(namespace).into(), ); } map.insert("name".into(), engine.get_interned_string(func.name).into()); map.insert( "access".into(), engine .get_interned_string(match func.access { FnAccess::Public => "public", FnAccess::Private => "private", }) .into(), ); map.insert( "is_anonymous".into(), func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); if let Some(this_type) = func.this_type { map.insert("this_type".into(), this_type.into()); } map.insert( "params".into(), func.params .iter() .map(|&p| engine.get_interned_string(p).into()) .collect::() .into(), ); #[cfg(feature = "metadata")] if !func.comments.is_empty() { map.insert( "comments".into(), func.comments .iter() .map(|&s| engine.get_interned_string(s).into()) .collect::() .into(), ); } Some(Dynamic::from_map(map)) }, false, ) } /// Return an array of object maps containing metadata of all script-defined functions. #[rhai_fn(name = "get_fn_metadata_list", volatile)] pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array { collect(ctx, |_, _, _, _, _| true) } /// Return an array of object maps containing metadata of all script-defined functions /// matching the specified name. #[rhai_fn(name = "get_fn_metadata_list", volatile)] pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array { collect(ctx, |_, _, n, _, _| n == name) } /// Return an array of object maps containing metadata of all script-defined functions /// matching the specified name and arity (number of parameters). #[rhai_fn(name = "get_fn_metadata_list", volatile)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array { if !(0..=crate::MAX_USIZE_INT).contains(¶ms) { return Array::new(); } collect(ctx, |_, _, n, p, _| p == (params as usize) && n == name) } } rhai-1.21.0/src/packages/logic.rs000064400000000000000000000311731046102023000146450ustar 00000000000000use crate::def_package; use crate::plugin::*; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(any( not(feature = "no_float"), all(not(feature = "only_i32"), not(feature = "only_i64")) ))] macro_rules! gen_cmp_functions { ($mod_name:ident => $($arg_type:ident),+) => { $({ #[export_module] #[allow(clippy::missing_const_for_fn)] pub mod cmp_functions { #[rhai_fn(name = "<")] pub fn lt(x: $arg_type, y: $arg_type) -> bool { x < y } #[rhai_fn(name = "<=")] pub fn lte(x: $arg_type, y: $arg_type) -> bool { x <= y } #[rhai_fn(name = ">")] pub fn gt(x: $arg_type, y: $arg_type) -> bool { x > y } #[rhai_fn(name = ">=")] pub fn gte(x: $arg_type, y: $arg_type) -> bool { x >= y } #[rhai_fn(name = "==")] pub fn eq(x: $arg_type, y: $arg_type) -> bool { x == y } #[rhai_fn(name = "!=")] pub fn ne(x: $arg_type, y: $arg_type) -> bool { x != y } pub fn max(x: $arg_type, y: $arg_type) -> $arg_type { if x >= y { x } else { y } } pub fn min(x: $arg_type, y: $arg_type) -> $arg_type { if x <= y { x } else { y } } } combine_with_exported_module!($mod_name, concat!("logic_", stringify($arg_type)), cmp_functions); })* }; } def_package! { /// Package of basic logic operators. pub LogicPackage(lib) { lib.set_standard_lib(true); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { gen_cmp_functions!(lib => i8, u8, i16, u16, i32, u32, u64); #[cfg(not(target_family = "wasm"))] gen_cmp_functions!(lib => i128, u128); } #[cfg(not(feature = "no_float"))] { combine_with_exported_module!(lib, "float", float_functions); #[cfg(not(feature = "f32_float"))] { gen_cmp_functions!(lib => f32); combine_with_exported_module!(lib, "f32", f32_functions); } #[cfg(feature = "f32_float")] { gen_cmp_functions!(lib => f64); combine_with_exported_module!(lib, "f64", f64_functions); } } #[cfg(feature = "decimal")] combine_with_exported_module!(lib, "decimal", decimal_functions); combine_with_exported_module!(lib, "logic", logic_functions); combine_with_exported_module!(lib, "min_max", min_max_functions); } } #[export_module] mod logic_functions { #[rhai_fn(name = "!")] pub const fn not(x: bool) -> bool { !x } } #[export_module] mod min_max_functions { use crate::INT; /// Return the number that is larger than the other number. /// /// # Example /// /// ```rhai /// max(42, 123); // returns 132 /// ``` pub const fn max(x: INT, y: INT) -> INT { if x >= y { x } else { y } } /// Return the number that is smaller than the other number. /// /// # Example /// /// ```rhai /// min(42, 123); // returns 42 /// ``` pub const fn min(x: INT, y: INT) -> INT { if x <= y { x } else { y } } } #[cfg(not(feature = "no_float"))] #[allow(clippy::cast_precision_loss)] #[export_module] mod float_functions { use crate::INT; #[rhai_fn(name = "max")] pub fn max_ff_32(x: f32, y: f32) -> f32 { if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_if_32(x: INT, y: f32) -> f32 { let (x, y) = (x as f32, y); if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_fi_32(x: f32, y: INT) -> f32 { let (x, y) = (x, y as f32); if x >= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_ff_32(x: f32, y: f32) -> f32 { if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_if_32(x: INT, y: f32) -> f32 { let (x, y) = (x as f32, y); if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_fi_32(x: f32, y: INT) -> f32 { let (x, y) = (x, y as f32); if x <= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_ff_64(x: f64, y: f64) -> f64 { if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_if_64(x: INT, y: f64) -> f64 { let (x, y) = (x as f64, y); if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_fi_64(x: f64, y: INT) -> f64 { let (x, y) = (x, y as f64); if x >= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_ff_64(x: f64, y: f64) -> f64 { if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_if_64(x: INT, y: f64) -> f64 { let (x, y) = (x as f64, y); if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_fi_64(x: f64, y: INT) -> f64 { let (x, y) = (x, y as f64); if x <= y { x } else { y } } } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] #[allow(clippy::cast_precision_loss)] #[export_module] mod f32_functions { use crate::{FLOAT, INT}; #[cfg(feature = "no_std")] use num_traits::Float; #[rhai_fn(name = "==")] pub fn eq_if(x: INT, y: f32) -> bool { #[cfg(feature = "unchecked")] return (x as f32) == y; #[cfg(not(feature = "unchecked"))] return (x as f32 - y).abs() <= f32::EPSILON; } #[rhai_fn(name = "==")] pub fn eq_fi(x: f32, y: INT) -> bool { #[cfg(feature = "unchecked")] return x == (y as f32); #[cfg(not(feature = "unchecked"))] return (x - y as f32).abs() <= f32::EPSILON; } #[rhai_fn(name = "!=")] pub fn neq_if(x: INT, y: f32) -> bool { #[cfg(feature = "unchecked")] return (x as f32) != y; #[cfg(not(feature = "unchecked"))] return (x as f32 - y).abs() > f32::EPSILON; } #[rhai_fn(name = "!=")] pub fn neq_fi(x: f32, y: INT) -> bool { #[cfg(feature = "unchecked")] return x != (y as f32); #[cfg(not(feature = "unchecked"))] return (x - y as f32).abs() > f32::EPSILON; } #[rhai_fn(name = ">")] pub fn gt_if(x: INT, y: f32) -> bool { (x as f32) > y } #[rhai_fn(name = ">")] pub fn gt_fi(x: f32, y: INT) -> bool { x > (y as f32) } #[rhai_fn(name = ">=")] pub fn gte_if(x: INT, y: f32) -> bool { (x as f32) >= y } #[rhai_fn(name = ">=")] pub fn gte_fi(x: f32, y: INT) -> bool { x >= (y as f32) } #[rhai_fn(name = "<")] pub fn lt_if(x: INT, y: f32) -> bool { (x as f32) < y } #[rhai_fn(name = "<")] pub fn lt_fi(x: f32, y: INT) -> bool { x < (y as f32) } #[rhai_fn(name = "<=")] pub fn lte_if(x: INT, y: f32) -> bool { (x as f32) <= y } #[rhai_fn(name = "<=")] pub fn lte_fi(x: f32, y: INT) -> bool { x <= (y as f32) } #[rhai_fn(name = "max")] pub fn max_64_32(x: FLOAT, y: f32) -> FLOAT { let (x, y) = (x, y as FLOAT); if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_32_64(x: f32, y: FLOAT) -> FLOAT { let (x, y) = (x as FLOAT, y); if x >= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_64_32(x: FLOAT, y: f32) -> FLOAT { let (x, y) = (x, y as FLOAT); if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_32_64(x: f32, y: FLOAT) -> FLOAT { let (x, y) = (x as FLOAT, y); if x <= y { x } else { y } } } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] #[allow(clippy::cast_precision_loss)] #[export_module] mod f64_functions { use crate::{FLOAT, INT}; #[cfg(feature = "no_std")] use num_traits::Float; #[rhai_fn(name = "==")] pub fn eq_if(x: INT, y: f64) -> bool { #[cfg(feature = "unchecked")] return (x as f64) == y; #[cfg(not(feature = "unchecked"))] return (x as f64 - y).abs() <= f64::EPSILON; } #[rhai_fn(name = "==")] pub fn eq_fi(x: f64, y: INT) -> bool { #[cfg(feature = "unchecked")] return x == (y as f64); #[cfg(not(feature = "unchecked"))] return (x - y as f64).abs() <= f64::EPSILON; } #[rhai_fn(name = "!=")] pub fn neq_if(x: INT, y: f64) -> bool { #[cfg(feature = "unchecked")] return (x as f64) != y; #[cfg(not(feature = "unchecked"))] return (x as f64 - y).abs() > f64::EPSILON; } #[rhai_fn(name = "!=")] pub fn neq_fi(x: f64, y: INT) -> bool { #[cfg(feature = "unchecked")] return x != (y as f64); #[cfg(not(feature = "unchecked"))] return (x - y as f64).abs() > f64::EPSILON; } #[rhai_fn(name = ">")] pub fn gt_if(x: INT, y: f64) -> bool { (x as f64) > y } #[rhai_fn(name = ">")] pub fn gt_fi(x: f64, y: INT) -> bool { x > (y as f64) } #[rhai_fn(name = ">=")] pub fn gte_if(x: INT, y: f64) -> bool { (x as f64) >= y } #[rhai_fn(name = ">=")] pub fn gte_fi(x: f64, y: INT) -> bool { x >= (y as f64) } #[rhai_fn(name = "<")] pub fn lt_if(x: INT, y: f64) -> bool { (x as f64) < y } #[rhai_fn(name = "<")] pub fn lt_fi(x: f64, y: INT) -> bool { x < (y as f64) } #[rhai_fn(name = "<=")] pub fn lte_if(x: INT, y: f64) -> bool { (x as f64) <= y } #[rhai_fn(name = "<=")] pub fn lte_fi(x: f64, y: INT) -> bool { x <= (y as f64) } #[rhai_fn(name = "max")] pub fn max_32_64(x: FLOAT, y: f64) -> FLOAT { let (x, y) = (x, y as FLOAT); if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_64_32(x: f64, y: FLOAT) -> FLOAT { let (x, y) = (x as FLOAT, y); if x >= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_32_64(x: FLOAT, y: f64) -> FLOAT { let (x, y) = (x, y as FLOAT); if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_64_32(x: f64, y: FLOAT) -> FLOAT { let (x, y) = (x as FLOAT, y); if x <= y { x } else { y } } } #[cfg(feature = "decimal")] #[export_module] mod decimal_functions { use crate::INT; use rust_decimal::Decimal; #[rhai_fn(name = "max")] pub fn max_dd(x: Decimal, y: Decimal) -> Decimal { if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_id(x: INT, y: Decimal) -> Decimal { let x = x.into(); if x >= y { x } else { y } } #[rhai_fn(name = "max")] pub fn max_di(x: Decimal, y: INT) -> Decimal { let y = y.into(); if x >= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_dd(x: Decimal, y: Decimal) -> Decimal { if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_id(x: INT, y: Decimal) -> Decimal { let x = x.into(); if x <= y { x } else { y } } #[rhai_fn(name = "min")] pub fn min_di(x: Decimal, y: INT) -> Decimal { let y = y.into(); if x <= y { x } else { y } } } rhai-1.21.0/src/packages/map_basic.rs000064400000000000000000000331401046102023000154620ustar 00000000000000#![cfg(not(feature = "no_object"))] use crate::engine::OP_EQUALS; use crate::plugin::*; use crate::{ def_package, Dynamic, FnPtr, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_index"))] use crate::Array; def_package! { /// Package of basic object map utilities. pub BasicMapPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "map", map_functions); } } #[export_module] mod map_functions { /// Return the number of properties in the object map. #[rhai_fn(pure)] pub fn len(map: &mut Map) -> INT { map.len() as INT } /// Return true if the map is empty. #[rhai_fn(pure)] pub fn is_empty(map: &mut Map) -> bool { map.len() == 0 } /// Returns `true` if the object map contains a specified property. /// /// # Example /// /// ```rhai /// let m = #{a: 1, b: 2, c: 3}; /// /// print(m.contains("b")); // prints true /// /// print(m.contains("x")); // prints false /// ``` pub fn contains(map: &mut Map, property: &str) -> bool { map.contains_key(property) } /// Get the value of the `property` in the object map and return a copy. /// /// If `property` does not exist in the object map, `()` is returned. /// /// # Example /// /// ```rhai /// let m = #{a: 1, b: 2, c: 3}; /// /// print(m.get("b")); // prints 2 /// /// print(m.get("x")); // prints empty (for '()') /// ``` pub fn get(map: &mut Map, property: &str) -> Dynamic { if map.is_empty() { return Dynamic::UNIT; } map.get(property).cloned().unwrap_or(Dynamic::UNIT) } /// Set the value of the `property` in the object map to a new `value`. /// /// If `property` does not exist in the object map, it is added. /// /// # Example /// /// ```rhai /// let m = #{a: 1, b: 2, c: 3}; /// /// m.set("b", 42)' /// /// print(m); // prints "#{a: 1, b: 42, c: 3}" /// /// x.set("x", 0); /// /// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}" /// ``` pub fn set(map: &mut Map, property: &str, value: Dynamic) { match map.get_mut(property) { Some(value_ref) => *value_ref = value, None => { map.insert(property.into(), value); } } } /// Clear the object map. pub fn clear(map: &mut Map) { if map.is_empty() { return; } map.clear(); } /// Remove any property of the specified `name` from the object map, returning its value. /// /// If the property does not exist, `()` is returned. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// /// let x = m.remove("b"); /// /// print(x); // prints 2 /// /// print(m); // prints "#{a:1, c:3}" /// ``` pub fn remove(map: &mut Map, property: &str) -> Dynamic { if map.is_empty() { return Dynamic::UNIT; } map.remove(property).unwrap_or(Dynamic::UNIT) } /// Add all property values of another object map into the object map. /// Existing property values of the same names are replaced. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// let n = #{a: 42, d:0}; /// /// m.mixin(n); /// /// print(m); // prints "#{a:42, b:2, c:3, d:0}" /// ``` #[rhai_fn(name = "mixin", name = "+=")] pub fn mixin(map: &mut Map, map2: Map) { if map2.is_empty() { return; } map.extend(map2); } /// Make a copy of the object map, add all property values of another object map /// (existing property values of the same names are replaced), then returning it. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// let n = #{a: 42, d:0}; /// /// print(m + n); // prints "#{a:42, b:2, c:3, d:0}" /// /// print(m); // prints "#{a:1, b:2, c:3}" /// ``` #[rhai_fn(name = "+")] pub fn merge(map1: Map, map2: Map) -> Map { if map2.is_empty() { return map1; } if map1.is_empty() { return map2; } let mut map1 = map1; map1.extend(map2); map1 } /// Add all property values of another object map into the object map. /// Only properties that do not originally exist in the object map are added. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// let n = #{a: 42, d:0}; /// /// m.fill_with(n); /// /// print(m); // prints "#{a:1, b:2, c:3, d:0}" /// ``` pub fn fill_with(map: &mut Map, map2: Map) { if map2.is_empty() { return; } if map.is_empty() { *map = map2; return; } for (key, value) in map2 { map.entry(key).or_insert(value); } } /// Return `true` if two object maps are equal (i.e. all property values are equal). /// /// The operator `==` is used to compare property values and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let m1 = #{a:1, b:2, c:3}; /// let m2 = #{a:1, b:2, c:3}; /// let m3 = #{a:1, c:3}; /// /// print(m1 == m2); // prints true /// /// print(m1 == m3); // prints false /// ``` #[rhai_fn(name = "==", return_raw, pure)] pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { if map1.len() != map2.len() { return Ok(false); } if !map1.is_empty() { let mut map2 = map2; for (m1, v1) in map1 { match map2.get_mut(m1) { Some(v2) => { let equals = ctx .call_native_fn_raw(OP_EQUALS, true, &mut [v1, v2])? .as_bool() .unwrap_or(false); if !equals { return Ok(false); } } _ => return Ok(false), } } } Ok(true) } /// Return `true` if two object maps are not equal (i.e. at least one property value is not equal). /// /// The operator `==` is used to compare property values and must be defined, /// otherwise `false` is assumed. /// /// # Example /// /// ```rhai /// let m1 = #{a:1, b:2, c:3}; /// let m2 = #{a:1, b:2, c:3}; /// let m3 = #{a:1, c:3}; /// /// print(m1 != m2); // prints false /// /// print(m1 != m3); // prints true /// ``` #[rhai_fn(name = "!=", return_raw, pure)] pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf { equals(ctx, map1, map2).map(|r| !r) } /// Return an array with all the property names in the object map. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// /// print(m.keys()); // prints ["a", "b", "c"] /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn keys(map: &mut Map) -> Array { if map.is_empty() { return Array::new(); } map.keys().cloned().map(Into::into).collect() } /// Return an array with all the property values in the object map. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// /// print(m.values()); // prints "[1, 2, 3]"" /// ``` #[cfg(not(feature = "no_index"))] #[rhai_fn(pure)] pub fn values(map: &mut Map) -> Array { if map.is_empty() { return Array::new(); } map.values().cloned().collect() } /// Iterate through all the elements in the object map, applying a `filter` function to each /// and return a new collection of all elements that return `true` as a new object map. /// /// # Function Parameters /// /// * `key`: current key /// * `value` _(optional)_: copy of element (bound to `this` if omitted) /// /// # Example /// /// ```rhai /// let x = #{a:1, b:2, c:3, d:4, e:5}; /// /// let y = x.filter(|k| this >= 3); /// /// print(y); // prints #{"c":3, "d":4, "e":5} /// /// let y = x.filter(|k, v| k != "d" && v < 5); /// /// print(y); // prints #{"a":1, "b":2, "c":3} /// ``` #[rhai_fn(return_raw)] pub fn filter(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf { if map.is_empty() { return Ok(Map::new()); } let mut result = Map::new(); for (key, item) in map.iter_mut() { if filter .call_raw_with_extra_args("filter", &ctx, Some(item), [key.into()], [], Some(1))? .as_bool() .unwrap_or(false) { result.insert(key.clone(), item.clone()); } } Ok(result) } /// Remove all elements in the object map that return `true` when applied the `filter` function and /// return them as a new object map. /// /// # Function Parameters /// /// * `key`: current key /// * `value` _(optional)_: copy of element (bound to `this` if omitted) /// /// # Example /// /// ```rhai /// let x = #{a:1, b:2, c:3, d:4, e:5}; /// /// let y = x.drain(|k| this < 3); /// /// print(x); // prints #{"c":3, "d":4, "e":5] /// /// print(y); // prints #{"a":1, "b"2} /// /// let z = x.drain(|k, v| k == "c" || v >= 5); /// /// print(x); // prints #{"d":4} /// /// print(z); // prints #{"c":3, "e":5} /// ``` #[rhai_fn(return_raw)] pub fn drain(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf { if map.is_empty() { return Ok(Map::new()); } let mut drained = Map::new(); let mut retained = Map::new(); for (key, mut value) in mem::take(map).into_iter() { if filter .call_raw_with_extra_args( "drain", &ctx, Some(&mut value), [key.clone().into()], [], Some(1), )? .as_bool() .unwrap_or(false) { drained.insert(key, value); } else { retained.insert(key, value); } } *map = retained; Ok(drained) } /// Remove all elements in the object map that do not return `true` when applied the `filter` function and /// return them as a new object map. /// /// # Function Parameters /// /// * `key`: current key /// * `value` _(optional)_: copy of element (bound to `this` if omitted) /// /// # Example /// /// ```rhai /// let x = #{a:1, b:2, c:3, d:4, e:5}; /// /// let y = x.retain(|k| this < 3); /// /// print(x); // prints #{"a":1, "b"2} /// /// print(y); // prints #{"c":3, "d":4, "e":5] /// /// let z = y.retain(|k, v| k == "c" || v >= 5); /// /// print(y); // prints #{"c":3, "e":5} /// /// print(z); // prints #{"d":4} /// ``` #[rhai_fn(return_raw)] pub fn retain(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf { if map.is_empty() { return Ok(Map::new()); } let mut drained = Map::new(); let mut retained = Map::new(); for (key, mut value) in mem::take(map).into_iter() { if filter .call_raw_with_extra_args( "retain", &ctx, Some(&mut value), [key.clone().into()], [], Some(1), )? .as_bool() .unwrap_or(false) { retained.insert(key, value); } else { drained.insert(key, value); } } *map = retained; Ok(drained) } /// Return the JSON representation of the object map. /// /// # Data types /// /// Only the following data types should be kept inside the object map: /// `INT`, `FLOAT`, `ImmutableString`, `char`, `bool`, `()`, `Array`, `Map`. /// /// # Errors /// /// Data types not supported by JSON serialize into formats that may /// invalidate the result. /// /// # Example /// /// ```rhai /// let m = #{a:1, b:2, c:3}; /// /// print(m.to_json()); // prints {"a":1, "b":2, "c":3} /// ``` pub fn to_json(map: &mut Map) -> String { #[cfg(feature = "metadata")] return serde_json::to_string(map).unwrap_or_else(|_| "ERROR".into()); #[cfg(not(feature = "metadata"))] return crate::format_map_as_json(map); } } rhai-1.21.0/src/packages/math_basic.rs000064400000000000000000000525301046102023000156420ustar 00000000000000#![allow(non_snake_case)] use crate::plugin::*; use crate::{def_package, FuncRegistration, Position, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] use crate::FLOAT; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] use num_traits::Float; macro_rules! gen_conv_functions { ($mod_name:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { $( FuncRegistration::new(stringify!($func_name)).set_into_module($mod_name, |x: $arg_type| x as $result_type); )* }; ($mod_name:ident => $func_name:ident ( $($arg_type:ident),+ ) .into() -> $result_type:ty) => { $( FuncRegistration::new(stringify!($func_name)).set_into_module($mod_name, |x: $arg_type| -> $result_type { x.into() }); )* }; } def_package! { /// Basic mathematical package. pub BasicMathPackage(lib) { lib.set_standard_lib(true); // Integer functions combine_with_exported_module!(lib, "int", int_functions); // Conversion functions gen_conv_functions!(lib => to_int(INT, char) -> INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { gen_conv_functions!(lib => to_int(i8, u8, i16, u16, i32, u32, i64, u64) -> INT); #[cfg(not(target_family = "wasm"))] gen_conv_functions!(lib => to_int(i128, u128) -> INT); } #[cfg(not(feature = "no_float"))] { // Floating point functions combine_with_exported_module!(lib, "float", float_functions); // Trig functions combine_with_exported_module!(lib, "trig", trig_functions); gen_conv_functions!(lib => to_float(FLOAT, INT) -> FLOAT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { gen_conv_functions!(lib => to_float(i8, u8, i16, u16, i32, u32, i64, u64) -> FLOAT); #[cfg(not(target_family = "wasm"))] gen_conv_functions!(lib => to_float(i128, u128) -> FLOAT); } } // Decimal functions #[cfg(feature = "decimal")] { use rust_decimal::Decimal; combine_with_exported_module!(lib, "decimal", decimal_functions); gen_conv_functions!(lib => to_decimal(Decimal) -> Decimal); gen_conv_functions!(lib => to_decimal(INT).into() -> Decimal); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] gen_conv_functions!(lib => to_decimal(i8, u8, i16, u16, i32, u32, i64, u64).into() -> Decimal); } } } #[export_module] mod int_functions { /// Parse a string into an integer number. /// /// # Example /// /// ```rhai /// let x = parse_int("123"); /// /// print(x); // prints 123 /// ``` #[rhai_fn(name = "parse_int", return_raw)] pub fn parse_int(string: &str) -> RhaiResultOf { parse_int_radix(string, 10) } /// Parse a string into an integer number of the specified `radix`. /// /// `radix` must be between 2 and 36. /// /// # Example /// /// ```rhai /// let x = parse_int("123"); /// /// print(x); // prints 123 /// /// let y = parse_int("123abc", 16); /// /// print(y); // prints 1194684 (0x123abc) /// ``` #[rhai_fn(name = "parse_int", return_raw)] pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf { if !(2..=36).contains(&radix) { return Err( ERR::ErrorArithmetic(format!("Invalid radix: '{radix}'"), Position::NONE).into(), ); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { ERR::ErrorArithmetic( format!("Error parsing integer number '{string}': {err}"), Position::NONE, ) .into() }) } } #[cfg(not(feature = "no_float"))] #[export_module] mod trig_functions { /// Return the sine of the floating-point number in radians. pub fn sin(x: FLOAT) -> FLOAT { x.sin() } /// Return the cosine of the floating-point number in radians. pub fn cos(x: FLOAT) -> FLOAT { x.cos() } /// Return the tangent of the floating-point number in radians. pub fn tan(x: FLOAT) -> FLOAT { x.tan() } /// Return the hyperbolic sine of the floating-point number in radians. pub fn sinh(x: FLOAT) -> FLOAT { x.sinh() } /// Return the hyperbolic cosine of the floating-point number in radians. pub fn cosh(x: FLOAT) -> FLOAT { x.cosh() } /// Return the hyperbolic tangent of the floating-point number in radians. pub fn tanh(x: FLOAT) -> FLOAT { x.tanh() } /// Return the arc-sine of the floating-point number, in radians. pub fn asin(x: FLOAT) -> FLOAT { x.asin() } /// Return the arc-cosine of the floating-point number, in radians. pub fn acos(x: FLOAT) -> FLOAT { x.acos() } /// Return the arc-tangent of the floating-point number, in radians. pub fn atan(x: FLOAT) -> FLOAT { x.atan() } /// Return the arc-tangent of the floating-point numbers `x` and `y`, in radians. #[rhai_fn(name = "atan")] pub fn atan2(x: FLOAT, y: FLOAT) -> FLOAT { x.atan2(y) } /// Return the arc-hyperbolic-sine of the floating-point number, in radians. pub fn asinh(x: FLOAT) -> FLOAT { x.asinh() } /// Return the arc-hyperbolic-cosine of the floating-point number, in radians. pub fn acosh(x: FLOAT) -> FLOAT { x.acosh() } /// Return the arc-hyperbolic-tangent of the floating-point number, in radians. pub fn atanh(x: FLOAT) -> FLOAT { x.atanh() } /// Return the hypotenuse of a triangle with sides `x` and `y`. pub fn hypot(x: FLOAT, y: FLOAT) -> FLOAT { x.hypot(y) } } #[cfg(not(feature = "no_float"))] #[export_module] mod float_functions { /// Return the natural number _e_. #[rhai_fn(name = "E")] pub const fn e() -> FLOAT { #[cfg(not(feature = "f32_float"))] return std::f64::consts::E; #[cfg(feature = "f32_float")] return std::f32::consts::E; } /// Return the number π. #[rhai_fn(name = "PI")] pub const fn pi() -> FLOAT { #[cfg(not(feature = "f32_float"))] return std::f64::consts::PI; #[cfg(feature = "f32_float")] return std::f32::consts::PI; } /// Convert degrees to radians. pub fn to_radians(x: FLOAT) -> FLOAT { x.to_radians() } /// Convert radians to degrees. pub fn to_degrees(x: FLOAT) -> FLOAT { x.to_degrees() } /// Return the square root of the floating-point number. pub fn sqrt(x: FLOAT) -> FLOAT { x.sqrt() } /// Return the exponential of the floating-point number. pub fn exp(x: FLOAT) -> FLOAT { x.exp() } /// Return the natural log of the floating-point number. pub fn ln(x: FLOAT) -> FLOAT { x.ln() } /// Return the log of the floating-point number with `base`. pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { x.log(base) } /// Return the log of the floating-point number with base 10. #[rhai_fn(name = "log")] pub fn log10(x: FLOAT) -> FLOAT { x.log10() } /// Return the largest whole number less than or equals to the floating-point number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } /// Return the smallest whole number larger than or equals to the floating-point number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } /// Return the nearest whole number closest to the floating-point number. /// Rounds away from zero. #[rhai_fn(name = "round", get = "round")] pub fn round(x: FLOAT) -> FLOAT { x.round() } /// Return the integral part of the floating-point number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } /// Return the fractional part of the floating-point number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } /// Return `true` if the floating-point number is `NaN` (Not A Number). #[rhai_fn(name = "is_nan", get = "is_nan")] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } /// Return `true` if the floating-point number is finite. #[rhai_fn(name = "is_finite", get = "is_finite")] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } /// Return `true` if the floating-point number is infinite. #[rhai_fn(name = "is_infinite", get = "is_infinite")] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f32_to_int(x: f32) -> RhaiResultOf { #[allow(clippy::cast_precision_loss)] if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) { return Err(ERR::ErrorArithmetic( format!("Integer overflow: to_int({x})"), Position::NONE, ) .into()); } Ok(x.trunc() as INT) } /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f64_to_int(x: f64) -> RhaiResultOf { #[allow(clippy::cast_precision_loss)] if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) { return Err(ERR::ErrorArithmetic( format!("Integer overflow: to_int({x})"), Position::NONE, ) .into()); } Ok(x.trunc() as INT) } /// Parse a string into a floating-point number. /// /// # Example /// /// ```rhai /// let x = parse_int("123.456"); /// /// print(x); // prints 123.456 /// ``` #[rhai_fn(return_raw)] pub fn parse_float(string: &str) -> RhaiResultOf { string.trim().parse::().map_err(|err| { ERR::ErrorArithmetic( format!("Error parsing floating-point number '{string}': {err}"), Position::NONE, ) .into() }) } /// Convert the 32-bit floating-point number to 64-bit. #[cfg(not(feature = "f32_float"))] #[rhai_fn(name = "to_float")] pub fn f32_to_f64(x: f32) -> f64 { x.into() } } #[cfg(feature = "decimal")] #[export_module] mod decimal_functions { use super::super::arithmetic::make_err; use num_traits::ToPrimitive; use rust_decimal::{ prelude::{FromStr, RoundingStrategy}, Decimal, MathematicalOps, }; #[cfg(not(feature = "no_float"))] use std::convert::TryFrom; /// Return the natural number _e_. #[cfg(feature = "no_float")] #[rhai_fn(name = "PI")] pub const fn pi() -> Decimal { Decimal::PI } /// Return the number π. #[cfg(feature = "no_float")] #[rhai_fn(name = "E")] pub const fn e() -> Decimal { Decimal::E } /// Parse a string into a decimal number. /// /// # Example /// /// ```rhai /// let x = parse_float("123.456"); /// /// print(x); // prints 123.456 /// ``` #[cfg(feature = "no_float")] #[rhai_fn(return_raw)] pub fn parse_float(s: &str) -> RhaiResultOf { parse_decimal(s) } /// Return the sine of the decimal number in radians. pub fn sin(x: Decimal) -> Decimal { x.sin() } /// Return the cosine of the decimal number in radians. pub fn cos(x: Decimal) -> Decimal { x.cos() } /// Return the tangent of the decimal number in radians. pub fn tan(x: Decimal) -> Decimal { x.tan() } /// Return the square root of the decimal number. #[rhai_fn(return_raw)] pub fn sqrt(x: Decimal) -> RhaiResultOf { x.sqrt() .ok_or_else(|| make_err(format!("Error taking the square root of {x}"))) } /// Return the exponential of the decimal number. #[rhai_fn(return_raw)] pub fn exp(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_exp() .ok_or_else(|| make_err(format!("Exponential overflow: e ** {x}"))) } else { Ok(x.exp()) } } /// Return the natural log of the decimal number. #[rhai_fn(return_raw)] pub fn ln(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_ln() .ok_or_else(|| make_err(format!("Error taking the natural log of {x}"))) } else { Ok(x.ln()) } } /// Return the log of the decimal number with base 10. #[rhai_fn(name = "log", return_raw)] pub fn log10(x: Decimal) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { x.checked_log10() .ok_or_else(|| make_err(format!("Error taking the log of {x}"))) } else { Ok(x.log10()) } } /// Return the largest whole number less than or equals to the decimal number. #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: Decimal) -> Decimal { x.floor() } /// Return the smallest whole number larger than or equals to the decimal number. #[rhai_fn(name = "ceiling", get = "ceiling")] pub fn ceiling(x: Decimal) -> Decimal { x.ceil() } /// Return the nearest whole number closest to the decimal number. /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", get = "round")] pub fn round(x: Decimal) -> Decimal { x.round() } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. /// Always round mid-point towards the closest even number. #[rhai_fn(name = "round", return_raw)] pub fn round_dp(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp(digits as u32)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. /// Always round away from zero. #[rhai_fn(return_raw)] pub fn round_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::AwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. /// Always round towards zero. #[rhai_fn(return_raw)] pub fn round_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::ToZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. /// Always round mid-points away from zero. #[rhai_fn(return_raw)] pub fn round_half_up(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointAwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. /// Always round mid-points towards zero. #[rhai_fn(return_raw)] pub fn round_half_down(x: Decimal, digits: INT) -> RhaiResultOf { if cfg!(not(feature = "unchecked")) { if digits < 0 { return Err(make_err(format!( "Invalid number of digits for rounding: {digits}" ))); } if cfg!(not(feature = "only_i32")) && digits > (u32::MAX as INT) { return Ok(x); } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } /// Convert the decimal number into an integer. #[rhai_fn(return_raw)] pub fn to_int(x: Decimal) -> RhaiResultOf { #[allow(clippy::bind_instead_of_map)] x.to_i64() .and_then(|n| { #[cfg(feature = "only_i32")] return if n > (INT::MAX as i64) || n < (INT::MIN as i64) { None } else { Some(n as i32) }; #[cfg(not(feature = "only_i32"))] return Some(n); }) .map_or_else( || { Err(ERR::ErrorArithmetic( format!("Integer overflow: to_int({x})"), Position::NONE, ) .into()) }, Ok, ) } /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: Decimal) -> Decimal { x.trunc() } /// Return the fractional part of the decimal number. #[rhai_fn(name = "fraction", get = "fraction")] pub fn fraction(x: Decimal) -> Decimal { x.fract() } /// Parse a string into a decimal number. /// /// # Example /// /// ```rhai /// let x = parse_decimal("123.456"); /// /// print(x); // prints 123.456 /// ``` #[rhai_fn(return_raw)] pub fn parse_decimal(string: &str) -> RhaiResultOf { Decimal::from_str(string) .or_else(|_| Decimal::from_scientific(string)) .map_err(|err| { ERR::ErrorArithmetic( format!("Error parsing decimal number '{string}': {err}"), Position::NONE, ) .into() }) } /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f32_to_decimal(x: f32) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { ERR::ErrorArithmetic( format!("Cannot convert to Decimal: to_decimal({x})"), Position::NONE, ) .into() }) } /// Convert the floating-point number to decimal. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "to_decimal", return_raw)] pub fn f64_to_decimal(x: f64) -> RhaiResultOf { Decimal::try_from(x).map_err(|_| { ERR::ErrorArithmetic( format!("Cannot convert to Decimal: to_decimal({x})"), Position::NONE, ) .into() }) } /// Convert the decimal number to floating-point. #[cfg(not(feature = "no_float"))] #[rhai_fn(return_raw)] pub fn to_float(x: Decimal) -> RhaiResultOf { FLOAT::try_from(x).map_err(|_| { ERR::ErrorArithmetic( format!("Cannot convert to floating-point: to_float({x})"), Position::NONE, ) .into() }) } } rhai-1.21.0/src/packages/mod.rs000064400000000000000000000202251046102023000143230ustar 00000000000000//! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages. use crate::{Engine, Module, SharedModule}; pub(crate) mod arithmetic; pub(crate) mod array_basic; pub(crate) mod bit_field; pub(crate) mod blob_basic; pub(crate) mod debugging; pub(crate) mod fn_basic; pub(crate) mod iter_basic; pub(crate) mod lang_core; pub(crate) mod logic; pub(crate) mod map_basic; pub(crate) mod math_basic; pub(crate) mod pkg_core; pub(crate) mod pkg_std; pub(crate) mod string_basic; pub(crate) mod string_more; pub(crate) mod time_basic; pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] pub use array_basic::BasicArrayPackage; pub use bit_field::BitFieldPackage; #[cfg(not(feature = "no_index"))] pub use blob_basic::BasicBlobPackage; #[cfg(feature = "debugging")] pub use debugging::DebuggingPackage; pub use fn_basic::BasicFnPackage; pub use iter_basic::BasicIteratorPackage; pub use lang_core::LanguageCorePackage; pub use logic::LogicPackage; #[cfg(not(feature = "no_object"))] pub use map_basic::BasicMapPackage; pub use math_basic::BasicMathPackage; pub use pkg_core::CorePackage; pub use pkg_std::StandardPackage; pub use string_basic::BasicStringPackage; pub use string_more::MoreStringPackage; #[cfg(not(feature = "no_time"))] pub use time_basic::BasicTimePackage; /// Trait that all packages must implement. pub trait Package { /// Initialize the package. /// Functions should be registered into `module` here. #[cold] fn init(module: &mut Module); /// Initialize the package with an [`Engine`]. /// /// Perform tasks such as registering custom operators/syntax. #[cold] #[inline] #[allow(unused_variables)] fn init_engine(engine: &mut Engine) {} /// Register the package with an [`Engine`]. /// /// # Example /// /// ```rust /// # use rhai::Engine; /// # use rhai::packages::{Package, CorePackage}; /// let mut engine = Engine::new_raw(); /// let package = CorePackage::new(); /// /// package.register_into_engine(&mut engine); /// ``` #[cold] #[inline] fn register_into_engine(&self, engine: &mut Engine) -> &Self { Self::init_engine(engine); engine.register_global_module(self.as_shared_module()); self } /// Register the package with an [`Engine`] under a static namespace. /// /// # Example /// /// ```rust /// # use rhai::Engine; /// # use rhai::packages::{Package, CorePackage}; /// let mut engine = Engine::new_raw(); /// let package = CorePackage::new(); /// /// package.register_into_engine_as(&mut engine, "core"); /// ``` #[cfg(not(feature = "no_module"))] #[cold] #[inline] fn register_into_engine_as(&self, engine: &mut Engine, name: &str) -> &Self { Self::init_engine(engine); engine.register_static_module(name, self.as_shared_module()); self } /// Get a reference to a shared module from this package. #[must_use] fn as_shared_module(&self) -> SharedModule; } /// Macro that makes it easy to define a _package_ (which is basically a shared [module][Module]) /// and register functions into it. /// /// Functions can be added to the package using [`Module::set_native_fn`]. /// /// # Example /// /// Define a package named `MyPackage` with a single function named `my_add`: /// /// ``` /// use rhai::{Dynamic, EvalAltResult}; /// use rhai::def_package; /// /// fn add(x: i64, y: i64) -> Result> { Ok(x + y) } /// /// def_package! { /// /// My super-duper package. /// pub MyPackage(module) { /// // Load a native Rust function. /// module.set_native_fn("my_add", add); /// } /// } /// ``` #[macro_export] macro_rules! def_package { ($($(#[$outer:meta])* $mod:vis $package:ident($lib:ident) $( : $($(#[$base_meta:meta])* $base_pkg:ty),+ )? $block:block $( |> | $engine:ident | $init_engine:block )? )+) => { $( $(#[$outer])* $mod struct $package($crate::Shared<$crate::Module>); impl $crate::packages::Package for $package { #[inline(always)] fn as_shared_module(&self) -> $crate::Shared<$crate::Module> { self.0.clone() } fn init($lib: &mut $crate::Module) { $($( $(#[$base_meta])* { <$base_pkg>::init($lib); } )*)* $block } fn init_engine(_engine: &mut $crate::Engine) { $($( $(#[$base_meta])* { <$base_pkg>::init_engine(_engine); } )*)* $( let $engine = _engine; $init_engine )* } } impl Default for $package { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl $package { #[doc=concat!("Create a new `", stringify!($package), "`")] #[inline] #[must_use] pub fn new() -> Self { let mut module = $crate::Module::new(); ::init(&mut module); module.build_index(); Self(module.into()) } } )* }; ($($(#[$outer:meta])* $root:ident :: $package:ident => | $lib:ident | $block:block)+) => { $( $(#[$outer])* /// # Deprecated /// /// This old syntax of `def_package!` is deprecated. Use the new syntax instead. /// /// This syntax will be removed in the next major version. #[deprecated(since = "1.5.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")] pub struct $package($root::Shared<$root::Module>); impl $root::packages::Package for $package { fn as_shared_module(&self) -> $root::Shared<$root::Module> { self.0.clone() } fn init($lib: &mut $root::Module) { $block } } impl Default for $package { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl $package { #[inline] #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); module.build_index(); Self(module.into()) } } )* }; ($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => { #[doc=$comment] /// /// # Deprecated /// /// This old syntax of `def_package!` is deprecated. Use the new syntax instead. /// /// This syntax will be removed in the next major version. #[deprecated(since = "1.4.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")] pub struct $package($root::Shared<$root::Module>); impl $root::packages::Package for $package { fn as_shared_module(&self) -> $root::Shared<$root::Module> { self.0.clone() } fn init($lib: &mut $root::Module) { $block } } impl Default for $package { #[inline(always)] #[must_use] fn default() -> Self { Self::new() } } impl $package { #[inline] #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); module.build_index(); Self(module.into()) } } }; } rhai-1.21.0/src/packages/pkg_core.rs000064400000000000000000000015461046102023000153420ustar 00000000000000#[cfg(feature = "no_std")] use std::prelude::v1::*; use super::*; use crate::def_package; def_package! { /// Core package containing basic facilities. /// /// # Contents /// /// * [`LanguageCorePackage`][super::LanguageCorePackage] /// * [`ArithmeticPackage`][super::ArithmeticPackage] /// * [`BasicStringPackage`][super::BasicStringPackage] /// * [`BasicIteratorPackage`][super::BasicIteratorPackage] /// * [`BasicFnPackage`][super::BasicFnPackage] /// * [`DebuggingPackage`][super::DebuggingPackage] pub CorePackage(lib) : LanguageCorePackage, ArithmeticPackage, BasicStringPackage, BasicIteratorPackage, BasicFnPackage, #[cfg(feature = "debugging")] DebuggingPackage { lib.set_standard_lib(true); } } rhai-1.21.0/src/packages/pkg_std.rs000064400000000000000000000022561046102023000152030ustar 00000000000000#[cfg(feature = "no_std")] use std::prelude::v1::*; use super::*; use crate::def_package; def_package! { /// Standard package containing all built-in features. /// /// # Contents /// /// * [`CorePackage`][super::CorePackage] /// * [`BitFieldPackage`][super::BitFieldPackage] /// * [`LogicPackage`][super::LogicPackage] /// * [`BasicMathPackage`][super::BasicMathPackage] /// * [`BasicArrayPackage`][super::BasicArrayPackage] /// * [`BasicBlobPackage`][super::BasicBlobPackage] /// * [`BasicMapPackage`][super::BasicMapPackage] /// * [`BasicTimePackage`][super::BasicTimePackage] /// * [`MoreStringPackage`][super::MoreStringPackage] pub StandardPackage(lib) : CorePackage, BitFieldPackage, LogicPackage, BasicMathPackage, #[cfg(not(feature = "no_index"))] BasicArrayPackage, #[cfg(not(feature = "no_index"))] BasicBlobPackage, #[cfg(not(feature = "no_object"))] BasicMapPackage, #[cfg(not(feature = "no_time"))] BasicTimePackage, MoreStringPackage { lib.set_standard_lib(true); } } rhai-1.21.0/src/packages/string_basic.rs000064400000000000000000000405331046102023000162170ustar 00000000000000use super::iter_basic::CharsStream; use crate::plugin::*; use crate::{def_package, FnPtr, ImmutableString, SmartString, INT, MAX_USIZE_INT}; use std::any::TypeId; use std::fmt::{Binary, LowerHex, Octal, Write}; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_index"))] use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; /// Standard pretty-print function. /// /// This function is called to convert any type into text format for display. pub const FUNC_TO_STRING: &str = "to_string"; /// Standard debug-print function. /// /// This function is called to convert any type into text format suitable for debugging. pub const FUNC_TO_DEBUG: &str = "to_debug"; def_package! { /// Package of basic string utilities (e.g. printing) pub BasicStringPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "print_debug", print_debug_functions); combine_with_exported_module!(lib, "number_formatting", number_formatting); combine_with_exported_module!(lib, "char", char_functions); // Register characters iterator lib.set_iterator::(); lib.set_iter(TypeId::of::(), |value| Box::new( CharsStream::new(value.cast::().as_str(), 0, MAX_USIZE_INT).map(Into::into) )); } } /// Print a value using a named function. #[inline] pub fn print_with_func( fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic, ) -> ImmutableString { match ctx.call_native_fn_raw(fn_name, true, &mut [value]) { Ok(result) if result.is_string() => result.into_immutable_string().unwrap(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Err(_) => { let mut buf = SmartString::new_const(); match fn_name { FUNC_TO_DEBUG => write!(&mut buf, "{value:?}").unwrap(), _ => write!(&mut buf, "{value}").unwrap(), } ctx.engine().map_type_name(&buf).into() } } } #[export_module] mod print_debug_functions { /// Convert the value of the `item` into a string. #[rhai_fn(name = "print", pure)] pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_STRING, &ctx, item) } /// Convert the value of the `item` into a string. #[rhai_fn(name = "to_string", pure)] pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{item}").unwrap(); ctx.engine().map_type_name(&buf).into() } /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "debug", pure)] pub fn debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { print_with_func(FUNC_TO_DEBUG, &ctx, item) } /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "to_debug", pure)] pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{item:?}").unwrap(); ctx.engine().map_type_name(&buf).into() } /// Return the empty string. #[rhai_fn(name = "print", name = "debug")] pub fn print_empty_string(ctx: NativeCallContext) -> ImmutableString { ctx.engine().const_empty_string() } /// Return the `string`. #[rhai_fn(name = "print", name = "to_string")] pub const fn print_string(string: ImmutableString) -> ImmutableString { string } /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_string(string: &str) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{string:?}").unwrap(); buf.into() } /// Return the character into a string. #[rhai_fn(name = "print", name = "to_string")] pub fn print_char(character: char) -> ImmutableString { let mut buf = SmartString::new_const(); buf.push(character); buf.into() } /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_char(character: char) -> ImmutableString { let mut buf = SmartString::new_const(); buf.push(character); buf.into() } /// Convert the function pointer into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{f}").unwrap(); buf.into() } /// Return the boolean value into a string. #[rhai_fn(name = "print", name = "to_string")] pub fn print_bool(value: bool) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{value}").unwrap(); buf.into() } /// Convert the boolean value into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_bool(value: bool) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{value:?}").unwrap(); buf.into() } /// Return the empty string. #[allow(unused_variables)] #[rhai_fn(name = "print", name = "to_string")] pub fn print_unit(ctx: NativeCallContext, unit: ()) -> ImmutableString { ctx.engine().const_empty_string() } /// Convert the unit into a string in debug format. #[allow(unused_variables)] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_unit(unit: ()) -> ImmutableString { "()".into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } /// Convert the array into a string. #[cfg(not(feature = "no_index"))] #[rhai_fn( name = "print", name = "to_string", name = "debug", name = "to_debug", pure )] pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString { let len = array.len(); let mut result = SmartString::new_const(); result.push_str("["); array.iter_mut().enumerate().for_each(|(i, x)| { result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, x)); if i < len - 1 { result.push_str(", "); } }); result.push_str("]"); result.into() } /// Convert the object map into a string. #[cfg(not(feature = "no_object"))] #[rhai_fn( name = "print", name = "to_string", name = "debug", name = "to_debug", pure )] pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString { let len = map.len(); let mut result = SmartString::new_const(); result.push_str("#{"); map.iter_mut().enumerate().for_each(|(i, (k, v))| { write!( result, "{:?}: {}{}", k, &print_with_func(FUNC_TO_DEBUG, &ctx, v), if i < len - 1 { ", " } else { "" } ) .unwrap(); }); result.push_str("}"); result.into() } } #[export_module] mod number_formatting { fn to_hex(value: T) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{value:x}").unwrap(); buf.into() } fn to_octal(value: T) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{value:o}").unwrap(); buf.into() } fn to_binary(value: T) -> ImmutableString { let mut buf = SmartString::new_const(); write!(&mut buf, "{value:b}").unwrap(); buf.into() } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn int_to_hex(value: INT) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn int_to_octal(value: INT) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn int_to_binary(value: INT) -> ImmutableString { to_binary(value) } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] pub mod numbers { /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u8_to_hex(value: u8) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u16_to_hex(value: u16) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u32_to_hex(value: u32) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u64_to_hex(value: u64) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i8_to_hex(value: i8) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i16_to_hex(value: i16) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i32_to_hex(value: i32) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i64_to_hex(value: i64) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u8_to_octal(value: u8) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u16_to_octal(value: u16) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u32_to_octal(value: u32) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u64_to_octal(value: u64) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i8_to_octal(value: i8) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i16_to_octal(value: i16) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i32_to_octal(value: i32) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i64_to_octal(value: i64) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u8_to_binary(value: u8) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u16_to_binary(value: u16) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u32_to_binary(value: u32) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u64_to_binary(value: u64) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i8_to_binary(value: i8) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i16_to_binary(value: i16) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i32_to_binary(value: i32) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i64_to_binary(value: i64) -> ImmutableString { to_binary(value) } #[cfg(not(target_family = "wasm"))] pub mod num_128 { /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn u128_to_hex(value: u128) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in hex format. #[rhai_fn(name = "to_hex")] pub fn i128_to_hex(value: i128) -> ImmutableString { to_hex(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn u128_to_octal(value: u128) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in octal format. #[rhai_fn(name = "to_octal")] pub fn i128_to_octal(value: i128) -> ImmutableString { to_octal(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn u128_to_binary(value: u128) -> ImmutableString { to_binary(value) } /// Convert the `value` into a string in binary format. #[rhai_fn(name = "to_binary")] pub fn i128_to_binary(value: i128) -> ImmutableString { to_binary(value) } } } } #[export_module] mod char_functions { /// Convert the Unicode character into a 32-bit integer value. pub const fn to_int(ch: char) -> INT { (ch as u32) as INT } } rhai-1.21.0/src/packages/string_more.rs000064400000000000000000001522441046102023000161030ustar 00000000000000use crate::plugin::*; use crate::{ def_package, Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, RhaiResultOf, SmartString, INT, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; use super::string_basic::{print_with_func, FUNC_TO_STRING}; def_package! { /// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage] pub MoreStringPackage(lib) { lib.set_standard_lib(true); combine_with_exported_module!(lib, "string", string_functions); } } #[export_module] mod string_functions { #[rhai_fn(name = "+", pure)] pub fn add_append( ctx: NativeCallContext, string: &mut ImmutableString, mut item: Dynamic, ) -> ImmutableString { let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); if s.is_empty() { return string.clone(); } let mut buf = >::as_ref(string).clone(); buf.push_str(&s); buf.into() } #[rhai_fn(name = "+=", name = "append")] pub fn add(ctx: NativeCallContext, string: &mut ImmutableString, mut item: Dynamic) { let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); if s.is_empty() { return; } let mut buf = >::as_ref(string).clone(); buf.push_str(&s); *string = buf.into(); } #[rhai_fn(name = "+", pure)] pub fn add_prepend( ctx: NativeCallContext, item: &mut Dynamic, string: &str, ) -> ImmutableString { let mut s = print_with_func(FUNC_TO_STRING, &ctx, item); if !string.is_empty() { s.make_mut().push_str(string); } s } // The following are needed in order to override the generic versions with `Dynamic` parameters. #[rhai_fn(name = "+")] pub fn add_append_str(string1: &str, string2: &str) -> ImmutableString { let mut buf = SmartString::new_const(); buf.push_str(string1); buf.push_str(string2); buf.into() } #[rhai_fn(name = "+")] pub fn add_append_char(string: &str, character: char) -> ImmutableString { let mut buf = SmartString::new_const(); buf.push_str(string); buf.push(character); buf.into() } #[rhai_fn(name = "+")] pub fn add_prepend_char(character: char, string: &str) -> ImmutableString { let mut buf = SmartString::new_const(); buf.push(character); buf.push_str(string); buf.into() } #[allow(unused_variables)] #[rhai_fn(name = "+")] pub const fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString { string } #[allow(unused_variables)] #[rhai_fn(name = "+")] pub const fn add_prepend_unit(item: (), string: ImmutableString) -> ImmutableString { string } #[rhai_fn(name = "+=")] pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) { *string1 += string2; } #[rhai_fn(name = "+=", pure)] pub fn add_assign_append_char(string: &mut ImmutableString, character: char) { *string += character; } #[allow(unused_variables)] #[rhai_fn(name = "+=")] pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) {} #[cfg(not(feature = "no_index"))] pub mod blob_functions { use crate::Blob; #[rhai_fn(name = "+")] pub fn add_append(string: ImmutableString, utf8: Blob) -> ImmutableString { if utf8.is_empty() { return string; } let s = String::from_utf8_lossy(&utf8); if string.is_empty() { match s { std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap().into(), std::borrow::Cow::Owned(_) => s.into_owned().into(), } } else { let mut x = string.into_owned(); x += s.as_ref(); x.into() } } #[rhai_fn(name = "+=", name = "append")] pub fn add(string: &mut ImmutableString, utf8: Blob) { if utf8.is_empty() { return; } let mut s = >::as_ref(string).clone(); s.push_str(&String::from_utf8_lossy(&utf8)); *string = s.into(); } #[rhai_fn(name = "+")] pub fn add_prepend(utf8: Blob, string: &str) -> ImmutableString { let s = String::from_utf8_lossy(&utf8); let mut s = match s { std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(), std::borrow::Cow::Owned(_) => s.into_owned(), }; if !string.is_empty() { s += string; } s.into() } /// Convert the string into an UTF-8 encoded byte-stream as a BLOB. /// /// # Example /// /// ```rhai /// let text = "朝には紅顔ありて夕べには白骨となる"; /// /// let bytes = text.to_blob(); /// /// print(bytes.len()); // prints 51 /// ``` pub fn to_blob(string: &str) -> Blob { if string.is_empty() { return Blob::new(); } string.as_bytes().into() } } /// Return the length of the string, in number of characters. /// /// # Example /// /// ```rhai /// let text = "朝には紅顔ありて夕べには白骨となる"; /// /// print(text.len); // prints 17 /// ``` #[rhai_fn(name = "len", get = "len")] pub fn len(string: &str) -> INT { if string.is_empty() { return 0; } string.chars().count() as INT } /// Return true if the string is empty. #[rhai_fn(name = "is_empty", get = "is_empty")] pub const fn is_empty(string: &str) -> bool { string.len() == 0 } /// Return the length of the string, in number of bytes used to store it in UTF-8 encoding. /// /// # Example /// /// ```rhai /// let text = "朝には紅顔ありて夕べには白骨となる"; /// /// print(text.bytes); // prints 51 /// ``` #[rhai_fn(name = "bytes", get = "bytes")] pub const fn bytes(string: &str) -> INT { if string.is_empty() { return 0; } string.len() as INT } /// Remove all occurrences of a sub-string from the string. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.remove("hello"); /// /// print(text); // prints ", world! , foobar!" /// ``` pub fn remove(string: &mut ImmutableString, sub_string: &str) { *string -= sub_string; } /// Remove all occurrences of a character from the string. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.remove("o"); /// /// print(text); // prints "hell, wrld! hell, fbar!" /// ``` #[rhai_fn(name = "remove")] pub fn remove_char(string: &mut ImmutableString, character: char) { *string -= character; } /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if string.is_empty() { return; } match string.get_mut() { Some(s) => s.clear(), _ => *string = ImmutableString::new(), } } /// Cut off the string at the specified number of characters. /// /// * If `len` ≤ 0, the string is cleared. /// * If `len` ≥ length of string, the string is not truncated. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.truncate(13); /// /// print(text); // prints "hello, world!" /// /// text.truncate(10); /// /// print(text); // prints "hello, world!" /// ``` pub fn truncate(string: &mut ImmutableString, len: INT) { if len <= 0 { clear(string); return; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; if let Some((index, _)) = string.char_indices().nth(len) { let copy = string.make_mut(); copy.truncate(index); } } /// Remove whitespace characters from both ends of the string. /// /// # Example /// /// ```rhai /// let text = " hello "; /// /// text.trim(); /// /// print(text); // prints "hello" /// ``` pub fn trim(string: &mut ImmutableString) { if let Some(s) = string.get_mut() { let trimmed = s.trim(); if trimmed != s { *s = trimmed.into(); } } else { let trimmed = string.trim(); if trimmed != string { *string = trimmed.into(); } } } /// Remove the last character from the string and return it. /// /// If the string is empty, `()` is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.pop()); // prints '!' /// /// print(text); // prints "hello, world" /// ``` pub fn pop(string: &mut ImmutableString) -> Dynamic { if string.is_empty() { return Dynamic::UNIT; } string.make_mut().pop().map_or(Dynamic::UNIT, Into::into) } /// Remove a specified number of characters from the end of the string and return it as a /// new string. /// /// * If `len` ≤ 0, the string is not modified and an empty string is returned. /// * If `len` ≥ length of string, the string is cleared and the entire string returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.pop(4)); // prints "rld!" /// /// print(text); // prints "hello, wo" /// ``` #[rhai_fn(name = "pop")] pub fn pop_string( ctx: NativeCallContext, string: &mut ImmutableString, len: INT, ) -> ImmutableString { if string.is_empty() || len <= 0 { return ctx.engine().const_empty_string(); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let mut chars = Vec::::with_capacity(len); for _ in 0..len { match string.make_mut().pop() { Some(c) => chars.push(c), None => break, } } chars.into_iter().rev().collect::().into() } /// Convert the string to all upper-case and return it as a new string. /// /// # Example /// /// ```rhai /// let text = "hello, world!" /// /// print(text.to_upper()); // prints "HELLO, WORLD!" /// /// print(text); // prints "hello, world!" /// ``` pub fn to_upper(string: ImmutableString) -> ImmutableString { if string.chars().all(char::is_uppercase) { return string; } string.to_uppercase().into() } /// Convert the string to all upper-case. /// /// # Example /// /// ```rhai /// let text = "hello, world!" /// /// text.make_upper(); /// /// print(text); // prints "HELLO, WORLD!"; /// ``` pub fn make_upper(string: &mut ImmutableString) { if string.is_empty() || string.chars().all(char::is_uppercase) { return; } *string = string.to_uppercase().into(); } /// Convert the string to all lower-case and return it as a new string. /// /// # Example /// /// ```rhai /// let text = "HELLO, WORLD!" /// /// print(text.to_lower()); // prints "hello, world!" /// /// print(text); // prints "HELLO, WORLD!" /// ``` pub fn to_lower(string: ImmutableString) -> ImmutableString { if string.is_empty() || string.chars().all(char::is_lowercase) { return string; } string.to_lowercase().into() } /// Convert the string to all lower-case. /// /// # Example /// /// ```rhai /// let text = "HELLO, WORLD!" /// /// text.make_lower(); /// /// print(text); // prints "hello, world!"; /// ``` pub fn make_lower(string: &mut ImmutableString) { if string.is_empty() || string.chars().all(char::is_lowercase) { return; } *string = string.to_lowercase().into(); } /// Convert the character to upper-case and return it as a new character. /// /// # Example /// /// ```rhai /// let ch = 'a'; /// /// print(ch.to_upper()); // prints 'A' /// /// print(ch); // prints 'a' /// ``` #[rhai_fn(name = "to_upper")] pub fn to_upper_char(character: char) -> char { let mut stream = character.to_uppercase(); let ch = stream.next().unwrap(); if stream.next().is_some() { character } else { ch } } /// Convert the character to upper-case. /// /// # Example /// /// ```rhai /// let ch = 'a'; /// /// ch.make_upper(); /// /// print(ch); // prints 'A' /// ``` #[rhai_fn(name = "make_upper")] pub fn make_upper_char(character: &mut char) { *character = to_upper_char(*character); } /// Convert the character to lower-case and return it as a new character. /// /// # Example /// /// ```rhai /// let ch = 'A'; /// /// print(ch.to_lower()); // prints 'a' /// /// print(ch); // prints 'A' /// ``` #[rhai_fn(name = "to_lower")] pub fn to_lower_char(character: char) -> char { let mut stream = character.to_lowercase(); let ch = stream.next().unwrap(); if stream.next().is_some() { character } else { ch } } /// Convert the character to lower-case. /// /// # Example /// /// ```rhai /// let ch = 'A'; /// /// ch.make_lower(); /// /// print(ch); // prints 'a' /// ``` #[rhai_fn(name = "make_lower")] pub fn make_lower_char(character: &mut char) { *character = to_lower_char(*character); } /// Return `true` if the string contains a specified string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.contains("hello")); // prints true /// /// print(text.contains("hey")); // prints false /// ``` pub fn contains(string: &str, match_string: &str) -> bool { string.contains(match_string) } /// Return `true` if the string contains a specified character. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.contains('h')); // prints true /// /// print(text.contains('x')); // prints false /// ``` #[rhai_fn(name = "contains")] pub fn contains_char(string: &str, character: char) -> bool { string.contains(character) } /// Return `true` if the string starts with a specified string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.starts_with("hello")); // prints true /// /// print(text.starts_with("world")); // prints false /// ``` pub fn starts_with(string: &str, match_string: &str) -> bool { string.starts_with(match_string) } /// Return `true` if the string ends with a specified string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.ends_with("world!")); // prints true /// /// print(text.ends_with("hello")); // prints false /// ``` pub fn ends_with(string: &str, match_string: &str) -> bool { string.ends_with(match_string) } /// Find the specified `character` in the string, starting from the specified `start` position, /// and return the first index where it is found. /// If the `character` is not found, `-1` is returned. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, `-1` is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.index_of('l', 5)); // prints 10 (first index after 5) /// /// print(text.index_of('o', -7)); // prints 8 /// /// print(text.index_of('x', 0)); // prints -1 /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { if string.is_empty() { return -1; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let start = if start < 0 { let abs_start = start.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_start as u64 > MAX_USIZE_INT as u64 { return -1 as INT; } let abs_start = abs_start as usize; let chars: Vec<_> = string.chars().collect(); let num_chars = chars.len(); if abs_start > num_chars { 0 } else { chars .into_iter() .take(num_chars - abs_start) .collect::() .len() } } else if start == 0 { 0 } else if start > MAX_USIZE_INT || start as usize >= string.chars().count() { return -1 as INT; } else { string .chars() .take(start as usize) .collect::() .len() }; string[start..].find(character).map_or(-1 as INT, |index| { string[0..start + index].chars().count() as INT }) } /// Find the specified `character` in the string and return the first index where it is found. /// If the `character` is not found, `-1` is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.index_of('l')); // prints 2 (first index) /// /// print(text.index_of('x')); // prints -1 /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_char(string: &str, character: char) -> INT { if string.is_empty() { return -1; } string .find(character) .map_or(-1 as INT, |index| string[0..index].chars().count() as INT) } /// Find the specified sub-string in the string, starting from the specified `start` position, /// and return the first index where it is found. /// If the sub-string is not found, `-1` is returned. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, `-1` is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// print(text.index_of("ll", 5)); // prints 16 (first index after 5) /// /// print(text.index_of("ll", -15)); // prints 16 /// /// print(text.index_of("xx", 0)); // prints -1 /// ``` #[rhai_fn(name = "index_of")] pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { if string.is_empty() { return -1; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let start = if start < 0 { let abs_start = start.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_start as u64 > MAX_USIZE_INT as u64 { return -1 as INT; } let abs_start = abs_start as usize; let chars = string.chars().collect::>(); let num_chars = chars.len(); if abs_start > num_chars { 0 } else { chars .into_iter() .take(num_chars - abs_start) .collect::() .len() } } else if start == 0 { 0 } else if start > MAX_USIZE_INT || start as usize >= string.chars().count() { return -1 as INT; } else { string .chars() .take(start as usize) .collect::() .len() }; string[start..] .find(find_string) .map_or(-1 as INT, |index| { string[0..start + index].chars().count() as INT }) } /// Find the specified `character` in the string and return the first index where it is found. /// If the `character` is not found, `-1` is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// print(text.index_of("ll")); // prints 2 (first index) /// /// print(text.index_of("xx:)); // prints -1 /// ``` #[rhai_fn(name = "index_of")] pub fn index_of(string: &str, find_string: &str) -> INT { if string.is_empty() { return -1; } string .find(find_string) .map_or(-1 as INT, |index| string[0..index].chars().count() as INT) } /// Get the character at the `index` position in the string. /// /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). /// * If `index` < -length of string, zero is returned. /// * If `index` ≥ length of string, zero is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.get(0)); // prints 'h' /// /// print(text.get(-1)); // prints '!' /// /// print(text.get(99)); // prints empty (for '()')' /// ``` pub fn get(string: &str, index: INT) -> Dynamic { if index >= 0 { if index > MAX_USIZE_INT { return Dynamic::UNIT; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let index = index as usize; string .chars() .nth(index) .map_or_else(|| Dynamic::UNIT, Into::into) } else { // Count from end if negative let abs_index = index.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_index as u64 > MAX_USIZE_INT as u64 { return Dynamic::UNIT; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let abs_index = abs_index as usize; string .chars() .rev() .nth(abs_index - 1) .map_or_else(|| Dynamic::UNIT, Into::into) } } /// Set the `index` position in the string to a new `character`. /// /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). /// * If `index` < -length of string, the string is not modified. /// * If `index` ≥ length of string, the string is not modified. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// text.set(3, 'x'); /// /// print(text); // prints "helxo, world!" /// /// text.set(-3, 'x'); /// /// print(text); // prints "hello, worxd!" /// /// text.set(99, 'x'); /// /// print(text); // prints "hello, worxd!" /// ``` pub fn set(string: &mut ImmutableString, index: INT, character: char) { if index >= 0 { if index > MAX_USIZE_INT { return; } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let index = index as usize; *string = string .chars() .enumerate() .map(|(i, ch)| if i == index { character } else { ch }) .collect(); } else { let abs_index = index.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_index as u64 > MAX_USIZE_INT as u64 { return; } #[allow(clippy::cast_possible_truncation)] let abs_index = abs_index as usize; let string_len = string.chars().count(); if abs_index <= string_len { let index = string_len - abs_index; *string = string .chars() .enumerate() .map(|(i, ch)| if i == index { character } else { ch }) .collect(); } } } /// Copy an exclusive range of characters from the string and return it as a new string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.sub_string(3..7)); // prints "lo, " /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_range( ctx: NativeCallContext, string: &str, range: ExclusiveRange, ) -> ImmutableString { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); sub_string(ctx, string, start, end - start) } /// Copy an inclusive range of characters from the string and return it as a new string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.sub_string(3..=7)); // prints "lo, w" /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_inclusive_range( ctx: NativeCallContext, string: &str, range: InclusiveRange, ) -> ImmutableString { let start = INT::max(*range.start(), 0); let end = INT::min(INT::max(*range.end(), start), INT::MAX - 1); sub_string(ctx, string, start, end - start + 1) } /// Copy a portion of the string and return it as a new string. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, an empty string is returned. /// * If `len` ≤ 0, an empty string is returned. /// * If `start` position + `len` ≥ length of string, entire portion of the string after the `start` position is copied and returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.sub_string(3, 4)); // prints "lo, " /// /// print(text.sub_string(-8, 3)); // prints ", w" /// ``` #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn sub_string( ctx: NativeCallContext, string: &str, start: INT, len: INT, ) -> ImmutableString { if string.is_empty() || len <= 0 { return ctx.engine().const_empty_string(); } let mut chars = Vec::with_capacity(string.len()); if string.is_empty() || len <= 0 { return ctx.engine().const_empty_string(); } let offset = if start < 0 { let abs_start = start.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_start as u64 > MAX_USIZE_INT as u64 { return ctx.engine().const_empty_string(); } #[allow(clippy::cast_possible_truncation)] let abs_start = abs_start as usize; chars.extend(string.chars()); if abs_start > chars.len() { 0 } else { chars.len() - abs_start } } else if start > MAX_USIZE_INT || start as usize >= string.chars().count() { return ctx.engine().const_empty_string(); } else { start as usize }; if chars.is_empty() { chars.extend(string.chars()); } let len = len.min(MAX_USIZE_INT) as usize; let len = if offset + len > chars.len() { chars.len() - offset } else { len }; chars .iter() .skip(offset) .take(len) .copied() .collect::() .into() } /// Copy a portion of the string beginning at the `start` position till the end and return it as /// a new string. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, the entire string is copied and returned. /// * If `start` ≥ length of string, an empty string is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.sub_string(5)); // prints ", world!" /// /// print(text.sub_string(-5)); // prints "orld!" /// ``` #[rhai_fn(name = "sub_string")] pub fn sub_string_starting_from( ctx: NativeCallContext, string: &str, start: INT, ) -> ImmutableString { if string.is_empty() { return ctx.engine().const_empty_string(); } let len = string.len() as INT; sub_string(ctx, string, start, len) } /// Remove all characters from the string except those within an exclusive `range`. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// text.crop(2..8); /// /// print(text); // prints "llo, w" /// ``` #[rhai_fn(name = "crop")] pub fn crop_range(ctx: NativeCallContext, string: &mut ImmutableString, range: ExclusiveRange) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); crop(ctx, string, start, end - start); } /// Remove all characters from the string except those within an inclusive `range`. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// text.crop(2..=8); /// /// print(text); // prints "llo, wo" /// ``` #[rhai_fn(name = "crop")] pub fn crop_inclusive_range( ctx: NativeCallContext, string: &mut ImmutableString, range: InclusiveRange, ) { let start = INT::max(*range.start(), 0); let end = INT::min(INT::max(*range.end(), start), INT::MAX - 1); crop(ctx, string, start, end - start + 1); } /// Remove all characters from the string except those within a range. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, position counts from the beginning of the string. /// * If `start` ≥ length of string, the entire string is cleared. /// * If `len` ≤ 0, the entire string is cleared. /// * If `start` position + `len` ≥ length of string, only the portion of the string after the `start` position is retained. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// text.crop(2, 8); /// /// print(text); // prints "llo, wor" /// /// text.crop(-5, 3); /// /// print(text); // prints ", w" /// ``` #[rhai_fn(name = "crop")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn crop(ctx: NativeCallContext, string: &mut ImmutableString, start: INT, len: INT) { if string.is_empty() { return; } if len <= 0 { *string = ctx.engine().const_empty_string(); return; } let mut chars = Vec::with_capacity(string.len()); if string.is_empty() || len <= 0 { string.make_mut().clear(); return; } let offset = if start < 0 { let abs_start = start.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_start as u64 > MAX_USIZE_INT as u64 { return; } let abs_start = abs_start as usize; chars.extend(string.chars()); if abs_start > chars.len() { 0 } else { chars.len() - abs_start } } else if start > MAX_USIZE_INT || start as usize >= string.chars().count() { string.make_mut().clear(); return; } else { start as usize }; if chars.is_empty() { chars.extend(string.chars()); } let len = len.min(MAX_USIZE_INT) as usize; let len = if offset + len > chars.len() { chars.len() - offset } else { len }; let copy = string.make_mut(); copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); } /// Remove all characters from the string up to the `start` position. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, the string is not modified. /// * If `start` ≥ length of string, the entire string is cleared. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// text.crop(5); /// /// print(text); // prints ", world!" /// /// text.crop(-3); /// /// print(text); // prints "ld!" /// ``` #[rhai_fn(name = "crop")] pub fn crop_string_starting_from( ctx: NativeCallContext, string: &mut ImmutableString, start: INT, ) { crop(ctx, string, start, string.len() as INT); } /// Replace all occurrences of the specified sub-string in the string with another string. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.replace("hello", "hey"); /// /// print(text); // prints "hey, world! hey, foobar!" /// ``` #[rhai_fn(name = "replace")] pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { if string.is_empty() { return; } *string = string.replace(find_string, substitute_string).into(); } /// Replace all occurrences of the specified sub-string in the string with the specified character. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.replace("hello", '*'); /// /// print(text); // prints "*, world! *, foobar!" /// ``` #[rhai_fn(name = "replace")] pub fn replace_string_with_char( string: &mut ImmutableString, find_string: &str, substitute_character: char, ) { if string.is_empty() { return; } *string = string .replace(find_string, &substitute_character.to_string()) .into(); } /// Replace all occurrences of the specified character in the string with another string. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.replace('l', "(^)"); /// /// print(text); // prints "he(^)(^)o, wor(^)d! he(^)(^)o, foobar!" /// ``` #[rhai_fn(name = "replace")] pub fn replace_char_with_string( string: &mut ImmutableString, find_character: char, substitute_string: &str, ) { if string.is_empty() { return; } *string = string .replace(&find_character.to_string(), substitute_string) .into(); } /// Replace all occurrences of the specified character in the string with another character. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foobar!"; /// /// text.replace("l", '*'); /// /// print(text); // prints "he**o, wor*d! he**o, foobar!" /// ``` #[rhai_fn(name = "replace")] pub fn replace_char( string: &mut ImmutableString, find_character: char, substitute_character: char, ) { if string.is_empty() { return; } *string = string .replace( &find_character.to_string(), &substitute_character.to_string(), ) .into(); } /// Pad the string to at least the specified number of characters with the specified `character`. /// /// If `len` ≤ length of string, no padding is done. /// /// # Example /// /// ```rhai /// let text = "hello"; /// /// text.pad(8, '!'); /// /// print(text); // prints "hello!!!" /// /// text.pad(5, '*'); /// /// print(text); // prints "hello!!!" /// ``` #[rhai_fn(return_raw)] pub fn pad( ctx: NativeCallContext, string: &mut ImmutableString, len: INT, character: char, ) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let _ctx = ctx; // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) .into()); } let orig_len = string.chars().count(); if len <= orig_len { return Ok(()); } let p = string.make_mut(); for _ in 0..(len - orig_len) { p.push(character); } #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) .into()); } Ok(()) } /// Pad the string to at least the specified number of characters with the specified string. /// /// If `len` ≤ length of string, no padding is done. /// /// # Example /// /// ```rhai /// let text = "hello"; /// /// text.pad(10, "(!)"); /// /// print(text); // prints "hello(!)(!)" /// /// text.pad(8, '***'); /// /// print(text); // prints "hello(!)(!)" /// ``` #[rhai_fn(name = "pad", return_raw)] pub fn pad_with_string( ctx: NativeCallContext, string: &mut ImmutableString, len: INT, padding: &str, ) -> RhaiResultOf<()> { if len <= 0 { return Ok(()); } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let _ctx = ctx; // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) .into()); } let mut str_len = string.chars().count(); let padding_len = padding.chars().count(); if len <= str_len { return Ok(()); } let p = string.make_mut(); while str_len < len { if str_len + padding_len <= len { p.push_str(padding); str_len += padding_len; } else { p.extend(padding.chars().take(len - str_len)); str_len = len; } } #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), crate::Position::NONE, ) .into()); } Ok(()) } /// Return the string that is lexically greater than the other string. /// /// # Example /// /// ```rhai /// max("hello", "world"); // returns "world" /// ``` #[rhai_fn(name = "max")] pub fn max_string(string1: ImmutableString, string2: ImmutableString) -> ImmutableString { if string1 >= string2 { string1 } else { string2 } } /// Return the string that is lexically smaller than the other string. /// /// # Example /// /// ```rhai /// min("hello", "world"); // returns "hello" /// ``` #[rhai_fn(name = "min")] pub fn min_string(string1: ImmutableString, string2: ImmutableString) -> ImmutableString { if string1 <= string2 { string1 } else { string2 } } /// Return the character that is lexically greater than the other character. /// /// # Example /// /// ```rhai /// max('h', 'w'); // returns 'w' /// ``` #[rhai_fn(name = "max")] pub const fn max_char(char1: char, char2: char) -> char { if char1 >= char2 { char1 } else { char2 } } /// Return the character that is lexically smaller than the other character. /// /// # Example /// /// ```rhai /// max('h', 'w'); // returns 'h' /// ``` #[rhai_fn(name = "min")] pub const fn min_char(char1: char, char2: char) -> char { if char1 <= char2 { char1 } else { char2 } } #[cfg(not(feature = "no_index"))] pub mod arrays { use crate::Array; /// Split the string into two at the specified `index` position and return it both strings /// as an array. /// /// The character at the `index` position (if any) is returned in the _second_ string. /// /// * If `index` < 0, position counts from the end of the string (`-1` is the last character). /// * If `index` < -length of string, it is equivalent to cutting at position 0. /// * If `index` ≥ length of string, it is equivalent to cutting at the end of the string. /// /// # Example /// /// ```rhai /// let text = "hello, world!"; /// /// print(text.split(6)); // prints ["hello,", " world!"] /// /// print(text.split(13)); // prints ["hello, world!", ""] /// /// print(text.split(-6)); // prints ["hello, ", "world!"] /// /// print(text.split(-99)); // prints ["", "hello, world!"] /// ``` #[rhai_fn(name = "split")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array { if index <= 0 { let abs_index = index.unsigned_abs(); #[allow(clippy::unnecessary_cast)] if abs_index as u64 > MAX_USIZE_INT as u64 { return vec![ ctx.engine().const_empty_string().into(), string.clone().into(), ]; } let abs_index = abs_index as usize; let num_chars = string.chars().count(); if abs_index > num_chars { vec![ ctx.engine().const_empty_string().into(), string.clone().into(), ] } else { let prefix: String = string.chars().take(num_chars - abs_index).collect(); let prefix_len = prefix.len(); vec![prefix.into(), string[prefix_len..].into()] } } else if index > MAX_USIZE_INT { vec![ string.clone().into(), ctx.engine().const_empty_string().into(), ] } else { let prefix: String = string.chars().take(index as usize).collect(); let prefix_len = prefix.len(); vec![prefix.into(), string[prefix_len..].into()] } } /// Return an array containing all the characters of the string. /// /// # Example /// /// ```rhai /// let text = "hello"; /// /// print(text.to_chars()); // prints "['h', 'e', 'l', 'l', 'o']" /// ``` #[rhai_fn(name = "to_chars")] pub fn to_chars(string: &str) -> Array { if string.is_empty() { Array::new() } else { string.chars().map(Into::into).collect() } } /// Split the string into segments based on whitespaces, returning an array of the segments. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split()); // prints ["hello,", "world!", "hello,", "foo!"] /// ``` #[rhai_fn(name = "split")] pub fn split_whitespace(string: &str) -> Array { if string.is_empty() { Array::new() } else { string.split_whitespace().map(Into::into).collect() } } /// Split the string into segments based on a `delimiter` string, returning an array of the segments. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split("ll")); // prints ["he", "o, world! he", "o, foo!"] /// ``` pub fn split(string: &str, delimiter: &str) -> Array { string.split(delimiter).map(Into::into).collect() } /// Split the string into at most the specified number of `segments` based on a `delimiter` string, /// returning an array of the segments. /// /// If `segments` < 1, only one segment is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"] /// ``` #[rhai_fn(name = "split")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { if segments < 1 { return [string.into()].into(); } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.splitn(pieces, delimiter).map(Into::into).collect() } /// Split the string into segments based on a `delimiter` character, returning an array of the segments. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split('l')); // prints ["he", "", "o, wor", "d! he", "", "o, foo!"] /// ``` #[rhai_fn(name = "split")] pub fn split_char(string: &str, delimiter: char) -> Array { string.split(delimiter).map(Into::into).collect() } /// Split the string into at most the specified number of `segments` based on a `delimiter` character, /// returning an array of the segments. /// /// If `segments` < 1, only one segment is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"] /// ``` #[rhai_fn(name = "split")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { if segments < 1 { return [string.into()].into(); } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.splitn(pieces, delimiter).map(Into::into).collect() } /// Split the string into segments based on a `delimiter` string, returning an array of the /// segments in _reverse_ order. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split_rev("ll")); // prints ["o, foo!", "o, world! he", "he"] /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit(string: &str, delimiter: &str) -> Array { string.rsplit(delimiter).map(Into::into).collect() } /// Split the string into at most a specified number of `segments` based on a `delimiter` string, /// returning an array of the segments in _reverse_ order. /// /// If `segments` < 1, only one segment is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"] /// ``` #[rhai_fn(name = "split_rev")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { if segments < 1 { return [string.into()].into(); } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.rsplitn(pieces, delimiter).map(Into::into).collect() } /// Split the string into segments based on a `delimiter` character, returning an array of /// the segments in _reverse_ order. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split_rev('l')); // prints ["o, foo!", "", "d! he", "o, wor", "", "he"] /// ``` #[rhai_fn(name = "split_rev")] pub fn rsplit_char(string: &str, delimiter: char) -> Array { string.rsplit(delimiter).map(Into::into).collect() } /// Split the string into at most the specified number of `segments` based on a `delimiter` character, /// returning an array of the segments. /// /// If `segments` < 1, only one segment is returned. /// /// # Example /// /// ```rhai /// let text = "hello, world! hello, foo!"; /// /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he" /// ``` #[rhai_fn(name = "split_rev")] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { if segments < 1 { return [string.into()].into(); } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.rsplitn(pieces, delimiter).map(Into::into).collect() } } } rhai-1.21.0/src/packages/time_basic.rs000064400000000000000000000255041046102023000156500ustar 00000000000000#![cfg(not(feature = "no_time"))] use super::arithmetic::make_err as make_arithmetic_err; use crate::plugin::*; use crate::{def_package, Dynamic, RhaiResult, RhaiResultOf, INT}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] use std::time::{Duration, Instant}; #[cfg(all(target_family = "wasm", target_os = "unknown"))] use instant::{Duration, Instant}; def_package! { /// Package of basic timing utilities. pub BasicTimePackage(lib) { lib.set_standard_lib(true); // Register date/time functions combine_with_exported_module!(lib, "time", time_functions); } } #[export_module] mod time_functions { /// Create a timestamp containing the current system time. /// /// # Example /// /// ```rhai /// let now = timestamp(); /// /// sleep(10.0); // sleep for 10 seconds /// /// print(now.elapsed); // prints 10.??? /// ``` #[rhai_fn(volatile)] pub fn timestamp() -> Instant { Instant::now() } /// Return the number of seconds between the current system time and the timestamp. /// /// # Example /// /// ```rhai /// let now = timestamp(); /// /// sleep(10.0); // sleep for 10 seconds /// /// print(now.elapsed); // prints 10.??? /// ``` #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] pub fn elapsed(timestamp: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] if timestamp > Instant::now() { Err(make_arithmetic_err("Time-stamp is later than now")) } else { Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into()) } #[cfg(feature = "no_float")] { let seconds = timestamp.elapsed().as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { return Err(make_arithmetic_err(format!( "Integer overflow for timestamp.elapsed: {seconds}" ))); } if timestamp > Instant::now() { return Err(make_arithmetic_err("Time-stamp is later than now")); } Ok((seconds as INT).into()) } } /// Return the number of seconds between two timestamps. #[rhai_fn(return_raw, name = "-")] #[allow(clippy::unnecessary_wraps)] pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult { #[cfg(not(feature = "no_float"))] return Ok(if timestamp2 > timestamp1 { -(timestamp2 - timestamp1).as_secs_f64() as FLOAT } else { (timestamp1 - timestamp2).as_secs_f64() as FLOAT } .into()); #[cfg(feature = "no_float")] if timestamp2 > timestamp1 { let seconds = (timestamp2 - timestamp1).as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { return Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: -{seconds}" ))); } Ok((-(seconds as INT)).into()) } else { let seconds = (timestamp1 - timestamp2).as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) { return Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: {seconds}" ))); } Ok((seconds as INT).into()) } } #[cfg(not(feature = "no_float"))] pub mod float_functions { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { return subtract_impl(timestamp, -seconds); } if cfg!(not(feature = "unchecked")) { if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) { return Err(make_arithmetic_err(format!( "Integer overflow for timestamp add: {seconds}" ))); } timestamp .checked_add(Duration::from_millis((seconds * 1000.0) as u64)) .ok_or_else(|| { make_arithmetic_err(format!( "Timestamp overflow when adding {seconds} second(s)" )) }) } else { Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64)) } } #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { return add_impl(timestamp, -seconds); } if cfg!(not(feature = "unchecked")) { if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) { return Err(make_arithmetic_err(format!( "Integer overflow for timestamp subtract: {seconds}" ))); } timestamp .checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .ok_or_else(|| { make_arithmetic_err(format!( "Timestamp overflow when subtracting {seconds} second(s)" )) }) } else { Ok(timestamp .checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .unwrap()) } } /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { add_impl(timestamp, seconds) } /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } } #[inline(always)] fn add_inner(timestamp: Instant, seconds: u64) -> Option { if cfg!(not(feature = "unchecked")) { timestamp.checked_add(Duration::from_secs(seconds)) } else { Some(timestamp + Duration::from_secs(seconds)) } } #[inline] fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { if seconds < 0 { subtract_inner(timestamp, seconds.unsigned_abs() as u64) } else { #[allow(clippy::cast_sign_loss)] add_inner(timestamp, seconds as u64) } .ok_or_else(|| { make_arithmetic_err(format!( "Timestamp overflow when adding {seconds} second(s)" )) }) } #[inline(always)] fn subtract_inner(timestamp: Instant, seconds: u64) -> Option { #[cfg(not(feature = "unchecked"))] return timestamp.checked_sub(Duration::from_secs(seconds)); #[cfg(feature = "unchecked")] return Some(timestamp - Duration::from_secs(seconds)); } #[inline] fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { if seconds < 0 { add_inner(timestamp, seconds.unsigned_abs() as u64) } else { #[allow(clippy::cast_sign_loss)] subtract_inner(timestamp, seconds as u64) } .ok_or_else(|| { make_arithmetic_err(format!( "Timestamp overflow when subtracting {seconds} second(s)" )) }) } /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "+")] pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf { add_impl(timestamp, seconds) } /// Add the specified number of `seconds` to the timestamp. #[rhai_fn(return_raw, name = "+=")] pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = add_impl(*timestamp, seconds)?; Ok(()) } /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp. #[rhai_fn(return_raw, name = "-")] pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf { subtract_impl(timestamp, seconds) } /// Subtract the specified number of `seconds` from the timestamp. #[rhai_fn(return_raw, name = "-=")] pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> { *timestamp = subtract_impl(*timestamp, seconds)?; Ok(()) } /// Return `true` if two timestamps are equal. #[rhai_fn(name = "==")] pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 == timestamp2 } /// Return `true` if two timestamps are not equal. #[rhai_fn(name = "!=")] pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 != timestamp2 } /// Return `true` if the first timestamp is earlier than the second. #[rhai_fn(name = "<")] pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 < timestamp2 } /// Return `true` if the first timestamp is earlier than or equals to the second. #[rhai_fn(name = "<=")] pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 <= timestamp2 } /// Return `true` if the first timestamp is later than the second. #[rhai_fn(name = ">")] pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 > timestamp2 } /// Return `true` if the first timestamp is later than or equals to the second. #[rhai_fn(name = ">=")] pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool { timestamp1 >= timestamp2 } } rhai-1.21.0/src/parser.rs000064400000000000000000004751511046102023000132760ustar 00000000000000//! Main module defining the lexer and parser. use crate::api::options::LangOptions; use crate::ast::{ ASTFlags, BinaryExpr, CaseBlocksList, Expr, FlowControl, FnCallExpr, FnCallHashes, Ident, OpAssignment, RangeCase, ScriptFuncDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, }; use crate::engine::{Precedence, OP_CONTAINS, OP_NOT}; use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::tokenizer::{ is_reserved_keyword_or_symbol, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; use crate::types::dynamic::{AccessMode, Union}; use crate::{ calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec, ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared, SmartString, StaticVec, ThinVec, VarDefInfo, AST, PERR, }; use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ convert::TryFrom, fmt, hash::{Hash, Hasher}, num::{NonZeroU8, NonZeroUsize}, }; pub type ParseResult = Result; #[cfg(not(feature = "no_function"))] type FnLib = StraightHashMap>; /// Invalid variable name that acts as a search barrier in a [`Scope`]. const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $"; impl PERR { /// Make a [`ParseError`] using the current type and position. #[cold] #[inline(never)] fn into_err(self, pos: Position) -> ParseError { ParseError(self.into(), pos) } } /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. pub struct ParseState<'a, 't, 'f> { /// Stream of input tokens. pub input: &'t mut TokenStream<'a>, /// Tokenizer control interface. pub tokenizer_control: TokenizerControl, /// Script-defined functions. #[cfg(not(feature = "no_function"))] pub lib: &'f mut FnLib, /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, /// External [scope][Scope] with constants. pub external_constants: Option<&'a Scope<'a>>, /// Global runtime state. pub global: Option>, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: Scope<'a>, /// Size of the local variables stack upon entry of the current block scope. pub frame_pointer: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] pub external_vars: ThinVec, /// An indicator that, when set to `false`, disables variable capturing into externals one /// single time up until the nearest consumed Identifier token. /// /// If set to `false` the next call to [`access_var`][ParseState::access_var] will not capture /// the variable. /// /// All consequent calls to [`access_var`][ParseState::access_var] will not be affected. pub allow_capture: bool, /// Encapsulates a local stack with imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] pub imports: ThinVec, /// List of globally-imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] pub global_imports: ThinVec, /// Unused dummy field. #[cfg(feature = "no_function")] pub _dummy: &'f (), } impl fmt::Debug for ParseState<'_, '_, '_> { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("ParseState"); f.field("tokenizer_control", &self.tokenizer_control) .field("external_constants_scope", &self.external_constants) .field("global", &self.global) .field("stack", &self.stack) .field("frame_pointer", &self.frame_pointer); #[cfg(not(feature = "no_closure"))] f.field("external_vars", &self.external_vars) .field("allow_capture", &self.allow_capture); #[cfg(not(feature = "no_module"))] f.field("imports", &self.imports) .field("global_imports", &self.global_imports); f.finish() } } impl<'a, 't, 'f> ParseState<'a, 't, 'f> { /// Create a new [`ParseState`]. #[inline] #[must_use] pub fn new( external_constants: Option<&'a Scope>, input: &'t mut TokenStream<'a>, tokenizer_control: TokenizerControl, #[cfg(not(feature = "no_function"))] lib: &'f mut FnLib, #[cfg(feature = "no_function")] dummy: &'f (), ) -> Self { Self { input, tokenizer_control, #[cfg(not(feature = "no_function"))] lib, #[cfg(feature = "no_function")] _dummy: dummy, expr_filter: |_| true, #[cfg(not(feature = "no_closure"))] external_vars: ThinVec::new(), allow_capture: true, external_constants, global: None, stack: Scope::new(), frame_pointer: 0, #[cfg(not(feature = "no_module"))] imports: ThinVec::new(), #[cfg(not(feature = "no_module"))] global_imports: ThinVec::new(), } } /// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order. /// /// The first return value is the offset to be deducted from `ParseState::stack::len()`, /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. /// /// If the variable is not present in the scope, the first return value is zero. /// /// The second return value indicates whether the barrier has been hit before finding the variable. #[must_use] pub fn find_var(&self, name: &str) -> (usize, bool) { let mut hit_barrier = false; let index = self .stack .iter_rev_inner() .position(|(n, ..)| { if n == SCOPE_SEARCH_BARRIER_MARKER { // Do not go beyond the barrier hit_barrier = true; false } else { n == name } }) .map_or(0, |i| i + 1); (index, hit_barrier) } /// Find a module by name in the [`ParseState`], searching in reverse. /// /// Returns the offset to be deducted from `Stack::len`, /// i.e. the top element of the [`ParseState`] is offset 1. /// /// Returns [`None`] when the variable name is not found in the [`ParseState`]. /// /// # Panics /// /// Panics when called under `no_module`. #[cfg(not(feature = "no_module"))] #[must_use] pub fn find_module(&self, name: &str) -> Option { self.imports .iter() .rev() .rposition(|n| n == name) .and_then(|i| NonZeroUsize::new(i + 1)) } } bitflags! { /// Bit-flags containing all status for [`ParseSettings`]. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct ParseSettingFlags: u8 { /// Is the construct being parsed located at global level? const GLOBAL_LEVEL = 0b0000_0001; /// Is the construct being parsed located inside a function definition? const FN_SCOPE = 0b0000_0010; /// Is the construct being parsed located inside a closure definition? const CLOSURE_SCOPE = 0b0000_0100; /// Is the construct being parsed located inside a breakable loop? const BREAKABLE = 0b0000_1000; /// Disallow statements in blocks? const DISALLOW_STATEMENTS_IN_BLOCKS = 0b0001_0000; /// Disallow unquoted map properties? const DISALLOW_UNQUOTED_MAP_PROPERTIES = 0b0010_0000; } } bitflags! { /// Bit-flags containing all status for parsing property/indexing/namespace chains. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] struct ChainingFlags: u8 { /// Is the construct being parsed a property? const PROPERTY = 0b0000_0001; /// Disallow namespaces? const DISALLOW_NAMESPACES = 0b0000_0010; } } /// A type that encapsulates all the settings for a particular parsing function. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ParseSettings { /// Flags. pub flags: ParseSettingFlags, /// Language options in effect (overrides Engine options). pub options: LangOptions, /// Current expression nesting level. pub level: usize, /// Current position. pub pos: Position, /// Maximum levels of expression nesting (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub max_expr_depth: usize, } impl ParseSettings { /// Is a particular flag on? #[inline(always)] #[must_use] pub const fn has_flag(&self, flag: ParseSettingFlags) -> bool { self.flags.intersects(flag) } /// Is a particular language option on? #[inline(always)] #[must_use] pub const fn has_option(&self, option: LangOptions) -> bool { self.options.intersects(option) } /// Create a new `ParseSettings` with one higher expression level. #[inline] pub fn level_up(&self) -> ParseResult { #[cfg(not(feature = "unchecked"))] if self.max_expr_depth > 0 && self.level >= self.max_expr_depth { return Err(PERR::ExprTooDeep.into_err(self.pos)); } Ok(Self { level: self.level + 1, ..*self }) } /// Create a new `ParseSettings` with one higher expression level. #[inline] pub fn level_up_with_position(&self, pos: Position) -> ParseResult { let mut x = self.level_up()?; x.pos = pos; Ok(x) } } /// Make an anonymous function. #[cfg(not(feature = "no_function"))] #[inline] #[must_use] pub fn make_anonymous_fn(hash: u64) -> crate::Identifier { use std::fmt::Write; let mut buf = crate::Identifier::new_const(); write!(&mut buf, "{}{hash:016x}", crate::engine::FN_ANONYMOUS).unwrap(); buf } /// Is this function an anonymous function? #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn is_anonymous_fn(fn_name: &str) -> bool { fn_name.starts_with(crate::engine::FN_ANONYMOUS) } impl Expr { /// Raise an error if the expression can never yield a boolean value. fn ensure_bool_expr(self) -> ParseResult { let type_name = match self { Self::Unit(..) => "()", Self::DynamicConstant(ref v, ..) if !v.is_bool() => v.type_name(), Self::IntegerConstant(..) => "a number", #[cfg(not(feature = "no_float"))] Self::FloatConstant(..) => "a floating-point number", Self::CharConstant(..) => "a character", Self::StringConstant(..) => "a string", Self::InterpolatedString(..) => "a string", Self::Array(..) => "an array", Self::Map(..) => "an object map", _ => return Ok(self), }; Err( PERR::MismatchedType("a boolean expression".into(), type_name.into()) .into_err(self.start_position()), ) } /// Raise an error if the expression can never yield an iterable value. fn ensure_iterable(self) -> ParseResult { let type_name = match self { Self::Unit(..) => "()", Self::BoolConstant(..) => "a boolean", Self::IntegerConstant(..) => "a number", #[cfg(not(feature = "no_float"))] Self::FloatConstant(..) => "a floating-point number", Self::CharConstant(..) => "a character", Self::Map(..) => "an object map", _ => return Ok(self), }; Err( PERR::MismatchedType("an iterable value".into(), type_name.into()) .into_err(self.start_position()), ) } } /// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). fn ensure_not_statement_expr( input: &mut TokenStream, type_name: &(impl ToString + ?Sized), ) -> ParseResult<()> { match input.peek().unwrap() { (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), _ => Ok(()), } } /// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { match input.peek().unwrap() { (token @ Token::Equals, pos) => Err(LexError::ImproperSymbol( token.literal_syntax().into(), "Possibly a typo of '=='?".into(), ) .into_err(*pos)), _ => Ok(()), } } /// Consume a particular [token][Token], checking that it is the expected one. /// /// # Panics /// /// Panics if the next token is not the expected one, or either tokens is not a literal symbol. #[inline(always)] fn eat_token(input: &mut TokenStream, expected_token: &Token) -> Position { let (t, pos) = input.next().unwrap(); debug_assert_eq!( &t, expected_token, "{} expected but gets {} at {}", expected_token.literal_syntax(), t.literal_syntax(), pos, ); pos } /// Match a particular [token][Token], consuming it if matched. #[inline] fn match_token(input: &mut TokenStream, token: &Token) -> (bool, Position) { let (t, pos) = input.peek().unwrap(); if t == token { (true, eat_token(input, token)) } else { (false, *pos) } } /// Process a block comment such that it indents properly relative to the start token. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] #[inline] fn unindent_block_comment(comment: String, pos: usize) -> String { if pos == 0 || !comment.contains('\n') { return comment; } // Note, use `trim_start_matches` instead of `trim` because `trim` will remove even multi-byte // Unicode spaces, which may cause the minimum offset to end up inside that multi-byte space // character. Therefore, be conservative and only remove ASCII spaces. let offset = comment .lines() .skip(1) .map(|s| s.len() - s.trim_start_matches(' ').len()) .min() .unwrap_or(pos) .min(pos); if offset == 0 { return comment; } comment .lines() .enumerate() .map(|(i, s)| if i > 0 { &s[offset..] } else { s }) .collect::>() .join("\n") } /// Parse a variable name. fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().unwrap() { // Variable name (Token::Identifier(s), pos) => Ok((*s, pos)), // Reserved keyword (Token::Reserved(s), pos) if is_valid_identifier(&s) => { Err(PERR::Reserved(s.to_string()).into_err(pos)) } // Bad identifier (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a variable name (.., pos) => Err(PERR::VariableExpected.into_err(pos)), } } /// Optimize the structure of a chained expression where the root expression is another chained expression. /// /// # Panics /// /// Panics if the expression is not a combo chain. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn optimize_combo_chain(expr: &mut Expr) { #[allow(clippy::type_complexity)] let (mut x, x_options, x_pos, mut root, mut root_options, root_pos, make_sub, make_root): ( _, _, _, _, _, _, fn(_, _, _) -> Expr, fn(_, _, _) -> Expr, ) = match expr.take() { #[cfg(not(feature = "no_index"))] Expr::Index(mut x, opt, pos) => match x.lhs.take() { Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Index), #[cfg(not(feature = "no_object"))] Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Dot), _ => unreachable!("combo chain expected"), }, #[cfg(not(feature = "no_object"))] Expr::Dot(mut x, opt, pos) => match x.lhs.take() { #[cfg(not(feature = "no_index"))] Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index), Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Dot), _ => unreachable!("combo chain expected"), }, _ => unreachable!("combo chain expected"), }; // Rewrite the chains like this: // // Source: ( x[y].prop_a )[z].prop_b // ^ ^ // parentheses that generated the combo chain // // From: Index( Index( x, Dot(y, prop_a) ), Dot(z, prop_b) ) // ^ ^ ^ // x root tail // // To: Index( x, Dot(y, Index(prop_a, Dot(z, prop_b) ) ) ) // // Equivalent to: x[y].prop_a[z].prop_b // Find the end of the root chain. let mut tail = root.as_mut(); let mut tail_options = &mut root_options; while !tail_options.intersects(ASTFlags::BREAK) { match tail.rhs { Expr::Index(ref mut x, ref mut options2, ..) => { tail = x.as_mut(); tail_options = options2; } #[cfg(not(feature = "no_object"))] Expr::Dot(ref mut x, ref mut options2, ..) => { tail = x.as_mut(); tail_options = options2; } _ => break, } } // Since we attach the outer chain to the root chain, we no longer terminate at the end of the // root chain, so remove the ASTFlags::BREAK flag. tail_options.remove(ASTFlags::BREAK); x.lhs = tail.rhs.take(); // remove tail and insert it into head of outer chain tail.rhs = make_sub(x, x_options, x_pos); // attach outer chain to tail *expr = make_root(root, root_options, root_pos); } impl Engine { /// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order. /// /// If the variable is not present in the scope adds it to the list of external variables. /// /// The return value is the offset to be deducted from `ParseState::stack::len()`, /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. /// /// # Return value: `(index, is_func_name)` /// /// * `index`: [`None`] when the variable name is not found in the `stack`, /// otherwise the index value. /// /// * `is_func_name`: `true` if the variable is actually the name of a function /// (in which case it will be converted into a function pointer). #[must_use] fn access_var( &self, state: &mut ParseState, name: &str, _pos: Position, ) -> (Option, bool) { let (index, hit_barrier) = state.find_var(name); #[cfg(not(feature = "no_function"))] let is_func_name = state.lib.values().any(|f| f.name == name); #[cfg(feature = "no_function")] let is_func_name = false; #[cfg(not(feature = "no_closure"))] if state.allow_capture { if !is_func_name && index == 0 && !state.external_vars.iter().any(|v| v.name == name) { let name = self.get_interned_string(name); state.external_vars.push(Ident { name, pos: _pos }); } } else { state.allow_capture = true; } let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten(); (index, is_func_name) } /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. /// All other variants are untouched. #[cfg(not(feature = "no_object"))] #[inline] #[must_use] fn convert_expr_into_property(&self, expr: Expr) -> Expr { match expr { #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("qualified property"), Expr::Variable(x, .., pos) => { let ident = x.1.clone(); let getter = self.get_interned_getter(&ident); let hash_get = calc_fn_hash(None, &getter, 1); let setter = self.get_interned_setter(&ident); let hash_set = calc_fn_hash(None, &setter, 2); Expr::Property( Box::new(((getter, hash_get), (setter, hash_set), ident)), pos, ) } _ => expr, } } /// Parse a function call. fn parse_fn_call( &self, state: &mut ParseState, settings: ParseSettings, id: ImmutableString, no_args: bool, capture_parent_scope: bool, #[cfg(not(feature = "no_module"))] mut namespace: crate::ast::Namespace, ) -> ParseResult { let (token, token_pos) = if no_args { &(Token::RightParen, Position::NONE) } else { state.input.peek().unwrap() }; let mut args = FnArgsVec::new(); match token { // id( Token::EOF => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the arguments list of this function call '{id}'"), ) .into_err(*token_pos)) } // id( Token::LexError(err) => return Err(err.clone().into_err(*token_pos)), // id() Token::RightParen => { if !no_args { eat_token(state.input, &Token::RightParen); } #[cfg(not(feature = "no_module"))] let hash = if namespace.is_empty() { calc_fn_hash(None, &id, 0) } else { let root = namespace.root(); let index = state.find_module(root); let is_global = false; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL; if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } namespace.index = index; calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, 0) }; #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, 0); let hashes = if is_valid_function_name(&id) { FnCallHashes::from_hash(hash) } else { FnCallHashes::from_native_only(hash) }; args.shrink_to_fit(); return Ok(FnCallExpr { name: self.get_interned_string(id), capture_parent_scope, op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, } .into_fn_call_expr(settings.pos)); } // id... _ => (), } let settings = settings.level_up()?; loop { match state.input.peek().unwrap() { // id(...args, ) - handle trailing comma (Token::RightParen, ..) => (), _ => args.push(self.parse_expr(state, settings)?), } match state.input.peek().unwrap() { // id(...args) (Token::RightParen, ..) => { eat_token(state.input, &Token::RightParen); #[cfg(not(feature = "no_module"))] let hash = if namespace.is_empty() { calc_fn_hash(None, &id, args.len()) } else { let root = namespace.root(); let index = state.find_module(root); #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] let is_global = root == crate::engine::KEYWORD_GLOBAL; #[cfg(any(feature = "no_function", feature = "no_module"))] let is_global = false; if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } namespace.index = index; calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, args.len()) }; #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, args.len()); let hashes = if is_valid_function_name(&id) { FnCallHashes::from_hash(hash) } else { FnCallHashes::from_native_only(hash) }; args.shrink_to_fit(); return Ok(FnCallExpr { name: self.get_interned_string(id), capture_parent_scope, op_token: None, #[cfg(not(feature = "no_module"))] namespace, hashes, args, } .into_fn_call_expr(settings.pos)); } // id(...args, (Token::Comma, ..) => { eat_token(state.input, &Token::Comma); } // id(...args (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the arguments list of this function call '{id}'"), ) .into_err(*pos)) } // id(...args (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)), // id(...args ??? (.., pos) => { return Err(PERR::MissingToken( Token::Comma.into(), format!("to separate the arguments to function call '{id}'"), ) .into_err(*pos)) } } } } /// Parse an indexing chain. /// Indexing binds to the right, so this call parses all possible levels of indexing following in the input. #[cfg(not(feature = "no_index"))] fn parse_index_chain( &self, state: &mut ParseState, mut settings: ParseSettings, lhs: Expr, options: ASTFlags, check_types: bool, ) -> ParseResult { fn check_argument_types(lhs: &Expr, idx_expr: &Expr) -> Result<(), ParseError> { // Check types of indexing that cannot be overridden // - arrays, maps, strings, bit-fields match *lhs { Expr::Map(..) => match *idx_expr { // lhs[int] Expr::IntegerConstant(..) => Err(PERR::MalformedIndexExpr( "Object map expects string index, not a number".into(), ) .into_err(idx_expr.start_position())), // lhs[string] Expr::StringConstant(..) | Expr::InterpolatedString(..) => Ok(()), // lhs[float] #[cfg(not(feature = "no_float"))] Expr::FloatConstant(..) => Err(PERR::MalformedIndexExpr( "Object map expects string index, not a float".into(), ) .into_err(idx_expr.start_position())), // lhs[char] Expr::CharConstant(..) => Err(PERR::MalformedIndexExpr( "Object map expects string index, not a character".into(), ) .into_err(idx_expr.start_position())), // lhs[()] Expr::Unit(..) => Err(PERR::MalformedIndexExpr( "Object map expects string index, not ()".into(), ) .into_err(idx_expr.start_position())), // lhs[??? && ???], lhs[??? || ???], lhs[true], lhs[false] Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => { Err(PERR::MalformedIndexExpr( "Object map expects string index, not a boolean".into(), ) .into_err(idx_expr.start_position())) } _ => Ok(()), }, Expr::IntegerConstant(..) | Expr::Array(..) | Expr::StringConstant(..) | Expr::InterpolatedString(..) => match *idx_expr { // lhs[int] Expr::IntegerConstant(..) => Ok(()), // lhs[string] Expr::StringConstant(..) | Expr::InterpolatedString(..) => { Err(PERR::MalformedIndexExpr( "Array, string or bit-field expects numeric index, not a string".into(), ) .into_err(idx_expr.start_position())) } // lhs[float] #[cfg(not(feature = "no_float"))] Expr::FloatConstant(..) => Err(PERR::MalformedIndexExpr( "Array, string or bit-field expects integer index, not a float".into(), ) .into_err(idx_expr.start_position())), // lhs[char] Expr::CharConstant(..) => Err(PERR::MalformedIndexExpr( "Array, string or bit-field expects integer index, not a character".into(), ) .into_err(idx_expr.start_position())), // lhs[()] Expr::Unit(..) => Err(PERR::MalformedIndexExpr( "Array, string or bit-field expects integer index, not ()".into(), ) .into_err(idx_expr.start_position())), // lhs[??? && ???], lhs[??? || ???], lhs[true], lhs[false] Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => { Err(PERR::MalformedIndexExpr( "Array, string or bit-field expects integer index, not a boolean" .into(), ) .into_err(idx_expr.start_position())) } _ => Ok(()), }, _ => Ok(()), } } let idx_expr = self.parse_expr(state, settings.level_up()?)?; if check_types { check_argument_types(&lhs, &idx_expr)?; } // Check if there is a closing bracket match state.input.peek().unwrap() { (Token::RightBracket, ..) => { eat_token(state.input, &Token::RightBracket); // Any more indexing following? match state.input.peek().unwrap() { // If another indexing level, right-bind it (Token::LeftBracket | Token::QuestionBracket, ..) => { let (token, pos) = state.input.next().unwrap(); let prev_pos = settings.pos; settings.pos = pos; let settings = settings.level_up()?; // Recursively parse the indexing chain, right-binding each let options = match token { Token::LeftBracket => ASTFlags::empty(), Token::QuestionBracket => ASTFlags::NEGATED, _ => unreachable!("`[` or `?[`"), }; let idx_expr = self.parse_index_chain(state, settings, idx_expr, options, false)?; // Indexing binds to right Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), options, prev_pos, )) } // Otherwise terminate the indexing chain _ => Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), options | ASTFlags::BREAK, settings.pos, )), } } (Token::LexError(err), pos) => Err(err.clone().into_err(*pos)), (.., pos) => Err(PERR::MissingToken( Token::RightBracket.into(), "for a matching [ in this index expression".into(), ) .into_err(*pos)), } } /// Parse an array literal. #[cfg(not(feature = "no_index"))] fn parse_array_literal( &self, state: &mut ParseState, mut settings: ParseSettings, ) -> ParseResult { // [ ... settings.pos = eat_token(state.input, &Token::LeftBracket); let mut array = ThinVec::new(); loop { const MISSING_RBRACKET: &str = "to end this array literal"; #[cfg(not(feature = "unchecked"))] if self.max_array_size() > 0 && array.len() >= self.max_array_size() { return Err(PERR::LiteralTooLarge( "Size of array literal".into(), self.max_array_size(), ) .into_err(state.input.peek().unwrap().1)); } match state.input.peek().unwrap() { (Token::RightBracket, ..) => { eat_token(state.input, &Token::RightBracket); break; } (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightBracket.into(), MISSING_RBRACKET.into(), ) .into_err(*pos)) } _ => array.push(self.parse_expr(state, settings.level_up()?)?), } match state.input.peek().unwrap() { (Token::Comma, ..) => { eat_token(state.input, &Token::Comma); } (Token::RightBracket, ..) => (), (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightBracket.into(), MISSING_RBRACKET.into(), ) .into_err(*pos)) } (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)), (.., pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) } }; } array.shrink_to_fit(); Ok(Expr::Array(array, settings.pos)) } /// Parse a map literal. #[cfg(not(feature = "no_object"))] fn parse_map_literal( &self, state: &mut ParseState, mut settings: ParseSettings, ) -> ParseResult { // #{ ... settings.pos = eat_token(state.input, &Token::MapStart); let mut map = StaticVec::<(Ident, Expr)>::new(); let mut template = std::collections::BTreeMap::::new(); loop { const MISSING_RBRACE: &str = "to end this object map literal"; match state.input.peek().unwrap() { (Token::RightBrace, ..) => { eat_token(state.input, &Token::RightBrace); break; } (Token::EOF, pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(*pos), ) } _ => (), } let (name, pos) = match state.input.next().unwrap() { (Token::Identifier(..), pos) if settings.has_flag(ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES) => { return Err(PERR::PropertyExpected.into_err(pos)) } (Token::Identifier(s) | Token::StringConstant(s), pos) => { if map.iter().any(|(p, ..)| p.as_str() == s.as_str()) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } (*s, pos) } (Token::InterpolatedString(..), pos) => { return Err(PERR::PropertyExpected.into_err(pos)) } (Token::Reserved(s), pos) if is_valid_identifier(&s) => { return Err(PERR::Reserved(s.to_string()).into_err(pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightBrace.into(), MISSING_RBRACE.into(), ) .into_err(pos)); } (.., pos) if map.is_empty() => { return Err(PERR::MissingToken( Token::RightBrace.into(), MISSING_RBRACE.into(), ) .into_err(pos)); } (.., pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; match state.input.next().unwrap() { (Token::Colon, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::Colon.into(), format!("to follow the property '{name}' in this object map literal"), ) .into_err(pos)) } }; #[cfg(not(feature = "unchecked"))] if self.max_map_size() > 0 && map.len() >= self.max_map_size() { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".into(), self.max_map_size(), ) .into_err(state.input.peek().unwrap().1)); } let expr = self.parse_expr(state, settings.level_up()?)?; template.insert(name.clone(), crate::Dynamic::UNIT); let name = self.get_interned_string(name); map.push((Ident { name, pos }, expr)); match state.input.peek().unwrap() { (Token::Comma, ..) => { eat_token(state.input, &Token::Comma); } (Token::RightBrace, ..) => (), (Token::Identifier(..), pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) } (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)), (.., pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(*pos), ) } } } map.shrink_to_fit(); Ok(Expr::Map((map, template).into(), settings.pos)) } /// Parse a switch expression. fn parse_switch(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // switch ... let settings = settings.level_up_with_position(eat_token(state.input, &Token::Switch))?; let item = self.parse_expr(state, settings)?; match state.input.next().unwrap() { (Token::LeftBrace, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::LeftBrace.into(), "to start a switch block".into(), ) .into_err(pos)) } } let mut expressions = FnArgsVec::::new(); let mut cases = StraightHashMap::::default(); let mut ranges = StaticVec::::new(); let mut def_case = None; let mut def_case_pos = Position::NONE; loop { const MISSING_RBRACE: &str = "to end this switch block"; let (case_expr_list, condition) = match state.input.peek().unwrap() { (Token::RightBrace, ..) => { eat_token(state.input, &Token::RightBrace); break; } (Token::EOF, pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) .into_err(*pos), ) } (Token::Underscore, pos) if def_case.is_none() => { def_case_pos = *pos; eat_token(state.input, &Token::Underscore); let (if_clause, if_pos) = match_token(state.input, &Token::If); if if_clause { return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos)); } ( StaticVec::new_const(), Expr::BoolConstant(true, Position::NONE), ) } _ if def_case.is_some() => { return Err(PERR::WrongSwitchDefaultCase.into_err(def_case_pos)) } _ => { let mut case_expr_list = StaticVec::new_const(); loop { let filter = state.expr_filter; state.expr_filter = |t| t != &Token::Pipe; let expr = self.parse_expr(state, settings); state.expr_filter = filter; match expr { Ok(expr) => case_expr_list.push(expr), Err(err) => { return Err(PERR::ExprExpected("literal".into()).into_err(err.1)) } } if !match_token(state.input, &Token::Pipe).0 { break; } } let condition = if match_token(state.input, &Token::If).0 { ensure_not_statement_expr(state.input, "a boolean")?; let guard = self.parse_expr(state, settings)?.ensure_bool_expr()?; ensure_not_assignment(state.input)?; guard } else { Expr::BoolConstant(true, Position::NONE) }; (case_expr_list, condition) } }; match state.input.next().unwrap() { (Token::DoubleArrow, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::DoubleArrow.into(), "in this switch case".into(), ) .into_err(pos)) } }; let (action_expr, need_comma) = if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { (self.parse_expr(state, settings)?, true) } else { let stmt = self.parse_stmt(state, settings)?; let need_comma = !stmt.is_self_terminated(); let stmt_block: StmtBlock = stmt.into(); (Expr::Stmt(stmt_block.into()), need_comma) }; expressions.push(BinaryExpr { lhs: condition, rhs: action_expr, }); let index = expressions.len() - 1; if case_expr_list.is_empty() { def_case = Some(index); } else { for expr in case_expr_list { let value = expr.get_literal_value().ok_or_else(|| { PERR::ExprExpected("a literal".into()).into_err(expr.start_position()) })?; let mut range_value: Option = None; if let Some(range) = value.read_lock::() { range_value = Some(range.clone().into()); } else if let Some(range) = value.read_lock::() { range_value = Some(range.clone().into()); } if let Some(mut r) = range_value { if !r.is_empty() { r.set_index(index); ranges.push(r); } } else if !ranges.is_empty() { // Check for numeric values after ranges let forbidden = match value { Dynamic(Union::Int(..)) => true, #[cfg(not(feature = "no_float"))] Dynamic(Union::Float(..)) => true, #[cfg(feature = "decimal")] Dynamic(Union::Decimal(..)) => true, _ => false, }; if forbidden { return Err( PERR::WrongSwitchIntegerCase.into_err(expr.start_position()) ); } } let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); cases .entry(hash) .or_insert(CaseBlocksList::new_const()) .push(index); } } match state.input.peek().unwrap() { (Token::Comma, ..) => { eat_token(state.input, &Token::Comma); } (Token::RightBrace, ..) => (), (Token::EOF, pos) => { return Err( PERR::MissingToken(Token::RightParen.into(), MISSING_RBRACE.into()) .into_err(*pos), ) } (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)), (.., pos) if need_comma => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the items in this switch block".into(), ) .into_err(*pos)) } _ => (), } } expressions.shrink_to_fit(); cases.shrink_to_fit(); ranges.shrink_to_fit(); let cases = SwitchCasesCollection { expressions, cases, ranges, def_case, }; Ok(Stmt::Switch((item, cases).into(), settings.pos)) } /// Parse a primary expression. fn parse_primary( &self, state: &mut ParseState, mut settings: ParseSettings, options: ChainingFlags, ) -> ParseResult { let (next_token, next_token_pos) = state.input.peek().unwrap(); settings.pos = *next_token_pos; let root_expr = match next_token { _ if !(state.expr_filter)(next_token) => { return Err(LexError::UnexpectedInput(next_token.to_string()).into_err(settings.pos)) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::Unit => { state.input.next(); Expr::Unit(settings.pos) } Token::IntegerConstant(..) | Token::CharConstant(..) | Token::StringConstant(..) | Token::True | Token::False => match state.input.next().unwrap().0 { Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { Expr::StringConstant(self.get_interned_string(*s), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), token => unreachable!("token is {:?}", token), }, Token::ExclusiveRange | Token::InclusiveRange => Expr::IntegerConstant(0, settings.pos), #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { let x = x.0; state.input.next(); Expr::FloatConstant(x, settings.pos) } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { let x = x.0; state.input.next(); Expr::DynamicConstant(Box::new(x.into()), settings.pos) } // { - block statement as expression Token::LeftBrace if settings.has_option(LangOptions::STMT_EXPR) => { match self.parse_block(state, settings.level_up()?)? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), } } // ( - grouped expression Token::LeftParen => { settings.pos = eat_token(state.input, &Token::LeftParen); let expr = self.parse_expr(state, settings.level_up()?)?; match state.input.next().unwrap() { // ( ... ) (Token::RightParen, ..) => expr, // ( (Token::LexError(err), pos) => return Err(err.into_err(pos)), // ( ... ??? (.., pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), "for a matching ( in this expression".into(), ) .into_err(pos)) } } } // If statement is allowed to act as expressions Token::If if settings.has_option(LangOptions::IF_EXPR) => { Expr::Stmt(Box::new(self.parse_if(state, settings.level_up()?)?.into())) } // Loops are allowed to act as expressions Token::While | Token::Loop if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => { Expr::Stmt(Box::new( self.parse_while_loop(state, settings.level_up()?)?.into(), )) } Token::Do if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => { Expr::Stmt(Box::new(self.parse_do(state, settings.level_up()?)?.into())) } Token::For if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => { Expr::Stmt(Box::new( self.parse_for(state, settings.level_up()?)?.into(), )) } // Switch statement is allowed to act as expressions Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new( self.parse_switch(state, settings.level_up()?)?.into(), )), // | ... #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) && state.lib.len() >= self.max_functions() => { return Err(PERR::TooManyFunctions.into_err(settings.pos)); } #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => { self.parse_anon_fn(state, settings, false)? } // Interpolated string Token::InterpolatedString(..) => { let mut segments = ThinVec::new(); let settings = settings.level_up()?; match state.input.next().unwrap() { (Token::InterpolatedString(s), ..) if s.is_empty() => (), (Token::InterpolatedString(s), pos) => { segments.push(Expr::StringConstant(self.get_interned_string(*s), pos)) } token => { unreachable!("Token::InterpolatedString expected but gets {:?}", token) } } loop { let expr = match self.parse_block(state, settings)? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }; match expr { Expr::StringConstant(s, ..) if s.is_empty() => (), _ => segments.push(expr), } // Make sure to parse the following as text state.tokenizer_control.borrow_mut().is_within_text = true; match state.input.next().unwrap() { (Token::StringConstant(s), pos) => { if !s.is_empty() { segments .push(Expr::StringConstant(self.get_interned_string(*s), pos)); } // End the interpolated string if it is terminated by a back-tick. break; } (Token::InterpolatedString(s), pos) => { if !s.is_empty() { segments .push(Expr::StringConstant(self.get_interned_string(*s), pos)); } } (Token::LexError(err), pos) => match *err { LexError::UnterminatedString | LexError::StringTooLong(_) => { return Err(err.into_err(pos)) } _ => unreachable!("improper lex error: {:?}", err), }, (token, ..) => unreachable!( "string within an interpolated string literal expected but gets {:?}", token ), } } if segments.is_empty() { Expr::StringConstant(self.get_interned_string(""), settings.pos) } else { segments.shrink_to_fit(); Expr::InterpolatedString(segments, settings.pos) } } // Array literal #[cfg(not(feature = "no_index"))] Token::LeftBracket => self.parse_array_literal(state, settings.level_up()?)?, // Map literal #[cfg(not(feature = "no_object"))] Token::MapStart => self.parse_map_literal(state, settings.level_up()?)?, // Custom syntax. #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) if self.custom_syntax.contains_key(&**key) => { let (key, syntax) = self.custom_syntax.get_key_value(&**key).unwrap(); let (.., pos) = state.input.next().unwrap(); let settings = settings.level_up_with_position(pos)?; self.parse_custom_syntax(state, settings, key, syntax)? } // Identifier Token::Identifier(..) => { #[cfg(not(feature = "no_module"))] let ns = crate::ast::Namespace::NONE; let s = match state.input.next().unwrap() { (Token::Identifier(s), ..) => s, token => unreachable!("Token::Identifier expected but gets {:?}", token), }; match state.input.peek().unwrap() { // Function call (Token::LeftParen | Token::Bang | Token::Unit, _) => { // Once the identifier consumed we must enable next variables capturing state.allow_capture = true; Expr::Variable( #[cfg(not(feature = "no_module"))] (None, self.get_interned_string(*s), ns, 0).into(), #[cfg(feature = "no_module")] (None, self.get_interned_string(*s)).into(), None, settings.pos, ) } // Namespace qualification #[cfg(not(feature = "no_module"))] (token @ Token::DoubleColon, pos) => { if options.intersects(ChainingFlags::DISALLOW_NAMESPACES) { return Err(LexError::ImproperSymbol( token.literal_syntax().into(), String::new(), ) .into_err(*pos)); } // Once the identifier consumed we must enable next variables capturing state.allow_capture = true; let name = self.get_interned_string(*s); Expr::Variable((None, name, ns, 0).into(), None, settings.pos) } // Normal variable access _ => { let (index, is_func) = self.access_var(state, &s, settings.pos); if !options.intersects(ChainingFlags::PROPERTY) && !is_func && index.is_none() && settings.has_option(LangOptions::STRICT_VAR) && !state .external_constants .map_or(false, |scope| scope.contains(&s)) { return Err( PERR::VariableUndefined(s.to_string()).into_err(settings.pos) ); } let short_index = index .and_then(|x| u8::try_from(x.get()).ok()) .and_then(NonZeroU8::new); let name = self.get_interned_string(*s); Expr::Variable( #[cfg(not(feature = "no_module"))] (index, name, ns, 0).into(), #[cfg(feature = "no_module")] (index, name).into(), short_index, settings.pos, ) } } } // Reserved keyword or symbol Token::Reserved(..) => { #[cfg(not(feature = "no_module"))] let ns = crate::ast::Namespace::NONE; let s = match state.input.next().unwrap() { (Token::Reserved(s), ..) => s, token => unreachable!("Token::Reserved expected but gets {:?}", token), }; match state.input.peek().unwrap().0 { // Function call is allowed to have reserved keyword Token::LeftParen | Token::Bang | Token::Unit if is_reserved_keyword_or_symbol(&s).1 => { Expr::Variable( #[cfg(not(feature = "no_module"))] (None, self.get_interned_string(*s), ns, 0).into(), #[cfg(feature = "no_module")] (None, self.get_interned_string(*s)).into(), None, settings.pos, ) } // Access to `this` as a variable #[cfg(not(feature = "no_function"))] _ if *s == crate::engine::KEYWORD_THIS => { // OK within a function scope if settings.has_flag(ParseSettingFlags::FN_SCOPE) { Expr::ThisPtr(settings.pos) } else { // Cannot access to `this` as a variable not in a function scope let msg = format!("'{s}' can only be used in functions"); return Err( LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos) ); } } _ => return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)), } } Token::LexError(..) => match state.input.next().unwrap() { (Token::LexError(err), ..) => return Err(err.into_err(settings.pos)), token => unreachable!("Token::LexError expected but gets {:?}", token), }, _ => { return Err(LexError::UnexpectedInput(next_token.to_string()).into_err(settings.pos)) } }; if !(state.expr_filter)(&state.input.peek().unwrap().0) { return Ok(root_expr); } self.parse_postfix(state, settings, root_expr, ChainingFlags::empty()) } /// Tail processing of all possible postfix operators of a primary expression. fn parse_postfix( &self, state: &mut ParseState, mut settings: ParseSettings, mut lhs: Expr, _options: ChainingFlags, ) -> ParseResult { // Break just in case `lhs` is `Expr::Dot` or `Expr::Index` let mut _parent_options = ASTFlags::BREAK; // Tail processing all possible postfix operators loop { let (tail_token, ..) = state.input.peek().unwrap(); if !lhs.is_valid_postfix(tail_token) { break; } let (tail_token, tail_pos) = state.input.next().unwrap(); settings.pos = tail_pos; lhs = match (lhs, tail_token) { // Qualified function call with ! #[cfg(not(feature = "no_module"))] (Expr::Variable(x, ..), Token::Bang) if !x.2.is_empty() => { return match state.input.peek().unwrap() { (Token::LeftParen | Token::Unit, ..) => { Err(LexError::UnexpectedInput(Token::Bang.into()).into_err(tail_pos)) } _ => Err(LexError::ImproperSymbol( "!".into(), "'!' cannot be used to call module functions".into(), ) .into_err(tail_pos)), }; } // Function call with ! (Expr::Variable(x, .., pos), Token::Bang) => { match state.input.peek().unwrap() { (Token::LeftParen | Token::Unit, ..) => (), (_, pos) => { return Err(PERR::MissingToken( Token::LeftParen.into(), "to start arguments list of function call".into(), ) .into_err(*pos)) } } let no_args = state.input.next().unwrap().0 == Token::Unit; #[cfg(not(feature = "no_module"))] let (_, name, ns, ..) = *x; #[cfg(feature = "no_module")] let (_, name) = *x; settings.pos = pos; self.parse_fn_call( state, settings, name, no_args, true, #[cfg(not(feature = "no_module"))] ns, )? } // Function call (Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => { #[cfg(not(feature = "no_module"))] let (_, name, ns, ..) = *x; #[cfg(feature = "no_module")] let (_, name) = *x; let no_args = t == Token::Unit; settings.pos = pos; self.parse_fn_call( state, settings, name, no_args, false, #[cfg(not(feature = "no_module"))] ns, )? } // Disallowed module separator #[cfg(not(feature = "no_module"))] (_, token @ Token::DoubleColon) if _options.intersects(ChainingFlags::DISALLOW_NAMESPACES) => { return Err(LexError::ImproperSymbol( token.literal_syntax().into(), String::new(), ) .into_err(tail_pos)) } // module access #[cfg(not(feature = "no_module"))] (Expr::Variable(x, .., pos), Token::DoubleColon) => { let (id2, pos2) = parse_var_name(state.input)?; let (_, name, mut namespace, ..) = *x; let var_name_def = Ident { name, pos }; namespace.path.push(var_name_def); let var_name = self.get_interned_string(id2); Expr::Variable((None, var_name, namespace, 0).into(), None, pos2) } // Indexing #[cfg(not(feature = "no_index"))] (expr, token @ (Token::LeftBracket | Token::QuestionBracket)) => { let opt = match token { Token::LeftBracket => ASTFlags::empty(), Token::QuestionBracket => ASTFlags::NEGATED, _ => unreachable!("`[` or `?[`"), }; let settings = settings.level_up()?; self.parse_index_chain(state, settings, expr, opt, true)? } // Property access #[cfg(not(feature = "no_object"))] (expr, op @ (Token::Period | Token::Elvis)) => { // Expression after dot must start with an identifier match state.input.peek().unwrap() { (Token::Identifier(..), ..) => { // Prevents capturing of the object properties as vars: xxx. state.allow_capture = false; } (Token::Reserved(s), ..) if is_reserved_keyword_or_symbol(s).2 => (), (Token::Reserved(s), pos) => { return Err(PERR::Reserved(s.to_string()).into_err(*pos)) } (.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)), } let op_flags = match op { Token::Period => ASTFlags::empty(), Token::Elvis => ASTFlags::NEGATED, _ => unreachable!("`.` or `?.`"), }; let options = ChainingFlags::PROPERTY | ChainingFlags::DISALLOW_NAMESPACES; let rhs = self.parse_primary(state, settings.level_up()?, options)?; self.make_dot_expr(expr, rhs, _parent_options, op_flags, tail_pos)? } // Unknown postfix operator (expr, token) => { unreachable!("unknown postfix operator '{}' for {:?}", token, expr) } }; // The chain is now extended _parent_options = ASTFlags::empty(); } // Optimize chain where the root expression is another chain #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] if matches!(lhs, Expr::Index(ref x, ..) | Expr::Dot(ref x, ..) if matches!(x.lhs, Expr::Index(..) | Expr::Dot(..))) { optimize_combo_chain(&mut lhs); } // Cache the hash key for namespace-qualified variables #[cfg(not(feature = "no_module"))] let namespaced_variable = match lhs { Expr::Variable(ref mut x, ..) if !x.2.is_empty() => Some(&mut **x), Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs { Expr::Variable(ref mut x, ..) if !x.2.is_empty() => Some(&mut **x), _ => None, }, _ => None, }; #[cfg(not(feature = "no_module"))] if let Some((.., name, namespace, hash)) = namespaced_variable { if !namespace.is_empty() { *hash = crate::calc_var_hash(namespace.path.iter().map(Ident::as_str), name); #[cfg(not(feature = "no_module"))] { let root = namespace.root(); let index = state.find_module(root); let is_global = false; #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL; if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } namespace.index = index; } } } // Make sure identifiers are valid Ok(lhs) } /// Parse a potential unary operator. fn parse_unary( &self, state: &mut ParseState, mut settings: ParseSettings, ) -> ParseResult { let (token, token_pos) = state.input.peek().unwrap(); if !(state.expr_filter)(token) { return Err(LexError::UnexpectedInput(token.to_string()).into_err(*token_pos)); } settings.pos = *token_pos; match token { // -expr Token::Minus | Token::UnaryMinus => { let token = token.clone(); let pos = eat_token(state.input, &token); match self.parse_unary(state, settings.level_up()?)? { // Negative integer Expr::IntegerConstant(num, ..) => num .checked_neg() .map(|i| Expr::IntegerConstant(i, pos)) .or_else(|| { #[cfg(not(feature = "no_float"))] return Some(Expr::FloatConstant((-(num as crate::FLOAT)).into(), pos)); #[cfg(feature = "no_float")] return None; }) .ok_or_else(|| LexError::MalformedNumber(format!("-{num}")).into_err(pos)), // Negative float #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x, ..) => Ok(Expr::FloatConstant((-(*x)).into(), pos)), // Call negative function expr => Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string("-"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "-", 1)), args: IntoIterator::into_iter([expr]).collect(), op_token: Some(token), capture_parent_scope: false, } .into_fn_call_expr(pos)), } } // +expr Token::Plus | Token::UnaryPlus => { let token = token.clone(); let pos = eat_token(state.input, &token); match self.parse_unary(state, settings.level_up()?)? { expr @ Expr::IntegerConstant(..) => Ok(expr), #[cfg(not(feature = "no_float"))] expr @ Expr::FloatConstant(..) => Ok(expr), // Call plus function expr => Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string("+"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "+", 1)), args: IntoIterator::into_iter([expr]).collect(), op_token: Some(token), capture_parent_scope: false, } .into_fn_call_expr(pos)), } } // !expr Token::Bang => { let token = token.clone(); let pos = eat_token(state.input, &Token::Bang); Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string("!"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "!", 1)), args: { let expr = self.parse_unary(state, settings.level_up()?)?; IntoIterator::into_iter([expr]).collect() }, op_token: Some(token), capture_parent_scope: false, } .into_fn_call_expr(pos)) } // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens _ => self.parse_primary(state, settings, ChainingFlags::empty()), } } /// Make an assignment statement. fn make_assignment_stmt( op: Option, state: &mut ParseState, lhs: Expr, rhs: Expr, op_pos: Position, ) -> ParseResult { #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) if parent_is_dot => { match x.lhs { Expr::Property(..) if !options.intersects(ASTFlags::BREAK) => { check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))) } Expr::Property(..) => None, // Anything other than a property after dotting (e.g. a method call) is not an l-value ref e => Some(e.position()), } } Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) => match x.lhs { Expr::Property(..) => unreachable!("unexpected Expr::Property in indexing"), _ if !options.intersects(ASTFlags::BREAK) => { check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))) } _ => None, }, Expr::Property(..) if parent_is_dot => None, Expr::Property(..) => unreachable!("unexpected Expr::Property in indexing"), e if parent_is_dot => Some(e.position()), _ => None, } } let op_info = op.map_or_else( || OpAssignment::new_assignment(op_pos), |op| OpAssignment::new_op_assignment_from_token(op, op_pos), ); match lhs { // this = rhs Expr::ThisPtr(_) => Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())), // var (non-indexed) = rhs Expr::Variable(ref x, None, _) if x.0.is_none() => { Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { let (index, name, ..) = &**x; let index = i.map_or_else( || index.expect("long or short index must be `Some`").get(), |n| n.get() as usize, ); match state .stack .get_mut_by_index(state.stack.len() - index) .access_mode() { AccessMode::ReadWrite => { Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // Constant values cannot be assigned to AccessMode::ReadOnly => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos)) } } } // xxx[???]... = rhs, xxx.prop... = rhs Expr::Index(ref x, options, ..) | Expr::Dot(ref x, options, ..) => { let valid_lvalue = if options.intersects(ASTFlags::BREAK) { None } else { check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(..))) }; if let Some(err_pos) = valid_lvalue { Err(PERR::AssignmentToInvalidLHS(String::new()).into_err(err_pos)) } else { match x.lhs { // var[???] = rhs, this[???] = rhs, var.??? = rhs, this.??? = rhs Expr::Variable(..) | Expr::ThisPtr(..) => { Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // expr[???] = rhs, expr.??? = rhs ref expr => { Err(PERR::AssignmentToInvalidLHS(String::new()) .into_err(expr.position())) } } } } // const_expr = rhs ref expr if expr.is_constant() => { Err(PERR::AssignmentToConstant(String::new()).into_err(lhs.start_position())) } // ??? && ??? = rhs, ??? || ??? = rhs, xxx ?? xxx = rhs Expr::And(..) | Expr::Or(..) | Expr::Coalesce(..) if !op_info.is_op_assignment() => { Err(LexError::ImproperSymbol( Token::Equals.literal_syntax().into(), "Possibly a typo of '=='?".into(), ) .into_err(op_pos)) } // expr = rhs _ => Err(PERR::AssignmentToInvalidLHS(String::new()).into_err(lhs.position())), } } /// Make a dot expression. #[cfg(not(feature = "no_object"))] fn make_dot_expr( &self, lhs: Expr, rhs: Expr, parent_options: ASTFlags, op_flags: ASTFlags, op_pos: Position, ) -> ParseResult { match (lhs, rhs) { // lhs[...][...].rhs (Expr::Index(mut x, options, pos), rhs) if !parent_options.intersects(ASTFlags::BREAK) => { let options = options | parent_options; x.rhs = self.make_dot_expr(x.rhs, rhs, options, op_flags, op_pos)?; Ok(Expr::Index(x, ASTFlags::empty(), pos)) } // lhs.module::id - syntax error #[cfg(not(feature = "no_module"))] (.., Expr::Variable(x, ..)) if !x.2.is_empty() => unreachable!("lhs.ns::id"), // lhs.id (lhs, var_expr @ Expr::Variable(..)) => { let rhs = self.convert_expr_into_property(var_expr); Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) } // lhs.prop (lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot( BinaryExpr { lhs, rhs: prop }.into(), op_flags, op_pos, )), // lhs.nnn::func(...) - syntax error #[cfg(not(feature = "no_module"))] (.., Expr::FnCall(f, ..)) if f.is_qualified() => unreachable!("lhs.ns::func()"), // lhs.Fn() or lhs.eval() (.., Expr::FnCall(f, func_pos)) if f.args.is_empty() && matches!( &*f.name, crate::engine::KEYWORD_FN_PTR | crate::engine::KEYWORD_EVAL ) => { let err_msg = format!( "'{}' should not be called in method style. Try {}(...);", f.name, f.name ); Err(LexError::ImproperSymbol(f.name.to_string(), err_msg).into_err(func_pos)) } // lhs.func!(...) (.., Expr::FnCall(f, func_pos)) if f.capture_parent_scope => { Err(PERR::MalformedCapture( "method-call style does not support running within the caller's scope".into(), ) .into_err(func_pos)) } // lhs.func(...) (lhs, Expr::FnCall(mut f, func_pos)) => { // Recalculate hash let args_len = f.args.len() + 1; f.hashes = if is_valid_function_name(&f.name) { #[cfg(not(feature = "no_function"))] { FnCallHashes::from_script_and_native( calc_fn_hash(None, &f.name, args_len - 1), calc_fn_hash(None, &f.name, args_len), ) } #[cfg(feature = "no_function")] { FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len)) } } else { FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len)) }; let rhs = Expr::MethodCall(f, func_pos); Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) } // lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs] (lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => { let (x, options, pos, is_dot) = match rhs { Expr::Dot(x, options, pos) => (x, options, pos, true), Expr::Index(x, options, pos) => (x, options, pos, false), expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr), }; match x.lhs { // lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("lhs.ns::id..."), // lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error #[cfg(not(feature = "no_module"))] Expr::FnCall(f, ..) if f.is_qualified() => { unreachable!("lhs.ns::func()...") } // lhs.id.dot_rhs or lhs.id[idx_rhs] Expr::Variable(..) | Expr::Property(..) => { let new_binary = BinaryExpr { lhs: self.convert_expr_into_property(x.lhs), rhs: x.rhs, } .into(); let rhs = if is_dot { Expr::Dot(new_binary, options, pos) } else { Expr::Index(new_binary, options, pos) }; Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) } // lhs.func().dot_rhs or lhs.func()[idx_rhs] Expr::FnCall(mut f, func_pos) => { // Recalculate hash let args_len = f.args.len() + 1; f.hashes = if is_valid_function_name(&f.name) { #[cfg(not(feature = "no_function"))] { FnCallHashes::from_script_and_native( calc_fn_hash(None, &f.name, args_len - 1), calc_fn_hash(None, &f.name, args_len), ) } #[cfg(feature = "no_function")] { FnCallHashes::from_native_only(calc_fn_hash( None, &f.name, args_len, )) } } else { FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len)) }; let new_lhs = BinaryExpr { lhs: Expr::MethodCall(f, func_pos), rhs: x.rhs, } .into(); let rhs = if is_dot { Expr::Dot(new_lhs, options, pos) } else { Expr::Index(new_lhs, options, pos) }; Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) } expr => unreachable!("invalid dot expression: {:?}", expr), } } // lhs.rhs (.., rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())), } } /// Parse a binary expression (if any). fn parse_binary_op( &self, state: &mut ParseState, mut settings: ParseSettings, parent_precedence: Option, lhs: Expr, ) -> ParseResult { settings.pos = lhs.position(); let mut root = lhs; loop { let (current_op, current_pos) = state.input.peek().unwrap(); if !(state.expr_filter)(current_op) { return Ok(root); } let precedence = match current_op { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos)) } _ => current_op.precedence(), }; let bind_right = current_op.is_bind_right(); // Bind left to the parent lhs expression if precedence is higher // If same precedence, then check if the operator binds right if precedence < parent_precedence || (precedence == parent_precedence && !bind_right) { return Ok(root); } let (op_token, pos) = state.input.next().unwrap(); // Parse the RHS let rhs = match op_token { Token::DoubleQuestion if matches!( state.input.peek().unwrap().0, Token::Break | Token::Continue | Token::Return | Token::Throw ) => { let stmt = self.parse_stmt(state, settings)?; let block: StmtBlock = stmt.into(); Expr::Stmt(block.into()) } // [xxx..] | (xxx..) | {xxx..} | xxx.., | xxx..; | xxx.. => // [xxx..=] | (xxx..=) | {xxx..=} | xxx..=, | xxx..=; | xxx..= => Token::ExclusiveRange | Token::InclusiveRange if matches!( state.input.peek().unwrap().0, Token::RightBracket | Token::RightParen | Token::RightBrace | Token::Comma | Token::SemiColon | Token::DoubleArrow ) => { let (_, next_pos) = state.input.peek().unwrap(); Expr::Unit(*next_pos) } _ => self.parse_unary(state, settings)?, }; let (next_op, next_pos) = state.input.peek().unwrap(); let next_precedence = match next_op { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords .get(&**c) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c) => { return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos)) } _ => next_op.precedence(), }; // Bind to right if the next operator has higher precedence // If same precedence, then check if the operator binds right let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { self.parse_binary_op(state, settings.level_up()?, precedence, rhs)? } else { // Otherwise bind to left (even if next operator has the same precedence) rhs }; settings = settings.level_up()?; settings.pos = pos; let op = op_token.to_string(); let hash = calc_fn_hash(None, &op, 2); let native_only = !is_valid_function_name(&op); let mut op_base = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string(&op), hashes: FnCallHashes::from_native_only(hash), args: IntoIterator::into_iter([root, rhs]).collect(), op_token: native_only.then(|| op_token.clone()), capture_parent_scope: false, }; root = match op_token { Token::Or => { let rhs = op_base.args[1].take().ensure_bool_expr()?; let lhs = op_base.args[0].take().ensure_bool_expr()?; Expr::Or(BinaryExpr { lhs, rhs }.into(), pos) } Token::And => { let rhs = op_base.args[1].take().ensure_bool_expr()?; let lhs = op_base.args[0].take().ensure_bool_expr()?; Expr::And(BinaryExpr { lhs, rhs }.into(), pos) } Token::DoubleQuestion => { let rhs = op_base.args[1].take(); let lhs = op_base.args[0].take(); Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos) } Token::In | Token::NotIn => { // Swap the arguments let (lhs, rhs) = op_base.args.split_first_mut().unwrap(); std::mem::swap(lhs, &mut rhs[0]); // Convert into a call to `contains` op_base.hashes = FnCallHashes::from_hash(calc_fn_hash(None, OP_CONTAINS, 2)); op_base.name = self.get_interned_string(OP_CONTAINS); let fn_call = op_base.into_fn_call_expr(pos); if op_token == Token::In { fn_call } else { // Put a `!` call in front let not_base = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string(OP_NOT), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)), args: IntoIterator::into_iter([fn_call]).collect(), op_token: Some(Token::Bang), capture_parent_scope: false, }; not_base.into_fn_call_expr(pos) } } Token::ExclusiveRange | Token::InclusiveRange => op_base.into_fn_call_expr(pos), #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(s) if self.custom_keywords.contains_key(&*s) => { op_base.hashes = if native_only { FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2)) } else { FnCallHashes::from_hash(calc_fn_hash(None, &s, 2)) }; op_base.into_fn_call_expr(pos) } _ => op_base.into_fn_call_expr(pos), }; } } /// Parse a custom syntax. #[cfg(not(feature = "no_custom_syntax"))] fn parse_custom_syntax( &self, state: &mut ParseState, mut settings: ParseSettings, key: impl Into, syntax: &crate::api::custom_syntax::CustomSyntax, ) -> ParseResult { #[allow(clippy::wildcard_imports)] use crate::api::custom_syntax::markers::*; const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); let pos = settings.pos; let mut inputs = FnArgsVec::new(); let mut segments = FnArgsVec::new(); let mut tokens = FnArgsVec::new(); // Adjust the variables stack if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. let marker = self.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER); state.stack.push(marker, ()); } let mut user_state = Dynamic::UNIT; let parse_func = &*syntax.parse; let mut required_token: ImmutableString = key.into(); tokens.push(required_token.clone()); segments.push(required_token.clone()); loop { let (fwd_token, fwd_pos) = state.input.peek().unwrap(); settings.pos = *fwd_pos; let settings = settings.level_up()?; required_token = match parse_func(&segments, &fwd_token.to_string(), &mut user_state) { Ok(Some(seg)) if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => { inputs.push(Expr::StringConstant(self.get_interned_string(seg), pos)); break; } Ok(Some(seg)) => seg, Ok(None) => break, Err(err) => return Err(err.0.into_err(settings.pos)), }; match required_token.as_str() { CUSTOM_SYNTAX_MARKER_IDENT => { let (name, pos) = parse_var_name(state.input)?; let name = self.get_interned_string(name); segments.push(name.clone()); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable( #[cfg(not(feature = "no_module"))] (None, name, crate::ast::Namespace::NONE, 0).into(), #[cfg(feature = "no_module")] (None, name).into(), None, pos, )); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = match state.input.next().unwrap() { // Standard symbol (token, pos) if token.is_standard_symbol() => { Ok((token.literal_syntax().into(), pos)) } // Reserved symbol (Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => { Ok((*s, pos)) } // Bad symbol (Token::LexError(err), pos) => Err(err.into_err(pos)), // Not a symbol (.., pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)), }?; let symbol = self.get_interned_string(symbol); segments.push(symbol.clone()); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL)); inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(self.parse_expr(state, settings)?); let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BLOCK => match self.parse_block(state, settings)? { block @ Stmt::Block(..) => { inputs.push(Expr::Stmt(Box::new(block.into()))); let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone()); tokens.push(keyword); } stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }, #[cfg(not(feature = "no_function"))] CUSTOM_SYNTAX_MARKER_FUNC => { let skip = match fwd_token { Token::Or | Token::Pipe => false, Token::LeftBrace => true, _ => { return Err(PERR::MissingSymbol("Expecting '{' or '|'".into()) .into_err(*fwd_pos)) } }; inputs.push(self.parse_anon_fn(state, settings, skip)?); let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_FUNC); segments.push(keyword.clone()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BOOL => match state.input.next().unwrap() { (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); segments.push(self.get_interned_string(b.literal_syntax())); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_BOOL)); } (.., pos) => { return Err( PERR::MissingSymbol("Expecting 'true' or 'false'".into()).into_err(pos) ) } }, CUSTOM_SYNTAX_MARKER_INT => match state.input.next().unwrap() { (Token::IntegerConstant(i), pos) => { inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_INT)); } (.., pos) => { return Err( PERR::MissingSymbol("Expecting an integer number".into()).into_err(pos) ) } }, #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT => match state.input.next().unwrap() { (Token::FloatConstant(f), pos) => { inputs.push(Expr::FloatConstant(f.0, pos)); segments.push(f.1.into()); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { return Err( PERR::MissingSymbol("Expecting a floating-point number".into()) .into_err(pos), ) } }, CUSTOM_SYNTAX_MARKER_STRING => match state.input.next().unwrap() { (Token::StringConstant(s), pos) => { let s = self.get_interned_string(*s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); } (.., pos) => { return Err(PERR::MissingSymbol("Expecting a string".into()).into_err(pos)) } }, s => match state.input.next().unwrap() { (Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::Identifier(t) | Token::Reserved(t) | Token::Custom(t), ..) if *t == s => { segments.push(required_token.clone()); tokens.push(required_token.clone()); } (t, ..) if t.is_literal() && t.literal_syntax() == s => { segments.push(required_token.clone()); tokens.push(required_token.clone()); } (.., pos) => { return Err(PERR::MissingToken( s.into(), format!("for '{}' expression", segments[0]), ) .into_err(pos)) } }, } } inputs.shrink_to_fit(); tokens.shrink_to_fit(); let self_terminated = matches!( &*required_token, // It is self-terminating if the last symbol is a block CUSTOM_SYNTAX_MARKER_BLOCK | // If the last symbol is `;` or `}`, it is self-terminating KEYWORD_SEMICOLON | KEYWORD_CLOSE_BRACE ); // It is self-terminating if the last symbol is a block #[cfg(not(feature = "no_function"))] let self_terminated = required_token == CUSTOM_SYNTAX_MARKER_FUNC || self_terminated; Ok(Expr::Custom( crate::ast::CustomExpr { inputs, tokens, state: user_state, scope_may_be_changed: syntax.scope_may_be_changed, self_terminated, } .into(), pos, )) } /// Parse an expression. fn parse_expr(&self, state: &mut ParseState, mut settings: ParseSettings) -> ParseResult { settings.pos = state.input.peek().unwrap().1; // Parse expression normally. let precedence = Precedence::new(1); let settings = settings.level_up()?; let lhs = self.parse_unary(state, settings)?; self.parse_binary_op(state, settings, precedence, lhs) } /// Parse an if statement. fn parse_if(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // if ... let settings = settings.level_up_with_position(eat_token(state.input, &Token::If))?; // if guard { if_body } ensure_not_statement_expr(state.input, "a boolean")?; let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?; ensure_not_assignment(state.input)?; let body = self.parse_block(state, settings)?.into(); // if guard { if_body } else ... let branch = if match_token(state.input, &Token::Else).0 { match state.input.peek().unwrap() { // if guard { if_body } else if ... (Token::If, ..) => self.parse_if(state, settings)?, // if guard { if_body } else { else-body } _ => self.parse_block(state, settings)?, } } else { Stmt::Noop(Position::NONE) } .into(); Ok(Stmt::If( FlowControl { expr, body, branch }.into(), settings.pos, )) } /// Parse a while loop. fn parse_while_loop( &self, state: &mut ParseState, settings: ParseSettings, ) -> ParseResult { let mut settings = settings.level_up()?; // while|loops ... let (expr, token_pos) = match state.input.next().unwrap() { (Token::While, pos) => { ensure_not_statement_expr(state.input, "a boolean")?; let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?; ensure_not_assignment(state.input)?; (expr, pos) } (Token::Loop, pos) => (Expr::Unit(Position::NONE), pos), token => unreachable!("Token::While or Token::Loop expected but gets {:?}", token), }; settings.pos = token_pos; settings.flags |= ParseSettingFlags::BREAKABLE; let body = self.parse_block(state, settings)?.into(); let branch = StmtBlock::NONE; Ok(Stmt::While( FlowControl { expr, body, branch }.into(), settings.pos, )) } /// Parse a do loop. fn parse_do(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // do ... let mut settings = settings.level_up_with_position(eat_token(state.input, &Token::Do))?; let orig_breakable = settings.has_flag(ParseSettingFlags::BREAKABLE); settings.flags |= ParseSettingFlags::BREAKABLE; // do { body } [while|until] guard let body = self.parse_block(state, settings)?.into(); let negated = match state.input.next().unwrap() { (Token::While, ..) => ASTFlags::empty(), (Token::Until, ..) => ASTFlags::NEGATED, (.., pos) => { return Err( PERR::MissingToken(Token::While.into(), "for the do statement".into()) .into_err(pos), ) } }; if !orig_breakable { settings.flags.remove(ParseSettingFlags::BREAKABLE); } ensure_not_statement_expr(state.input, "a boolean")?; let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?; ensure_not_assignment(state.input)?; let branch = StmtBlock::NONE; Ok(Stmt::Do( FlowControl { expr, body, branch }.into(), negated, settings.pos, )) } /// Parse a for loop. fn parse_for(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // for ... let mut settings = settings.level_up_with_position(eat_token(state.input, &Token::For))?; // for name ... let (name, name_pos, counter_name, counter_pos) = if match_token(state.input, &Token::LeftParen).0 { // ( name, counter ) let (name, name_pos) = parse_var_name(state.input)?; let (has_comma, pos) = match_token(state.input, &Token::Comma); if !has_comma { return Err(PERR::MissingToken( Token::Comma.into(), "after the iteration variable name".into(), ) .into_err(pos)); } let (counter_name, counter_pos) = parse_var_name(state.input)?; if counter_name == name { return Err(PERR::DuplicatedVariable(counter_name.into()).into_err(counter_pos)); } let (has_close_paren, pos) = match_token(state.input, &Token::RightParen); if !has_close_paren { return Err(PERR::MissingToken( Token::RightParen.into(), "to close the iteration variable".into(), ) .into_err(pos)); } (name, name_pos, Some(counter_name), counter_pos) } else { // name let (name, name_pos) = parse_var_name(state.input)?; (name, name_pos, None, Position::NONE) }; // for name in ... match state.input.next().unwrap() { (Token::In, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::In.into(), "after the iteration variable".into(), ) .into_err(pos)) } } // for name in expr { body } ensure_not_statement_expr(state.input, "a boolean")?; let expr = self.parse_expr(state, settings)?.ensure_iterable()?; let counter_var = counter_name.map(|counter_name| Ident { name: self.get_interned_string(counter_name), pos: counter_pos, }); let loop_var = Ident { name: self.get_interned_string(name), pos: name_pos, }; let prev_stack_len = { let prev_stack_len = state.stack.len(); if let Some(ref counter_var) = counter_var { state.stack.push(counter_var.name.clone(), ()); } state.stack.push(&loop_var.name, ()); prev_stack_len }; settings.flags |= ParseSettingFlags::BREAKABLE; let body = self.parse_block(state, settings)?.into(); state.stack.rewind(prev_stack_len); let branch = StmtBlock::NONE; Ok(Stmt::For( Box::new((loop_var, counter_var, FlowControl { expr, body, branch })), settings.pos, )) } /// Parse a variable definition statement. fn parse_let( &self, state: &mut ParseState, mut settings: ParseSettings, access: AccessMode, is_export: bool, ) -> ParseResult { // let/const... (specified in `var_type`) settings.pos = state.input.next().unwrap().1; // let name ... let (name, pos) = parse_var_name(state.input)?; if !self.allow_shadowing() && state.stack.get(&name).is_some() { return Err(PERR::VariableExists(name.into()).into_err(pos)); } if let Some(ref filter) = self.def_var_filter { let will_shadow = state.stack.get(&name).is_some(); let global = state .global .get_or_insert_with(|| self.new_global_runtime_state().into()); global.level = settings.level; let is_const = access == AccessMode::ReadOnly; let info = VarDefInfo::new(&name, is_const, settings.level, will_shadow); let caches = &mut Caches::new(); let context = EvalContext::new(self, global, caches, &mut state.stack, None); match filter(false, info, context) { Ok(true) => (), Ok(false) => return Err(PERR::ForbiddenVariable(name.into()).into_err(pos)), Err(err) => { return Err(match *err { EvalAltResult::ErrorParsing(e, pos) => e.into_err(pos), _ => PERR::ForbiddenVariable(name.into()).into_err(pos), }) } } } let name = self.get_interned_string(name); // let name = ... let expr = if match_token(state.input, &Token::Equals).0 { // let name = expr self.parse_expr(state, settings.level_up()?)? } else { Expr::Unit(Position::NONE) }; let export = if is_export { ASTFlags::EXPORTED } else { ASTFlags::empty() }; let (existing, hit_barrier) = state.find_var(&name); let existing = if !hit_barrier && existing > 0 { match state.stack.len() - existing { // Variable has been aliased #[cfg(not(feature = "no_module"))] offset if !state.stack.get_entry_by_index(offset).2.is_empty() => None, // Defined in parent block offset if offset < state.frame_pointer => None, offset => Some(offset), } } else { None }; let idx = if let Some(n) = existing { state.stack.get_mut_by_index(n).set_access_mode(access); Some(NonZeroUsize::new(state.stack.len() - n).unwrap()) } else { state.stack.push_entry(name.clone(), access, Dynamic::UNIT); None }; #[cfg(not(feature = "no_module"))] if is_export { state .stack .add_alias_by_index(state.stack.len() - 1, name.clone()); } let var_def = (Ident { name, pos }, expr, idx).into(); Ok(match access { // let name = expr AccessMode::ReadWrite => Stmt::Var(var_def, export, settings.pos), // const name = { expr:constant } AccessMode::ReadOnly => Stmt::Var(var_def, ASTFlags::CONSTANT | export, settings.pos), }) } /// Parse an import statement. #[cfg(not(feature = "no_module"))] fn parse_import(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // import ... let settings = settings.level_up_with_position(eat_token(state.input, &Token::Import))?; // import expr ... let expr = self.parse_expr(state, settings)?; let export = if match_token(state.input, &Token::As).0 { // import expr as name ... let (name, pos) = parse_var_name(state.input)?; Ident { name: self.get_interned_string(name), pos, } } else { // import expr; Ident { name: self.get_interned_string(""), pos: Position::NONE, } }; state.imports.push(export.name.clone()); Ok(Stmt::Import((expr, export).into(), settings.pos)) } /// Parse an export statement. #[cfg(not(feature = "no_module"))] fn parse_export( &self, state: &mut ParseState, mut settings: ParseSettings, ) -> ParseResult { settings.pos = eat_token(state.input, &Token::Export); match state.input.peek().unwrap() { (Token::Let, pos) => { let pos = *pos; let settings = settings.level_up()?; let mut stmt = self.parse_let(state, settings, AccessMode::ReadWrite, true)?; stmt.set_position(pos); return Ok(stmt); } (Token::Const, pos) => { let pos = *pos; let settings = settings.level_up()?; let mut stmt = self.parse_let(state, settings, AccessMode::ReadOnly, true)?; stmt.set_position(pos); return Ok(stmt); } _ => (), } let (id, id_pos) = parse_var_name(state.input)?; let (alias, alias_pos) = if match_token(state.input, &Token::As).0 { parse_var_name(state.input).map(|(name, pos)| (self.get_interned_string(name), pos))? } else { (self.get_interned_string(""), Position::NONE) }; let (existing, hit_barrier) = state.find_var(&id); if !hit_barrier && existing > 0 { state .stack .add_alias_by_index(state.stack.len() - existing, alias.clone()); } let export = ( Ident { name: self.get_interned_string(id), pos: id_pos, }, Ident { name: alias, pos: alias_pos, }, ); Ok(Stmt::Export(export.into(), settings.pos)) } /// Parse a statement block. fn parse_block(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult { // Must start with { let brace_start_pos = match state.input.next().unwrap() { (Token::LeftBrace, pos) => pos, (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::LeftBrace.into(), "to start a statement block".into(), ) .into_err(pos)) } }; let mut settings = settings.level_up_with_position(brace_start_pos)?; let mut block = StmtBlock::empty(settings.pos); if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { let stmt = self.parse_expr_stmt(state, settings)?; block.statements_mut().push(stmt); // Must end with } return match state.input.next().unwrap() { (Token::RightBrace, pos) => { Ok(Stmt::Block(StmtBlock::new(block, settings.pos, pos).into())) } (Token::LexError(err), pos) => Err(err.into_err(pos)), (.., pos) => Err(PERR::MissingToken( Token::LeftBrace.into(), "to start a statement block".into(), ) .into_err(pos)), }; } let prev_frame_pointer = state.frame_pointer; state.frame_pointer = state.stack.len(); #[cfg(not(feature = "no_module"))] let orig_imports_len = state.imports.len(); let end_pos = loop { // Terminated? match state.input.peek().unwrap() { (Token::RightBrace, ..) => break eat_token(state.input, &Token::RightBrace), (Token::EOF, pos) => { return Err(PERR::MissingToken( Token::RightBrace.into(), "to terminate this block".into(), ) .into_err(*pos)); } _ => (), } // Parse statements inside the block settings.flags.remove(ParseSettingFlags::GLOBAL_LEVEL); let stmt = self.parse_stmt(state, settings)?; if stmt.is_noop() { continue; } // See if it needs a terminating semicolon let need_semicolon = !stmt.is_self_terminated(); block.statements_mut().push(stmt); match state.input.peek().unwrap() { // { ... stmt } (Token::RightBrace, ..) => break eat_token(state.input, &Token::RightBrace), // { ... stmt; (Token::SemiColon, ..) if need_semicolon => { eat_token(state.input, &Token::SemiColon); } // { ... { stmt } ; (Token::SemiColon, ..) if !need_semicolon => { eat_token(state.input, &Token::SemiColon); } // { ... { stmt } ??? _ if !need_semicolon => (), // { ... stmt (Token::LexError(err), err_pos) => return Err(err.clone().into_err(*err_pos)), // { ... stmt ??? (.., pos) => { // Semicolons are not optional between statements return Err(PERR::MissingToken( Token::SemiColon.into(), "to terminate this statement".into(), ) .into_err(*pos)); } } }; state.stack.rewind(state.frame_pointer); state.frame_pointer = prev_frame_pointer; #[cfg(not(feature = "no_module"))] state.imports.truncate(orig_imports_len); Ok(Stmt::Block( StmtBlock::new(block, settings.pos, end_pos).into(), )) } /// Parse an expression as a statement. fn parse_expr_stmt( &self, state: &mut ParseState, mut settings: ParseSettings, ) -> ParseResult { settings.pos = state.input.peek().unwrap().1; let expr = self.parse_expr(state, settings)?; let (op, pos) = match state.input.peek().unwrap() { // var = ... (Token::Equals, ..) => (None, eat_token(state.input, &Token::Equals)), // var op= ... (token, ..) if token.is_op_assignment() => { state.input.next().map(|(op, pos)| (Some(op), pos)).unwrap() } // Not op-assignment _ => return Ok(Stmt::Expr(expr.into())), }; settings.pos = pos; let rhs = self.parse_expr(state, settings)?; Self::make_assignment_stmt(op, state, expr, rhs, pos) } /// Parse a single statement. fn parse_stmt(&self, state: &mut ParseState, mut settings: ParseSettings) -> ParseResult { use AccessMode::{ReadOnly, ReadWrite}; #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] let comments = { let mut comments = StaticVec::::new_const(); let mut comments_pos = Position::NONE; let mut buf = SmartString::new_const(); // Handle doc-comments. while let (Token::Comment(ref comment), pos) = state.input.peek().unwrap() { if comments_pos.is_none() { comments_pos = *pos; } debug_assert!( crate::tokenizer::is_doc_comment(comment), "doc-comment expected but gets {:?}", comment ); if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) { return Err(PERR::WrongDocComment.into_err(comments_pos)); } match state.input.next().unwrap() { (Token::Comment(comment), pos) => { if comment.contains('\n') { // Assume block comment if !buf.is_empty() { comments.push(buf.clone()); buf.clear(); } let c = unindent_block_comment(*comment, pos.position().unwrap_or(1) - 1); comments.push(c.into()); } else { if !buf.is_empty() { buf.push_str("\n"); } buf.push_str(&comment); } match state.input.peek().unwrap() { (Token::Fn | Token::Private, ..) => break, (Token::Comment(..), ..) => (), _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), } } (token, ..) => unreachable!("Token::Comment expected but gets {:?}", token), } } if !buf.is_empty() { comments.push(buf); } comments }; let (token, token_pos) = match state.input.peek().unwrap() { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), (x, pos) => (x, *pos), }; settings.pos = token_pos; match token { // ; - empty statement Token::SemiColon => { eat_token(state.input, &Token::SemiColon); Ok(Stmt::Noop(token_pos)) } // { - statements block Token::LeftBrace => Ok(self.parse_block(state, settings.level_up()?)?), // fn ... #[cfg(not(feature = "no_function"))] Token::Fn if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => { Err(PERR::WrongFnDefinition.into_err(token_pos)) } #[cfg(not(feature = "no_function"))] Token::Fn | Token::Private => { let access = if matches!(token, Token::Private) { eat_token(state.input, &Token::Private); crate::FnAccess::Private } else { crate::FnAccess::Public }; match state.input.next().unwrap() { #[cfg(not(feature = "unchecked"))] (Token::Fn, pos) if state.lib.len() >= self.max_functions() => { Err(PERR::TooManyFunctions.into_err(pos)) } (Token::Fn, pos) => { // Build new parse state let new_state = &mut ParseState::new( state.external_constants, state.input, state.tokenizer_control.clone(), state.lib, ); #[cfg(not(feature = "no_module"))] { // Do not allow storing an index to a globally-imported module // just in case the function is separated from this `AST`. // // Keep them in `global_imports` instead so that strict variables // mode will not complain. new_state.global_imports.clone_from(&state.global_imports); new_state.global_imports.extend(state.imports.clone()); } // Brand new options let options = self.options | (settings.options & LangOptions::STRICT_VAR); // Brand new flags, turn on function scope let flags = ParseSettingFlags::FN_SCOPE | (settings.flags & ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES); let new_settings = ParseSettings { flags, level: 0, options, pos, #[cfg(not(feature = "unchecked"))] max_expr_depth: self.max_function_expr_depth(), }; let f = self.parse_fn( new_state, new_settings, access, #[cfg(feature = "metadata")] comments, )?; let hash = calc_fn_hash(None, &f.name, f.params.len()); #[cfg(not(feature = "no_object"))] let hash = f .this_type .as_ref() .map_or(hash, |typ| crate::calc_typed_method_hash(hash, typ)); if state.lib.contains_key(&hash) { return Err(PERR::FnDuplicatedDefinition( f.name.to_string(), f.params.len(), ) .into_err(pos)); } state.lib.insert(hash, f.into()); Ok(Stmt::Noop(pos)) } (.., pos) => Err(PERR::MissingToken( Token::Fn.into(), format!("following '{}'", Token::Private), ) .into_err(pos)), } } Token::If => self.parse_if(state, settings.level_up()?), Token::Switch => self.parse_switch(state, settings.level_up()?), Token::While | Token::Loop if self.allow_looping() => { self.parse_while_loop(state, settings.level_up()?) } Token::Do if self.allow_looping() => self.parse_do(state, settings.level_up()?), Token::For if self.allow_looping() => self.parse_for(state, settings.level_up()?), Token::Continue if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) => { let pos = eat_token(state.input, &Token::Continue); Ok(Stmt::BreakLoop(None, ASTFlags::empty(), pos)) } Token::Break if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) => { let pos = eat_token(state.input, &Token::Break); let current_pos = state.input.peek().unwrap().1; match self.parse_expr(state, settings.level_up()?) { Ok(expr) => Ok(Stmt::BreakLoop(Some(expr.into()), ASTFlags::BREAK, pos)), Err(err) => { if state.input.peek().unwrap().1 == current_pos { Ok(Stmt::BreakLoop(None, ASTFlags::BREAK, pos)) } else { return Err(err); } } } } Token::Continue | Token::Break if self.allow_looping() => { Err(PERR::LoopBreak.into_err(token_pos)) } Token::Return | Token::Throw => { let (return_type, token_pos) = state .input .next() .map(|(token, pos)| { let flags = match token { Token::Return => ASTFlags::empty(), Token::Throw => ASTFlags::BREAK, token => unreachable!( "Token::Return or Token::Throw expected but gets {:?}", token ), }; (flags, pos) }) .unwrap(); let current_pos = state.input.peek().unwrap().1; match self.parse_expr(state, settings.level_up()?) { Ok(expr) => Ok(Stmt::Return(Some(expr.into()), return_type, token_pos)), Err(err) => { if state.input.peek().unwrap().1 == current_pos { Ok(Stmt::Return(None, return_type, token_pos)) } else { return Err(err); } } } } Token::Try => self.parse_try_catch(state, settings.level_up()?), Token::Let => self.parse_let(state, settings.level_up()?, ReadWrite, false), Token::Const => self.parse_let(state, settings.level_up()?, ReadOnly, false), #[cfg(not(feature = "no_module"))] Token::Import => self.parse_import(state, settings.level_up()?), #[cfg(not(feature = "no_module"))] Token::Export if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => { Err(PERR::WrongExport.into_err(token_pos)) } #[cfg(not(feature = "no_module"))] Token::Export => self.parse_export(state, settings.level_up()?), _ => self.parse_expr_stmt(state, settings.level_up()?), } } /// Parse a try/catch statement. fn parse_try_catch( &self, state: &mut ParseState, settings: ParseSettings, ) -> ParseResult { // try ... let settings = settings.level_up_with_position(eat_token(state.input, &Token::Try))?; // try { try_block } let body = self.parse_block(state, settings)?.into(); // try { try_block } catch let (matched, catch_pos) = match_token(state.input, &Token::Catch); if !matched { return Err( PERR::MissingToken(Token::Catch.into(), "for the 'try' statement".into()) .into_err(catch_pos), ); } // try { try_block } catch ( let catch_var = if match_token(state.input, &Token::LeftParen).0 { let (name, pos) = parse_var_name(state.input)?; let (matched, err_pos) = match_token(state.input, &Token::RightParen); if !matched { return Err(PERR::MissingToken( Token::RightParen.into(), "to enclose the catch variable".into(), ) .into_err(err_pos)); } let name = self.get_interned_string(name); state.stack.push(name.clone(), ()); Ident { name, pos } } else { Ident { name: self.get_interned_string(""), pos: Position::NONE, } }; // try { try_block } catch ( var ) { catch_block } let branch = self.parse_block(state, settings)?.into(); let expr = if catch_var.is_empty() { Expr::Unit(catch_var.pos) } else { // Remove the error variable from the stack state.stack.pop(); Expr::Variable( #[cfg(not(feature = "no_module"))] (None, catch_var.name, <_>::default(), 0).into(), #[cfg(feature = "no_module")] (None, catch_var.name).into(), None, catch_var.pos, ) }; Ok(Stmt::TryCatch( FlowControl { expr, body, branch }.into(), settings.pos, )) } /// Parse a function definition. #[cfg(not(feature = "no_function"))] fn parse_fn( &self, state: &mut ParseState, settings: ParseSettings, access: crate::FnAccess, #[cfg(feature = "metadata")] comments: impl IntoIterator, ) -> ParseResult { let settings = settings.level_up()?; let (token, pos) = state.input.next().unwrap(); // Parse type for `this` pointer #[cfg(not(feature = "no_object"))] let ((token, pos), this_type) = { let (next_token, next_pos) = state.input.peek().unwrap(); match token { Token::StringConstant(s) if next_token == &Token::Period => { eat_token(state.input, &Token::Period); let s = match s.as_str() { "int" => self.get_interned_string(std::any::type_name::()), #[cfg(not(feature = "no_float"))] "float" => self.get_interned_string(std::any::type_name::()), _ => self.get_interned_string(*s), }; (state.input.next().unwrap(), Some(s)) } Token::StringConstant(..) => { return Err(PERR::MissingToken( Token::Period.into(), "after the type name for 'this'".into(), ) .into_err(*next_pos)) } Token::Identifier(s) if next_token == &Token::Period => { eat_token(state.input, &Token::Period); let s = match s.as_str() { "int" => self.get_interned_string(std::any::type_name::()), #[cfg(not(feature = "no_float"))] "float" => self.get_interned_string(std::any::type_name::()), _ => self.get_interned_string(*s), }; (state.input.next().unwrap(), Some(s)) } _ => ((token, pos), None), } }; let name = match token { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(s) if is_valid_function_name(&s) => *s, Token::Identifier(s) if is_valid_function_name(&s) => *s, Token::Reserved(s) => return Err(PERR::Reserved(s.to_string()).into_err(pos)), _ => return Err(PERR::FnMissingName.into_err(pos)), }; let no_params = match state.input.peek().unwrap() { (Token::LeftParen, ..) => { eat_token(state.input, &Token::LeftParen); match_token(state.input, &Token::RightParen).0 } (Token::Unit, ..) => { eat_token(state.input, &Token::Unit); true } (.., pos) => return Err(PERR::FnMissingParams(name.into()).into_err(*pos)), }; let mut params = StaticVec::<(ImmutableString, _)>::new_const(); if !no_params { let sep_err = format!("to separate the parameters of function '{name}'"); loop { match state.input.next().unwrap() { (Token::RightParen, ..) => break, (Token::Identifier(s), pos) => { if params.iter().any(|(p, _)| p == &*s) { return Err( PERR::FnDuplicatedParam(name.into(), s.to_string()).into_err(pos) ); } let s = self.get_interned_string(*s); state.stack.push(s.clone(), ()); params.push((s, pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the parameters list of function '{name}'"), ) .into_err(pos)) } } match state.input.next().unwrap() { (Token::RightParen, ..) => break, (Token::Comma, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } } } } // Parse function body let body = match state.input.peek().unwrap() { (Token::LeftBrace, ..) => self.parse_block(state, settings)?, (.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)), } .into(); let mut params: FnArgsVec<_> = params.into_iter().map(|(p, ..)| p).collect(); params.shrink_to_fit(); Ok(ScriptFuncDef { name: self.get_interned_string(name), access, #[cfg(not(feature = "no_object"))] this_type, params, body, #[cfg(feature = "metadata")] comments: comments.into_iter().collect(), }) } /// Creates a curried expression from a list of external variables #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_closure"))] fn make_curry_from_externals( &self, state: &mut ParseState, fn_expr: Expr, externals: impl AsRef<[Ident]> + IntoIterator, pos: Position, ) -> Expr { // If there are no captured variables, no need to curry if externals.as_ref().is_empty() { return fn_expr; } let num_externals = externals.as_ref().len(); let mut args = FnArgsVec::with_capacity(externals.as_ref().len() + 1); args.push(fn_expr); args.extend( externals .as_ref() .iter() .cloned() .map(|Ident { name, pos }| { let (index, is_func) = self.access_var(state, &name, pos); let idx = match index { Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new), _ => None, }; #[cfg(not(feature = "no_module"))] return Expr::Variable((index, name, <_>::default(), 0).into(), idx, pos); #[cfg(feature = "no_module")] return Expr::Variable((index, name).into(), idx, pos); }), ); let expr = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, name: self.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native_only(calc_fn_hash( None, crate::engine::KEYWORD_FN_PTR_CURRY, num_externals + 1, )), args, op_token: None, capture_parent_scope: false, } .into_fn_call_expr(pos); // Convert the entire expression into a statement block, then insert the relevant // [`Share`][Stmt::Share] statements. let mut statements = StaticVec::with_capacity(2); statements.push(Stmt::Share( externals .into_iter() .map(|var| { let (index, _) = self.access_var(state, &var.name, var.pos); (var, index) }) .collect::>() .into(), )); statements.push(Stmt::Expr(expr.into())); Expr::Stmt(StmtBlock::new(statements, pos, Position::NONE).into()) } /// Parse an anonymous function definition. #[cfg(not(feature = "no_function"))] fn parse_anon_fn( &self, state: &mut ParseState, settings: ParseSettings, skip_parameters: bool, ) -> ParseResult { // Build new parse state let new_state = &mut ParseState::new( state.external_constants, state.input, state.tokenizer_control.clone(), state.lib, ); #[cfg(not(feature = "no_module"))] { // Do not allow storing an index to a globally-imported module // just in case the function is separated from this `AST`. // // Keep them in `global_imports` instead so that strict variables // mode will not complain. new_state.global_imports.clone_from(&state.global_imports); new_state.global_imports.extend(state.imports.clone()); } let mut params_list = StaticVec::::new_const(); // Parse parameters if !skip_parameters && new_state.input.next().unwrap().0 != Token::Or && !match_token(new_state.input, &Token::Pipe).0 { loop { match new_state.input.next().unwrap() { (Token::Pipe, ..) => break, (Token::Identifier(s), pos) => { if params_list.iter().any(|p| p == &*s) { return Err( PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos) ); } let s = self.get_interned_string(*s); new_state.stack.push(s.clone(), ()); params_list.push(s); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::Pipe.into(), "to close the parameters list of anonymous function or closure".into(), ) .into_err(pos)) } } match new_state.input.next().unwrap() { (Token::Pipe, ..) => break, (Token::Comma, ..) => (), (Token::LexError(err), pos) => return Err(err.into_err(pos)), (.., pos) => { return Err(PERR::MissingToken( Token::Comma.into(), "to separate the parameters of anonymous function".into(), ) .into_err(pos)) } } } } // Brand new options #[cfg(not(feature = "no_closure"))] let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode #[cfg(feature = "no_closure")] let options = self.options | (settings.options & LangOptions::STRICT_VAR); // Brand new flags, turn on function scope and closure scope let flags = ParseSettingFlags::FN_SCOPE | ParseSettingFlags::CLOSURE_SCOPE | (settings.flags & (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS)); let new_settings = ParseSettings { flags, options, ..settings }; // Parse function body let body = self.parse_stmt(new_state, new_settings.level_up()?)?; let _ = new_settings; // Make sure it doesn't leak into code below // External variables may need to be processed in a consistent order, // so extract them into a list. #[cfg(not(feature = "no_closure"))] let (mut params, _externals) = { let externals = std::mem::take(&mut new_state.external_vars); let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len()); params.extend(externals.iter().map(|Ident { name, .. }| name.clone())); (params, externals) }; #[cfg(feature = "no_closure")] let (mut params, _externals) = ( FnArgsVec::with_capacity(params_list.len()), ThinVec::::new(), ); let _ = new_state; // Make sure it doesn't leak into code below params.append(&mut params_list); // Create unique function name by hashing the script body plus the parameters. let hasher = &mut get_hasher(); params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); let fn_name = self.get_interned_string(make_anonymous_fn(hash)); // Define the function let fn_def = Shared::new(ScriptFuncDef { name: fn_name.clone(), access: crate::FnAccess::Public, #[cfg(not(feature = "no_object"))] this_type: None, params, body: body.into(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: <_>::default(), }); // Define the function pointer let fn_ptr = crate::FnPtr { name: fn_name, curry: ThinVec::new(), #[cfg(not(feature = "no_function"))] env: None, typ: crate::types::fn_ptr::FnPtrType::Normal, }; let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), new_settings.pos); // Finished with `new_state` here. Revert back to using `state`. #[cfg(not(feature = "no_closure"))] for Ident { name, pos } in &_externals { let (index, is_func) = self.access_var(state, name, *pos); if !is_func && index.is_none() && !settings.has_flag(ParseSettingFlags::CLOSURE_SCOPE) && settings.has_option(LangOptions::STRICT_VAR) && !state .external_constants .map_or(false, |scope| scope.contains(name)) { // If the parent scope is not inside another capturing closure // then we can conclude that the captured variable doesn't exist. // Under Strict Variables mode, this is not allowed. return Err(PERR::VariableUndefined(name.to_string()).into_err(*pos)); } } let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len()); state.lib.insert(hash_script, fn_def); #[cfg(not(feature = "no_closure"))] let expr = self.make_curry_from_externals(state, expr, _externals, settings.pos); Ok(expr) } /// Parse a global level expression. pub(crate) fn parse_global_expr( &self, mut state: ParseState, process_settings: impl FnOnce(&mut ParseSettings), #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { let options = self.options & !LangOptions::STMT_EXPR & !LangOptions::LOOP_EXPR; let mut settings = ParseSettings { level: 0, flags: ParseSettingFlags::GLOBAL_LEVEL | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS, options, pos: Position::START, #[cfg(not(feature = "unchecked"))] max_expr_depth: self.max_expr_depth(), }; process_settings(&mut settings); let expr = self.parse_expr(&mut state, settings)?; match state.input.peek().unwrap() { (Token::EOF, ..) => (), // Return error if the expression doesn't end (token, pos) => return Err(LexError::UnexpectedInput(token.to_string()).into_err(*pos)), } let mut statements = StmtBlockContainer::new_const(); statements.push(Stmt::Expr(expr.into())); #[cfg(not(feature = "no_optimize"))] return Ok(self.optimize_into_ast( state.external_constants, statements, #[cfg(not(feature = "no_function"))] state.lib.values().cloned().collect::>(), optimization_level, )); #[cfg(feature = "no_optimize")] return Ok(AST::new( statements, #[cfg(not(feature = "no_function"))] crate::Module::from(state.lib.values().cloned()), )); } /// Parse the global level statements. fn parse_global_level( &self, state: &mut ParseState, process_settings: impl FnOnce(&mut ParseSettings), ) -> ParseResult<(StmtBlockContainer, Vec>)> { let mut statements = StmtBlockContainer::new_const(); let mut settings = ParseSettings { level: 0, flags: ParseSettingFlags::GLOBAL_LEVEL, options: self.options, pos: Position::START, #[cfg(not(feature = "unchecked"))] max_expr_depth: self.max_expr_depth(), }; process_settings(&mut settings); while state.input.peek().unwrap().0 != Token::EOF { let stmt = self.parse_stmt(state, settings)?; if stmt.is_noop() { continue; } let need_semicolon = !stmt.is_self_terminated(); statements.push(stmt); match state.input.peek().unwrap() { // EOF (Token::EOF, ..) => break, // stmt ; (Token::SemiColon, ..) if need_semicolon => { eat_token(state.input, &Token::SemiColon); } // stmt ; (Token::SemiColon, ..) if !need_semicolon => (), // { stmt } ??? _ if !need_semicolon => (), // stmt (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)), // stmt ??? (.., pos) => { // Semicolons are not optional between statements return Err(PERR::MissingToken( Token::SemiColon.into(), "to terminate this statement".into(), ) .into_err(*pos)); } } } #[cfg(not(feature = "no_function"))] let lib = state.lib.values().cloned().collect(); #[cfg(feature = "no_function")] let lib = Vec::new(); Ok((statements, lib)) } /// Run the parser on an input stream, returning an AST. #[inline] pub(crate) fn parse( &self, mut state: ParseState, #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { let (statements, _lib) = self.parse_global_level(&mut state, |_| {})?; #[cfg(not(feature = "no_optimize"))] return Ok(self.optimize_into_ast( state.external_constants, statements, #[cfg(not(feature = "no_function"))] _lib, optimization_level, )); #[cfg(feature = "no_optimize")] return Ok(AST::new( statements, #[cfg(not(feature = "no_function"))] { let mut new_lib = crate::Module::new(); new_lib.extend(_lib); new_lib }, )); } } rhai-1.21.0/src/reify.rs000064400000000000000000000044121046102023000131040ustar 00000000000000/// Macro to cast an identifier or expression to another type with type checks. /// /// Runs _code_ if _variable_ or _expression_ is of type _type_, otherwise run _fallback_. /// /// # Syntax /// /// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_`,` `||` _fallback_ `)` /// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_ `)` /// * `reify! { `_variable_ or _expression_ `=>` `Option<`_type_`>` `)` /// * `reify! { `_variable_ or _expression_ `=>` _type_ `)` /// /// * `reify! { `_expression_ `=> !!!` _type_ `)` (unsafe, no type checks!) macro_rules! reify { ($old:ident => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ #[allow(clippy::redundant_else)] if std::any::TypeId::of::<$t>() == std::any::Any::type_id(&$old) { // SAFETY: This is safe because we already checked to make sure the two types // are actually the same. let $new: $t = unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new($old)) }; $code } else { $fallback } }}; ($old:expr => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ let old = $old; reify! { old => |$new: $t| $code, || $fallback } }}; ($old:ident => |$new:ident : $t:ty| $code:expr) => { reify! { $old => |$new: $t| $code, || () } }; ($old:expr => |$new:ident : $t:ty| $code:expr) => { reify! { $old => |$new: $t| $code, || () } }; ($old:ident => Option<$t:ty>) => { reify! { $old => |v: $t| Some(v), || None } }; ($old:expr => Option<$t:ty>) => { reify! { $old => |v: $t| Some(v), || None } }; ($old:ident => $t:ty) => { reify! { $old => |v: $t| v, || unreachable!() } }; ($old:expr => $t:ty) => { reify! { $old => |v: $t| v, || unreachable!() } }; ($old:expr => !!! $t:ty) => {{ let old_value = $old; let new_value: $t = // SAFETY: This is really dangerous (thus the exclamation marks). // Make sure that the transmute is safe. unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new(old_value)) }; new_value }}; } rhai-1.21.0/src/serde/de.rs000064400000000000000000000532241046102023000134650ustar 00000000000000//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use crate::api::formatting::map_std_type_name; use crate::types::dynamic::Union; use crate::{Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, ERR}; use serde::de::{Error, IntoDeserializer, Visitor}; use serde::{Deserialize, Deserializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::type_name, fmt}; /// Deserializer for [`Dynamic`][crate::Dynamic]. pub struct DynamicDeserializer<'de>(&'de Dynamic); impl<'de> IntoDeserializer<'de, RhaiError> for &'de Dynamic { type Deserializer = DynamicDeserializer<'de>; #[inline(always)] #[must_use] fn into_deserializer(self) -> Self::Deserializer { DynamicDeserializer(self) } } impl<'de> DynamicDeserializer<'de> { /// Create a [`DynamicDeserializer`] from a reference to a [`Dynamic`][crate::Dynamic] value. /// /// The reference is necessary because the deserialized type may hold references /// (especially `&str`) to the source [`Dynamic`][crate::Dynamic]. #[inline(always)] #[must_use] pub const fn new(value: &'de Dynamic) -> Self { Self(value) } /// Shortcut for a type conversion error. #[cold] #[inline(always)] fn type_error(&self) -> RhaiResultOf { self.type_error_str(map_std_type_name(type_name::(), false)) } /// Shortcut for a type conversion error. #[cold] #[inline(never)] fn type_error_str(&self, actual: &str) -> RhaiResultOf { let expected = map_std_type_name(self.0.type_name(), false).into(); Err(ERR::ErrorMismatchOutputType(actual.into(), expected, Position::NONE).into()) } #[inline(always)] fn deserialize_int>(v: crate::INT, visitor: V) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return visitor.visit_i64(v); #[cfg(feature = "only_i32")] return visitor.visit_i32(v); } } /// Deserialize a [`Dynamic`][crate::Dynamic] value into a Rust type that implements [`serde::Deserialize`]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_index"))] /// # #[cfg(not(feature = "no_object"))] /// # { /// use rhai::{Dynamic, Array, Map, INT}; /// use rhai::serde::from_dynamic; /// use serde::Deserialize; /// /// #[derive(Debug, Deserialize, PartialEq)] /// struct Hello { /// a: INT, /// b: bool, /// } /// /// #[derive(Debug, Deserialize, PartialEq)] /// struct Test { /// int: u32, /// seq: Vec, /// obj: Hello, /// } /// /// let mut map = Map::new(); /// map.insert("int".into(), Dynamic::from(42_u32)); /// /// let mut map2 = Map::new(); /// map2.insert("a".into(), (123 as INT).into()); /// map2.insert("b".into(), true.into()); /// /// map.insert("obj".into(), map2.into()); /// /// let arr: Array = vec!["foo".into(), "bar".into(), "baz".into()]; /// map.insert("seq".into(), arr.into()); /// /// let value: Test = from_dynamic(&map.into())?; /// /// let expected = Test { /// int: 42, /// seq: vec!["foo".into(), "bar".into(), "baz".into()], /// obj: Hello { a: 123, b: true }, /// }; /// /// assert_eq!(value, expected); /// # } /// # Ok(()) /// # } /// ``` pub fn from_dynamic<'de, T: Deserialize<'de>>(value: &'de Dynamic) -> RhaiResultOf { T::deserialize(DynamicDeserializer::new(value)) } impl Error for RhaiError { #[cold] #[inline(never)] fn custom(err: T) -> Self { LexError::ImproperSymbol(String::new(), err.to_string()) .into_err(Position::NONE) .into() } } impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { if type_name::() == type_name::() { return Ok(reify! { self.0.clone() => !!! V::Value }); } match self.0 .0 { Union::Unit(..) => self.deserialize_unit(visitor), Union::Bool(..) => self.deserialize_bool(visitor), Union::Str(..) => self.deserialize_str(visitor), Union::Char(..) => self.deserialize_char(visitor), #[cfg(not(feature = "only_i32"))] Union::Int(..) => self.deserialize_i64(visitor), #[cfg(feature = "only_i32")] Union::Int(..) => self.deserialize_i32(visitor), #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] Union::Float(..) => self.deserialize_f64(visitor), #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] Union::Float(..) => self.deserialize_f32(visitor), #[cfg(feature = "decimal")] #[cfg(not(feature = "f32_float"))] Union::Decimal(..) => self.deserialize_f64(visitor), #[cfg(feature = "decimal")] #[cfg(feature = "f32_float")] Union::Decimal(..) => self.deserialize_f32(visitor), #[cfg(not(feature = "no_index"))] Union::Array(..) => self.deserialize_seq(visitor), #[cfg(not(feature = "no_index"))] Union::Blob(..) => self.deserialize_bytes(visitor), #[cfg(not(feature = "no_object"))] Union::Map(..) => self.deserialize_map(visitor), Union::FnPtr(..) => self.type_error(), #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => self.type_error(), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i8(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i16(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i32(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i64(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_i128(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_u8(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_u16(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_u32(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_u64(visitor), Union::Variant(ref value, ..) if value.is::() => self.deserialize_u128(visitor), Union::Variant(..) => self.type_error(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => self.type_error(), } } fn deserialize_bool>(self, visitor: V) -> RhaiResultOf { visitor.visit_bool(self.0.as_bool().or_else(|_| self.type_error())?) } fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i8(x)), } } fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i16(x)), } } fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), _ if cfg!(feature = "only_i32") => self.type_error(), _ => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i32(x)), } } fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), _ if cfg!(not(feature = "only_i32")) => self.type_error(), _ => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i64(x)), } } fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), _ if cfg!(not(feature = "only_i32")) => self.type_error(), _ => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_i128(x)), } } fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u8(x)), } } fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u16(x)), } } fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u32(x)), } } fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u64(x)), } } fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { match self.0.as_int() { Ok(v) => Self::deserialize_int(v, visitor), Err(_) => self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_u128(x)), } } fn deserialize_f32>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] { use rust_decimal::prelude::ToPrimitive; return self .0 .downcast_ref::() .and_then(|&x| x.to_f32()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); } self.type_error_str("f32") } } fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] { use rust_decimal::prelude::ToPrimitive; return self .0 .downcast_ref::() .and_then(|&x| x.to_f64()) .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); } self.type_error_str("f64") } } fn deserialize_char>(self, visitor: V) -> RhaiResultOf { self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |&x| visitor.visit_char(x)) } fn deserialize_str>(self, visitor: V) -> RhaiResultOf { self.0 .downcast_ref::() .map_or_else(|| self.type_error(), |x| visitor.visit_borrowed_str(x)) } fn deserialize_string>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } fn deserialize_bytes>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self .0 .downcast_ref::() .map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x)); #[cfg(feature = "no_index")] return self.type_error(); } fn deserialize_byte_buf>(self, visitor: V) -> RhaiResultOf { self.deserialize_bytes(visitor) } fn deserialize_option>(self, visitor: V) -> RhaiResultOf { if self.0.is_unit() { visitor.visit_none() } else { visitor.visit_some(self) } } fn deserialize_unit>(self, visitor: V) -> RhaiResultOf { self.0 .downcast_ref::<()>() .map_or_else(|| self.type_error(), |()| visitor.visit_unit()) } fn deserialize_unit_struct>( self, _name: &'static str, visitor: V, ) -> RhaiResultOf { self.deserialize_unit(visitor) } fn deserialize_newtype_struct>( self, _name: &'static str, visitor: V, ) -> RhaiResultOf { visitor.visit_newtype_struct(self) } fn deserialize_seq>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return self.0.downcast_ref::().map_or_else( || self.type_error(), |arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())), ); #[cfg(feature = "no_index")] return self.type_error(); } fn deserialize_tuple>(self, _len: usize, visitor: V) -> RhaiResultOf { self.deserialize_seq(visitor) } fn deserialize_tuple_struct>( self, _name: &'static str, _len: usize, visitor: V, ) -> RhaiResultOf { self.deserialize_seq(visitor) } fn deserialize_map>(self, _visitor: V) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { _visitor.visit_map(IterateMap::new( map.keys().map(crate::SmartString::as_str), map.values(), )) }, ); #[cfg(feature = "no_object")] return self.type_error(); } fn deserialize_struct>( self, _name: &'static str, _fields: &'static [&'static str], _visitor: V, ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return self.0.downcast_ref::().map_or_else( || { Err(ERR::ErrorMismatchOutputType( map_std_type_name(type_name::(), false).into(), map_std_type_name(self.0.type_name(), false).into(), Position::NONE, ) .into()) }, |map| { _visitor.visit_map(IterateMap::new( map.keys().map(crate::SmartString::as_str), map.values(), )) }, ); #[cfg(feature = "no_object")] return self.type_error(); } fn deserialize_enum>( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> RhaiResultOf { match self.0.as_immutable_string_ref() { Ok(s) => visitor.visit_enum(s.into_deserializer()), Err(_) => { #[cfg(not(feature = "no_object"))] return self.0.downcast_ref::().map_or_else( || self.type_error(), |map| { let mut iter = map.iter(); let first = iter.next(); let second = iter.next(); match (first, second) { (Some((key, value)), None) => visitor.visit_enum(EnumDeserializer { tag: key, content: DynamicDeserializer::new(value), }), _ => self.type_error(), } }, ); #[cfg(feature = "no_object")] return self.type_error(); } } } #[inline(always)] fn deserialize_identifier>(self, visitor: V) -> RhaiResultOf { self.deserialize_str(visitor) } #[inline(always)] fn deserialize_ignored_any>(self, visitor: V) -> RhaiResultOf { self.deserialize_any(visitor) } } /// `SeqAccess` implementation for arrays. #[cfg(not(feature = "no_index"))] struct IterateDynamicArray<'de, ITER: Iterator> { /// Iterator for a stream of [`Dynamic`][crate::Dynamic] values. iter: ITER, } #[cfg(not(feature = "no_index"))] impl<'de, ITER: Iterator> IterateDynamicArray<'de, ITER> { #[inline(always)] #[must_use] pub const fn new(iter: ITER) -> Self { Self { iter } } } #[cfg(not(feature = "no_index"))] impl<'de, ITER: Iterator> serde::de::SeqAccess<'de> for IterateDynamicArray<'de, ITER> { type Error = RhaiError; fn next_element_seed>( &mut self, seed: T, ) -> RhaiResultOf> { // Deserialize each item coming out of the iterator. self.iter.next().map_or(Ok(None), |item| { seed.deserialize(item.into_deserializer()).map(Some) }) } } /// `MapAccess` implementation for maps. #[cfg(not(feature = "no_object"))] struct IterateMap<'de, K: Iterator, V: Iterator> { // Iterator for a stream of [`Dynamic`][crate::Dynamic] keys. keys: K, // Iterator for a stream of [`Dynamic`][crate::Dynamic] values. values: V, } #[cfg(not(feature = "no_object"))] impl<'de, K: Iterator, V: Iterator> IterateMap<'de, K, V> { #[inline(always)] #[must_use] pub const fn new(keys: K, values: V) -> Self { Self { keys, values } } } #[cfg(not(feature = "no_object"))] impl<'de, K: Iterator, V: Iterator> serde::de::MapAccess<'de> for IterateMap<'de, K, V> { type Error = RhaiError; fn next_key_seed>( &mut self, seed: S, ) -> RhaiResultOf> { // Deserialize each `Identifier` key coming out of the keys iterator. self.keys .next() .map(<_>::into_deserializer) .map_or(Ok(None), |d| seed.deserialize(d).map(Some)) } fn next_value_seed>( &mut self, seed: S, ) -> RhaiResultOf { // Deserialize each value item coming out of the iterator. seed.deserialize(self.values.next().unwrap().into_deserializer()) } } #[cfg(not(feature = "no_object"))] struct EnumDeserializer<'de> { tag: &'de str, content: DynamicDeserializer<'de>, } #[cfg(not(feature = "no_object"))] impl<'de> serde::de::EnumAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; type Variant = Self; fn variant_seed>( self, seed: V, ) -> RhaiResultOf<(V::Value, Self::Variant)> { seed.deserialize(self.tag.into_deserializer()) .map(|v| (v, self)) } } #[cfg(not(feature = "no_object"))] impl<'de> serde::de::VariantAccess<'de> for EnumDeserializer<'de> { type Error = RhaiError; #[inline(always)] fn unit_variant(self) -> RhaiResultOf<()> { Deserialize::deserialize(self.content) } #[inline(always)] fn newtype_variant_seed>( self, seed: T, ) -> RhaiResultOf { seed.deserialize(self.content) } #[inline(always)] fn tuple_variant>(self, len: usize, visitor: V) -> RhaiResultOf { self.content.deserialize_tuple(len, visitor) } #[inline(always)] fn struct_variant>( self, fields: &'static [&'static str], visitor: V, ) -> RhaiResultOf { self.content.deserialize_struct("", fields, visitor) } } rhai-1.21.0/src/serde/deserialize.rs000064400000000000000000000232571046102023000154000ustar 00000000000000//! Implementations of [`serde::Deserialize`]. use crate::{Dynamic, Identifier, ImmutableString, Scope, INT}; use serde::{ de::{Error, SeqAccess, Visitor}, Deserialize, Deserializer, }; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(feature = "decimal")] use num_traits::FromPrimitive; struct DynamicVisitor; impl<'de> Visitor<'de> for DynamicVisitor { type Value = Dynamic; #[cold] #[inline(never)] fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("any type that can be converted into a Dynamic") } #[inline(always)] fn visit_bool(self, v: bool) -> Result { Ok(v.into()) } #[inline(always)] fn visit_i8(self, v: i8) -> Result { Ok(INT::from(v).into()) } #[inline(always)] fn visit_i16(self, v: i16) -> Result { Ok(INT::from(v).into()) } #[inline(always)] fn visit_i32(self, v: i32) -> Result { Ok(INT::from(v).into()) } #[inline] fn visit_i64(self, v: i64) -> Result { #[cfg(not(feature = "only_i32"))] return Ok(v.into()); #[cfg(feature = "only_i32")] if v <= INT::MAX as i64 { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_i64(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn visit_i128(self, v: i128) -> Result { if v <= i128::from(INT::MAX) { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_i128(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline(always)] fn visit_u8(self, v: u8) -> Result { Ok(INT::from(v).into()) } #[inline(always)] fn visit_u16(self, v: u16) -> Result { Ok(INT::from(v).into()) } #[inline] fn visit_u32(self, v: u32) -> Result { #[cfg(not(feature = "only_i32"))] return Ok(Dynamic::from(v as INT)); #[cfg(feature = "only_i32")] if v <= INT::MAX as u32 { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u32(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn visit_u64(self, v: u64) -> Result { if v <= INT::MAX as u64 { return Ok(Dynamic::from(v as INT)); } #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u64(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); #[allow(unreachable_code)] Err(Error::custom(format!("integer number too large: {v}"))) } #[inline] fn visit_u128(self, v: u128) -> Result { if v <= INT::MAX as u128 { return Ok(Dynamic::from(v as INT)); } #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u128(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); #[allow(unreachable_code)] Err(Error::custom(format!("integer number too large: {v}"))) } #[cfg(not(feature = "no_float"))] #[inline(always)] fn visit_f32(self, v: f32) -> Result { #[cfg(not(feature = "no_float"))] return Ok((v as crate::FLOAT).into()); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_f32(v) { return Ok(Dynamic::from_decimal(n)); } Err(Error::custom(format!( "floating-point number is not supported: {v}" ))) } } #[cfg(not(feature = "no_float"))] #[inline(always)] fn visit_f64(self, v: f64) -> Result { #[cfg(not(feature = "no_float"))] return Ok((v as crate::FLOAT).into()); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_f64(v) { return Ok(Dynamic::from_decimal(n)); } Err(Error::custom(format!( "floating-point number is not supported: {v}" ))) } } #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] #[inline] fn visit_f32(self, v: f32) -> Result { use std::convert::TryFrom; rust_decimal::Decimal::try_from(v) .map(|v| v.into()) .map_err(Error::custom) } #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] #[inline] fn visit_f64(self, v: f64) -> Result { use std::convert::TryFrom; rust_decimal::Decimal::try_from(v) .map(|v| v.into()) .map_err(Error::custom) } #[inline(always)] fn visit_char(self, v: char) -> Result { Ok(v.into()) } #[inline(always)] fn visit_str(self, v: &str) -> Result { Ok(v.into()) } #[inline(always)] fn visit_string(self, v: String) -> Result { Ok(v.into()) } #[cfg(not(feature = "no_index"))] #[inline(always)] fn visit_bytes(self, v: &[u8]) -> Result { Ok(Dynamic::from_blob(v.to_vec())) } #[inline(always)] fn visit_unit(self) -> Result { Ok(Dynamic::UNIT) } #[inline(always)] fn visit_newtype_struct>(self, de: D) -> Result { Deserialize::deserialize(de) } #[cfg(not(feature = "no_index"))] fn visit_seq>(self, mut seq: A) -> Result { let mut arr = crate::Array::new(); while let Some(v) = seq.next_element()? { arr.push(v); } Ok(arr.into()) } #[cfg(not(feature = "no_object"))] fn visit_map>(self, mut map: M) -> Result { let mut m = crate::Map::new(); while let Some((k, v)) = map.next_entry()? { m.insert(k, v); } Ok(m.into()) } } impl<'de> Deserialize<'de> for Dynamic { #[inline(always)] fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_any(DynamicVisitor) } } impl<'de> Deserialize<'de> for ImmutableString { #[inline(always)] fn deserialize>(deserializer: D) -> Result { let s: String = Deserialize::deserialize(deserializer)?; Ok(s.into()) } } impl<'de> Deserialize<'de> for Scope<'_> { #[inline(always)] fn deserialize>(deserializer: D) -> Result { #[derive(Debug, Clone, Hash, Deserialize)] struct ScopeEntry { pub name: Identifier, pub value: Dynamic, #[serde(default)] pub is_constant: bool, } struct VecVisitor; impl<'de> Visitor<'de> for VecVisitor { type Value = Scope<'static>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a sequence") } #[inline] fn visit_seq(self, mut access: A) -> Result where A: SeqAccess<'de>, { let mut scope = access .size_hint() .map_or_else(Scope::new, Scope::with_capacity); while let Some(ScopeEntry { name, value, is_constant, }) = access.next_element()? { if is_constant { scope.push_constant_dynamic(name, value); } else { scope.push_dynamic(name, value); } } Ok(scope) } } deserializer.deserialize_seq(VecVisitor) } } rhai-1.21.0/src/serde/metadata.rs000064400000000000000000000275151046102023000146610ustar 00000000000000//! Serialization of functions metadata. #![cfg(feature = "metadata")] use crate::api::formatting::format_param_type_for_display; use crate::func::RhaiFunc; use crate::module::{calc_native_fn_hash, FuncMetadata}; use crate::types::custom_types::CustomTypeInfo; use crate::{calc_fn_hash, Engine, FnAccess, SmartString, ThinVec, AST}; use serde::{Deserialize, Serialize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] enum FnType { Script, Native, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnParam<'a> { #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option<&'a str>, #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] pub typ: Option>, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct CustomTypeMetadata<'a> { pub type_name: &'a str, pub display_name: &'a str, #[serde(default, skip_serializing_if = "ThinVec::is_empty")] pub doc_comments: ThinVec<&'a str>, } impl PartialOrd for CustomTypeMetadata<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for CustomTypeMetadata<'_> { fn cmp(&self, other: &Self) -> Ordering { match self.display_name.cmp(other.display_name) { Ordering::Equal => self.display_name.cmp(other.display_name), cmp => cmp, } } } impl<'a> From<(&'a str, &'a CustomTypeInfo)> for CustomTypeMetadata<'a> { fn from(value: (&'a str, &'a CustomTypeInfo)) -> Self { Self { type_name: value.0, display_name: &value.1.display_name, doc_comments: value.1.comments.iter().map(<_>::as_ref).collect(), } } } #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnMetadata<'a> { pub base_hash: u64, pub full_hash: u64, #[cfg(not(feature = "no_module"))] pub namespace: crate::FnNamespace, pub access: FnAccess, pub name: &'a str, #[cfg(not(feature = "no_function"))] pub is_anonymous: bool, #[serde(rename = "type")] pub typ: FnType, #[cfg(not(feature = "no_object"))] #[serde(default, skip_serializing_if = "Option::is_none")] pub this_type: Option<&'a str>, pub num_params: usize, #[serde(default, skip_serializing_if = "ThinVec::is_empty")] pub params: ThinVec>, #[serde(default, skip_serializing_if = "str::is_empty")] pub return_type: Cow<'a, str>, pub signature: SmartString, #[serde(default, skip_serializing_if = "ThinVec::is_empty")] pub doc_comments: ThinVec<&'a str>, } impl PartialOrd for FnMetadata<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for FnMetadata<'_> { fn cmp(&self, other: &Self) -> Ordering { match self.name.cmp(other.name) { Ordering::Equal => self.num_params.cmp(&other.num_params), cmp => cmp, } } } impl<'a> From<(&'a RhaiFunc, &'a FuncMetadata)> for FnMetadata<'a> { fn from(info: (&'a RhaiFunc, &'a FuncMetadata)) -> Self { let (f, m) = info; let base_hash = calc_fn_hash(None, &m.name, m.num_params); let (typ, full_hash, _this_type) = if f.is_script() { ( FnType::Script, base_hash, #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] f.get_script_fn_def() .unwrap() .this_type .as_ref() .map(|s| s.as_str()), #[cfg(any(feature = "no_object", feature = "no_function"))] None::<&str>, ) } else { ( FnType::Native, calc_native_fn_hash(None, &m.name, &m.param_types), None::<&str>, ) }; Self { base_hash, full_hash, #[cfg(not(feature = "no_module"))] namespace: m.namespace, access: m.access, name: &m.name, #[cfg(not(feature = "no_function"))] is_anonymous: crate::parser::is_anonymous_fn(&m.name), typ, #[cfg(not(feature = "no_object"))] this_type: _this_type, num_params: m.num_params, params: m .params_info .iter() .map(|s| { let mut seg = s.splitn(2, ':'); let name = match seg.next().unwrap().trim() { "_" => None, s => Some(s), }; let typ = seg.next().map(|s| format_param_type_for_display(s, false)); FnParam { name, typ } }) .collect(), return_type: format_param_type_for_display(&m.return_type, true), signature: m.gen_signature(Into::into).into(), doc_comments: if f.is_script() { #[cfg(feature = "no_function")] unreachable!("script-defined functions should not exist under no_function"); #[cfg(not(feature = "no_function"))] f.get_script_fn_def() .expect("`ScriptFuncDef`") .comments .iter() .map(<_>::as_ref) .collect() } else { m.comments.iter().map(<_>::as_ref).collect() }, } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct ModuleMetadata<'a> { #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub modules: BTreeMap<&'a str, Self>, #[serde(default, skip_serializing_if = "ThinVec::is_empty")] pub custom_types: ThinVec>, #[serde(default, skip_serializing_if = "ThinVec::is_empty")] pub functions: ThinVec>, #[serde(default, skip_serializing_if = "str::is_empty")] pub doc: &'a str, } impl ModuleMetadata<'_> { #[inline(always)] pub fn new() -> Self { Self { doc: "", modules: BTreeMap::new(), custom_types: ThinVec::new(), functions: ThinVec::new(), } } } impl<'a> From<&'a crate::Module> for ModuleMetadata<'a> { fn from(module: &'a crate::Module) -> Self { let modules = module .iter_sub_modules() .map(|(name, m)| (name, m.as_ref().into())) .collect(); let mut custom_types = module .iter_custom_types() .map(Into::into) .collect::>(); custom_types.sort(); let mut functions = module.iter_fn().map(Into::into).collect::>(); functions.sort(); Self { doc: module.doc(), modules, custom_types, functions, } } } #[cfg(feature = "internals")] impl crate::api::definitions::Definitions<'_> { /// Generate a list of all functions in JSON format. /// /// Functions from the following sources are included: /// 1) Functions defined in an [`AST`][crate::AST] /// 2) Functions registered into the global namespace /// 3) Functions in static modules /// 4) Functions in registered global packages /// 5) Functions in standard packages (optional) #[inline(always)] pub fn json(&self) -> serde_json::Result { self.engine() .gen_metadata_to_json_raw(None, self.config().include_standard_packages) } } impl Engine { /// Generate a list of all functions in JSON format. fn gen_metadata_to_json_raw( &self, ast: Option<&AST>, include_standard_packages: bool, ) -> serde_json::Result { let _ast = ast; let mut global_doc = String::new(); let mut global = ModuleMetadata::new(); #[cfg(not(feature = "no_module"))] for (name, m) in &self.global_sub_modules { global.modules.insert(name, m.as_ref().into()); } self.global_modules .iter() .filter(|&m| include_standard_packages || !m.is_standard_lib()) .for_each(|m| { if !m.doc().is_empty() { if !global_doc.is_empty() { global_doc += "\n"; } global_doc += m.doc(); } m.iter_custom_types() .for_each(|c| global.custom_types.push(c.into())); m.iter_fn().for_each(|f| { #[allow(unused_mut)] let mut meta: FnMetadata = f.into(); #[cfg(not(feature = "no_module"))] { meta.namespace = crate::FnNamespace::Global; } global.functions.push(meta); }) }); #[cfg(not(feature = "no_function"))] if let Some(ast) = _ast { ast.shared_lib() .iter_custom_types() .for_each(|c| global.custom_types.push(c.into())); ast.shared_lib().iter_fn().for_each(|f| { #[allow(unused_mut)] let mut meta: FnMetadata = f.into(); #[cfg(not(feature = "no_module"))] { meta.namespace = crate::FnNamespace::Global; } global.functions.push(meta); }); } global.custom_types.sort(); global.functions.sort(); if let Some(ast) = _ast { if !ast.doc().is_empty() { if !global_doc.is_empty() { global_doc += "\n"; } global_doc += ast.doc(); } } global.doc = &global_doc; serde_json::to_string_pretty(&global) } /// _(metadata)_ Generate a list of all functions (including those defined in an /// [`AST`][crate::AST]) in JSON format. /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included: /// 1) Functions defined in an [`AST`][crate::AST] /// 2) Functions registered into the global namespace /// 3) Functions in static modules /// 4) Functions in registered global packages /// 5) Functions in standard packages (optional) #[inline(always)] pub fn gen_fn_metadata_with_ast_to_json( &self, ast: &AST, include_standard_packages: bool, ) -> serde_json::Result { self.gen_metadata_to_json_raw(Some(ast), include_standard_packages) } /// Generate a list of all functions in JSON format. /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included: /// 1) Functions registered into the global namespace /// 2) Functions in static modules /// 3) Functions in registered global packages /// 4) Functions in standard packages (optional) #[inline(always)] pub fn gen_fn_metadata_to_json( &self, include_standard_packages: bool, ) -> serde_json::Result { self.gen_metadata_to_json_raw(None, include_standard_packages) } } rhai-1.21.0/src/serde/mod.rs000064400000000000000000000004771046102023000136560ustar 00000000000000//! _(serde)_ Serialization and deserialization support for [`serde`](https://crates.io/crates/serde). //! Exported under the `serde` feature only. mod de; mod deserialize; mod metadata; mod ser; mod serialize; pub use de::{from_dynamic, DynamicDeserializer}; pub use ser::{to_dynamic, DynamicSerializer}; rhai-1.21.0/src/serde/ser.rs000064400000000000000000000535511046102023000136710ustar 00000000000000//! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use crate::{Dynamic, Identifier, Position, RhaiError, RhaiResult, RhaiResultOf, ERR, INT}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; use serde::{Serialize, Serializer}; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(feature = "decimal")] use num_traits::FromPrimitive; /// Serializer for [`Dynamic`][crate::Dynamic]. pub struct DynamicSerializer { /// Buffer to hold a temporary key. _key: Identifier, /// Buffer to hold a temporary value. _value: Dynamic, } impl DynamicSerializer { /// Create a [`DynamicSerializer`] from a [`Dynamic`][crate::Dynamic] value. #[must_use] pub const fn new(value: Dynamic) -> Self { Self { _key: Identifier::new_const(), _value: value, } } } /// Serialize a Rust type that implements [`serde::Serialize`] into a [`Dynamic`][crate::Dynamic]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_index"))] /// # #[cfg(not(feature = "no_object"))] /// # #[cfg(not(feature = "no_float"))] /// # #[cfg(not(feature = "f32_float"))] /// # { /// use rhai::{Dynamic, Array, Map}; /// use rhai::serde::to_dynamic; /// use serde::Serialize; /// /// #[derive(Debug, serde::Serialize, PartialEq)] /// struct Point { /// x: f64, /// y: f64 /// } /// /// #[derive(Debug, serde::Serialize, PartialEq)] /// struct MyStruct { /// a: i64, /// b: Vec, /// c: bool, /// d: Point /// } /// /// let x = MyStruct { /// a: 42, /// b: vec![ "hello".into(), "world".into() ], /// c: true, /// d: Point { x: 123.456, y: 999.0 } /// }; /// /// // Convert the 'MyStruct' into a 'Dynamic' /// let value = to_dynamic(x)?; /// /// assert!(value.is::()); /// /// let map = value.cast::(); /// let point = map["d"].as_map_ref().unwrap(); /// assert_eq!(*point["x"].read_lock::().unwrap(), 123.456); /// assert_eq!(*point["y"].read_lock::().unwrap(), 999.0); /// # } /// # Ok(()) /// # } /// ``` pub fn to_dynamic(value: T) -> RhaiResult { let mut s = DynamicSerializer::new(Dynamic::UNIT); value.serialize(&mut s) } impl Error for RhaiError { fn custom(err: T) -> Self { ERR::ErrorRuntime(err.to_string().into(), Position::NONE).into() } } impl Serializer for &mut DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; type SerializeSeq = DynamicSerializer; type SerializeTuple = DynamicSerializer; type SerializeTupleStruct = DynamicSerializer; #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] type SerializeTupleVariant = TupleVariantSerializer; #[cfg(any(feature = "no_object", feature = "no_index"))] type SerializeTupleVariant = serde::ser::Impossible; type SerializeMap = DynamicSerializer; type SerializeStruct = DynamicSerializer; #[cfg(not(feature = "no_object"))] type SerializeStructVariant = StructVariantSerializer; #[cfg(feature = "no_object")] type SerializeStructVariant = serde::ser::Impossible; #[inline(always)] fn serialize_bool(self, v: bool) -> RhaiResultOf { Ok(v.into()) } #[inline(always)] fn serialize_i8(self, v: i8) -> RhaiResultOf { Ok(INT::from(v).into()) } #[inline(always)] fn serialize_i16(self, v: i16) -> RhaiResultOf { Ok(INT::from(v).into()) } #[inline(always)] fn serialize_i32(self, v: i32) -> RhaiResultOf { Ok(INT::from(v).into()) } #[inline] fn serialize_i64(self, v: i64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return Ok(v.into()); #[cfg(feature = "only_i32")] if v <= INT::MAX as i64 { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_i64(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn serialize_i128(self, v: i128) -> RhaiResultOf { if v <= i128::from(INT::MAX) { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_i128(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline(always)] fn serialize_u8(self, v: u8) -> RhaiResultOf { Ok(INT::from(v).into()) } #[inline(always)] fn serialize_u16(self, v: u16) -> RhaiResultOf { Ok(INT::from(v).into()) } #[inline] fn serialize_u32(self, v: u32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return Ok(Dynamic::from(v as INT)); #[cfg(feature = "only_i32")] if v <= INT::MAX as u32 { return Ok(Dynamic::from(v as INT)); } #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u32(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn serialize_u64(self, v: u64) -> RhaiResultOf { if v <= INT::MAX as u64 { return Ok(Dynamic::from(v as INT)); } #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u64(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); #[allow(unreachable_code)] Err(Error::custom(format!("integer number too large: {v}"))) } #[inline] fn serialize_u128(self, v: u128) -> RhaiResultOf { if v <= INT::MAX as u128 { return Ok(Dynamic::from(v as INT)); } #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_u128(v) { return Ok(Dynamic::from_decimal(n)); } #[cfg(not(feature = "no_float"))] return Ok(Dynamic::from_float(v as crate::FLOAT)); #[allow(unreachable_code)] Err(Error::custom(format!("integer number too large: {v}"))) } #[inline(always)] fn serialize_f32(self, v: f32) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return Ok((v as crate::FLOAT).into()); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_f32(v) { return Ok(Dynamic::from_decimal(n)); } Err(Error::custom(format!( "floating-point number is not supported: {v}" ))) } } #[inline(always)] fn serialize_f64(self, v: f64) -> RhaiResultOf { #[cfg(not(feature = "no_float"))] return Ok((v as crate::FLOAT).into()); #[allow(unreachable_code)] { #[cfg(feature = "decimal")] if let Some(n) = rust_decimal::Decimal::from_f64(v) { return Ok(Dynamic::from_decimal(n)); } Err(Error::custom(format!( "floating-point number is not supported: {v}" ))) } } #[inline(always)] fn serialize_char(self, v: char) -> RhaiResultOf { Ok(v.into()) } #[inline(always)] fn serialize_str(self, v: &str) -> RhaiResultOf { Ok(v.into()) } #[inline] fn serialize_bytes(self, _v: &[u8]) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(Dynamic::from_blob(_v.to_vec())); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "BLOB's are not supported under 'no_index'".into(), Position::NONE, ) .into()); } #[inline(always)] fn serialize_none(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } #[inline(always)] fn serialize_some(self, value: &T) -> RhaiResultOf { value.serialize(&mut *self) } #[inline(always)] fn serialize_unit(self) -> RhaiResultOf { Ok(Dynamic::UNIT) } #[inline(always)] fn serialize_unit_struct(self, _name: &'static str) -> RhaiResultOf { self.serialize_unit() } #[inline(always)] fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> RhaiResultOf { self.serialize_str(variant) } #[inline(always)] fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> RhaiResultOf { value.serialize(&mut *self) } #[inline] fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T, ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(make_variant(_variant, to_dynamic(_value)?)); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } #[inline] fn serialize_seq(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(DynamicSerializer::new(crate::Array::new().into())); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } #[inline(always)] fn serialize_tuple(self, len: usize) -> RhaiResultOf { self.serialize_seq(Some(len)) } #[inline(always)] fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> RhaiResultOf { self.serialize_seq(Some(len)) } #[inline] fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] return Ok(TupleVariantSerializer { variant: _variant, array: crate::Array::with_capacity(_len), }); #[cfg(any(feature = "no_object", feature = "no_index"))] return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported under 'no_index' or 'no_object'".into(), Position::NONE, ) .into()); } #[inline] fn serialize_map(self, _len: Option) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(DynamicSerializer::new(crate::Map::new().into())); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } #[inline(always)] fn serialize_struct( self, _name: &'static str, len: usize, ) -> RhaiResultOf { self.serialize_map(Some(len)) } #[inline] fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(StructVariantSerializer { variant: _variant, map: crate::Map::new(), }); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } } impl SerializeSeq for DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); arr.push(value); Ok(()) } #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } // Close the sequence. #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "arrays are not supported under 'no_index'".into(), Position::NONE, ) .into()); } } impl SerializeTuple for DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_element(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); arr.push(value); Ok(()) } #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } } impl SerializeTupleStruct for DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_field(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_index"))] { let value = _value.serialize(&mut *self)?; let arr = self._value.downcast_mut::().unwrap(); arr.push(value); Ok(()) } #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_index"))] return Ok(self._value); #[cfg(feature = "no_index")] return Err(ERR::ErrorMismatchDataType( "".into(), "tuples are not supported under 'no_index'".into(), Position::NONE, ) .into()); } } impl SerializeMap for DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_key(&mut self, _key: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let key = _key.serialize(&mut *self)?; self._key = key .into_immutable_string() .map_err(|typ| { ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })? .into(); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } fn serialize_value(&mut self, _value: &T) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let key = std::mem::take(&mut self._key); let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); map.insert(key, value); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } fn serialize_entry( &mut self, _key: &K, _value: &T, ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let key: Dynamic = _key.serialize(&mut *self)?; let key = key.into_immutable_string().map_err(|typ| { ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) })?; let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); map.insert(key.into(), value); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } } impl SerializeStruct for DynamicSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_field( &mut self, _key: &'static str, _value: &T, ) -> RhaiResultOf<()> { #[cfg(not(feature = "no_object"))] { let value = _value.serialize(&mut *self)?; let map = self._value.downcast_mut::().unwrap(); map.insert(_key.into(), value); Ok(()) } #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } #[inline] fn end(self) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] return Ok(self._value); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), "object maps are not supported under 'no_object'".into(), Position::NONE, ) .into()); } } #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] pub struct TupleVariantSerializer { variant: &'static str, array: crate::Array, } #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] impl serde::ser::SerializeTupleVariant for TupleVariantSerializer { type Ok = Dynamic; type Error = RhaiError; fn serialize_field(&mut self, value: &T) -> RhaiResultOf<()> { let value = to_dynamic(value)?; self.array.push(value); Ok(()) } #[inline] fn end(self) -> RhaiResultOf { Ok(make_variant(self.variant, self.array.into())) } } #[cfg(not(feature = "no_object"))] pub struct StructVariantSerializer { variant: &'static str, map: crate::Map, } #[cfg(not(feature = "no_object"))] impl serde::ser::SerializeStructVariant for StructVariantSerializer { type Ok = Dynamic; type Error = RhaiError; #[inline] fn serialize_field( &mut self, key: &'static str, value: &T, ) -> RhaiResultOf<()> { let value = to_dynamic(value)?; self.map.insert(key.into(), value); Ok(()) } #[inline] fn end(self) -> RhaiResultOf { Ok(make_variant(self.variant, self.map.into())) } } #[cfg(not(feature = "no_object"))] #[inline] fn make_variant(variant: &'static str, value: Dynamic) -> Dynamic { let mut map = crate::Map::new(); map.insert(variant.into(), value); map.into() } rhai-1.21.0/src/serde/serialize.rs000064400000000000000000000104501046102023000150560ustar 00000000000000//! Implementations of [`serde::Serialize`]. use crate::types::dynamic::Union; use crate::{Dynamic, ImmutableString, Scope}; use serde::{ser::SerializeSeq, Serialize, Serializer}; use std::iter::once; #[cfg(feature = "no_std")] use std::prelude::v1::*; #[cfg(not(feature = "no_object"))] use serde::ser::SerializeMap; #[cfg(not(feature = "no_time"))] use crate::types::dynamic::Variant; impl Serialize for Dynamic { fn serialize(&self, ser: S) -> Result { match self.0 { Union::Unit(..) => ser.serialize_unit(), Union::Bool(x, ..) => ser.serialize_bool(x), Union::Str(ref s, ..) => ser.serialize_str(s), Union::Char(c, ..) => ser.serialize_char(c), #[cfg(not(feature = "only_i32"))] Union::Int(x, ..) => ser.serialize_i64(x), #[cfg(feature = "only_i32")] Union::Int(x, ..) => ser.serialize_i32(x), #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] Union::Float(x, ..) => ser.serialize_f64(*x), #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] Union::Float(x, ..) => ser.serialize_f32(*x), #[cfg(feature = "decimal")] #[cfg(not(feature = "f32_float"))] Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; match x.to_f64() { Some(v) => ser.serialize_f64(v), None => ser.serialize_str(&x.to_string()), } } #[cfg(feature = "decimal")] #[cfg(feature = "f32_float")] Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; match x.to_f32() { Some(v) => ser.serialize_f32(v), _ => ser.serialize_str(&x.to_string()), } } #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => (**a).serialize(ser), #[cfg(not(feature = "no_index"))] Union::Blob(ref a, ..) => ser.serialize_bytes(a), #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => { let mut map = ser.serialize_map(Some(m.len()))?; m.iter().try_for_each(|(k, v)| map.serialize_entry(k, v))?; map.end() } Union::FnPtr(ref f, ..) if f.is_curried() => { ser.collect_seq(once(f.fn_name().into()).chain(f.iter_curry().cloned())) } Union::FnPtr(ref f, ..) => ser.serialize_str(f.fn_name()), #[cfg(not(feature = "no_time"))] Union::TimeStamp(ref x, ..) => ser.serialize_str(x.as_ref().type_name()), Union::Variant(ref v, ..) => ser.serialize_str((***v).type_name()), #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] Union::Shared(ref cell, ..) => cell.borrow().serialize(ser), #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] Union::Shared(ref cell, ..) => cell.read().unwrap().serialize(ser), } } } impl Serialize for ImmutableString { #[inline(always)] fn serialize(&self, ser: S) -> Result { ser.serialize_str(self) } } impl Serialize for Scope<'_> { #[inline(always)] fn serialize(&self, ser: S) -> Result { #[derive(Debug, Clone, Hash, Serialize)] struct ScopeEntry<'a> { pub name: &'a str, pub value: &'a Dynamic, #[serde(default, skip_serializing_if = "is_false")] pub is_constant: bool, } #[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(value: &bool) -> bool { !value } let mut ser = ser.serialize_seq(Some(self.len()))?; for (name, is_constant, value) in self.iter_inner() { let entry = ScopeEntry { name, value, is_constant, }; ser.serialize_element(&entry)?; } ser.end() } } rhai-1.21.0/src/tests.rs000064400000000000000000000047221046102023000131340ustar 00000000000000//! Module containing unit tests. #![cfg(test)] /// This test is to make sure no code changes increase the sizes of critical data structures. #[test] fn check_struct_sizes() { use crate::*; use std::mem::size_of; const IS_32_BIT: bool = cfg!(target_pointer_width = "32"); const PACKED: bool = cfg!(all( target_pointer_width = "32", feature = "only_i32", any(feature = "no_float", feature = "f32_float") )); const WORD_SIZE: usize = size_of::(); assert_eq!( size_of::(), if PACKED { 8 } else if IS_32_BIT { 12 } else { 16 } ); assert_eq!( size_of::>(), if PACKED { 8 } else if IS_32_BIT { 12 } else { 16 } ); assert_eq!( size_of::(), if cfg!(feature = "no_position") { 0 } else { 4 } ); assert_eq!( size_of::(), if IS_32_BIT { if cfg!(feature = "only_i32") { 2 * WORD_SIZE } else { 3 * WORD_SIZE } } else { 2 * WORD_SIZE } ); assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::(), if IS_32_BIT { 12 } else { 16 }); assert_eq!( size_of::>(), if IS_32_BIT { 12 } else { 16 } ); #[cfg(feature = "internals")] assert_eq!(size_of::(), 3 * WORD_SIZE); // The following only on 64-bit platforms if !cfg!(target_pointer_width = "64") { return; } assert_eq!(size_of::(), 24); assert_eq!( size_of::(), 48 - if cfg!(feature = "no_function") { 2 * WORD_SIZE } else { 0 } ); assert_eq!(size_of::(), 48); assert_eq!( size_of::(), 16 - if cfg!(feature = "no_position") { WORD_SIZE } else { 0 } ); assert_eq!(size_of::(), 64); assert_eq!( size_of::(), 56 - if cfg!(feature = "no_position") { WORD_SIZE } else { 0 } ); } rhai-1.21.0/src/tokenizer.rs000064400000000000000000003060161046102023000140050ustar 00000000000000//! Main module defining the lexer and parser. use rhai_codegen::expose_under_internals; use crate::engine::Precedence; use crate::func::native::OnParseTokenCallback; use crate::{Engine, Identifier, LexError, Position, SmartString, StaticVec, INT, UNSIGNED_INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ cell::RefCell, char, fmt, iter::{repeat, FusedIterator, Peekable}, rc::Rc, str::{Chars, FromStr}, }; /// _(internals)_ A type containing commands to control the tokenizer. #[derive(Debug, Clone, Eq, PartialEq, Default, Hash)] pub struct TokenizerControlBlock { /// Is the current tokenizer position within an interpolated text string? /// /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. pub is_within_text: bool, /// Global comments. #[cfg(feature = "metadata")] pub global_comments: String, /// Whitespace-compressed version of the script (if any). /// /// Set to `Some` in order to collect a compressed script. pub compressed: Option, } impl TokenizerControlBlock { /// Create a new `TokenizerControlBlock`. #[inline] #[must_use] pub const fn new() -> Self { Self { is_within_text: false, #[cfg(feature = "metadata")] global_comments: String::new(), compressed: None, } } } /// _(internals)_ A shared object that allows control of the tokenizer from outside. pub type TokenizerControl = Rc>; type LERR = LexError; /// Separator character for numbers. const NUMBER_SEPARATOR: char = '_'; /// A stream of tokens. pub type TokenStream<'a> = Peekable>; /// _(internals)_ A Rhai language token. /// Exported under the `internals` feature only. #[derive(Debug, PartialEq, Clone, Hash)] #[non_exhaustive] pub enum Token { /// An `INT` constant. IntegerConstant(INT), /// A `FLOAT` constant, including its text representation. /// /// Reserved under the `no_float` feature. #[cfg(not(feature = "no_float"))] FloatConstant(Box<(crate::types::FloatWrapper, Identifier)>), /// A [`Decimal`][rust_decimal::Decimal] constant. /// /// Requires the `decimal` feature, including its text representation. #[cfg(feature = "decimal")] DecimalConstant(Box<(rust_decimal::Decimal, Identifier)>), /// An identifier. Identifier(Box), /// A character constant. CharConstant(char), /// A string constant. StringConstant(Box), /// An interpolated string. InterpolatedString(Box), /// `{` LeftBrace, /// `}` RightBrace, /// `(` LeftParen, /// `)` RightParen, /// `[` LeftBracket, /// `]` RightBracket, /// `()` Unit, /// `+` Plus, /// `+` (unary) UnaryPlus, /// `-` Minus, /// `-` (unary) UnaryMinus, /// `*` Multiply, /// `/` Divide, /// `%` Modulo, /// `**` PowerOf, /// `<<` LeftShift, /// `>>` RightShift, /// `;` SemiColon, /// `:` Colon, /// `::` DoubleColon, /// `=>` DoubleArrow, /// `_` Underscore, /// `,` Comma, /// `.` Period, /// `?.` /// /// Reserved under the `no_object` feature. #[cfg(not(feature = "no_object"))] Elvis, /// `??` DoubleQuestion, /// `?[` /// /// Reserved under the `no_object` feature. #[cfg(not(feature = "no_index"))] QuestionBracket, /// `..` ExclusiveRange, /// `..=` InclusiveRange, /// `#{` MapStart, /// `=` Equals, /// `true` True, /// `false` False, /// `let` Let, /// `const` Const, /// `if` If, /// `else` Else, /// `switch` Switch, /// `do` Do, /// `while` While, /// `until` Until, /// `loop` Loop, /// `for` For, /// `in` In, /// `!in` NotIn, /// `<` LessThan, /// `>` GreaterThan, /// `<=` LessThanEqualsTo, /// `>=` GreaterThanEqualsTo, /// `==` EqualsTo, /// `!=` NotEqualsTo, /// `!` Bang, /// `|` Pipe, /// `||` Or, /// `^` XOr, /// `&` Ampersand, /// `&&` And, /// `fn` /// /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Fn, /// `continue` Continue, /// `break` Break, /// `return` Return, /// `throw` Throw, /// `try` Try, /// `catch` Catch, /// `+=` PlusAssign, /// `-=` MinusAssign, /// `*=` MultiplyAssign, /// `/=` DivideAssign, /// `<<=` LeftShiftAssign, /// `>>=` RightShiftAssign, /// `&=` AndAssign, /// `|=` OrAssign, /// `^=` XOrAssign, /// `%=` ModuloAssign, /// `**=` PowerOfAssign, /// `private` /// /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Private, /// `import` /// /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Import, /// `export` /// /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Export, /// `as` /// /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] As, /// A lexer error. LexError(Box), /// A comment block. Comment(Box), /// A reserved symbol. Reserved(Box), /// A custom keyword. /// /// Not available under `no_custom_syntax`. #[cfg(not(feature = "no_custom_syntax"))] Custom(Box), /// End of the input stream. /// Used as a placeholder for the end of input. EOF, } impl fmt::Display for Token { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[allow(clippy::enum_glob_use)] use Token::*; match self { IntegerConstant(i) => write!(f, "{i}"), #[cfg(not(feature = "no_float"))] FloatConstant(v) => write!(f, "{}", v.0), #[cfg(feature = "decimal")] DecimalConstant(d) => write!(f, "{}", d.0), StringConstant(s) => write!(f, r#""{s}""#), InterpolatedString(..) => f.write_str("string"), CharConstant(c) => write!(f, "{c}"), Identifier(s) => f.write_str(s), Reserved(s) => f.write_str(s), #[cfg(not(feature = "no_custom_syntax"))] Custom(s) => f.write_str(s), LexError(err) => write!(f, "{err}"), Comment(s) => f.write_str(s), EOF => f.write_str("{EOF}"), token => f.write_str(token.literal_syntax()), } } } // Table-driven keyword recognizer generated by GNU `gperf` on the file `tools/keywords.txt`. // // When adding new keywords, make sure to update `tools/keywords.txt` and re-generate this. const MIN_KEYWORD_LEN: usize = 1; const MAX_KEYWORD_LEN: usize = 8; const MIN_KEYWORD_HASH_VALUE: usize = 1; const MAX_KEYWORD_HASH_VALUE: usize = 152; static KEYWORD_ASSOC_VALUES: [u8; 257] = [ 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 115, 153, 100, 153, 110, 105, 40, 80, 2, 20, 25, 125, 95, 15, 40, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 55, 35, 10, 5, 0, 30, 110, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 120, 105, 100, 85, 90, 153, 125, 5, 0, 125, 35, 10, 100, 153, 20, 0, 153, 10, 0, 45, 55, 0, 153, 50, 55, 5, 0, 153, 0, 0, 35, 153, 45, 50, 30, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, ]; static KEYWORDS_LIST: [(&str, Token); 153] = [ ("", Token::EOF), (">", Token::GreaterThan), (">=", Token::GreaterThanEqualsTo), (")", Token::RightParen), ("", Token::EOF), ("const", Token::Const), ("=", Token::Equals), ("==", Token::EqualsTo), ("continue", Token::Continue), ("", Token::EOF), ("catch", Token::Catch), ("<", Token::LessThan), ("<=", Token::LessThanEqualsTo), ("for", Token::For), ("loop", Token::Loop), ("", Token::EOF), (".", Token::Period), ("<<", Token::LeftShift), ("<<=", Token::LeftShiftAssign), ("", Token::EOF), ("false", Token::False), ("*", Token::Multiply), ("*=", Token::MultiplyAssign), ("let", Token::Let), ("", Token::EOF), ("while", Token::While), ("+", Token::Plus), ("+=", Token::PlusAssign), ("", Token::EOF), ("", Token::EOF), ("throw", Token::Throw), ("}", Token::RightBrace), (">>", Token::RightShift), (">>=", Token::RightShiftAssign), ("", Token::EOF), ("", Token::EOF), (";", Token::SemiColon), ("=>", Token::DoubleArrow), ("", Token::EOF), ("else", Token::Else), ("", Token::EOF), ("/", Token::Divide), ("/=", Token::DivideAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("{", Token::LeftBrace), ("**", Token::PowerOf), ("**=", Token::PowerOfAssign), ("", Token::EOF), ("", Token::EOF), ("|", Token::Pipe), ("|=", Token::OrAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), (":", Token::Colon), ("..", Token::ExclusiveRange), ("..=", Token::InclusiveRange), ("", Token::EOF), ("until", Token::Until), ("switch", Token::Switch), #[cfg(not(feature = "no_function"))] ("private", Token::Private), #[cfg(feature = "no_function")] ("", Token::EOF), ("try", Token::Try), ("true", Token::True), ("break", Token::Break), ("return", Token::Return), #[cfg(not(feature = "no_function"))] ("fn", Token::Fn), #[cfg(feature = "no_function")] ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), #[cfg(not(feature = "no_module"))] ("import", Token::Import), #[cfg(feature = "no_module")] ("", Token::EOF), #[cfg(not(feature = "no_object"))] ("?.", Token::Elvis), #[cfg(feature = "no_object")] ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), #[cfg(not(feature = "no_module"))] ("export", Token::Export), #[cfg(feature = "no_module")] ("", Token::EOF), ("in", Token::In), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("(", Token::LeftParen), ("||", Token::Or), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("^", Token::XOr), ("^=", Token::XOrAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("_", Token::Underscore), ("::", Token::DoubleColon), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("-", Token::Minus), ("-=", Token::MinusAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("]", Token::RightBracket), ("()", Token::Unit), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("&", Token::Ampersand), ("&=", Token::AndAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("%", Token::Modulo), ("%=", Token::ModuloAssign), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("!", Token::Bang), ("!=", Token::NotEqualsTo), ("!in", Token::NotIn), ("", Token::EOF), ("", Token::EOF), ("[", Token::LeftBracket), ("if", Token::If), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), (",", Token::Comma), ("do", Token::Do), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), #[cfg(not(feature = "no_module"))] ("as", Token::As), #[cfg(feature = "no_module")] ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), #[cfg(not(feature = "no_index"))] ("?[", Token::QuestionBracket), #[cfg(feature = "no_index")] ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("??", Token::DoubleQuestion), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("&&", Token::And), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("", Token::EOF), ("#{", Token::MapStart), ]; // Table-driven reserved symbol recognizer generated by GNU `gperf` on the file `tools/reserved.txt`. // // When adding new reserved symbols, make sure to update `tools/reserved.txt` and re-generate this. const MIN_RESERVED_LEN: usize = 1; const MAX_RESERVED_LEN: usize = 10; const MIN_RESERVED_HASH_VALUE: usize = 1; const MAX_RESERVED_HASH_VALUE: usize = 149; static RESERVED_ASSOC_VALUES: [u8; 256] = [ 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 10, 150, 5, 35, 150, 150, 150, 45, 35, 30, 30, 150, 20, 15, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 35, 30, 15, 5, 25, 0, 25, 150, 150, 150, 150, 150, 65, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 40, 150, 150, 150, 150, 150, 0, 150, 0, 0, 0, 15, 45, 10, 15, 150, 150, 35, 25, 10, 50, 0, 150, 5, 0, 15, 0, 5, 25, 45, 15, 150, 150, 25, 150, 20, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, ]; static RESERVED_LIST: [(&str, bool, bool, bool); 150] = [ ("", false, false, false), ("?", true, false, false), ("as", cfg!(feature = "no_module"), false, false), ("use", true, false, false), ("case", true, false, false), ("async", true, false, false), ("public", true, false, false), ("package", true, false, false), ("", false, false, false), ("", false, false, false), ("super", true, false, false), ("#", true, false, false), ("private", cfg!(feature = "no_function"), false, false), ("var", true, false, false), ("protected", true, false, false), ("spawn", true, false, false), ("shared", true, false, false), ("is", true, false, false), ("===", true, false, false), ("sync", true, false, false), ("curry", true, true, true), ("static", true, false, false), ("default", true, false, false), ("!==", true, false, false), ("is_shared", cfg!(not(feature = "no_closure")), true, true), ("print", true, true, false), ("", false, false, false), ("#!", true, false, false), ("", false, false, false), ("this", true, false, false), ("is_def_var", true, true, false), ("thread", true, false, false), ("?.", cfg!(feature = "no_object"), false, false), ("", false, false, false), ("is_def_fn", cfg!(not(feature = "no_function")), true, false), ("yield", true, false, false), ("", false, false, false), ("fn", cfg!(feature = "no_function"), false, false), ("new", true, false, false), ("call", true, true, true), ("match", true, false, false), ("~", true, false, false), ("!.", true, false, false), ("", false, false, false), ("eval", true, true, false), ("await", true, false, false), ("", false, false, false), (":=", true, false, false), ("...", true, false, false), ("null", true, false, false), ("debug", true, true, false), ("@", true, false, false), ("type_of", true, true, true), ("", false, false, false), ("with", true, false, false), ("", false, false, false), ("", false, false, false), ("<-", true, false, false), ("", false, false, false), ("void", true, false, false), ("", false, false, false), ("import", cfg!(feature = "no_module"), false, false), ("--", true, false, false), ("nil", true, false, false), ("exit", false, false, false), ("", false, false, false), ("export", cfg!(feature = "no_module"), false, false), ("<|", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("$", true, false, false), ("->", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("|>", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("module", true, false, false), ("?[", cfg!(feature = "no_index"), false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("Fn", true, true, false), ("::<", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("++", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), (":;", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("*)", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("(*", true, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("", false, false, false), ("go", true, false, false), ("", false, false, false), ("goto", true, false, false), ]; impl Token { /// Is the token a literal symbol? #[must_use] pub const fn is_literal(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; match self { IntegerConstant(..) => false, #[cfg(not(feature = "no_float"))] FloatConstant(..) => false, #[cfg(feature = "decimal")] DecimalConstant(..) => false, StringConstant(..) | InterpolatedString(..) | CharConstant(..) | Identifier(..) | Reserved(..) => false, #[cfg(not(feature = "no_custom_syntax"))] Custom(..) => false, LexError(..) | Comment(..) => false, EOF => false, _ => true, } } /// Get the literal syntax of the token. /// /// # Panics /// /// Panics if the token is not a literal symbol. #[must_use] pub const fn literal_syntax(&self) -> &'static str { #[allow(clippy::enum_glob_use)] use Token::*; match self { LeftBrace => "{", RightBrace => "}", LeftParen => "(", RightParen => ")", LeftBracket => "[", RightBracket => "]", Unit => "()", Plus => "+", UnaryPlus => "+", Minus => "-", UnaryMinus => "-", Multiply => "*", Divide => "/", SemiColon => ";", Colon => ":", DoubleColon => "::", DoubleArrow => "=>", Underscore => "_", Comma => ",", Period => ".", #[cfg(not(feature = "no_object"))] Elvis => "?.", DoubleQuestion => "??", #[cfg(not(feature = "no_index"))] QuestionBracket => "?[", ExclusiveRange => "..", InclusiveRange => "..=", MapStart => "#{", Equals => "=", True => "true", False => "false", Let => "let", Const => "const", If => "if", Else => "else", Switch => "switch", Do => "do", While => "while", Until => "until", Loop => "loop", For => "for", In => "in", NotIn => "!in", LessThan => "<", GreaterThan => ">", Bang => "!", LessThanEqualsTo => "<=", GreaterThanEqualsTo => ">=", EqualsTo => "==", NotEqualsTo => "!=", Pipe => "|", Or => "||", Ampersand => "&", And => "&&", Continue => "continue", Break => "break", Return => "return", Throw => "throw", Try => "try", Catch => "catch", PlusAssign => "+=", MinusAssign => "-=", MultiplyAssign => "*=", DivideAssign => "/=", LeftShiftAssign => "<<=", RightShiftAssign => ">>=", AndAssign => "&=", OrAssign => "|=", XOrAssign => "^=", LeftShift => "<<", RightShift => ">>", XOr => "^", Modulo => "%", ModuloAssign => "%=", PowerOf => "**", PowerOfAssign => "**=", #[cfg(not(feature = "no_function"))] Fn => "fn", #[cfg(not(feature = "no_function"))] Private => "private", #[cfg(not(feature = "no_module"))] Import => "import", #[cfg(not(feature = "no_module"))] Export => "export", #[cfg(not(feature = "no_module"))] As => "as", _ => panic!("token is not a literal symbol"), } } /// Is this token an op-assignment operator? #[inline] #[must_use] pub const fn is_op_assignment(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; matches!( self, PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | ModuloAssign | PowerOfAssign | AndAssign | OrAssign | XOrAssign ) } /// Get the corresponding operator of the token if it is an op-assignment operator. #[must_use] pub const fn get_base_op_from_assignment(&self) -> Option { #[allow(clippy::enum_glob_use)] use Token::*; Some(match self { PlusAssign => Plus, MinusAssign => Minus, MultiplyAssign => Multiply, DivideAssign => Divide, LeftShiftAssign => LeftShift, RightShiftAssign => RightShift, ModuloAssign => Modulo, PowerOfAssign => PowerOf, AndAssign => Ampersand, OrAssign => Pipe, XOrAssign => XOr, _ => return None, }) } /// Has this token a corresponding op-assignment operator? #[inline] #[must_use] pub const fn has_op_assignment(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; matches!( self, Plus | Minus | Multiply | Divide | LeftShift | RightShift | Modulo | PowerOf | Ampersand | Pipe | XOr ) } /// Get the corresponding op-assignment operator of the token. #[must_use] pub const fn convert_to_op_assignment(&self) -> Option { #[allow(clippy::enum_glob_use)] use Token::*; Some(match self { Plus => PlusAssign, Minus => MinusAssign, Multiply => MultiplyAssign, Divide => DivideAssign, LeftShift => LeftShiftAssign, RightShift => RightShiftAssign, Modulo => ModuloAssign, PowerOf => PowerOfAssign, Ampersand => AndAssign, Pipe => OrAssign, XOr => XOrAssign, _ => return None, }) } /// Reverse lookup a symbol token from a piece of syntax. #[inline] #[must_use] pub fn lookup_symbol_from_syntax(syntax: &str) -> Option { // This implementation is based upon a pre-calculated table generated // by GNU `gperf` on the list of keywords. let utf8 = syntax.as_bytes(); let len = utf8.len(); if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) { return None; } let mut hash_val = len; match len { 1 => (), _ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize, } hash_val += KEYWORD_ASSOC_VALUES[utf8[0] as usize] as usize; if !(MIN_KEYWORD_HASH_VALUE..=MAX_KEYWORD_HASH_VALUE).contains(&hash_val) { return None; } match KEYWORDS_LIST[hash_val] { (_, Self::EOF) => None, // Fail early to avoid calling memcmp(). // Since we are already working with bytes, mind as well check the first one. (s, ref t) if s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax => { Some(t.clone()) } _ => None, } } /// If another operator is after these, it's probably a unary operator /// (not sure about `fn` name). #[must_use] pub const fn is_next_unary(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; match self { SemiColon | // ; - is unary Colon | // #{ foo: - is unary Comma | // ( ... , -expr ) - is unary //Period | //Elvis | DoubleQuestion | // ?? - is unary ExclusiveRange | // .. - is unary InclusiveRange | // ..= - is unary LeftBrace | // { -expr } - is unary // RightBrace | // { expr } - expr not unary & is closing LeftParen | // ( -expr ) - is unary // RightParen | // ( expr ) - expr not unary & is closing LeftBracket | // [ -expr ] - is unary // RightBracket | // [ expr ] - expr not unary & is closing Plus | PlusAssign | UnaryPlus | Minus | MinusAssign | UnaryMinus | Multiply | MultiplyAssign | Divide | DivideAssign | Modulo | ModuloAssign | PowerOf | PowerOfAssign | LeftShift | LeftShiftAssign | RightShift | RightShiftAssign | Equals | EqualsTo | NotEqualsTo | LessThan | GreaterThan | Bang | LessThanEqualsTo | GreaterThanEqualsTo | Pipe | Ampersand | If | //Do | While | Until | In | NotIn | And | AndAssign | Or | OrAssign | XOr | XOrAssign | Return | Throw => true, #[cfg(not(feature = "no_index"))] QuestionBracket => true, // ?[ - is unary LexError(..) => true, _ => false, } } /// Get the precedence number of the token. #[must_use] pub const fn precedence(&self) -> Option { #[allow(clippy::enum_glob_use)] use Token::*; Precedence::new(match self { Or | XOr | Pipe => 30, And | Ampersand => 60, EqualsTo | NotEqualsTo => 90, In | NotIn => 110, LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130, DoubleQuestion => 135, ExclusiveRange | InclusiveRange => 140, Plus | Minus => 150, Divide | Multiply | Modulo => 180, PowerOf => 190, LeftShift | RightShift => 210, _ => 0, }) } /// Does an expression bind to the right (instead of left)? #[must_use] pub const fn is_bind_right(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; match self { // Exponentiation binds to the right PowerOf => true, _ => false, } } /// Is this token a standard symbol used in the language? #[must_use] pub const fn is_standard_symbol(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; match self { LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus | UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | DoubleQuestion | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true, #[cfg(not(feature = "no_object"))] Elvis => true, #[cfg(not(feature = "no_index"))] QuestionBracket => true, _ => false, } } /// Is this token a standard keyword? #[inline] #[must_use] pub const fn is_standard_keyword(&self) -> bool { #[allow(clippy::enum_glob_use)] use Token::*; match self { #[cfg(not(feature = "no_function"))] Fn | Private => true, #[cfg(not(feature = "no_module"))] Import | Export | As => true, True | False | Let | Const | If | Else | Do | While | Until | Loop | For | In | Continue | Break | Return | Throw | Try | Catch => true, _ => false, } } /// Is this token a reserved keyword or symbol? #[inline(always)] #[must_use] pub const fn is_reserved(&self) -> bool { matches!(self, Self::Reserved(..)) } /// Is this token a custom keyword? #[cfg(not(feature = "no_custom_syntax"))] #[inline(always)] #[must_use] pub const fn is_custom(&self) -> bool { matches!(self, Self::Custom(..)) } } impl From for String { #[inline(always)] fn from(token: Token) -> Self { token.to_string() } } /// _(internals)_ State of the tokenizer. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string. /// /// Not available under `unchecked`. #[cfg(not(feature = "unchecked"))] pub max_string_len: Option, /// Can the next token be a unary operator? pub next_token_cannot_be_unary: bool, /// Shared object to allow controlling the tokenizer externally. pub tokenizer_control: TokenizerControl, /// Is the tokenizer currently inside a block comment? pub comment_level: usize, /// Include comments? pub include_comments: bool, /// Is the current tokenizer position within the text stream of an interpolated string? pub is_within_text_terminated_by: Option, /// Textual syntax of the current token, if any. /// /// Set to `Some` to begin tracking this information. pub last_token: Option, } /// _(internals)_ Trait that encapsulates a peekable character input stream. /// Exported under the `internals` feature only. pub trait InputStream { /// Un-get a character back into the `InputStream`. /// The next [`get_next`][InputStream::get_next] or [`peek_next`][InputStream::peek_next] /// will return this character instead. fn unget(&mut self, ch: char); /// Get the next character from the `InputStream`. fn get_next(&mut self) -> Option; /// Peek the next character in the `InputStream`. #[must_use] fn peek_next(&mut self) -> Option; /// Consume the next character. #[inline(always)] fn eat_next_and_advance(&mut self, pos: &mut Position) -> Option { pos.advance(); self.get_next() } } /// _(internals)_ Parse a raw string literal. Exported under the `internals` feature only. /// /// Raw string literals do not process any escapes. They start with the character `#` (`U+0023`) /// repeated any number of times, then finally a `"` (`U+0022`, double-quote). /// /// The raw string _body_ can contain any sequence of Unicode characters. It is terminated only by /// another `"` (`U+0022`, double-quote) character, followed by the same number of `#` (`U+0023`) /// characters. /// /// All Unicode characters contained in the raw string body represent themselves, including the /// characters `"` (`U+0022`, double-quote), except when followed by at least as many `#` (`U+0023`) /// characters as were used to start the raw string literal, `\` (`U+005C`) etc., and do not have /// any special meaning. /// /// Returns the parsed string. /// /// # Returns /// /// | Type | Return Value |`state.is_within_text_terminated_by` | /// |---------------------------|:-----------------------------------:|:------------------------------------:| /// |`#"hello"#` |`StringConstant("hello")` |`None` | /// |`#"hello`_{EOF}_ |`StringConstant("hello")` |`Some("#")` | /// |`####"hello`_{EOF}_ |`StringConstant("hello")` |`Some("####")` | /// |`#" "hello" "`_{EOF}_ |`LexError` |`None` | /// |`#""hello""#` |`StringConstant("\"hello\"")` |`None` | /// |`##"hello #"# world"##` |`StringConstant("hello #\"# world")` |`None` | /// |`#"R"#` |`StringConstant("R")` |`None` | /// |`#"\x52"#` |`StringConstant("\\x52")` |`None` | /// /// This function does _not_ throw a `LexError` for an unterminated raw string at _{EOF}_ /// /// This is to facilitate using this function to parse a script line-by-line, where the end of the /// line (i.e. _{EOF}_) is not necessarily the end of the script. /// /// Any time a [`StringConstant`][`Token::StringConstant`] is returned with /// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions. pub fn parse_raw_string_literal( stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, mut hash_count: usize, ) -> Result<(SmartString, Position), (LexError, Position)> { let start = *pos; let mut first_char = Position::NONE; if hash_count == 0 { // Count the number of '#'s // Start with 1 because the first '#' is already consumed hash_count = 1; while let Some('#') = stream.peek_next() { stream.eat_next_and_advance(pos); hash_count += 1; } // Match '"' match stream.get_next() { Some('"') => pos.advance(), Some(c) => return Err((LERR::UnexpectedInput(c.to_string()), start)), None => return Err((LERR::UnterminatedString, start)), } } let collect: SmartString = repeat('#').take(hash_count).collect(); if let Some(ref mut last) = state.last_token { last.clear(); last.push_str(&collect); last.push('"'); } state.is_within_text_terminated_by = Some(collect); // Match everything until the same number of '#'s are seen, prepended by a '"' // Counts the number of '#' characters seen after a quotation mark. // Becomes Some(0) after a quote is seen, but resets to None if a hash doesn't follow. let mut seen_hashes: Option = None; let mut result = SmartString::new_const(); loop { let next_char = match stream.get_next() { Some(ch) => ch, None => break, // Allow unterminated string }; pos.advance(); match (next_char, &mut seen_hashes) { // Begin attempt to close string ('"', None) => seen_hashes = Some(0), // Restart attempt to close string ('"', Some(count)) => { // result.reserve(*count as usize+c.len()); result.push('"'); result.extend(repeat('#').take(*count as usize)); seen_hashes = Some(0); } // Continue attempt to close string ('#', Some(count)) => { *count += 1; if *count == hash_count { state.is_within_text_terminated_by = None; break; } } // Fail to close the string - add previous quote and hashes (c, Some(count)) => { // result.reserve(*count as usize +1+c.len()); result.push('"'); result.extend(repeat('#').take(*count as usize)); result.push(c); seen_hashes = None; } // New line ('\n', _) => { result.push('\n'); pos.new_line(); } // Normal new character seen (c, None) => result.push(c), } // Check string length #[cfg(not(feature = "unchecked"))] if let Some(max) = state.max_string_len { if result.len() > max.get() { return Err((LexError::StringTooLong(max.get()), start)); } } if first_char.is_none() { first_char = *pos; } } Ok((result, first_char)) } /// _(internals)_ Parse a string literal ended by a specified termination character. /// Exported under the `internals` feature only. /// /// Returns the parsed string and a boolean indicating whether the string is /// terminated by an interpolation `${`. /// /// # Returns /// /// | Type | Return Value |`state.is_within_text_terminated_by`| /// |---------------------------------|:--------------------------:|:----------------------------------:| /// |`"hello"` |`StringConstant("hello")` |`None` | /// |`"hello`_{LF}_ or _{EOF}_ |`LexError` |`None` | /// |`"hello\`_{EOF}_ or _{LF}{EOF}_ |`StringConstant("hello")` |`Some('"')` | /// |`` `hello``_{EOF}_ |`StringConstant("hello")` |``Some('`')`` | /// |`` `hello``_{LF}{EOF}_ |`StringConstant("hello\n")` |``Some('`')`` | /// |`` `hello ${`` |`InterpolatedString("hello ")`
next token is `{`|`None` | /// |`` } hello` `` |`StringConstant(" hello")` |`None` | /// |`} hello`_{EOF}_ |`StringConstant(" hello")` |``Some('`')`` | /// /// This function does not throw a `LexError` for the following conditions: /// /// * Unterminated literal string at _{EOF}_ /// /// * Unterminated normal string with continuation at _{EOF}_ /// /// This is to facilitate using this function to parse a script line-by-line, where the end of the /// line (i.e. _{EOF}_) is not necessarily the end of the script. /// /// Any time a [`StringConstant`][`Token::StringConstant`] is returned with /// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions. pub fn parse_string_literal( stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, termination_char: char, verbatim: bool, allow_line_continuation: bool, allow_interpolation: bool, ) -> Result<(SmartString, bool, Position), (LexError, Position)> { let mut result = SmartString::new_const(); let mut escape = SmartString::new_const(); let start = *pos; let mut first_char = Position::NONE; let mut interpolated = false; #[cfg(not(feature = "no_position"))] let mut skip_space_until = 0; state.is_within_text_terminated_by = Some(termination_char.to_string().into()); if let Some(ref mut last) = state.last_token { last.clear(); last.push(termination_char); } loop { debug_assert!( !verbatim || escape.is_empty(), "verbatim strings should not have any escapes" ); let next_char = match stream.get_next() { Some(ch) => { pos.advance(); ch } None if verbatim => { debug_assert_eq!(escape, "", "verbatim strings should not have any escapes"); pos.advance(); break; } None if allow_line_continuation && !escape.is_empty() => { debug_assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); pos.advance(); break; } None => { pos.advance(); state.is_within_text_terminated_by = None; return Err((LERR::UnterminatedString, start)); } }; if let Some(ref mut last) = state.last_token { last.push(next_char); } // String interpolation? if allow_interpolation && next_char == '$' && escape.is_empty() && stream.peek_next().map_or(false, |ch| ch == '{') { interpolated = true; state.is_within_text_terminated_by = None; break; } // Check string length #[cfg(not(feature = "unchecked"))] if let Some(max) = state.max_string_len { if result.len() > max.get() { return Err((LexError::StringTooLong(max.get()), start)); } } // Close wrapper if termination_char == next_char && escape.is_empty() { // Double wrapper if stream.peek_next().map_or(false, |c| c == termination_char) { stream.eat_next_and_advance(pos); if let Some(ref mut last) = state.last_token { last.push(termination_char); } } else { state.is_within_text_terminated_by = None; break; } } if first_char.is_none() { first_char = *pos; } match next_char { // \r - ignore if followed by \n '\r' if stream.peek_next().map_or(false, |ch| ch == '\n') => (), // \r 'r' if !escape.is_empty() => { escape.clear(); result.push_str("\r"); } // \n 'n' if !escape.is_empty() => { escape.clear(); result.push_str("\n"); } // \... '\\' if !verbatim && escape.is_empty() => { escape.push_str("\\"); } // \\ '\\' if !escape.is_empty() => { escape.clear(); result.push_str("\\"); } // \t 't' if !escape.is_empty() => { escape.clear(); result.push_str("\t"); } // \x??, \u????, \U???????? ch @ ('x' | 'u' | 'U') if !escape.is_empty() => { let mut seq = escape.clone(); escape.clear(); seq.push(ch); let mut out_val: u32 = 0; let len = match ch { 'x' => 2, 'u' => 4, 'U' => 8, c => unreachable!("x or u or U expected but gets '{}'", c), }; for _ in 0..len { let c = stream .get_next() .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; pos.advance(); seq.push(c); if let Some(ref mut last) = state.last_token { last.push(c); } out_val *= 16; out_val += c .to_digit(16) .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; } result.push( char::from_u32(out_val) .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?, ); } // LF - Verbatim '\n' if verbatim => { debug_assert_eq!(escape, "", "verbatim strings should not have any escapes"); pos.new_line(); result.push_str("\n"); } // LF - Line continuation '\n' if allow_line_continuation && !escape.is_empty() => { debug_assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); escape.clear(); pos.new_line(); #[cfg(not(feature = "no_position"))] { let start_position = start.position().unwrap(); skip_space_until = start_position + 1; } } // LF - Unterminated string '\n' => { pos.rewind(); state.is_within_text_terminated_by = None; return Err((LERR::UnterminatedString, start)); } // \{termination_char} - escaped termination character ch if termination_char == ch && !escape.is_empty() => { escape.clear(); result.push(termination_char); } // Unknown escape sequence ch if !escape.is_empty() => { escape.push(ch); return Err((LERR::MalformedEscapeSequence(escape.to_string()), *pos)); } // Whitespace to skip #[cfg(not(feature = "no_position"))] ch if ch.is_whitespace() && pos.position().unwrap() < skip_space_until => (), // All other characters ch => { escape.clear(); result.push(ch); #[cfg(not(feature = "no_position"))] { skip_space_until = 0; } } } } // Check string length #[cfg(not(feature = "unchecked"))] if let Some(max) = state.max_string_len { if result.len() > max.get() { return Err((LexError::StringTooLong(max.get()), start)); } } Ok((result, interpolated, first_char)) } /// Scan for a block comment until the end. fn scan_block_comment( stream: &mut (impl InputStream + ?Sized), level: usize, pos: &mut Position, comment: Option<&mut String>, ) -> usize { let mut level = level; let mut comment = comment; while let Some(c) = stream.get_next() { pos.advance(); if let Some(comment) = comment.as_mut() { comment.push(c); } match c { '/' => { if let Some(c2) = stream.peek_next().filter(|&ch| ch == '*') { stream.eat_next_and_advance(pos); if let Some(comment) = comment.as_mut() { comment.push(c2); } level += 1; } } '*' => { if let Some(c2) = stream.peek_next().filter(|&ch| ch == '/') { stream.eat_next_and_advance(pos); if let Some(comment) = comment.as_mut() { comment.push(c2); } level -= 1; } } '\n' => pos.new_line(), _ => (), } if level == 0 { break; } } level } /// Test if the given character is a hex character. #[inline(always)] const fn is_hex_digit(c: char) -> bool { c.is_ascii_hexdigit() } /// Test if the given character is a numeric digit (i.e. 0-9). #[inline(always)] const fn is_numeric_digit(c: char) -> bool { c.is_ascii_digit() } /// Test if the given character is an octal digit (i.e. 0-7). #[inline(always)] const fn is_octal_digit(c: char) -> bool { matches!(c, '0'..='7') } /// Test if the given character is a binary digit (i.e. 0 or 1). #[inline(always)] const fn is_binary_digit(c: char) -> bool { c == '0' || c == '1' } /// Test if the comment block is a doc-comment. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] #[inline] #[must_use] pub fn is_doc_comment(comment: &str) -> bool { (comment.starts_with("///") && !comment.starts_with("////")) || (comment.starts_with("/**") && !comment.starts_with("/***")) } /// _(internals)_ Get the next token from the input stream. /// Exported under the `internals` feature only. #[inline(always)] #[must_use] pub fn get_next_token( stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, ) -> (Token, Position) { let result = get_next_token_inner(stream, state, pos); // Save the last token's state state.next_token_cannot_be_unary = !result.0.is_next_unary(); result } /// Get the next token. #[must_use] fn get_next_token_inner( stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, ) -> (Token, Position) { state.last_token.as_mut().map(SmartString::clear); // Still inside a comment? if state.comment_level > 0 { let start_pos = *pos; let mut comment = String::new(); let comment_buf = state.include_comments.then_some(&mut comment); state.comment_level = scan_block_comment(stream, state.comment_level, pos, comment_buf); let return_comment = state.include_comments; #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] let return_comment = return_comment || is_doc_comment(&comment); if return_comment { return (Token::Comment(comment.into()), start_pos); } // Reached EOF without ending comment block? if state.comment_level > 0 { return (Token::EOF, *pos); } } // Within text? match state.is_within_text_terminated_by.take() { Some(ch) if ch.starts_with('#') => { return parse_raw_string_literal(stream, state, pos, ch.len()).map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, start_pos)| (Token::StringConstant(result.into()), start_pos), ) } Some(ch) => { let c = ch.chars().next().unwrap(); return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, interpolated, start_pos)| { if interpolated { (Token::InterpolatedString(result.into()), start_pos) } else { (Token::StringConstant(result.into()), start_pos) } }, ); } None => (), } let mut negated: Option = None; while let Some(c) = stream.get_next() { pos.advance(); let start_pos = *pos; let cc = stream.peek_next().unwrap_or('\0'); // Identifiers and strings that can have non-ASCII characters match (c, cc) { // digit ... ('0'..='9', ..) => { let mut result = SmartString::new_const(); let mut radix_base: Option = None; let mut valid: fn(char) -> bool = is_numeric_digit; let mut _has_period = false; let mut _has_e = false; result.push(c); while let Some(next_char) = stream.peek_next() { match next_char { NUMBER_SEPARATOR => { stream.eat_next_and_advance(pos); } ch if valid(ch) => { result.push(ch); stream.eat_next_and_advance(pos); } #[cfg(any(not(feature = "no_float"), feature = "decimal"))] '.' if !_has_period && radix_base.is_none() => { stream.get_next().unwrap(); // Check if followed by digits or something that cannot start a property name match stream.peek_next() { // digits after period - accept the period Some('0'..='9') => { result.push_str("."); pos.advance(); _has_period = true; } // _ - cannot follow a decimal point Some(NUMBER_SEPARATOR) => { stream.unget('.'); break; } // .. - reserved symbol, not a floating-point number Some('.') => { stream.unget('.'); break; } // symbol after period - probably a float Some(ch) if !is_id_first_alphabetic(ch) => { result.push_str("."); pos.advance(); result.push_str("0"); _has_period = true; } // Not a floating-point number _ => { stream.unget('.'); break; } } } #[cfg(not(feature = "no_float"))] 'e' if !_has_e && radix_base.is_none() => { stream.get_next().unwrap(); // Check if followed by digits or +/- match stream.peek_next() { // digits after e - accept the e (no decimal points allowed) Some('0'..='9') => { result.push_str("e"); pos.advance(); _has_e = true; _has_period = true; } // +/- after e - accept the e and the sign (no decimal points allowed) Some('+' | '-') => { result.push_str("e"); pos.advance(); result.push(stream.get_next().unwrap()); pos.advance(); _has_e = true; _has_period = true; } // Not a floating-point number _ => { stream.unget('e'); break; } } } // 0x????, 0o????, 0b???? at beginning ch @ ('x' | 'o' | 'b' | 'X' | 'O' | 'B') if c == '0' && result.len() <= 1 => { result.push(ch); stream.eat_next_and_advance(pos); valid = match ch { 'x' | 'X' => is_hex_digit, 'o' | 'O' => is_octal_digit, 'b' | 'B' => is_binary_digit, c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c), }; radix_base = Some(match ch { 'x' | 'X' => 16, 'o' | 'O' => 8, 'b' | 'B' => 2, c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c), }); } _ => break, } } let num_pos = negated.map_or(start_pos, |negated_pos| { result.insert(0, '-'); negated_pos }); if let Some(ref mut last) = state.last_token { *last = result.clone(); } // Parse number let token = if let Some(radix) = radix_base { let result = &result[2..]; UNSIGNED_INT::from_str_radix(result, radix) .map(|v| v as INT) .map_or_else( |_| Token::LexError(LERR::MalformedNumber(result.to_string()).into()), Token::IntegerConstant, ) } else { (|| { let num = INT::from_str(&result).map(Token::IntegerConstant); // If integer parsing is unnecessary, try float instead #[cfg(not(feature = "no_float"))] if num.is_err() { if let Ok(v) = crate::types::FloatWrapper::from_str(&result) { return Token::FloatConstant((v, result).into()); } } // Then try decimal #[cfg(feature = "decimal")] if num.is_err() { if let Ok(v) = rust_decimal::Decimal::from_str(&result) { return Token::DecimalConstant((v, result).into()); } } // Then try decimal in scientific notation #[cfg(feature = "decimal")] if num.is_err() { if let Ok(v) = rust_decimal::Decimal::from_scientific(&result) { return Token::DecimalConstant((v, result).into()); } } num.unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.to_string()).into()) }) })() }; return (token, num_pos); } // " - string literal ('"', ..) => { return parse_string_literal(stream, state, pos, c, false, true, false) .map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, ..)| (Token::StringConstant(result.into()), start_pos), ); } // ` - string literal ('`', ..) => { // Start from the next line if at the end of line match stream.peek_next() { // `\r - start from next line Some('\r') => { stream.eat_next_and_advance(pos); // `\r\n if stream.peek_next() == Some('\n') { stream.eat_next_and_advance(pos); } pos.new_line(); } // `\n - start from next line Some('\n') => { stream.eat_next_and_advance(pos); pos.new_line(); } _ => (), } return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, interpolated, ..)| { if interpolated { (Token::InterpolatedString(result.into()), start_pos) } else { (Token::StringConstant(result.into()), start_pos) } }, ); } // r - raw string literal ('#', '"' | '#') => { return parse_raw_string_literal(stream, state, pos, 0).map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, ..)| (Token::StringConstant(result.into()), start_pos), ); } // ' - character literal ('\'', '\'') => { return ( Token::LexError(LERR::MalformedChar(String::new()).into()), start_pos, ) } ('\'', ..) => { return parse_string_literal(stream, state, pos, c, false, false, false) .map_or_else( |(err, err_pos)| (Token::LexError(err.into()), err_pos), |(result, ..)| { let mut chars = result.chars(); let first = chars.next().unwrap(); if chars.next().is_some() { ( Token::LexError(LERR::MalformedChar(result.to_string()).into()), start_pos, ) } else { (Token::CharConstant(first), start_pos) } }, ) } // Braces ('{', ..) => return (Token::LeftBrace, start_pos), ('}', ..) => return (Token::RightBrace, start_pos), // Unit ('(', ')') => { stream.eat_next_and_advance(pos); return (Token::Unit, start_pos); } // Parentheses ('(', '*') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("(*".into())), start_pos); } ('(', ..) => return (Token::LeftParen, start_pos), (')', ..) => return (Token::RightParen, start_pos), // Indexing ('[', ..) => return (Token::LeftBracket, start_pos), (']', ..) => return (Token::RightBracket, start_pos), // Map literal #[cfg(not(feature = "no_object"))] ('#', '{') => { stream.eat_next_and_advance(pos); return (Token::MapStart, start_pos); } // Shebang ('#', '!') => return (Token::Reserved(Box::new("#!".into())), start_pos), ('#', ' ') => { stream.eat_next_and_advance(pos); let token = if stream.peek_next() == Some('{') { stream.eat_next_and_advance(pos); "# {" } else { "#" }; return (Token::Reserved(Box::new(token.into())), start_pos); } ('#', ..) => return (Token::Reserved(Box::new("#".into())), start_pos), // Operators ('+', '=') => { stream.eat_next_and_advance(pos); return (Token::PlusAssign, start_pos); } ('+', '+') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("++".into())), start_pos); } ('+', ..) if !state.next_token_cannot_be_unary => return (Token::UnaryPlus, start_pos), ('+', ..) => return (Token::Plus, start_pos), ('-', '0'..='9') if !state.next_token_cannot_be_unary => negated = Some(start_pos), ('-', '0'..='9') => return (Token::Minus, start_pos), ('-', '=') => { stream.eat_next_and_advance(pos); return (Token::MinusAssign, start_pos); } ('-', '>') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("->".into())), start_pos); } ('-', '-') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("--".into())), start_pos); } ('-', ..) if !state.next_token_cannot_be_unary => { return (Token::UnaryMinus, start_pos) } ('-', ..) => return (Token::Minus, start_pos), ('*', ')') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("*)".into())), start_pos); } ('*', '=') => { stream.eat_next_and_advance(pos); return (Token::MultiplyAssign, start_pos); } ('*', '*') => { stream.eat_next_and_advance(pos); return ( if stream.peek_next() == Some('=') { stream.eat_next_and_advance(pos); Token::PowerOfAssign } else { Token::PowerOf }, start_pos, ); } ('*', ..) => return (Token::Multiply, start_pos), // Comments ('/', '/') => { stream.eat_next_and_advance(pos); let mut comment: Option = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] Some('/') => { stream.eat_next_and_advance(pos); // Long streams of `///...` are not doc-comments match stream.peek_next() { Some('/') => None, _ => Some("///".into()), } } #[cfg(feature = "metadata")] Some('!') => { stream.eat_next_and_advance(pos); Some("//!".into()) } _ if state.include_comments => Some("//".into()), _ => None, }; while let Some(c) = stream.get_next() { if c == '\r' { // \r\n if stream.peek_next() == Some('\n') { stream.eat_next_and_advance(pos); } pos.new_line(); break; } if c == '\n' { pos.new_line(); break; } if let Some(comment) = comment.as_mut() { comment.push(c); } pos.advance(); } match comment { #[cfg(feature = "metadata")] Some(comment) if comment.starts_with("//!") => { let g = &mut state.tokenizer_control.borrow_mut().global_comments; if !g.is_empty() { *g += "\n"; } *g += &comment; } Some(comment) => return (Token::Comment(comment.into()), start_pos), None => (), } } ('/', '*') => { state.comment_level += 1; stream.eat_next_and_advance(pos); let mut comment: Option = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] Some('*') => { stream.eat_next_and_advance(pos); // Long streams of `/****...` are not doc-comments match stream.peek_next() { Some('*') => None, _ => Some("/**".into()), } } _ if state.include_comments => Some("/*".into()), _ => None, }; state.comment_level = scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); if let Some(comment) = comment { return (Token::Comment(comment.into()), start_pos); } } ('/', '=') => { stream.eat_next_and_advance(pos); return (Token::DivideAssign, start_pos); } ('/', ..) => return (Token::Divide, start_pos), (';', ..) => return (Token::SemiColon, start_pos), (',', ..) => return (Token::Comma, start_pos), ('.', '.') => { stream.eat_next_and_advance(pos); return ( match stream.peek_next() { Some('.') => { stream.eat_next_and_advance(pos); Token::Reserved(Box::new("...".into())) } Some('=') => { stream.eat_next_and_advance(pos); Token::InclusiveRange } _ => Token::ExclusiveRange, }, start_pos, ); } ('.', ..) => return (Token::Period, start_pos), ('=', '=') => { stream.eat_next_and_advance(pos); if stream.peek_next() == Some('=') { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("===".into())), start_pos); } return (Token::EqualsTo, start_pos); } ('=', '>') => { stream.eat_next_and_advance(pos); return (Token::DoubleArrow, start_pos); } ('=', ..) => return (Token::Equals, start_pos), #[cfg(not(feature = "no_module"))] (':', ':') => { stream.eat_next_and_advance(pos); if stream.peek_next() == Some('<') { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("::<".into())), start_pos); } return (Token::DoubleColon, start_pos); } (':', '=') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new(":=".into())), start_pos); } (':', ';') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new(":;".into())), start_pos); } (':', ..) => return (Token::Colon, start_pos), ('<', '=') => { stream.eat_next_and_advance(pos); return (Token::LessThanEqualsTo, start_pos); } ('<', '-') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("<-".into())), start_pos); } ('<', '<') => { stream.eat_next_and_advance(pos); return ( if stream.peek_next() == Some('=') { stream.eat_next_and_advance(pos); Token::LeftShiftAssign } else { Token::LeftShift }, start_pos, ); } ('<', '|') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("<|".into())), start_pos); } ('<', ..) => return (Token::LessThan, start_pos), ('>', '=') => { stream.eat_next_and_advance(pos); return (Token::GreaterThanEqualsTo, start_pos); } ('>', '>') => { stream.eat_next_and_advance(pos); return ( if stream.peek_next() == Some('=') { stream.eat_next_and_advance(pos); Token::RightShiftAssign } else { Token::RightShift }, start_pos, ); } ('>', ..) => return (Token::GreaterThan, start_pos), ('!', 'i') => { stream.get_next().unwrap(); if stream.peek_next() == Some('n') { stream.get_next().unwrap(); match stream.peek_next() { Some(c) if is_id_continue(c) => { stream.unget('n'); stream.unget('i'); return (Token::Bang, start_pos); } _ => { pos.advance(); pos.advance(); return (Token::NotIn, start_pos); } } } stream.unget('i'); return (Token::Bang, start_pos); } ('!', '=') => { stream.eat_next_and_advance(pos); if stream.peek_next() == Some('=') { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("!==".into())), start_pos); } return (Token::NotEqualsTo, start_pos); } ('!', '.') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("!.".into())), start_pos); } ('!', ..) => return (Token::Bang, start_pos), ('|', '|') => { stream.eat_next_and_advance(pos); return (Token::Or, start_pos); } ('|', '=') => { stream.eat_next_and_advance(pos); return (Token::OrAssign, start_pos); } ('|', '>') => { stream.eat_next_and_advance(pos); return (Token::Reserved(Box::new("|>".into())), start_pos); } ('|', ..) => return (Token::Pipe, start_pos), ('&', '&') => { stream.eat_next_and_advance(pos); return (Token::And, start_pos); } ('&', '=') => { stream.eat_next_and_advance(pos); return (Token::AndAssign, start_pos); } ('&', ..) => return (Token::Ampersand, start_pos), ('^', '=') => { stream.eat_next_and_advance(pos); return (Token::XOrAssign, start_pos); } ('^', ..) => return (Token::XOr, start_pos), ('~', ..) => return (Token::Reserved(Box::new("~".into())), start_pos), ('%', '=') => { stream.eat_next_and_advance(pos); return (Token::ModuloAssign, start_pos); } ('%', ..) => return (Token::Modulo, start_pos), ('@', ..) => return (Token::Reserved(Box::new("@".into())), start_pos), ('$', ..) => return (Token::Reserved(Box::new("$".into())), start_pos), ('?', '.') => { stream.eat_next_and_advance(pos); return ( #[cfg(not(feature = "no_object"))] Token::Elvis, #[cfg(feature = "no_object")] Token::Reserved(Box::new("?.".into())), start_pos, ); } ('?', '?') => { stream.eat_next_and_advance(pos); return (Token::DoubleQuestion, start_pos); } ('?', '[') => { stream.eat_next_and_advance(pos); return ( #[cfg(not(feature = "no_index"))] Token::QuestionBracket, #[cfg(feature = "no_index")] Token::Reserved(Box::new("?[".into())), start_pos, ); } ('?', ..) => return (Token::Reserved(Box::new("?".into())), start_pos), // letter or underscore ... _ if is_id_first_alphabetic(c) || c == '_' => { return parse_identifier_token(stream, state, pos, start_pos, c); } // \n ('\n', ..) => pos.new_line(), // Whitespace - follows Rust's SPACE, TAB, CR, LF, FF which is the same as WhatWG. (ch, ..) if ch.is_ascii_whitespace() => (), _ => { return ( Token::LexError(LERR::UnexpectedInput(c.to_string()).into()), start_pos, ) } } } pos.advance(); (Token::EOF, *pos) } /// Get the next token, parsing it as an identifier. fn parse_identifier_token( stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, start_pos: Position, first_char: char, ) -> (Token, Position) { let mut identifier = SmartString::new_const(); identifier.push(first_char); if let Some(ref mut last) = state.last_token { last.clear(); last.push(first_char); } while let Some(next_char) = stream.peek_next() { match next_char { x if is_id_continue(x) => { stream.eat_next_and_advance(pos); identifier.push(x); if let Some(ref mut last) = state.last_token { last.push(x); } } _ => break, } } if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { return (token, start_pos); } if is_reserved_keyword_or_symbol(&identifier).0 { return (Token::Reserved(Box::new(identifier)), start_pos); } if !is_valid_identifier(&identifier) { return ( Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), start_pos, ); } (Token::Identifier(identifier.into()), start_pos) } /// _(internals)_ Is a text string a valid identifier? /// Exported under the `internals` feature only. #[must_use] pub fn is_valid_identifier(name: &str) -> bool { let mut first_alphabetic = false; for ch in name.chars() { match ch { '_' => (), _ if is_id_first_alphabetic(ch) => first_alphabetic = true, _ if !first_alphabetic => return false, _ if char::is_ascii_alphanumeric(&ch) => (), _ => return false, } } first_alphabetic } /// _(internals)_ Is a text string a valid script-defined function name? /// Exported under the `internals` feature only. #[inline(always)] #[must_use] pub fn is_valid_function_name(name: &str) -> bool { is_valid_identifier(name) && !is_reserved_keyword_or_symbol(name).0 && Token::lookup_symbol_from_syntax(name).is_none() } /// Is a character valid to start an identifier? #[inline(always)] #[must_use] #[allow(clippy::missing_const_for_fn)] pub fn is_id_first_alphabetic(x: char) -> bool { #[cfg(feature = "unicode-xid-ident")] return unicode_xid::UnicodeXID::is_xid_start(x); #[cfg(not(feature = "unicode-xid-ident"))] return x.is_ascii_alphabetic(); } /// Is a character valid for an identifier? #[inline(always)] #[must_use] #[allow(clippy::missing_const_for_fn)] pub fn is_id_continue(x: char) -> bool { #[cfg(feature = "unicode-xid-ident")] return unicode_xid::UnicodeXID::is_xid_continue(x); #[cfg(not(feature = "unicode-xid-ident"))] return x.is_ascii_alphanumeric() || x == '_'; } /// Is a piece of syntax a reserved keyword or reserved symbol? /// /// # Return values /// /// The first `bool` indicates whether it is a reserved keyword or symbol. /// /// The second `bool` indicates whether the keyword can be called normally as a function. /// `false` if it is not a reserved keyword. /// /// The third `bool` indicates whether the keyword can be called in method-call style. /// `false` if it is not a reserved keyword or it cannot be called as a function. #[inline] #[must_use] pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) { // This implementation is based upon a pre-calculated table generated // by GNU `gperf` on the list of keywords. let utf8 = syntax.as_bytes(); let len = utf8.len(); if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) { return (false, false, false); } let mut hash_val = len; match len { 1 => (), _ => hash_val += RESERVED_ASSOC_VALUES[utf8[1] as usize] as usize, } hash_val += RESERVED_ASSOC_VALUES[utf8[0] as usize] as usize; hash_val += RESERVED_ASSOC_VALUES[utf8[len - 1] as usize] as usize; if !(MIN_RESERVED_HASH_VALUE..=MAX_RESERVED_HASH_VALUE).contains(&hash_val) { return (false, false, false); } match RESERVED_LIST[hash_val] { ("", ..) => (false, false, false), (s, true, a, b) => { // Fail early to avoid calling memcmp(). // Since we are already working with bytes, mind as well check the first one. let is_reserved = s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax; (is_reserved, is_reserved && a, is_reserved && a && b) } _ => (false, false, false), } } /// _(internals)_ A type that implements the [`InputStream`] trait. /// Exported under the `internals` feature only. /// /// Multiple character streams are jointed together to form one single stream. pub struct MultiInputsStream<'a> { /// Buffered characters, if any. pub buf: [Option; 2], /// The current stream index. pub index: usize, /// Input character streams. pub streams: StaticVec>>, } impl InputStream for MultiInputsStream<'_> { #[inline] fn unget(&mut self, ch: char) { match self.buf { [None, ..] => self.buf[0] = Some(ch), [_, None] => self.buf[1] = Some(ch), _ => unreachable!("cannot unget more than 2 characters!"), } } fn get_next(&mut self) -> Option { match self.buf { [None, ..] => (), [ch @ Some(_), None] => { self.buf[0] = None; return ch; } [_, ch @ Some(_)] => { self.buf[1] = None; return ch; } } loop { if self.index >= self.streams.len() { // No more streams return None; } if let Some(ch) = self.streams[self.index].next() { // Next character in main stream return Some(ch); } // Jump to the next stream self.index += 1; } } fn peek_next(&mut self) -> Option { match self.buf { [None, ..] => (), [ch @ Some(_), None] => return ch, [_, ch @ Some(_)] => return ch, } loop { if self.index >= self.streams.len() { // No more streams return None; } if let Some(&ch) = self.streams[self.index].peek() { // Next character in main stream return Some(ch); } // Jump to the next stream self.index += 1; } } } /// _(internals)_ An iterator on a [`Token`] stream. /// Exported under the `internals` feature only. pub struct TokenIterator<'a> { /// Reference to the scripting `Engine`. pub engine: &'a Engine, /// Current state. pub state: TokenizeState, /// Current position. pub pos: Position, /// Input character stream. pub stream: MultiInputsStream<'a>, /// A processor function that maps a token to another. pub token_mapper: Option<&'a OnParseTokenCallback>, } impl<'a> Iterator for TokenIterator<'a> { type Item = (Token, Position); fn next(&mut self) -> Option { let (within_interpolated, compress_script) = { let control = &mut *self.state.tokenizer_control.borrow_mut(); if control.is_within_text { // Switch to text mode terminated by back-tick self.state.is_within_text_terminated_by = Some("`".to_string().into()); // Reset it control.is_within_text = false; } ( self.state.is_within_text_terminated_by.is_some(), control.compressed.is_some(), ) }; let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) { // {EOF} r @ (Token::EOF, _) => return Some(r), // {EOF} after unterminated string. // The only case where `TokenizeState.is_within_text_terminated_by` is set is when // a verbatim string or a string with continuation encounters {EOF}. // This is necessary to handle such cases for line-by-line parsing, but for an entire // script it is a syntax error. (Token::StringConstant(..), pos) if self.state.is_within_text_terminated_by.is_some() => { self.state.is_within_text_terminated_by = None; return Some((Token::LexError(LERR::UnterminatedString.into()), pos)); } // Reserved keyword/symbol (Token::Reserved(s), pos) => (match (s.as_str(), #[cfg(not(feature = "no_custom_syntax"))] self.engine.custom_keywords.contains_key(&*s), #[cfg(feature = "no_custom_syntax")] false ) { ("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(), ).into()), ("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(), ).into()), ("->", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'->' is not a valid symbol. This is not C or C++!".to_string()).into()), ("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(), ).into()), (":=", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "':=' is not a valid assignment operator. This is not Go or Pascal! Should it be simply '='?".to_string(), ).into()), (":;", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "':;' is not a valid symbol. Should it be '::'?".to_string(), ).into()), ("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(), ).into()), ("(*" | "*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(), ).into()), ("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'#' is not a valid symbol. Should it be '#{'?".to_string(), ).into()), // Reserved keyword/operator that is custom. #[cfg(not(feature = "no_custom_syntax"))] (.., true) => Token::Custom(s), #[cfg(feature = "no_custom_syntax")] (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. (token, false) if self.engine.is_symbol_disabled(token) => { let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, // Reserved keyword/operator that is not custom. (.., false) => Token::Reserved(s), }, pos), // Custom keyword #[cfg(not(feature = "no_custom_syntax"))] (Token::Identifier(s), pos) if self.engine.custom_keywords.contains_key(&*s) => { (Token::Custom(s), pos) } // Custom keyword/symbol - must be disabled #[cfg(not(feature = "no_custom_syntax"))] (token, pos) if token.is_literal() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { // Active standard keyword should never be a custom keyword! debug_assert!(self.engine.is_symbol_disabled(token.literal_syntax()), "{:?} is an active keyword", token); (Token::Custom(Box::new(token.literal_syntax().into())), pos) } // Disabled symbol (token, pos) if token.is_literal() && self.engine.is_symbol_disabled(token.literal_syntax()) => { (Token::Reserved(Box::new(token.literal_syntax().into())), pos) } // Normal symbol r => r, }; // Run the mapper, if any let token = match self.token_mapper { Some(func) => func(token, pos, &self.state), None => token, }; // Collect the compressed script, if needed if compress_script { let control = &mut *self.state.tokenizer_control.borrow_mut(); if token != Token::EOF { if let Some(ref mut compressed) = control.compressed { use std::fmt::Write; let last_token = self.state.last_token.as_ref().unwrap(); let mut buf = SmartString::new_const(); if last_token.is_empty() { write!(buf, "{token}").unwrap(); } else if within_interpolated && matches!( token, Token::StringConstant(..) | Token::InterpolatedString(..) ) { *compressed += &last_token[1..]; } else { buf = last_token.clone(); } if !buf.is_empty() && !compressed.is_empty() { let cur = buf.chars().next().unwrap(); if cur == '_' || is_id_first_alphabetic(cur) || is_id_continue(cur) { let prev = compressed.chars().last().unwrap(); if prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev) { *compressed += " "; } } } *compressed += &buf; } } } Some((token, pos)) } } impl FusedIterator for TokenIterator<'_> {} impl Engine { /// _(internals)_ Tokenize an input text stream. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn lex<'a>( &'a self, inputs: impl IntoIterator + 'a)>, ) -> (TokenIterator<'a>, TokenizerControl) { self.lex_raw(inputs, self.token_mapper.as_deref()) } /// _(internals)_ Tokenize an input text stream with a mapping function. /// Exported under the `internals` feature only. #[expose_under_internals] #[inline(always)] #[must_use] fn lex_with_map<'a>( &'a self, inputs: impl IntoIterator + 'a)>, token_mapper: &'a OnParseTokenCallback, ) -> (TokenIterator<'a>, TokenizerControl) { self.lex_raw(inputs, Some(token_mapper)) } /// Tokenize an input text stream with an optional mapping function. #[inline] #[must_use] pub(crate) fn lex_raw<'a>( &'a self, inputs: impl IntoIterator + 'a)>, token_mapper: Option<&'a OnParseTokenCallback>, ) -> (TokenIterator<'a>, TokenizerControl) { let buffer: TokenizerControl = RefCell::new(TokenizerControlBlock::new()).into(); let buffer2 = buffer.clone(); ( TokenIterator { engine: self, state: TokenizeState { #[cfg(not(feature = "unchecked"))] max_string_len: std::num::NonZeroUsize::new(self.max_string_size()), next_token_cannot_be_unary: false, tokenizer_control: buffer, comment_level: 0, include_comments: false, is_within_text_terminated_by: None, last_token: None, }, pos: Position::new(1, 0), stream: MultiInputsStream { buf: [None, None], streams: inputs .into_iter() .map(|s| s.as_ref().chars().peekable()) .collect(), index: 0, }, token_mapper, }, buffer2, ) } } rhai-1.21.0/src/types/bloom_filter.rs000064400000000000000000000067141046102023000156160ustar 00000000000000//! A simple bloom filter implementation for `u64` hash values only. #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ mem, ops::{Add, AddAssign}, }; /// Number of bits for a `usize`. const USIZE_BITS: usize = mem::size_of::() * 8; /// Number of `usize` values required for 256 bits. const SIZE: usize = 256 / USIZE_BITS; /// _(internals)_ A simple bloom filter implementation for `u64` hash values only - i.e. all 64 bits are assumed /// to be relatively random. /// Exported under the `internals` feature only. /// /// For this reason, the implementation is simplistic - it just looks at the least significant byte /// of the `u64` hash value and sets the corresponding bit in a 256-long bit vector. /// /// The rationale of this type is to avoid pulling in another dependent crate. #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct BloomFilterU64([usize; SIZE]); impl BloomFilterU64 { /// Get the bit position of a `u64` hash value. #[inline(always)] #[must_use] const fn calc_hash(value: u64) -> (usize, usize) { let hash = (value & 0x00ff) as usize; (hash / USIZE_BITS, 0x01 << (hash % USIZE_BITS)) } /// Create a new [`BloomFilterU64`]. #[inline(always)] #[must_use] pub const fn new() -> Self { Self([0; SIZE]) } /// Is this [`BloomFilterU64`] empty? #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.0 == [0; SIZE] } /// Clear this [`BloomFilterU64`]. #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.0 = [0; SIZE]; self } /// Mark a `u64` hash into this [`BloomFilterU64`]. #[inline] pub fn mark(&mut self, hash: u64) -> &mut Self { let (offset, mask) = Self::calc_hash(hash); self.0[offset] |= mask; self } /// Is a `u64` hash definitely absent from this [`BloomFilterU64`]? #[inline] #[must_use] pub const fn is_absent(&self, hash: u64) -> bool { let (offset, mask) = Self::calc_hash(hash); (self.0[offset] & mask) == 0 } /// If a `u64` hash is absent from this [`BloomFilterU64`], return `true` and then mark it. /// Otherwise return `false`. #[inline] #[must_use] pub fn is_absent_and_set(&mut self, hash: u64) -> bool { let (offset, mask) = Self::calc_hash(hash); let result = (self.0[offset] & mask) == 0; self.0[offset] |= mask; result } } impl Add for &BloomFilterU64 { type Output = BloomFilterU64; #[inline] fn add(self, rhs: Self) -> Self::Output { let mut buf = [0; SIZE]; self.0 .iter() .zip(rhs.0.iter()) .map(|(&a, &b)| a | b) .zip(buf.iter_mut()) .for_each(|(v, x)| *x = v); BloomFilterU64(buf) } } impl Add for &BloomFilterU64 { type Output = BloomFilterU64; #[inline(always)] fn add(self, rhs: BloomFilterU64) -> Self::Output { self + &rhs } } impl AddAssign for BloomFilterU64 { #[inline(always)] fn add_assign(&mut self, rhs: Self) { *self += &rhs; } } impl AddAssign<&Self> for BloomFilterU64 { #[inline] fn add_assign(&mut self, rhs: &Self) { self.0 .iter_mut() .zip(rhs.0.iter()) .for_each(|(x, &v)| *x |= v); } } rhai-1.21.0/src/types/custom_types.rs000064400000000000000000000105541046102023000156740ustar 00000000000000//! Collection of custom types. use crate::Identifier; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::type_name, collections::BTreeMap}; /// _(internals)_ Information for a registered custom type. /// Exported under the `internals` feature only. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] pub struct CustomTypeInfo { /// Rust name of the custom type. pub type_name: Identifier, /// Friendly display name of the custom type. pub display_name: Identifier, /// Comments. /// /// Block doc-comments are kept in separate strings. /// /// Line doc-comments are merged, with line-breaks, into a single string without a final /// termination line-break. /// /// Leading white-spaces are stripped, each string always starting with the corresponding /// doc-comment leader: `///` or `/**`. /// /// Each line in non-block doc-comments starts with `///`. #[cfg(feature = "metadata")] pub comments: crate::StaticVec, } /// _(internals)_ A collection of custom types. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] pub struct CustomTypesCollection(BTreeMap>); impl Default for CustomTypesCollection { #[inline(always)] fn default() -> Self { Self::new() } } impl CustomTypesCollection { /// Create a new [`CustomTypesCollection`]. #[inline(always)] pub const fn new() -> Self { Self(BTreeMap::new()) } /// Clear the [`CustomTypesCollection`]. #[inline(always)] pub fn clear(&mut self) { self.0.clear(); } /// Register a custom type. #[inline(always)] pub fn add(&mut self, type_name: impl Into, name: impl Into) { let type_name = type_name.into(); let custom_type = CustomTypeInfo { type_name: type_name.clone(), display_name: name.into(), #[cfg(feature = "metadata")] comments: <_>::default(), }; self.add_raw(type_name, custom_type); } /// Register a custom type with doc-comments. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] pub fn add_with_comments>( &mut self, type_name: impl Into, name: impl Into, comments: impl IntoIterator, ) { let type_name = type_name.into(); let custom_type = CustomTypeInfo { type_name: type_name.clone(), display_name: name.into(), comments: comments.into_iter().map(Into::into).collect(), }; self.add_raw(type_name, custom_type); } /// Register a custom type. #[inline(always)] pub fn add_type(&mut self, name: &str) { self.add_raw( type_name::(), CustomTypeInfo { type_name: type_name::().into(), display_name: name.into(), #[cfg(feature = "metadata")] comments: <_>::default(), }, ); } /// Register a custom type with doc-comments. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] pub fn add_type_with_comments(&mut self, name: &str, comments: &[&str]) { self.add_raw( type_name::(), CustomTypeInfo { type_name: type_name::().into(), display_name: name.into(), #[cfg(feature = "metadata")] comments: comments.iter().map(|&s| s.into()).collect(), }, ); } /// Register a custom type. #[inline(always)] pub fn add_raw(&mut self, type_name: impl Into, custom_type: CustomTypeInfo) { self.0.insert(type_name.into(), custom_type.into()); } /// Find a custom type. #[inline(always)] #[must_use] pub fn get(&self, key: &str) -> Option<&CustomTypeInfo> { self.0.get(key).map(<_>::as_ref) } /// Iterate all the custom types. #[inline(always)] pub fn iter(&self) -> impl Iterator { self.0.iter().map(|(k, v)| (k.as_str(), v.as_ref())) } } rhai-1.21.0/src/types/dynamic.rs000064400000000000000000003503551046102023000145700ustar 00000000000000//! Helper module which defines the [`Dynamic`] data type. use crate::{ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, Any, TypeId}, fmt, hash::{Hash, Hasher}, mem, ops::{Deref, DerefMut}, str::FromStr, }; pub use super::Variant; #[cfg(not(feature = "no_time"))] #[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))] pub use std::time::Instant; #[cfg(not(feature = "no_time"))] #[cfg(all(target_family = "wasm", target_os = "unknown"))] pub use instant::Instant; #[cfg(not(feature = "no_index"))] use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; /// _(internals)_ Modes of access. /// Exported under the `internals` feature only. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[non_exhaustive] pub enum AccessMode { /// Mutable. ReadWrite, /// Immutable. ReadOnly, } /// Arbitrary data attached to a [`Dynamic`] value. #[cfg(target_pointer_width = "64")] pub type Tag = i32; /// Arbitrary data attached to a [`Dynamic`] value. #[cfg(target_pointer_width = "32")] pub type Tag = i16; /// Default tag value for [`Dynamic`]. const DEFAULT_TAG_VALUE: Tag = 0; /// Dynamic type containing any value. #[must_use] pub struct Dynamic(pub(crate) Union); /// Internal [`Dynamic`] representation. /// /// Most variants are boxed to reduce the size. #[must_use] pub enum Union { /// The Unit value - (). Unit((), Tag, AccessMode), /// A boolean value. Bool(bool, Tag, AccessMode), /// An [`ImmutableString`] value. Str(ImmutableString, Tag, AccessMode), /// A character value. Char(char, Tag, AccessMode), /// An integer value. Int(INT, Tag, AccessMode), /// A floating-point value. #[cfg(not(feature = "no_float"))] Float(super::FloatWrapper, Tag, AccessMode), /// _(decimal)_ A fixed-precision decimal value. /// Exported under the `decimal` feature only. #[cfg(feature = "decimal")] Decimal(Box, Tag, AccessMode), /// An array value. #[cfg(not(feature = "no_index"))] Array(Box, Tag, AccessMode), /// An blob (byte array). #[cfg(not(feature = "no_index"))] Blob(Box, Tag, AccessMode), /// An object map value. #[cfg(not(feature = "no_object"))] Map(Box, Tag, AccessMode), /// A function pointer. FnPtr(Box, Tag, AccessMode), /// A timestamp value. #[cfg(not(feature = "no_time"))] TimeStamp(Box, Tag, AccessMode), /// Any type as a trait object. /// /// An extra level of redirection is used in order to avoid bloating the size of [`Dynamic`] /// because `Box` is a fat pointer. Variant(Box>, Tag, AccessMode), /// A _shared_ value of any type. #[cfg(not(feature = "no_closure"))] Shared(crate::Shared>, Tag, AccessMode), } /// _(internals)_ Lock guard for reading a [`Dynamic`]. /// Exported under the `internals` feature only. /// /// This type provides transparent interoperability between normal [`Dynamic`] and shared /// [`Dynamic`] values. #[derive(Debug)] #[must_use] pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>); /// Different types of read guards for [`DynamicReadLock`]. #[derive(Debug)] #[must_use] enum DynamicReadLockInner<'d, T: Clone> { /// A simple reference to a non-shared value. Reference(&'d T), /// A read guard to a _shared_ value. #[cfg(not(feature = "no_closure"))] Guard(crate::func::native::LockGuard<'d, Dynamic>), } impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { match self.0 { DynamicReadLockInner::Reference(reference) => reference, #[cfg(not(feature = "no_closure"))] DynamicReadLockInner::Guard(ref guard) => guard.downcast_ref().unwrap(), } } } /// _(internals)_ Lock guard for writing a [`Dynamic`]. /// Exported under the `internals` feature only. /// /// This type provides transparent interoperability between normal [`Dynamic`] and shared /// [`Dynamic`] values. #[derive(Debug)] #[must_use] pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>); /// Different types of write guards for [`DynamicReadLock`]. #[derive(Debug)] #[must_use] enum DynamicWriteLockInner<'d, T: Clone> { /// A simple mutable reference to a non-shared value. Reference(&'d mut T), /// A write guard to a _shared_ value. #[cfg(not(feature = "no_closure"))] Guard(crate::func::native::LockGuardMut<'d, Dynamic>), } impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref reference) => reference, #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(ref guard) => guard.downcast_ref().unwrap(), } } } impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref mut reference) => reference, #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(ref mut guard) => guard.downcast_mut().unwrap(), } } } impl Dynamic { /// Get the arbitrary data attached to this [`Dynamic`]. #[must_use] pub const fn tag(&self) -> Tag { match self.0 { Union::Unit((), tag, _) | Union::Bool(_, tag, _) | Union::Str(_, tag, _) | Union::Char(_, tag, _) | Union::Int(_, tag, _) | Union::FnPtr(_, tag, _) | Union::Variant(_, tag, _) => tag, #[cfg(not(feature = "no_float"))] Union::Float(_, tag, _) => tag, #[cfg(feature = "decimal")] Union::Decimal(_, tag, _) => tag, #[cfg(not(feature = "no_index"))] Union::Array(_, tag, _) | Union::Blob(_, tag, _) => tag, #[cfg(not(feature = "no_object"))] Union::Map(_, tag, _) => tag, #[cfg(not(feature = "no_time"))] Union::TimeStamp(_, tag, _) => tag, #[cfg(not(feature = "no_closure"))] Union::Shared(_, tag, _) => tag, } } /// Attach arbitrary data to this [`Dynamic`]. pub fn set_tag(&mut self, value: Tag) -> &mut Self { match self.0 { Union::Unit((), ref mut tag, _) | Union::Bool(_, ref mut tag, _) | Union::Str(_, ref mut tag, _) | Union::Char(_, ref mut tag, _) | Union::Int(_, ref mut tag, _) | Union::FnPtr(_, ref mut tag, _) | Union::Variant(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_float"))] Union::Float(_, ref mut tag, _) => *tag = value, #[cfg(feature = "decimal")] Union::Decimal(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_index"))] Union::Array(_, ref mut tag, _) | Union::Blob(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_object"))] Union::Map(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_time"))] Union::TimeStamp(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_closure"))] Union::Shared(_, ref mut tag, _) => *tag = value, } self } /// Does this [`Dynamic`] hold a variant data type instead of one of the supported system /// primitive types? #[inline(always)] #[must_use] pub const fn is_variant(&self) -> bool { matches!(self.0, Union::Variant(..)) } /// Is the value held by this [`Dynamic`] shared? /// /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] #[inline(always)] #[must_use] pub const fn is_shared(&self) -> bool { matches!(self.0, Union::Shared(..)) } /// Is the value held by this [`Dynamic`] a particular type? /// /// # Panics or Deadlocks When Value is Shared /// /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[inline] #[must_use] pub fn is(&self) -> bool { #[cfg(not(feature = "no_closure"))] if self.is_shared() { return TypeId::of::() == self.type_id(); } if TypeId::of::() == TypeId::of::<()>() { return matches!(self.0, Union::Unit(..)); } if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Bool(..)); } if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Char(..)); } if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Int(..)); } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Float(..)); } if TypeId::of::() == TypeId::of::() || TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Str(..)); } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Array(..)); } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Blob(..)); } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Map(..)); } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::Decimal(..)); } if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::FnPtr(..)); } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return matches!(self.0, Union::TimeStamp(..)); } TypeId::of::() == self.type_id() } /// Get the [`TypeId`] of the value held by this [`Dynamic`]. /// /// # Panics or Deadlocks When Value is Shared /// /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[must_use] pub fn type_id(&self) -> TypeId { match self.0 { Union::Unit(..) => TypeId::of::<()>(), Union::Bool(..) => TypeId::of::(), Union::Str(..) => TypeId::of::(), Union::Char(..) => TypeId::of::(), Union::Int(..) => TypeId::of::(), #[cfg(not(feature = "no_float"))] Union::Float(..) => TypeId::of::(), #[cfg(feature = "decimal")] Union::Decimal(..) => TypeId::of::(), #[cfg(not(feature = "no_index"))] Union::Array(..) => TypeId::of::(), #[cfg(not(feature = "no_index"))] Union::Blob(..) => TypeId::of::(), #[cfg(not(feature = "no_object"))] Union::Map(..) => TypeId::of::(), Union::FnPtr(..) => TypeId::of::(), #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => TypeId::of::(), Union::Variant(ref v, ..) => (***v).type_id(), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).type_id(), } } /// Get the name of the type of the value held by this [`Dynamic`]. /// /// # Panics or Deadlocks When Value is Shared /// /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). /// Otherwise, this call panics if the data is currently borrowed for write. #[must_use] pub fn type_name(&self) -> &'static str { match self.0 { Union::Unit(..) => "()", Union::Bool(..) => "bool", Union::Str(..) => "string", Union::Char(..) => "char", Union::Int(..) => type_name::(), #[cfg(not(feature = "no_float"))] Union::Float(..) => type_name::(), #[cfg(feature = "decimal")] Union::Decimal(..) => "decimal", #[cfg(not(feature = "no_index"))] Union::Array(..) => "array", #[cfg(not(feature = "no_index"))] Union::Blob(..) => "blob", #[cfg(not(feature = "no_object"))] Union::Map(..) => "map", Union::FnPtr(..) => "Fn", #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => "timestamp", Union::Variant(ref v, ..) => (***v).type_name(), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).type_name(), } } } impl Hash for Dynamic { /// Hash the [`Dynamic`] value. /// /// # Panics /// /// Panics if the [`Dynamic`] value contains an unrecognized trait object. fn hash(&self, state: &mut H) { mem::discriminant(&self.0).hash(state); match self.0 { Union::Unit(..) => (), Union::Bool(ref b, ..) => b.hash(state), Union::Str(ref s, ..) => s.hash(state), Union::Char(ref c, ..) => c.hash(state), Union::Int(ref i, ..) => i.hash(state), #[cfg(not(feature = "no_float"))] Union::Float(ref f, ..) => f.hash(state), #[cfg(feature = "decimal")] Union::Decimal(ref d, ..) => d.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => a.hash(state), #[cfg(not(feature = "no_index"))] Union::Blob(ref a, ..) => a.hash(state), #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => m.hash(state), #[cfg(not(feature = "no_function"))] Union::FnPtr(ref f, ..) if f.env.is_some() => { unimplemented!("FnPtr with embedded environment cannot be hashed") } Union::FnPtr(ref f, ..) => { f.fn_name().hash(state); f.curry().hash(state); } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).hash(state), Union::Variant(ref v, ..) => { let _value_any = (***v).as_any(); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] if let Some(value) = _value_any.downcast_ref::() { return value.to_ne_bytes().hash(state); } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] if let Some(value) = _value_any.downcast_ref::() { return value.to_ne_bytes().hash(state); } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } else if let Some(value) = _value_any.downcast_ref::() { return value.hash(state); } if let Some(range) = _value_any.downcast_ref::() { return range.hash(state); } else if let Some(range) = _value_any.downcast_ref::() { return range.hash(state); } unimplemented!("Custom type {} cannot be hashed", self.type_name()) } #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => unimplemented!("Timestamp cannot be hashed"), } } } impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Union::Unit(..) => Ok(()), Union::Bool(ref v, ..) => fmt::Display::fmt(v, f), Union::Str(ref v, ..) => fmt::Display::fmt(v, f), Union::Char(ref v, ..) => fmt::Display::fmt(v, f), Union::Int(ref v, ..) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_float"))] Union::Float(ref v, ..) => fmt::Display::fmt(v, f), #[cfg(feature = "decimal")] Union::Decimal(ref v, ..) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_index"))] Union::Array(..) => fmt::Debug::fmt(self, f), #[cfg(not(feature = "no_index"))] Union::Blob(..) => fmt::Debug::fmt(self, f), #[cfg(not(feature = "no_object"))] Union::Map(..) => fmt::Debug::fmt(self, f), Union::FnPtr(ref v, ..) => fmt::Display::fmt(v, f), #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => f.write_str(""), Union::Variant(ref v, ..) => { let _value_any = (***v).as_any(); let _type_id = _value_any.type_id(); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Display::fmt(value, f); } if let Some(range) = _value_any.downcast_ref::() { return if range.end == INT::MAX { write!(f, "{}..", range.start) } else { write!(f, "{}..{}", range.start, range.end) }; } else if let Some(range) = _value_any.downcast_ref::() { return if *range.end() == INT::MAX { write!(f, "{}..=", range.start()) } else { write!(f, "{}..={}", range.start(), range.end()) }; } f.write_str((***v).type_name()) } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) if cfg!(feature = "unchecked") => { match crate::func::locked_read(cell) { Some(v) => write!(f, "{} (shared)", *v), _ => f.write_str(""), } } #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { #[cfg(feature = "no_std")] use hashbrown::HashSet; #[cfg(not(feature = "no_std"))] use std::collections::HashSet; // Avoid infinite recursion for shared values in a reference loop. fn display_fmt_print( f: &mut fmt::Formatter<'_>, value: &Dynamic, dict: &mut HashSet<*const Dynamic>, ) -> fmt::Result { match value.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) { Some(v) => { if dict.insert(value) { display_fmt_print(f, &v, dict)?; f.write_str(" (shared)") } else { f.write_str("") } } _ => f.write_str(""), }, #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { dict.insert(value); f.write_str("[")?; for (i, v) in arr.iter().enumerate() { if i > 0 { f.write_str(", ")?; } display_fmt_print(f, v, dict)?; } f.write_str("]") } #[cfg(not(feature = "no_object"))] Union::Map(ref map, ..) => { dict.insert(value); f.write_str("#{")?; for (i, (k, v)) in map.iter().enumerate() { if i > 0 { f.write_str(", ")?; } fmt::Display::fmt(k, f)?; f.write_str(": ")?; display_fmt_print(f, v, dict)?; } f.write_str("}") } _ => fmt::Display::fmt(value, f), } } display_fmt_print(f, self, &mut <_>::default()) } } } } impl fmt::Debug for Dynamic { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Union::Unit(ref v, ..) => fmt::Debug::fmt(v, f), Union::Bool(ref v, ..) => fmt::Debug::fmt(v, f), Union::Str(ref v, ..) => fmt::Debug::fmt(v, f), Union::Char(ref v, ..) => fmt::Debug::fmt(v, f), Union::Int(ref v, ..) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_float"))] Union::Float(ref v, ..) => fmt::Debug::fmt(v, f), #[cfg(feature = "decimal")] Union::Decimal(ref v, ..) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_index"))] Union::Array(ref v, ..) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_index"))] Union::Blob(ref v, ..) => { f.write_str("[")?; v.iter().enumerate().try_for_each(|(i, v)| { if i > 0 && i % 8 == 0 { f.write_str(" ")?; } write!(f, "{v:02x}") })?; f.write_str("]") } #[cfg(not(feature = "no_object"))] Union::Map(ref v, ..) => { f.write_str("#")?; fmt::Debug::fmt(v, f) } Union::FnPtr(ref v, ..) => fmt::Debug::fmt(v, f), #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => write!(f, ""), Union::Variant(ref v, ..) => { let _value_any = (***v).as_any(); let _type_id = _value_any.type_id(); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } else if let Some(value) = _value_any.downcast_ref::() { return fmt::Debug::fmt(value, f); } if let Some(range) = _value_any.downcast_ref::() { return if range.end == INT::MAX { write!(f, "{}..", range.start) } else { write!(f, "{}..{}", range.start, range.end) }; } else if let Some(range) = _value_any.downcast_ref::() { return if *range.end() == INT::MAX { write!(f, "{}..=", range.start()) } else { write!(f, "{}..={}", range.start(), range.end()) }; } f.write_str((***v).type_name()) } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) if cfg!(feature = "unchecked") => { match crate::func::locked_read(cell) { Some(v) => write!(f, "{:?} (shared)", *v), _ => f.write_str(""), } } #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { #[cfg(feature = "no_std")] use hashbrown::HashSet; #[cfg(not(feature = "no_std"))] use std::collections::HashSet; // Avoid infinite recursion for shared values in a reference loop. fn checked_debug_fmt( f: &mut fmt::Formatter<'_>, value: &Dynamic, dict: &mut HashSet<*const Dynamic>, ) -> fmt::Result { match value.0 { Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) { Some(v) => { if dict.insert(value) { checked_debug_fmt(f, &v, dict)?; f.write_str(" (shared)") } else { f.write_str("") } } _ => f.write_str(""), }, #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { dict.insert(value); f.write_str("[")?; for (i, v) in arr.iter().enumerate() { if i > 0 { f.write_str(", ")?; } checked_debug_fmt(f, v, dict)?; } f.write_str("]") } #[cfg(not(feature = "no_object"))] Union::Map(ref map, ..) => { dict.insert(value); f.write_str("#{")?; for (i, (k, v)) in map.iter().enumerate() { if i > 0 { f.write_str(", ")?; } fmt::Debug::fmt(k, f)?; f.write_str(": ")?; checked_debug_fmt(f, v, dict)?; } f.write_str("}") } Union::FnPtr(ref fnptr, ..) => { dict.insert(value); fmt::Display::fmt(&fnptr.typ, f)?; f.write_str("(")?; fmt::Debug::fmt(fnptr.fn_name(), f)?; for curry in &fnptr.curry { f.write_str(", ")?; checked_debug_fmt(f, curry, dict)?; } f.write_str(")") } _ => fmt::Debug::fmt(value, f), } } checked_debug_fmt(f, self, &mut <_>::default()) } } } } #[allow(clippy::enum_glob_use)] use AccessMode::*; impl Clone for Dynamic { /// Clone the [`Dynamic`] value. /// /// # WARNING /// /// The cloned copy is marked read-write even if the original is read-only. fn clone(&self) -> Self { match self.0 { Union::Unit(v, tag, ..) => Self(Union::Unit(v, tag, ReadWrite)), Union::Bool(v, tag, ..) => Self(Union::Bool(v, tag, ReadWrite)), Union::Str(ref v, tag, ..) => Self(Union::Str(v.clone(), tag, ReadWrite)), Union::Char(v, tag, ..) => Self(Union::Char(v, tag, ReadWrite)), Union::Int(v, tag, ..) => Self(Union::Int(v, tag, ReadWrite)), #[cfg(not(feature = "no_float"))] Union::Float(v, tag, ..) => Self(Union::Float(v, tag, ReadWrite)), #[cfg(feature = "decimal")] Union::Decimal(ref v, tag, ..) => Self(Union::Decimal(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_index"))] Union::Array(ref v, tag, ..) => Self(Union::Array(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_index"))] Union::Blob(ref v, tag, ..) => Self(Union::Blob(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_object"))] Union::Map(ref v, tag, ..) => Self(Union::Map(v.clone(), tag, ReadWrite)), Union::FnPtr(ref v, tag, ..) => Self(Union::FnPtr(v.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_time"))] Union::TimeStamp(ref v, tag, ..) => Self(Union::TimeStamp(v.clone(), tag, ReadWrite)), Union::Variant(ref v, tag, ..) => Self(Union::Variant( v.as_ref().as_ref().clone_object().into(), tag, ReadWrite, )), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, tag, ..) => Self(Union::Shared(cell.clone(), tag, ReadWrite)), } } } impl Default for Dynamic { #[inline(always)] fn default() -> Self { Self::UNIT } } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] use std::f32::consts as FloatConstants; #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] use std::f64::consts as FloatConstants; impl Dynamic { /// A [`Dynamic`] containing a `()`. pub const UNIT: Self = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite)); /// A [`Dynamic`] containing a `true`. pub const TRUE: Self = Self::from_bool(true); /// A [`Dynamic`] containing a [`false`]. pub const FALSE: Self = Self::from_bool(false); /// A [`Dynamic`] containing the integer zero. pub const ZERO: Self = Self::from_int(0); /// A [`Dynamic`] containing the integer 1. pub const ONE: Self = Self::from_int(1); /// A [`Dynamic`] containing the integer 2. pub const TWO: Self = Self::from_int(2); /// A [`Dynamic`] containing the integer 3. pub const THREE: Self = Self::from_int(3); /// A [`Dynamic`] containing the integer 10. pub const TEN: Self = Self::from_int(10); /// A [`Dynamic`] containing the integer 100. pub const HUNDRED: Self = Self::from_int(100); /// A [`Dynamic`] containing the integer 1,000. pub const THOUSAND: Self = Self::from_int(1000); /// A [`Dynamic`] containing the integer 1,000,000. pub const MILLION: Self = Self::from_int(1_000_000); /// A [`Dynamic`] containing the integer -1. pub const NEGATIVE_ONE: Self = Self::from_int(-1); /// A [`Dynamic`] containing the integer -2. pub const NEGATIVE_TWO: Self = Self::from_int(-2); /// A [`Dynamic`] containing `0.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_ZERO: Self = Self::from_float(0.0); /// A [`Dynamic`] containing `1.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_ONE: Self = Self::from_float(1.0); /// A [`Dynamic`] containing `2.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_TWO: Self = Self::from_float(2.0); /// A [`Dynamic`] containing `10.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_TEN: Self = Self::from_float(10.0); /// A [`Dynamic`] containing `100.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_HUNDRED: Self = Self::from_float(100.0); /// A [`Dynamic`] containing `1000.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_THOUSAND: Self = Self::from_float(1000.0); /// A [`Dynamic`] containing `1000000.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_MILLION: Self = Self::from_float(1_000_000.0); /// A [`Dynamic`] containing `-1.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_NEGATIVE_ONE: Self = Self::from_float(-1.0); /// A [`Dynamic`] containing `-2.0`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_NEGATIVE_TWO: Self = Self::from_float(-2.0); /// A [`Dynamic`] containing `0.5`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_HALF: Self = Self::from_float(0.5); /// A [`Dynamic`] containing `0.25`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_QUARTER: Self = Self::from_float(0.25); /// A [`Dynamic`] containing `0.2`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_FIFTH: Self = Self::from_float(0.2); /// A [`Dynamic`] containing `0.1`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_TENTH: Self = Self::from_float(0.1); /// A [`Dynamic`] containing `0.01`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_HUNDREDTH: Self = Self::from_float(0.01); /// A [`Dynamic`] containing `0.001`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_THOUSANDTH: Self = Self::from_float(0.001); /// A [`Dynamic`] containing `0.000001`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_MILLIONTH: Self = Self::from_float(0.000_001); /// A [`Dynamic`] containing π. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_PI: Self = Self::from_float(FloatConstants::PI); /// A [`Dynamic`] containing π/2. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_HALF_PI: Self = Self::from_float(FloatConstants::FRAC_PI_2); /// A [`Dynamic`] containing π/4. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_QUARTER_PI: Self = Self::from_float(FloatConstants::FRAC_PI_4); /// A [`Dynamic`] containing 2π. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_TWO_PI: Self = Self::from_float(FloatConstants::TAU); /// A [`Dynamic`] containing 1/π. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_INVERSE_PI: Self = Self::from_float(FloatConstants::FRAC_1_PI); /// A [`Dynamic`] containing _e_. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_E: Self = Self::from_float(FloatConstants::E); /// A [`Dynamic`] containing `log` _e_. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_LOG_E: Self = Self::from_float(FloatConstants::LOG10_E); /// A [`Dynamic`] containing `ln 10`. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] pub const FLOAT_LN_10: Self = Self::from_float(FloatConstants::LN_10); /// Create a new [`Dynamic`] from a [`bool`]. #[inline(always)] pub const fn from_bool(value: bool) -> Self { Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a new [`Dynamic`] from an [`INT`]. #[inline(always)] pub const fn from_int(value: INT) -> Self { Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a new [`Dynamic`] from a [`char`]. #[inline(always)] pub const fn from_char(value: char) -> Self { Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a new [`Dynamic`] from a [`FLOAT`][crate::FLOAT]. /// /// Not available under `no_float`. #[cfg(not(feature = "no_float"))] #[inline(always)] pub const fn from_float(value: crate::FLOAT) -> Self { Self(Union::Float( super::FloatWrapper::new(value), DEFAULT_TAG_VALUE, ReadWrite, )) } /// Create a new [`Dynamic`] from a [`Decimal`](https://docs.rs/rust_decimal). /// /// Exported under the `decimal` feature only. #[cfg(feature = "decimal")] #[inline(always)] pub fn from_decimal(value: rust_decimal::Decimal) -> Self { Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a [`Dynamic`] from an [`Array`]. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn from_array(array: Array) -> Self { Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a [`Dynamic`] from a [`Blob`]. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn from_blob(blob: Blob) -> Self { Self(Union::Blob(blob.into(), DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a [`Dynamic`] from a [`Map`]. #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn from_map(map: Map) -> Self { Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite)) } /// Create a new [`Dynamic`] from an [`Instant`]. /// /// Not available under `no-std` or `no_time`. #[cfg(not(feature = "no_time"))] #[inline(always)] pub fn from_timestamp(value: Instant) -> Self { Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } /// Get the [`AccessMode`] for this [`Dynamic`]. #[must_use] pub(crate) const fn access_mode(&self) -> AccessMode { match self.0 { Union::Unit(.., access) | Union::Bool(.., access) | Union::Str(.., access) | Union::Char(.., access) | Union::Int(.., access) | Union::FnPtr(.., access) | Union::Variant(.., access) => access, #[cfg(not(feature = "no_float"))] Union::Float(.., access) => access, #[cfg(feature = "decimal")] Union::Decimal(.., access) => access, #[cfg(not(feature = "no_index"))] Union::Array(.., access) | Union::Blob(.., access) => access, #[cfg(not(feature = "no_object"))] Union::Map(.., access) => access, #[cfg(not(feature = "no_time"))] Union::TimeStamp(.., access) => access, #[cfg(not(feature = "no_closure"))] Union::Shared(.., access) => access, } } /// Set the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn set_access_mode(&mut self, typ: AccessMode) -> &mut Self { match self.0 { Union::Unit(.., ref mut access) | Union::Bool(.., ref mut access) | Union::Str(.., ref mut access) | Union::Char(.., ref mut access) | Union::Int(.., ref mut access) | Union::FnPtr(.., ref mut access) | Union::Variant(.., ref mut access) => *access = typ, #[cfg(not(feature = "no_float"))] Union::Float(.., ref mut access) => *access = typ, #[cfg(feature = "decimal")] Union::Decimal(.., ref mut access) => *access = typ, #[cfg(not(feature = "no_index"))] Union::Array(ref mut a, _, ref mut access) => { *access = typ; for v in a.as_mut() { v.set_access_mode(typ); } } #[cfg(not(feature = "no_index"))] Union::Blob(.., ref mut access) => *access = typ, #[cfg(not(feature = "no_object"))] Union::Map(ref mut m, _, ref mut access) => { *access = typ; for v in m.values_mut() { v.set_access_mode(typ); } } #[cfg(not(feature = "no_time"))] Union::TimeStamp(.., ref mut access) => *access = typ, #[cfg(not(feature = "no_closure"))] Union::Shared(.., ref mut access) => *access = typ, } self } /// Make this [`Dynamic`] read-only (i.e. a constant). #[inline(always)] pub fn into_read_only(self) -> Self { let mut value = self; value.set_access_mode(AccessMode::ReadOnly); value } /// Is this [`Dynamic`] read-only? /// /// Constant [`Dynamic`] values are read-only. /// /// # Usage /// /// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function /// can use this information to return the error /// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value /// will be modified. /// /// This safe-guards constant values from being modified within Rust functions. /// /// # Shared Value /// /// If a [`Dynamic`] holds a _shared_ value, then it is read-only only if the shared value /// itself is read-only. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its access mode cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[must_use] pub fn is_read_only(&self) -> bool { #[cfg(not(feature = "no_closure"))] if let Union::Shared(ref cell, ..) = self.0 { return match crate::func::locked_read(cell).map_or(ReadWrite, |v| v.access_mode()) { ReadWrite => false, ReadOnly => true, }; } match self.access_mode() { ReadWrite => false, ReadOnly => true, } } /// Can this [`Dynamic`] be hashed? /// /// # Shared Value /// /// If a [`Dynamic`] holds a _shared_ value, then it is hashable only if the shared value /// itself is hashable. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[must_use] pub(crate) fn is_hashable(&self) -> bool { match self.0 { Union::Unit(..) | Union::Bool(..) | Union::Str(..) | Union::Char(..) | Union::Int(..) => true, #[cfg(not(feature = "no_float"))] Union::Float(..) => true, #[cfg(feature = "decimal")] Union::Decimal(..) => true, #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => a.iter().all(Self::is_hashable), #[cfg(not(feature = "no_index"))] Union::Blob(..) => true, #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => m.values().all(Self::is_hashable), #[cfg(not(feature = "no_function"))] Union::FnPtr(ref f, ..) if f.env.is_some() => false, Union::FnPtr(ref f, ..) => f.curry().iter().all(Self::is_hashable), #[cfg(not(feature = "no_time"))] Union::TimeStamp(..) => false, Union::Variant(ref v, ..) => { let _value_any = (***v).as_any(); let _type_id = _value_any.type_id(); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] if _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() || _type_id == TypeId::of::() { return true; } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] if _type_id == TypeId::of::() { return true; } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] if _type_id == TypeId::of::() { return true; } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] if _type_id == TypeId::of::() || _type_id == TypeId::of::() { return true; } if _type_id == TypeId::of::() || _type_id == TypeId::of::() { return true; } false } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) if cfg!(feature = "unchecked") => { crate::func::locked_read(cell).map_or(false, |v| v.is_hashable()) } #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { #[cfg(feature = "no_std")] use hashbrown::HashSet; #[cfg(not(feature = "no_std"))] use std::collections::HashSet; // Avoid infinite recursion for shared values in a reference loop. fn checked_is_hashable( value: &Dynamic, dict: &mut HashSet<*const Dynamic>, ) -> bool { match value.0 { Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .map_or(false, |v| { dict.insert(value) && checked_is_hashable(&v, dict) }), #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => a.iter().all(|v| checked_is_hashable(v, dict)), #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => m.values().all(|v| checked_is_hashable(v, dict)), Union::FnPtr(ref f, ..) => { f.env.is_none() && f.curry().iter().all(|v| checked_is_hashable(v, dict)) } _ => value.is_hashable(), } } checked_is_hashable(self, &mut <_>::default()) } } } /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// /// # Arrays /// /// Beware that you need to pass in an [`Array`] type for it to be recognized as /// an [`Array`]. A [`Vec`][Vec] does not get automatically converted to an /// [`Array`], but will be a custom type instead (stored as a trait object). /// /// Use `array.into()` or `array.into_iter()` to convert a [`Vec`][Vec] into a [`Dynamic`] as /// an [`Array`] value. See the examples for details. /// /// # Hash Maps /// /// Similarly, passing in a [`HashMap`][std::collections::HashMap] or /// [`BTreeMap`][std::collections::BTreeMap] will not get a [`Map`] but a /// custom type. /// /// Again, use `map.into()` to get a [`Dynamic`] with a [`Map`] value. /// See the examples for details. /// /// # Examples /// /// ``` /// use rhai::Dynamic; /// /// let result = Dynamic::from(42_i64); /// assert_eq!(result.type_name(), "i64"); /// assert_eq!(result.to_string(), "42"); /// /// let result = Dynamic::from("hello"); /// assert_eq!(result.type_name(), "string"); /// assert_eq!(result.to_string(), "hello"); /// /// let new_result = Dynamic::from(result); /// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.to_string(), "hello"); /// /// # #[cfg(not(feature = "no_index"))] /// # { /// // Arrays - this is a custom object! /// let result = Dynamic::from(vec![1_i64, 2, 3]); /// assert_eq!(result.type_name(), "alloc::vec::Vec"); /// /// // Use '.into()' to convert a Vec into an Array /// let result: Dynamic = vec![1_i64, 2, 3].into(); /// assert_eq!(result.type_name(), "array"); /// # } /// /// # #[cfg(not(feature = "no_object"))] /// # { /// # use std::collections::HashMap; /// // Hash map /// let mut map = HashMap::new(); /// map.insert("a".to_string(), 1_i64); /// /// // This is a custom object! /// let result = Dynamic::from(map.clone()); /// assert_eq!(result.type_name(), "std::collections::hash::map::HashMap"); /// /// // Use '.into()' to convert a HashMap into an object map /// let result: Dynamic = map.into(); /// assert_eq!(result.type_name(), "map"); /// # } /// ``` #[inline] pub fn from(value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. reify! { value => |v: Self| return v } reify! { value => |v: INT| return v.into() } #[cfg(not(feature = "no_float"))] reify! { value => |v: crate::FLOAT| return v.into() } #[cfg(feature = "decimal")] reify! { value => |v: rust_decimal::Decimal| return v.into() } reify! { value => |v: bool| return v.into() } reify! { value => |v: char| return v.into() } reify! { value => |v: ImmutableString| return v.into() } reify! { value => |v: String| return v.into() } reify! { value => |v: &str| return v.into() } reify! { value => |v: ()| return v.into() } #[cfg(not(feature = "no_index"))] reify! { value => |v: Array| return v.into() } #[cfg(not(feature = "no_index"))] // don't use blob.into() because it'll be converted into an Array reify! { value => |v: Blob| return Self::from_blob(v) } #[cfg(not(feature = "no_object"))] reify! { value => |v: Map| return v.into() } reify! { value => |v: FnPtr| return v.into() } #[cfg(not(feature = "no_time"))] reify! { value => |v: Instant| return v.into() } #[cfg(not(feature = "no_closure"))] reify! { value => |v: crate::Shared>| return v.into() } Self(Union::Variant( Box::new(Box::new(value)), DEFAULT_TAG_VALUE, ReadWrite, )) } /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an /// [`Rc>`][std::rc::Rc] or [`Arc>`][std::sync::Arc] /// depending on the `sync` feature. /// /// Not available under `no_closure`. /// /// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the /// reference counts. /// /// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] /// values. /// /// If the [`Dynamic`] value is already shared, this method returns itself. #[cfg(not(feature = "no_closure"))] #[inline] pub fn into_shared(self) -> Self { let _access = self.access_mode(); match self.0 { Union::Shared(..) => self, _ => Self(Union::Shared( crate::Locked::new(self).into(), DEFAULT_TAG_VALUE, _access, )), } } /// Return this [`Dynamic`], replacing it with [`Dynamic::UNIT`]. #[inline(always)] pub fn take(&mut self) -> Self { mem::take(self) } /// Convert the [`Dynamic`] value into specific type. /// /// Casting to a [`Dynamic`] simply returns itself. /// /// # Errors /// /// Returns [`None`] if types mismatch. /// /// # Shared Value /// /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no /// outstanding references, or a cloned copy otherwise. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// /// ``` /// use rhai::Dynamic; /// /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.try_cast::().expect("x should be u32"), 42); /// ``` #[inline(always)] #[must_use] #[allow(unused_mut)] pub fn try_cast(mut self) -> Option { self.try_cast_result().ok() } /// Convert the [`Dynamic`] value into specific type. /// /// Casting to a [`Dynamic`] simply returns itself. /// /// # Errors /// /// Returns itself as an error if types mismatch. /// /// # Shared Value /// /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no /// outstanding references, or a cloned copy otherwise. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[allow(unused_mut)] pub fn try_cast_result(mut self) -> Result { // Coded this way in order to maximally leverage potentials for dead-code removal. #[cfg(not(feature = "no_closure"))] { self = self.flatten(); } if TypeId::of::() == TypeId::of::() { return Ok(reify! { self => !!! T }); } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { Union::Unit(..) => Ok(reify! { () => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Int(n, ..) => Ok(reify! { n => !!! T }), _ => Err(self), }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Float(v, ..) => Ok(reify! { *v => !!! T }), _ => Err(self), }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Decimal(v, ..) => Ok(reify! { *v => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Bool(b, ..) => Ok(reify! { b => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(s, ..) => Ok(reify! { s => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(s, ..) => Ok(reify! { s.to_string() => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Char(c, ..) => Ok(reify! { c => !!! T }), _ => Err(self), }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Array(a, ..) => Ok(reify! { *a => !!! T }), _ => Err(self), }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Blob(b, ..) => Ok(reify! { *b => !!! T }), _ => Err(self), }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Map(m, ..) => Ok(reify! { *m => !!! T }), _ => Err(self), }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::FnPtr(f, ..) => Ok(reify! { *f => !!! T }), _ => Err(self), }; } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::TimeStamp(t, ..) => Ok(reify! { *t => !!! T }), _ => Err(self), }; } match self.0 { Union::Variant(v, ..) if TypeId::of::() == (**v).type_id() => { Ok((*v).as_boxed_any().downcast().map(|x| *x).unwrap()) } _ => Err(self), } } /// Convert the [`Dynamic`] value into a specific type. /// /// Casting to a [`Dynamic`] just returns as is. /// /// # Panics /// /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). /// /// # Shared Value /// /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no /// outstanding references, or a cloned copy otherwise. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the _shared_ value is simply cloned, which means that the returned /// value is also shared. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// /// ``` /// use rhai::Dynamic; /// /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.cast::(), 42); /// ``` #[inline] #[must_use] pub fn cast(self) -> T { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { return reify! { self => !!! T }; } #[cfg(not(feature = "no_closure"))] let self_type_name = if self.is_shared() { // Avoid panics/deadlocks with shared values "" } else { self.type_name() }; #[cfg(feature = "no_closure")] let self_type_name = self.type_name(); self.try_cast::() .unwrap_or_else(|| panic!("cannot cast {} to {}", self_type_name, type_name::())) } /// Clone the [`Dynamic`] value and convert it into a specific type. /// /// Casting to a [`Dynamic`] just returns as is. /// /// # Panics /// /// Panics if the cast fails (e.g. the type of the actual value is not the /// same as the specified type). /// /// # Shared Value /// /// If the [`Dynamic`] is a _shared_ value, a cloned copy of the shared value is returned. /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the _shared_ value is simply cloned. /// /// This normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// /// ``` /// use rhai::Dynamic; /// /// let x = Dynamic::from(42_u32); /// let y = &x; /// /// assert_eq!(y.clone_cast::(), 42); /// ``` #[inline(always)] #[must_use] pub fn clone_cast(&self) -> T { self.flatten_clone().cast::() } /// Flatten the [`Dynamic`] and clone it. /// /// If the [`Dynamic`] is not a _shared_ value, a cloned copy is returned. /// /// If the [`Dynamic`] is a _shared_ value, a cloned copy of the shared value is returned. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the _shared_ value is simply cloned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn flatten_clone(&self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or_else(|| self.clone(), |v| v.flatten_clone()) } _ => self.clone(), } } /// Flatten the [`Dynamic`]. /// /// If the [`Dynamic`] is not a _shared_ value, it simply returns itself. /// /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no /// outstanding references, or a cloned copy otherwise. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the _shared_ value is simply cloned, meaning that the result /// value will also be _shared_. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn flatten(self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(cell, tag, access) => match crate::func::native::shared_try_take(cell) { // If there are no outstanding references, consume the shared value and return it #[cfg(not(feature = "sync"))] Ok(value) => value.into_inner().flatten(), #[cfg(feature = "sync")] #[cfg(not(feature = "no_std"))] Ok(value) => value.into_inner().unwrap().flatten(), #[cfg(feature = "sync")] #[cfg(feature = "no_std")] Ok(value) => value.into_inner().flatten(), // If there are outstanding references, return a cloned copy Err(cell) => { if let Some(guard) = crate::func::locked_read(&cell) { return guard.flatten_clone(); } Self(Union::Shared(cell, tag, access)) } }, _ => self, } } /// Is the [`Dynamic`] a _shared_ value that is locked? /// /// Not available under `no_closure`. /// /// ## Note /// /// Under the `sync` feature, shared values use [`RwLock`][std::sync::RwLock] and they are never locked. /// Access just waits until the [`RwLock`][std::sync::RwLock] is released. /// So this method always returns [`false`] under [`Sync`]. #[cfg(not(feature = "no_closure"))] #[inline] #[must_use] pub fn is_locked(&self) -> bool { #[cfg(not(feature = "no_closure"))] if let Union::Shared(ref _cell, ..) = self.0 { #[cfg(not(feature = "sync"))] return _cell.try_borrow().is_err(); #[cfg(feature = "sync")] return false; } false } /// Get a reference of a specific type to the [`Dynamic`]. /// /// Casting to [`Dynamic`] just returns a reference to it. /// /// Returns [`None`] if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, this call also fails if the data is currently borrowed for write. /// /// Under these circumstances, [`None`] is also returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn read_lock(&self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { return match crate::func::locked_read(cell) { Some(guard) if TypeId::of::() == TypeId::of::() || (*guard).type_id() == TypeId::of::() => { Some(DynamicReadLock(DynamicReadLockInner::Guard(guard))) } _ => None, }; } _ => (), } self.downcast_ref() .map(DynamicReadLockInner::Reference) .map(DynamicReadLock) } /// Get a mutable reference of a specific type to the [`Dynamic`]. /// /// Casting to [`Dynamic`] just returns a mutable reference to it. /// /// Returns [`None`] if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, this call also fails if the data is currently borrowed for write. /// /// Under these circumstances, [`None`] is also returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn write_lock(&mut self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { return match crate::func::locked_write(cell) { Some(guard) if TypeId::of::() == TypeId::of::() || (*guard).type_id() == TypeId::of::() => { Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard))) } _ => None, }; } _ => (), } self.downcast_mut() .map(DynamicWriteLockInner::Reference) .map(DynamicWriteLock) } /// Get a reference of a specific type to the [`Dynamic`]. /// /// Casting to [`Dynamic`] just returns a reference to it. /// /// Returns [`None`] if the cast fails. /// /// # Shared Value /// /// Returns [`None`] also if the value is _shared_. #[inline] #[must_use] pub(crate) fn downcast_ref(&self) -> Option<&T> { // Coded this way in order to maximally leverage potentials for dead-code removal. if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Int(ref v, ..) => v.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Float(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Decimal(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Bool(ref v, ..) => v.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(ref v, ..) => v.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Char(ref v, ..) => v.as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Array(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Blob(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Map(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::FnPtr(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::TimeStamp(ref v, ..) => v.as_ref().as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { Union::Unit(ref v, ..) => v.as_any().downcast_ref::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return self.as_any().downcast_ref::(); } match self.0 { Union::Variant(ref v, ..) => (***v).as_any().downcast_ref::(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => None, _ => None, } } /// Get a mutable reference of a specific type to the [`Dynamic`]. /// /// Casting to [`Dynamic`] just returns a mutable reference to it. /// /// Returns [`None`] if the cast fails. /// /// # Shared Value /// /// Returns [`None`] also if the value is _shared_. #[inline] #[must_use] pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { // Coded this way in order to maximally leverage potentials for dead-code removal. if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Int(ref mut v, ..) => v.as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Float(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Decimal(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Bool(ref mut v, ..) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Str(ref mut v, ..) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Char(ref mut v, ..) => v.as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Array(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Blob(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::Map(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { Union::FnPtr(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { Union::TimeStamp(ref mut v, ..) => v.as_mut().as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { Union::Unit(ref mut v, ..) => v.as_any_mut().downcast_mut::(), _ => None, }; } if TypeId::of::() == TypeId::of::() { return self.as_any_mut().downcast_mut::(); } match self.0 { Union::Variant(ref mut v, ..) => (***v).as_any_mut().downcast_mut::(), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => None, _ => None, } } /// Return `true` if the [`Dynamic`] holds a `()`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_unit(&self) -> bool { match self.0 { Union::Unit(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Unit(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds the system integer type [`INT`]. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_int(&self) -> bool { match self.0 { Union::Int(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Int(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds the system floating-point type [`FLOAT`][crate::FLOAT]. /// /// Not available under `no_float`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_float"))] #[inline] #[must_use] pub fn is_float(&self) -> bool { match self.0 { Union::Float(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Float(..))) } _ => false, } } /// _(decimal)_ Return `true` if the [`Dynamic`] holds a [`Decimal`][rust_decimal::Decimal]. /// Exported under the `decimal` feature only. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(feature = "decimal")] #[inline] #[must_use] pub fn is_decimal(&self) -> bool { match self.0 { Union::Decimal(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Decimal(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`bool`]. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_bool(&self) -> bool { match self.0 { Union::Bool(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Bool(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`char`]. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_char(&self) -> bool { match self.0 { Union::Char(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Char(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds an [`ImmutableString`]. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_string(&self) -> bool { match self.0 { Union::Str(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Str(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds an [`Array`]. /// /// Not available under `no_index`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline] #[must_use] pub fn is_array(&self) -> bool { match self.0 { Union::Array(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Array(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`Blob`]. /// /// Not available under `no_index`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline] #[must_use] pub fn is_blob(&self) -> bool { match self.0 { Union::Blob(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Blob(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`Map`]. /// /// Not available under `no_object`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_object"))] #[inline] #[must_use] pub fn is_map(&self) -> bool { match self.0 { Union::Map(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::Map(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`FnPtr`]. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_fnptr(&self) -> bool { match self.0 { Union::FnPtr(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { crate::func::locked_read(cell).map_or(false, |v| matches!(v.0, Union::FnPtr(..))) } _ => false, } } /// Return `true` if the [`Dynamic`] holds a [timestamp][Instant]. /// /// Not available under `no_time`. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, `false` is returned. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_time"))] #[inline] #[must_use] pub fn is_timestamp(&self) -> bool { match self.0 { Union::TimeStamp(..) => true, #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .map_or(false, |v| matches!(v.0, Union::TimeStamp(..))), _ => false, } } /// Cast the [`Dynamic`] as a unit `()`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_unit(&self) -> Result<(), &'static str> { match self.0 { Union::Unit(..) => Ok(()), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Unit(..) => Some(()), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as the system integer type [`INT`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_int(&self) -> Result { match self.0 { Union::Int(n, ..) => Ok(n), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Int(n, ..) => Some(n), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`][crate::FLOAT]. /// /// Not available under `no_float`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_float"))] #[inline] pub fn as_float(&self) -> Result { match self.0 { Union::Float(n, ..) => Ok(*n), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Float(n, ..) => Some(*n), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`][rust_decimal::Decimal]. /// Exported under the `decimal` feature only. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(feature = "decimal")] #[inline] pub fn as_decimal(&self) -> Result { match self.0 { Union::Decimal(ref n, ..) => Ok(**n), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Decimal(ref n, ..) => Some(**n), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as a [`bool`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b, ..) => Ok(b), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Bool(b, ..) => Some(b), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as a [`char`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_char(&self) -> Result { match self.0 { Union::Char(c, ..) => Ok(c), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Char(c, ..) => Some(c), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as an [`ImmutableString`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_immutable_string_ref( &self, ) -> Result + '_, &'static str> { self.read_lock::() .ok_or_else(|| self.type_name()) } /// Cast the [`Dynamic`] as a mutable reference to an [`ImmutableString`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_immutable_string_mut( &mut self, ) -> Result + '_, &'static str> { let type_name = self.type_name(); self.write_lock::().ok_or(type_name) } /// Cast the [`Dynamic`] as an [`Array`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn as_array_ref(&self) -> Result + '_, &'static str> { self.read_lock::().ok_or_else(|| self.type_name()) } /// Cast the [`Dynamic`] as a mutable reference to an [`Array`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn as_array_mut(&mut self) -> Result + '_, &'static str> { let type_name = self.type_name(); self.write_lock::().ok_or(type_name) } /// Cast the [`Dynamic`] as a [`Blob`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn as_blob_ref(&self) -> Result + '_, &'static str> { self.read_lock::().ok_or_else(|| self.type_name()) } /// Cast the [`Dynamic`] as a mutable reference to a [`Blob`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn as_blob_mut(&mut self) -> Result + '_, &'static str> { let type_name = self.type_name(); self.write_lock::().ok_or(type_name) } /// Cast the [`Dynamic`] as a [`Map`]. /// /// Not available under `no_object`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn as_map_ref(&self) -> Result + '_, &'static str> { self.read_lock::().ok_or_else(|| self.type_name()) } /// Cast the [`Dynamic`] as a mutable reference to a [`Map`]. /// /// Not available under `no_object`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn as_map_mut(&mut self) -> Result + '_, &'static str> { let type_name = self.type_name(); self.write_lock::().ok_or(type_name) } /// Convert the [`Dynamic`] into a [`String`]. /// /// If there are other references to the same string, a cloned copy is returned. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn into_string(self) -> Result { self.into_immutable_string() .map(ImmutableString::into_owned) } /// Convert the [`Dynamic`] into an [`ImmutableString`]. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn into_immutable_string(self) -> Result { match self.0 { Union::Str(s, ..) => Ok(s), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Str(ref s, ..) => Some(s.clone()), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into an [`Array`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_array(self) -> Result { match self.0 { Union::Array(a, ..) => Ok(*a), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Array(ref a, ..) => Some(a.as_ref().clone()), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into a [`Vec`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_typed_array(self) -> Result, &'static str> { match self.0 { Union::Array(a, ..) => a .into_iter() .map(|v| { #[cfg(not(feature = "no_closure"))] let typ = if v.is_shared() { // Avoid panics/deadlocks with shared values "" } else { v.type_name() }; #[cfg(feature = "no_closure")] let typ = v.type_name(); v.try_cast::().ok_or(typ) }) .collect(), Union::Blob(b, ..) if TypeId::of::() == TypeId::of::() => { Ok(reify! { *b => !!! Vec }) } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Array(ref a, ..) => a .iter() .map(|v| v.read_lock::().map(|v| v.clone())) .collect(), Union::Blob(ref b, ..) if TypeId::of::() == TypeId::of::() => { Some(reify! { b.clone() => !!! Vec }) } _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into a [`Blob`]. /// /// Not available under `no_index`. /// /// # Errors /// /// Returns the name of the actual type as an error if the cast fails. /// /// # Shared Value /// /// Under the `sync` feature, a _shared_ value may deadlock. /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// /// Under these circumstances, the cast also fails. /// /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_blob(self) -> Result { match self.0 { Union::Blob(b, ..) => Ok(*b), #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => crate::func::locked_read(cell) .and_then(|guard| match guard.0 { Union::Blob(ref b, ..) => Some(b.as_ref().clone()), _ => None, }) .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Recursively scan for [`Dynamic`] values within this [`Dynamic`] (e.g. items in an array or map), /// calling a filter function on each. /// /// # Shared Value /// /// Shared values are _NOT_ scanned. #[inline] #[allow(clippy::only_used_in_recursion)] pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) { fn scan_inner(value: &mut Dynamic, filter: &mut (impl FnMut(&mut Dynamic) + ?Sized)) { filter(value); match &mut value.0 { #[cfg(not(feature = "no_index"))] Union::Array(a, ..) => a.iter_mut().for_each(|v| scan_inner(v, filter)), #[cfg(not(feature = "no_object"))] Union::Map(m, ..) => m.values_mut().for_each(|v| scan_inner(v, filter)), Union::FnPtr(f, ..) => f.iter_curry_mut().for_each(|v| scan_inner(v, filter)), _ => (), } } scan_inner(self, &mut filter); } } impl From<()> for Dynamic { #[inline(always)] fn from(value: ()) -> Self { Self(Union::Unit(value, DEFAULT_TAG_VALUE, ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: bool) -> Self { Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: INT) -> Self { Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite)) } } #[cfg(not(feature = "no_float"))] impl From for Dynamic { #[inline(always)] fn from(value: crate::FLOAT) -> Self { Self(Union::Float(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } #[cfg(not(feature = "no_float"))] impl From> for Dynamic { #[inline(always)] fn from(value: super::FloatWrapper) -> Self { Self(Union::Float(value, DEFAULT_TAG_VALUE, ReadWrite)) } } #[cfg(feature = "decimal")] impl From for Dynamic { #[inline(always)] fn from(value: rust_decimal::Decimal) -> Self { Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: char) -> Self { Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite)) } } impl> From for Dynamic { #[inline(always)] fn from(value: S) -> Self { Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } impl FromStr for Dynamic { type Err = (); fn from_str(value: &str) -> Result { Ok(Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite))) } } #[cfg(not(feature = "no_index"))] impl From> for Dynamic { #[inline] fn from(value: Vec) -> Self { Self(Union::Array( Box::new(value.into_iter().map(Self::from).collect()), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_index"))] impl From<&[T]> for Dynamic { #[inline] fn from(value: &[T]) -> Self { Self(Union::Array( Box::new(value.iter().cloned().map(Self::from).collect()), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_index"))] impl std::iter::FromIterator for Dynamic { #[inline] fn from_iter>(iter: X) -> Self { Self(Union::Array( Box::new(iter.into_iter().map(Self::from).collect()), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_std"))] impl, T: Variant + Clone> From> for Dynamic { #[inline] fn from(value: std::collections::HashMap) -> Self { Self(Union::Map( Box::new( value .into_iter() .map(|(k, v)| (k.into(), Self::from(v))) .collect(), ), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_std"))] impl> From> for Dynamic { #[inline] fn from(value: std::collections::HashSet) -> Self { Self(Union::Map( Box::new(value.into_iter().map(|k| (k.into(), Self::UNIT)).collect()), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_object"))] impl, T: Variant + Clone> From> for Dynamic { #[inline] fn from(value: std::collections::BTreeMap) -> Self { Self(Union::Map( Box::new( value .into_iter() .map(|(k, v)| (k.into(), Self::from(v))) .collect(), ), DEFAULT_TAG_VALUE, ReadWrite, )) } } #[cfg(not(feature = "no_object"))] impl> From> for Dynamic { #[inline] fn from(value: std::collections::BTreeSet) -> Self { Self(Union::Map( Box::new(value.into_iter().map(|k| (k.into(), Self::UNIT)).collect()), DEFAULT_TAG_VALUE, ReadWrite, )) } } impl From for Dynamic { #[inline(always)] fn from(value: FnPtr) -> Self { Self(Union::FnPtr(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } #[cfg(not(feature = "no_time"))] impl From for Dynamic { #[inline(always)] fn from(value: Instant) -> Self { Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } #[cfg(not(feature = "no_closure"))] impl From>> for Dynamic { #[inline(always)] fn from(value: crate::Shared>) -> Self { Self(Union::Shared(value, DEFAULT_TAG_VALUE, ReadWrite)) } } impl From for Dynamic { #[inline(always)] fn from(value: ExclusiveRange) -> Self { Self::from(value) } } impl From for Dynamic { #[inline(always)] fn from(value: InclusiveRange) -> Self { Self::from(value) } } rhai-1.21.0/src/types/error.rs000064400000000000000000000620051046102023000142650ustar 00000000000000//! Module containing error definitions for the evaluation process. use crate::{Dynamic, ParseErrorType, Position, INT}; #[cfg(feature = "no_std")] use core_error::Error; #[cfg(not(feature = "no_std"))] use std::error::Error; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Evaluation result. /// /// All wrapped [`Position`] values represent the location in the script where the error occurs. /// /// Some errors never appear when certain features are turned on. /// They still exist so that the application can turn features on and off without going through /// massive code changes to remove/add back enum variants in match statements. /// /// # Thread Safety /// /// Currently, [`EvalAltResult`] is neither [`Send`] nor [`Sync`]. /// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`]. #[derive(Debug)] #[non_exhaustive] #[must_use] pub enum EvalAltResult { /// System error. Wrapped values are the error message and the internal error. #[cfg(not(feature = "sync"))] ErrorSystem(String, Box), /// System error. Wrapped values are the error message and the internal error. #[cfg(feature = "sync")] ErrorSystem(String, Box), /// Syntax error. ErrorParsing(ParseErrorType, Position), /// Shadowing of an existing variable disallowed. Wrapped value is the variable name. ErrorVariableExists(String, Position), /// Forbidden variable name. Wrapped value is the variable name. ErrorForbiddenVariable(String, Position), /// Access of an unknown variable. Wrapped value is the variable name. ErrorVariableNotFound(String, Position), /// Access of an unknown object map property. Wrapped value is the property name. ErrorPropertyNotFound(String, Position), /// Access of an invalid index. Wrapped value is the index name. ErrorIndexNotFound(Dynamic, Position), /// Call to an unknown function. Wrapped value is the function signature. ErrorFunctionNotFound(String, Position), /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name. ErrorModuleNotFound(String, Position), /// An error has occurred inside a called function. /// Wrapped values are the function name, function source, and the interior error. ErrorInFunctionCall(String, String, Box, Position), /// An error has occurred while loading a [module][crate::Module]. /// Wrapped value are the [module][crate::Module] name and the interior error. ErrorInModule(String, Box, Position), /// Access to `this` that is not bound. ErrorUnboundThis(Position), /// Data is not of the required type. /// Wrapped values are the type requested and type of the actual result. ErrorMismatchDataType(String, String, Position), /// Returned type is not the same as the required output type. /// Wrapped values are the type requested and type of the actual result. ErrorMismatchOutputType(String, String, Position), /// Trying to index into a type that has no indexer function defined. Wrapped value is the type name. ErrorIndexingType(String, Position), /// Array access out-of-bounds. /// Wrapped values are the current number of elements in the array and the index number. ErrorArrayBounds(usize, INT, Position), /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, INT, Position), /// Bit-field indexing out-of-bounds. /// Wrapped values are the current number of bits in the bit-field and the index number. ErrorBitFieldBounds(usize, INT, Position), /// The `for` statement encounters a type that is not iterable. ErrorFor(Position), /// Data race detected when accessing a variable. Wrapped value is the variable name. ErrorDataRace(String, Position), /// Calling a non-pure method on a constant. Wrapped value is the function name. ErrorNonPureMethodCallOnConstant(String, Position), /// Assignment to a constant variable. Wrapped value is the variable name. ErrorAssignmentToConstant(String, Position), /// Inappropriate property access. Wrapped value is the property name. ErrorDotExpr(String, Position), /// Arithmetic error encountered. Wrapped value is the error message. ErrorArithmetic(String, Position), /// Number of operations over maximum limit. ErrorTooManyOperations(Position), /// Number of variables over maximum limit. ErrorTooManyVariables(Position), /// [Modules][crate::Module] over maximum limit. ErrorTooManyModules(Position), /// Call stack over maximum limit. ErrorStackOverflow(Position), /// Data value over maximum size limit. Wrapped value is the type name. ErrorDataTooLarge(String, Position), /// The script is prematurely terminated. Wrapped value is the termination token. ErrorTerminated(Dynamic, Position), /// Error encountered for a custom syntax. Wrapped values are the error message and /// custom syntax symbols stream. /// /// Normally this should never happen, unless an [`AST`][crate::AST] is compiled on one /// [`Engine`][crate::Engine] but evaluated on another unrelated [`Engine`][crate::Engine]. ErrorCustomSyntax(String, Vec, Position), /// Run-time error encountered. Wrapped value is the error token. ErrorRuntime(Dynamic, Position), /// Breaking out of loops - not an error if within a loop. /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). LoopBreak(bool, Dynamic, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), /// Not an error: Value returned from a script via the `exit` function. /// Wrapped value is the exit value. Exit(Dynamic, Position), } impl Error for EvalAltResult {} impl fmt::Display for EvalAltResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{err}")?, Self::ErrorSystem(s, err) => write!(f, "{s}: {err}")?, Self::ErrorParsing(p, ..) => write!(f, "Syntax error: {p}")?, #[cfg(not(feature = "no_function"))] Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => { write!(f, "{err}\nin closure call")?; if !src.is_empty() { write!(f, " @ '{src}'")?; } } Self::ErrorInFunctionCall(s, src, err, ..) => { write!(f, "{err}\nin call to function '{s}'")?; if !src.is_empty() { write!(f, " @ '{src}'")?; } } Self::ErrorInModule(s, err, ..) if s.is_empty() => write!(f, "{err}\nin module")?, Self::ErrorInModule(s, err, ..) => write!(f, "{err}\nin module '{s}'")?, Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?, Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?, Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {s}")?, Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {s}")?, Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?, Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?, Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?, Self::ErrorDataRace(s, ..) if s.is_empty() => write!(f, "Data race detected")?, Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?, Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?, Self::ErrorDotExpr(s, ..) => f.write_str(s)?, Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?, Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?, Self::ErrorFor(..) => f.write_str("For loop expects iterable type")?, Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?, Self::ErrorTooManyVariables(..) => f.write_str("Too many variables defined")?, Self::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?, Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?, Self::ErrorTerminated(..) => f.write_str("Script terminated")?, Self::ErrorRuntime(d, ..) if d.is_unit() => f.write_str("Runtime error")?, Self::ErrorRuntime(d, ..) if d.as_immutable_string_ref().map_or(false, |v| v.is_empty()) => { write!(f, "Runtime error")? } Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {d}")?, #[cfg(not(feature = "no_object"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s.starts_with(crate::engine::FN_GET) => { let prop = &s[crate::engine::FN_GET.len()..]; write!(f, "Non-pure property {prop} cannot be accessed on constant")? } #[cfg(not(feature = "no_object"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s.starts_with(crate::engine::FN_SET) => { let prop = &s[crate::engine::FN_SET.len()..]; write!(f, "Cannot modify property '{prop}' of constant")? } #[cfg(not(feature = "no_index"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => { write!(f, "Non-pure indexer cannot be accessed on constant")? } #[cfg(not(feature = "no_index"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => { write!(f, "Cannot assign to indexer of constant")? } Self::ErrorNonPureMethodCallOnConstant(s, ..) => { write!(f, "Non-pure method '{s}' cannot be called on constant")? } Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?, Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) { ("", e) => write!(f, "Output type incorrect, expecting {e}"), (a, "") => write!(f, "Output type incorrect: {a}"), (a, e) => write!(f, "Output type incorrect: {a} (expecting {e})"), }?, Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) { ("", e) => write!(f, "Data type incorrect, expecting {e}"), (a, "") => write!(f, "Data type incorrect: {a}"), (a, e) => write!(f, "Data type incorrect: {a} (expecting {e})"), }?, Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?, Self::ErrorArithmetic(s, ..) => f.write_str(s)?, Self::LoopBreak(true, ..) => f.write_str("'break' must be within a loop")?, Self::LoopBreak(false, ..) => f.write_str("'continue' must be within a loop")?, Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?, Self::Exit(..) => f.write_str("NOT AN ERROR - exit value")?, Self::ErrorArrayBounds(max, index, ..) => match max { 0 => write!(f, "Array index {index} out of bounds: array is empty"), 1 => write!( f, "Array index {index} out of bounds: only 1 element in array", ), _ => write!( f, "Array index {index} out of bounds: only {max} elements in array", ), }?, Self::ErrorStringBounds(max, index, ..) => match max { 0 => write!(f, "String index {index} out of bounds: string is empty"), 1 => write!( f, "String index {index} out of bounds: only 1 character in string", ), _ => write!( f, "String index {index} out of bounds: only {max} characters in string", ), }?, Self::ErrorBitFieldBounds(max, index, ..) => write!( f, "Bit-field index {index} out of bounds: only {max} bits in bit-field", )?, Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} too large")?, Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?, } // Do not write any position if None if !self.position().is_none() { write!(f, " ({})", self.position())?; } Ok(()) } } impl> From for EvalAltResult { #[cold] #[inline(never)] fn from(err: T) -> Self { Self::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE) } } impl> From for Box { #[cold] #[inline(always)] fn from(err: T) -> Self { Into::::into(err).into() } } impl EvalAltResult { /// Is this a pseudo error? A pseudo error is one that does not occur naturally. /// /// [`LoopBreak`][EvalAltResult::LoopBreak], [`Return`][EvalAltResult::Return] and [`Exit`][EvalAltResult::Exit] are pseudo errors. #[cold] #[inline(never)] #[must_use] pub const fn is_pseudo_error(&self) -> bool { matches!( self, Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..) ) } /// Can this error be caught? #[cold] #[inline(never)] #[must_use] pub const fn is_catchable(&self) -> bool { match self { Self::ErrorSystem(..) => false, Self::ErrorParsing(..) => false, Self::ErrorFunctionNotFound(..) | Self::ErrorInFunctionCall(..) | Self::ErrorInModule(..) | Self::ErrorUnboundThis(..) | Self::ErrorMismatchDataType(..) | Self::ErrorArrayBounds(..) | Self::ErrorStringBounds(..) | Self::ErrorBitFieldBounds(..) | Self::ErrorIndexingType(..) | Self::ErrorFor(..) | Self::ErrorVariableExists(..) | Self::ErrorForbiddenVariable(..) | Self::ErrorVariableNotFound(..) | Self::ErrorPropertyNotFound(..) | Self::ErrorIndexNotFound(..) | Self::ErrorModuleNotFound(..) | Self::ErrorDataRace(..) | Self::ErrorNonPureMethodCallOnConstant(..) | Self::ErrorAssignmentToConstant(..) | Self::ErrorMismatchOutputType(..) | Self::ErrorDotExpr(..) | Self::ErrorArithmetic(..) | Self::ErrorRuntime(..) => true, // Custom syntax raises errors only when they are compiled by one // [`Engine`][crate::Engine] and run by another, causing a mismatch. // // Therefore, this error should not be catchable. Self::ErrorCustomSyntax(..) => false, Self::ErrorTooManyOperations(..) | Self::ErrorTooManyVariables(..) | Self::ErrorTooManyModules(..) | Self::ErrorStackOverflow(..) | Self::ErrorDataTooLarge(..) | Self::ErrorTerminated(..) => false, Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..) => false, } } /// Is this error a system exception? #[cold] #[inline(never)] #[must_use] pub const fn is_system_exception(&self) -> bool { matches!( self, Self::ErrorSystem(..) | Self::ErrorParsing(..) | Self::ErrorCustomSyntax(..) | Self::ErrorTooManyOperations(..) | Self::ErrorTooManyVariables(..) | Self::ErrorTooManyModules(..) | Self::ErrorStackOverflow(..) | Self::ErrorDataTooLarge(..) | Self::ErrorTerminated(..) ) } /// Get the [position][Position] of this error. #[cfg(not(feature = "no_object"))] #[cold] #[inline(never)] pub(crate) fn dump_fields(&self, map: &mut crate::Map) { map.insert( "error".into(), format!("{self:?}") .split('(') .next() .expect("`ErrorXXX(...)`") .into(), ); match self { Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..) => (), Self::ErrorSystem(..) | Self::ErrorParsing(..) | Self::ErrorUnboundThis(..) | Self::ErrorFor(..) | Self::ErrorArithmetic(..) | Self::ErrorTooManyOperations(..) | Self::ErrorTooManyVariables(..) | Self::ErrorTooManyModules(..) | Self::ErrorStackOverflow(..) | Self::ErrorRuntime(..) => (), Self::ErrorFunctionNotFound(f, ..) | Self::ErrorNonPureMethodCallOnConstant(f, ..) => { map.insert("function".into(), f.into()); } Self::ErrorInFunctionCall(f, s, ..) => { map.insert("function".into(), f.into()); map.insert("source".into(), s.into()); } Self::ErrorMismatchDataType(r, a, ..) | Self::ErrorMismatchOutputType(r, a, ..) => { map.insert("requested".into(), r.into()); map.insert("actual".into(), a.into()); } Self::ErrorArrayBounds(n, i, ..) | Self::ErrorStringBounds(n, i, ..) | Self::ErrorBitFieldBounds(n, i, ..) => { map.insert("length".into(), (*n as INT).into()); map.insert("index".into(), (*i as INT).into()); } Self::ErrorVariableExists(v, ..) | Self::ErrorForbiddenVariable(v, ..) | Self::ErrorVariableNotFound(v, ..) | Self::ErrorPropertyNotFound(v, ..) | Self::ErrorDataRace(v, ..) | Self::ErrorAssignmentToConstant(v, ..) => { map.insert("variable".into(), v.into()); } Self::ErrorIndexNotFound(v, ..) => { map.insert("index".into(), v.clone()); } Self::ErrorInModule(m, ..) | Self::ErrorModuleNotFound(m, ..) => { map.insert("module".into(), m.into()); } Self::ErrorDotExpr(p, ..) => { map.insert("property".into(), p.into()); } Self::ErrorIndexingType(t, ..) | Self::ErrorDataTooLarge(t, ..) => { map.insert("type".into(), t.into()); } Self::ErrorTerminated(t, ..) => { map.insert("token".into(), t.clone()); } Self::ErrorCustomSyntax(_, tokens, _) => { map.insert( "tokens".into(), #[cfg(not(feature = "no_index"))] Dynamic::from_array(tokens.iter().map(Into::into).collect()), #[cfg(feature = "no_index")] tokens .iter() .map(String::as_str) .collect::>() .join(" ") .into(), ); } }; } /// Unwrap this error and get the very base error. #[cold] #[inline(never)] pub fn unwrap_inner(&self) -> &Self { match self { Self::ErrorInFunctionCall(.., err, _) | Self::ErrorInModule(.., err, _) => { err.unwrap_inner() } _ => self, } } /// Get the [position][Position] of this error. #[cold] #[inline(never)] #[must_use] pub const fn position(&self) -> Position { match self { Self::ErrorSystem(..) => Position::NONE, Self::ErrorParsing(.., pos) | Self::ErrorFunctionNotFound(.., pos) | Self::ErrorInFunctionCall(.., pos) | Self::ErrorInModule(.., pos) | Self::ErrorUnboundThis(pos) | Self::ErrorMismatchDataType(.., pos) | Self::ErrorArrayBounds(.., pos) | Self::ErrorStringBounds(.., pos) | Self::ErrorBitFieldBounds(.., pos) | Self::ErrorIndexingType(.., pos) | Self::ErrorFor(pos) | Self::ErrorVariableExists(.., pos) | Self::ErrorForbiddenVariable(.., pos) | Self::ErrorVariableNotFound(.., pos) | Self::ErrorPropertyNotFound(.., pos) | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos) | Self::ErrorArithmetic(.., pos) | Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyVariables(pos) | Self::ErrorTooManyModules(pos) | Self::ErrorStackOverflow(pos) | Self::ErrorDataTooLarge(.., pos) | Self::ErrorTerminated(.., pos) | Self::ErrorCustomSyntax(.., pos) | Self::ErrorRuntime(.., pos) | Self::LoopBreak(.., pos) | Self::Return(.., pos) | Self::Exit(.., pos) => *pos, } } /// Remove the [position][Position] information from this error. /// /// The [position][Position] of this error is set to [`NONE`][Position::NONE] afterwards. #[cold] #[inline(never)] pub fn clear_position(&mut self) -> &mut Self { self.set_position(Position::NONE) } /// Remove the [position][Position] information from this error and return it. /// /// The [position][Position] of this error is set to [`NONE`][Position::NONE] afterwards. #[cold] #[inline(never)] #[must_use] pub fn take_position(&mut self) -> Position { let pos = self.position(); self.set_position(Position::NONE); pos } /// Override the [position][Position] of this error. #[cold] #[inline(never)] pub fn set_position(&mut self, new_position: Position) -> &mut Self { match self { Self::ErrorSystem(..) => (), Self::ErrorParsing(.., pos) | Self::ErrorFunctionNotFound(.., pos) | Self::ErrorInFunctionCall(.., pos) | Self::ErrorInModule(.., pos) | Self::ErrorUnboundThis(pos) | Self::ErrorMismatchDataType(.., pos) | Self::ErrorArrayBounds(.., pos) | Self::ErrorStringBounds(.., pos) | Self::ErrorBitFieldBounds(.., pos) | Self::ErrorIndexingType(.., pos) | Self::ErrorFor(pos) | Self::ErrorVariableExists(.., pos) | Self::ErrorForbiddenVariable(.., pos) | Self::ErrorVariableNotFound(.., pos) | Self::ErrorPropertyNotFound(.., pos) | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) | Self::ErrorNonPureMethodCallOnConstant(.., pos) | Self::ErrorAssignmentToConstant(.., pos) | Self::ErrorMismatchOutputType(.., pos) | Self::ErrorDotExpr(.., pos) | Self::ErrorArithmetic(.., pos) | Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyVariables(pos) | Self::ErrorTooManyModules(pos) | Self::ErrorStackOverflow(pos) | Self::ErrorDataTooLarge(.., pos) | Self::ErrorTerminated(.., pos) | Self::ErrorCustomSyntax(.., pos) | Self::ErrorRuntime(.., pos) | Self::LoopBreak(.., pos) | Self::Return(.., pos) | Self::Exit(.., pos) => *pos = new_position, } self } /// Consume the current [`EvalAltResult`] and return a new one with the specified [`Position`] /// if the current position is [`Position::NONE`]. #[cold] #[inline(never)] #[must_use] pub(crate) fn fill_position(mut self: Box, new_position: Position) -> Box { if self.position().is_none() { self.set_position(new_position); } self } } rhai-1.21.0/src/types/float.rs000064400000000000000000000057121046102023000142430ustar 00000000000000#![cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fmt, hash::{Hash, Hasher}, ops::{Deref, DerefMut}, str::FromStr, }; use num_traits::float::FloatCore as Float; /// _(internals)_ A type that wraps a floating-point number and implements [`Hash`]. /// Exported under the `internals` feature only. /// /// Not available under `no_float`. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] #[must_use] pub struct FloatWrapper(F); impl Hash for FloatWrapper { #[inline] fn hash(&self, state: &mut H) { self.0.to_ne_bytes().hash(state); } } impl AsRef for FloatWrapper { #[inline(always)] #[must_use] fn as_ref(&self) -> &F { &self.0 } } impl AsMut for FloatWrapper { #[inline(always)] #[must_use] fn as_mut(&mut self) -> &mut F { &mut self.0 } } impl Deref for FloatWrapper { type Target = F; #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for FloatWrapper { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl fmt::Debug for FloatWrapper { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } impl> fmt::Display for FloatWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let abs = self.0.abs(); if abs.is_zero() { f.write_str("0.0") } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() { write!(f, "{:e}", self.0) } else { fmt::Display::fmt(&self.0, f)?; if abs.fract().is_zero() { f.write_str(".0")?; } Ok(()) } } } impl From for FloatWrapper { #[inline(always)] fn from(value: F) -> Self { Self::new(value) } } impl FromStr for FloatWrapper { type Err = ::Err; #[inline] fn from_str(s: &str) -> Result { F::from_str(s).map(Into::into) } } impl FloatWrapper { /// Maximum floating-point number for natural display before switching to scientific notation. pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10_000_000_000_000.0; /// Minimum floating-point number for natural display before switching to scientific notation. pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.000_000_000_000_1; /// Create a new [`FloatWrapper`]. #[inline(always)] pub const fn new(value: F) -> Self { Self(value) } } rhai-1.21.0/src/types/fn_ptr.rs000064400000000000000000000512621046102023000144270ustar 00000000000000//! The `FnPtr` type. use crate::func::FnCallArgs; use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_function_name, Token}; use crate::types::dynamic::Variant; use crate::{ expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult, RhaiResultOf, Shared, StaticVec, ThinVec, AST, ERR, PERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::type_name, convert::{TryFrom, TryInto}, fmt, mem, ops::{Index, IndexMut}, }; /// Function pointer type. #[derive(Clone, Default)] pub enum FnPtrType { /// Normal function pointer. #[default] Normal, /// Linked to a script-defined function. #[cfg(not(feature = "no_function"))] Script(Shared), /// Embedded native Rust function. #[cfg(not(feature = "sync"))] Native(Shared RhaiResult + 'static>), #[cfg(feature = "sync")] Native( Shared RhaiResult + Send + Sync + 'static>, ), } impl fmt::Display for FnPtrType { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Normal => f.write_str("Fn"), #[cfg(not(feature = "no_function"))] Self::Script(..) => f.write_str("Fn*"), Self::Native(..) => f.write_str("Fn"), } } } /// A general function pointer, which may carry additional (i.e. curried) argument values /// to be passed onto a function during a call. #[derive(Clone)] pub struct FnPtr { /// Name of the function. pub(crate) name: ImmutableString, /// Curried arguments. pub(crate) curry: ThinVec, /// Encapsulated environment. #[cfg(not(feature = "no_function"))] pub(crate) env: Option>, /// Type of function pointer. pub(crate) typ: FnPtrType, } impl fmt::Display for FnPtr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Fn({})", self.fn_name()) } } impl fmt::Debug for FnPtr { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let ff = &mut f.debug_tuple(&self.typ.to_string()); ff.field(&self.name); self.curry.iter().for_each(|curry| { ff.field(curry); }); ff.finish()?; Ok(()) } } impl FnPtr { /// Create a new function pointer. /// /// # Errors /// /// Returns an error if the function name is not a valid identifier or is a reserved keyword. #[inline(always)] pub fn new(name: impl Into) -> RhaiResultOf { name.into().try_into() } /// Create a new function pointer from a native Rust function. /// /// # Errors /// /// Returns an error if the function name is not a valid identifier or is a reserved keyword. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(context: NativeCallContext, &mut [&mut Dynamic]) -> Result>` #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] pub fn from_fn( name: impl Into, #[cfg(not(feature = "sync"))] func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + 'static, #[cfg(feature = "sync")] func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync + 'static, ) -> RhaiResultOf { #[allow(deprecated)] Self::from_dyn_fn(name, Box::new(func)) } /// Create a new function pointer from a native Rust function. /// /// # Errors /// /// Returns an error if the function name is not a valid identifier or is a reserved keyword. /// /// # WARNING - Unstable API /// /// This API is volatile and may change in the future. /// /// # Callback Function Signature /// /// `Fn(context: NativeCallContext, &mut [&mut Dynamic]) -> Result>` #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline] pub fn from_dyn_fn( name: impl Into, #[cfg(not(feature = "sync"))] func: Box< dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + 'static, >, #[cfg(feature = "sync")] func: Box< dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync + 'static, >, ) -> RhaiResultOf { let mut fp = Self::new(name)?; fp.typ = FnPtrType::Native(Shared::new(func)); Ok(fp) } /// Get the name of the function. #[inline(always)] #[must_use] pub fn fn_name(&self) -> &str { self.fn_name_raw() } /// Get the name of the function. #[inline(always)] #[must_use] pub(crate) const fn fn_name_raw(&self) -> &ImmutableString { &self.name } /// Get the curried arguments. #[inline(always)] pub fn curry(&self) -> &[Dynamic] { self.curry.as_ref() } /// Iterate the curried arguments. #[inline(always)] pub fn iter_curry(&self) -> impl Iterator { self.curry.iter() } /// Mutably-iterate the curried arguments. #[inline(always)] pub fn iter_curry_mut(&mut self) -> impl Iterator { self.curry.iter_mut() } /// Add a new curried argument. #[inline(always)] pub fn add_curry(&mut self, value: Dynamic) -> &mut Self { self.curry.push(value); self } /// Set curried arguments to the function pointer. #[inline] pub fn set_curry(&mut self, values: impl IntoIterator) -> &mut Self { self.curry = values.into_iter().collect(); self } /// Is the function pointer curried? #[inline(always)] #[must_use] pub fn is_curried(&self) -> bool { !self.curry.is_empty() } /// Does the function pointer refer to an anonymous function? /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] pub fn is_anonymous(&self) -> bool { crate::func::is_anonymous_fn(&self.name) } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. /// /// This method is intended for calling a function pointer directly, possibly on another [`Engine`]. /// Therefore, the [`AST`] is _NOT_ evaluated before calling the function. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::{Engine, FnPtr}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile("fn foo(x, y) { len(x) + y }")?; /// /// let mut fn_ptr = FnPtr::new("foo")?; /// /// // Curry values into the function pointer /// fn_ptr.set_curry(vec!["abc".into()]); /// /// // Values are only needed for non-curried parameters /// let result: i64 = fn_ptr.call(&engine, &ast, ( 39_i64, ) )?; /// /// assert_eq!(result, 42); /// # } /// # Ok(()) /// # } /// ``` #[inline] pub fn call( &self, engine: &Engine, ast: &AST, args: impl FuncArgs, ) -> RhaiResultOf { let _ast = ast; let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let global = &mut engine.new_global_runtime_state(); #[cfg(not(feature = "no_function"))] global.lib.push(_ast.shared_lib().clone()); let ctx = (engine, self.fn_name(), None, &*global, Position::NONE).into(); self.call_raw(&ctx, None, arg_values).and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = engine.map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => engine.map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE) .into() }) }) } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. /// /// This method is intended for calling a function pointer that is passed into a native Rust /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the /// function. #[inline] pub fn call_within_context( &self, context: &NativeCallContext, args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); self.call_raw(context, None, arg_values).and_then(|result| { result.try_cast_result().map_err(|r| { let result_type = context.engine().map_type_name(r.type_name()); let cast_type = match type_name::() { typ if typ.contains("::") => context.engine().map_type_name(typ), typ => typ, }; ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE) .into() }) }) } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. /// /// This method is intended for calling a function pointer that is passed into a native Rust /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the /// function. /// /// # WARNING - Low Level API /// /// This function is very low level. /// /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. /// /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. #[inline] pub fn call_raw( &self, context: &NativeCallContext, this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { let mut arg_values = arg_values; let mut arg_values = arg_values.as_mut(); let mut args_data; if self.is_curried() { args_data = FnArgsVec::with_capacity(self.curry().len() + arg_values.len()); args_data.extend(self.curry().iter().cloned()); args_data.extend(arg_values.iter_mut().map(mem::take)); arg_values = &mut *args_data; }; let args = &mut StaticVec::with_capacity(arg_values.len() + 1); args.extend(arg_values.iter_mut()); match self.typ { // Linked to scripted function? #[cfg(not(feature = "no_function"))] FnPtrType::Script(ref fn_def) if fn_def.params.len() == args.len() => { let global = &mut context.global_runtime_state().clone(); global.level += 1; let caches = &mut crate::eval::Caches::new(); return context.engine().call_script_fn( global, caches, &mut crate::Scope::new(), this_ptr, #[cfg(not(feature = "no_function"))] self.env.as_deref(), #[cfg(feature = "no_function")] None, fn_def, args, true, context.position(), ); } _ => (), } let is_method = this_ptr.is_some(); if let Some(obj) = this_ptr { args.insert(0, obj); } context.call_fn_raw(self.fn_name(), is_method, is_method, args) } /// _(internals)_ Make a call to a function pointer with either a specified number of arguments, /// or with extra arguments attached. /// Exported under the `internals` feature only. /// /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`. /// /// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr` /// is removed and inserted as the appropriate parameter number. /// /// This is useful for calling predicate closures within an iteration loop where the extra /// argument is the current element's index. /// /// If the function pointer is linked to a scripted function definition, use the appropriate /// number of arguments to call it directly (one version attaches extra arguments). #[expose_under_internals] #[inline(always)] fn call_raw_with_extra_args( &self, fn_name: &str, ctx: &NativeCallContext, this_ptr: Option<&mut Dynamic>, args: [Dynamic; N], extras: [Dynamic; E], move_this_ptr_to_args: Option, ) -> RhaiResult { match move_this_ptr_to_args { Some(m) => { self._call_with_extra_args::(fn_name, ctx, this_ptr, args, extras, m) } None => { self._call_with_extra_args::(fn_name, ctx, this_ptr, args, extras, 0) } } } /// Make a call to a function pointer with either a specified number of arguments, or with extra /// arguments attached. fn _call_with_extra_args( &self, fn_name: &str, ctx: &NativeCallContext, mut this_ptr: Option<&mut Dynamic>, args: [Dynamic; N], extras: [Dynamic; E], move_this_ptr_to_args: usize, ) -> RhaiResult { match self.typ { #[cfg(not(feature = "no_function"))] FnPtrType::Script(ref fn_def) => { let arity = fn_def.params.len(); if arity == N + self.curry().len() { return self.call_raw(ctx, this_ptr, args); } if MOVE_PTR && this_ptr.is_some() { if arity == N + 1 + self.curry().len() { let mut args2 = FnArgsVec::with_capacity(args.len() + 1); if move_this_ptr_to_args == 0 { args2.push(this_ptr.as_mut().unwrap().clone()); args2.extend(args); } else { args2.extend(args); args2.insert(move_this_ptr_to_args, this_ptr.as_mut().unwrap().clone()); } return self.call_raw(ctx, None, args2); } if arity == N + E + 1 + self.curry().len() { let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1); if move_this_ptr_to_args == 0 { args2.push(this_ptr.as_mut().unwrap().clone()); args2.extend(args); } else { args2.extend(args); args2.insert(move_this_ptr_to_args, this_ptr.as_mut().unwrap().clone()); } args2.extend(extras); return self.call_raw(ctx, None, args2); } } if arity == N + E + self.curry().len() { let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len()); args2.extend(args); args2.extend(extras); return self.call_raw(ctx, this_ptr, args2); } } _ => (), } self.call_raw(ctx, this_ptr.as_deref_mut(), args.clone()) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(sig, ..) if MOVE_PTR && this_ptr.is_some() && sig.starts_with(self.fn_name()) => { let mut args2 = FnArgsVec::with_capacity(args.len() + 1); if move_this_ptr_to_args == 0 { args2.push(this_ptr.as_mut().unwrap().clone()); args2.extend(args.clone()); } else { args2.extend(args.clone()); args2.insert(move_this_ptr_to_args, this_ptr.as_mut().unwrap().clone()); } self.call_raw(ctx, None, args2) } _ => Err(err), }) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => { if MOVE_PTR { if let Some(ref mut this_ptr) = this_ptr { let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1); if move_this_ptr_to_args == 0 { args2.push(this_ptr.clone()); args2.extend(args); args2.extend(extras); } else { args2.extend(args); args2.extend(extras); args2.insert(move_this_ptr_to_args, this_ptr.clone()); } return self.call_raw(ctx, None, args2); } } let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len()); args2.extend(args); args2.extend(extras); self.call_raw(ctx, this_ptr, args2) } _ => Err(err), }) .map_err(|err| { Box::new(ERR::ErrorInFunctionCall( fn_name.to_string(), ctx.source().unwrap_or("").to_string(), err, Position::NONE, )) }) } } impl TryFrom for FnPtr { type Error = RhaiError; #[inline(always)] fn try_from(value: ImmutableString) -> RhaiResultOf { if is_valid_function_name(&value) { Ok(Self { name: value, curry: ThinVec::new(), #[cfg(not(feature = "no_function"))] env: None, typ: FnPtrType::Normal, }) } else if is_reserved_keyword_or_symbol(&value).0 || Token::lookup_symbol_from_syntax(&value).is_some() { Err(ERR::ErrorParsing(PERR::Reserved(value.to_string()), Position::NONE).into()) } else { Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) } } } #[cfg(not(feature = "no_function"))] impl>> From for FnPtr { #[inline(always)] fn from(value: T) -> Self { let fn_def = value.into(); Self { name: fn_def.name.clone(), curry: ThinVec::new(), #[cfg(not(feature = "no_function"))] env: None, typ: FnPtrType::Script(fn_def), } } } impl Index for FnPtr { type Output = Dynamic; #[inline(always)] fn index(&self, index: usize) -> &Self::Output { self.curry.index(index) } } impl IndexMut for FnPtr { #[inline(always)] fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.curry.index_mut(index) } } impl Extend for FnPtr { #[inline(always)] fn extend>(&mut self, iter: T) { self.curry.extend(iter); } } rhai-1.21.0/src/types/immutable_string.rs000064400000000000000000000464161046102023000165110ustar 00000000000000//! The `ImmutableString` type. use crate::func::{shared_get_mut, shared_make_mut, shared_take}; use crate::{Shared, SmartString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ borrow::Borrow, cmp::Ordering, fmt, hash::Hash, iter::FromIterator, ops::{Add, AddAssign, Deref, Sub, SubAssign}, str::FromStr, }; /// The system immutable string type. /// /// An [`ImmutableString`] wraps an `Rc` (or `Arc` under the `sync` feature) /// so that it can be simply shared and not cloned. /// /// # Example /// /// ``` /// use rhai::ImmutableString; /// /// let s1: ImmutableString = "hello".into(); /// /// // No actual cloning of the string is involved below. /// let s2 = s1.clone(); /// let s3 = s2.clone(); /// /// assert_eq!(s1, s2); /// /// // Clones the underlying string (because it is already shared) and extracts it. /// let mut s: String = s1.into_owned(); /// /// // Changing the clone has no impact on the previously shared version. /// s += ", world!"; /// /// // The old version still exists. /// assert_eq!(s2, s3); /// assert_eq!(s2.as_str(), "hello"); /// /// // Not equals! /// assert_ne!(s2.as_str(), s.as_str()); /// assert_eq!(s, "hello, world!"); /// ``` #[derive(Clone, Eq, Ord, Hash, Default)] pub struct ImmutableString(Shared); impl Deref for ImmutableString { type Target = str; #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for ImmutableString { #[inline(always)] #[must_use] fn as_ref(&self) -> &str { &self.0 } } impl AsRef for ImmutableString { #[inline(always)] #[must_use] fn as_ref(&self) -> &SmartString { &self.0 } } impl Borrow for ImmutableString { #[inline(always)] #[must_use] fn borrow(&self) -> &str { &self.0 } } impl Borrow for ImmutableString { #[inline(always)] #[must_use] fn borrow(&self) -> &SmartString { &self.0 } } impl From<&Self> for ImmutableString { #[inline(always)] fn from(value: &Self) -> Self { Self(value.0.clone()) } } impl From<&str> for ImmutableString { #[inline(always)] fn from(value: &str) -> Self { let value: SmartString = value.into(); Self(value.into()) } } impl From> for ImmutableString { #[inline(always)] fn from(value: Box) -> Self { let value: SmartString = value.into(); Self(value.into()) } } impl From<&String> for ImmutableString { #[inline(always)] fn from(value: &String) -> Self { let value: SmartString = value.into(); Self(value.into()) } } impl From for ImmutableString { #[inline(always)] fn from(value: String) -> Self { let value: SmartString = value.into(); Self(value.into()) } } impl From<&SmartString> for ImmutableString { #[inline(always)] fn from(value: &SmartString) -> Self { Self(value.clone().into()) } } impl From for ImmutableString { #[inline(always)] fn from(value: SmartString) -> Self { Self(value.into()) } } impl From<&ImmutableString> for SmartString { #[inline(always)] fn from(value: &ImmutableString) -> Self { value.0.as_ref().clone() } } impl From for SmartString { #[inline(always)] fn from(mut value: ImmutableString) -> Self { let _ = value.make_mut(); // Make sure it is unique reference shared_take(value.0) // Should succeed } } impl From<&ImmutableString> for String { #[inline(always)] fn from(value: &ImmutableString) -> Self { value.0.as_ref().to_string() } } impl From for String { #[inline(always)] fn from(value: ImmutableString) -> Self { value.into_owned() } } impl From<&ImmutableString> for Box { #[inline(always)] fn from(value: &ImmutableString) -> Self { value.0.as_str().into() } } impl From for Box { #[inline(always)] fn from(value: ImmutableString) -> Self { value.0.as_str().into() } } impl FromStr for ImmutableString { type Err = (); #[inline(always)] fn from_str(s: &str) -> Result { let s: SmartString = s.into(); Ok(Self(s.into())) } } impl FromIterator for ImmutableString { #[inline] #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator<&'a char> for ImmutableString { #[inline] #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().copied().collect::().into()) } } impl<'a> FromIterator<&'a str> for ImmutableString { #[inline] #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl FromIterator for ImmutableString { #[inline] #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl FromIterator for ImmutableString { #[inline] #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl fmt::Display for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl fmt::Debug for ImmutableString { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } impl Add for ImmutableString { type Output = Self; #[inline] fn add(mut self, rhs: Self) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs } else { self.make_mut().push_str(rhs.as_str()); self } } } impl Add for &ImmutableString { type Output = ImmutableString; #[inline] fn add(self, rhs: Self) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs.clone() } else { let mut s = self.clone(); s.make_mut().push_str(rhs.as_str()); s } } } impl Add<&Self> for ImmutableString { type Output = Self; #[inline] fn add(mut self, rhs: &Self) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs.clone() } else { self.make_mut().push_str(rhs.as_str()); self } } } impl Add for &ImmutableString { type Output = ImmutableString; #[inline] fn add(self, rhs: ImmutableString) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs } else { let mut s = self.clone(); s.make_mut().push_str(rhs.as_str()); s } } } impl AddAssign for ImmutableString { #[inline] fn add_assign(&mut self, rhs: Self) { if !rhs.is_empty() { if self.is_empty() { self.0 = rhs.0; } else { self.make_mut().push_str(&rhs); } } } } impl AddAssign<&Self> for ImmutableString { #[inline] fn add_assign(&mut self, rhs: &Self) { if !rhs.is_empty() { if self.is_empty() { self.0 = rhs.0.clone(); } else { self.make_mut().push_str(rhs.as_str()); } } } } impl Add<&str> for ImmutableString { type Output = Self; #[inline] fn add(mut self, rhs: &str) -> Self::Output { if !rhs.is_empty() { self.make_mut().push_str(rhs); } self } } impl Add<&str> for &ImmutableString { type Output = ImmutableString; #[inline] fn add(self, rhs: &str) -> Self::Output { if rhs.is_empty() { self.clone() } else { let mut s = self.clone(); s.make_mut().push_str(rhs); s } } } impl AddAssign<&str> for ImmutableString { #[inline] fn add_assign(&mut self, rhs: &str) { if !rhs.is_empty() { self.make_mut().push_str(rhs); } } } impl Add for ImmutableString { type Output = Self; #[inline] fn add(mut self, rhs: String) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs.into() } else { self.make_mut().push_str(&rhs); self } } } impl Add for &ImmutableString { type Output = ImmutableString; #[inline] fn add(self, rhs: String) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs.into() } else { let mut s = self.clone(); s.make_mut().push_str(&rhs); s } } } impl AddAssign for ImmutableString { #[inline] fn add_assign(&mut self, rhs: String) { if !rhs.is_empty() { if self.is_empty() { let rhs: SmartString = rhs.into(); self.0 = rhs.into(); } else { self.make_mut().push_str(&rhs); } } } } impl Add for ImmutableString { type Output = Self; #[inline] fn add(mut self, rhs: char) -> Self::Output { self.make_mut().push(rhs); self } } impl Add for &ImmutableString { type Output = ImmutableString; #[inline] fn add(self, rhs: char) -> Self::Output { let mut s = self.clone(); s.make_mut().push(rhs); s } } impl AddAssign for ImmutableString { #[inline] fn add_assign(&mut self, rhs: char) { self.make_mut().push(rhs); } } impl Sub for ImmutableString { type Output = Self; #[inline] fn sub(self, rhs: Self) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs } else { self.replace(rhs.as_str(), "").into() } } } impl Sub for &ImmutableString { type Output = ImmutableString; #[inline] fn sub(self, rhs: Self) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs.clone() } else { self.replace(rhs.as_str(), "").into() } } } impl SubAssign for ImmutableString { #[inline] fn sub_assign(&mut self, rhs: Self) { if !rhs.is_empty() { if self.is_empty() { self.0 = rhs.0; } else { let rhs: SmartString = self.replace(rhs.as_str(), "").into(); self.0 = rhs.into(); } } } } impl SubAssign<&Self> for ImmutableString { #[inline] fn sub_assign(&mut self, rhs: &Self) { if !rhs.is_empty() { if self.is_empty() { self.0 = rhs.0.clone(); } else { let rhs: SmartString = self.replace(rhs.as_str(), "").into(); self.0 = rhs.into(); } } } } impl Sub for ImmutableString { type Output = Self; #[inline] fn sub(self, rhs: String) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs.into() } else { self.replace(&rhs, "").into() } } } impl Sub for &ImmutableString { type Output = ImmutableString; #[inline] fn sub(self, rhs: String) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs.into() } else { self.replace(&rhs, "").into() } } } impl SubAssign for ImmutableString { #[inline] fn sub_assign(&mut self, rhs: String) { if !rhs.is_empty() { let rhs: SmartString = self.replace(&rhs, "").into(); self.0 = rhs.into(); } } } impl Sub<&str> for ImmutableString { type Output = Self; #[inline] fn sub(self, rhs: &str) -> Self::Output { if rhs.is_empty() { self } else if self.is_empty() { rhs.into() } else { self.replace(rhs, "").into() } } } impl Sub<&str> for &ImmutableString { type Output = ImmutableString; #[inline] fn sub(self, rhs: &str) -> Self::Output { if rhs.is_empty() { self.clone() } else if self.is_empty() { rhs.into() } else { self.replace(rhs, "").into() } } } impl SubAssign<&str> for ImmutableString { #[inline] fn sub_assign(&mut self, rhs: &str) { if !rhs.is_empty() { let rhs: SmartString = self.replace(rhs, "").into(); self.0 = rhs.into(); } } } impl Sub for ImmutableString { type Output = Self; #[inline(always)] fn sub(self, rhs: char) -> Self::Output { self.replace(rhs, "").into() } } impl Sub for &ImmutableString { type Output = ImmutableString; #[inline(always)] fn sub(self, rhs: char) -> Self::Output { self.replace(rhs, "").into() } } impl SubAssign for ImmutableString { #[inline] fn sub_assign(&mut self, rhs: char) { let rhs: SmartString = self.replace(rhs, "").into(); self.0 = rhs.into(); } } impl + ?Sized> PartialEq for ImmutableString { #[inline(always)] fn eq(&self, other: &S) -> bool { self.as_str().eq(other.as_ref()) } } impl PartialEq for &ImmutableString { #[inline(always)] fn eq(&self, other: &str) -> bool { self.as_str().eq(other) } } impl PartialEq for &ImmutableString { #[inline(always)] fn eq(&self, other: &String) -> bool { self.as_str().eq(other.as_str()) } } impl PartialEq for str { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { self.eq(other.as_str()) } } impl PartialEq for &str { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { (*self).eq(other.as_str()) } } impl PartialEq for String { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { self.as_str().eq(other.as_str()) } } impl PartialEq<&ImmutableString> for String { #[inline(always)] fn eq(&self, other: &&ImmutableString) -> bool { self.as_str().eq(other.as_str()) } } impl PartialEq for &String { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { self.as_str().eq(other.as_str()) } } impl + ?Sized> PartialOrd for ImmutableString { fn partial_cmp(&self, other: &S) -> Option { self.as_str().partial_cmp(other.as_ref()) } } impl PartialOrd for &ImmutableString { fn partial_cmp(&self, other: &str) -> Option { self.as_str().partial_cmp(other) } } impl PartialOrd for &ImmutableString { fn partial_cmp(&self, other: &String) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl PartialOrd for str { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { self.partial_cmp(other.as_str()) } } impl PartialOrd for &str { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { (*self).partial_cmp(other.as_str()) } } impl PartialOrd for String { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl PartialOrd<&ImmutableString> for String { #[inline(always)] fn partial_cmp(&self, other: &&ImmutableString) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl PartialOrd for &String { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl ImmutableString { /// Create a new [`ImmutableString`]. #[inline(always)] #[must_use] pub fn new() -> Self { Self(SmartString::new_const().into()) } /// Get the string slice. #[inline(always)] #[must_use] pub fn as_str(&self) -> &str { self.0.as_str() } /// Strong count of references to the underlying string. #[inline(always)] #[must_use] pub fn strong_count(&self) -> usize { Shared::strong_count(&self.0) } /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// /// If there are other references to the same string, a cloned copy is returned. #[inline] #[must_use] pub fn into_owned(mut self) -> String { let _ = self.make_mut(); // Make sure it is unique reference shared_take(self.0).into() // Should succeed } /// Make sure that the [`ImmutableString`] is unique (i.e. no other outstanding references). /// Then return a mutable reference to the [`SmartString`]. /// /// If there are other references to the same string, a cloned copy is used. #[inline(always)] #[must_use] pub fn make_mut(&mut self) -> &mut SmartString { shared_make_mut(&mut self.0) } /// Return a mutable reference to the [` SmartString`] wrapped by the [`ImmutableString`] /// if there are no other outstanding references to it. #[inline(always)] pub fn get_mut(&mut self) -> Option<&mut SmartString> { shared_get_mut(&mut self.0) } /// Returns `true` if the two [`ImmutableString`]'s point to the same allocation. /// /// # Example /// /// ``` /// use rhai::ImmutableString; /// /// let s1: ImmutableString = "hello".into(); /// let s2 = s1.clone(); /// let s3: ImmutableString = "hello".into(); /// /// assert_eq!(s1, s2); /// assert_eq!(s1, s3); /// assert_eq!(s2, s3); /// /// assert!(s1.ptr_eq(&s2)); /// assert!(!s1.ptr_eq(&s3)); /// assert!(!s2.ptr_eq(&s3)); /// ``` #[inline(always)] #[must_use] pub fn ptr_eq(&self, other: &Self) -> bool { Shared::ptr_eq(&self.0, &other.0) } } rhai-1.21.0/src/types/interner.rs000064400000000000000000000126371046102023000147700ustar 00000000000000//! A strings interner type. use super::BloomFilterU64; use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::ImmutableString; #[cfg(feature = "no_std")] use hashbrown::hash_map::Entry; #[cfg(not(feature = "no_std"))] use std::collections::hash_map::Entry; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fmt, hash::{Hash, Hasher}, ops::AddAssign, }; /// Maximum length of strings interned. pub const MAX_STRING_LEN: usize = 32; /// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. #[derive(Clone)] pub struct StringsInterner { /// Maximum number of strings to be interned. max_strings_interned: usize, /// Cached strings. cache: StraightHashMap, /// Bloom filter to avoid caching "one-hit wonders". bloom_filter: BloomFilterU64, } impl fmt::Debug for StringsInterner { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self.cache.values()).finish() } } impl StringsInterner { /// Create a new [`StringsInterner`]. #[inline(always)] #[must_use] pub fn new(max_strings_interned: usize) -> Self { Self { max_strings_interned, cache: <_>::default(), bloom_filter: BloomFilterU64::new(), } } /// Get an identifier from a text string, adding it to the interner if necessary. #[inline(always)] #[must_use] pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { self.get_with_mapper(0, Into::into, text) } /// Set the maximum number of strings to be interned. #[inline(always)] pub fn set_max(&mut self, max: usize) { self.max_strings_interned = max; self.throttle_cache(None); } /// The maximum number of strings to be interned. #[inline(always)] #[must_use] pub const fn max(&self) -> usize { self.max_strings_interned } /// Get an identifier from a text string, adding it to the interner if necessary. #[inline] #[must_use] pub fn get_with_mapper>( &mut self, category: u8, mapper: impl FnOnce(S) -> ImmutableString, text: S, ) -> ImmutableString { let key = text.as_ref(); if self.max() == 0 { return mapper(text); } let hasher = &mut get_hasher(); hasher.write_u8(category); key.hash(hasher); let hash = hasher.finish(); // Do not cache long strings and avoid caching "one-hit wonders". if key.len() > MAX_STRING_LEN || self.bloom_filter.is_absent_and_set(hash) { return mapper(text); } if self.cache.is_empty() { // Reserve a good size to kick start the strings interner self.cache.reserve(128); } let result = match self.cache.entry(hash) { Entry::Occupied(e) => return e.get().clone(), Entry::Vacant(e) => e.insert(mapper(text)).clone(), }; // Throttle the cache upon exit self.throttle_cache(Some(hash)); result } /// If the interner is over capacity, remove the longest entry that has the lowest count #[inline] fn throttle_cache(&mut self, skip_hash: Option) { if self.max() == 0 { self.clear(); return; } if self.cache.len() <= self.max() { return; } // Leave some buffer to grow when shrinking the cache. // We leave at least two entries, one for the empty string, and one for the string // that has just been inserted. while self.cache.len() > self.max() - 3 { let mut max_len = 0; let mut min_count = usize::MAX; let mut index = 0; for (&k, v) in &self.cache { if skip_hash.map_or(true, |hash| k != hash) && (v.strong_count() < min_count || (v.strong_count() == min_count && v.len() > max_len)) { max_len = v.len(); min_count = v.strong_count(); index = k; } } self.cache.remove(&index); } } /// Number of strings interned. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn len(&self) -> usize { self.cache.len() } /// Returns `true` if there are no interned strings. #[inline(always)] #[must_use] #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.cache.is_empty() } /// Clear all interned strings. #[inline(always)] #[allow(dead_code)] pub fn clear(&mut self) { self.cache.clear(); self.bloom_filter.clear(); } } impl AddAssign for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.cache.extend(rhs.cache); self.bloom_filter += rhs.bloom_filter; } } impl AddAssign<&Self> for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { self.cache .extend(rhs.cache.iter().map(|(&k, v)| (k, v.clone()))); self.bloom_filter += &rhs.bloom_filter; } } rhai-1.21.0/src/types/mod.rs000064400000000000000000000017551046102023000137200ustar 00000000000000//! Module defining Rhai data types. pub mod bloom_filter; pub mod custom_types; pub mod dynamic; pub mod error; pub mod float; pub mod fn_ptr; pub mod immutable_string; pub mod interner; pub mod parse_error; pub mod position; pub mod position_none; pub mod scope; pub mod var_def; pub mod variant; pub use bloom_filter::BloomFilterU64; pub use custom_types::{CustomTypeInfo, CustomTypesCollection}; pub use dynamic::Dynamic; #[cfg(not(feature = "no_time"))] pub use dynamic::Instant; pub use error::EvalAltResult; #[cfg(not(feature = "no_float"))] pub use float::FloatWrapper; pub use fn_ptr::FnPtr; pub use immutable_string::ImmutableString; pub use interner::StringsInterner; pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use var_def::VarDefInfo; #[cfg(not(feature = "no_position"))] pub use position::{Position, Span}; #[cfg(feature = "no_position")] pub use position_none::{Position, Span}; pub use scope::Scope; pub use variant::Variant; rhai-1.21.0/src/types/parse_error.rs000064400000000000000000000351541046102023000154640ustar 00000000000000//! Module containing error definitions for the parsing process. use crate::tokenizer::is_valid_identifier; use crate::{Position, RhaiError, ERR}; #[cfg(feature = "no_std")] use core_error::Error; #[cfg(not(feature = "no_std"))] use std::error::Error; use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Error encountered when tokenizing the script text. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] #[must_use] pub enum LexError { /// An unexpected symbol is encountered. UnexpectedInput(String), /// A string literal is not terminated before a new-line or EOF. UnterminatedString, /// An identifier or string literal is longer than the maximum allowed length. StringTooLong(usize), /// An string/character/numeric escape sequence is in an invalid format. MalformedEscapeSequence(String), /// An numeric literal is in an invalid format. MalformedNumber(String), /// An character literal is in an invalid format. MalformedChar(String), /// An identifier is in an invalid format. MalformedIdentifier(String), /// Bad symbol encountered. ImproperSymbol(String, String), /// Runtime error occurred. Runtime(String), } impl Error for LexError {} impl fmt::Display for LexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::UnexpectedInput(s) => write!(f, "Unexpected '{s}'"), Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{s}'"), Self::MalformedNumber(s) => write!(f, "Invalid number: '{s}'"), Self::MalformedChar(s) => write!(f, "Invalid character: '{s}'"), Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{s}'"), Self::UnterminatedString => f.write_str("Open string is not terminated"), Self::StringTooLong(max) => write!(f, "String is too long (max {max})"), Self::ImproperSymbol(s, d) if d.is_empty() => { write!(f, "Invalid symbol encountered: '{s}'") } Self::ImproperSymbol(.., d) | Self::Runtime(d) => f.write_str(d), } } } impl LexError { /// Convert a [`LexError`] into a [`ParseError`]. #[cold] #[inline(never)] pub fn into_err(self, pos: Position) -> ParseError { ParseError(Box::new(self.into()), pos) } } /// Error encountered when parsing a script. /// /// Some errors never appear when certain features are turned on. /// They still exist so that the application can turn features on and off without going through /// massive code changes to remove/add back enum variants in match statements. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] #[must_use] pub enum ParseErrorType { /// The script ends prematurely. UnexpectedEOF, /// Error in the script text. Wrapped value is the lex error. BadInput(LexError), /// An unknown operator is encountered. Wrapped value is the operator. UnknownOperator(String), /// Expecting a particular token but not finding one. Wrapped values are the token and description. MissingToken(String, String), /// Expecting a particular symbol but not finding one. Wrapped value is the description. MissingSymbol(String), /// An expression in function call arguments `()` has syntax error. Wrapped value is the error /// description (if any). /// /// # Deprecated /// /// This error is no longer used and will be removed in the next major version. #[deprecated( since = "1.16.0", note = "This error variant is no longer used and will be removed in the next major version." )] MalformedCallExpr(String), /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error /// description (if any). MalformedIndexExpr(String), /// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any). /// /// # Deprecated /// /// This error is no longer used and will be removed in the next major version. #[deprecated( since = "1.16.0", note = "This error variant is no longer used and will be removed in the next major version." )] MalformedInExpr(String), /// A capturing has syntax error. Wrapped value is the error description (if any). MalformedCapture(String), /// A map definition has duplicated property names. Wrapped value is the property name. DuplicatedProperty(String), /// A `switch` case is duplicated. /// /// # Deprecated /// /// This error is no longer used and will be removed in the next major version. #[deprecated( since = "1.9.0", note = "This error variant is no longer used and will be removed in the next major version." )] DuplicatedSwitchCase, /// A variable name is duplicated. Wrapped value is the variable name. DuplicatedVariable(String), /// A numeric case of a `switch` statement is in an appropriate place. WrongSwitchIntegerCase, /// The default case of a `switch` statement is in an appropriate place. WrongSwitchDefaultCase, /// The case condition of a `switch` statement is not appropriate. WrongSwitchCaseCondition, /// Missing a property name for custom types and maps. PropertyExpected, /// Missing a variable name after the `let`, `const`, `for` or `catch` keywords. VariableExpected, /// Forbidden variable name. Wrapped value is the variable name. ForbiddenVariable(String), /// An identifier is a reserved symbol. Reserved(String), /// An expression is of the wrong type. /// Wrapped values are the type requested and type of the actual result. MismatchedType(String, String), /// Missing an expression. Wrapped value is the expression type. ExprExpected(String), /// Defining a doc-comment in an appropriate place (e.g. not at global level). WrongDocComment, /// Defining a function `fn` in an appropriate place (e.g. inside another function). WrongFnDefinition, /// Defining a function with a name that conflicts with an existing function. /// Wrapped values are the function name and number of parameters. FnDuplicatedDefinition(String, usize), /// Missing a function name after the `fn` keyword. FnMissingName, /// A function definition is missing the parameters list. Wrapped value is the function name. FnMissingParams(String), /// A function definition has duplicated parameters. Wrapped values are the function name and /// parameter name. FnDuplicatedParam(String, String), /// A function definition is missing the body. Wrapped value is the function name. FnMissingBody(String), /// Export statement not at global level. WrongExport, /// Assignment to an a constant variable. Wrapped value is the constant variable name. AssignmentToConstant(String), /// Assignment to an inappropriate LHS (left-hand-side) expression. /// Wrapped value is the error message (if any). AssignmentToInvalidLHS(String), /// A variable is already defined. /// /// Only appears when variables shadowing is disabled. VariableExists(String), /// A variable is not found. /// /// Only appears when strict variables mode is enabled. VariableUndefined(String), /// An imported module is not found. /// /// Only appears when strict variables mode is enabled. ModuleUndefined(String), /// Expression exceeding the maximum levels of complexity. ExprTooDeep, /// Number of scripted functions over maximum limit. TooManyFunctions, /// Literal exceeding the maximum size. Wrapped values are the data type name and the maximum size. LiteralTooLarge(String, usize), /// Break statement not inside a loop. LoopBreak, } impl fmt::Display for ParseErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BadInput(err) => write!(f, "{err}"), Self::UnknownOperator(s) => write!(f, "Unknown operator: '{s}'"), Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"), Self::MalformedIndexExpr(s) => f.write_str(s), Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"), Self::MalformedCapture(s) => f.write_str(s), Self::FnDuplicatedDefinition(s, n) => { write!(f, "Function {s} with ")?; match n { 0 => f.write_str("no parameters already exists"), 1 => f.write_str("1 parameter already exists"), _ => write!(f, "{n} parameters already exists"), } } Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"), Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function {s}"), Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {s}"), Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {arg} for function {s}"), Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {s}"), Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {s}"), Self::VariableExists(s) => write!(f, "Variable already defined: {s}"), Self::VariableUndefined(s) => write!(f, "Undefined variable: {s}"), Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"), Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"), Self::ExprExpected(s) => write!(f, "Expecting {s} expression"), Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"), Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"), Self::MissingSymbol(s) => f.write_str(s), Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"), Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant {s}"), Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"), Self::AssignmentToInvalidLHS(s) => f.write_str(s), Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"), Self::Reserved(s) if is_valid_identifier(s) => write!(f, "'{s}' is a reserved keyword"), Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Numeric switch case cannot follow a range case"), Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"), Self::PropertyExpected => f.write_str("Expecting name of a property"), Self::VariableExpected => f.write_str("Expecting name of a variable"), Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {s}"), Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"), Self::FnMissingName => f.write_str("Expecting function name in function declaration"), Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"), Self::WrongExport => f.write_str("Export statement can only appear at global level"), Self::ExprTooDeep => f.write_str("Expression exceeds maximum complexity"), Self::TooManyFunctions => f.write_str("Number of functions defined exceeds maximum limit"), Self::LoopBreak => f.write_str("Break statement should only be used inside a loop"), #[allow(deprecated)] Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), #[allow(deprecated)] Self::MalformedCallExpr(s) if s.is_empty() => f.write_str(s), #[allow(deprecated)] Self::MalformedCallExpr(..) => f.write_str("Invalid expression in function call arguments"), #[allow(deprecated)] Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"), #[allow(deprecated)] Self::MalformedInExpr(s) => f.write_str(s), } } } impl From for ParseErrorType { #[cold] #[inline(never)] fn from(err: LexError) -> Self { match err { LexError::StringTooLong(max) => { Self::LiteralTooLarge("Length of string".to_string(), max) } _ => Self::BadInput(err), } } } /// Error when parsing a script. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[must_use] pub struct ParseError( /// Parse error type. pub Box, /// [Position] of the parse error. pub Position, ); impl Error for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f)?; // Do not write any position if None if !self.1.is_none() { write!(f, " ({})", self.1)?; } Ok(()) } } impl ParseError { /// Get the [type][ParseErrorType] of this parse error. #[cold] #[inline(never)] pub const fn err_type(&self) -> &ParseErrorType { &self.0 } /// Get the [position][Position] of this parse error. #[cold] #[inline(never)] #[must_use] pub const fn position(&self) -> Position { self.1 } } impl From for RhaiError { #[cold] #[inline(never)] fn from(err: ParseErrorType) -> Self { Self::new(err.into()) } } impl From for ERR { #[cold] #[inline(never)] fn from(err: ParseErrorType) -> Self { Self::ErrorParsing(err, Position::NONE) } } impl From for RhaiError { #[cold] #[inline(never)] fn from(err: ParseError) -> Self { Self::new(err.into()) } } impl From for ERR { #[cold] #[inline(never)] fn from(err: ParseError) -> Self { Self::ErrorParsing(*err.0, err.1) } } rhai-1.21.0/src/types/position.rs000064400000000000000000000170111046102023000147750ustar 00000000000000//! Script character position type. #![cfg(not(feature = "no_position"))] #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fmt, ops::{Add, AddAssign}, }; /// A location (line number + character position) in the input script. /// /// # Limitations /// /// In order to keep footprint small, both line number and character position have 16-bit resolution, /// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line. /// /// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { /// Line number: 0 = none line: u16, /// Character position: 0 = BOL pos: u16, } impl Position { /// A [`Position`] representing no position. pub const NONE: Self = Self { line: 0, pos: 0 }; /// A [`Position`] representing the first position. pub const START: Self = Self { line: 1, pos: 0 }; /// Create a new [`Position`]. /// /// `line` must not be zero. /// /// If `position` is zero, then it is at the beginning of a line. /// /// # Panics /// /// Panics if `line` is zero. #[inline] #[must_use] pub const fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); let _pos = position; Self { line, pos: _pos } } /// Get the line number (1-based), or [`None`] if there is no position. /// /// Always returns [`None`] under `no_position`. #[inline] #[must_use] pub const fn line(self) -> Option { if self.is_none() { None } else { Some(self.line as usize) } } /// Get the character position (1-based), or [`None`] if at beginning of a line. /// /// Always returns [`None`] under `no_position`. #[inline] #[must_use] pub const fn position(self) -> Option { if self.is_none() || self.pos == 0 { None } else { Some(self.pos as usize) } } /// Advance by one character position. #[inline] pub(crate) fn advance(&mut self) { assert!(!self.is_none(), "cannot advance Position::NONE"); // Advance up to maximum position self.pos = self.pos.saturating_add(1); } /// Go backwards by one character position. /// /// # Panics /// /// Panics if already at beginning of a line - cannot rewind to a previous line. #[inline] pub(crate) fn rewind(&mut self) { assert!(!self.is_none(), "cannot rewind Position::NONE"); assert!(self.pos > 0, "cannot rewind at position 0"); self.pos -= 1; } /// Advance to the next line. #[inline] pub(crate) fn new_line(&mut self) { assert!(!self.is_none(), "cannot advance Position::NONE"); // Advance up to maximum position if self.line < u16::MAX { self.line += 1; self.pos = 0; } } /// Is this [`Position`] at the beginning of a line? /// /// Always returns `false` under `no_position`. #[inline] #[must_use] pub const fn is_beginning_of_line(self) -> bool { self.pos == 0 && !self.is_none() } /// Is there no [`Position`]? /// /// Always returns `true` under `no_position`. #[inline] #[must_use] pub const fn is_none(self) -> bool { self.line == 0 && self.pos == 0 } /// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]? /// /// Always returns the fallback under `no_position`. #[inline] #[must_use] pub const fn or_else(self, pos: Self) -> Self { if self.is_none() { pos } else { self } } } impl Default for Position { #[inline(always)] #[must_use] fn default() -> Self { Self::START } } impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { write!(f, "none") } else { write!(f, "line {}, position {}", self.line, self.pos) } } } impl fmt::Debug for Position { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { f.write_str("none") } else if self.is_beginning_of_line() { write!(f, "{}", self.line) } else { write!(f, "{}:{}", self.line, self.pos) } } } impl Add for Position { type Output = Self; fn add(self, rhs: Self) -> Self::Output { if rhs.is_none() { self } else { Self { line: self.line + rhs.line - 1, pos: if rhs.is_beginning_of_line() { self.pos } else { self.pos + rhs.pos - 1 }, } } } } impl AddAssign for Position { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } /// _(internals)_ A span consisting of a starting and an ending [positions][Position]. /// Exported under the `internals` feature only. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Span { /// Starting [position][Position]. start: Position, /// Ending [position][Position]. end: Position, } impl Default for Span { #[inline(always)] #[must_use] fn default() -> Self { Self::NONE } } impl Span { /// Empty [`Span`]. pub const NONE: Self = Self::new(Position::NONE, Position::NONE); /// Create a new [`Span`]. #[inline(always)] #[must_use] pub const fn new(start: Position, end: Position) -> Self { Self { start, end } } /// Is this [`Span`] non-existent? /// /// Always returns `true` under `no_position`. #[inline(always)] #[must_use] pub const fn is_none(self) -> bool { self.start.is_none() && self.end.is_none() } /// Get the [`Span`]'s starting [position][Position]. /// /// Always returns [`Position::NONE`] under `no_position`. #[inline(always)] #[must_use] pub const fn start(self) -> Position { self.start } /// Get the [`Span`]'s ending [position][Position]. /// /// Always returns [`Position::NONE`] under `no_position`. #[inline(always)] #[must_use] pub const fn end(self) -> Position { self.end } } impl fmt::Display for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _f = f; match (self.start(), self.end()) { (Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE), (Position::NONE, end) => write!(_f, "..{end:?}"), (start, Position::NONE) => write!(_f, "{start:?}"), (start, end) if start.line() != end.line() => { write!(_f, "{start:?}-{end:?}") } (start, end) => write!( _f, "{}:{}-{}", start.line().unwrap(), start.position().unwrap_or(0), end.position().unwrap_or(0) ), } } } impl fmt::Debug for Span { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } rhai-1.21.0/src/types/position_none.rs000064400000000000000000000101121046102023000160070ustar 00000000000000//! Placeholder script character position type. #![cfg(feature = "no_position")] #![allow(unused_variables)] #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fmt, ops::{Add, AddAssign}, }; /// A location (line number + character position) in the input script. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] pub struct Position; impl Position { /// A [`Position`] representing no position. pub const NONE: Self = Self; /// A [`Position`] representing the first position. pub const START: Self = Self; /// Create a new [`Position`]. #[inline(always)] #[must_use] pub const fn new(line: u16, position: u16) -> Self { Self } /// Get the line number (1-based), or [`None`] if there is no position. /// /// Always returns [`None`]. #[inline(always)] #[must_use] pub const fn line(self) -> Option { None } /// Get the character position (1-based), or [`None`] if at beginning of a line. /// /// Always returns [`None`]. #[inline(always)] #[must_use] pub const fn position(self) -> Option { None } /// Advance by one character position. #[inline(always)] pub(crate) fn advance(&mut self) {} /// Go backwards by one character position. #[inline(always)] pub(crate) fn rewind(&mut self) {} /// Advance to the next line. #[inline(always)] pub(crate) fn new_line(&mut self) {} /// Is this [`Position`] at the beginning of a line? /// /// Always returns `false`. #[inline(always)] #[must_use] pub const fn is_beginning_of_line(self) -> bool { false } /// Is there no [`Position`]? /// /// Always returns `true`. #[inline(always)] #[must_use] pub const fn is_none(self) -> bool { true } /// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]? /// /// Always returns the fallback. #[inline(always)] #[must_use] pub const fn or_else(self, pos: Self) -> Self { pos } } impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "none") } } impl fmt::Debug for Position { #[cold] #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("none") } } impl Add for Position { type Output = Self; #[inline(always)] fn add(self, rhs: Self) -> Self::Output { Self } } impl AddAssign for Position { #[inline(always)] fn add_assign(&mut self, rhs: Self) {} } /// _(internals)_ A span consisting of a starting and an ending [positions][Position]. /// Exported under the `internals` feature only. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)] pub struct Span; impl Span { /// Empty [`Span`]. pub const NONE: Self = Self; /// Create a new [`Span`]. #[inline(always)] #[must_use] pub const fn new(start: Position, end: Position) -> Self { Self } /// Is this [`Span`] non-existent? /// /// Always returns `true`. #[inline(always)] #[must_use] pub const fn is_none(&self) -> bool { true } /// Get the [`Span`]'s starting [position][Position]. /// /// Always returns [`Position::NONE`]. #[inline(always)] #[must_use] pub const fn start(&self) -> Position { Position::NONE } /// Get the [`Span`]'s ending [position][Position]. /// /// Always returns [`Position::NONE`]. #[inline(always)] #[must_use] pub const fn end(&self) -> Position { Position::NONE } } impl fmt::Display for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let f = f; write!(f, "{:?}", Position) } } impl fmt::Debug for Span { #[cold] #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } rhai-1.21.0/src/types/scope.rs000064400000000000000000000760121046102023000142500ustar 00000000000000//! Module that defines the [`Scope`] type representing a function call-stack scope. use super::dynamic::{AccessMode, Variant}; use crate::{Dynamic, Identifier, ImmutableString, StaticVec, ThinVec}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ fmt, iter, iter::{Extend, FromIterator}, marker::PhantomData, }; /// Minimum number of entries in the [`Scope`] to avoid reallocations. pub const MIN_SCOPE_ENTRIES: usize = 8; /// Type containing information about the current scope. Useful for keeping state between /// [`Engine`][crate::Engine] evaluation runs. /// /// # Lifetime /// /// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for /// future versions. Until then, `'static` can be used. /// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it /// [`Send`] `+` [`Sync`]. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// let mut my_scope = Scope::new(); /// /// my_scope.push("z", 40_i64); /// /// engine.run_with_scope(&mut my_scope, "let x = z + 1; z = 0;")?; /// /// let result: i64 = engine.eval_with_scope(&mut my_scope, "x + 1")?; /// /// assert_eq!(result, 42); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 41); /// assert_eq!(my_scope.get_value::("z").expect("z should exist"), 0); /// # Ok(()) /// # } /// ``` /// /// When searching for entries, newly-added entries are found before similarly-named but older /// entries, allowing for automatic _shadowing_. // // # Implementation Notes // // [`Scope`] is implemented as three arrays. Two (`values` and `names`) are of exactly the same // length. That's because in the vast majority of cases the name is NOT used to look up a variable. // Variable lookup is usually via direct indexing, by-passing the name altogether. // // [`Dynamic`] is reasonably small so packing it tightly improves cache performance. #[derive(Debug, Hash, Default)] pub struct Scope<'a> { /// Current value of the entry. values: ThinVec, /// Name of the entry. names: ThinVec, /// Aliases of the entry. /// /// This `Vec` is not filled until needed because aliases are used rarely /// (only for `export` statements). aliases: ThinVec>, /// Phantom to keep the lifetime parameter in order not to break existing code. dummy: PhantomData<&'a ()>, } impl fmt::Display for Scope<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, (name, constant, value)) in self.iter_inner().enumerate() { writeln!( f, "[{}] {}{} = {:?}", i + 1, if constant { "const " } else { "" }, name, value, )?; } Ok(()) } } impl Clone for Scope<'_> { #[inline] fn clone(&self) -> Self { Self { values: self .values .iter() .map(|v| { // Also copy the value's access mode (otherwise will turn to read-write) let mut v2 = v.clone(); v2.set_access_mode(v.access_mode()); v2 }) .collect(), names: self.names.clone(), aliases: self.aliases.clone(), dummy: self.dummy, } } } impl IntoIterator for Scope<'_> { type Item = (String, Dynamic, Vec); type IntoIter = Box>; #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values .into_iter() .zip( self.names.into_iter().zip( self.aliases .into_iter() .map(|a| a.to_vec()) .chain(iter::repeat(Vec::new())), ), ) .map(|(value, (name, alias))| (name.to_string(), value, alias)), ) } } impl<'a> IntoIterator for &'a Scope<'_> { type Item = (&'a str, &'a Dynamic, &'a [ImmutableString]); type IntoIter = Box + 'a>; #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values .iter() .zip( self.names.iter().zip( self.aliases .iter() .map(<_>::as_ref) .chain(iter::repeat(&[][..])), ), ) .map(|(value, (name, alias))| (name.as_str(), value, alias)), ) } } impl Scope<'_> { /// Create a new [`Scope`]. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] #[must_use] pub fn new() -> Self { Self { values: ThinVec::new(), names: ThinVec::new(), aliases: ThinVec::new(), dummy: PhantomData, } } /// Create a new [`Scope`] with a particular capacity. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::with_capacity(10); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self { values: ThinVec::with_capacity(capacity), names: ThinVec::with_capacity(capacity), aliases: ThinVec::new(), dummy: PhantomData, } } /// Empty the [`Scope`]. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert!(my_scope.contains("x")); /// assert_eq!(my_scope.len(), 1); /// assert!(!my_scope.is_empty()); /// /// my_scope.clear(); /// assert!(!my_scope.contains("x")); /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` #[inline(always)] pub fn clear(&mut self) -> &mut Self { self.names.clear(); self.values.clear(); self.aliases.clear(); self } /// Get the number of entries inside the [`Scope`]. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// assert_eq!(my_scope.len(), 0); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.len(), 1); /// ``` #[inline(always)] #[must_use] pub fn len(&self) -> usize { self.values.len() } /// Returns `true` if this [`Scope`] contains no variables. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// assert!(my_scope.is_empty()); /// /// my_scope.push("x", 42_i64); /// assert!(!my_scope.is_empty()); /// ``` #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { self.values.is_empty() } /// Add (push) a new entry to the [`Scope`]. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] pub fn push(&mut self, name: impl Into, value: impl Variant + Clone) -> &mut Self { self.push_entry( name.into().into(), AccessMode::ReadWrite, Dynamic::from(value), ) } /// Add (push) a new [`Dynamic`] entry to the [`Scope`]. /// /// # Example /// /// ``` /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] pub fn push_dynamic(&mut self, name: impl Into, value: Dynamic) -> &mut Self { self.push_entry(name.into().into(), value.access_mode(), value) } /// Add (push) a new constant to the [`Scope`]. /// /// Constants are immutable and cannot be assigned to. Their values never change. /// Constants propagation is a technique used to optimize an [`AST`][crate::AST]. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] pub fn push_constant( &mut self, name: impl Into, value: impl Variant + Clone, ) -> &mut Self { self.push_entry( name.into().into(), AccessMode::ReadOnly, Dynamic::from(value), ) } /// Add (push) a new constant with a [`Dynamic`] value to the Scope. /// /// Constants are immutable and cannot be assigned to. Their values never change. /// Constants propagation is a technique used to optimize an [`AST`][crate::AST]. /// /// # Example /// /// ``` /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline(always)] pub fn push_constant_dynamic( &mut self, name: impl Into, value: Dynamic, ) -> &mut Self { self.push_entry(name.into().into(), AccessMode::ReadOnly, value) } /// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`]. #[inline] pub(crate) fn push_entry( &mut self, name: ImmutableString, access: AccessMode, mut value: Dynamic, ) -> &mut Self { if self.is_empty() { self.names.reserve(MIN_SCOPE_ENTRIES); self.values.reserve(MIN_SCOPE_ENTRIES); } self.names.push(name); value.set_access_mode(access); self.values.push(value); self } /// Remove the last entry from the [`Scope`]. /// /// # Panics /// /// Panics is the [`Scope`] is empty. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// my_scope.push("y", 123_i64); /// assert!(my_scope.contains("x")); /// assert!(my_scope.contains("y")); /// assert_eq!(my_scope.len(), 2); /// /// my_scope.pop(); /// assert!(my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// assert_eq!(my_scope.len(), 1); /// /// my_scope.pop(); /// assert!(!my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` #[inline(always)] pub fn pop(&mut self) -> &mut Self { self.names .pop() .unwrap_or_else(|| panic!("`Scope` is empty")); self.values.truncate(self.names.len()); self.aliases.truncate(self.names.len()); self } /// Remove the last entry from the [`Scope`] and return it. #[inline(always)] #[allow(dead_code)] pub(crate) fn pop_entry(&mut self) -> Option<(ImmutableString, Dynamic, Vec)> { self.values.pop().map(|value| { ( self.names.pop().unwrap(), value, if self.aliases.len() > self.values.len() { self.aliases.pop().unwrap().to_vec() } else { Vec::new() }, ) }) } /// Truncate (rewind) the [`Scope`] to a previous size. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// my_scope.push("y", 123_i64); /// assert!(my_scope.contains("x")); /// assert!(my_scope.contains("y")); /// assert_eq!(my_scope.len(), 2); /// /// my_scope.rewind(1); /// assert!(my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// assert_eq!(my_scope.len(), 1); /// /// my_scope.rewind(0); /// assert!(!my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// assert_eq!(my_scope.len(), 0); /// assert!(my_scope.is_empty()); /// ``` #[inline(always)] pub fn rewind(&mut self, size: usize) -> &mut Self { self.names.truncate(size); self.values.truncate(size); self.aliases.truncate(size); self } /// Does the [`Scope`] contain the entry? /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert!(my_scope.contains("x")); /// assert!(!my_scope.contains("y")); /// ``` #[inline] #[must_use] pub fn contains(&self, name: &str) -> bool { self.names.iter().any(|key| name == key) } /// Find an entry in the [`Scope`], starting from the last. #[inline] #[must_use] pub(crate) fn search(&self, name: &str) -> Option { self.names .iter() .rev() // Always search a Scope in reverse order .position(|key| name == key) .map(|i| self.len() - 1 - i) } /// Get the value of an entry in the [`Scope`], starting from the last. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// ``` #[inline] #[must_use] pub fn get_value(&self, name: &str) -> Option { self.names .iter() .rev() .position(|key| name == key) .and_then(|i| self.values[self.len() - 1 - i].flatten_clone().try_cast()) } /// Get a reference the value of an entry in the [`Scope`], starting from the last. /// /// # Panics /// /// Panics if the value is _shared_. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// /// let ptr = my_scope.get_value_ref::("x").expect("x should exist"); /// /// assert_eq!(*ptr, 42); /// ``` #[inline] #[must_use] pub fn get_value_ref(&self, name: &str) -> Option<&T> { self.names .iter() .rev() .position(|key| name == key) .and_then(|i| { let v = &self.values[self.len() - 1 - i]; #[cfg(not(feature = "no_closure"))] assert!(!v.is_shared()); v.downcast_ref() }) } /// Get a mutable reference the value of an entry in the [`Scope`], starting from the last. /// /// # Panics /// /// Panics if the value is _shared_. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// /// let ptr = my_scope.get_value_mut::("x").expect("x should exist"); /// /// *ptr = 0; /// /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 0); /// ``` #[inline] #[must_use] pub fn get_value_mut(&mut self, name: &str) -> Option<&mut T> { let len = self.len(); self.names .iter_mut() .rev() .position(|key| name == key) .and_then(move |i| { let v = &mut self.values[len - 1 - i]; #[cfg(not(feature = "no_closure"))] assert!(!v.is_shared()); v.downcast_mut() }) } /// Check if the named entry in the [`Scope`] is constant. /// /// Search starts backwards from the last, stopping at the first entry matching the specified name. /// /// Returns [`None`] if no entry matching the specified name is found. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.is_constant("x"), Some(true)); /// assert_eq!(my_scope.is_constant("y"), None); /// ``` #[inline] #[must_use] pub fn is_constant(&self, name: &str) -> Option { self.search(name) .map(|n| match self.values[n].access_mode() { AccessMode::ReadWrite => false, AccessMode::ReadOnly => true, }) } /// Update the value of the named entry in the [`Scope`] if it already exists and is not constant. /// Push a new entry with the value into the [`Scope`] if the name doesn't exist or if the /// existing entry is constant. /// /// Search starts backwards from the last, and only the first entry matching the specified name is updated. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.set_or_push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// assert_eq!(my_scope.len(), 1); /// /// my_scope.set_or_push("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 0); /// assert_eq!(my_scope.len(), 1); /// /// my_scope.set_or_push("y", 123_i64); /// assert_eq!(my_scope.get_value::("y").expect("y should exist"), 123); /// assert_eq!(my_scope.len(), 2); /// ``` #[inline] pub fn set_or_push( &mut self, name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { match self .search(name.as_ref()) .map(|n| (n, self.values[n].access_mode())) { None | Some((.., AccessMode::ReadOnly)) => { self.push(name, value); } Some((index, AccessMode::ReadWrite)) => { let value_ref = self.values.get_mut(index).unwrap(); *value_ref = Dynamic::from(value); } } self } /// Update the value of the named entry in the [`Scope`]. /// /// Search starts backwards from the last, and only the first entry matching the specified name is updated. /// If no entry matching the specified name is found, a new one is added. /// /// # Panics /// /// Panics when trying to update the value of a constant. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// /// my_scope.set_value("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 0); /// ``` #[inline] pub fn set_value( &mut self, name: impl AsRef + Into, value: impl Variant + Clone, ) -> &mut Self { match self .search(name.as_ref()) .map(|n| (n, self.values[n].access_mode())) { None => { self.push(name, value); } Some((.., AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()), Some((index, AccessMode::ReadWrite)) => { let value_ref = self.values.get_mut(index).unwrap(); *value_ref = Dynamic::from(value); } } self } /// Get a reference to an entry in the [`Scope`]. /// /// If the entry by the specified name is not found, [`None`] is returned. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// /// let value = my_scope.get("x").expect("x should exist"); /// /// assert_eq!(value.as_int().unwrap(), 42); /// /// assert!(my_scope.get("z").is_none()); /// ``` #[inline(always)] #[must_use] pub fn get(&self, name: &str) -> Option<&Dynamic> { self.search(name).map(|index| &self.values[index]) } /// Get a reference to an entry in the [`Scope`] based on the index. /// /// # Panics /// /// Panics if the index is out of bounds. #[inline(always)] #[allow(dead_code)] pub(crate) fn get_entry_by_index(&self, index: usize) -> (&str, &Dynamic, &[ImmutableString]) { ( &self.names[index], &self.values[index], if self.aliases.len() > index { &self.aliases[index] } else { &[] }, ) } /// Remove the last entry in the [`Scope`] by the specified name and return its value. /// /// If the entry by the specified name is not found, [`None`] is returned. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 123_i64); // first 'x' /// my_scope.push("x", 42_i64); // second 'x', shadows first /// /// assert_eq!(my_scope.len(), 2); /// /// let value = my_scope.remove::("x").expect("x should exist"); /// /// assert_eq!(value, 42); /// /// assert_eq!(my_scope.len(), 1); /// /// let value = my_scope.get_value::("x").expect("x should still exist"); /// /// assert_eq!(value, 123); /// ``` #[inline(always)] #[must_use] pub fn remove(&mut self, name: &str) -> Option { self.search(name).and_then(|index| { self.names.remove(index); if self.aliases.len() > index { self.aliases.remove(index); } self.values.remove(index).try_cast() }) } /// Get a mutable reference to the value of an entry in the [`Scope`]. /// /// If the entry by the specified name is not found, or if it is read-only, /// [`None`] is returned. /// /// # Example /// /// ``` /// use rhai::Scope; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 42); /// /// let ptr = my_scope.get_mut("x").expect("x should exist"); /// *ptr = 123_i64.into(); /// /// assert_eq!(my_scope.get_value::("x").expect("x should exist"), 123); /// /// my_scope.push_constant("z", 1_i64); /// assert!(my_scope.get_mut("z").is_none()); /// ``` #[inline] #[must_use] pub fn get_mut(&mut self, name: &str) -> Option<&mut Dynamic> { self.search(name) .and_then(move |n| match self.values[n].access_mode() { AccessMode::ReadWrite => Some(self.get_mut_by_index(n)), AccessMode::ReadOnly => None, }) } /// Get a mutable reference to the value of an entry in the [`Scope`] based on the index. /// /// # Panics /// /// Panics if the index is out of bounds. #[inline(always)] pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { &mut self.values[index] } /// Add an alias to an entry in the [`Scope`]. /// /// # Panics /// /// Panics if the index is out of bounds. #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn add_alias_by_index(&mut self, index: usize, alias: ImmutableString) -> &mut Self { if self.aliases.len() <= index { self.aliases.resize(index + 1, <_>::default()); } let aliases = self.aliases.get_mut(index).unwrap(); if !aliases.contains(&alias) { aliases.push(alias); } self } /// Add an alias to a variable in the [`Scope`] so that it is exported under that name. /// This is an advanced API. /// /// Variable aliases are used, for example, in [`Module::eval_ast_as_new`][crate::Module::eval_ast_as_new] /// to create a new module with exported variables under different names. /// /// If the alias is empty, then the variable is exported under its original name. /// /// Multiple aliases can be added to any variable. /// /// Only the last variable matching the name (and not other shadowed versions) is aliased by this call. #[cfg(not(feature = "no_module"))] #[inline] pub fn set_alias( &mut self, name: impl AsRef + Into, alias: impl Into, ) { if let Some(index) = self.search(name.as_ref()) { let alias = match alias.into() { x if x.is_empty() => name.into().into(), x => x, }; self.add_alias_by_index(index, alias); } } /// Clone the [`Scope`], keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. #[inline] #[must_use] pub fn clone_visible(&self) -> Self { let len = self.len(); let mut scope = Self::new(); self.names.iter().rev().enumerate().for_each(|(i, name)| { if scope.names.contains(name) { return; } let index = len - 1 - i; let v1 = &self.values[index]; scope.push_entry(name.clone(), v1.access_mode(), v1.clone()); if self.aliases.len() > index { scope.aliases.resize(scope.len() - 1, <_>::default()); scope.aliases.push(self.aliases[index].clone()); } }); scope } /// Get an iterator to entries in the [`Scope`]. /// Shared values are flatten-cloned. /// /// # Example /// /// ``` /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// /// my_scope.push("x", 42_i64); /// my_scope.push_constant("foo", "hello"); /// /// let mut iter = my_scope.iter(); /// /// let (name, is_constant, value) = iter.next().expect("value should exist"); /// assert_eq!(name, "x"); /// assert!(!is_constant); /// assert_eq!(value.cast::(), 42); /// /// let (name, is_constant, value) = iter.next().expect("value should exist"); /// assert_eq!(name, "foo"); /// assert!(is_constant); /// assert_eq!(value.cast::(), "hello"); /// ``` #[inline(always)] pub fn iter(&self) -> impl Iterator { self.iter_inner() .map(|(name, constant, value)| (name.as_str(), constant, value.flatten_clone())) } /// Get an iterator to entries in the [`Scope`]. /// Shared values are not expanded. #[inline(always)] pub fn iter_raw(&self) -> impl Iterator { self.iter_rev_inner() .map(|(name, constant, value)| (name.as_str(), constant, value)) } /// Get an iterator to entries in the [`Scope`]. /// Shared values are not expanded. #[inline] pub(crate) fn iter_inner(&self) -> impl Iterator { self.names .iter() .zip(self.values.iter()) .map(|(name, value)| (name, value.is_read_only(), value)) } /// Get a reverse iterator to entries in the [`Scope`]. /// Shared values are not expanded. #[inline] pub(crate) fn iter_rev_inner( &self, ) -> impl Iterator { self.names .iter() .rev() .zip(self.values.iter().rev()) .map(|(name, value)| (name, value.is_read_only(), value)) } /// Remove a range of entries within the [`Scope`]. /// /// # Panics /// /// Panics if the range is out of bounds. #[inline] #[allow(dead_code)] pub(crate) fn remove_range(&mut self, start: usize, len: usize) { self.values.drain(start..start + len).for_each(|_| {}); self.names.drain(start..start + len).for_each(|_| {}); if self.aliases.len() > start { if self.aliases.len() <= start + len { self.aliases.truncate(start); } else { self.aliases.drain(start..start + len).for_each(|_| {}); } } } } impl> Extend<(K, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { for (name, value) in iter { self.push_entry(name.into().into(), AccessMode::ReadWrite, value); } } } impl> FromIterator<(K, Dynamic)> for Scope<'_> { #[inline] fn from_iter>(iter: T) -> Self { let mut scope = Self::new(); scope.extend(iter); scope } } impl> Extend<(K, bool, Dynamic)> for Scope<'_> { #[inline] fn extend>(&mut self, iter: T) { for (name, is_constant, value) in iter { self.push_entry( name.into().into(), if is_constant { AccessMode::ReadOnly } else { AccessMode::ReadWrite }, value, ); } } } impl> FromIterator<(K, bool, Dynamic)> for Scope<'_> { #[inline] fn from_iter>(iter: T) -> Self { let mut scope = Self::new(); scope.extend(iter); scope } } rhai-1.21.0/src/types/var_def.rs000064400000000000000000000061441046102023000145440ustar 00000000000000//! Variable declaration information. #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Information on a variable declaration. #[derive(Debug, Clone, Hash)] pub struct VarDefInfo<'a> { /// Name of the variable to be declared. /// /// # Deprecated API /// /// [`VarDefInfo`] fields will be private in the next major version. Use `name()` instead. #[deprecated( since = "1.16.0", note = "`VarDefInfo` fields will be private in the next major version. Use `name()` instead." )] pub name: &'a str, /// `true` if the statement is `const`, otherwise it is `let`. /// /// # Deprecated API /// /// [`VarDefInfo`] fields will be private in the next major version. Use `is_const()` instead. #[deprecated( since = "1.16.0", note = "`VarDefInfo` fields will be private in the next major version. Use `is_const()` instead." )] pub is_const: bool, /// The current nesting level, with zero being the global level. /// /// # Deprecated API /// /// [`VarDefInfo`] fields will be private in the next major version. Use `nesting_level()` instead. #[deprecated( since = "1.16.0", note = "`VarDefInfo` fields will be private in the next major version. Use `nesting_level()` instead." )] pub nesting_level: usize, /// Will the variable _shadow_ an existing variable? /// /// # Deprecated API /// /// [`VarDefInfo`] fields will be private in the next major version. Use `will_shadow_other_variables()` instead. #[deprecated( since = "1.16.0", note = "`VarDefInfo` fields will be private in the next major version. Use `will_shadow_other_variables()` instead." )] pub will_shadow: bool, } #[allow(deprecated)] impl<'a> VarDefInfo<'a> { /// Create a new [`VarDefInfo`]. #[inline(always)] #[must_use] pub(crate) const fn new( name: &'a str, is_const: bool, nesting_level: usize, will_shadow: bool, ) -> Self { Self { name, is_const, nesting_level, will_shadow, } } /// Name of the variable to be declared. #[inline(always)] #[must_use] pub const fn name(&self) -> &str { self.name } /// `true` if the statement is `const`, otherwise it is `let`. #[inline(always)] #[must_use] pub const fn is_const(&self) -> bool { self.is_const } /// The current nesting level, with zero being the global level. #[inline(always)] #[must_use] pub const fn nesting_level(&self) -> usize { self.nesting_level } /// `true` if the variable is declared at global level (i.e. nesting level zero). #[inline(always)] #[must_use] pub const fn is_global_level(&self) -> bool { self.nesting_level == 0 } /// Will the variable _shadow_ an existing variable? #[inline(always)] #[must_use] pub const fn will_shadow_other_variables(&self) -> bool { self.will_shadow } } rhai-1.21.0/src/types/variant.rs000064400000000000000000000060501046102023000145760ustar 00000000000000//! [`Variant`] trait to to allow custom type handling. use crate::func::SendSync; use std::any::{type_name, Any, TypeId}; #[cfg(feature = "no_std")] use std::prelude::v1::*; mod private { use crate::func::SendSync; use std::any::Any; /// A sealed trait that prevents other crates from implementing [`Variant`][super::Variant]. pub trait Sealed {} impl Sealed for T {} } /// _(internals)_ Trait to represent any type. /// Exported under the `internals` feature only. /// /// This trait is sealed and cannot be implemented. /// /// Currently, [`Variant`] is not [`Send`] nor [`Sync`], so it can practically be any type. /// Turn on the `sync` feature to restrict it to only types that implement [`Send`] `+` [`Sync`]. #[cfg(not(feature = "sync"))] pub trait Variant: Any + private::Sealed { /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. #[must_use] fn as_any(&self) -> &dyn Any; /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. #[must_use] fn as_any_mut(&mut self) -> &mut dyn Any; /// Convert this [`Variant`] trait object to [`Box`][Any]. #[must_use] fn as_boxed_any(self: Box) -> Box; /// Get the name of this type. #[must_use] fn type_name(&self) -> &'static str; /// Clone this [`Variant`] trait object. #[must_use] fn clone_object(&self) -> Box; } /// _(internals)_ Trait to represent any type. /// Exported under the `internals` feature only. /// /// This trait is sealed and cannot be implemented. #[cfg(feature = "sync")] pub trait Variant: Any + Send + Sync + private::Sealed { /// Convert this [`Variant`] trait object to [`&dyn Any`][Any]. #[must_use] fn as_any(&self) -> &dyn Any; /// Convert this [`Variant`] trait object to [`&mut dyn Any`][Any]. #[must_use] fn as_any_mut(&mut self) -> &mut dyn Any; /// Convert this [`Variant`] trait object to [`Box`][Any]. #[must_use] fn as_boxed_any(self: Box) -> Box; /// Get the name of this type. #[must_use] fn type_name(&self) -> &'static str; /// Clone this [`Variant`] trait object. #[must_use] fn clone_object(&self) -> Box; } impl Variant for T { #[inline(always)] fn as_any(&self) -> &dyn Any { self } #[inline(always)] fn as_any_mut(&mut self) -> &mut dyn Any { self } #[inline(always)] fn as_boxed_any(self: Box) -> Box { self } #[inline(always)] fn type_name(&self) -> &'static str { type_name::() } #[inline(always)] fn clone_object(&self) -> Box { Box::new(self.clone()) as Box } } impl dyn Variant { /// Is this [`Variant`] a specific type? #[inline(always)] #[must_use] pub fn is(&self) -> bool { TypeId::of::() == self.type_id() } }