This commit is contained in:
kristof 2025-04-03 09:18:05 +02:00
parent f6935492fb
commit 7e9ad524cc
329 changed files with 45891 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

523
rhai_engine/Cargo.lock generated Normal file
View File

@ -0,0 +1,523 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[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 = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[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-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crunchy"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
dependencies = [
"portable-atomic",
]
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rhai"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
dependencies = [
"ahash",
"bitflags",
"instant",
"num-traits",
"once_cell",
"rhai_codegen",
"smallvec",
"smartstring",
"thin-vec",
]
[[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",
]
[[package]]
name = "rhai_engine"
version = "0.1.0"
dependencies = [
"chrono",
"rhai",
"serde",
"serde_json",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "smartstring"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [
"autocfg",
"static_assertions",
"version_check",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thin-vec"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
[[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.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[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.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[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",
]

18
rhai_engine/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "rhai_engine"
version = "0.1.0"
edition = "2021"
[lib]
name = "rhai_engine"
path = "src/lib.rs"
[[bin]]
name = "rhai_engine"
path = "src/main.rs"
[dependencies]
rhai = "1.12.0"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -0,0 +1,176 @@
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

View File

@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,338 @@
The Rhai Scripting Language
==========================
[The Rhai Book](index.md)
----------------------
Users Guide
============
- [Introduction](about/index.md)
- [Features of Rhai](about/features.md)
- [What Rhai Isn't](about/non-design.md)
- [Benchmarks](about/benchmarks.md)
- [Supported Targets and Builds](about/targets.md)
- [Dependencies](about/dependencies.md)
- [Licensing](about/license.md)
- [Related Resources](about/related.md)
- [Getting Started](start/index.md)
- [Online Playground](start/playground.md)
- [Install the Rhai Crate](start/install.md)
- [Optional Features](start/features.md)
- [Packaged Utilities](start/bin.md)
- [The Scripting Engine](engine/index.md)
- [“Hello, Rhai”](engine/hello-world.md)
- [Compile to AST](engine/compile.md)
- [Raw Engine](engine/raw.md)
- [Built-in Operators](engine/builtin.md)
- [Scope – Maintaining State](engine/scope.md)
- [Expressions Only](engine/expressions.md)
- [Options](engine/options.md)
- [Examples](start/examples/index.md)
- [Rust](start/examples/rust.md)
- [Scripts](start/examples/scripts.md)
- [Special Builds](start/builds/index.md)
- [Performance](start/builds/performance.md)
- [Minimal](start/builds/minimal.md)
- [no-std](start/builds/no-std.md)
- [WebAssembly (WASM)](start/builds/wasm.md)
----------------------
Rust Integration
================
- [Introduction](rust/index.md)
- [Traits](rust/traits.md)
- [Register a Rust Function](rust/functions.md)
- [Function Overloading](rust/overloading.md)
- [Generic Functions](rust/generic.md)
- [String Parameters](rust/strings.md)
- [`Dynamic` Parameters](rust/dynamic-args.md)
- [`Dynamic` Return Value](rust/dynamic-return.md)
- [Fallible Functions](rust/fallible.md)
- [`NativeCallContext`](rust/context.md)
- [Restore `NativeCallContext`](rust/context-restore.md)
- [Override a Built-in Function](rust/override.md)
- [Call a Rhai Function from Rust](engine/call-fn.md)
- [Create a Rust Closure from a Rhai Function](engine/func.md)
- [Operator Overloading](rust/operators.md)
- [Working with Any Rust Type](rust/custom-types.md)
- [Auto-Generate API](rust/derive-custom-type.md)
- [Custom Type Builder](rust/build-type.md)
- [Manually Register Custom Type](rust/reg-custom-type.md)
- [Methods](rust/methods.md)
- [Call Method as Function](rust/methods-fn-call.md)
- [Property Getters and Setters](rust/getters-setters.md)
- [Indexers](rust/indexers.md)
- [Fallback to Properties](rust/indexer-prop-fallback.md)
- [Collection Types](rust/collections.md)
- [Disable Custom Types](rust/disable-custom.md)
- [Printing Custom Types](rust/print-custom.md)
- [Modules](rust/modules/index.md)
- [Create in Rust](rust/modules/create.md)
- [Create from AST](rust/modules/ast.md)
- [Use a Module](rust/modules/use.md)
- [Module Resolvers](rust/modules/resolvers/index.md)
- [Built-in Module Resolvers](rust/modules/resolvers/built-in.md)
- [`DummyModuleResolver`](rust/modules/resolvers/dummy.md)
- [`FileModuleResolver`](rust/modules/resolvers/file.md)
- [`StaticModuleResolver`](rust/modules/resolvers/static.md)
- [`ModuleResolversCollection`](rust/modules/resolvers/collection.md)
- [`DylibModuleResolver` (external)](rust/modules/resolvers/dylib.md)
- [Custom Module Resolvers](rust/modules/resolvers/custom.md)
- [Self-Contained AST](rust/modules/self-contained.md)
- [Plugin Modules](plugins/index.md)
- [Packages](rust/packages/index.md)
- [Built-in Packages](rust/packages/builtin.md)
- [Create Custom Packages](rust/packages/create.md)
- [Create Packages as Crates](rust/packages/crate.md)
- [External Packages](lib/index.md)
- [Random Number Generation, Shuffling and Sampling](lib/rhai-rand.md)
- [Scientific Computing](lib/rhai-sci.md)
- [AI and Machine Learning](lib/rhai-ml.md)
- [Filesystem Access](lib/rhai-fs.md)
- [Working with Urls](lib/rhai-url.md)
----------------------
Scripting Language
==================
- [Comments](language/comments.md)
- [Doc-Comments](language/doc-comments.md)
- [Values and Types](language/values-and-types.md)
- [`Dynamic` Values](language/dynamic.md)
- [type_of()](language/type-of.md)
- [Interop with Rust](language/dynamic-rust.md)
- [Value Tag](language/dynamic-tag.md)
- [Serialization/Deserialization with `serde`](rust/serde.md)
- [Numbers](language/numbers.md)
- [Operators](language/num-op.md)
- [Standard Functions](language/num-fn.md)
- [Value Conversions](language/convert.md)
- [Ranges](language/ranges.md)
- [Bit-Fields](language/bit-fields.md)
- [Strings and Characters](language/strings-chars.md)
- [String Interpolation](language/string-interp.md)
- [`ImmutableString`](rust/immutable-string.md)
- [Standard Functions and Operators](language/string-fn.md)
- [Strings Interner](rust/strings-interner.md)
- [Arrays](language/arrays.md)
- [BLOB's (Byte Arrays)](language/blobs.md)
- [Out-of-Bounds Index](language/arrays-oob.md)
- [Object Maps](language/object-maps.md)
- [Parse from JSON](language/json.md)
- [Special Support for OOP](language/object-maps-oop.md)
- [Non-Existent Property](language/object-maps-missing-prop.md)
- [Timestamps](language/timestamps.md)
- [Keywords](language/keywords.md)
- [Statements](language/statements.md)
- [Statement Expression](language/statement-expression.md)
- [Variables](language/variables.md)
- [Variable Shadowing](language/shadow.md)
- [Strict Variables Mode](engine/strict-var.md)
- [Variable Definition Filter](engine/def-var.md)
- [Variable Resolver](engine/var.md)
- [Constants](language/constants.md)
- [Automatic Global Module](language/global.md)
- [Assignments](language/assignment.md)
- [Compound Assignments](language/assignment-op.md)
- [Logic Operators](language/logic.md)
- [In Operator](language/in.md)
- [If Statement](language/if.md)
- [Switch Statement](language/switch.md)
- [Switch Expression](language/switch-expression.md)
- [While Loop](language/while.md)
- [Do Loop](language/do.md)
- [Loop Statement](language/loop.md)
- [For Loop](language/for.md)
- [Standard Iterable Types](language/iter.md)
- [Make a Custom Type Iterable](language/iterator.md)
- [Return Value](language/return.md)
- [Throw Exception on Error](language/throw.md)
- [Catch Exceptions](language/try-catch.md)
- [Functions](language/functions.md)
- [Method Calls](language/fn-method.md)
- [Overloading](language/overload.md)
- [Namespaces](language/fn-namespaces.md)
- [Function Pointers](language/fn-ptr.md)
- [Currying](language/fn-curry.md)
- [Anonymous Functions](language/fn-anon.md)
- [Closures](language/fn-closure.md)
- [Metadata](engine/metadata/index.md)
- [Get Scripted Functions Metadata in Rhai](language/fn-metadata.md)
- [Get Scripted Functions Metadata from AST](rust/functions-metadata.md)
- [Get Native Function Signatures](engine/metadata/gen_fn_sig.md)
- [Export All Functions Metadata to JSON](engine/metadata/export_to_json.md)
- [Generate Definition Files for Language Server](engine/metadata/definitions.md)
- [Print and Debug](language/print-debug.md)
- [Modules](language/modules/index.md)
- [Export Variables, Functions and Sub-Modules from Script](language/modules/export.md)
- [Import Modules](language/modules/import.md)
- [Eval Function](language/eval.md)
----------------------
Safety and Protection
=====================
- [Introduction](safety/index.md)
- [Sand-Boxing](safety/sandbox.md)
- [Limiting Run Time](safety/progress.md)
- [Limiting Memory Usage](safety/memory.md)
- [Limiting Stack Usage](safety/stack.md)
- [Built-in Safety Limits](safety/limits.md)
- [Maximum Length of Strings](safety/max-string-size.md)
- [Maximum Size of Arrays](safety/max-array-size.md)
- [Maximum Size of Object Maps](safety/max-map-size.md)
- [Maximum Number of Operations](safety/max-operations.md)
- [Maximum Number of Variables](safety/max-variables.md)
- [Maximum Number of Functions](safety/max-functions.md)
- [Maximum Number of Modules](safety/max-modules.md)
- [Maximum Call Stack Depth](safety/max-call-stack.md)
- [Maximum Expression Depth](safety/max-stmt-depth.md)
- [Turn Off Safety Checks](safety/checked.md)
----------------------
Script Optimization
===================
- [Introduction](engine/optimize/index.md)
- [Optimization Passes](engine/optimize/passes.md)
- [Dead Code Elimination](engine/optimize/dead-code.md)
- [Constants Propagation](engine/optimize/constants.md)
- [Compound Assignment Rewrite](engine/optimize/rewrite.md)
- [Eager Operator Evaluation](engine/optimize/op-eval.md)
- [Eager Function Evaluation](engine/optimize/eager.md)
- [Side-Effect Considerations](engine/optimize/side-effects.md)
- [Volatility Considerations](engine/optimize/volatility.md)
- [Subtle Semantic Changes](engine/optimize/semantics.md)
- [Re-Optimize an `AST`](engine/optimize/reoptimize.md)
----------------------
Advanced Topics
===============
- [Manage AST's](engine/ast.md)
- [Low-Level API to Register Functions](rust/register-raw.md)
- [Evaluation Context](engine/eval-context.md)
- [Call Function Within Caller's Scope](language/fn-parent-scope.md)
- [Use Rhai in Dynamic Libraries](engine/dynamic-lib.md)
- [Use Rhai as a DSL](engine/dsl.md)
- [Remap Tokens During Parsing](engine/token-mapper.md)
- [Disable Keywords and/or Operators](engine/disable-keywords.md)
- [Disable Looping](engine/disable-looping.md)
- [Custom Operators](engine/custom-op.md)
- [Operator Precedence](engine/precedence.md)
- [Extend with Custom Syntax](engine/custom-syntax.md)
- [Custom Syntax Parsers](engine/custom-syntax-parsers.md)
- [Debugging Interface](engine/debugging/index.md)
- [Debugger](engine/debugging/debugger.md)
- [State](engine/debugging/state.md)
- [Call Stack](engine/debugging/call-stack.md)
- [Break-Points](engine/debugging/break-points.md)
- [Debugging Server](engine/debugging/server.md)
----------------------
Usage Patterns
==============
- [Object-Oriented Programming (OOP)](patterns/oop.md)
- [Scriptable Event Handler with State](patterns/events.md)
- [Main Style](patterns/events-1.md)
- [JS Style](patterns/events-2.md)
- [Map Style](patterns/events-3.md)
- [Control Layer Over Rust Backend](patterns/control.md)
- [Singleton Command Object](patterns/singleton.md)
- [Loadable Configuration](patterns/config.md)
- [Multi-Layered Functions](patterns/multi-layer.md)
- [Hot Reloading](patterns/hot-reload.md)
- [Builder Pattern / Fluent API](patterns/builder.md)
- [Objects with Defined Behaviors](patterns/objects.md)
- [Dynamic Constants Provider](patterns/dynamic-const.md)
- [Global Constants](patterns/constants.md)
- [Mutable Global State](patterns/global-mutable-state.md)
- [Working with Rust Enums](patterns/enums.md)
- [Simulate Macros to Simplify Scripts](patterns/macros.md)
- [One Engine Instance Per Call](patterns/parallel.md)
- [Multi-Threaded Synchronization](patterns/multi-threading.md)
- [Blocking/Async Function Calls](patterns/blocking.md)
- [External References (Unsafe)](patterns/references.md)
- [Static Hashing](patterns/static-hash.md)
- [Serialize an AST](patterns/serialize-ast.md)
- [Domain-Specific Tools](patterns/domain-tools.md)
- [Multiple Instantiation](patterns/multiple.md)
----------------------
Language Reference
==================
- [Introduction](ref/index.md)
- [Comments](ref/comments.md)
- [Value Types](ref/values-and-types.md)
- [`Dynamic` Values](ref/dynamic.md)
- [type_of()](ref/type-of.md)
- [Value Tag](ref/dynamic-tag.md)
- [Numbers](ref/numbers.md)
- [Operators](ref/num-op.md)
- [Standard Functions](ref/num-fn.md)
- [Value Conversions](ref/convert.md)
- [Ranges](ref/ranges.md)
- [Bit-Fields](ref/bit-fields.md)
- [Strings and Characters](ref/strings-chars.md)
- [Standard Functions and Operators](ref/string-fn.md)
- [Arrays](ref/arrays.md)
- [BLOB's (Byte Arrays)](ref/blobs.md)
- [Object Maps](ref/object-maps.md)
- [Timestamps](ref/timestamps.md)
- [Keywords](ref/keywords.md)
- [Statements](ref/statements.md)
- [Variables](ref/variables.md)
- [Constants](ref/constants.md)
- [Assignments](ref/assignment.md)
- [Compound Assignments](ref/assignment-op.md)
- [Operators](ref/operators.md)
- [Indexing](ref/indexing.md)
- [Properties](ref/getters-setters.md)
- [Methods](ref/methods.md)
- [If Statement](ref/if.md)
- [Switch Statement](ref/switch.md)
- [While Loop](ref/while.md)
- [Do Loop](ref/do.md)
- [Infinite Loop](ref/loop.md)
- [For Loop](ref/for.md)
- [Return Value](ref/return.md)
- [Throw Exception on Error](ref/throw.md)
- [Catch Exceptions](ref/try-catch.md)
- [Functions](ref/functions.md)
- [Method Calls](ref/fn-method.md)
- [Overloading](ref/overload.md)
- [Function Pointers](ref/fn-ptr.md)
- [Closures](ref/fn-closure.md)
- [Metadata](ref/fn-metadata.md)
- [Print and Debug](ref/print-debug.md)
- [Modules](ref/modules/index.md)
- [Export Variables, Functions and Sub-Modules from Script](ref/modules/export.md)
- [Import Modules](ref/modules/import.md)
- [Eval Function](ref/eval.md)
----------------------
Appendix
========
- [External Tools](tools/index.md)
- [Online Playground](tools/playground.md)
- [Language Server](tools/lsp.md)
- [`rhai-doc`](tools/rhai-doc.md)
- [Dynamic Loadable Libraries](lib/rhai-dylib.md)
- [Generate MarkDown/MDX API Documentation](lib/rhai-autodocs.md)
- [Keywords](appendix/keywords.md)
- [Operators and Symbols](appendix/operators.md)
- [Literals](appendix/literals.md)

View File

@ -0,0 +1,95 @@
Features of Rhai
================
{{#include ../links.md}}
```admonish success "Easy"
* Simple language similar to JavaScript+Rust with dynamic typing.
* Tight integration with native Rust [functions] and [types][custom types] including
[getters/setters], [methods] and [indexers].
* Freely pass Rust values into a script as [variables]/[constants] via an external [`Scope`] –
all clonable Rust types are supported seamlessly without the need to implement any special trait.
* Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust.
* Very few additional dependencies – right now only [`smallvec`](https://crates.io/crates/smallvec),
[`thin-vec`](https://crates.io/crates/thin-vec), [`num-traits`](https://crates.io/crates/num-traits),
[`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`];
for [`no-std`] and [WASM] builds, a number of additional dependencies are pulled in to provide for missing functionalities.
* [Plugins] system powered by procedural macros simplifies custom API development.
```
```admonish danger "Fast"
* 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)
(also see [benchmarks](benchmarks.md)).
* Compile once to [AST][`AST`] for repeated evaluations.
* Scripts are [optimized][script optimization] – useful for template-based machine-generated scripts.
```
```admonish tip "Dynamic"
* [Function overloading]({{rootUrl}}/language/overload.md).
* [Operator overloading]({{rootUrl}}/rust/operators.md).
* Organize code base with dynamically-loadable [modules], optionally overriding the
[resolution][module resolver] process.
* Dynamic dispatch via [function pointers] with additional support for [currying].
* [Closures] that can capture shared variables.
* Some support for [object-oriented programming (OOP)][OOP].
* Hook into variables access via a [variable resolver], or control definition of variables via a
[variable definition filter].
```
```admonish warning "Safe"
* Relatively little `unsafe` code – yes there are some for performance reasons.
* Sand-boxed – the scripting [`Engine`], if declared immutable, cannot mutate the containing
environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md).
* Passes Miri.
```
```admonish bug "Rugged"
* [_Don't Panic_][safety] guarantee – Any panic is a bug. It never panics the host system.
* Protected against malicious attacks – such as [stack-overflow][maximum call stack depth],
[over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations]
etc. – that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress] and manually terminate a script run.
```
```admonish example "Flexible"
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
* Support for [`Decimal`][rust_decimal] numbers.
* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde).
* Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM].
* Surgically [disable keywords and operators] to restrict the language.
* Use as a [DSL] by defining [custom operators] and/or extending the language with [custom syntax].
* A [debugging][debugger] interface provides powerful debugging support.
```

View File

@ -0,0 +1,44 @@
Introduction to Rhai
====================
{{#include ../links.md}}
Trivia
------
```admonish question "Etymology of the name \\"Rhai\\""
In the beginning there was [ChaiScript](http://chaiscript.com),
which is an embedded scripting language for C++.
Originally it was intended to be a scripting language similar to **JavaScript**.
With java being a kind of hot beverage, the new language was named after
another hot beverage – [**Chai**](https://en.wikipedia.org/wiki/Chai),
which is the word for "tea" in many world languages and, in particular,
a popular kind of [spicy milk tea consumed in India](https://en.wikipedia.org/wiki/Masala_chai).
Later, when the novel implementation technique behind ChaiScript was ported from C++ to Rust,
logically the `C` was changed to an `R` to make it "RhaiScript", or just "Rhai".
– Rhai author [Johnathan Turner](https://github.com/jntrnr)
```
```admonish question "Origin of the Rhai logo"
<div style="float:right;text-align:center;width:20%">
<img src="{{rootUrl}}/images/logo/rhai_logo_old.png" title="Prototype Rhai logo">
<div style="font-size:60%">Original prototype</div>
</div>
One of Rhai's maintainers, [`@schungx`](https://github.com/schungx), was thinking about a logo
when he accidentally came across a copy of _Catcher in the Rye_ in a restaurant, and drew the
first prototype version of the logo.
Then [`@semirix`](https://github.com/semirix) refined it to the current version.
```
~~~admonish question "The \`rhai.rs\` domain"
[`@yrashk`](https://github.com/yrashk) sponsored the domain [`rhai.rs`](https://rhai.rs).
~~~

View File

@ -0,0 +1,77 @@
Keywords List
=============
{{#include ../links.md}}
| Keyword | Description | Inactive under | Is function? |
| :-------------------: | ----------------------------------------------------------------------- | :-----------------------------: | :----------: |
| `true` | boolean true literal | | **no** |
| `false` | boolean false literal | | **no** |
| `let` | [variable] declaration | | **no** |
| `const` | [constant] declaration | | **no** |
| `if` | [`if`] statement | | **no** |
| `else` | `else` block of [`if`] statement | | **no** |
| `switch` | [matching][`switch`] | | **no** |
| `do` | [looping][`do`] | | **no** |
| `while` | <ol><li>[`while`] loop</li><li>condition for [`do`] loop</li></ol> | | **no** |
| `until` | [`do`] loop | | **no** |
| `loop` | infinite [`loop`] | | **no** |
| `for` | [`for`] loop | | **no** |
| `in` | <ol><li>[containment][`in`] test</li><li>part of [`for`] loop</li></ol> | | **no** |
| `!in` | negated [containment][`in`] test | | **no** |
| `continue` | continue a loop at the next iteration | | **no** |
| `break` | break out of loop iteration | | **no** |
| `return` | [return][`return`] value | | **no** |
| `throw` | throw [exception] | | **no** |
| `try` | [trap][`try`] [exception] | | **no** |
| `catch` | [catch][`catch`] [exception] | | **no** |
| `import` | [import][`import`] [module] | [`no_module`] | **no** |
| `export` | [export][`export`] [variable] | [`no_module`] | **no** |
| `as` | alias for [variable] [export][`export`] | [`no_module`] | **no** |
| `global` | automatic [global][`global`] [module] | [`no_module`], [`no_function`] | **no** |
| `private` | mark [function] [private][`private`] | [`no_function`] | **no** |
| `fn` (lower-case `f`) | [function] definition | [`no_function`] | **no** |
| `Fn` (capital `F`) | create a [function pointer] | | yes |
| `call` | call a [function pointer] | | yes |
| `curry` | curry a [function pointer] | | yes |
| `is_shared` | is a [variable] shared? | [`no_function`], [`no_closure`] | yes |
| `is_def_fn` | is [function] defined? | [`no_function`] | yes |
| `is_def_var` | is [variable] defined? | | yes |
| `this` | reference to base object for method call | [`no_function`] | **no** |
| `type_of` | get type name of value | | yes |
| `print` | print value | | yes |
| `debug` | print value in debug format | | yes |
| `eval` | evaluate script | | yes |
Reserved Keywords
-----------------
| Keyword | Potential usage |
| :---------: | --------------------- |
| `var` | variable declaration |
| `static` | variable declaration |
| `shared` | share value |
| `goto` | control flow |
| `match` | matching |
| `case` | matching |
| `public` | function/field access |
| `protected` | function/field access |
| `new` | constructor |
| `use` | import namespace |
| `with` | scope |
| `is` | type check |
| `module` | module |
| `package` | package |
| `super` | base class/module |
| `thread` | threading |
| `spawn` | threading |
| `go` | threading |
| `await` | async |
| `async` | async |
| `sync` | async |
| `yield` | async |
| `default` | special value |
| `void` | special value |
| `null` | special value |
| `nil` | special value |

View File

@ -0,0 +1,20 @@
Literals Syntax
===============
{{#include ../links.md}}
| Type | Literal syntax |
| :------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------- |
| `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
| `FLOAT`,<br/>[`Decimal`][rust_decimal] (requires [`no_float`]+[`decimal`]) | `42.0`, `-123.456`, `123.`, `123.456e-10` |
| [Ranges] in [`switch`] cases | `-10..10` (exclusive), `0..=50` (inclusive) |
| Normal [string] | `"... \x?? \u???? \U???????? ..."` |
| [String] with continuation | `"this is the first line\`<br/>`second line\`<br/>`the third line"` |
| Multi-line literal [string] | `` `this is the first line``<br/>``second line``</br>``the last line` `` |
| Multi-line literal [string] with interpolation | `` `this is the first field: ${obj.field1}``<br/>``second field: {obj.field2}``</br>``the last field: ${obj.field3}` `` |
| [Character] | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
| [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` |
| Boolean false | `false` |
| `Nothing`/`null`/`nil`/`void`/Unit | `()` |

View File

@ -0,0 +1,86 @@
Operators and Symbols
=====================
{{#include ../links.md}}
Operators
---------
| Operator | Description | Binary? | Binding direction |
| :------------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: |
| `+` | add | yes | left |
| `-` | 1) subtract<br/>2) negative (prefix) | yes<br/>no | left<br/>right |
| `*` | multiply | yes | left |
| `/` | divide | yes | left |
| `%` | modulo | yes | left |
| `**` | power/exponentiation | yes | right |
| `>>` | right bit-shift | yes | left |
| `<<` | left bit-shift | yes | left |
| `&` | 1) bit-wise _AND_<br/>2) boolean _AND_ | yes | left |
| <code>\|</code> | 1) bit-wise _OR_<br/>2) boolean _OR_ | yes | left |
| `^` | 1) bit-wise _XOR_<br/>2) boolean _XOR_ | yes | left |
| `=`, `+=`, `-=`, `*=`, `/=`,<br/>`**=`, `%=`, `<<=`, `>>=`, `&=`,<br/><code>\|=</code>, `^=` | assignments | yes | n/a |
| `==` | equals to | yes | left |
| `!=` | not equals to | yes | left |
| `>` | greater than | yes | left |
| `>=` | greater than or equals to | yes | left |
| `<` | less than | yes | left |
| `<=` | less than or equals to | yes | left |
| `&&` | boolean _AND_ (short-circuits) | yes | left |
| <code>\|\|</code> | boolean _OR_ (short-circuits) | yes | left |
| `??` | null-coalesce (short-circuits) | yes | left |
| `!` | boolean _NOT_ | **no** | right |
| `[` ... `]`, `?[` ... `]` | indexing | yes | left |
| `.`, `?.` | 1) property access<br/>2) method call | yes | left |
| `..` | exclusive range | yes | left |
| `..=` | inclusive range | yes | left |
Symbols and Patterns
--------------------
| Symbol | Name | Description |
| :---------------------------------: | ---------------------------------- | ------------------------------------- |
| `_` | underscore | default `switch` case |
| `;` | semicolon | statement separator |
| `,` | comma | list separator |
| `:` | colon | [object map] property value separator |
| `::` | path | module path separator |
| `#{` ... `}` | hash map | [object map] literal |
| `"` ... `"` | double quote | [string] |
| `` ` `` ... `` ` `` | back-tick | multi-line literal [string] |
| `'` ... `'` | single quote | [character] |
| `\` | 1) escape<br/>2) line continuation | escape character literal |
| `()` | unit | null value |
| `(` ... `)` | parentheses | expression grouping |
| `{` ... `}` | braces | block statement |
| <code>\|</code> ... <code>\|</code> | pipes | closure |
| `[` ... `]` | brackets | [array] literal |
| `!` | bang | function call in calling scope |
| `=>` | double arrow | `switch` expression case separator |
| `//` | comment | line comment |
| `///` | doc-comment | line [doc-comment] |
| `//!` | module doc | [module documentation][comments] |
| `/*` ... `*/` | comment | block comment |
| `/**` ... `*/` | doc-comment | block [doc-comment] |
| `(*` ... `*)` | comment | _reserved_ |
| `#!` | shebang | _reserved_ |
| `++` | increment | _reserved_ |
| `--` | decrement | _reserved_ |
| `...` | rest | _reserved_ |
| `~` | tilde | _reserved_ |
| `!.` | | _reserved_ |
| `?` | question | _reserved_ |
| `#` | hash | _reserved_ |
| `@` | at | _reserved_ |
| `$` | dollar | _reserved_ |
| `->` | arrow | _reserved_ |
| `<-` | left arrow | _reserved_ |
| <code><\|</code> | left triangle | _reserved_ |
| <code>\|></code> | right triangle | _reserved_ |
| `===` | strict equals to | _reserved_ |
| `!==` | strict not equals to | _reserved_ |
| `:=` | assignment | _reserved_ |
| `:;` | typo to `::` | _reserved_ |
| `::<` ... `>` | turbofish | _reserved_ |

View File

@ -0,0 +1,5 @@
version = "1.21.0"
repoHome = "https://github.com/rhaiscript/rhai/blob/main"
rootUrl = ""
#rootUrl = "/book"
#rootUrl = "/book/vnext"

View File

@ -0,0 +1,164 @@
Manage `AST`'s
==============
{{#include ../links.md}}
When compiling a Rhai script to an [`AST`], the following data are packaged together as a single unit:
| Data | Type | Description | Requires feature | Access API |
| -------------------------------- | :---------------------------------------: | ------------------------------------------------------------------------------------------------- | :------------------------------------: | :------------------------------------------------------------------------------------------------------------: |
| Source name | [`ImmutableString`] | optional text name to identify the source of the script | | `source(&self)`,<br/>`clone_source(&self)`,<br/>`set_source(&mut self, source)`,<br/>`clear_source(&mut self)` |
| [Module documentation][comments] | [`Vec<SmartString>`][`SmartString`] | documentation of the script | [`metadata`] | `doc(&self)`,<br/>`clear_doc(&mut self)` |
| Statements | `Vec<Stmt>` | list of script statements at global level | [`internals`] | `statements(&self)`,<br/>`statements_mut(&mut self)` |
| Functions | [`Shared<Module>`][`Module`] | [functions] defined in the script | [`internals`],<br/>not [`no_function`] | `shared_lib(&self)` |
| Embedded [module resolver] | [`StaticModuleResolver`][module resolver] | embedded [module resolver] for [self-contained `AST`]({{rootUrl}}/rust/modules/self-contained.md) | [`internals`],<br/>not [`no_module`] | `resolver(&self)` |
Most of the [`AST`] API is available only under the [`internals`] feature.
```admonish tip.small "Tip: Source name"
Use the source name to identify the source script in errors &ndash; useful when multiple [modules]
are imported recursively.
```
~~~admonish info.small "`AST` public API"
For the complete [`AST`] API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.AST.html) online.
~~~
Extract Only Functions
----------------------
The following methods, not available under [`no_function`], allow manipulation of the [functions]
encapsulated within an [`AST`]:
| Method | Description |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `clone_functions_only(&self)` | clone the [`AST`] into a new [`AST`] with only [functions], excluding statements |
| `clone_functions_only_filtered(&self, filter)` | clone the [`AST`] into a new [`AST`] with only [functions] that pass the filter predicate, excluding statements |
| `retain_functions(&mut self, filter)` | remove all [functions] in the [`AST`] that do not pass a particular predicate filter; statements are untouched |
| `iter_functions(&self)` | return an iterator on all the [functions] in the [`AST`] |
| `clear_functions(&mut self)` | remove all [functions] from the [`AST`], leaving only statements |
Extract Only Statements
-----------------------
The following methods allow manipulation of the statements in an [`AST`]:
| Method | Description |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `clone_statements_only(&self)` | clone the [`AST`] into a new [`AST`] with only the statements, excluding [functions] |
| `clear_statements(&mut self)` | remove all statements from the [`AST`], leaving only [functions] |
| `iter_literal_variables(&self, constants, variables)` | return an iterator on all top-level literal constant and/or variable definitions in the [`AST`] |
Merge and Combine AST's
-----------------------
The following methods merge one [`AST`] with another:
| Method | Description |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `merge(&self, &ast)`,<br />`+` operator | append the second [`AST`] to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
| `merge_filtered(&self, &ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
| `combine(&mut self, ast)`,<br />`+=` operator | append the second [`AST`] to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
| `combine_filtered(&mut self, ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
When statements are appended, beware that this may change the semantics of the script.
```rust
// First script
let ast1 = engine.compile(
"
fn foo(x) { 42 + x }
foo(1)
")?;
// Second script
let ast2 = engine.compile(
"
fn foo(n) { `hello${n}` }
foo("!")
")?;
// Merge them
let merged = ast1.merge(&ast2);
// Notice that using the '+' operator also works:
let merged = &ast1 + &ast2;
```
`merged` in the above example essentially contains the following script program:
```js
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!"
```
Walk an AST
-----------
The [`internals`] feature allows access to internal Rhai data structures, particularly the nodes
that make up the [`AST`].
### AST node types
There are a few useful types when walking an [`AST`]:
| Type | Description |
| ------------ | ----------------------------------------------------------------- |
| `ASTNode` | an `enum` with two variants: `Expr` or `Stmt` |
| `Expr` | an _expression_ |
| `Stmt` | a _statement_ |
| `BinaryExpr` | a sub-type containing the LHS and RHS of a binary expression |
| `FnCallExpr` | a sub-type containing information on a function call |
| `CustomExpr` | a sub-type containing information on a [custom syntax] expression |
The `AST::walk` method takes a callback function and recursively walks the [`AST`] in depth-first
manner, with the parent node visited before its children.
### Callback function signature
The signature of the callback function takes the following form.
> ```rust
> FnMut(&[ASTNode]) -> bool
> ```
The single argument passed to the method contains a slice of `ASTNode` types representing the path
from the current node to the root of the [`AST`].
Return `true` to continue walking the [`AST`], or `false` to terminate.
### Children visit order
The order of visits to the children of each node type:
| Node type | Children visit order |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`if`] statement | <ol><li>condition expression</li><li>_then_ statements</li><li>_else_ statements (if any)</li></ol> |
| [`switch`] statement | <ol><li>match element</li><li>each of the case conditions and statements, in order</li><li>each of the range conditions and statements, in order</li><li>default statements (if any)</li></ol> |
| [`while`], [`do`], [`loop`] statement | <ol><li>condition expression</li><li>statements body</li></ol> |
| [`for`] statement | <ol><li>collection expression</li><li>statements body</li></ol> |
| [`return`] statement | return value expression |
| [`throw`] statement | exception value expression |
| [`try` ... `catch`][exception] statement | <ol><li>`try` statements body</li><li>`catch` statements body</li></ol> |
| [`import`] statement | path expression |
| [Array] literal | each of the element expressions, in order |
| [Object map] literal | each of the element expressions, in order |
| Interpolated [string] | each of the [string]/expression segments, in order |
| Indexing | <ol><li>LHS expression</li><li>RHS (index) expression</li></ol> |
| Field access/method call | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
| `&&`, <code>\|\|</code>, `??` | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
| [Function] call, [operator] expression | each of the argument expressions, in order |
| [`let`][variable], [`const`][constant] statement | value expression |
| Assignment statement | <ol><li>l-value expression</li><li>value expression</li></ol> |
| Statements block | each of the statements, in order |
| Custom syntax expression | each of the inputs stream, in order |
| All others | single child (if any) |

View File

@ -0,0 +1,24 @@
Built-in Operators
==================
{{#include ../links.md}}
The following operators are built-in, meaning that they are always available, even when using a [raw `Engine`].
All built-in operators are binary, and are supported for both operands of the same type.
| Operators | Assignment operators | Supported types<br/>(see [standard types]) |
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `+`, | `+=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li></ul> |
| `-`, `*`, `/`, `%`, `**`, | `-=`, `*=`, `/=`, `%=`, `**=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li></ul> |
| `<<`, `>>` | `<<=`, `>>=` | <ul><li>`INT`</li></ul> |
| `&`, <code>\|</code>, `^` | `&=`, <code>\|=</code>, `^=` | <ul><li>`INT` (bit-wise)</li><li>`bool` (non-short-circuiting)</li></ul> |
| `&&`, <code>\|\|</code> | | <ul><li>`bool` (short-circuits)</li></ul> |
| `==`, `!=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`bool`</li><li>`char`</li><li>[string]</li><li>[BLOB]</li><li>numeric [range]</li><li>`()`</li></ul> |
| `>`, `>=`, `<`, `<=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li><li>`()`</li></ul> |
```admonish tip.small
`FLOAT` and [`Decimal`][rust_decimal] also inter-operate with `INT`, while [strings] inter-operate
with [characters][string] for certain operators (e.g. `+`).
```

View File

@ -0,0 +1,275 @@
Call Rhai Functions from Rust
=============================
{{#include ../links.md}}
Rhai also allows working _backwards_ from the other direction &ndash; i.e. calling a Rhai-scripted
[function] from Rust via `Engine::call_fn`.
```rust
┌─────────────┐
│ Rhai script │
└─────────────┘
import "process" as proc; // this is evaluated every time
fn hello(x, y) {
// hopefully 'my_var' is in scope when this is called
x.len + y + my_var
}
fn hello(x) {
// hopefully 'my_string' is in scope when this is called
x * my_string.len()
}
fn hello() {
// hopefully 'MY_CONST' is in scope when this is called
if MY_CONST {
proc::process_data(42); // can access imported module
}
}
┌──────┐
│ Rust │
└──────┘
// Compile the script to AST
let ast = engine.compile(script)?;
// Create a custom 'Scope'
let mut scope = Scope::new();
// A custom 'Scope' can also contain any variables/constants available to
// the functions
scope.push("my_var", 42_i64);
scope.push("my_string", "hello, world!");
scope.push_constant("MY_CONST", true);
// Evaluate a function defined in the script, passing arguments into the
// script as a tuple.
//
// Beware, arguments must be of the correct types because Rhai does not
// have built-in type conversions. If arguments of the wrong types are passed,
// the Engine will not find the function.
//
// Variables/constants pushed into the custom 'Scope'
// (i.e. 'my_var', 'my_string', 'MY_CONST') are visible to the function.
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
// ^^^ ^^^^^^^^^^^^^^^^^^
// return type must be specified put arguments in a tuple
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( 123_i64, ) )?;
// ^^^^^^^^^^^^ tuple of one
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", () )?;
// ^^ unit = tuple of zero
```
~~~admonish danger.small "Warning: Functions with one parameter"
Functions with only one single parameter is easy to get wrong.
The proper Rust syntax is a _tuple with one item_:
```rust
( arg , )
```
Notice the comma (`,`) after the argument. Without it, the expression is a single value
`(arg)` which is the same as `arg` and not a tuple.
A syntax error with very confusing error message will be generated by the Rust compiler
if the comma is omitted.
~~~
~~~admonish warning.small "Default behavior"
When using `Engine::call_fn`, the [`AST`] is always evaluated _before_ the [function] is called.
This is usually desirable in order to [import][`import`] the necessary external [modules] that are
needed by the [function].
All new [variables]/[constants] introduced are, by default, _not_ retained inside the [`Scope`].
In other words, the [`Scope`] is _rewound_ before each call.
If these default behaviors are not desirable, override them with `Engine::call_fn_with_options`.
~~~
`FuncArgs` Trait
----------------
```admonish note.side
Rhai implements [`FuncArgs`][traits] for tuples, arrays and `Vec<T>`.
```
`Engine::call_fn` takes a parameter of any type that implements the [`FuncArgs`][traits] trait,
which is used to parse a data type into individual argument values for the [function] call.
Custom types (e.g. structures) can also implement [`FuncArgs`][traits] so they can be used for
calling `Engine::call_fn`.
```rust
use std::iter::once;
use rhai::FuncArgs;
// A struct containing function arguments
struct Options {
pub foo: bool,
pub bar: String,
pub baz: i64
}
impl FuncArgs for Options {
fn parse<C: Extend<Dynamic>>(self, container: &mut C) {
container.extend(once(self.foo.into()));
container.extend(once(self.bar.into()));
container.extend(once(self.baz.into()));
}
}
let options = Options { foo: true, bar: "world", baz: 42 };
// The type 'Options' can now be used as argument to 'call_fn'
// to call a function with three parameters: fn hello(foo, bar, baz)
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", options)?;
```
```admonish warning.small "Warning: You don't need this"
Implementing `FuncArgs` is almost never needed because Rhai works directly with
any [custom type].
It is used only in niche cases where a [custom type's][custom type] fields need
to be split up to pass to functions.
```
`Engine::call_fn_with_options`
------------------------------
For more control, use `Engine::call_fn_with_options`, which takes a type `CallFnOptions`:
```rust
use rhai::{Engine, CallFnOptions};
let options = CallFnOptions::new()
.eval_ast(false) // do not evaluate the AST
.rewind_scope(false) // do not rewind the scope (i.e. keep new variables)
.bind_this_ptr(&mut state); // 'this' pointer
let result = engine.call_fn_with_options::<i64>(
options, // options
&mut scope, // scope to use
&ast, // AST containing the functions
"hello", // function entry-point
( "abc", 123_i64 ) // arguments
)?;
```
`CallFnOptions` allows control of the following:
| Field | Type | Default | Build method | Description |
| -------------- | :---------------------------------: | :-----: | :-------------: | --------------------------------------------------------------------------------------------------------- |
| `eval_ast` | `bool` | `true` | `eval_ast` | evaluate the [`AST`] before calling the target [function] (useful to run [`import` statements]) |
| `rewind_scope` | `bool` | `true` | `rewind_scope` | rewind the custom [`Scope`] at the end of the [function] call so new local variables are removed |
| `this_ptr` | [`Option<&mut Dynamic>`][`Dynamic`] | `None` | `bind_this_ptr` | bind the `this` pointer to a specific value |
| `tag` | [`Option<Dynamic>`][`Dynamic`] | `None` | `with_tag` | set the _custom state_ for this evaluation (accessed via [`NativeCallContext::tag`][`NativeCallContext`]) |
### Skip evaluation of the `AST`
By default, the [`AST`] is evaluated before calling the target [function].
This is necessary to make sure that necessary [modules] imported via [`import`] statements are available.
Setting `eval_ast` to `false` skips this evaluation.
### Keep new variables/constants
By default, the [`Engine`] _rewinds_ the custom [`Scope`] after each call to the initial size,
so any new [variable]/[constant] defined are cleared and will not spill into the custom [`Scope`].
This prevents the [`Scope`] from being continuously polluted by new [variables] and is usually the
intuitively expected behavior.
Setting `rewind_scope` to `false` retains new [variables]/[constants] within the custom [`Scope`].
This allows the [function] to easily pass values back to the caller by leaving them inside the
custom [`Scope`].
~~~admonish warning.small "Warning: new variables persist in `Scope`"
If the [`Scope`] is not rewound, beware that all [variables]/[constants] defined at top level of the
[function] or in the script body will _persist_ inside the custom [`Scope`].
If any of them are temporary and not intended to be retained, define them inside a statements block
(see example below).
~~~
```rust
┌─────────────┐
│ Rhai script │
└─────────────┘
fn initialize() {
let x = 42; // 'x' is retained
let y = x * 2; // 'y' is retained
// Use a new statements block to define temp variables
{
let temp = x + y; // 'temp' is NOT retained
foo = temp * temp; // 'foo' is visible in the scope
}
}
let foo = 123; // 'foo' is retained
// Use a new statements block to define temp variables
{
let bar = foo / 2; // 'bar' is NOT retained
foo = bar * bar;
}
┌──────┐
│ Rust │
└──────┘
let options = CallFnOptions::new().rewind_scope(false);
engine.call_fn_with_options(options, &mut scope, &ast, "initialize", ())?;
// At this point, 'scope' contains these variables: 'foo', 'x', 'y'
```
### Bind the `this` pointer
```admonish note.side
`Engine::call_fn` cannot call functions in _method-call_ style.
```
`CallFnOptions` can also bind a value to the `this` pointer of a script-defined [function].
It is possible, then, to call a [function] that uses `this`.
```rust
let ast = engine.compile("fn action(x) { this += x; }")?;
let mut value: Dynamic = 1_i64.into();
let options = CallFnOptions::new()
.eval_ast(false)
.rewind_scope(false)
.bind_this_ptr(&mut value);
engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?;
assert_eq!(value.as_int()?, 42);
```

View File

@ -0,0 +1,160 @@
Compile a Script (to AST)
=========================
{{#include ../links.md}}
To repeatedly evaluate a script, _compile_ it first with `Engine::compile` into an `AST`
(**A**bstract **S**yntax **T**ree) form.
`Engine::eval_ast_XXX` and `Engine::run_ast_XXX` evaluate a pre-compiled `AST`.
```rust
// Compile to an AST and store it for later evaluations
let ast = engine.compile("40 + 2")?;
for _ in 0..42 {
let result: i64 = engine.eval_ast(&ast)?;
println!("Answer #{i}: {result}"); // prints 42
}
```
~~~admonish tip.small "Tip: Compile script file"
Compiling script files is also supported via `Engine::compile_file`
(not available for [`no_std`] or [WASM] builds).
```rust
let ast = engine.compile_file("hello_world.rhai".into())?;
```
~~~
~~~admonish info.small "See also: `AST` manipulation API"
Advanced users who may want to manipulate an `AST`, especially the functions contained within,
should see the section on [_Manage AST's_](ast.md) for more details.
~~~
Practical Use &ndash; Header Template Scripts
---------------------------------------------
Sometimes it is desirable to include a standardized _header template_ in a script that contains
pre-defined [functions], [constants] and [imported][`import`] [modules].
```rust
// START OF THE HEADER TEMPLATE
// The following should run before every script...
import "hello" as h;
import "world" as w;
// Standard constants
const GLOBAL_CONSTANT = 42;
const SCALE_FACTOR = 1.2;
// Standard functions
fn foo(x, y) { ... }
fn bar() { ... }
fn baz() { ... }
// END OF THE HEADER TEMPLATE
// Everything below changes from run to run
foo(bar() + GLOBAL_CONSTANT, baz() * SCALE_FACTOR)
```
### Option 1 &ndash; The easy way
Prepend the script header template onto independent scripts and run them as a whole.
> **Pros:** Easy!
>
> **Cons:** If the header template is long, work is duplicated every time to parse it.
```rust
let header_template = "..... // scripts... .....";
for index in 0..10000 {
let user_script = db.get_script(index);
// Just merge the two scripts...
let combined_script = format!("{header_template}\n{user_script}\n");
// Run away!
let result = engine.eval::<i64>(combined_script)?;
println!("{result}");
}
```
### Option 2 &ndash; The hard way
Option 1 requires the script header template to be recompiled every time. This can be expensive if
the header is very long.
This option compiles both the script header template and independent scripts as separate `AST`'s
which are then joined together to form a combined `AST`.
> **Pros:** No need to recompile the header template!
>
> **Cons:** More work...
```rust
let header_template = "..... // scripts... .....";
let mut template_ast = engine.compile(header_template)?;
// If you don't want to run the template, only keep the functions
// defined inside (e.g. closures), clear out the statements.
template_ast.clear_statements();
for index in 0..10000 {
let user_script = db.get_script(index);
let user_ast = engine.compile(user_script)?;
// Merge the two AST's
let combined_ast = template_ast + user_ast;
// Run away!
let result = engine.eval_ast::<i64>(combined_ast)?;
println!("{result}");
```
### Option 3 &ndash; The not-so-hard way
Option 1 does repeated work, option 2 requires manipulating `AST`'s...
This option makes the scripted [functions] (not [imported][`import`] [modules] nor [constants]
however) available globally by first making it a [module] (via [`Module::eval_ast_as_new`](modules/ast.md))
and then loading it into the [`Engine`] via `Engine::register_global_module`.
> **Pros:** No need to recompile the header template!
>
> **Cons:** No [imported][`import`] [modules] nor [constants]; if the header template is changed, a new [`Engine`] must be created.
```rust
let header_template = "..... // scripts... .....";
let template_ast = engine.compile(header_template)?;
let template_module = Module::eval_ast_as_new(Scope::new(), &template_ast, &engine)?;
engine.register_global_module(template_module.into());
for index in 0..10000 {
let user_script = db.get_script(index);
// Run away!
let result = engine.eval::<i64>(user_script)?;
println!("{result}");
}
```

View File

@ -0,0 +1,97 @@
Custom Operators
================
{{#include ../links.md}}
```admonish info.side "See also"
See [this section][precedence] for details on operator [precedence].
```
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
customized operators performing specific logic.
`Engine::register_custom_operator` registers a [keyword] as a custom operator, giving it a particular
_[precedence]_ (which cannot be zero).
Support for custom operators can be disabled via the [`no_custom_syntax`] feature.
Example
-------
```rust
use rhai::Engine;
let mut engine = Engine::new();
// Register a custom operator '#' and give it a precedence of 160
// (i.e. between +|- and *|/)
// Also register the implementation of the custom operator as a function
engine.register_custom_operator("#", 160)?
.register_fn("#", |x: i64, y: i64| (x * y) - (x + y));
// The custom operator can be used in expressions
let result = engine.eval_expression::<i64>("1 + 2 * 3 # 4 - 5 / 6")?;
// ^ custom operator
// The above is equivalent to: 1 + ((2 * 3) # 4) - (5 / 6)
result == 15;
```
Alternatives to a Custom Operator
---------------------------------
Custom operators are merely _syntactic sugar_. They map directly to registered functions.
```rust
let mut engine = Engine::new();
// Define 'foo' operator
engine.register_custom_operator("foo", 160)?;
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // use custom operator
engine.eval::<i64>("1 + foo(2 * 3, 4) - 5 / 6")?; // <- above is equivalent to this
```
A script using custom operators can always be pre-processed, via a pre-processor application,
into a syntax that uses the corresponding function calls.
Using `Engine::register_custom_operator` merely enables a convenient shortcut.
Must be a Valid Identifier or Reserved Symbol
---------------------------------------------
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols),
[disabled operators or keywords][disable keywords and operators].
```rust
engine.register_custom_operator("foo", 20)?; // 'foo' is a valid custom operator
engine.register_custom_operator("#", 20)?; // the reserved symbol '#' is also
// a valid custom operator
engine.register_custom_operator("+", 30)?; // <- error: '+' is an active operator
engine.register_custom_operator("=>", 30)?; // <- error: '=>' is an active symbol
```
Binary Operators Only
---------------------
All custom operators must be _binary_ (i.e. they take two operands).
_Unary_ custom operators are not supported.
```rust
// Register unary '#' operator
engine.register_custom_operator("#", 160)?
.register_fn("#", |x: i64| x * x);
engine.eval::<i64>("# 42")?; // <- syntax error
```

View File

@ -0,0 +1,246 @@
Really Advanced &ndash; Custom Parsers
======================================
{{#include ../links.md}}
Sometimes it is desirable to have multiple [custom syntax] starting with the same symbol.
This is especially common for _command-style_ syntax where the second symbol calls a particular command:
```rust
// The following simulates a command-style syntax, all starting with 'perform'.
perform hello world; // A fixed sequence of symbols
perform action 42; // Perform a system action with a parameter
perform update system; // Update the system
perform check all; // Check all system settings
perform cleanup; // Clean up the system
perform add something; // Add something to the system
perform remove something; // Delete something from the system
```
Alternatively, a [custom syntax] may have variable length, with a termination symbol:
```rust
// The following is a variable-length list terminated by '>'
tags < "foo", "bar", 123, ... , x+y, true >
```
For even more flexibility in order to handle these advanced use cases, there is a
_low level_ API for [custom syntax] that allows the registration of an entire mini-parser.
Use `Engine::register_custom_syntax_with_state_raw` to register a [custom syntax] _parser_ together
with an implementation function, both of which accept a custom user-defined _state_ value.
How Custom Parsers Work
-----------------------
### Leading Symbol
Under this API, the leading symbol for a custom parser is no longer restricted to be valid identifiers.
It can either be:
* an identifier that isn't a normal [keyword] unless [disabled][disable keywords and operators], or
* a valid symbol (see [list]({{rootUrl}}/appendix/operators.md)) which is not a normal [operator] unless [disabled][disable keywords and operators].
### Parser Function Signature
The [custom syntax] parser has the following signature.
> ```rust
> Fn(symbols: &[ImmutableString], look_ahead: &str, state: &mut Dynamic) -> Result<Option<ImmutableString>, ParseError>
> ```
where:
| Parameter | Type | Description |
| ------------ | :---------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbols` | [`&[ImmutableString]`][`ImmutableString`] | 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` | `&str` | a string slice containing the next symbol that is about to be read |
| `state` | [`&mut Dynamic`][`Dynamic`] | mutable reference to a user-defined _state_ |
Most strings are [`ImmutableString`]'s so it is usually more efficient to just `clone` the appropriate one
(if any matches, or keep an internal cache for commonly-used symbols) as the return value.
### Parameter #1 &ndash; Symbols Parsed So Far
The symbols parsed so far are provided as a slice of [`ImmutableString`]s.
The custom parser can inspect this symbols stream to determine the next symbol to parse.
| Argument type | Value |
| :-----------: | ----------------- |
| text [string] | text value |
| `$ident$` | identifier name |
| `$symbol$` | symbol literal |
| `$expr$` | `$expr$` |
| `$block$` | `$block$` |
| `$func$` | `$func$` |
| `$bool$` | `true` or `false` |
| `$int$` | value of number |
| `$float$` | value of number |
| `$string$` | [string] text |
### Parameter #2 &ndash; Look-Ahead Symbol
The _look-ahead_ symbol is the symbol that will be parsed _next_.
If the look-ahead is an expected symbol, the customer parser just returns it to continue parsing,
or it can return `$ident$` to parse it as an identifier, or even `$expr$` to start parsing
an expression.
```admonish tip.side.wide "Tip: Strings vs identifiers"
The look-ahead of an identifier (e.g. [variable] name) is its text name.
That of a [string] literal is its content wrapped in _quotes_ (`"`), e.g. `"this is a string"`.
```
If the look-ahead is `{`, then the custom parser may also return `$block$` to start parsing a
statements block.
If the look-ahead is unexpected, the custom parser should then return the symbol expected
and Rhai will fail with a parse error containing information about the expected symbol.
### Parameter #3 &ndash; User-Defined Custom _State_
The _state's_ value starts off as [`()`].
Its type is [`Dynamic`], possible to hold any value.
Usually it is set to an [object map] that contains information on the state of parsing.
### Return value
The return value is `Result<Option<ImmutableString>, ParseError>` where:
| Value | Description |
| :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Ok(None)` | parsing is complete and there is no more symbol to match |
| `Ok(Some(symbol))` | the next `symbol` to match, which can also be `$expr$`, `$ident$`, `$block$` etc. |
| `Err(error)` | `error` that is reflected back to the [`Engine`] &ndash; normally `ParseError( ParseErrorType::BadInput( LexError::ImproperSymbol(message) ), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. |
A custom parser always returns `Some` with the _next_ symbol expected (which can be `$ident$`,
`$expr$`, `$block$` etc.) or `None` if parsing should terminate (_without_ reading the
look-ahead symbol).
#### The `$$` return symbol short-cut
A return symbol starting with `$$` is treated specially.
Like `None`, it also terminates parsing, but at the same time it adds this symbol as text into the
_inputs_ stream at the end.
This is typically used to inform the implementation function which [custom syntax] variant was
actually parsed.
```rust
fn implementation_fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
{
// Get the last symbol
let key = inputs.last().unwrap().get_string_value().unwrap();
// Make sure it starts with '$$'
assert!(key.starts_with("$$"));
// Execute the custom syntax expression
match key {
"$$hello" => { ... }
"$$world" => { ... }
"$$foo" => { ... }
"$$bar" => { ... }
_ => Err(...)
}
}
```
`$$` is a convenient _short-cut_. An alternative method is to pass such information in the user-defined
custom _state_.
### Implementation Function Signature
The signature of an implementation function for `Engine::register_custom_syntax_with_state_raw` is
as follows, which is slightly different from the function for `Engine::register_custom_syntax`.
> ```rust
> Fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :---------------------------------: | ----------------------------------------------------- |
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
| `inputs` | `&[Expression]` | a list of input expression trees |
| `state` | [`&Dynamic`][`Dynamic`] | reference to the user-defined state |
Custom Parser Example
---------------------
```rust
engine.register_custom_syntax_with_state_raw(
// The leading symbol - which needs not be an identifier.
"perform",
// The custom parser implementation - always returns the next symbol expected
// 'look_ahead' is the next symbol about to be read
//
// Return symbols starting with '$$' also terminate parsing but allows us
// to determine which syntax variant was actually parsed so we can perform the
// appropriate action. This is a convenient short-cut to keeping the value
// inside the state.
//
// The return type is 'Option<ImmutableString>' to allow common text strings
// to be interned and shared easily, reducing allocations during parsing.
|symbols, look_ahead, state| match symbols.len() {
// perform ...
1 => Ok(Some("$ident$".into())),
// perform command ...
2 => match symbols[1].as_str() {
"action" => Ok(Some("$expr$".into())),
"hello" => Ok(Some("world".into())),
"update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())),
"cleanup" => Ok(Some("$$cleanup".into())),
cmd => Err(LexError::ImproperSymbol(format!("Improper command: {cmd}"))
.into_err(Position::NONE)),
},
// perform command arg ...
3 => match (symbols[1].as_str(), symbols[2].as_str()) {
("action", _) => Ok(Some("$$action".into())),
("hello", "world") => Ok(Some("$$hello-world".into())),
("update", arg) => match arg {
"system" => Ok(Some("$$update-system".into())),
"client" => Ok(Some("$$update-client".into())),
_ => Err(LexError::ImproperSymbol(format!("Cannot update {arg}"))
.into_err(Position::NONE))
},
("check", arg) => Ok(Some("$$check".into())),
("add", arg) => Ok(Some("$$add".into())),
("remove", arg) => Ok(Some("$$remove".into())),
(cmd, arg) => Err(LexError::ImproperSymbol(
format!("Invalid argument for command {cmd}: {arg}")
).into_err(Position::NONE)),
},
_ => unreachable!(),
},
// No variables declared/removed by this custom syntax
false,
// Implementation function
|context, inputs, state| {
let cmd = inputs.last().unwrap().get_string_value().unwrap();
match cmd {
"$$cleanup" => { ... }
"$$action" => { ... }
"$$update-system" => { ... }
"$$update-client" => { ... }
"$$check" => { ... }
"$$add" => { ... }
"$$remove" => { ... }
_ => Err(format!("Invalid command: {cmd}"))
}
}
);
```

View File

@ -0,0 +1,491 @@
Extend Rhai with Custom Syntax
==============================
{{#include ../links.md}}
For the ultimate adventurous, there is a built-in facility to _extend_ the Rhai language with
custom-defined _syntax_.
But before going off to define the next weird statement type, heed this warning:
```admonish danger.small "Don't Do It™"
Stick with standard language syntax as much as possible.
Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another obscure
language syntax just to do something.
Try [custom operators] first. A custom syntax should be considered a _last resort_.
```
```admonish success.small "Where this might be useful"
* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing.
* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances
understanding of the code's intent.
* Where certain logic cannot be easily encapsulated inside a function.
* Where you just want to confuse your user and make their lives miserable, because you can.
```
```admonish tip.small "Disable custom syntax"
Custom syntax can be disabled via the [`no_custom_syntax`] feature.
```
How to Do It
------------
### Step One &ndash; Design The Syntax
A custom syntax is simply a list of symbols.
These symbol types can be used:
* Standard [keywords]
* Standard [operators]
* Reserved [symbols]({{rootUrl}}/appendix/operators.md#symbols).
* Identifiers following the [variable] naming rules.
* `$expr$` &ndash; any valid expression, statement or statements block.
* `$block$` &ndash; any valid statements block (i.e. must be enclosed by `{` ... `}`).
* `$func$` &ndash; any valid [closure], or any valid statements block as the body of a [closure] with no parameters (if not [`no_function`]).
* `$ident$` &ndash; any [variable] name.
* `$symbol$` &ndash; any [symbol][operator], active or reserved.
* `$bool$` &ndash; a boolean value.
* `$int$` &ndash; an integer number.
* `$float$` &ndash; a floating-point number (if not [`no_float`]).
* `$string$` &ndash; a [string] literal.
#### The first symbol must be an identifier
There is no specific limit on the combination and sequencing of each symbol type,
except the _first_ symbol which must be a custom [keyword] that follows the naming rules
of [variables].
The first symbol also cannot be a normal [keyword] unless it is [disabled][disable keywords and operators].
Any valid identifier that is not an active [keyword] works fine, even if it is a reserved [keyword].
#### The first symbol must be unique
Rhai uses the _first_ symbol as a clue to parse custom syntax.
Therefore, at any one time, there can only be _one_ custom syntax starting with each unique symbol.
Any new custom syntax definition using the same first symbol simply _overwrites_ the previous one.
#### Example
```rust
exec [ $ident$ $symbol$ $int$ ] <- $expr$ : $block$
```
The above syntax is made up of a stream of symbols:
| Position | Input slot | Symbol | Description |
| :------: | :--------: | :--------: | -------------------------------------------------------------------------------------------------------- |
| 1 | | `exec` | custom keyword |
| 2 | | `[` | the left bracket symbol |
| 2 | 0 | `$ident$` | a [variable] name |
| 3 | 1 | `$symbol$` | the operator |
| 4 | 2 | `$int$` | an integer number |
| 5 | | `]` | the right bracket symbol |
| 6 | | `<-` | the left-arrow symbol (which is a [reserved symbol]({{rootUrl}}/appendix/operators.md#symbols) in Rhai). |
| 7 | 3 | `$expr$` | an expression, which may be enclosed with `{` ... `}`, or not. |
| 8 | | `:` | the colon symbol |
| 9 | 4 | `$block$` | a statements block, which must be enclosed with `{` ... `}`. |
This syntax matches the following sample code and generates five inputs (one for each non-keyword):
```rust
// Assuming the 'exec' custom syntax implementation declares the variable 'hello':
let x = exec [hello < 42] <- foo(1, 2) : {
hello += bar(hello);
baz(hello);
};
print(x); // variable 'x' has a value returned by the custom syntax
print(hello); // variable declared by a custom syntax persists!
```
### Step Two &ndash; Implementation
Any custom syntax must include an _implementation_ of it.
#### Function signature
The signature of an implementation function is as follows.
> ```rust
> Fn(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :---------------------------------: | ----------------------------------------------------- |
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
| `inputs` | `&[Expression]` | a list of input expression trees |
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
#### Return value
Return value is the result of evaluating the custom syntax expression.
#### Access arguments
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
and statements blocks (`$block$`) are provided.
To access a particular argument, use the following patterns:
| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description |
| :-----------: | ------------------------------------------------------------------------------------------------------------ | :---------------------------------: | --------------------------------------------------- |
| `$ident$` | `inputs[n].get_string_value().unwrap()` | `&str` | [variable] name |
| `$symbol$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()` | [`ImmutableString`] | symbol literal |
| `$expr$` | `&inputs[n]` | `&Expression` | an expression tree |
| `$block$` | `&inputs[n]` | `&Expression` | an expression tree |
| `$func$` | `&inputs[n]` | `&Expression` | an expression tree (output is a [function pointer]) |
| `$bool$` | `inputs[n].get_literal_value::<bool>().unwrap()` | `bool` | boolean value |
| `$int$` | `inputs[n].get_literal_value::<INT>().unwrap()` | `INT` | integer number |
| `$float$` | `inputs[n].get_literal_value::<FLOAT>().unwrap()` | `FLOAT` | floating-point number |
| `$string$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()`<br/><br/>`inputs[n].get_string_value().unwrap()` | [`ImmutableString`]<br/><br/>`&str` | [string] text |
#### Get literal constants
Several argument types represent literal constants that can be obtained directly via
`Expression::get_literal_value<T>` or `Expression::get_string_value` (for [strings]).
```rust
let expression = &inputs[0];
// Use 'get_literal_value' with a turbo-fish type to extract the value
let string_value = expression.get_literal_value::<ImmutableString>().unwrap();
let string_slice = expression.get_string_value().unwrap();
let float_value = expression.get_literal_value::<FLOAT>().unwrap();
// Or assign directly to a variable with type...
let int_value: i64 = expression.get_literal_value().unwrap();
// Or use type inference!
let bool_value = expression.get_literal_value().unwrap();
if bool_value { ... } // 'bool_value' inferred to be 'bool'
```
#### Evaluate an expression tree
Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expression tree
within the current evaluation context.
```rust
let expression = &inputs[0];
let result = context.eval_expression_tree(expression)?;
```
#### Retain variables in block scope
When an expression tree actually contains a statements block (i.e. `$block`), local
[variables]/[constants] defined within that block are usually removed at the end of the block.
Sometimes it is useful to retain these local [variables]/[constants] for further processing
(e.g. collecting new [variables] into an [object map]).
As such, evaluate the expression tree using the `EvalContext::eval_expression_tree_raw` method which
contains a parameter to control whether the statements block should be rewound.
```rust
// Assume 'expression' contains a statements block with local variable definitions
let expression = &inputs[0];
let result = context.eval_expression_tree_raw(expression, false)?;
// Variables defined within 'expression' persist in context.scope()
```
#### Declare variables
New [variables]/[constants] maybe declared (usually with a [variable] name that is passed in via `$ident$`).
It can simply be pushed into the [`Scope`].
```rust
let var_name = inputs[0].get_string_value().unwrap();
let expression = &inputs[1];
context.scope_mut().push(var_name, 0_i64); // declare new variable
let result = context.eval_expression_tree(expression)?;
```
### Step Three &ndash; Register the Custom Syntax
Use `Engine::register_custom_syntax` to register a custom syntax.
Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting
with that symbol, the previous syntax will be overwritten.
The syntax is passed simply as a slice of `&str`.
```rust
// Custom syntax implementation
fn implementation_func(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> {
let var_name = inputs[0].get_string_value().unwrap();
let stmt = &inputs[1];
let condition = &inputs[2];
// Push new variable into the scope BEFORE 'context.eval_expression_tree'
context.scope_mut().push(var_name.to_string(), 0_i64);
let mut count = 0_i64;
loop {
// Evaluate the statements block
context.eval_expression_tree(stmt)?;
count += 1;
// Declare a new variable every three turns...
if count % 3 == 0 {
context.scope_mut().push(format!("{var_name}{count}"), count);
}
// Evaluate the condition expression
let expr_result = !context.eval_expression_tree(condition)?;
match expr_result.as_bool() {
Ok(true) => (),
Ok(false) => break,
Err(err) => return Err(EvalAltResult::ErrorMismatchDataType(
"bool".to_string(),
err.to_string(),
condition.position(),
).into()),
}
}
Ok(Dynamic::UNIT)
}
// Register the custom syntax (sample): exec<x> -> { x += 1 } while x < 0
engine.register_custom_syntax(
[ "exec", "<", "$ident$", ">", "->", "$block$", "while", "$expr$" ], // the custom syntax
true, // variables declared within this custom syntax
implementation_func
)?;
```
Remember that a custom syntax acts as an _expression_, so it can show up practically anywhere:
```rust
// Use as an expression:
let foo = (exec<x> -> { x += 1 } while x < 42) * 100;
// New variables are successfully declared...
x == 42;
x3 == 3;
x6 == 6;
// Use as a function call argument:
do_something(exec<x> -> { x += 1 } while x < 42, 24, true);
// Use as a statement:
exec<x> -> { x += 1 } while x < 0;
// ^ terminate statement with ';' unless the custom
// syntax already ends with '}'
```
### Step Four &ndash; Disable Unneeded Statement Types
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
Therefore, many statement types actually may not make sense under the same usage scenario.
So, while at it, better [disable][disable keywords and operators] those built-in keywords
and [operators] that should not be used by the user. The would leave only the bare minimum
language surface exposed, together with the custom syntax that is tailor-designed for
the scenario.
A [keyword] or [operator] that is disabled can still be used in a custom syntax.
In an extreme case, it is possible to disable _every_ [keyword] in the language, leaving only
custom syntax (plus possibly expressions). But again, Don't Do It™ &ndash; unless you are certain
of what you're doing.
### Step Five &ndash; Document
For custom syntax, documentation is crucial.
Make sure there are _lots_ of examples for users to follow.
### Step Six &ndash; Profit!
Practical Example &ndash; Matrix Literal
----------------------------------------
Say you'd want to use something like [`ndarray`](https://crates.io/crates/ndarray) to manipulate matrices.
However, you'd like to write matrix literals in a more intuitive syntax than an [array]
of [arrays].
In other words, you'd like to turn:
```rust
// Array of arrays
let matrix = [ [ a, b, 0 ],
[ -b, a, 0 ],
[ 0, 0, c * d ] ];
```
into:
```rust
// Directly parse to an ndarray::Array (look ma, no commas!)
let matrix = @| a b 0 |
| -b a 0 |
| 0 0 c*d |;
```
This can easily be done via a custom syntax, which yields a syntax that is more pleasing.
```rust
// Disable the '|' symbol since it'll conflict with the bit-wise OR operator.
// Do this BEFORE registering the custom syntax.
engine.disable_symbol("|");
engine.register_custom_syntax(
["@", "|", "$expr$", "$expr$", "$expr$", "|",
"|", "$expr$", "$expr$", "$expr$", "|",
"|", "$expr$", "$expr$", "$expr$", "|"
],
false,
|context, inputs| {
use ndarray::arr2;
let mut values = [[0.0; 3]; 3];
for y in 0..3 {
for x in 0..3 {
let offset = y * 3 + x;
match context.eval_expression_tree(&inputs[offset])?.as_float() {
Ok(v) => values[y][x] = v,
Err(typ) => return Err(Box::new(EvalAltResult::ErrorMismatchDataType(
"float".to_string(), typ.to_string(),
inputs[offset].position()
)))
}
}
}
let matrix = arr2(&values);
Ok(Dynamic::from(matrix))
},
)?;
```
For matrices of flexible dimensions, check out [custom syntax parsers](custom-syntax-parsers.md).
Practical Example &ndash; Defining Temporary Variables
------------------------------------------------------
It is possible to define temporary [variables]/[constants] which are available only to code blocks
within the custom syntax.
```rust
engine.register_custom_syntax(
[ "with", "offset", "(", "$expr$", ",", "$expr$", ")", "$block$", ],
true, // must be true in order to define new variables
|context, inputs| {
// Get the two offsets
let x = context.eval_expression_tree(&inputs[0])?.as_int().map_err(|typ| Box::new(
EvalAltResult::ErrorMismatchDataType("integer".to_string(), typ.to_string(), inputs[0].position())
))?;
let y = context.eval_expression_tree(&inputs[1])?.as_int().map_err(|typ| Box::new(
EvalAltResult::ErrorMismatchDataType("integer".to_string(), typ.to_string(), inputs[1].position())
))?;
// Add them as temporary constants into the scope, available only to the code block
let orig_len = context.scope().len();
context.scope_mut().push_constant("x", x);
context.scope_mut().push_constant("y", y);
// Run the code block
let result = context.eval_expression_tree(&inputs[2]);
// Remove the temporary constants from the scope so they don't leak outside
context.scope_mut().rewind(orig_len);
// Return the result
result
},
)?;
```
Practical Example &ndash; Recreating C's Ternary Operator
---------------------------------------------------------
Rhai has [if-expressions](../language/if.md#if-expression), but sometimes a C-style _ternary_ operator
is more concise.
```rust
// A custom syntax must start with a unique symbol, so we use 'iff'.
// Register the custom syntax: iff condition ? true-value : false-value
engine.register_custom_syntax(
["iff", "$expr$", "?", "$expr$", ":", "$expr$"],
false,
|context, inputs| match context.eval_expression_tree(&inputs[0])?.as_bool() {
Ok(true) => context.eval_expression_tree(&inputs[1]),
Ok(false) => context.eval_expression_tree(&inputs[2]),
Err(typ) => Err(Box::new(EvalAltResult::ErrorMismatchDataType(
"bool".to_string(), typ.to_string(), inputs[0].position()
))),
},
)?;
```
```admonish tip.small "Tip: Custom syntax performance"
The code in the example above is essentially what the [`if`] statement does internally, and since
custom syntax is pre-parsed, there really is no performance penalty!
```
Practical Example &ndash; Recreating JavaScript's `var` Statement
-----------------------------------------------------------------
The following example recreates a statement similar to the `var` variable declaration syntax in
JavaScript, which creates a global variable if one doesn't already exist.
There is currently no equivalent in Rhai.
```rust
// Register the custom syntax: var x = ???
engine.register_custom_syntax([ "var", "$ident$", "=", "$expr$" ], true, |context, inputs| {
let var_name = inputs[0].get_string_value().unwrap().to_string();
let expr = &inputs[1];
// Evaluate the expression
let value = context.eval_expression_tree(expr)?;
// Push a new variable into the scope if it doesn't already exist.
// Otherwise just set its value.
if !context.scope().is_constant(var_name).unwrap_or(false) {
context.scope_mut().set_value(var_name.to_string(), value);
Ok(Dynamic::UNIT)
} else {
Err(format!("variable {} is constant", var_name).into())
}
})?;
```

View File

@ -0,0 +1,54 @@
Break-Points
============
{{#include ../../links.md}}
A _break-point_ **always** stops the current evaluation and calls the [debugging][debugger]
callback.
A break-point is represented by the `debugger::BreakPoint` type, which is an `enum` with
the following variants.
| `BreakPoint` variant | Not available under | Description |
| ---------------------------------------- | :-----------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `AtPosition { source, pos, enabled }` | [`no_position`] | breaks at the specified position in the specified source (empty if none);<br/>if `pos` is at beginning of line, breaks anywhere on the line |
| `AtFunctionName { name, enabled }` | | breaks when a function matching the specified name is called (can be [operator]) |
| `AtFunctionCall { name, args, enabled }` | | breaks when a function matching the specified name (can be [operator]) and the specified number of arguments is called |
| `AtProperty { name, enabled }` | [`no_object`] | breaks at the specified property access |
Access Break-Points
-------------------
The following [`debugger::Debugger`] methods allow access to break-points for manipulation.
| Method | Return type | Description |
| ------------------ | :--------------------: | ------------------------------------------------- |
| `break_points` | `&[BreakPoint]` | returns a slice of all `BreakPoint`'s |
| `break_points_mut` | `&mut Vec<BreakPoint>` | returns a mutable reference to all `BreakPoint`'s |
Example
-------
```rust
use rhai::debugger::*;
let debugger = &mut context.global_runtime_state_mut().debugger_mut();
// Get number of break-points.
let num_break_points = debugger.break_points().len();
// Add a new break-point on calls to 'foo(_, _, _)'
debugger.break_points_mut().push(
BreakPoint::AtFunctionCall { name: "foo".into(), args: 3 }
);
// Display all break-points
for bp in debugger.break_points().iter() {
println!("{bp}");
}
// Clear all break-points
debugger.break_points_mut().clear();
```

View File

@ -0,0 +1,32 @@
Call Stack
==========
{{#include ../../links.md}}
```admonish info.side.wide "Call stack frames"
Each "frame" in the call stack corresponds to one layer of [function] call (script-defined
or native Rust).
A call stack frame has the type `debugger::CallStackFrame`.
```
The [debugger] keeps a _call stack_ of [function] calls with argument values.
This call stack can be examined to determine the control flow at any particular point.
The `Debugger::call_stack` method returns a slice of all call stack frames.
```rust
use rhai::debugger::*;
let debugger = &mut context.global_runtime_state().debugger();
// Get depth of the call stack.
let depth = debugger.call_stack().len();
// Display all function calls
for frame in debugger.call_stack().iter() {
println!("{frame}");
}
```

View File

@ -0,0 +1,110 @@
Register with the Debugger
==========================
{{#include ../../links.md}}
Hooking up a debugging interface is as simple as providing closures to the [`Engine`]'s built-in
debugger via `Engine::register_debugger`.
```rust
use rhai::debugger::{ASTNode, DebuggerCommand};
let mut engine = Engine::new();
engine.register_debugger(
// Provide a callback to initialize the debugger state
|engine, mut debugger| {
debugger.set_state(...);
debugger
},
// Provide a callback for each debugging step
|context, event, node, source, pos| {
...
DebuggerCommand::StepOver
}
);
```
~~~admonish tip.small "Tip: Accessing the `Debugger`"
The type `debugger::Debugger` allows for manipulating [break-points], among others.
The [`Engine`]'s debugger instance can be accessed via `context.global_runtime_state().debugger()` (immutable)
or `context.global_runtime_state_mut().debugger_mut()` (mutable).
~~~
Callback Functions Signature
----------------------------
There are two callback functions to register for the debugger.
The first is simply a function to initialize the state of the debugger with the following signature.
> ```rust
> Fn(&Engine, debugger::Debugger) -> debugger::Debugger
> ```
The second callback is a function which will be called by the debugger during each step, with the
following signature.
> ```rust
> Fn(context: EvalContext, event: debugger::DebuggerEvent, node: ASTNode, source: &str, pos: Position) -> Result<debugger::DebuggerCommand, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------- |
| `context` | [`EvalContext`] | the current _evaluation context_ |
| `event` | `DebuggerEvent` | an `enum` indicating the event that triggered the debugger |
| `node` | `ASTNode` | an `enum` with two variants: `Expr` or `Stmt`, corresponding to the current expression node or statement node in the [`AST`] |
| `source` | `&str` | the source of the current [`AST`], or empty if none |
| `pos` | `Position` | position of the current node, same as `node.position()` |
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
### Event
The `event` parameter of the second closure passed to `Engine::register_debugger` contains a
`debugger::DebuggerEvent` which is an `enum` with the following variants.
| `DebuggerEvent` variant | Description |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| `Start` | the debugger is triggered at the beginning of evaluation |
| `Step` | the debugger is triggered at the next step of evaluation |
| `BreakPoint(`_n_`)` | the debugger is triggered by the _n_-th [break-point] |
| `FunctionExitWithValue(`_r_`)` | the debugger is triggered by a function call returning with value _r_ which is `&Dynamic` |
| `FunctionExitWithError(`_err_`)` | the debugger is triggered by a function call exiting with error _err_ which is `&EvalAltResult` |
| `End` | the debugger is triggered at the end of evaluation |
### Return value
```admonish tip.side.wide "Tip: Initialization"
When a script starts evaluation, the debugger always stops at the very _first_ [`AST`] node
with the `event` parameter set to `DebuggerStatus::Start`.
This allows initialization to be done (e.g. setting up [break-points]).
```
The second closure passed to `Engine::register_debugger` will be called when stepping into or over
expressions and statements, or when [break-points] are hit.
The return type of the closure is `Result<debugger::DebuggerCommand, Box<EvalAltResult>>`.
If an error is returned, the script evaluation at that particular instance returns with that
particular error. It is thus possible to _abort_ the script evaluation by returning an error that is
not _catchable_, such as `EvalAltResult::ErrorTerminated`.
If no error is returned, then the return `debugger::DebuggerCommand` variant determines the
continued behavior of the debugger.
| `DebuggerCommand` variant | Behavior | `gdb` equivalent |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | :--------------: |
| `Continue` | continue with normal script evaluation | `continue` |
| `StepInto` | run to the next expression or statement, diving into functions | `step` |
| `StepOver` | run to the next expression or statement, skipping over functions | |
| `Next` | run to the next statement, skipping over functions | `next` |
| `FunctionExit` | run to the end of the current function call; debugger is triggered _before_ the function call returns and the [`Scope`] cleared | `finish` |

View File

@ -0,0 +1,50 @@
Debugging Interface
===================
{{#include ../../links.md}}
For systems open to external user-created scripts, it is usually desirable to provide a _debugging_
experience to the user. The alternative is to provide a custom implementation of [`debug`] via
`Engine::on_debug` that traps debug output to show in a side panel, for example, which is actually
extremely simple.
Nevertheless, in some systems, it may not be convenient, or even possible, for the user to debug his
or her scripts simply via good-old [`print`] or [`debug`] statements &ndash; the system does not
have any facility for printed output, for instance.
Or the system may require more advanced debugging facilities than mere [`print`] statements &ndash;
such as [break-points].
For these advanced scenarios, Rhai contains a _Debugging_ interface, turned on via the [`debugging`]
feature (which implies the [`internals`] feature).
The debugging interface resides under the `debugger` sub-module.
```admonish tip.small "The Rhai Debugger"
The [`rhai-dbg`]({{repoHome}}/src/bin/rhai-dbg.rs) bin tool shows a simple example of
employing the debugging interface to create a debugger for Rhai scripts!
```
Built-in Functions
------------------
The following functions (defined in the [`DebuggingPackage`][built-in packages] but excluded when
using a [raw `Engine`]) provides runtime information for debugging purposes.
| Function | Parameter(s) | Not available under | Description |
| ------------ | ------------ | :---------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `back_trace` | _none_ | [`no_function`], [`no_index`] | returns an [array] of [object maps] or [strings], each containing one level of [function] call;</br>returns an empty [array] if no [debugger] is registered |
```rust
// This recursive function prints its own call stack during each run
fn foo(x) {
print(back_trace()); // prints the current call stack
if x > 0 {
foo(x - 1)
}
}
```

View File

@ -0,0 +1,92 @@
Implement a Debugging Server
============================
Sometimes it is desirable to embed a debugging _server_ inside the application such that an external
debugger interface can connect to the application's running instance at runtime.
This way, when scripts are run within the application, it is easy for an external interface to debug
those scripts as they run.
Such connections may take the form of any communication channel, for example a TCP/IP connection, a
named pipe, or an MPSC channel.
Example
-------
### Server side
The following example assumes bi-direction, blocking messaging channels, such as a WebSocket
connection, with a server that accepts connections and creates those channels.
```rust
use rhai::debugger::{ASTNode, DebuggerCommand};
let mut engine = Engine::new();
engine.register_debugger(
// Use the initialization callback to set up the communications channel
// and listen to it
|engine, mut debugger| {
// Create server that will listen to requests
let mut server = MyCommServer::new();
server.listen("localhost:8080");
// Wrap it up in a shared locked cell so it can be 'Clone'
let server = Rc::new(RefCell::new(server));
// Store the channel in the debugger state
debugger.set_state(Dynamic::from(server));
debugger
},
// Trigger the server during each debugger stop point
|context, event, node, source, pos| {
// Get the state
let mut state = context.tag_mut();
// Get the server
let mut server = state.write_lock::<MyCommServer>().unwrap();
// Send the event to the server - blocking call
server.send_message(...);
// Receive command - blocking call
match server.receive_message() {
None => DebuggerCommand::StepOver,
// Decode command
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
:
:
}
}
);
```
### Client side
The client can be any system that can work with WebSockets for messaging.
```js
// Connect to the application's debugger
let webSocket = new WebSocket("wss://localhost:8080");
webSocket.on_message = (event) => {
let msg = JSON.parse(event.data);
switch msg.type {
// handle debugging events from the application...
case "step": {
:
}
:
:
}
};
// Send command to the application
webSocket.send("step-over");
```

View File

@ -0,0 +1,67 @@
Debugger State
==============
{{#include ../../links.md}}
Sometimes it is useful to keep a persistent _state_ within the [debugger].
The `Engine::register_debugger` API accepts a function that returns the initial value of the
[debugger's][debugger] state, which is a [`Dynamic`] and can hold any value.
This state value is the stored into the [debugger]'s custom state.
Access the Debugger State
-------------------------
Use `EvalContext::global_runtime_state().debugger()` (immutable) or
`EvalContext::global_runtime_state_mut().debugger_mut()` (mutable) to gain access to the current
[`debugger::Debugger`] instance.
The following [`debugger::Debugger`] methods allow access to the custom [debugger] state.
| Method | Parameter type | Return type | Description |
| ----------- | :-------------------------------: | :-------------------------: | ----------------------------------------------- |
| `state` | _none_ | [`&Dynamic`][`Dynamic`] | returns the custom state |
| `state_mut` | _none_ | [`&mut Dynamic`][`Dynamic`] | returns a mutable reference to the custom state |
| `set_state` | [`impl Into<Dynamic>`][`Dynamic`] | _none_ | sets the value of the custom state |
Example
-------
```rust
engine.register_debugger(
|engine, mut debugger| {
// Say, use an object map for the debugger state
let mut state = Map::new();
// Initialize properties
state.insert("hello".into(), 42_64.into());
state.insert("foo".into(), false.into());
debugger.set_state(state);
debugger
},
|context, node, source, pos| {
// Print debugger state - which is an object map
let state = context.global_runtime_state().debugger().state();
println!("Current state = {state}");
// Get the state as an object map
let mut state = context.global_runtime_state_mut()
.debugger_mut().state_mut()
.write_lock::<Map>().unwrap();
// Read state
let hello = state.get("hello").unwrap().as_int().unwrap();
// Modify state
state.insert("hello".into(), (hello + 1).into());
state.insert("foo".into(), true.into());
state.insert("something_new".into(), "hello, world!".into());
// Continue with debugging
Ok(DebuggerCommand::StepInto)
}
);
```

View File

@ -0,0 +1,85 @@
Variable Definition Filter
==========================
{{#include ../links.md}}
[`Engine::on_def_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_def_var
Although it is easy to disable variable _[shadowing]_ via [`Engine::set_allow_shadowing`][options],
sometimes more fine-grained control is needed.
For example, it may be the case that not _all_ variables [shadowing] must be disallowed, but that only
a particular variable name needs to be protected and not others. Or only under very special
circumstances.
Under this scenario, it is possible to provide a _filter_ closure to the [`Engine`] via
[`Engine::on_def_var`] that traps variable definitions (i.e. [`let`][variable] or
[`const`][constant] statements) in a Rhai script.
The filter is called when a [variable] or [constant] is defined both during runtime and compilation.
```rust
let mut engine = Engine::new();
// Register a variable definition filter.
engine.on_def_var(|is_runtime, info, context| {
match (info.name, info.is_const) {
// Disallow defining 'MYSTIC_NUMBER' as a constant!
("MYSTIC_NUMBER", true) => Ok(false),
// Disallow defining constants not at global level!
(_, true) if info.nesting_level > 0 => Ok(false),
// Throw any exception you like...
("hello", _) => Err(EvalAltResult::ErrorVariableNotFound(info.name.to_string(), Position::NONE).into()),
// Return Ok(true) to continue with normal variable definition.
_ => Ok(true)
}
});
```
Function Signature
------------------
The function signature passed to [`Engine::on_def_var`] takes the following form.
> ```rust
> Fn(is_runtime: bool, info: VarDefInfo, context: EvalContext) -> Result<bool, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| ------------ | :-------------: | ----------------------------------------------------------------------------------------------- |
| `is_runtime` | `bool` | `true` if the [variable] definition event happens during runtime, `false` if during compilation |
| `info` | `VarDefInfo` | information on the [variable] being defined |
| `context` | [`EvalContext`] | the current _evaluation context_ |
and `VarDefInfo` is a simple `struct` that contains the following fields:
| Field | Type | Description |
| --------------- | :-----: | --------------------------------------------------------------------------------------- |
| `name` | `&str` | [variable] name |
| `is_const` | `bool` | `true` if the definition is a [`const`][constant]; `false` if it is a [`let`][variable] |
| `nesting_level` | `usize` | the current nesting level; the global level is zero |
| `will_shadow` | `bool` | will this [variable] _[shadow]_ an existing [variable] of the same name? |
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
### Return value
The return value is `Result<bool, Box<EvalAltResult>>` where:
| Value | Description |
| ------------------------- | -------------------------------------------------- |
| `Ok(true)` | normal [variable] definition should continue |
| `Ok(false)` | [throws][exception] a runtime or compilation error |
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`] |
```admonish bug.small "Error during compilation"
During compilation (i.e. when `is_runtime` is `false`), `EvalAltResult::ErrorParsing` is passed
through as the compilation error.
All other errors map to `ParseErrorType::ForbiddenVariable`.
```

View File

@ -0,0 +1,28 @@
Disable Certain Keywords and/or Operators
=========================================
{{#include ../links.md}}
For certain embedded usage, it is sometimes necessary to restrict the language to a strict subset of
Rhai to prevent usage of certain language features.
Rhai supports surgically disabling a [keyword] or [operator] via `Engine::disable_symbol`.
```rust
use rhai::Engine;
let mut engine = Engine::new();
engine
.disable_symbol("if") // disable the 'if' keyword
.disable_symbol("+="); // disable the '+=' operator
// The following all return parse errors.
engine.compile("let x = if true { 42 } else { 0 };")?;
// ^ 'if' is rejected as a reserved keyword
engine.compile("let x = 40 + 2; x += 1;")?;
// ^ '+=' is not recognized as an operator
// ^ other operators are not affected
```

View File

@ -0,0 +1,33 @@
Disable Looping
===============
{{#include ../links.md}}
For certain scripts, especially those in embedded usage for straight calculations, or where Rhai
script [`AST`]'s are eventually transcribed into some other instruction set, looping may be
undesirable as it may not be supported by the application itself.
Rhai looping constructs include the [`while`], [`loop`], [`do`] and [`for`] statements.
Although it is possible to disable these keywords via
[`Engine::disable_symbol`][disable keywords and operators], it is simpler to disable all looping
via [`Engine::set_allow_looping`][options].
```rust
use rhai::Engine;
let mut engine = Engine::new();
// Disable looping
engine.set_allow_looping(false);
// The following all return parse errors.
engine.compile("while x == y { x += 1; }")?;
engine.compile(r#"loop { print("hello world!"); }"#)?;
engine.compile("do { x += 1; } until x > 10;")?;
engine.compile("for n in 0..10 { print(n); }")?;
```

View File

@ -0,0 +1,94 @@
Use Rhai as a Domain-Specific Language (DSL)
============================================
{{#include ../links.md}}
Rhai can be successfully used as a domain-specific language (DSL).
Expressions Only
----------------
In many DSL scenarios, only evaluation of expressions is needed.
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict a script to
expressions only.
Unicode Standard Annex #31 Identifiers
--------------------------------------
[Variable] names and other identifiers do not necessarily need to be ASCII-only.
The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow [variable] names and
identifiers that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
This is sometimes useful in a non-English DSL.
Disable Keywords and/or Operators
---------------------------------
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
language features that are not necessary or dangerous to the application.
For example, a DSL may disable the [`while`] loop while keeping all other statement types intact.
It is possible, in Rhai, to surgically [disable keywords and operators].
Custom Operators
----------------
Some DSL scenarios require special operators that make sense only for that specific environment.
In such cases, it is possible to define [custom operators] in Rhai.
```rust
let animal = "rabbit";
let food = "carrot";
animal eats food // custom operator 'eats'
eats(animal, food) // <- the above actually de-sugars to this
let x = foo # bar; // custom operator '#'
let x = #(foo, bar) // <- the above actually de-sugars to this
```
Although a [custom operator] always de-sugars to a simple function call, nevertheless it makes the
DSL syntax much simpler and expressive.
Custom Syntax
-------------
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] &ndash;
essentially custom statement types.
For example, the following is a SQL-like syntax for some obscure DSL operation:
```rust
let table = [..., ..., ..., ...];
// Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$
let total = calculate sum(table->price) => row : row.weight > 50;
// Note: There is nothing special about those symbols; to make it look exactly like SQL:
// Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$
let total = SELECT sum(price) AS row FROM table WHERE row.weight > 50;
```
After registering this [custom syntax] with Rhai, it can be used anywhere inside a script as
a normal expression.
For its evaluation, the callback function will receive the following list of inputs:
* `inputs[0] = "sum"` &ndash; math operator
* `inputs[1] = "price"` &ndash; field name
* `inputs[2] = "row"` &ndash; loop variable name
* `inputs[3] = Expression(table)` &ndash; data source
* `inputs[4] = Expression(row.weight > 50)` &ndash; filter predicate
Other identifiers, such as `"calculate"`, `"FROM"`, as well as symbols such as `->` and `:` etc.,
are parsed in the order defined within the [custom syntax].

View File

@ -0,0 +1,56 @@
Use Rhai in Dynamic Libraries
=============================
{{#include ../links.md}}
Sometimes functions registered into a Rhai [`Engine`] come from _dynamic libraries_ (a.k.a. _shared
libraries_ in Linux or _DLL's_ in Windows), which are compiled separately from the main binary, not
statically linked but loaded dynamically at runtime.
~~~admonish example.small "`rhai-dylib`"
The project [`rhai-dylib`] demonstrates an API for creating dynamically-loadable libraries
for use with a Rhai [`Engine`].
~~~
Problem Symptom
---------------
The symptom is usually _Function Not Found_ errors even though the relevant functions (within the
dynamic library) have already been registered into the [`Engine`].
This usually happens when a mutable reference to the [`Engine`] is passed into an entry-point
function exposed by the dynamic library and the [`Engine`]'s function registration API is called
inside the dynamic library.
Problem Cause
-------------
To counter [DOS] attacks, the _hasher_ used by Rhai, [`ahash`], automatically generates a different
_seed_ for hashing during each compilation and execution run.
This means that hash values generated by the hasher will not be _stable_ &ndash; they change
during each compile, and during each run.
This creates hash mismatches between the main binary and the loaded dynamic library because, as they
are not linked together at compile time, two independent copies of the hasher reside in them,
resulting in different hashes for even the same function signature.
Solution
--------
Use [static hashing] for force predictable hashes.
```admonish warning.small "Warning: Safety considerations"
Static hashing allows dynamic libraries with Rhai code to be loaded and used with
an [`Engine`] in the main binary.
However, a fixed seed enlarges the attack surface of Rhai to malicious intent
(e.g. [DOS] attacks).
This safety trade-off should be carefully considered.
```

View File

@ -0,0 +1,26 @@
`EvalContext`
=============
{{#include ../links.md}}
Many functions in advanced APIs contain a parameter of type `EvalContext` in order to allow the
current evaluation state to be accessed and/or modified.
`EvalContext` encapsulates the current _evaluation context_ and exposes the following methods.
| Method | Return type | Description |
| ---------------------------- | :----------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scope()` | [`&Scope`][`Scope`] | reference to the current [`Scope`] |
| `scope_mut()` | [`&mut Scope`][`Scope`] | mutable reference to the current [`Scope`]; [variables] can be added to/removed from it |
| `engine()` | [`&Engine`][`Engine`] | reference to the current [`Engine`] |
| `source()` | `Option<&str>` | reference to the current source, if any |
| `tag()` | [`&Dynamic`][`Dynamic`] | reference to the custom state that is persistent during the current run |
| `tag_mut()` | [`&mut Dynamic`][`Dynamic`] | mutable reference to the custom state that is persistent during the current run |
| `iter_imports()` | `impl Iterator<Item = (&str,`[`&Module`][`Module`]`)>` | iterator of the current stack of [modules] imported via [`import`] statements, in reverse order (i.e. later [modules] come first); not available under [`no_module`] |
| `global_runtime_state()` | [`&GlobalRuntimeState`][`GlobalRuntimeState`] | reference to the current [global runtime state][`GlobalRuntimeState`] (including the stack of [modules] imported via [`import`] statements) |
| `global_runtime_state_mut()` | [`&mut &mut GlobalRuntimeState`][`GlobalRuntimeState`] | mutable reference to the current [global runtime state][`GlobalRuntimeState`]; use this to access the [`debugger`][debugger] field in order to set/clear [break-points] |
| `iter_namespaces()` | `impl Iterator<Item =`[`&Module`][`Module`]`>` | iterator of the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions], in reverse order (i.e. later [modules] come first) |
| `namespaces()` | [`&[&Module]`][`Module`] | reference to the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions] |
| `this_ptr()` | [`Option<&Dynamic>`][`Dynamic`] | reference to the current bound `this` pointer, if any |
| `this_ptr_mut()` | [`&mut Option<&mut Dynamic>`][`Dynamic`] | mutable reference to the current bound `this` pointer, if any |
| `call_level()` | `usize` | the current nesting level of [function] calls |

View File

@ -0,0 +1,84 @@
Evaluate Expressions Only
=========================
{{#include ../links.md}}
~~~admonish tip.side "Tip: `Dynamic`"
Use [`Dynamic`] if you're uncertain of the return type.
~~~
Very often, a use case does not require a full-blown scripting _language_, but only needs to
evaluate _expressions_.
In these cases, use the `Engine::compile_expression` and `Engine::eval_expression` methods or their
`_with_scope` variants.
```rust
let result: i64 = engine.eval_expression("2 + (10 + 10) * 2")?;
let result: Dynamic = engine.eval_expression("get_value(42)")?;
// Usually this is done together with a custom scope with variables...
let mut scope = Scope::new();
scope.push("x", 42_i64);
scope.push_constant("SCALE", 10_i64);
let result: i64 = engine.eval_expression_with_scope(&mut scope,
"(x + 1) * SCALE"
)?;
```
~~~admonish bug "No statements allowed"
When evaluating _expressions_, no full-blown statement (e.g. [`while`], [`for`], `fn`) &ndash;
not even [variable] assignment &ndash; is supported and will be considered syntax errors.
This is true also for [statement expressions]({{rootUrl}}/language/statement-expression.md)
and [closures].
```rust
// The following are all syntax errors because the script
// is not a strict expression.
engine.eval_expression::<()>("x = 42")?;
let ast = engine.compile_expression("let x = 42")?;
let result = engine.eval_expression_with_scope::<i64>(&mut scope,
"{ let y = calc(x); x + y }"
)?;
let fp: FnPtr = engine.eval_expression("|x| x + 1")?;
```
~~~
~~~admonish tip "Tip: `if`-expressions and `switch`-expressions"
[`if` expressions]({{rootUrl}}/language/if.md#if-expression) are allowed if both statement blocks
contain only a single expression each.
[`switch` expressions]({{rootUrl}}/language/switch-expression.md) are allowed if all match
actions are expressions and not statements.
loop expressions are not allowed.
```rust
// The following are allowed.
let result = engine.eval_expression_with_scope::<i64>(&mut scope,
"if x { 42 } else { 123 }"
)?;
let result = engine.eval_expression_with_scope::<i64>(&mut scope, "
switch x {
0 => x * 42,
1..=9 => foo(123) + bar(1),
10 => 0,
}
")?;
```
~~~

View File

@ -0,0 +1,46 @@
Create a Rust Closure from a Rhai Function
==========================================
{{#include ../links.md}}
```admonish tip.side "Tip"
Very useful as callback functions!
```
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust closure.
Creating them is accomplished via the `Func` trait which contains `create_from_script`
(as well as its companion method `create_from_ast`).
```rust
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 + y.len < 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<dyn Fn(i64, &str) -> Result<bool, Box<EvalAltResult>>> 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 closure
schedule_callback(func); // pass it as a callback to another function
// Although there is nothing you can't do by manually writing out the closure yourself...
let engine = Engine::new();
let ast = engine.compile(script)?;
schedule_callback(Box::new(move |x: i64, y: String| {
engine.call_fn::<bool>(&mut Scope::new(), &ast, "calc", (x, y))
}));
```

View File

@ -0,0 +1,134 @@
Your First Script in Rhai
=========================
{{#include ../links.md}}
Run a Script
------------
To get going with Rhai is as simple as creating an instance of the scripting engine `rhai::Engine`
via `Engine::new`, then calling `Engine::run`.
```rust
use rhai::{Engine, EvalAltResult};
pub fn main() -> Result<(), Box<EvalAltResult>>
// ^^^^^^^^^^^^^^^^^^
// Rhai API error type
{
// Create an 'Engine'
let engine = Engine::new();
// Your first Rhai Script
let script = "print(40 + 2);";
// Run the script - prints "42"
engine.run(script)?;
// Done!
Ok(())
}
```
Get a Return Value
------------------
To return a value from the script, use `Engine::eval` instead.
```rust
use rhai::{Engine, EvalAltResult};
pub fn main() -> Result<(), Box<EvalAltResult>>
{
let engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?;
// ^^^^^^^ required: cast the result to a type
println!("Answer: {result}"); // prints 42
Ok(())
}
```
Use Script Files
----------------
```admonish info.side "Script file extension"
Rhai script files are customarily named with extension `.rhai`.
```
Or evaluate a script file directly with `Engine::run_file` or `Engine::eval_file`.
Loading and running script files is not available for [`no_std`] or [WASM] builds.
```rust
let result = engine.eval_file::<i64>("hello_world.rhai".into())?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// a 'PathBuf' is needed
// Running a script file also works in a similar manner
engine.run_file("hello_world.rhai".into())?;
```
```admonish tip "Tip: Unix shebangs"
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
If a script file starts with `#!`, the entire first line is skipped by `Engine::compile_file` and
`Engine::eval_file`. Because of this, Rhai scripts with shebangs at the beginning need no special processing.
This behavior is also present for non-Unix (e.g. Windows) environments so scripts are portable.
~~~js
#!/home/to/me/bin/rhai-run
// This is a Rhai script
let answer = 42;
print(`The answer is: ${answer}`);
~~~
```
Specify the Return Type
-----------------------
~~~admonish tip.side "Tip: `Dynamic`"
Use [`Dynamic`] if you're uncertain of the return type.
~~~
The type parameter for `Engine::eval` is used to specify the type of the return value, which _must_
match the actual type or an error is returned. Rhai is very strict here.
There are two ways to specify the return type: _turbofish_ notation, or type inference.
### Turbofish
```rust
let result = engine.eval::<i64>("40 + 2")?; // return type is i64
result.is::<i64>() == true;
let result = engine.eval::<Dynamic>("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
```
### Type inference
```rust
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
result.is::<i64>() == true;
let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
let result: String = engine.eval("40 + 2")?; // returns an error because the actual return type is i64, not String
```

View File

@ -0,0 +1,8 @@
Using the Engine
================
{{#include ../links.md}}
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.
This section shows how to set up, configure and use this scripting engine.

View File

@ -0,0 +1,184 @@
Generate Definition Files for Language Server
=============================================
{{#include ../../links.md}}
Rhai's [language server][lsp] works with IDEs to provide integrated support for the Rhai scripting language.
Functions and [modules] registered with an [`Engine`] can output their [metadata][functions metadata]
into _definition files_ which are used by the [language server][lsp].
Definitions are generated via the `Engine::definitions` and `Engine::definitions_with_scope` API.
This API requires the [`metadata`] and [`internals`] feature.
Configurable Options
--------------------
The `Definitions` type supports the following options in a fluent method-chaining style.
| Option | Method | Default |
| ------------------------------------------------------------------- | --------------------------- | :-----: |
| Write headers in definition files? | `with_headers` | `false` |
| Include [standard packages][built-in packages] in definition files? | `include_standard_packages` | `true` |
```rust
engine
.definitions()
.with_headers(true) // write headers in all files
.include_standard_packages(false) // skip standard packages
.write_to_dir("path/to/my/definitions")
.unwrap();
```
Example
-------
```rust
use rhai::{Engine, Scope};
use rhai::plugin::*;
// Plugin module: 'general_kenobi'
#[export_module]
pub mod general_kenobi {
use std::convert::TryInto;
/// Returns a string where "hello there" is repeated 'n' times.
pub fn hello_there(n: i64) -> String {
"hello there ".repeat(n.try_into().unwrap())
}
}
// Create scripting engine
let mut engine = Engine::new();
// Create custom Scope
let mut scope = Scope::new();
// This variable will also show up in the generated definition file.
scope.push("hello_there", "hello there");
// Static module namespaces will generate independent definition files.
engine.register_static_module(
"general_kenobi",
exported_module!(general_kenobi).into()
);
// Custom operators will also show up in the generated definition file.
engine.register_custom_operator("minus", 100).unwrap();
engine.register_fn("minus", |a: i64, b: i64| a - b);
engine.run_with_scope(&mut scope,
"hello_there = general_kenobi::hello_there(4 minus 2);"
)?;
// Output definition files in the specified directory.
engine
.definitions()
.write_to_dir("path/to/my/definitions")
.unwrap();
// Output definition files in the specified directory.
// Variables in the provided 'Scope' are included.
engine
.definitions_with_scope(&scope)
.write_to_dir("path/to/my/definitions")
.unwrap();
// Output a single definition file with everything merged.
// Variables in the provided 'Scope' are included.
engine
.definitions_with_scope(&scope)
.write_to_file("path/to/my/definitions/all_in_one.d.rhai")
.unwrap();
// Output functions metadata to a JSON string.
// Functions in standard packages are skipped and not included.
let json = engine
.definitions()
.include_standard_packages(false) // skip standard packages
.unwrap();
```
Definition Files
----------------
The generated definition files will look like the following.
```rust
┌───────────────────────┐
│ general_kenobi.d.rhai │
└───────────────────────┘
module general_kenobi;
/// Returns a string where "hello there" is repeated 'n' times.
fn hello_there(n: int) -> String;
┌──────────────────┐
__scope__.d.rhai │
└──────────────────┘
module static;
let hello_there;
┌───────────────────┐
__static__.d.rhai │
└───────────────────┘
module static;
op minus(int, int) -> int;
:
:
┌────────────────────┐
__builtin__.d.rhai │
└────────────────────┘
module static;
:
:
┌──────────────────────────────┐
__builtin-operators__.d.rhai │
└──────────────────────────────┘
module static;
:
:
```
All-in-One Definition File
--------------------------
`Definitions::write_to_file` generates a single definition file with everything merged in, like the following.
```rust
module static;
op minus(int, int) -> int;
:
:
module general_kenobi {
/// Returns a string where "hello there" is repeated 'n' times.
fn hello_there(n: int) -> String;
}
let hello_there;
```

View File

@ -0,0 +1,147 @@
Export Functions Metadata to JSON
=================================
{{#include ../../links.md}}
`Engine::gen_fn_metadata_to_json`<br/>`Engine::gen_fn_metadata_with_ast_to_json`
--------------------------------------------------------------------------------
As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` and the corresponding
`Engine::gen_fn_metadata_with_ast_to_json` export the full list of [custom types] and
[functions metadata] in JSON format.
~~~admonish warning.small "Requires `metadata`"
The [`metadata`] feature is required for this API, which also pulls in the
[`serde_json`](https://crates.io/crates/serde_json) crate.
~~~
### Sources
Functions and [custom types] from the following sources are included:
1. Script-defined functions in an [`AST`] (for `Engine::gen_fn_metadata_with_ast_to_json`)
2. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
3. [Custom types] registered into the global namespace via the `Engine::register_type_with_name` API
4. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) and [custom types] in static modules
registered via `Engine::register_static_module`
5. Native Rust functions and [custom types] in external [packages] registered via `Engine::register_global_module`
6. Native Rust functions and [custom types] in [built-in packages] (optional)
JSON Schema
-----------
The JSON schema used to hold metadata is very simple, containing a nested structure of
`modules`, a list of `customTypes` and a list of `functions`.
### Module Schema
```json
{
"doc": "//! Module documentation",
"modules":
{
"sub_module_1": /* namespace 'sub_module_1' */
{
"modules":
{
"sub_sub_module_A": /* namespace 'sub_module_1::sub_sub_module_A' */
{
"doc": "//! Module documentation can also occur in any sub-module",
"customTypes": /* custom types exported in 'sub_module_1::sub_sub_module_A' */
[
{ ... custom type metadata ... },
{ ... custom type metadata ... },
{ ... custom type metadata ... }
...
],
"functions": /* functions exported in 'sub_module_1::sub_sub_module_A' */
[
{ ... function metadata ... },
{ ... function metadata ... },
{ ... function metadata ... },
{ ... function metadata ... }
...
]
},
"sub_sub_module_B": /* namespace 'sub_module_1::sub_sub_module_B' */
{
...
}
}
},
"sub_module_2": /* namespace 'sub_module_2' */
{
...
},
...
},
"customTypes": /* custom types registered globally */
[
{ ... custom type metadata ... },
{ ... custom type metadata ... },
{ ... custom type metadata ... },
...
],
"functions": /* functions registered globally or in the 'AST' */
[
{ ... function metadata ... },
{ ... function metadata ... },
{ ... function metadata ... },
{ ... function metadata ... },
...
]
}
```
### Custom Type Metadata Schema
```json
{
"typeName": "alloc::string::String", /* name of Rust type */
"displayName": "MyType",
"docComments": /* omitted if none */
[
"/// My super-string type.",
...
]
}
```
### Function Metadata Schema
```json
{
"baseHash": 9876543210, /* partial hash with only number of parameters */
"fullHash": 1234567890, /* full hash with actual parameter types */
"namespace": "internal" | "global",
"access": "public" | "private",
"name": "fn_name",
"isAnonymous": false,
"type": "native" | "script",
"numParams": 42, /* number of parameters */
"params": /* omitted if no parameters */
[
{ "name": "param_1", "type": "type_1" },
{ "name": "param_2" }, /* no type name */
{ "type": "type_3" }, /* no parameter name */
...
],
"thisType": "this_type", /* omitted if none */
"returnType": "ret_type", /* omitted if () or unknown */
"signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type",
"docComments": /* omitted if none */
[
"/// doc-comment line 1",
"/// doc-comment line 2",
"/** doc-comment block */",
...
]
}
```

View File

@ -0,0 +1,93 @@
Get Native Function Signatures
==============================
{{#include ../../links.md}}
`Engine::gen_fn_signatures`
---------------------------
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_
(as `Vec<String>`), each corresponding to a particular native function available to that [`Engine`] instance.
> _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
The [`metadata`] feature must be used to turn on this API.
### Sources
Functions from the following sources are included, in order:
1. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
2. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules
registered via `Engine::register_static_module`.
3. Native Rust functions in external [packages] registered via `Engine::register_global_module`
4. Native Rust functions in [built-in packages] (optional)
Functions Metadata
------------------
Beware, however, that not all function signatures contain parameters and return value information.
### `Engine::register_XXX`
For instance, functions registered via `Engine::register_XXX` contain no information on the names of
parameter because Rust simply does not make such metadata available natively.
Type names, however, _are_ provided.
A function registered under the name `foo` with three parameters.
> `foo(_: i64, _: char, _: &str) -> String`
An [operator] function. Notice that function names do not need to be valid identifiers.
> `+=(_: &mut i64, _: i64)`
A [property setter][getters/setters].
Notice that function names do not need to be valid identifiers.
In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`:
> `set$prop(_: &mut TestStruct, _: i64)`
### Script-Defined Functions
Script-defined [function] signatures contain parameter names.
Since _all_ parameters, as well as the return value, are [`Dynamic`] the types are simply not shown.
> `foo(x, y, z)`
is probably defined simply as:
```rust
/// This is a doc-comment, included in this function's metadata.
fn foo(x, y, z) {
...
}
```
which is really the same as:
> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result<Dynamic, Box<EvalAltResult>>`
### Plugin Functions
Functions defined in [plugin modules] are the best.
They contain all metadata describing the functions, including [doc-comments].
For example, a plugin function `combine`:
> `/// This is a doc-comment, included in this function's metadata.`
> `combine(list: &mut MyStruct<i64>, num: usize, name: &str) -> bool`
Notice that function names do not need to be valid identifiers.
For example, an [operator] defined as a [fallible function] in a [plugin module] via
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
> `+=(list: &mut MyStruct<i64>, value: &str) -> Result<bool, Box<EvalAltResult>>`
For example, a [property getter][getters/setters] defined in a [plugin module]:
> `get$prop(obj: &mut MyStruct<i64>) -> String`

View File

@ -0,0 +1,49 @@
Functions and Custom Types Metadata
===================================
{{#include ../../links.md}}
~~~admonish warning.small "Requires `metadata`"
Exporting metadata requires the [`metadata`] feature.
~~~
Functions
---------
The _metadata_ of a [function] means all relevant information related to a function's
definition including:
1. Its callable name
2. Its access mode (public or [private][`private`])
3. Its parameter names and types (if any)
4. Its return value and type (if any)
5. Its nature (i.e. native Rust or Rhai-scripted)
6. Its [namespace][function namespace] ([module] or global)
7. Its purpose, in the form of [doc-comments]
8. Usage notes, warnings, examples etc., in the form of [doc-comments]
A function's _signature_ encapsulates the first four pieces of information in a single concise line
of definition:
> `[private]` _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
Custom Types
------------
The _metadata_ of a [custom type] include:
1. Its full Rust type name
2. Its pretty-print _display name_ (which can be the same as its Rust type name)
3. Its purpose, in the form of [doc-comments]

View File

@ -0,0 +1,212 @@
Constants Propagation
=====================
{{#include ../../links.md}}
```admonish tip.side
Effective in template-based machine-generated scripts to turn on/off certain sections.
```
[Constants] propagation is commonly used to:
* remove dead code,
* avoid [variable] lookups,
* [pre-calculate](op-eval.md) [constant] expressions.
```rust
const ABC = true;
const X = 41;
if ABC || calc(X+1) { print("done!"); } // 'ABC' is constant so replaced by 'true'...
// 'X' is constant so replaced by 41...
if true || calc(42) { print("done!"); } // '41+1' is replaced by 42
// since '||' short-circuits, 'calc' is never called
if true { print("done!"); } // <- the line above is equivalent to this
print("done!"); // <- the line above is further simplified to this
// because the condition is always true
```
~~~admonish tip "Tip: Custom `Scope` constants"
[Constant] values can be provided in a custom [`Scope`] object to the [`Engine`]
for optimization purposes.
```rust
use rhai::{Engine, Scope};
let engine = Engine::new();
let mut scope = Scope::new();
// Add constant to custom scope
scope.push_constant("ABC", true);
// Evaluate script with custom scope
engine.run_with_scope(&mut scope,
r#"
if ABC { // 'ABC' is replaced by 'true'
print("done!");
}
"#)?;
```
~~~
~~~admonish tip "Tip: Customer module constants"
[Constants] defined in [modules] that are registered into an [`Engine`] via
`Engine::register_global_module` are used in optimization.
```rust
use rhai::{Engine, Module};
let mut engine = Engine::new();
let mut module = Module::new();
// Add constant to module
module.set_var("ABC", true);
// Register global module
engine.register_global_module(module.into());
// Evaluate script
engine.run(
r#"
if ABC { // 'ABC' is replaced by 'true'
print("done!");
}
"#)?;
```
~~~
~~~admonish danger "Caveat: Constants in custom scope and modules are also propagated into functions"
[Constants] defined at _global_ level typically cannot be seen by script [functions] because they are _pure_.
```rust
const MY_CONSTANT = 42; // <- constant defined at global level
print(MY_CONSTANT); // <- optimized to: print(42)
fn foo() {
MY_CONSTANT // <- not optimized: 'foo' cannot see 'MY_CONSTANT'
}
print(foo()); // error: 'MY_CONSTANT' not found
```
When [constants] are provided in a custom [`Scope`] (e.g. via `Engine::compile_with_scope`,
`Engine::eval_with_scope` or `Engine::run_with_scope`), or in a [module] registered via
`Engine::register_global_module`, instead of defined within the same script, they are also
propagated to [functions].
This is usually the intuitive usage and behavior expected by regular users, even though it means
that a script will behave differently (essentially a runtime error) when [script optimization] is disabled.
```rust
use rhai::{Engine, Scope};
let engine = Engine::new();
let mut scope = Scope::new();
// Add constant to custom scope
scope.push_constant("MY_CONSTANT", 42_i64);
engine.run_with_scope(&mut scope,
"
print(MY_CONSTANT); // optimized to: print(42)
fn foo() {
MY_CONSTANT // optimized to: fn foo() { 42 }
}
print(foo()); // prints 42
")?;
```
The script will act differently when [script optimization] is disabled because script [functions]
are _pure_ and typically cannot see [constants] within the custom [`Scope`].
Therefore, constants in [functions] now throw a runtime error.
```rust
use rhai::{Engine, Scope, OptimizationLevel};
let mut engine = Engine::new();
// Turn off script optimization, no constants propagation is performed
engine.set_optimization_level(OptimizationLevel::None);
let mut scope = Scope::new();
// Add constant to custom scope
scope.push_constant("MY_CONSTANT", 42_i64);
engine.run_with_scope(&mut scope,
"
print(MY_CONSTANT); // prints 42
fn foo() {
MY_CONSTANT // <- 'foo' cannot see 'MY_CONSTANT'
}
print(foo()); // error: 'MY_CONSTANT' not found
")?;
```
~~~
~~~admonish danger "Caveat: Beware of large constants"
[Constants] propagation replaces each usage of the [constant] with a clone of its value.
This may have negative implications to performance if the [constant] value is expensive to clone
(e.g. if the type is very large).
```rust
let mut scope = Scope::new();
// Push a large constant into the scope...
let big_type = AVeryLargeType::take_long_time_to_create();
scope.push_constant("MY_BIG_TYPE", big_type);
// Causes each usage of 'MY_BIG_TYPE' in the script below to be replaced
// by cloned copies of 'AVeryLargeType'.
let result = engine.run_with_scope(&mut scope,
"
let value = MY_BIG_TYPE.value;
let data = MY_BIG_TYPE.data;
let len = MY_BIG_TYPE.len();
let has_options = MY_BIG_TYPE.has_options();
let num_options = MY_BIG_TYPE.options_len();
")?;
```
To avoid this, compile the script first to an [`AST`] _without_ the [constants], then evaluate the
[`AST`] (e.g. with `Engine::eval_ast_with_scope` or `Engine::run_ast_with_scope`) together with
the [constants].
~~~
~~~admonish danger "Caveat: Constants may be modified by Rust methods"
If the [constants] are modified later on (yes, it is possible, via Rust _methods_),
the modified values will not show up in the optimized script.
Only the initialization values of [constants] are ever retained.
```rust
const MY_SECRET_ANSWER = 42;
MY_SECRET_ANSWER.update_to(666); // assume 'update_to(&mut i64)' is a Rust function
print(MY_SECRET_ANSWER); // prints 42 because the constant is propagated
```
This is almost never a problem because real-world scripts seldom modify a [constant],
but the possibility is always there.
~~~

View File

@ -0,0 +1,51 @@
Dead Code Elimination
=====================
{{#include ../../links.md}}
```admonish question.side.wide "But who writes dead code?"
Nobody deliberately writes scripts with dead code (we hope).
They are, however, extremely common in template-based machine-generated scripts.
```
Rhai attempts to eliminate _dead code_.
"Dead code" is code that does nothing and has no side effects.
Example is an pure expression by itself as a statement (allowed in Rhai).
The result of the expression is calculated then immediately discarded and not used.
```rust
{
let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval')
123; // eliminated: no effect
"hello"; // eliminated: no effect
[1, 2, x, 4]; // eliminated: no effect
if 42 > 0 { // '42 > 0' is replaced by 'true' and the first branch promoted
foo(42); // promoted, NOT eliminated: the function 'foo' may have side-effects
} else {
bar(x); // eliminated: branch is never reached
}
let z = x; // eliminated: local variable, no side-effects, and only pure afterwards
666 // NOT eliminated: this is the return value of the block,
// and the block is the last one so this is the return value of the whole script
}
```
The above script optimizes to:
```rust
{
let x = 999;
foo(42);
666
}
```

View File

@ -0,0 +1,25 @@
Turn Off Script Optimizations
=============================
{{#include ../../links.md}}
When scripts:
* are known to be run only _once_ and then thrown away,
* are known to contain no dead code,
* do not use constants in calculations
the optimization pass may be a waste of time and resources.
In that case, turn optimization off by setting the optimization level to [`OptimizationLevel::None`].
```rust
let engine = rhai::Engine::new();
// Turn off the optimizer
engine.set_optimization_level(rhai::OptimizationLevel::None);
```
Alternatively, disable optimizations via the [`no_optimize`] feature.

View File

@ -0,0 +1,75 @@
Eager Function Evaluation When Using Full Optimization Level
============================================================
{{#include ../../links.md}}
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to
be _pure_ and will _eagerly_ evaluated all function calls with constant arguments, using the result
to replace the call.
This also applies to all operators (which are implemented as functions).
```rust
// When compiling the following with OptimizationLevel::Full...
const DECISION = 1;
// this condition is now eliminated because 'sign(DECISION) > 0'
if sign(DECISION) > 0 // <- is a call to the 'sign' and '>' functions, and they return 'true'
{
print("hello!"); // <- this block is promoted to the parent level
} else {
print("boo!"); // <- this block is eliminated because it is never reached
}
print("hello!"); // <- the above is equivalent to this
// ('print' and 'debug' are handled specially)
```
~~~admonish danger "Won't this be dangerous?"
Yes! _Very!_
```rust
// Nuclear silo control
if launch_nukes && president_okeyed {
print("This is NOT a drill!");
update_defcon(1);
start_world_war(3);
launch_all_nukes();
} else {
print("This is a drill. Thank you for your cooperation.");
}
```
In the script above (well... as if nuclear silos will one day be controlled by Rhai scripts),
the functions `update_defcon`, `start_world_war` and `launch_all_nukes` will be evaluated
during _compilation_ because they have constant arguments.
The [variables] `launch_nukes` and `president_okeyed` are never checked, because the script
actually has not yet been run! The functions are called during compilation.
This is, _obviously_, not what you want.
**Moral of the story: compile with an [`Engine`] that does not have any functions registered.
Register functions _AFTER_ compilation.**
~~~
~~~admonish question "Why would I ever want to do this then?"
Good question! There are two reasons:
* A function call may result in cleaner code than the resultant value.
In Rust, this would have been handled via a `const` function.
* Evaluating a value to a [custom type] that has no representation in script.
```rust
// A complex function that returns a unique ID based on the arguments
let id = make_unique_id(123, "hello", true);
// The above is arguably clearer than:
// let id = 835781293546; // generated from 123, "hello" and true
// A custom type that cannot be represented in script
let complex_obj = make_complex_obj(42);
```
~~~

View File

@ -0,0 +1,62 @@
Script Optimization
===================
{{#title Script Optimization}}
{{#include ../../links.md}}
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
This can reduce resource utilization and increase execution speed.
Script optimization can be turned off via the [`no_optimize`] feature.
Optimization Levels
===================
{{#include ../../links.md}}
There are three levels of optimization: `None`, `Simple` and `Full`.
The default is `Simple`.
An [`Engine`]'s optimization level is set via [`Engine::set_optimization_level`][options].
```rust
// Turn on aggressive optimizations
engine.set_optimization_level(rhai::OptimizationLevel::Full);
```
`None`
------
`None` is obvious &ndash; no optimization on the AST is performed.
`Simple` (Default)
------------------
`Simple` performs only relatively _safe_ optimizations without causing side-effects (i.e. it only
relies on static analysis and [built-in operators] for [constant] [standard types], and will not
perform any external function calls).
```admonish warning.small
After _constants propagation_ is performed, if the [constants] are then modified (yes, it is possible, via Rust functions),
the modified values will _not_ show up in the optimized script.
Only the initialization values of [constants] are ever retained.
```
```admonish warning.small
Overriding a [built-in operator] in the [`Engine`] afterwards has no effect after the
optimizer replaces an expression with its calculated value.
```
`Full`
------
`Full` is _much_ more aggressive, _including_ calling external functions on [constant] arguments to
determine their results.
One benefit to this is that many more optimization opportunities arise, especially with regards to
comparison operators.

View File

@ -0,0 +1,85 @@
Eager Operator Evaluation
=========================
{{#include ../../links.md}}
Most operators are actually function calls, and those functions can be overridden, so whether they
are optimized away depends on the situation:
```admonish info.side.wide "No external functions"
Rhai guarantees that no external function will be run, which may trigger side-effects
(unless the optimization level is [`OptimizationLevel::Full`]).
```
* if the operands are not [constant] values, it is **not** optimized;
* if the [operator] is [overloaded][operator overloading], it is **not** optimized because the
overloading function may not be _pure_ (i.e. may cause side-effects when called);
* if the [operator] is not _built-in_ (see list of [built-in operators]), it is **not** optimized;
* if the [operator] is a [built-in operator] for a [standard type][standard types], it is called and
replaced by a [constant] result.
```js
// The following is most likely generated by machine.
const DECISION = 1; // this is an integer, one of the standard types
if DECISION == 1 { // this is optimized into 'true'
:
} else if DECISION == 2 { // this is optimized into 'false'
:
} else if DECISION == 3 { // this is optimized into 'false'
:
} else {
:
}
// Or an equivalent using 'switch':
switch DECISION {
1 => ..., // this statement is promoted
2 => ..., // this statement is eliminated
3 => ..., // this statement is eliminated
_ => ... // this statement is eliminated
}
```
Pre-Evaluation of Constant Expressions
--------------------------------------
Because of the eager evaluation of [operators][built-in operators] for [standard types], many
[constant] expressions will be evaluated and replaced by the result.
```rust
let x = (1+2) * 3 - 4/5 % 6; // will be replaced by 'let x = 9'
let y = (1 > 2) || (3 <= 4); // will be replaced by 'let y = true'
```
For operators that are not optimized away due to one of the above reasons, the function calls are
simply left behind.
```rust
// Assume 'new_state' returns some custom type that is NOT one of the standard types.
// Also assume that the '==' operator is defined for that custom type.
const DECISION_1 = new_state(1);
const DECISION_2 = new_state(2);
const DECISION_3 = new_state(3);
if DECISION == 1 { // NOT optimized away because the operator is not built-in
: // and may cause side-effects if called!
:
} else if DECISION == 2 { // same here, NOT optimized away
:
} else if DECISION == 3 { // same here, NOT optimized away
:
} else {
:
}
```
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].

View File

@ -0,0 +1,21 @@
Optimization Passes
===================
{{#include ../../links.md}}
[Script optimization] is performed via multiple _passes_.
Each pass does a specific optimization.
The optimization is completed when no passes can simplify the [`AST`] any further.
Built-in Optimization Passes
----------------------------
| Pass | Description |
| ------------------------------------------ | ------------------------------------------------- |
| [Dead code elimination](dead-code.md) | Eliminates code that cannot be reached |
| [Constants propagation](constants.md) | Replaces [constants] with values |
| [Compound assignments rewrite](rewrite.md) | Rewrites assignments into compound assignments |
| [Eager operator evaluation](op-eval.md) | Eagerly calls operators with [constant] arguments |
| [Eager function evaluation](eager.md) | Eagerly calls functions with [constant] arguments |

View File

@ -0,0 +1,50 @@
Re-Optimize an AST
==================
{{#include ../../links.md}}
Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by
constant variables. This script is compiled once to an [`AST`].
Then, depending on the execution environment, constants are passed into the [`Engine`] and the
[`AST`] is _re_-optimized based on those constants via `Engine::optimize_ast`, effectively pruning
out unused code sections.
The final, optimized [`AST`] is then used for evaluations.
```rust
// Compile master script to AST
let master_ast = engine.compile(
"
fn do_work() {
// Constants in scope are also propagated into functions
print(SCENARIO);
}
switch SCENARIO {
1 => do_work(),
2 => do_something(),
3 => do_something_else(),
_ => do_nothing()
}
")?;
for n in 0..5_i64 {
// Create a new scope - put constants in it to aid optimization
let mut scope = Scope::new();
scope.push_constant("SCENARIO", n);
// Re-optimize the AST
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
// Run it
engine.run_ast(&new_ast)?;
}
```
```admonish note.small "Constants propagation"
Beware that [constants] inside the custom [`Scope`] will also be propagated to [functions] defined
within the script while normally such [functions] are _pure_ and cannot see [variables]/[constants]
within the global [`Scope`].
```

View File

@ -0,0 +1,47 @@
Compound Assignment Rewrite
===========================
{{#include ../../links.md}}
```admonish info.side "Avoid cloning"
Arguments passed as value are always cloned.
```
Usually, a _compound assignment_ (e.g. `+=` for append) takes a mutable first parameter
(i.e. `&mut`) while the corresponding simple [operator] (i.e. `+`) does not.
The script optimizer rewrites normal assignments into _compound assignments_ wherever possible in
order to avoid unnecessary cloning.
```rust
let big = create_some_very_big_type();
big = big + 1;
// ^ 'big' is cloned here
// The above is equivalent to:
let temp_value = big + 1;
big = temp_value;
big += 1; // <- 'big' is NOT cloned
```
~~~admonish warning.small "Warning: Simple references only"
Only _simple variable references_ are optimized.
No [_common sub-expression elimination_](https://en.wikipedia.org/wiki/Common_subexpression_elimination)
is performed by Rhai.
```rust
x = x + 1; // <- this statement...
x += 1; // <- ... is rewritten to this
x[y] = x[y] + 1; // <- but this is not,
// so MUCH slower...
x[y] += 1; // <- ... than this
```
~~~

View File

@ -0,0 +1,85 @@
Subtle Semantic Changes After Optimization
==========================================
{{#include ../../links.md}}
Some optimizations can alter subtle semantics of the script, causing the script to behave
differently when run with or without optimization.
Typically, this involves some form of error that may arise in the original, unoptimized script but
is optimized away by the [script optimizer][script optimization].
```admonish danger.small "DO NOT depend on runtime errors"
Needless to say, it is usually a _Very Bad Idea™_ to depend on a script failing with a runtime error
or such kind of subtleties.
If it turns out to be necessary (why? I would never guess), turn script optimization off by setting
the optimization level to [`OptimizationLevel::None`].
```
Disappearing Runtime Errors
---------------------------
For example:
```rust
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level
}
foo(42) // <- the above optimizes to this
```
If the original script were evaluated instead, it would have been an error &ndash;
the variable `hello` does not exist, so the script would have been terminated at that point
with a runtime error.
In fact, any errors inside a statement that has been eliminated will silently _disappear_.
```rust
print("start!");
if my_decision { /* do nothing... */ } // eliminated due to no effect
print("end!");
// The above optimizes to:
print("start!");
print("end!");
```
In the script above, if `my_decision` holds anything other than a boolean value,
the script should have been terminated due to a type error.
However, after optimization, the entire [`if`] statement is removed (because an access to
`my_decision` produces no side-effects), thus the script silently runs to completion without errors.
Eliminated Useless Work
-----------------------
Another example is more subtle &ndash; that of an empty loop body.
```rust
// ... say, the 'Engine' is limited to no more than 10,000 operations...
// The following should fail because it exceeds the operations limit:
for n in 0..42000 {
// empty loop
}
// The above is optimized away because the loop body is empty
// and the iterations simply do nothing.
()
```
Normally, and empty loop body inside a [`for`] statement with a pure iterator does nothing and can
be safely eliminated.
Thus the script now runs silently to completion without errors.
Without optimization, the script may fail by exceeding the [maximum number of operations] allowed.

View File

@ -0,0 +1,23 @@
Side-Effect Considerations for Full Optimization Level
======================================================
{{#include ../../links.md}}
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_
custom types and functions are registered.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie
within a pruned code block).
If custom functions are registered to overload [built-in operators], they will also be called when
the operators are used (in an [`if`] statement, for example), potentially causing side-effects.
```admonish tip.small "Rule of thumb"
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and,
when that happens, existing scripts may break in subtle ways.
```

View File

@ -0,0 +1,53 @@
Volatility Considerations for Full Optimization Level
=====================================================
{{#include ../../links.md}}
Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_,
i.e. it _depends_ on external environment and does not guarantee the same result for the same inputs.
A perfect example is a function that gets the current time &ndash; obviously each run will return a
different value!
```rust
print(get_current_time(true)); // prints the current time
// notice the call to 'get_current_time'
// has constant arguments
// The above, under full optimization level, is rewritten to:
print("10:25AM"); // the function call is replaced by
// its result at the time of optimization!
```
```admonish danger.small "Warning"
**Avoid using [`OptimizationLevel::Full`]** if volatile custom functions are involved.
```
The optimizer, when using [`OptimizationLevel::Full`], _merrily assumes_ that all functions are
_non-volatile_, so when it finds [constant] arguments (or none) it eagerly executes the function
call and replaces it with the result.
This causes the script to behave differently from the intended semantics.
~~~admonish tip "Tip: Mark a function as volatile"
All native functions are assumed to be **non-volatile**, meaning that they are eagerly called under
[`OptimizationLevel::Full`] when all arguments are [constant] (or none).
It is possible to [mark a function defined within a plugin module as volatile]({{rootUrl}}/plugins/module.md#volatile-functions)
to prevent this behavior.
```rust
#[export_module]
mod my_module {
// This function is marked 'volatile' and will not be
// eagerly executed even under OptimizationLevel::Full.
#[rhai_fn(volatile)]
pub get_current_time(am_pm: bool) -> String {
// ...
}
}
```
~~~

View File

@ -0,0 +1,54 @@
Engine Configuration Options
============================
{{#include ../links.md}}
A number of other configuration options are available from the [`Engine`] to fine-tune behavior and safeguards.
Compile-Time Language Features
------------------------------
| Method | Description | Default |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | :-------------------------------------: |
| `set_optimization_level`<br/>(not available under [`no_optimize`]) | sets the amount of script _optimizations_ performed (see [script optimization]) | [`Simple`][`OptimizationLevel::Simple`] |
| `set_allow_if_expression` | allows/disallows [`if`-expressions]({{rootUrl}}/language/if.md#if-expression) | allow |
| `set_allow_switch_expression` | allows/disallows [`switch` expressions]({{rootUrl}}/language/switch-expression.md) | allow |
| `set_allow_loop_expressions` | allows/disallows loop expressions | allow |
| `set_allow_statement_expression` | allows/disallows [statement expressions]({{rootUrl}}/language/statement-expression.md) | allow |
| `set_allow_anonymous_fn`<br/>(not available under [`no_function`]) | allows/disallows [anonymous functions] | allow |
| `set_allow_looping` | allows/disallows looping (i.e. [`while`], [`loop`], [`do`] and [`for`] statements) | allow |
| `set_allow_shadowing` | allows/disallows _[shadowing]_ of [variables] | allow |
| `set_strict_variables` | enables/disables [_Strict Variables_ mode][strict variables] | disabled |
| `set_fast_operators` | enables/disables [_Fast Operators_ mode][fast operators] | enabled |
| `disable_symbol` | disables a certain [keyword] or [operator] (see [disable keywords and operators]) | |
Beware that these options activate during _compile-time_ only. If an [`AST`] is compiled on an
[`Engine`] but then evaluated on a different [`Engine`] with different configuration, disallowed
features contained inside the [`AST`] will still run as normal.
Runtime Behavior
----------------
| Method | Description |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `set_fail_on_invalid_map_property`<br/>(not available under [`no_object`]) | sets whether to raise errors (instead of returning [`()`]) when invalid properties are accessed on [object maps] |
| `set_default_tag` | sets the default value of the _custom state_ (which can be obtained via [`NativeCallContext::tag`][`NativeCallContext`]) for each evaluation run |
Safety Limits
-------------
| Method | Not available under | Description |
| -------------------------- | :----------------------------: | --------------------------------------------------------------------------------------------------------------------------------------- |
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement (see [maximum statement depth]) |
| `set_max_call_levels` | [`unchecked`], [`no_function`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion (see [maximum call stack depth]) |
| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume (see [maximum number of operations]) |
| `set_max_variables` | [`unchecked`] | sets the maximum number of [variables] that a script is allowed to define within a single [`Scope`] (see [maximum number of variables]) |
| `set_max_functions` | [`unchecked`], [`no_function`] | sets the maximum number of [functions] that a script is allowed to define (see [maximum number of functions]) |
| `set_max_modules` | [`unchecked`], [`no_modules`] | sets the maximum number of [modules] that a script is allowed to load (see [maximum number of modules]) |
| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings] (see [maximum length of strings]) |
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays] (see [maximum size of arrays]) |
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps] (see [maximum size of object maps]) |
| `set_max_strings_interned` | | sets the maximum number of [strings] to be interned (if zero, the [strings interner] is disabled) |

View File

@ -0,0 +1,28 @@
Operator Precedence
===================
{{#include ../links.md}}
All operators in Rhai has a _precedence_ indicating how tightly they bind.
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
When registering a custom operator, the operator's precedence must also be provided.
The following _precedence table_ shows the built-in precedence of standard Rhai operators:
| Category | Operators | Binding | Precedence (0-255) |
| ------------------- | :--------------------------------------: | :-----: | :----------------: |
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | left | 30 |
| Logic and bit masks | `&&`, `&` | left | 60 |
| Comparisons | `==`, `!=` | left | 90 |
| Containment | [`in`] | left | 110 |
| Comparisons | `>`, `>=`, `<`, `<=` | left | 130 |
| Null-coalesce | `??` | left | 135 |
| Ranges | `..`, `..=` | left | 140 |
| Arithmetic | `+`, `-` | left | 150 |
| Arithmetic | `*`, `/`, `%` | left | 180 |
| Arithmetic | `**` | right | 190 |
| Bit-shifts | `<<`, `>>` | left | 210 |
| Unary operators | `+`, `-`, `!` | right | highest |
| Object field access | `.`, `?.` | right | highest |

View File

@ -0,0 +1,76 @@
Raw `Engine`
============
{{#include ../links.md}}
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to `stdout`
via [`print`] or [`debug`]).
In many controlled embedded environments, however, these may not be needed and unnecessarily occupy
application code storage space.
```admonish info.side.wide "Built-in operators"
Even with a raw [`Engine`], some operators are built-in and always available.
See [_Built-in Operators_][built-in operators] for a full list.
```
Use `Engine::new_raw` to create a _raw_ [`Engine`], in which only a minimal set of
[built-in][built-in operators] basic arithmetic and logical operators are supported.
To add more functionalities to a _raw_ [`Engine`], load [packages] into it.
Since [packages] can be _shared_, this is an extremely efficient way to create multiple instances of
the same [`Engine`] with the same set of functions.
| | `Engine::new` | `Engine::new_raw` |
| --------------------- | :------------------: | :---------------: |
| [Built-in operators] | yes | yes |
| [Package] loaded | `StandardPackage` | _none_ |
| [Module resolver] | `FileModuleResolver` | _none_ |
| [Strings interner] | yes | _no_ |
| [`on_print`][`print`] | yes | _none_ |
| [`on_debug`][`debug`] | yes | _none_ |
```admonish warning.small "Warning: No strings interner"
A _raw_ [`Engine`] disables the _[strings interner]_ by default.
This may lead to a significant increase in memory usage if many strings are created in scripts.
Turn the _[strings interner]_ back on via [`Engine::set_max_strings_interned`][options].
```
~~~admonish example "`Engine::new` is equivalent to..."
```rust
use rhai::module_resolvers::FileModuleResolver;
use rhai::packages::StandardPackage;
// Create a raw scripting Engine
let mut engine = Engine::new_raw();
// Use the file-based module resolver
engine.set_module_resolver(FileModuleResolver::new());
// Enable the strings interner
engine.set_max_strings_interned(1024);
// Default print/debug implementations
engine.on_print(|text| println!("{text}"));
engine.on_debug(|text, source, pos| match (source, pos) {
(Some(source), Position::NONE) => println!("{source} | {text}"),
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
(None, Position::NONE) => println!("{text}"),
(None, pos) => println!("{pos:?} | {text}"),
});
// Register the Standard Package
let package = StandardPackage::new();
// Load the package into the [`Engine`]
package.register_into_engine(&mut engine);
```
~~~

View File

@ -0,0 +1,215 @@
`Scope` &ndash; Maintaining State
=================================
{{#include ../links.md}}
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions
that have been registered but no global state.
This gives each evaluation a clean starting slate.
In order to continue using the same global state from one invocation to the next, such a state
(a `Scope`) must be manually created and passed in.
All `Scope` [variables] and [constants] have values that are [`Dynamic`], meaning they can store
values of any type.
Under [`sync`], however, only types that are `Send + Sync` are supported, and the entire `Scope`
itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications.
```admonish info.small "Shadowing"
A newly-added [variable] or [constant] _[shadows][shadow]_ previous ones of the same name.
In other words, all versions are kept for [variables] and [constants], but only the latest ones can
be accessed via `get_value<T>`, `get_mut<T>` and `set_value<T>`.
Essentially, a `Scope` is always searched in _reverse order_.
```
```admonish tip.small "Tip: The lifetime parameter"
`Scope` has a _lifetime_ parameter, in the vast majority of cases it can be omitted and
automatically inferred to be `'static`.
Currently, that lifetime parameter is not used. It is there to maintain backwards compatibility
as well as for possible future expansion when references can also be put into the `Scope`.
The lifetime parameter is not guaranteed to remain unused for future versions.
In order to put a `Scope` into a `struct`, use `Scope<'static>`.
```
~~~admonish tip.small "Tip: The `const` generic parameter"
`Scope` also has a _`const` generic_ parameter, which is a number that defaults to 8.
It indicates the number of entries that the `Scope` can keep _inline_ without allocations.
The larger this number, the larger the `Scope` type gets, but allocations will happen far
less frequently.
A smaller number makes `Scope` smaller, but allocation costs will be incurred when the
number of entries exceed the _inline_ capacity.
~~~
`Scope` API
-----------
| Method | Description |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `new` _instance method_ | create a new empty `Scope` |
| `with_capacity` _instance method_ | create a new empty `Scope` with a specified initial capacity |
| `len` | number of [variables]/[constants] currently within the `Scope` |
| `rewind` | _rewind_ (i.e. reset) the `Scope` to a particular number of [variables]/[constants] |
| `clear` | remove all [variables]/[constants] from the `Scope`, making it empty |
| `is_empty` | is the `Scope` empty? |
| `is_constant` | is the particular [variable]/[constant] in the `Scope` a [constant]? |
| `push`, `push_constant` | add a new [variable]/[constant] into the `Scope` with a specified value |
| `push_dynamic`, `push_constant_dynamic` | add a new [variable]/[constant] into the `Scope` with a [`Dynamic`] value |
| `set_or_push<T>` | set the value of the last [variable] within the `Scope` by name if it exists and is not [constant]; add a new [variable] into the `Scope` otherwise |
| `contains` | does the particular [variable] or [constant] exist in the `Scope`? |
| `get_value<T>` | get the value of the last [variable]/[constant] within the `Scope` by name |
| `set_value<T>` | set the value of the last [variable] within the `Scope` by name, panics if it is [constant] |
| `remove<T>` | remove the last [variable]/[constant] from the `Scope` by name, returning its value |
| `get` | get a reference to the value of the last [variable]/[constant] within the `Scope` by name |
| `get_mut` | get a reference to the value of the last [variable] within the `Scope` by name, `None` if it is [constant] |
| `set_alias` | [exported][`export`] the last [variable]/[constant] within the `Scope` by name |
| `iter`, `iter_raw`, `IntoIterator::into_iter` | get an iterator to the [variables]/[constants] within the `Scope` |
| `Extend::extend` | add [variables]/[constants] to the `Scope` |
~~~admonish info.small "`Scope` public API"
For details on the `Scope` API, refer to the
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Scope.html) online.
~~~
Serializing/Deserializing
-------------------------
With the [`serde`] feature, `Scope` is serializable and deserializable via
[`serde`](https://crates.io/crates/serde).
[Custom types] stored in the `Scope`, however, are serialized as full type-name strings.
Data in [custom types] are not serialized.
Example
-------
In the following example, a `Scope` is created with a few initialized variables, then it is threaded
through multiple evaluations.
```rust
use rhai::{Engine, Scope, EvalAltResult};
let engine = Engine::new();
// First create the state
let mut scope = Scope::new();
// Then push (i.e. add) some initialized variables into the state.
// Remember the system number types in Rhai are i64 (i32 if 'only_i32')
// and f64 (f32 if 'f32_float').
// Better stick to them or it gets hard working with the script.
scope.push("y", 42_i64)
.push("z", 999_i64)
.push_constant("MY_NUMBER", 123_i64) // constants can also be added
.set_value("s", "hello, world!"); // 'set_value' adds a new variable when one doesn't exist
// First invocation
engine.run_with_scope(&mut scope,
"
let x = 4 + 5 - y + z + MY_NUMBER + s.len;
y = 1;
")?;
// Second invocation using the same state.
// Notice that the new variable 'x', defined previously, is still here.
let result = engine.eval_with_scope::<i64>(&mut scope, "x + y")?;
println!("result: {result}"); // prints 1103
// Variable y is changed in the script - read it with 'get_value'
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);
// We can modify scope variables directly with 'set_value'
scope.set_value("y", 42_i64);
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 42);
```
`Engine` API Using `Scope`
--------------------------
[`Engine`] API methods that accept a `Scope` parameter all end in `_with_scope`, making that
`Scope` (and everything inside it) available to the script:
| `Engine` API | Not available under |
| --------------------------------------- | :-----------------: |
| `Engine::eval_with_scope` | |
| `Engine::eval_ast_with_scope` | |
| `Engine::eval_file_with_scope` | [`no_std`] |
| `Engine::eval_expression_with_scope` | |
| `Engine::run_with_scope` | |
| `Engine::run_ast_with_scope` | |
| `Engine::run_file_with_scope` | [`no_std`] |
| `Engine::compile_file_with_scope` | [`no_std`] |
| `Engine::compile_expression_with_scope` | |
~~~admonish danger "Don't forget to `rewind`"
[Variables] or [constants] defined at the global level of a script persist inside the custom `Scope`
even after the script ends.
```rust
let mut scope = Scope::new();
engine.run_with_scope(&mut scope, "let x = 42;")?;
// Variable 'x' stays inside the custom scope!
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
```
Due to [variable shadowing][shadowing], new [variables]/[constants] are simply added on top of
existing ones (even when they already exist), so care must be taken that new [variables]/[constants]
inside the custom `Scope` do not grow without bounds.
```rust
let mut scope = Scope::new();
// Don't do this - this creates 1 million variables named 'x'
// inside 'scope'!!!
for _ in 0..1_000_000 {
engine.run_with_scope(&mut scope, "let x = 42;")?;
}
// The 'scope' contains a LOT of variables...
assert_eq!(scope.len(), 1_000_000);
// Variable 'x' stays inside the custom scope!
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
```
In order to remove [variables] or [constants] introduced by a script, use the `rewind` method.
```rust
// Run a million times
for _ in 0..1_000_000 {
// Save the current size of the 'scope'
let orig_scope_size = scope.len();
engine.run_with_scope(&mut scope, "let x = 42;")?;
// Rewind the 'scope' to the original size
scope.rewind(orig_scope_size);
}
// The 'scope' is empty
assert_eq!(scope.len(), 0);
// Variable 'x' is no longer inside 'scope'!
engine.run_with_scope(&mut scope, "print(x);")?; // error: variable 'x' not found
```
~~~

View File

@ -0,0 +1,75 @@
Strict Variables Mode
=====================
{{#include ../links.md}}
~~~admonish tip.side "`Scope` constants"
[Constants] in the external [`Scope`], when provided, count as definition.
~~~
By default, Rhai looks up access to [variables] from the enclosing block scope,
working its way outwards until it reaches the top (global) level, then it
searches the [`Scope`] (if any) that is passed into the `Engine::eval_with_scope` call.
Setting [`Engine::set_strict_variables`][options] to `true` turns on _Strict Variables Mode_,
which requires that:
* all [variables]/[constants] be defined within the same script before use,
or they must be [variables]/[constants] within the provided [`Scope`] (if any),
* [modules] must be [imported][`import`], also within the same script, before use.
Within _Strict Variables_ mode, any attempt to access a [variable] or [module] before
definition/[import][`import`] results in a parse error.
This way, variable access errors (usually typos) are caught during compile time instead of runtime.
```rust
let x = 42;
let y = x * z; // <- parse error under strict variables mode:
// variable 'z' is not yet defined
let z = x + w; // <- parse error under strict variables mode:
// variable 'w' is undefined
foo::bar::baz(); // <- parse error under strict variables mode:
// module 'foo' is not yet defined
fn test1() {
foo::bar::baz(); // <- parse error under strict variables mode:
// module 'foo' is defined
}
import "my_module" as foo;
foo::bar::baz(); // ok!
print(foo::xyz); // ok!
let x = abc::def; // <- parse error under strict variables mode:
// module 'abc' is undefined
fn test2() {
foo:bar::baz(); // ok!
}
```
```admonish question "TL;DR &ndash; Why isn't there a _Strict Functions_ mode?"
Why can't function calls be checked for validity as well?
Rust functions in Rhai can be [overloaded][function overloading]. This means that multiple versions of
the same Rust function can exist under the same name, each accepting different numbers and/or types
of arguments.
While it is possible to check, at compile time, whether a [variable] has been previously declared,
it is impossible to predict, at compile time, the _types_ of arguments to function calls, unless the
function in question takes no parameters.
Therefore, it is impossible to check, at compile time, whether a function call is valid given that
the types of arguments are unknown until runtime. QED.
Not to mention that it is also impossible to check for a function called via a [function pointer].
```

View File

@ -0,0 +1,79 @@
Remap Tokens During Parsing
===========================
{{#include ../links.md}}
[`Engine::on_parse_token`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_parse_token
[`Token`]: https://docs.rs/rhai/{{version}}/rhai/enum.Token.html
The Rhai [`Engine`] first parses a script into a stream of _tokens_.
Tokens have the type [`Token`] which is only exported under [`internals`].
The function [`Engine::on_parse_token`], available only under [`internals`], allows registration of a
_mapper function_ that converts (remaps) a [`Token`] into another.
```admonish tip.small "Hot Tips: Use as safety checks"
Since it is called for _every_ token parsed from the script, this token mapper function
can also be used to implement _safety checks_ against, say, stack-overflow or out-of-memory
situations during parsing.
See [here][memory] for more details.
```
Function Signature
------------------
```admonish tip.side "Tip: Raising errors"
Raise a parse error by returning [`Token::LexError`](https://docs.rs/rhai/{{version}}/rhai/enum.Token.html#variant.LexError)
as the mapped token.
```
The function signature passed to [`Engine::on_parse_token`] takes the following form.
> ```rust
> Fn(token: Token, pos: Position, state: &TokenizeState) -> Token
> ```
where:
| Parameter | Type | Description |
| --------- | :---------------------------------------------------------------------------------: | -------------------------------- |
| `token` | [`Token`] | the next symbol parsed |
| `pos` | `Position` | location of the [token][`Token`] |
| `state` | [`&TokenizeState`](https://docs.rs/rhai/{{version}}/rhai/struct.TokenizeState.html) | current state of the tokenizer |
Example
-------
```rust
use rhai::{Engine, FLOAT, Token};
let mut engine = Engine::new();
// Register a token mapper function.
engine.on_parse_token(|token, pos, state| {
match token {
// Change 'begin' ... 'end' to '{' ... '}'
Token::Identifier(s) if &s == "begin" => Token::LeftBrace,
Token::Identifier(s) if &s == "end" => Token::RightBrace,
// Change all integer literals to floating-point
Token::IntegerConstant(n) => Token::FloatConstant((n as FLOAT).into()),
// Disallow '()'
Token::Unit => Token::LexError(
LexError::ImproperSymbol("()".to_string(), "".to_string()).into()
),
// Pass through all other tokens unchanged
_ => token
}
});
```

View File

@ -0,0 +1,96 @@
Variable Resolver
=================
{{#include ../links.md}}
[`Engine::on_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_var
By default, Rhai looks up access to [variables] from the enclosing block scope, working its way
outwards until it reaches the top (global) level, then it searches the [`Scope`] that is passed into
the `Engine::eval` call.
There is a built-in facility for advanced users to _hook_ into the [variable] resolution service and
to override its default behavior.
To do so, provide a closure to the [`Engine`] via [`Engine::on_var`].
```rust
let mut engine = Engine::new();
// Register a variable resolver.
engine.on_var(|name, index, context| {
match name {
"MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
// Override a variable - make it not found even if it exists!
"DO_NOT_USE" => Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()),
// Silently maps 'chameleon' into 'innocent'.
"chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()
),
// Return Ok(None) to continue with the normal variable resolution process.
_ => Ok(None)
}
});
```
```admonish info.small "Benefits of using a variable resolver"
1. Avoid having to maintain a custom [`Scope`] with all [variables] regardless of need
(because a script may not use them all).
2. _Short-circuit_ [variable] access, essentially overriding standard behavior.
3. _Lazy-load_ [variables] when they are accessed, not up-front.
This benefits when the number of [variables] is very large, when they are timing-dependent,
or when they are expensive to load.
4. Rename system [variables] on a script-by-script basis without having to construct different [`Scope`]'s.
```
```admonish warning.small "Returned values are constants"
[Variable] values returned by a variable resolver are treated as _[constants]_.
This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.
To change these [variables], better push them into a custom [`Scope`] instead of
using a variable resolver.
```
```admonish tip.small "Tip: Returning shared values"
It is possible to return a _shared_ value from a variable resolver.
This is one way to implement [Mutable Global State]({{rootUrl}}/patterns/global-mutable-state.md).
```
Function Signature
------------------
The function signature passed to [`Engine::on_var`] takes the following form.
> ```rust
> Fn(name: &str, index: usize, context: EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `&str` | [variable] name |
| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the [variable] is supposed to reside.<br/>Offsets start from 1, with 1 meaning the last [variable] in the current [`Scope`]. Essentially the correct [variable] is at position `scope.len() - index`.<br/>If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. |
| `context` | [`EvalContext`] | mutable reference to the current _evaluation context_ |
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
### Return value
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where:
| Value | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Ok(None)` | normal [variable] resolution process should continue, i.e. continue searching through the [`Scope`] |
| `Ok(Some(value))` | value (a [`Dynamic`]) of the [variable], treated as a [constant] |
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`], normally `EvalAltResult::ErrorVariableNotFound` to indicate that the [variable] does not exist, but it can be any `EvalAltResult`. |

View File

@ -0,0 +1,17 @@
The Rhai Book
=============
{{#title The Rhai Book}}
{{#include links.md}}
![Rhai Logo]({{rootUrl}}/images/logo/rhai-banner-transparent-colour.svg)
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application.
Versions
--------
This Book is for version **{{version}}** of Rhai.

View File

@ -0,0 +1,76 @@
Out-of-Bounds Index for Arrays
==============================
{{#include ../links.md}}
[`Engine::on_invalid_array_index`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_invalid_array_index
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
~~~admonish warning.small "Requires `internals`"
This is an advanced feature that requires the [`internals`] feature to be enabled.
~~~
Normally, when an index is out-of-bounds for an [array], an error is raised.
It is possible to completely control this behavior via a special callback function
registered into an [`Engine`] via `on_invalid_array_index`.
Using this callback, for instance, it is simple to instruct Rhai to extend the [array] to
accommodate this new element, or to return a default value instead of raising an error.
Function Signature
------------------
The function signature passed to [`Engine::on_invalid_array_index`] takes the following form.
> ```rust
> Fn(array: &mut Array, index: i64, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------------: | -------------------------------- |
| `array` | [`&mut Array`][array] | the [array] being accessed |
| `index` | `i64` | index value |
| `context` | [`EvalContext`] | the current _evaluation context_ |
### Return value
The return value is `Result<Target, Box<EvalAltResult>>`.
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
_reference_ to a [`Dynamic`] value.
It can be used to point to a particular value within the [array] or a new temporary value.
Example
-------
```rust
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();
Ok(value_ref.into())
}
100 => {
// Return a temporary value (not a reference)
let value = Dynamic::from(100_i64);
Ok(value.into())
}
// Return the standard out-of-bounds error
_ => Err(EvalAltResult::ErrorArrayBounds(
arr.len(), index, Position::NONE
).into()),
}
});
```

View File

@ -0,0 +1,300 @@
Arrays
======
{{#include ../links.md}}
```admonish tip.side "Safety"
Always limit the [maximum size of arrays].
```
Arrays are first-class citizens in Rhai.
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with
elements added or removed.
The Rust type of a Rhai array is `rhai::Array` which is an alias to `Vec<Dynamic>`.
[`type_of()`] an array returns `"array"`.
Arrays are disabled via the [`no_index`] feature.
Literal Syntax
--------------
Array literals are built within square brackets `[` ... `]` and separated by commas `,`:
> `[` _value_`,` _value_`,` ... `,` _value_ `]`
>
> `[` _value_`,` _value_`,` ... `,` _value_ `,` `]` `// trailing comma is OK`
Element Access Syntax
---------------------
### From beginning
Like C, arrays are accessed with zero-based, non-negative integer indices:
> _array_ `[` _index position from 0 to (length1)_ `]`
### From end
A _negative_ position accesses an element in the array counting from the _end_, with 1 being the
_last_ element.
> _array_ `[` _index position from 1 to length_ `]`
Out-of-Bounds Index
-------------------
Trying to read from an index that is out of bounds causes an error.
```admonish tip.small "Advanced tip: Override standard behavior"
For fine-tuned control on what happens when an out-of-bounds index is accessed,
see [_Out-of-Bounds Index for Arrays_](arrays-oob.md).
```
Built-in Functions
------------------
The following methods (mostly defined in the [`BasicArrayPackage`][built-in packages] but excluded
when using a [raw `Engine`]) operate on arrays.
| Function | Parameter(s) | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get` | position, counting from end if < 0 | gets a copy of the element at a certain position ([`()`] if the position is not valid) |
| `set` | <ol><li>position, counting from end if < 0</li><li>new element</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
| `push`, `+=` operator | element to append (not an array) | appends an element to the end |
| `append`, `+=` operator | array to append | concatenates the second array to the end of the first |
| `+` operator | <ol><li>first array</li><li>second array</li></ol> | concatenates the first array with the second |
| `==` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays the same (elements compared with the `==` operator, if defined)? |
| `!=` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays different (elements compared with the `==` operator, if defined)? |
| `insert` | <ol><li>position, counting from end if < 0, end if length</li><li>element to insert</li></ol> | inserts an element at a certain position |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `extract` | <ol><li>start position, counting from end if < 0, end if length</li><li>_(optional)_ number of elements to extract, none if ≤ 0, to end if omitted</li></ol> | extracts a portion of the array into a new array |
| `extract` | [range] of elements to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the array into a new array |
| `remove` | position, counting from end if < 0 | removes an element at a particular position and returns it ([`()`] if the position is not valid) |
| `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements |
| `is_empty` method and property | _none_ | returns `true` if the array is empty |
| `pad` | <ol><li>target length</li><li>element to pad</li></ol> | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length |
| `split` | <ol><li>array</li><li>position to split at, counting from end if < 0, end if length</li></ol> | splits the array into two arrays, starting from a specified position |
| `for_each` | [function pointer] for processing elements | run through each element in the array in order, binding each to `this` and calling the processing function taking the following parameters: <ol><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
| `drain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `drain` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of elements to remove, none if ≤ 0</li></ol> | removes a portion of the array, returning the removed elements as a new array |
| `drain` | [range] of elements to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the array, returning the removed elements as a new array |
| `retain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that do not return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `retain` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of elements to retain, none if ≤ 0</li></ol> | retains a portion of the array, removes all other elements and returning them as a new array |
| `retain` | [range] of elements to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the array, removes all other bytes and returning them as a new array |
| `splice` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of elements to remove, none if ≤ 0</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
| `splice` | <ol><li>[range] of elements to remove, from beginning if ≤ 0, to end if ≥ length</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all elements that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `contains`, [`in`] operator | element to find | does the array contain an element? The `==` operator (if defined) is used to compare [custom types] |
| `index_of` | <ol><li>element to find (not a [function pointer])</li><li>_(optional)_ start position, counting from end if < 0, end if length</li></ol> | returns the position of the first element in the array that equals the supplied element (using the `==` operator, if defined), or 1 if not found</li></ol> |
| `index_of` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if length</li></ol> | returns the position of the first element in the array that returns `true` when called with the predicate function, or 1 if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
| `find` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if length</li></ol> | returns the first element in the array that returns `true` when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
| `find_map` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if length</li></ol> | returns the first non-[`()`] value of the first element in the array when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
| `dedup` | _(optional)_ [function pointer] to predicate (usually a [closure]); if omitted, the `==` operator is used, if defined | removes all but the first of _consecutive_ elements in the array that return `true` when called with the predicate function (non-consecutive duplicates are _not_ removed):<br/>1st & 2nd parameters: two elements in the array |
| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all elements mapped to the result of applying the conversion function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `reduce` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
| `reduce_rev` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array (in reverse order) into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
| `zip` | <ol><li>array to zip</li><li>[function pointer] to conversion function (usually a [closure])</li></ol> | constructs a new array with all element pairs from two arrays mapped to the result of applying the conversion function taking the following parameters:<ol><li>first array element</li><li>second array element</li><li>_(optional)_ index position</li></ol> |
| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any element returns `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all elements return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function taking the following parameters:<ol><li>first element</li><li>second element<br/>return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second</li></ol> |
| `sort` | _none_ | sorts a _homogeneous_ array containing only elements of the same comparable built-in type (`INT`, `FLOAT`, [`Decimal`][rust_decimal], [string], [character], `bool`, [`()`]) |
```admonish tip.small "Tip: Use custom types with arrays"
To use a [custom type] with arrays, a number of functions need to be manually implemented,
in particular the `==` operator in order to support the [`in`] operator which uses `==` (via the
`contains` method) to compare elements.
See the section on [custom types] for more details.
```
Examples
--------
```rust
let y = [2, 3]; // y == [2, 3]
let y = [2, 3,]; // y == [2, 3]
y.insert(0, 1); // y == [1, 2, 3]
y.insert(999, 4); // y == [1, 2, 3, 4]
y.len == 4;
y[0] == 1;
y[1] == 2;
y[2] == 3;
y[3] == 4;
(1 in y) == true; // use 'in' to test if an element exists in the array
(42 in y) == false; // 'in' uses the 'contains' function, which uses the
// '==' operator (that users can override)
// to check if the target element exists in the array
y.contains(1) == true; // the above de-sugars to this
y[1] = 42; // y == [1, 42, 3, 4]
(42 in y) == true;
y.remove(2) == 3; // y == [1, 42, 4]
y.len == 3;
y[2] == 4; // elements after the removed element are shifted
ts.list = y; // arrays can be assigned completely (by value copy)
ts.list[1] == 42;
[1, 2, 3][0] == 1; // indexing on array literal
[1, 2, 3][-1] == 3; // negative position counts from the end
fn abc() {
[42, 43, 44] // a function returning an array
}
abc()[0] == 42;
y.push(4); // y == [1, 42, 4, 4]
y += 5; // y == [1, 42, 4, 4, 5]
y.len == 5;
y.shift() == 1; // y == [42, 4, 4, 5]
y.chop(3); // y == [4, 4, 5]
y.len == 3;
y.pop() == 5; // y == [4, 4]
y.len == 2;
for element in y { // arrays can be iterated with a 'for' statement
print(element);
}
y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"]
y.len == 6;
y.truncate(4); // y == [4, 4, "hello", "hello"]
y.len == 4;
y.clear(); // y == []
y.len == 0;
// The examples below use 'a' as the master array
let a = [42, 123, 99];
a.for_each(|| this *= 2);
a == [84, 246, 198];
a.for_each(|i| this /= 2);
a == [42, 123, 99];
a.map(|v| v + 1); // returns [43, 124, 100]
a.map(|| this + 1); // returns [43, 124, 100]
a.map(|v, i| v + i); // returns [42, 124, 101]
a.filter(|v| v > 50); // returns [123, 99]
a.filter(|| this > 50); // returns [123, 99]
a.filter(|v, i| i == 1); // returns [123]
a.filter("is_odd"); // returns [123, 99]
a.filter(Fn("is_odd")); // <- previous statement is equivalent to this...
a.filter(|v| is_odd(v)); // <- or this
a.some(|v| v > 50); // returns true
a.some(|| this > 50); // returns true
a.some(|v, i| v < i); // returns false
a.all(|v| v > 50); // returns false
a.all(|| this > 50); // returns false
a.all(|v, i| v > i); // returns true
// Reducing - initial value provided directly
a.reduce(|sum| sum + this, 0) == 264;
// Reducing - initial value provided directly
a.reduce(|sum, v| sum + v, 0) == 264;
// Reducing - initial value is '()'
a.reduce(
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
) == 264;
// Reducing - initial value has index position == 0
a.reduce(|sum, v, i|
if i == 0 { v } else { sum + v }
) == 264;
// Reducing in reverse - initial value provided directly
a.reduce_rev(|sum| sum + this, 0) == 264;
// Reducing in reverse - initial value provided directly
a.reduce_rev(|sum, v| sum + v, 0) == 264;
// Reducing in reverse - initial value is '()'
a.reduce_rev(
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
) == 264;
// Reducing in reverse - initial value has index position == 0
a.reduce_rev(|sum, v, i|
if i == 2 { v } else { sum + v }
) == 264;
// In-place modification
a.splice(1..=1, [1, 3, 2]); // a == [42, 1, 3, 2, 99]
a.extract(1..=3); // returns [1, 3, 2]
a.sort(|x, y| y - x); // a == [99, 42, 3, 2, 1]
a.sort(); // a == [1, 2, 3, 42, 99]
a.drain(|v| v <= 1); // a == [2, 3, 42, 99]
a.drain(|v, i| i ≥ 3); // a == [2, 3, 42]
a.retain(|v| v > 10); // a == [42]
a.retain(|v, i| i > 0); // a == []
```

View File

@ -0,0 +1,89 @@
Compound Assignments
====================
{{#include ../links.md}}
Compound assignments are assignments with a [binary operator][operators] attached.
```rust
number += 8; // number = number + 8
number -= 7; // number = number - 7
number *= 6; // number = number * 6
number /= 5; // number = number / 5
number %= 4; // number = number % 4
number **= 3; // number = number ** 3
number <<= 2; // number = number << 2
number >>= 1; // number = number >> 1
number &= 0x00ff; // number = number & 0x00ff;
number |= 0x00ff; // number = number | 0x00ff;
number ^= 0x00ff; // number = number ^ 0x00ff;
```
The Flexible `+=`
-----------------
The the `+` and `+=` operators are often [overloaded][function overloading] to perform build-up
operations for different data types.
### Build strings
```rust
let my_str = "abc";
my_str += "ABC";
my_str += 12345;
my_str == "abcABC12345"
```
### Concatenate arrays
```rust
let my_array = [1, 2, 3];
my_array += [4, 5];
my_array == [1, 2, 3, 4, 5];
```
### Concatenate BLOB's
```rust
let my_blob = blob(3, 0x42);
my_blob += blob(5, 0x89);
my_blob.to_string() == "[4242428989898989]";
```
### Mix two object maps together
```rust
let my_obj = #{ a:1, b:2 };
my_obj += #{ c:3, d:4, e:5 };
my_obj == #{ a:1, b:2, c:3, d:4, e:5 };
```
### Add seconds to timestamps
```rust
let now = timestamp();
now += 42.0;
(now - timestamp()).round() == 42.0;
```

View File

@ -0,0 +1,125 @@
Assignments
===========
{{#include ../links.md}}
Value assignments to [variables] use the `=` symbol.
```rust
let foo = 42;
bar = 123 * 456 - 789;
x[1][2].prop = do_calculation();
```
Valid Assignment Targets
------------------------
The left-hand-side (LHS) of an assignment statement must be a valid
_[l-value](https://en.wikipedia.org/wiki/Value_(computer_science))_, which must be rooted in a
[variable], potentially extended via indexing or properties.
~~~admonish bug "Assigning to invalid l-value"
Expressions that are not valid _l-values_ cannot be assigned to.
```rust
x = 42; // variable is an l-value
x[1][2][3] = 42 // variable indexing is an l-value
x.prop1.prop2 = 42; // variable property is an l-value
foo(x) = 42; // syntax error: function call is not an l-value
x.foo() = 42; // syntax error: method call is not an l-value
(x + y) = 42; // syntax error: binary op is not an l-value
```
~~~
Values are Cloned
-----------------
Values assigned are always _cloned_.
So care must be taken when assigning large data types (such as [arrays]).
```rust
x = y; // value of 'y' is cloned
x == y; // both 'x' and 'y' hold different copies
// of the same value
```
Moving Data
-----------
When assigning large data types, sometimes it is desirable to _move_ the data instead of cloning it.
Use the `take` function (defined in the [`LangCorePackage`][built-in packages] but excluded
when using a [raw `Engine`]) to _move_ data.
### The original variable is left with `()`
```rust
x = take(y); // value of 'y' is moved to 'x'
y == (); // 'y' now holds '()'
x != y; // 'x' holds the original value of 'y'
```
### Return large data types from functions
`take` is convenient when returning large data types from a [function].
```rust
fn get_large_value_naive() {
let large_result = do_complex_calculation();
large_result.done = true;
// Return a cloned copy of the result, then the
// local variable 'large_result' is thrown away!
large_result
}
fn get_large_value_smart() {
let large_result = do_complex_calculation();
large_result.done = true;
// Return the result without cloning!
// Method style call is also OK.
large_result.take()
}
```
### Assigning large data types to object map properties
`take` is useful when assigning large data types to [object map] properties.
```rust
let x = [];
// Build a large array
for n in 0..1000000 { x += n; }
// The following clones the large array from 'x'.
// Both 'my_object.my_property' and 'x' now hold exact copies
// of the same large array!
my_object.my_property = x;
// Move it to object map property via 'take' without cloning.
// 'x' now holds '()'.
my_object.my_property = x.take();
// Without 'take', the following must be done to avoid cloning:
my_object.my_property = [];
for n in 0..1000000 { my_object.my_property += n; }
```

View File

@ -0,0 +1,125 @@
Integer as Bit-Fields
=====================
{{#include ../links.md}}
```admonish note.side
Nothing here cannot be done via standard bit-manipulation (i.e. shifting and masking).
Built-in support is more elegant and performant since it usually replaces a sequence of multiple steps.
```
Since bit-wise operators are defined on integer numbers, individual bits can also be accessed and
manipulated via an indexing syntax.
If a bit is set (i.e. `1`), the index access returns `true`.
If a bit is not set (i.e. `0`), the index access returns `false`.
When a [range] is used, the bits within the [range] are shifted and extracted as an integer value.
Bit-fields are very commonly used in embedded systems which must squeeze data into limited memory.
Built-in support makes handling them efficient.
Indexing an integer as a bit-field is disabled for the [`no_index`] feature.
Syntax
------
### From Least-Significant Bit (LSB)
Bits in a bit-field are accessed with zero-based, non-negative integer indices:
> _integer_ `[` _index from 0 to 63 or 31_ `]`
>
> _integer_ `[` _index from 0 to 63 or 31_ `] =` `true` or `false` ;
[Ranges] can also be used:
> _integer_ `[` _start_ `..` _end_ `]`
> _integer_ `[` _start_ `..=` _end_ `]`
>
> _integer_ `[` _start_ `..` _end_ `] =` _new integer value_ ;
> _integer_ `[` _start_ `..=` _end_ `] =` _new integer value_ ;
```admonish warning.small "Number of bits"
The maximum bit number that can be accessed is 63 (or 31 under [`only_i32`]).
Bits outside of the range are ignored.
```
### From Most-Significant Bit (MSB)
A _negative_ index accesses a bit in the bit-field counting from the _end_, or from the
_most-significant bit_, with 1 being the _highest_ bit.
> _integer_ `[` _index from 1 to 64 or 32_ `]`
>
> _integer_ `[` _index from 1 to 64 or 32_ `] =` `true` or `false` ;
[Ranges] always count from the least-significant bit (LSB) and has no support for negative positions.
```admonish warning.small "Number of bits"
The maximum bit number that can be accessed is 64 (or 32 under [`only_i32`]).
```
Bit-Field Functions
-------------------
The following standard functions (defined in the [`BitFieldPackage`][built-in packages] but excluded
when using a [raw `Engine`]) operate on `INT` bit-fields.
These functions are available even under the [`no_index`] feature.
| Function | Parameter(s) | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `get_bit` | bit number, counting from MSB if < 0 | returns the state of a bit: `true` if `1`, `false` if `0` |
| `set_bit` | <ol><li>bit number, counting from MSB if < 0</li><li>new state: `true` if `1`, `false` if `0`</li></ol> | sets the state of a bit |
| `get_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to extract, none if < 1, to MSB if _length_</li></ol> | extracts a number of bits, shifted towards LSB |
| `get_bits` | [range] of bits | extracts a number of bits, shifted towards LSB |
| `set_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to set, none if < 1, to MSB if _length_<br/>3) new value</li></ol> | sets a number of bits from the new value |
| `set_bits` | <ol><li>[range] of bits</li><li>new value</li></ol> | sets a number of bits from the new value |
| `bits` method and property | <ol><li>_(optional)_ starting bit number, counting from MSB if < 0</li><li>_(optional)_ number of bits to extract, none if < 1, to MSB if _length_</li></ol> | allows iteration over the bits of a bit-field |
| `bits` | [range] of bits | allows iteration over the bits of a bit-field |
Example
-------
```js , no_run
// Assume the following bits fields in a single 16-bit word:
// ┌─────────┬────────────┬──────┬─────────┐
// │ 15-12 │ 11-4 │ 3 │ 2-0 │
// ├─────────┼────────────┼──────┼─────────┤
// │ 0 │ 0-255 data │ flag │ command │
// └─────────┴────────────┴──────┴─────────┘
let value = read_start_hw_register(42);
let command = value.get_bits(0, 3); // Command = bits 0-2
let flag = value[3]; // Flag = bit 3
let data = value[4..=11]; // Data = bits 4-11
let data = value.get_bits(4..=11); // <- above is the same as this
let reserved = value.get_bits(-4); // Reserved = last 4 bits
if reserved != 0 {
throw reserved;
}
switch command {
0 => print(`Data = ${data}`),
1 => value[4..=11] = data / 2,
2 => value[3] = !flag,
_ => print(`Unknown: ${command}`)
}
```

View File

@ -0,0 +1,202 @@
BLOB's
======
{{#include ../links.md}}
```admonish tip.side "Safety"
Always limit the [maximum size of arrays].
```
BLOB's (**B**inary **L**arge **OB**jects), used to hold packed arrays of bytes, have built-in support in Rhai.
A BLOB has no literal representation, but is created via the `blob` function, or simply returned as
the result of a function call (e.g. `generate_thumbnail_image` that generates a thumbnail version of
a large image as a BLOB).
All items stored in a BLOB are bytes (i.e. `u8`) and the BLOB can freely grow or shrink with bytes
added or removed.
The Rust type of a Rhai BLOB is `rhai::Blob` which is an alias to `Vec<u8>`.
[`type_of()`] a BLOB returns `"blob"`.
BLOB's are disabled via the [`no_index`] feature.
Element Access Syntax
---------------------
### From beginning
Like [arrays], BLOB's are accessed with zero-based, non-negative integer indices:
> _blob_ `[` _index position from 0 to (length1)_ `]`
### From end
A _negative_ position accesses an element in the BLOB counting from the _end_, with 1 being the
_last_ element.
> _blob_ `[` _index position from 1 to length_ `]`
```admonish info.small "Byte values"
The value of a particular byte in a BLOB is mapped to an `INT` (which can be 64-bit or 32-bit
depending on the [`only_i32`] feature).
Only the lowest 8 bits are significant, all other bits are ignored.
```
Create a BLOB
-------------
The function `blob` allows creating an empty BLOB, optionally filling it to a required size with a
particular value (default zero).
```rust
let x = blob(); // empty BLOB
let x = blob(10); // BLOB with ten zeros
let x = blob(50, 42); // BLOB with 50x 42's
```
```admonish tip "Tip: Initialize with byte stream"
To quickly initialize a BLOB with a particular byte stream, the `write_be` method can be used to
write eight bytes at a time (four under [`only_i32`]) in big-endian byte order.
If fewer than eight bytes are needed, remember to right-pad the number as big-endian byte order is used.
~~~rust
let buf = blob(12, 0); // BLOB with 12x zeros
// Write eight bytes at a time, in big-endian order
buf.write_be(0, 8, 0xab_cd_ef_12_34_56_78_90);
buf.write_be(8, 8, 0x0a_0b_0c_0d_00_00_00_00);
// ^^^^^^^^^^^ remember to pad unused bytes
print(buf); // prints "[abcdef1234567890 0a0b0c0d]"
buf[3] == 0x12;
buf[10] == 0x0c;
// Under 'only_i32', write four bytes at a time:
buf.write_be(0, 4, 0xab_cd_ef_12);
buf.write_be(4, 4, 0x34_56_78_90);
buf.write_be(8, 4, 0x0a_0b_0c_0d);
~~~
```
Writing ASCII Bytes
-------------------
```admonish warning.side "Non-ASCII"
Non-ASCII characters (i.e. characters not within 1-127) are ignored.
```
For many embedded applications, it is necessary to encode an ASCII [string] as a byte stream.
Use the `write_ascii` method to write ASCII [strings] into any specific [range] within a BLOB.
The following is an example of a building a 16-byte command to send to an embedded device.
```rust
// Assume the following 16-byte command for an embedded device:
// ┌─────────┬───────────────┬──────────────────────────────────┬───────┐
// │ 0 │ 1 │ 2-13 │ 14-15 │
// ├─────────┼───────────────┼──────────────────────────────────┼───────┤
// │ command │ string length │ ASCII string, max. 12 characters │ CRC │
// └─────────┴───────────────┴──────────────────────────────────┴───────┘
let buf = blob(16, 0); // initialize command buffer
let text = "foo & bar"; // text string to send to device
buf[0] = 0x42; // command code
buf[1] = s.len(); // length of string
buf.write_ascii(2..14, text); // write the string
let crc = buf.calc_crc(); // calculate CRC
buf.write_le(14, 2, crc); // write CRC
print(buf); // prints "[4209666f6f202620 626172000000abcd]"
// ^^ command code ^^^^ CRC
// ^^ string length
// ^^^^^^^^^^^^^^^^^^^ foo & bar
device.send(buf); // send command to device
```
```admonish question.small "What if I need UTF-8?"
The `write_utf8` function writes a string in UTF-8 encoding.
UTF-8, however, is not very common for embedded applications.
```
Built-in Functions
------------------
The following functions (mostly defined in the [`BasicBlobPackage`][built-in packages] but excluded
when using a [raw `Engine`]) operate on BLOB's.
| Functions | Parameter(s) | Description |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blob` constructor function | <ol><li>_(optional)_ initial length of the BLOB</li><li>_(optional)_ initial byte value</li></ol> | creates a new BLOB, optionally of a particular length filled with an initial byte value (default = 0) |
| `to_array` | _none_ | converts the BLOB into an [array] of integers |
| `as_string` | _none_ | converts the BLOB into a [string] (the byte stream is interpreted as UTF-8) |
| `get` | position, counting from end if < 0 | gets a copy of the byte at a certain position (0 if the position is not valid) |
| `set` | <ol><li>position, counting from end if < 0</li><li>new byte value</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
| `push`, `append`, `+=` operator | <ol><li>BLOB</li><li>byte to append</li></ol> | appends a byte to the end |
| `append`, `+=` operator | <ol><li>BLOB</li><li>BLOB to append</li></ol> | concatenates the second BLOB to the end of the first |
| `append`, `+=` operator | <ol><li>BLOB</li><li>[string]/[character] to append</li></ol> | concatenates a [string] or [character] (as UTF-8 encoded byte-stream) to the end of the BLOB |
| `+` operator | <ol><li>first BLOB</li><li>[string] to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string] |
| `+` operator | <ol><li>[string]</li><li>BLOB to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string] |
| `+` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | concatenates the first BLOB with the second |
| `==` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's the same? |
| `!=` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's different? |
| `insert` | <ol><li>position, counting from end if < 0, end if length</li><li>byte to insert</li></ol> | inserts a byte at a certain position |
| `pop` | _none_ | removes the last byte and returns it (0 if empty) |
| `shift` | _none_ | removes the first byte and returns it (0 if empty) |
| `extract` | <ol><li>start position, counting from end if < 0, end if length</li><li>_(optional)_ number of bytes to extract, none if ≤ 0</li></ol> | extracts a portion of the BLOB into a new BLOB |
| `extract` | [range] of bytes to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the BLOB into a new BLOB |
| `remove` | position, counting from end if < 0 | removes a byte at a particular position and returns it (0 if the position is not valid) |
| `reverse` | _none_ | reverses the BLOB byte by byte |
| `len` method and property | _none_ | returns the number of bytes in the BLOB |
| `is_empty` method and property | _none_ | returns `true` if the BLOB is empty |
| `pad` | <ol><li>target length</li><li>byte value to pad</li></ol> | pads the BLOB with a byte value to at least a specified length |
| `clear` | _none_ | empties the BLOB |
| `truncate` | target length | cuts off the BLOB at exactly a specified length (discarding all subsequent bytes) |
| `chop` | target length | cuts off the head of the BLOB, leaving the tail at exactly a specified length |
| `contains`, [`in`] operator | byte value to find | does the BLOB contain a particular byte value? |
| `split` | <ol><li>BLOB</li><li>position to split at, counting from end if < 0, end if length</li></ol> | splits the BLOB into two BLOB's, starting from a specified position |
| `drain` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to remove, none if ≤ 0</li></ol> | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
| `drain` | [range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
| `retain` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to retain, none if ≤ 0</li></ol> | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
| `retain` | [range] of bytes to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
| `splice` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to remove, none if ≤ 0</li><li>BLOB to insert</li></ol> | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
| `splice` | <ol><li>[range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length</li><li>BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
| `parse_le_int` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_int` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_le_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `parse_be_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_le` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_le` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_be` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_be` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
| `write_utf8` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of bytes to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
| `write_utf8` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
| `write_ascii` | <ol><li>start position, counting from end if < 0, end if length</li><li>number of [characters] to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
| `write_ascii` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |

View File

@ -0,0 +1,65 @@
Comments
========
{{#include ../links.md}}
Comments are C-style, including `/*` ... `*/` pairs for block comments and `//` for comments to the
end of the line.
Block comments can be nested.
```rust
let /* intruder comment */ name = "Bob";
// This is a very important one-line comment
/* This comment spans
multiple lines, so it
only makes sense that
it is even more important */
/* Fear not, Rhai satisfies all nesting needs with nested comments:
/*/*/*/*/**/*/*/*/*/
*/
```
Module Documentation
--------------------
Comment lines starting with `//!` make up the _module documentation_.
They are used to document the containing [module] &ndash; or for a Rhai script file,
to document the file itself.
~~~admonish warning.small "Requires `metadata`"
Module documentation is only supported under the [`metadata`] feature.
If [`metadata`] is not active, they are treated as normal [comments].
~~~
```rust
//! Documentation for this script file.
//! This script is used to calculate something and display the result.
fn calculate(x) {
...
}
fn display(msg) {
//! Module documentation can be placed anywhere within the file.
...
}
//! All module documentation lines will be collected into a single block.
```
For the example above, the module documentation block is:
```rust
//! Documentation for this script file.
//! This script is used to calculate something and display the result.
//! Module documentation can be placed anywhere within the file.
//! All module documentation lines will be collected into a single block.
```

View File

@ -0,0 +1,145 @@
Constants
=========
{{#include ../links.md}}
Constants can be defined using the `const` keyword and are immutable.
```rust
const X; // 'X' is a constant '()'
const X = 40 + 2; // 'X' is a constant 42
print(X * 2); // prints 84
X = 123; // <- syntax error: constant modified
```
```admonish tip.small "Tip: Naming"
Constants follow the same naming rules as [variables], but as a convention are often named with
all-capital letters.
```
Manually Add Constant into Custom Scope
---------------------------------------
```admonish tip.side "Tip: Singleton"
A constant value holding a [custom type] essentially acts
as a [_singleton_]({{rootUrl}}/patterns/singleton.md).
```
It is possible to add a constant into a custom [`Scope`] via `Scope::push_constant` so it'll be
available to scripts running with that [`Scope`].
```rust
use rhai::{Engine, Scope};
#[derive(Debug, Clone)]
struct TestStruct(i64); // custom type
let mut engine = Engine::new();
engine
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
.register_get("value", |obj: &mut TestStruct| obj.0), // property getter
.register_fn("update_value",
|obj: &mut TestStruct, value: i64| obj.0 = value // mutating method
);
let script =
"
MY_NUMBER.update_value(42);
print(MY_NUMBER.value);
";
let ast = engine.compile(script)?;
let mut scope = Scope::new(); // create custom scope
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
// Beware: constant objects can still be modified via a method call!
engine.run_ast_with_scope(&mut scope, &ast)?; // prints 42
// Running the script directly, as below, is less desirable because
// the constant 'MY_NUMBER' will be propagated and copied into each usage
// during the script optimization step
engine.run_with_scope(&mut scope, script)?;
```
Caveat &ndash; Constants Can be Modified via Rust
-------------------------------------------------
```admonish tip.side.wide "Tip: Plugin functions"
In [plugin functions], `&mut` parameters disallow constant values by default.
This is different from the `Engine::register_XXX` API.
However, if a [plugin function] is marked with `#[export_fn(pure)]` or `#[rhai_fn(pure)]`,
it is assumed _pure_ (i.e. will not modify its arguments) and so constants are allowed.
```
A [custom type] stored as a constant cannot be modified via script, but _can_ be modified via a
registered Rust function that takes a first `&mut` parameter &ndash; because there is no way for
Rhai to know whether the Rust function modifies its argument!
By default, native Rust functions with a first `&mut` parameter always allow constants to be passed
to them. This is because using `&mut` can avoid unnecessary cloning of a [custom type] value, even
though it is actually not modified &ndash; for example returning the size of a collection type.
In line with intuition, Rhai is smart enough to always pass a _cloned copy_ of a constant as the
first `&mut` argument if the function is called in normal function call style.
If it is called as a [method], however, the Rust function will be able to modify the constant's value.
Also, property [setters][getters/setters] and [indexers] are always assumed to mutate the first
`&mut` parameter and so they always raise errors when passed constants by default.
```rust
// For the below, assume 'increment' is a Rust function with '&mut' first parameter
const X = 42; // a constant
increment(X); // call 'increment' in normal FUNCTION-CALL style
// since 'X' is constant, a COPY is passed instead
X == 42; // value is 'X" is unchanged
X.increment(); // call 'increment' in METHOD-CALL style
X == 43; // value of 'X' is changed!
// must use 'Dynamic::is_read_only' to check if parameter is constant
fn double() {
this *= 2; // function doubles 'this'
}
let y = 1; // 'y' is not constant and mutable
y.double(); // double it...
y == 2; // value of 'y' is changed as expected
X.double(); // since 'X' is constant, a COPY is passed to 'this'
X == 43; // value of 'X' is unchanged by script
```
```admonish info.small "Implications on script optimization"
Rhai _assumes_ that constants are never changed, even via Rust functions.
This is important to keep in mind because the script [optimizer][script optimization]
by default does _constant propagation_ as a operation.
If a constant is eventually modified by a Rust function, the optimizer will not see
the updated value and will propagate the original initialization value instead.
`Dynamic::is_read_only` can be used to detect whether a [`Dynamic`] value is constant or not within
a Rust function.
```

View File

@ -0,0 +1,98 @@
Value Conversions
=================
{{#include ../links.md}}
Convert Between Integer and Floating-Point
------------------------------------------
| Function | Not available under | From type | To type |
| ------------ | :-----------------: | :---------------------------------------: | :-----------------------: |
| `to_int` | | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `INT` |
| `to_float` | [`no_float`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `FLOAT` |
| `to_decimal` | non-[`decimal`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
That's it; for other conversions, register custom conversion functions.
```js
let x = 42; // 'x' is an integer
let y = x * 100.0; // integer and floating-point can inter-operate
let y = x.to_float() * 100.0; // convert integer to floating-point with 'to_float'
let z = y.to_int() + x; // convert floating-point to integer with 'to_int'
let d = y.to_decimal(); // convert floating-point to Decimal with 'to_decimal'
let w = z.to_decimal() + x; // Decimal and integer can inter-operate
let c = 'X'; // character
print(`c is '${c}' and its code is ${c.to_int()}`); // prints "c is 'X' and its code is 88"
```
Parse String into Number
------------------------
| Function | From type | To type |
| ---------------------------------------- | :-------: | :-----------------------: |
| `parse_int` | [string] | `INT` |
| `parse_int` with radix 2-36 | [string] | `INT` (specified radix) |
| `parse_float` (not [`no_float`]) | [string] | `FLOAT` |
| `parse_float` ([`no_float`]+[`decimal`]) | [string] | [`Decimal`][rust_decimal] |
| `parse_decimal` (requires [`decimal`]) | [string] | [`Decimal`][rust_decimal] |
```rust
let x = parse_float("123.4"); // parse as floating-point
x == 123.4;
type_of(x) == "f64";
let x = parse_decimal("123.4"); // parse as Decimal value
type_of(x) == "decimal";
let x = 1234.to_decimal() / 10; // alternate method to create a Decimal value
type_of(x) == "decimal";
let dec = parse_int("42"); // parse as integer
dec == 42;
type_of(dec) == "i64";
let dec = parse_int("42", 10); // radix = 10 is the default
dec == 42;
type_of(dec) == "i64";
let bin = parse_int("110", 2); // parse as binary (radix = 2)
bin == 0b110;
type_of(bin) == "i64";
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
hex == 0xab;
type_of(hex) == "i64";
```
Format Numbers
--------------
| Function | From type | To type | Format |
| ----------- | :-------: | :------: | :----------------------------: |
| `to_binary` | `INT` | [string] | binary (i.e. only `1` and `0`) |
| `to_octal` | `INT` | [string] | octal (i.e. `0` ... `7`) |
| `to_hex` | `INT` | [string] | hex (i.e. `0` ... `f`) |
```rust
let x = 0x1234abcd;
x == 305441741;
x.to_string() == "305441741";
x.to_binary() == "10010001101001010101111001101";
x.to_octal() == "2215125715";
x.to_hex() == "1234abcd";
```

View File

@ -0,0 +1,70 @@
Do Loop
=======
{{#include ../links.md}}
`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`.
Like the [`while`] loop, `continue` can be used to skip to the next iteration, by-passing all
following statements; `break` can be used to break out of the loop unconditionally.
~~~admonish tip.small "Tip: Disable `do` loops"
`do` loops can be disabled via [`Engine::set_allow_looping`][options].
~~~
```rust
let x = 10;
do {
x -= 1;
if x < 6 { continue; } // skip to the next iteration
print(x);
if x == 5 { break; } // break out of do loop
} while x > 0;
do {
x -= 1;
if x < 6 { continue; } // skip to the next iteration
print(x);
if x == 5 { break; } // break out of do loop
} until x == 0;
```
Do Expression
-------------
Like Rust, `do` statements can also be used as _expressions_.
The `break` statement takes an optional expression that provides the return value.
The default return value of a `do` expression is [`()`].
~~~admonish tip.small "Tip: Disable all loop expressions"
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
~~~
```js
let x = 0;
// 'do' can be used just like an expression
let result = do {
if is_magic_number(x) {
// if the 'do' loop breaks here, return a specific value
break get_magic_result(x);
}
x += 1;
// ... if the 'do' loop exits here, the return value is ()
} until x >= 100;
if result == () {
print("Magic number not found!");
} else {
print(`Magic result = ${result}!`);
}
```

View File

@ -0,0 +1,79 @@
Doc-Comments
============
{{#include ../links.md}}
Similar to Rust, [comments] starting with `///` (three slashes) or `/**` (two asterisks)
are _doc-comments_.
Doc-comments can only appear in front of [function] definitions, not any other elements.
Therefore, doc-comments are not available under [`no_function`].
~~~admonish warning.small "Requires `metadata`"
Doc-comments are only supported under the [`metadata`] feature.
If [`metadata`] is not active, doc-comments are treated as normal [comments].
~~~
```rust
/// This is a valid one-line doc-comment
fn foo() {}
/** This is a
** valid block
** doc-comment
**/
fn bar(x) {
/// Syntax error: this doc-comment is invalid
x + 1
}
/** Syntax error: this doc-comment is invalid */
let x = 42;
/// Syntax error: this doc-comment is also invalid
{
let x = 42;
}
```
~~~admonish tip "Tip: Special cases"
Long streams of `//////`... and `/*****`... do _NOT_ form doc-comments.
This is consistent with popular [comment] block styles for C-like languages.
```rust
/////////////////////////////// <- this is not a doc-comment
// This is not a doc-comment // <- this is not a doc-comment
/////////////////////////////// <- this is not a doc-comment
// However, watch out for comment lines starting with '///'
////////////////////////////////////////// <- this is not a doc-comment
/// This, however, IS a doc-comment!!! /// <- doc-comment!
////////////////////////////////////////// <- this is not a doc-comment
/****************************************
* *
* This is also not a doc-comment block *
* so we don't have to put this in *
* front of a function. *
* *
****************************************/
```
~~~
Using Doc-Comments
------------------
Doc-comments are stored within the script's [`AST`] after compilation.
The `AST::iter_functions` method provides a `ScriptFnMetadata` instance for each function defined
within the script, which includes doc-comments.
Doc-comments never affect the evaluation of a script nor do they incur significant performance overhead.
However, third party tools can take advantage of this information to auto-generate documentation for
Rhai script functions.

View File

@ -0,0 +1,257 @@
Interop `Dynamic` Data with Rust
================================
{{#include ../links.md}}
Create a `Dynamic` from Rust Type
---------------------------------
| Rust type<br/>`T: Clone`,<br/>`K: Into<String>` | Unavailable under | Use API |
| ------------------------------------------------------------------ | :-----------------------: | ---------------------------- |
| `INT` (`i64` or `i32`) | | `value.into()` |
| `FLOAT` (`f64` or `f32`) | [`no_float`] | `value.into()` |
| [`Decimal`][rust_decimal] (requires [`decimal`]) | | `value.into()` |
| `bool` | | `value.into()` |
| [`()`] | | `value.into()` |
| [`String`][string], [`&str`][string], [`ImmutableString`] | | `value.into()` |
| `char` | | `value.into()` |
| [`Array`][array] | [`no_index`] | `Dynamic::from_array(value)` |
| [`Blob`][BLOB] | [`no_index`] | `Dynamic::from_blob(value)` |
| `Vec<T>`, `&[T]`, `Iterator<T>` | [`no_index`] | `value.into()` |
| [`Map`][object map] | [`no_object`] | `Dynamic::from_map(value)` |
| `HashMap<K, T>`, `HashSet<K>`,<br/>`BTreeMap<K, T>`, `BTreeSet<K>` | [`no_object`] | `value.into()` |
| [`INT..INT`][range], [`INT..=INT`][range] | | `value.into()` |
| `Rc<RwLock<T>>` or `Arc<Mutex<T>>` | [`no_closure`] | `value.into()` |
| [`Instant`][timestamp] | [`no_time`] or [`no_std`] | `value.into()` |
| All types (including above) | | `Dynamic::from(value)` |
Type Checking and Casting
-------------------------
~~~admonish tip.side "Tip: `try_cast` and `try_cast_result`"
The `try_cast` method does not panic but returns `None` upon failure.
The `try_cast_result` method also does not panic but returns the original value upon failure.
~~~
A [`Dynamic`] value's actual type can be checked via `Dynamic::is`.
The `cast` method then converts the value into a specific, known type.
Use `clone_cast` to clone a reference to [`Dynamic`].
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0].clone(); // an element in an 'Array' is 'Dynamic'
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
let value = list[0].clone_cast::<i64>(); // use 'clone_cast' on '&Dynamic'
let value: i64 = list[0].clone_cast();
```
Type Name and Matching Types
----------------------------
The `type_name` method gets the name of the actual type as a static string slice,
which can be `match`-ed against.
This is a very simple and direct way to act on a [`Dynamic`] value based on the actual type of
the data value.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
match item.type_name() { // 'type_name' returns the name of the actual Rust type
"()" => ...
"i64" => ...
"f64" => ...
"rust_decimal::Decimal" => ...
"core::ops::range::Range<i64>" => ...
"core::ops::range::RangeInclusive<i64>" => ...
"alloc::string::String" => ...
"bool" => ...
"char" => ...
"rhai::FnPtr" => ...
"std::time::Instant" => ...
"crate::path::to::module::TestStruct" => ...
:
}
```
```admonish warning.small "Always full path name"
`type_name` always returns the _full_ Rust path name of the type, even when the type
has been registered with a friendly name via `Engine::register_type_with_name`.
This behavior is different from that of the [`type_of`][`type_of()`] function in Rhai.
```
Getting a Reference to Data
---------------------------
Use `Dynamic::read_lock` and `Dynamic::write_lock` to get an immutable/mutable reference to the data
inside a [`Dynamic`].
```rust
struct TheGreatQuestion {
answer: i64
}
let question = TheGreatQuestion { answer: 42 };
let mut value: Dynamic = Dynamic::from(question);
let q_ref: &TheGreatQuestion =
&*value.read_lock::<TheGreatQuestion>().unwrap();
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
println!("answer = {}", q_ref.answer); // prints 42
let q_mut: &mut TheGreatQuestion =
&mut *value.write_lock::<TheGreatQuestion>().unwrap();
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
q_mut.answer = 0; // mutate value
let value = value.cast::<TheGreatQuestion>();
println!("new answer = {}", value.answer); // prints 0
```
~~~admonish question "TL;DR &ndash; Why `read_lock` and `write_lock`?"
As the naming shows, something is _locked_ in order to allow accessing the data within a [`Dynamic`],
and that something is a _shared value_ created by capturing variables from [closures].
Shared values are implemented as `Rc<RefCell<Dynamic>>` (`Arc<RwLock<Dynamic>>` under [`sync`]).
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
no capturing, this API de-sugars to a simple reference cast.
In other words, there is no locking and reference counting overhead for the vast majority of
non-shared values.
If the value _is_ a shared value, then it is first _locked_ and the returned _lock guard_
allows access to the underlying value in the specified type.
~~~
Methods and Traits
------------------
The following methods are available when working with [`Dynamic`]:
| Method | Not available under | Return type | Description |
| --------------- | :-------------------------: | :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type_name` | | `&str` | name of the value's type |
| `into_shared` | [`no_closure`] | [`Dynamic`] | turn the value into a _shared_ value |
| `flatten_clone` | | [`Dynamic`] | clone the value (a _shared_ value, if any, is cloned into a separate copy) |
| `flatten` | | [`Dynamic`] | clone the value into a separate copy if it is _shared_ and there are multiple outstanding references, otherwise _shared_ values are turned unshared |
| `read_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value for reading |
| `write_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value exclusively for writing |
| `deep_scan` | | | recursively scan for [`Dynamic`] values (e.g. items inside an [array] or [object map], or [curried arguments][currying] in a [function pointer]) |
### Constructor instance methods
| Method | Not available under | Value type | Data type |
| ---------------- | :-----------------------: | :---------------------------------------------------------: | :-----------------------: |
| `from_bool` | | `bool` | `bool` |
| `from_int` | | `INT` | integer number |
| `from_float` | [`no_float`] | `FLOAT` | floating-point number |
| `from_decimal` | non-[`decimal`] | [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
| `from_str` | | `&str` | [string] |
| `from_char` | | `char` | [character] |
| `from_array` | [`no_index`] | `Vec<T>` | [array] |
| `from_blob` | [`no_index`] | `Vec<u8>` | [BLOB] |
| `from_map` | [`no_object`] | `Map` | [object map] |
| `from_timestamp` | [`no_time`] or [`no_std`] | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | [timestamp] |
| `from<T>` | | `T` | [custom type] |
### Detection methods
| Method | Not available under | Return type | Description |
| -------------- | :-----------------------: | :---------: | ---------------------------------------------------------------------- |
| `is<T>` | | `bool` | is the value of type `T`? |
| `is_variant` | | `bool` | is the value a trait object (i.e. not one of Rhai's [standard types])? |
| `is_read_only` | | `bool` | is the value [constant]? A [constant] value should not be modified. |
| `is_shared` | [`no_closure`] | `bool` | is the value _shared_ via a [closure]? |
| `is_locked` | [`no_closure`] | `bool` | is the value _shared_ and locked (i.e. currently being read)? |
| `is_unit` | | `bool` | is the value [`()`]? |
| `is_int` | | `bool` | is the value an integer? |
| `is_float` | [`no_float`] | `bool` | is the value a floating-point number? |
| `is_decimal` | non-[`decimal`] | `bool` | is the value a [`Decimal`][rust_decimal]? |
| `is_bool` | | `bool` | is the value a `bool`? |
| `is_char` | | `bool` | is the value a [character]? |
| `is_string` | | `bool` | is the value a [string]? |
| `is_array` | [`no_index`] | `bool` | is the value an [array]? |
| `is_blob` | [`no_index`] | `bool` | is the value a [BLOB]? |
| `is_map` | [`no_object`] | `bool` | is the value an [object map]? |
| `is_timestamp` | [`no_time`] or [`no_std`] | `bool` | is the value a [timestamp]? |
### Casting methods
The following methods cast a [`Dynamic`] into a specific type:
| Method | Not available under | Return type (error is name of actual type if `&str`) |
| ------------------------- | :-----------------: | :------------------------------------------------------------------------: |
| `cast<T>` | | `T` (panics on failure) |
| `try_cast<T>` | | `Option<T>` |
| `try_cast_result<T>` | | `Result<T, Dynamic>` |
| `clone_cast<T>` | | cloned copy of `T` (panics on failure) |
| `as_unit` | | `Result<(), &str>` |
| `as_int` | | `Result<INT, &str>` |
| `as_float` | [`no_float`] | `Result<FLOAT, &str>` |
| `as_decimal` | non-[`decimal`] | [`Result<Decimal, &str>`][rust_decimal] |
| `as_bool` | | `Result<bool, &str>` |
| `as_char` | | `Result<char, &str>` |
| `as_immutable_string_ref` | | [`Result<impl Deref<Target=ImmutableString>, &str>`][`ImmutableString`] |
| `as_immutable_string_mut` | | [`Result<impl DerefMut<Target=ImmutableString>, &str>`][`ImmutableString`] |
| `as_array_ref` | [`no_index`] | [`Result<impl Deref<Target=Array>, &str>`][array] |
| `as_array_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Array>, &str>`][array] |
| `as_blob_ref` | [`no_index`] | [`Result<impl Deref<Target=Blob>, &str>`][BLOB] |
| `as_blob_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Blob>, &str>`][BLOB] |
| `as_map_ref` | [`no_object`] | [`Result<impl Deref<Target=Map>, &str>`][object map] |
| `as_map_mut` | [`no_object`] | [`Result<impl DerefMut<Target=Map>, &str>`][object map] |
| `into_string` | | `Result<String, &str>` |
| `into_immutable_string` | | [`Result<ImmutableString, &str>`][`ImmutableString`] |
| `into_array` | [`no_index`] | [`Result<Array, &str>`][array] |
| `into_blob` | [`no_index`] | [`Result<Blob, &str>`][BLOB] |
| `into_typed_array<T>` | [`no_index`] | `Result<Vec<T>, &str>` |
### Constructor traits
The following constructor traits are implemented for [`Dynamic`] where `T: Clone`:
| Trait | Not available under | Data type |
| ------------------------------------------------------------------------------ | :----------------------------: | :-----------------------: |
| `From<()>` | | `()` |
| `From<INT>` | | integer number |
| `From<FLOAT>` | [`no_float`] | floating-point number |
| `From<Decimal>` | non-[`decimal`] | [`Decimal`][rust_decimal] |
| `From<bool>` | | `bool` |
| `From<S: Into<ImmutableString>>`<br/>e.g. `From<String>`, `From<&str>` | | [`ImmutableString`] |
| `From<char>` | | [character] |
| `From<Vec<T>>` | [`no_index`] | [array] |
| `From<&[T]>` | [`no_index`] | [array] |
| `From<BTreeMap<K: Into<SmartString>, T>>`<br/>e.g. `From<BTreeMap<String, T>>` | [`no_object`] | [object map] |
| `From<BTreeSet<K: Into<SmartString>>>`<br/>e.g. `From<BTreeSet<String>>` | [`no_object`] | [object map] |
| `From<HashMap<K: Into<SmartString>, T>>`<br/>e.g. `From<HashMap<String, T>>` | [`no_object`] or [`no_std`] | [object map] |
| `From<HashSet<K: Into<SmartString>>>`<br/>e.g. `From<HashSet<String>>` | [`no_object`] or [`no_std`] | [object map] |
| `From<FnPtr>` | | [function pointer] |
| `From<Instant>` | [`no_time`] or [`no_std`] | [timestamp] |
| `From<Rc<RefCell<Dynamic>>>` | [`sync`] or [`no_closure`] | [`Dynamic`] |
| `From<Arc<RwLock<Dynamic>>>` ([`sync`]) | non-[`sync`] or [`no_closure`] | [`Dynamic`] |
| `FromIterator<X: IntoIterator<Item=T>>` | [`no_index`] | [array] |

View File

@ -0,0 +1,255 @@
Dynamic Value Tag
=================
{{#include ../links.md}}
Each [`Dynamic`] value can contain a _tag_ that is `i32` and can contain any arbitrary data.
On 32-bit targets, however, the tag is only `i16`.
The tag defaults to zero.
```admonish bug.small "Value out of bounds"
It is an error to set a tag to a value beyond the bounds of `i32` (`i16` on 32-bit targets).
```
Examples
--------
```rust
let x = 42;
x.tag == 0; // tag defaults to zero
x.tag = 123; // set tag value
set_tag(x, 123); // 'set_tag' function also works
x.tag == 123; // get updated tag value
x.tag() == 123; // method also works
tag(x) == 123; // function call style also works
x.tag[3..5] = 2; // tag can be used as a bit-field
x.tag[3..5] == 2;
let y = x;
y.tag == 123; // the tag is copied across assignment
y.tag = 3000000000; // runtime error: 3000000000 is too large for 'i32'
```
Practical Applications
----------------------
Attaching arbitrary information together with a value has a lot of practical uses.
### Identify code path
For example, it is easy to attach an ID number to a value to indicate how or why that value is
originally set.
This is tremendously convenient for debugging purposes where it is necessary to figure out which
code path a particular value went through.
After the script is verified, all tag assignment statements can simply be removed.
```js
const ROUTE1 = 1;
const ROUTE2 = 2;
const ROUTE3 = 3;
const ERROR_ROUTE = 9;
fn some_complex_calculation(x) {
let result;
if some_complex_condition(x) {
result = 42;
result.tag = ROUTE1; // record route #1
} else if some_other_very_complex_condition(x) == 1 {
result = 123;
result.tag = ROUTE2; // record route #2
} else if some_non_understandable_calculation(x) > 0 {
result = 0;
result.tag = ROUTE3; // record route #3
} else {
result = -1;
result.tag = ERROR_ROUTE; // record error
}
result // this value now contains the tag
}
let my_result = some_complex_calculation(key);
// The code path that 'my_result' went through is now in its tag.
// It is now easy to trace how 'my_result' gets its final value.
print(`Result = ${my_result} and reason = ${my_result.tag}`);
```
### Identify data source
It is convenient to use the tag value to record the _source_ of a piece of data.
```js
let x = [0, 1, 2, 3, 42, 99, 123];
// Store the index number of each value into its tag before
// filtering out all even numbers, leaving only odd numbers
let filtered = x.map(|v, i| { v.tag = i; v }).filter(|v| v.is_odd());
// The tag now contains the original index position
for (data, i) in filtered {
print(`${i + 1}: Value ${data} from position #${data.tag + 1}`);
}
```
### Identify code conditions
The tag value may also contain a _[bit-field]_ of up to 32 (16 under 32-bit targets) individual bits,
recording up to 32 (or 16 under 32-bit targets) logic conditions that contributed to the value.
Again, after the script is verified, all tag assignment statements can simply be removed.
```js
fn some_complex_calculation(x) {
let result = x;
// Check first condition
if some_complex_condition() {
result += 1;
result.tag[0] = true; // Set first bit in bit-field
}
// Check second condition
if some_other_very_complex_condition(x) == 1 {
result *= 10;
result.tag[1] = true; // Set second bit in bit-field
}
// Check third condition
if some_non_understandable_calculation(x) > 0 {
result -= 42;
result.tag[2] = true; // Set third bit in bit-field
}
// Check result
if result > 100 {
result = 0;
result.tag[3] = true; // Set forth bit in bit-field
}
result
}
let my_result = some_complex_calculation(42);
// The tag of 'my_result' now contains a bit-field indicating
// the result of each condition.
// It is now easy to trace how 'my_result' gets its final value.
// Use indexing on the tag to get at individual bits.
print(`Result = ${my_result}`);
print(`First condition = ${my_result.tag[0]}`);
print(`Second condition = ${my_result.tag[1]}`);
print(`Third condition = ${my_result.tag[2]}`);
print(`Result check = ${my_result.tag[3]}`);
```
### Return auxillary info
Sometimes it is useful to return auxillary info from a [function].
```rust
// Verify Bell's Inequality by calculating a norm
// and comparing it with a hypotenuse.
// https://en.wikipedia.org/wiki/Bell%27s_theorem
//
// Returns the smaller of the norm or hypotenuse.
// Tag is 1 if norm <= hypo, 0 if otherwise.
fn bells_inequality(x, y, z) {
let norm = sqrt(x ** 2 + y ** 2);
let result;
if norm <= z {
result = norm;
result.tag = 1;
} else {
result = z;
result.tag = 0;
}
result
}
let dist = bells_inequality(x, y, z);
print(`Value = ${dist}`);
if dist.tag == 1 {
print("Local realism maintained! Einstein rules!");
} else {
print("Spooky action at a distance detected! Einstein will hate this...");
}
```
### Poor-man's tuples
Rust has _tuples_ but Rhai does not (nor does JavaScript in this sense).
Similar to the JavaScript situation, practical alternatives using Rhai include returning an
[object map] or an [array].
Both of these alternatives, however, incur overhead that may be wasteful when the amount of
additional information is small &ndash; e.g. in many cases, a single `bool`, or a small number.
To return a number of _small_ values from [functions], the tag value as a [bit-field] is an ideal
container without resorting to a full-blown [object map] or [array].
```rust
// This function essentially returns a tuple of four numbers:
// (result, a, b, c)
fn complex_calc(x, y, z) {
let a = x + y;
let b = x - y + z;
let c = (a + b) * z / y;
let r = do_complex_calculation(a, b, c);
// Store 'a', 'b' and 'c' into tag if they are small
r.tag[0..8] = a;
r.tag[8..16] = b;
r.tag[16..32] = c;
r
}
// Deconstruct the tuple
let result = complex_calc(x, y, z);
let a = r.tag[0..8];
let b = r.tag[8..16];
let c = r.tag[16..32];
```
TL;DR
-----
```admonish question "Tell me, really, what is the _point_?"
Due to byte alignment requirements on modern CPU's, there are unused spaces in a [`Dynamic`] type,
of the order of 4 bytes on 64-bit targets (2 bytes on 32-bit).
It is empty space that can be put to good use and not wasted, especially when Rhai does not have
built-in support of tuples in order to return multiple values from [functions].
```

View File

@ -0,0 +1,77 @@
Dynamic Values
==============
{{#include ../links.md}}
A `Dynamic` value can be _any_ type, as long as it implements `Clone`.
~~~admonish warning.small "`Send + Sync`"
Under the [`sync`] feature, all types must also be `Send + Sync`.
~~~
```rust
let x = 42; // value is an integer
x = 123.456; // value is now a floating-point number
x = "hello"; // value is now a string
x = x.len > 0; // value is now a boolean
x = [x]; // value is now an array
x = #{x: x}; // value is now an object map
```
Use `type_of()` to Get Value Type
---------------------------------
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
it is usually used to perform type-specific actions based on the actual value's type.
```js
let mystery = get_some_dynamic_value();
switch type_of(mystery) {
"()" => print("Hey, I got the unit () here!"),
"i64" => print("Hey, I got an integer here!"),
"f64" => print("Hey, I got a float here!"),
"decimal" => print("Hey, I got a decimal here!"),
"range" => print("Hey, I got an exclusive range here!"),
"range=" => print("Hey, I got an inclusive range here!"),
"string" => print("Hey, I got a string here!"),
"bool" => print("Hey, I got a boolean here!"),
"array" => print("Hey, I got an array here!"),
"blob" => print("Hey, I got a BLOB here!"),
"map" => print("Hey, I got an object map here!"),
"Fn" => print("Hey, I got a function pointer here!"),
"timestamp" => print("Hey, I got a time-stamp here!"),
"TestStruct" => print("Hey, I got the TestStruct custom type here!"),
_ => print(`I don't know what this is: ${type_of(mystery)}`)
}
```
Parse from JSON
---------------
~~~admonish warning.side "Requires `metadata`"
`parse_json` is defined in the [`LanguageCorePackage`][built-in packages], which is excluded when using a [raw `Engine`].
It also requires the [`metadata`] feature; the [`no_index`] and [`no_object`] features must _not_ be set.
~~~
Use `parse_json` to parse a JSON string into a [`Dynamic`] value.
| JSON type | Rhai type |
| :---------------------------: | :----------: |
| `number` (no decimal point) | `INT` |
| `number` (with decimal point) | `FLOAT` |
| `string` | [string] |
| `boolean` | `bool` |
| `Array` | [array] |
| `Object` | [object map] |
| `null` | [`()`] |

View File

@ -0,0 +1,97 @@
`eval` Function
===============
{{#include ../links.md}}
Or "How to Shoot Yourself in the Foot even Easier"
--------------------------------------------------
Saving the best for last, there is the ever-dreaded... `eval` [function]!
```rust
let x = 10;
fn foo(x) { x += 12; x }
let script =
"
let y = x;
y += foo(y);
x + y
";
let result = eval(script); // <- look, JavaScript, we can also do this!
result == 42;
x == 10; // prints 10 - arguments are passed by value
y == 32; // prints 32 - variables defined in 'eval' persist!
eval("{ let z = y }"); // to keep a variable local, use a statements block
print(z); // <- error: variable 'z' not found
"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
```
~~~admonish danger.small "`eval` executes inside the current scope!"
Script segments passed to `eval` execute inside the _current_ [`Scope`], so they can access and modify
_everything_, including all [variables] that are visible at that position in code!
```rust
let script = "x += 32";
let x = 10;
eval(script); // variable 'x' is visible!
print(x); // prints 42
// The above is equivalent to:
let script = "x += 32";
let x = 10;
x += 32;
print(x);
```
`eval` can also be used to define new [variables] and do other things normally forbidden inside
a [function] call.
```rust
let script = "let x = 42";
eval(script);
print(x); // prints 42
```
Treat it as if the script segments are physically pasted in at the position of the `eval` call.
~~~
~~~admonish warning.small "Cannot define new functions"
New [functions] cannot be defined within an `eval` call, since [functions] can only be defined at
the _global_ level!
~~~
~~~admonish failure.small "`eval` is evil"
For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil),
disable `eval` via [`Engine::disable_symbol`][disable keywords and operators].
```rust
// Disable usage of 'eval'
engine.disable_symbol("eval");
```
~~~
~~~admonish question.small "Do you regret implementing `eval` in Rhai?"
Or course we do.
Having the possibility of an `eval` call disrupts any predictability in the Rhai script,
thus disabling a large number of optimizations.
~~~
```admonish question.small "Why did it then???!!!"
Brendan Eich puts it well: "it is just too easy to implement." _(source wanted)_
```

View File

@ -0,0 +1,109 @@
Anonymous Functions
===================
{{#include ../links.md}}
Many functions in the standard API expect [function pointer] as parameters.
For example:
```rust
// Function 'double' defined here - used only once
fn double(x) { 2 * x }
// Function 'square' defined here - again used only once
fn square(x) { x * x }
let x = [1, 2, 3, 4, 5];
// Pass a function pointer to 'double'
let y = x.map(double);
// Pass a function pointer to 'square' using Fn(...) notation
let z = y.map(Fn("square"));
```
Sometimes it gets tedious to define separate [functions] only to dispatch them via single [function pointers] &ndash;
essentially, those [functions] are only ever called in one place.
This scenario is especially common when simulating object-oriented programming ([OOP]).
```rust
// Define functions one-by-one
fn obj_inc(x, y) { this.data += x * y; }
fn obj_dec(x) { this.data -= x; }
fn obj_print() { print(this.data); }
// Define object
let obj = #{
data: 42,
increment: obj_inc, // use function pointers to
decrement: obj_dec, // refer to method functions
print: obj_print
};
```
Syntax
------
Anonymous [functions] have a syntax similar to Rust's _closures_ (they are _not_ the same).
> `|`_param 1_`,` _param 2_`,` ... `,` _param n_`|` _statement_
>
> `|`_param 1_`,` _param 2_`,` ... `,` _param n_`| {` _statements_... `}`
No parameters:
> `||` _statement_
>
> `|| {` _statements_... `}`
Anonymous functions can be disabled via [`Engine::set_allow_anonymous_function`][options].
Rewrite Using Anonymous Functions
---------------------------------
The above can be rewritten using _anonymous [functions]_.
```rust
let x = [1, 2, 3, 4, 5];
let y = x.map(|x| 2 * x);
let z = y.map(|x| x * x);
let obj = #{
data: 42,
increment: |x, y| this.data += x * y, // one statement
decrement: |x| this.data -= x, // one statement
print_obj: || {
print(this.data); // full function body
}
};
```
This de-sugars to:
```rust
// Automatically generated...
fn anon_fn_0001(x) { 2 * x }
fn anon_fn_0002(x) { x * x }
fn anon_fn_0003(x, y) { this.data += x * y; }
fn anon_fn_0004(x) { this.data -= x; }
fn anon_fn_0005() { print(this.data); }
let x = [1, 2, 3, 4, 5];
let y = x.map(anon_fn_0001);
let z = y.map(anon_fn_0002);
let obj = #{
data: 42,
increment: anon_fn_0003,
decrement: anon_fn_0004,
print: anon_fn_0005
};
```

View File

@ -0,0 +1,194 @@
Closures
========
{{#include ../links.md}}
~~~admonish tip.side "Tip: `is_shared`"
Use `Dynamic::is_shared` to check whether a particular [`Dynamic`] value is shared.
~~~
Although [anonymous functions] de-sugar to standard function definitions, they differ from standard
functions because they can _captures_ [variables] that are not defined within the current scope,
but are instead defined in an external scope &ndash; i.e. where the [anonymous function] is created.
All [variables] that are accessible during the time the [anonymous function] is created are
automatically captured when they are used, as long as they are not shadowed by local [variables]
defined within the function's.
The captured [variables] are automatically converted into **reference-counted shared values**
(`Rc<RefCell<Dynamic>>`, or `Arc<RwLock<Dynamic>>` under [`sync`]).
Therefore, similar to closures in many languages, these captured shared values persist through
reference counting, and may be read or modified even after the [variables] that hold them go out of
scope and no longer exist.
```admonish tip.small "Tip: Disable closures"
Capturing external [variables] can be turned off via the [`no_closure`] feature.
```
Examples
--------
```rust
let x = 1; // a normal variable
x.is_shared() == false;
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value!
f.call(2) == 3; // 1 + 2 == 3
x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
// The above de-sugars into something like this:
fn anon_0001(x, y) { x + y } // parameter 'x' is inserted
make_shared(x); // convert variable 'x' into a shared value
let f = anon_0001.curry(x); // shared 'x' is curried
```
~~~admonish bug "Beware: Captured Variables are Truly Shared"
The example below is a typical tutorial sample for many languages to illustrate the traps
that may accompany capturing external [variables] in closures.
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is
ever only _one_ captured [variable], and all ten closures capture the _same_ [variable].
```rust
let list = [];
for i in 0..10 {
list.push(|| print(i)); // the for loop variable 'i' is captured
}
list.len() == 10; // 10 closures stored in the array
list[0].type_of() == "Fn"; // make sure these are closures
for f in list {
f.call(); // all references to 'i' are the same variable!
}
```
~~~
~~~admonish danger "Be Careful to Prevent Data Races"
Rust does not have data races, but that doesn't mean Rhai doesn't.
Avoid performing a method call on a captured shared [variable] (which essentially takes a
mutable reference to the shared object) while using that same [variable] as a parameter
in the method call &ndash; this is a sure-fire way to generate a data race error.
If a shared value is used as the `this` pointer in a method call to a closure function,
then the same shared value _must not_ be captured inside that function, or a data race
will occur and the script will terminate with an error.
```rust
let x = 20;
x.is_shared() == false; // 'x' is not shared, so no data race is possible
let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared
x.call(f, 2); // <- error: data race detected on 'x'
```
~~~
~~~admonish danger "Data Races in `sync` Builds Can Become Deadlocks"
Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race
conditions no longer raise an error.
Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks.
On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock
is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1)
depending on the O/S.
```rust
let x = 20;
let f = |a| this += x + a; // 'x' is captured in this closure
// Under `sync`, the following may wait forever, or may panic,
// because 'x' is locked as the `this` pointer but also accessed
// via a captured shared value.
x.call(f, 2);
```
~~~
TL;DR
-----
```admonish question "How is it actually implemented?"
The actual implementation of closures de-sugars to:
1. Keeping track of what [variables] are accessed inside the [anonymous function],
2. If a [variable] is not defined within the [anonymous function's][anonymous function] scope,
it is looked up _outside_ the [function] and in the current execution scope &ndash;
where the [anonymous function] is created.
3. The [variable] is added to the parameters list of the [anonymous function], at the front.
4. The [variable] is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external [variable] is the only way to create a
reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself,
essentially carrying a reference to that shared value and inserting it into future calls of the [function].
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates closures.
```
```admonish question "Why automatic currying?"
In concept, a closure _closes_ over captured variables from the outer scope &ndash; that's why
they are called _closures_. When this happen, a typical language implementation hoists
those variables that are captured away from the stack frame and into heap-allocated storage.
This is because those variables may be needed after the stack frame goes away.
These heap-allocated captured variables only go away when all the closures that need them
are finished with them. A garbage collector makes this trivial to implement &ndash; they are
automatically collected as soon as all closures needing them are destroyed.
In Rust, this can be done by reference counting instead, with the potential pitfall of creating
reference loops that will prevent those variables from being deallocated forever.
Rhai avoids this by clone-copying most data values, so reference loops are hard to create.
Rhai does the hoisting of captured variables into the heap by converting those values
into reference-counted locked values, also allocated on the heap. The process is identical.
Closures are usually implemented as a data structure containing two items:
1. A function pointer to the function body of the closure,
2. A data structure containing references to the captured shared variables on the heap.
Usually a language implementation passes the structure containing references to captured
shared variables into the function pointer, the function body taking this data structure
as an additional parameter.
This is essentially what Rhai does, except that Rhai passes each variable individually
as separate parameters to the function, instead of creating a structure and passing that
structure as a single parameter. This is the only difference.
Therefore, in most languages, essentially all closures are implemented as automatic currying of
shared variables hoisted into the heap, automatically passing those variables as parameters into
the function. Rhai just brings this directly up to the front.
```

View File

@ -0,0 +1,31 @@
Function Pointer Currying
=========================
{{#include ../links.md}}
It is possible to _curry_ a [function pointer] by providing partial (or all) arguments.
Currying is done via the `curry` keyword and produces a new [function pointer] which carries
the curried arguments.
When the curried [function pointer] is called, the curried arguments are inserted starting from the _left_.
The actual call arguments should be reduced by the number of curried arguments.
```rust
fn mul(x, y) { // function with two parameters
x * y
}
let func = mul; // <- de-sugars to 'Fn("mul")'
func.call(21, 2) == 42; // two arguments are required for 'mul'
let curried = func.curry(21); // currying produces a new function pointer which
// carries 21 as the first argument
let curried = curry(func, 21); // function-call style also works
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
// only one argument is now required
```

View File

@ -0,0 +1,39 @@
Get Functions Metadata in Scripts
=================================
{{#include ../links.md}}
The built-in function `get_fn_metadata_list` returns an [array] of [object maps], each containing
the metadata of one script-defined [function] in scope.
`get_fn_metadata_list` is defined in the [`LanguageCorePackage`][built-in packages], which is
excluded when using a [raw `Engine`].
`get_fn_metadata_list` has a few versions taking different parameters:
| Signature | Description |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `get_fn_metadata_list()` | returns an [array] for _all_ script-defined [functions] |
| `get_fn_metadata_list(name)` | returns an [array] containing all script-defined [functions] matching a specified name |
| `get_fn_metadata_list(name, params)` | returns an [array] containing all script-defined [functions] matching a specified name and accepting the specified number of parameters |
[Functions] from the following sources are returned, in order:
1. Encapsulated script environment (e.g. when loading a [module] from a script file),
2. Current script,
3. [Modules] registered via `Engine::register_global_module` (latest registrations first)
4. [Modules] imported via the [`import`] statement (latest imports first),
5. [Modules] added via `Engine::register_static_module` (latest registrations first)
The return value is an [array] of [object maps] (so `get_fn_metadata_list` is also not available under
[`no_index`] or [`no_object`]), containing the following fields.
| Field | Type | Optional? | Description |
| -------------- | :------------------------: | :-------: | ----------------------------------------------------------------------------------- |
| `namespace` | [string] | **yes** | the module _namespace_ if the [function] is defined within a [module] |
| `access` | [string] | no | `"public"` if the function is public,<br/>`"private"` if it is [private][`private`] |
| `name` | [string] | no | [function] name |
| `params` | [array] of [strings] | no | parameter names |
| `this_type` | [string](strings-chars.md) | **yes** | restrict the type of `this` if the [function] is a [method] |
| `is_anonymous` | `bool` | no | is this [function] an [anonymous function]? |
| `comments` | [array] of [strings] | **yes** | [doc-comments], if any, one per line |

View File

@ -0,0 +1,197 @@
`this` &ndash; Simulating an Object Method
==========================================
{{#include ../links.md}}
```admonish warning.side "Functions are pure"
The only way for a script-defined [function] to change an external value is via `this`.
```
Arguments passed to script-defined [functions] are always by _value_ because [functions] are _pure_.
However, [functions] can also be called in _method-call_ style (not available under [`no_object`]):
> _object_ `.` _method_ `(` _parameters_ ... `)`
When a [function] is called this way, the keyword `this` binds to the object in the method call and
can be changed.
```rust
fn change() { // note that the method does not need a parameter
this = 42; // 'this' binds to the object in method-call
}
let x = 500;
x.change(); // call 'change' in method-call style, 'this' binds to 'x'
x == 42; // 'x' is changed!
change(); // <- error: 'this' is unbound
```
Elvis Operator
--------------
The [_Elvis_ operator][elvis] can be used to short-circuit the method call when the object itself is [`()`].
> _object_ `?.` _method_ `(` _parameters_ ... `)`
In the above, the _method_ is never called if _object_ is [`()`].
Restrict the Type of `this` in Function Definitions
---------------------------------------------------
```admonish tip.side.wide "Tip: Automatically global"
Methods defined this way are automatically exposed to the [global namespace][function namespaces].
```
In many cases it may be desirable to implement _methods_ for different [custom types] using
script-defined [functions].
### The Problem
Doing so is brittle and requires a lot of type checking code because there can only be one
[function] definition for the same name and arity:
```js
// Really painful way to define a method called 'do_update' on various data types
fn do_update(x) {
switch type_of(this) {
"i64" => this *= x,
"string" => this.len += x,
"bool" if this => this *= x,
"bool" => this *= 42,
"MyType" => this.update(x),
"Strange-Type#Name::with_!@#symbols" => this.update(x),
_ => throw `I don't know how to handle ${type_of(this)}`!`
}
}
```
### The Solution
With a special syntax, it is possible to restrict a [function] to be callable only when the object
pointed to by `this` is of a certain type:
> `fn` _type name_ `.` _method_ `(` _parameters_ ... `) {` ... `}`
or in quotes if the type name is not a valid identifier itself:
> `fn` `"`_type name string_`"` `.` _method_ `(` _parameters_ ... `) {` ... `}`
Needless to say, this _typed method_ definition style is not available under [`no_object`].
~~~admonish warning.small "Type name must be the same as `type_of`"
The _type name_ specified in front of the [function] name must match the output of [`type_of`]
for the required type.
~~~
~~~admonish tip.small "Tip: `int` and `float`"
`int` can be used in place of the system integer type (usually `i64` or `i32`).
`float` can be used in place of the system floating-point type (usually `f64` or `f32`).
Using these make scripts more portable.
~~~
### Examples
```js
/// This 'do_update' can only be called on objects of type 'MyType' in method style
fn MyType.do_update(x, y) {
this.update(x * y);
}
/// This 'do_update' can only be called on objects of type 'Strange-Type#Name::with_!@#symbols'
/// (which can be specified via 'Engine::register_type_with_name') in method style
fn "Strange-Type#Name::with_!@#symbols".do_update(x, y) {
this.update(x * y);
}
/// Define a blanket version
fn do_update(x, y) {
this = `${this}, ${x}, ${y}`;
}
/// This 'do_update' can only be called on integers in method style
fn int.do_update(x, y) {
this += x * y
}
let obj = create_my_type(); // 'x' is 'MyType'
obj.type_of() == "MyType";
obj.do_update(42, 123); // ok!
let x = 42; // 'x' is an integer
x.type_of() == "i64";
x.do_update(42, 123); // ok!
let x = true; // 'x' is a boolean
x.type_of() == "bool";
x.do_update(42, 123); // <- this works because there is a blanket version
// Use 'is_def_fn' with three parameters to test for typed methods
is_def_fn("MyType", "do_update", 2) == true;
is_def_fn("int", "do_update", 2) == true;
```
Bind to `this` for Module Functions
-----------------------------------
### The Problem
The _method-call_ syntax is not possible for [functions] [imported][`import`] from [modules].
```js
import "my_module" as foo;
let x = 42;
x.foo::change_value(1); // <- syntax error
```
### The Solution
In order to call a [module] [function] as a method, it must be defined with a restriction on the
type of object pointed to by `this`:
```js
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// This is a typed method function requiring 'this' to be an integer.
// Typed methods are automatically marked global when importing this module.
fn int.change_value(offset) {
// 'this' is guaranteed to be an integer
this += offset;
}
┌───────────┐
│ main.rhai │
└───────────┘
import "my_module";
let x = 42;
x.change_value(1); // ok!
x == 43;
```

View File

@ -0,0 +1,124 @@
Function Namespaces
===================
{{#include ../links.md}}
Each Function is a Separate Compilation Unit
--------------------------------------------
[Functions] in Rhai are _pure_ and they form individual _compilation units_.
This means that individual [functions] can be separated, exported, re-grouped, imported, and
generally mix-'n-matched with other completely unrelated scripts.
For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators)
allow combining all [functions] in one [`AST`] into another, forming a new, unified, group of [functions].
Namespace Types
---------------
In general, there are two main types of _namespaces_ where [functions] are looked up:
| Namespace | Quantity | Source | Lookup | Sub-modules? | Variables? |
| --------- | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | :----------: | :--------: |
| Global | one | <ol><li>[`AST`] being evaluated</li><li>`Engine::register_XXX` API</li><li>global registered [modules]</li><li>[functions] in [imported][`import`] [modules] marked _global_</li><li>[functions] in registered static [modules] marked _global_</li></ol> | simple name | ignored | ignored |
| Module | many | <ol><li>[Module] registered via `Engine::register_static_module`</li><li>[Module] loaded via [`import`] statement</li></ol> | namespace-qualified name | yes | yes |
### Module Namespaces
There can be multiple [module] namespaces at any time during a script evaluation, usually loaded via
the [`import`] statement.
_Static_ [module] namespaces can also be registered into an [`Engine`] via `Engine::register_static_module`.
[Functions] and [variables] in module namespaces are isolated and encapsulated within their own environments.
They must be called or accessed in a _namespace-qualified_ manner.
```js
import "my_module" as m; // new module namespace 'm' created via 'import'
let x = m::calc_result(); // namespace-qualified function call
let y = m::MY_NUMBER; // namespace-qualified variable/constant access
let z = calc_result(); // <- error: function 'calc_result' not found
// in global namespace!
```
### Global Namespace
There is one _global_ namespace for every [`Engine`], which includes (in the following search order):
* all [functions] defined in the [`AST`] currently being evaluated,
* all native Rust functions and iterators registered via the `Engine::register_XXX` API,
* all functions and iterators defined in global [modules] that are registered into the [`Engine`]
via `register_global_module`,
* functions defined in [modules] registered into the [`Engine`] via `register_static_module` that
are specifically marked for exposure to the global namespace (e.g. via the `#[rhai(global)]`
attribute in a [plugin module]).
* [functions] defined in [imported][`import`] [modules] that are specifically marked for exposure to
the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]).
Anywhere in a Rhai script, when a function call is made, the function is searched within the
global namespace, in the above search order.
Therefore, function calls in Rhai are _late_ bound &ndash; meaning that the function called cannot be
determined or guaranteed; there is no way to _lock down_ the function being called.
This aspect is very similar to JavaScript before ES6 modules.
```rust
// Compile a script into AST
let ast1 = engine.compile(
r#"
fn get_message() {
"Hello!" // greeting message
}
fn say_hello() {
print(get_message()); // prints message
}
say_hello();
"#)?;
// Compile another script with an overriding function
let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?;
// Combine the two AST's
ast1 += ast2; // 'message' will be overwritten
engine.run_ast(&ast1)?; // prints 'Boo!'
```
Therefore, care must be taken when _cross-calling_ [functions] to make sure that the correct
[function] is called.
The only practical way to ensure that a [function] is a correct one is to use [modules] &ndash;
i.e. define the [function] in a separate [module] and then [`import`] it:
```js
┌──────────────┐
│ message.rhai │
└──────────────┘
fn get_message() { "Hello!" }
┌─────────────┐
│ script.rhai │
└─────────────┘
import "message" as msg;
fn say_hello() {
print(msg::get_message());
}
say_hello();
```

View File

@ -0,0 +1,96 @@
Call a Function Within the Caller's Scope
=========================================
{{#include ../links.md}}
Peeking Out of The Pure Box
---------------------------
```admonish info.side "Only scripts"
This is only meaningful for _scripted_ [functions].
Native Rust functions can never access any [`Scope`].
```
Rhai [functions] are _pure_, meaning that they depend on on their arguments and have no access to
the calling environment.
When a [function] accesses a [variable] that is not defined within that [function]'s [`Scope`],
it raises an evaluation error.
It is possible, through a special syntax, to actually run the [function] call within the [`Scope`]
of the parent caller &ndash; i.e. the [`Scope`] that makes the [function] call &ndash; and
access/mutate [variables] defined there.
```rust
fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined
x += y; // 'x' is modified in this function
let z = 0; // 'z' is defined in this function's scope
x
}
let x = 1; // 'x' is defined here in the parent scope
foo(41); // error: variable 'x' not found
// Calling a function with a '!' causes it to run within the caller's scope
foo!(41) == 42; // the function can access and mutate the value of 'x'!
x == 42; // 'x' is changed!
z == 0; // <- error: variable 'z' not found
x.method!(); // <- syntax error: not allowed in method-call style
// Also works for function pointers
let f = foo; // <- de-sugars to 'Fn("foo")'
call!(f, 42) == 84; // must use function-call style
x == 84; // 'x' is changed once again
f.call!(41); // <- syntax error: not allowed in method-call style
// But not allowed for module functions
import "hello" as h;
h::greet!(); // <- syntax error: not allowed in namespace-qualified calls
```
```admonish danger.small "The caller's scope can be mutated"
Changes to [variables] in the calling [`Scope`] persist.
With this syntax, it is possible for a Rhai [function] to mutate its calling environment.
```
```admonish warning.small "New variables are not retained"
[Variables] or [constants] defined within the [function] are _not_ retained.
They remain local to the [function].
Although the syntax resembles a Rust _macro_ invocation, it is still a [function] call.
```
```admonish danger "Caveat emptor"
[Functions] relying on the calling [`Scope`] is often a _Very Bad Idea™_ because it makes code
almost impossible to reason about and maintain, as their behaviors are volatile and unpredictable.
Rhai [functions] are normally _pure_, meaning that you can rely on the fact that they never mutate
the outside environment. Using this syntax breaks this guarantee.
[Functions] called in this manner behave more like macros that are expanded inline than actual
function calls, thus the syntax is also similar to Rust's macro invocations.
This usage should be at the last resort.
**YOU HAVE BEEN WARNED**.
```

View File

@ -0,0 +1,310 @@
Function Pointers
=================
{{#include ../links.md}}
```admonish question.side "Trivia"
A function pointer simply stores the _name_ of the [function] as a [string].
```
It is possible to store a _function pointer_ in a variable just like a normal value.
A function pointer is created via the `Fn` [function], which takes a [string] parameter.
Call a function pointer via the `call` method.
Short-Hand Notation
-------------------
```admonish warning.side "Not for native"
Native Rust functions cannot use this short-hand notation.
```
Having to write `Fn("foo")` in order to create a function pointer to the [function] `foo` is a chore,
so there is a short-hand available.
A function pointer to any _script-defined_ [function] _within the same script_ can be obtained simply
by referring to the [function's][function] name.
```rust
fn foo() { ... } // function definition
let f = foo; // function pointer to 'foo'
let f = Fn("foo"); // <- the above is equivalent to this
let g = bar; // error: variable 'bar' not found
```
The short-hand notation is particularly useful when passing [functions] as [closure] arguments.
```rust
fn is_even(n) { n % 2 == 0 }
let array = [1, 2, 3, 4, 5];
array.filter(is_even);
array.filter(Fn("is_even")); // <- the above is equivalent to this
array.filter(|n| n % 2 == 0); // <- ... or this
```
Built-in Functions
------------------
The following standard methods (mostly defined in the [`BasicFnPackage`][built-in packages] but
excluded when using a [raw `Engine`]) operate on function pointers.
| Function | Parameter(s) | Description |
| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ |
| `name` method and property | _none_ | returns the name of the [function] encapsulated by the function pointer |
| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? Not available under [`no_function`]. |
| `call` | _arguments_ | calls the [function] matching the function pointer's name with the _arguments_ |
Examples
--------
```rust
fn foo(x) { 41 + x }
let func = Fn("foo"); // use the 'Fn' function to create a function pointer
let func = foo; // <- short-hand: equivalent to 'Fn("foo")'
print(func); // prints 'Fn(foo)'
let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
func.name == "foo";
func.call(1) == 42; // call a function pointer with the 'call' method
foo(1) == 42; // <- the above de-sugars to this
call(func, 1); // normal function call style also works for 'call'
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
len.call("hello") == 5;
let fn_name = "hello"; // the function name does not have to exist yet
let hello = Fn(fn_name + "_world");
hello.call(0); // error: function not found - 'hello_world (i64)'
```
```admonish warning "Not First-Class Functions"
Beware that function pointers are _not_ first-class functions.
They are _syntactic sugar_ only, capturing only the _name_ of a [function] to call.
They do not hold the actual [functions].
The actual [function] must be defined in the appropriate [namespace][function namespace]
for the call to succeed.
```
~~~admonish warning "Global Namespace Only"
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
They can only refer to [functions] within the global [namespace][function namespace].
```js
import "foo" as f; // assume there is 'f::do_work()'
f::do_work(); // works!
let p = Fn("f::do_work"); // error: invalid function name
fn do_work_now() { // call it from a local function
f::do_work();
}
let p = Fn("do_work_now");
p.call(); // works!
```
~~~
Dynamic Dispatch
----------------
The purpose of function pointers is to enable rudimentary _dynamic dispatch_, meaning to determine,
at runtime, which function to call among a group.
Although it is possible to simulate dynamic dispatch via a number and a large
[`if-then-else-if`][`if`] statement, using function pointers significantly simplifies the code.
```rust
let x = some_calculation();
// These are the functions to call depending on the value of 'x'
fn method1(x) { ... }
fn method2(x) { ... }
fn method3(x) { ... }
// Traditional - using decision variable
let func = sign(x);
// Dispatch with if-statement
if func == -1 {
method1(42);
} else if func == 0 {
method2(42);
} else if func == 1 {
method3(42);
}
// Using pure function pointer
let func = if x < 0 {
method1
} else if x == 0 {
method2
} else if x > 0 {
method3
};
// Dynamic dispatch
func.call(42);
// Using functions map
let map = [ method1, method2, method3 ];
let func = sign(x) + 1;
// Dynamic dispatch
map[func].call(42);
```
Bind the `this` Pointer
-----------------------
When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch
to a function call while binding the object in the method call to the `this` pointer of the function.
To achieve this, pass the function pointer as the _first_ argument to `call`:
```rust
fn add(x) { // define function which uses 'this'
this += x;
}
let func = add; // function pointer to 'add'
func.call(1); // error: 'this' pointer is not bound
let x = 41;
func.call(x, 1); // error: function 'add (i64, i64)' not found
call(func, x, 1); // error: function 'add (i64, i64)' not found
x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func'
x == 42;
```
Beware that this only works for [_method-call_](fn-method.md) style.
Normal function-call style cannot bind the `this` pointer (for syntactic reasons).
Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].
Call a Function Pointer within a Rust Function (as a Callback)
--------------------------------------------------------------
It is completely normal to register a Rust function with an [`Engine`] that takes parameters
whose types are function pointers. The Rust type in question is `rhai::FnPtr`.
A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function
to call in script. Therefore, the script's _execution context_ (i.e. [`NativeCallContext`])
is needed in order to call a function pointer.
```rust
use rhai::{Engine, FnPtr, NativeCallContext};
let mut engine = Engine::new();
// A function expecting a callback in form of a function pointer.
fn super_call(context: NativeCallContext, callback: FnPtr, value: i64)
-> Result<String, Box<EvalAltResult>>
{
// Use 'FnPtr::call_within_context' to call the function pointer using the call context.
// 'FnPtr::call_within_context' automatically casts to the required result type.
callback.call_within_context(&context, (value,))
// ^^^^^^^^ arguments passed in tuple
}
engine.register_fn("super_call", super_call);
```
Call a Function Pointer Directly
--------------------------------
The `FnPtr::call` method allows the function pointer to be called directly on any [`Engine`] and
[`AST`], making it possible to reuse the `FnPtr` data type in may different calls and scripting
environments.
```rust
use rhai::{Engine, FnPtr};
let engine = Engine::new();
// Compile script to AST
let ast = engine.compile(
r#"
let test = "hello";
|x| test + x // this creates a closure
"#)?;
// Save the closure together with captured variables
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
// 'f' captures: the Engine, the AST, and the closure
let f = move |x: i64| -> Result<String, _> {
fn_ptr.call(&engine, &ast, (x,))
};
// 'f' can be called like a normal function
let result = f(42)?;
result == "hello42";
```
Bind to a native Rust Function
------------------------------
It is also possible to create a function pointer that binds to a native Rust function or a Rust closure.
The signature of the native Rust function takes the following form.
> ```rust
> Fn(context: NativeCallContext, args: &mut [&mut Dynamic])
> -> Result<Dynamic, Box<EvalAltResult>> + 'static
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------------: | ----------------------------------------------- |
| `context` | [`NativeCallContext`] | mutable reference to the current _call context_ |
| `args` | `&mut [&mut Dynamic]` | mutable reference to list of arguments |
When such a function pointer is used in script, the native Rust function will be called
with the arguments provided.
The Rust function should check whether the appropriate number of arguments have been passed.

View File

@ -0,0 +1,84 @@
For Loop
========
{{#include ../links.md}}
Iterating through a numeric [range] or an [array], or any type with a registered [type iterator],
is provided by the `for` ... `in` loop.
There are two alternative syntaxes, one including a counter variable:
> `for` _variable_ `in` _expression_ `{` ... `}`
>
> `for (` _variable_ `,` _counter_ `)` `in` _expression_ `{` ... `}`
~~~admonish tip.small "Tip: Disable `for` loops"
`for` loops can be disabled via [`Engine::set_allow_looping`][options].
~~~
Break or Continue
-----------------
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements.
`break` can be used to break out of the loop unconditionally.
For Expression
--------------
Unlike Rust, `for` statements can also be used as _expressions_.
The `break` statement takes an optional expression that provides the return value.
The default return value of a `for` expression is [`()`].
~~~admonish tip.small "Tip: Disable all loop expressions"
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
~~~
```js
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
// 'for' can be used just like an expression
let index = for (item, count) in a {
// if the 'for' loop breaks here, return a specific value
switch item.type_of() {
"i64" if item.is_even => break count,
"f64" if item.to_int().is_even => break count,
}
// ... if the 'for' loop exits here, the return value is ()
};
if index == () {
print("Magic number not found!");
} else {
print(`Magic number found at index ${index}!`);
}
```
Counter Variable
----------------
The counter variable, if specified, starts from zero, incrementing upwards.
```js , no_run
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
// Loop through the array
for (item, count) in a {
if x.type_of() == "string" {
continue; // skip to the next iteration
}
// 'item' contains a copy of each element during each iteration
// 'count' increments (starting from zero) for each iteration
print(`Item #${count + 1} = ${item}`);
if x == 42 { break; } // break out of for loop
}
```

View File

@ -0,0 +1,222 @@
Functions
=========
{{#include ../links.md}}
Rhai supports defining functions in script via the `fn` keyword, with a syntax that is very similar to Rust without types.
Valid function names are the same as valid [variable] names.
```rust
fn add(x, y) {
x + y
}
fn sub(x, y,) { // trailing comma in parameters list is OK
x - y
}
add(2, 3) == 5;
sub(2, 3,) == -1; // trailing comma in arguments list is OK
```
```admonish tip.small "Tip: Disable functions"
Defining functions can be disabled via the [`no_function`] feature.
```
~~~admonish tip.small "Tip: `is_def_fn`"
Use `is_def_fn` (not available under [`no_function`]) to detect if a Rhai function is defined
(and therefore callable) based on its name and the number of parameters (_arity_).
```rust
fn foo(x) { x + 1 }
is_def_fn("foo", 1) == true;
is_def_fn("foo", 0) == false;
is_def_fn("foo", 2) == false;
is_def_fn("bar", 1) == false;
```
~~~
Implicit Return
---------------
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is
_always_ the block's return value regardless of whether it is terminated with a semicolon `;`.
This is different from Rust.
```rust
fn add(x, y) { // implicit return:
x + y; // value of the last statement (no need for ending semicolon)
// is used as the return value
}
fn add2(x) {
return x + 2; // explicit return
}
add(2, 3) == 5;
add2(42) == 44;
```
Global Definitions Only
-----------------------
Functions can only be defined at the global level, never inside a block or another function.
Again, this is different from Rust.
```rust
// Global level is OK
fn add(x, y) {
x + y
}
// The following will not compile
fn do_addition(x) {
fn add_y(n) { // <- syntax error: cannot define inside another function
n + y
}
add_y(x)
}
```
No Access to External Scope
---------------------------
Functions are not _closures_. They do not capture the calling environment and can only access their
own parameters.
They cannot access [variables] external to the function itself.
```rust
let x = 42;
fn foo() {
x // <- error: variable 'x' not found
}
```
But Can Call Other Functions and Access Modules
-----------------------------------------------
All functions in the same [`AST`] can call each other.
```rust
fn foo(x) { // function defined in the global namespace
x + 1
}
fn bar(x) {
foo(x) // ok! function 'foo' can be called
}
```
In addition, [modules] [imported][`import`] at global level can be accessed.
```js
import "hello" as hey;
import "world" as woo;
{
import "x" as xyz; // <- this module is not at global level
} // <- it goes away here
fn foo(x) {
hey::process(x); // ok! imported module 'hey' can be accessed
print(woo::value); // ok! imported module 'woo' can be accessed
xyz::do_work(); // <- error: module 'xyz' not found
}
```
Automatic Global Module
-----------------------
When a [constant] is declared at global scope, it is added to a special [module] called [`global`].
Functions can access those [constants] via the special [`global`] [module].
Naturally, the automatic [`global`] [module] is not available under [`no_function`] nor [`no_module`].
```rust
const CONSTANT = 42; // this constant is automatically added to 'global'
let hello = 1; // variables are not added to 'global'
{
const INNER = 0; // this constant is not at global level
} // <- it goes away here
fn foo(x) {
x * global::hello // <- error: variable 'hello' not found in 'global'
x * global::CONSTANT // ok! 'CONSTANT' exists in 'global'
x * global::INNER // <- error: constant 'INNER' not found in 'global'
}
```
Use Before Definition Allowed
-----------------------------
Unlike C/C++, functions in Rhai can be defined _anywhere_ at global level.
A function does not need to be defined prior to being used in a script; a statement in the script
can freely call a function defined afterwards.
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
```rust
let x = foo(41); // <- I can do this!
fn foo(x) { // <- define 'foo' after use
x + 1
}
```
Arguments are Passed by Value
-----------------------------
Functions defined in script always take [`Dynamic`] parameters (i.e. they can be of any types).
Therefore, functions with the same name and same _number_ of parameters are equivalent.
All arguments are passed by _value_, so all Rhai script-defined functions are _pure_
(i.e. they never modify their arguments).
Any update to an argument will **not** be reflected back to the caller.
```rust
fn change(s) { // 's' is passed by value
s = 42; // only a COPY of 's' is changed
}
let x = 500;
change(x);
x == 500; // 'x' is NOT changed!
```
```admonish warning.small "Rhai functions are pure"
The only possibility for a Rhai script-defined function to modify an external variable is
via the [`this`](fn-method.md) pointer.
```

View File

@ -0,0 +1,42 @@
Automatic Global Module
=======================
{{#include ../links.md}}
When a [constant] is declared at global scope, it is added to a special [module] called `global`.
[Functions] can access those [constants] via the special `global` [module].
Naturally, the automatic `global` [module] is not available under [`no_function`] nor [`no_module`].
```rust
const CONSTANT = 42; // this constant is automatically added to 'global'
{
const INNER = 0; // this constant is not at global level
} // <- it goes away here
fn foo(x) {
x *= global::CONSTANT; // ok! 'CONSTANT' exists in 'global'
x * global::INNER // <- error: constant 'INNER' not found in 'global'
}
```
Override `global`
-----------------
It is possible to _override_ the automatic global [module] by [importing][`import`] another [module]
under the name `global`.
```rust
import "foo" as global; // import a module as 'global'
const CONSTANT = 42; // this constant is NOT added to 'global'
fn foo(x) {
global::CONSTANT // <- error: constant 'CONSTANT' not found in 'global'
}
```

View File

@ -0,0 +1,92 @@
If Statement
============
{{#include ../links.md}}
`if` statements follow C syntax.
```rust
if foo(x) {
print("It's true!");
} else if bar == baz {
print("It's true again!");
} else if baz.is_foo() {
print("Yet again true.");
} else if foo(bar - baz) {
print("True again... this is getting boring.");
} else {
print("It's finally false!");
}
```
~~~admonish warning.small "Braces are mandatory"
Unlike C, the condition expression does _not_ need to be enclosed in parentheses `(`...`)`, but all
branches of the `if` statement must be enclosed within braces `{`...`}`, even when there is only
one statement inside the branch.
Like Rust, there is no ambiguity regarding which `if` clause a branch belongs to.
```rust
// Rhai is not C!
if (decision) print(42);
// ^ syntax error, expecting '{'
```
~~~
If Expression
-------------
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional
operators in other C-like languages.
~~~admonish tip.small "Tip: Disable `if` expressions"
`if` expressions can be disabled via [`Engine::set_allow_if_expression`][options].
~~~
```rust
// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2;
let x = 1 + if decision { 42 } else { 123 } / 2;
x == 22;
let x = if decision { 42 }; // no else branch defaults to '()'
x == ();
```
~~~admonish danger.small "Statement before expression"
Beware that, like Rust, `if` is parsed primarily as a statement where it makes sense.
This is to avoid surprises.
```rust
fn index_of(x) {
// 'if' is parsed primarily as a statement
if this.contains(x) {
return this.find_index(x)
}
-1
}
```
The above will not be parsed as a single expression:
```rust
fn index_of(x) {
if this.contains(x) { return this.find_index(x) } - 1
// error due to '() - 1' ^
}
```
To force parsing as an expression, parentheses are required:
```rust
fn calc_index(b, offset) {
(if b { 1 } else { 0 }) + offset
// ^---------------------^ parentheses
}
```
~~~

View File

@ -0,0 +1,138 @@
In Operator
===========
{{#include ../links.md}}
```admonish question.side.wide "Trivia"
The `in` operator is simply syntactic sugar for a call to the `contains` function.
Similarly, `!in` is a call to `!contains`.
```
The `in` operator is used to check for _containment_ &ndash; i.e. whether a particular collection
data type _contains_ a particular item.
Similarly, `!in` is used to check for non-existence &ndash; i.e. it is `true` if a particular
collection data type does _not_ contain a particular item.
```rust
42 in array;
array.contains(42); // <- the above is equivalent to this
123 !in array;
!array.contains(123); // <- the above is equivalent to this
```
Built-in Support for Standard Data Types
----------------------------------------
| Data type | Check for |
| :-------------: | :---------------------------------: |
| Numeric [range] | integer number |
| [Array] | contained item |
| [Object map] | property name |
| [String] | [sub-string][string] or [character] |
Examples
--------
```rust
let array = [1, "abc", 42, ()];
42 in array == true; // check array for item
let map = #{
foo: 42,
bar: true,
baz: "hello"
};
"foo" in map == true; // check object map for property name
'w' in "hello, world!" == true; // check string for character
'w' !in "hello, world!" == false;
"wor" in "hello, world" == true; // check string for sub-string
42 in -100..100 == true; // check range for number
```
Array Items Comparison
----------------------
The default implementation of the `in` operator for [arrays] uses the `==` operator (if defined)
to compare items.
~~~admonish warning.small "`==` defaults to `false`"
For a [custom type], `==` defaults to `false` when comparing it with a value of of the same type.
See the section on [_Logic Operators_](logic.md) for more details.
~~~
```rust
let ts = new_ts(); // assume 'new_ts' returns a custom type
let array = [1, 2, 3, ts, 42, 999];
// ^^ custom type
42 in array == true; // 42 cannot be compared with 'ts'
// so it defaults to 'false'
// because == operator is not defined
```
Custom Implementation of `contains`
-----------------------------------
The `in` and `!in` operators map directly to a call to a function `contains` with the two operands switched.
```rust
// This expression...
item in container
// maps to this...
contains(container, item)
// or...
container.contains(item)
```
Support for the `in` and `!in` operators can be easily extended to other types by registering a
custom binary function named `contains` with the correct parameter types.
Since `!in` maps to `!(... in ...)`, `contains` is enough to support both operators.
```rust
let mut engine = Engine::new();
engine.register_type::<TestStruct>()
.register_fn("new_ts", || TestStruct::new())
.register_fn("contains", |ts: &mut TestStruct, item: i64| -> bool {
// Remember the parameters are switched from the 'in' expression
ts.contains(item)
});
// Now the 'in' operator can be used for 'TestStruct' and integer
engine.run(
r#"
let ts = new_ts();
if 42 in ts { // this calls 'ts.contains(42)'
print("I got 42!");
} else if 123 !in ts { // this calls '!ts.contains(123)'
print("I ain't got 123!");
}
let err = "hello" in ts; // <- runtime error: 'contains' not found
// for 'TestStruct' and string
"#)?;
```

View File

@ -0,0 +1,183 @@
Standard Iterable Types
========================
{{#include ../links.md}}
Certain [standard types] are iterable via a [`for`] statement.
Iterate Through Arrays
----------------------
Iterating through an [array] yields cloned _copies_ of each element.
```rust
let a = [1, 3, 5, 7, 9, 42];
// Loop through the array
for x in a {
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
```
Iterate Through Strings
-----------------------
Iterating through a [string] yields individual [characters].
The `chars` method also allow iterating through characters in a [string], optionally accepting the
character position to start from (counting from the end if negative), as well as the number of
characters to iterate (defaults to all).
`char` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
```rust
let s = "hello, world!";
// Iterate through all the characters.
for ch in s {
print(ch);
}
// Iterate starting from the 3rd character and stopping at the 7th.
for ch in s.chars(2, 5) {
if ch > 'z' { continue; } // skip to the next iteration
print(ch);
if x == '@' { break; } // break out of for loop
}
// Iterate starting from the 3rd character and stopping at the end.
for ch in s.chars(2..s.len) {
if ch > 'z' { continue; } // skip to the next iteration
print(ch);
if x == '@' { break; } // break out of for loop
}
```
Iterate Through Numeric Ranges
------------------------------
[Ranges] are created via the `..` (exclusive) and `..=` (inclusive) operators.
The `range` function similarly creates exclusive [ranges], plus allowing optional step values.
```rust
// Iterate starting from 0 and stopping at 49
// The step is assumed to be 1 when omitted for integers
for x in 0..50 {
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
// The 'range' function is just the same
for x in range(0, 50) {
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
// The 'range' function also takes a step
for x in range(0, 50, 3) { // step by 3
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
// The 'range' function can also step backwards
for x in range(50..0, -3) { // step down by -3
if x < 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
// It works also for floating-point numbers
for x in range(5.0, 0.0, -2.0) { // step down by -2.0
if x < 10 { continue; } // skip to the next iteration
print(x);
if x == 4.2 { break; } // break out of for loop
}
```
Iterate Through Bit-Fields
--------------------------
The `bits` function allows iterating through an integer as a [bit-field].
`bits` optionally accepts the bit number to start from (counting from the most-significant-bit if
negative), as well as the number of bits to iterate (defaults all).
`bits` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
```js , no_run
let x = 0b_1001110010_1101100010_1100010100;
let num_on = 0;
// Iterate through all the bits
for bit in x.bits() {
if bit { num_on += 1; }
}
print(`There are ${num_on} bits turned on!`);
const START = 3;
// Iterate through all the bits from 3 through 12
for (bit, index) in x.bits(START, 10) {
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
if index >= 7 { break; } // break out of for loop
}
// Iterate through all the bits from 3 through 12
for (bit, index) in x.bits(3..=12) {
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
if index >= 7 { break; } // break out of for loop
}
```
Iterate Through Object Maps
---------------------------
Two methods, `keys` and `values`, return [arrays] containing cloned _copies_
of all property names and values of an [object map], respectively.
These [arrays] can be iterated.
```rust
let map = #{a:1, b:3, c:5, d:7, e:9};
// Property names are returned in unsorted, random order
for x in map.keys() {
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
}
// Property values are returned in unsorted, random order
for val in map.values() {
print(val);
}
```

View File

@ -0,0 +1,59 @@
Make a Custom Type Iterable
===========================
{{#include ../links.md}}
```admonish info.side "Built-in type iterators"
Type iterators are already defined for built-in [standard types] such as [strings], [ranges],
[bit-fields], [arrays] and [object maps].
That's why they can be used with the [`for`] loop.
```
If a [custom type] is iterable, the [`for`] loop can be used to iterate through
its items in sequence, as long as it has a _type iterator_ registered.
`Engine::register_iterator<T>` allows registration of a type iterator for any type
that implements `IntoIterator`.
With a type iterator registered, the [custom type] can be iterated through.
```rust
// Custom type
#[derive(Debug, Clone)]
struct TestStruct { fields: Vec<i64> }
// Implement 'IntoIterator' trait
impl IntoIterator<Item = i64> for TestStruct {
type Item = i64;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.fields.into_iter()
}
}
let mut engine = Engine::new();
// Register API and type iterator for 'TestStruct'
engine.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", || TestStruct { fields: vec![1, 2, 3, 42] })
.register_iterator::<TestStruct>();
// 'TestStruct' is now iterable
engine.run(
"
for value in new_ts() {
...
}
")?;
```
```admonish tip.small "Tip: Fallible type iterators"
`Engine::register_iterator_result` allows registration of a _fallible_ type iterator &ndash;
i.e. an iterator that returns `Result<T, Box<EvalAltResult>>`.
On in very rare situations will this be necessary though.
```

View File

@ -0,0 +1,108 @@
Parse an Object Map from JSON
=============================
{{#include ../links.md}}
Do It Without `serde`
---------------------
```admonish info.side.wide "Object map vs. JSON"
A valid JSON object hash does not start with a hash character `#` while a Rhai [object map] does.
That's the only difference!
```
The syntax for an [object map] is extremely similar to the JSON representation of a object hash,
with the exception of `null` values which can technically be mapped to [`()`].
Use the `Engine::parse_json` method to parse a piece of JSON into an [object map].
```rust
// JSON string - notice that JSON property names are always quoted
// notice also that comments are acceptable within the JSON string
let json = r#"{
"a": 1, // <- this is an integer number
"b": true,
"c": 123.0, // <- this is a floating-point number
"$d e f!": "hello", // <- any text can be a property name
"^^^!!!": [1,42,"999"], // <- value can be array or another hash
"z": null // <- JSON 'null' value
}"#;
// Parse the JSON expression as an object map
// Set the second boolean parameter to true in order to map 'null' to '()'
let map = engine.parse_json(json, true)?;
map.len() == 6; // 'map' contains all properties in the JSON string
// Put the object map into a 'Scope'
let mut scope = Scope::new();
scope.push("map", map);
let result = engine.eval_with_scope::<i64>(&mut scope, r#"map["^^^!!!"].len()"#)?;
result == 3; // the object map is successfully used in the script
```
```admonish warning.small "Warning: Must be object hash"
The JSON text must represent a single object hash &ndash; i.e. must be wrapped within braces
`{`...`}`.
It cannot be a primitive type (e.g. number, string etc.).
Otherwise it cannot be converted into an [object map] and a type error is returned.
```
```admonish note.small "Representation of numbers"
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`)
(except under [`no_float`]).
Most common generators of JSON data distinguish between integer and floating-point values by always
serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is
assumed to be an integer).
This style can be used successfully with Rhai [object maps].
```
Sub-objects are handled transparently by `Engine::parse_json`.
It is _not_ necessary to replace `{` with `#{` in order to fake a Rhai [object map] literal.
```rust
// JSON with sub-object 'b'.
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
// 'parse_json' handles this just fine.
let map = engine.parse_json(json, false)?;
// 'map' contains two properties: 'a' and 'b'
map.len() == 2;
```
```admonish question "TL;DR &ndash; How is it done?"
Internally, `Engine::parse_json` _cheats_ by treating the JSON text as a Rhai script.
That is why it even supports [comments] and arithmetic expressions in the JSON text,
although it is not a good idea to rely on non-standard JSON formats.
A [token remap filter] is used to convert `{` into `#{` and `null` to [`()`].
```
Use `serde`
-----------
```admonish info.side "See also"
See _[Serialization/ Deserialization of `Dynamic` with `serde`][`serde`]_ for more details.
```
Remember, `Engine::parse_json` is nothing more than a _cheap_ alternative to true JSON parsing.
If strict correctness is needed, or for more configuration possibilities, turn on the
[`serde`][features] feature to pull in [`serde`](https://crates.io/crates/serde) which enables
serialization and deserialization to/from multiple formats, including JSON.
Beware, though... the [`serde`](https://crates.io/crates/serde) crate is quite heavy.

View File

@ -0,0 +1,30 @@
Keywords
========
{{#include ../links.md}}
The following are reserved keywords in Rhai.
| Active keywords | Reserved keywords | Usage | Inactive under feature |
| -------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------- | :----------------------------: |
| `true`, `false` | | [constants] | |
| [`let`][variable], [`const`][constant] | `var`, `static` | [variables] | |
| `is_shared` | | _shared_ values | [`no_closure`] |
| | `is` | type checking | |
| [`if`], [`else`][`if`] | `goto` | control flow | |
| [`switch`] | `match`, `case` | switching and matching | |
| [`do`], [`while`], [`loop`], `until`, [`for`], [`in`], `continue`, `break` | | looping | |
| [`fn`][function], [`private`], `is_def_fn`, `this` | `public`, `protected`, `new` | [functions] | [`no_function`] |
| [`return`] | | return values | |
| [`throw`], [`try`], [`catch`] | | [throw/catch][`catch`] [exceptions] | |
| [`import`], [`export`], `as` | `use`, `with`, `module`, `package`, `super` | [modules] | [`no_module`] |
| [`global`] | | automatic global [module] | [`no_function`], [`no_module`] |
| [`Fn`][function pointer], `call`, [`curry`][currying] | | [function pointers] | |
| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async | |
| [`type_of`], [`print`], [`debug`], [`eval`], `is_def_var` | | special functions | |
| | `default`, `void`, `null`, `nil` | special values | |
```admonish warning.small
Keywords cannot become the name of a [function] or [variable], even when they are
[disabled][disable keywords and operators].
```

View File

@ -0,0 +1,234 @@
Logic Operators
===============
{{#include ../links.md}}
Comparison Operators
--------------------
| Operator | Description<br/>(`x` _operator_ `y`) | `x`, `y` same type or are numeric | `x`, `y` different types |
| :------: | ------------------------------------ | :-------------------------------: | :----------------------: |
| `==` | `x` is equals to `y` | error if not defined | `false` if not defined |
| `!=` | `x` is not equals to `y` | error if not defined | `true` if not defined |
| `>` | `x` is greater than `y` | error if not defined | `false` if not defined |
| `>=` | `x` is greater than or equals to `y` | error if not defined | `false` if not defined |
| `<` | `x` is less than `y` | error if not defined | `false` if not defined |
| `<=` | `x` is less than or equals to `y` | error if not defined | `false` if not defined |
Comparison operators between most values of the same type are built in for all [standard types].
Others are defined in the [`LogicPackage`][built-in packages] but excluded when using a [raw `Engine`].
### Floating-point numbers interoperate with integers
Comparing a floating-point number (`FLOAT`) with an integer is also supported.
```rust
42 == 42.0; // true
42.0 == 42; // true
42.0 > 42; // false
42 >= 42.0; // true
42.0 < 42; // false
```
### Decimal numbers interoperate with integers
Comparing a [`Decimal`][rust_decimal] number with an integer is also supported.
```rust
let d = parse_decimal("42");
42 == d; // true
d == 42; // true
d > 42; // false
42 >= d; // true
d < 42; // false
```
### Strings interoperate with characters
Comparing a [string] with a [character] is also supported, with the character first turned into a
[string] before performing the comparison.
```rust
'x' == "x"; // true
"" < 'a'; // true
'x' > "hello"; // false
```
### Comparing different types defaults to `false`
Comparing two values of _different_ data types defaults to `false` unless the appropriate operator
functions have been registered.
The exception is `!=` (not equals) which defaults to `true`. This is in line with intuition.
```rust
42 > "42"; // false: i64 cannot be compared with string
42 <= "42"; // false: i64 cannot be compared with string
let ts = new_ts(); // custom type
ts == 42; // false: different types cannot be compared
ts != 42; // true: different types cannot be compared
ts == ts; // error: '==' not defined for the custom type
```
### Safety valve: Comparing different _numeric_ types has no default
Beware that the above default does _NOT_ apply to numeric values of different types
(e.g. comparison between `i64` and `u16`, `i32` and `f64`) &ndash; when multiple numeric types are
used it is too easy to mess up and for subtle errors to creep in.
```rust
// Assume variable 'x' = 42_u16, 'y' = 42_u16 (both types of u16)
x == y; // true: '==' operator for u16 is built-in
x == "hello"; // false: different non-numeric operand types default to false
x == 42; // error: ==(u16, i64) not defined, no default for numeric types
42 == y; // error: ==(i64, u16) not defined, no default for numeric types
```
### Caution: Beware operators for custom types
```admonish tip.side.wide "Tip: Always the full set"
It is strongly recommended that, when defining operators for [custom types], always define the
**full set** of six operators together, or at least the `==` and `!=` pair.
```
Operators are completely separate from each other. For example:
* `!=` does not equal `!(==)`
* `>` does not equal `!(<=)`
* `<=` does not equal `<` plus `==`
* `<=` does not imply `<`
Therefore, if a [custom type] misses an [operator] definition, it simply raises an error
or returns the default.
This behavior can be counter-intuitive.
```rust
let ts = new_ts(); // custom type with '<=' and '==' defined
ts <= ts; // true: '<=' defined
ts < ts; // error: '<' not defined, even though '<=' is
ts == ts; // true: '==' defined
ts != ts; // error: '!=' not defined, even though '==' is
```
Boolean Operators
-----------------
```admonish note.side
All boolean operators are [built in][built-in operators] for the `bool` data type.
```
| Operator | Description | Arity | Short-circuits? |
| :---------------: | :---------: | :----: | :-------------: |
| `!` _(prefix)_ | _NOT_ | unary | no |
| `&&` | _AND_ | binary | **yes** |
| `&` | _AND_ | binary | no |
| <code>\|\|</code> | _OR_ | binary | **yes** |
| <code>\|</code> | _OR_ | binary | no |
Double boolean operators `&&` and `||` _short-circuit_ &ndash; meaning that the second operand will not be evaluated
if the first one already proves the condition wrong.
Single boolean operators `&` and `|` always evaluate both operands.
```rust
a() || b(); // b() is not evaluated if a() is true
a() && b(); // b() is not evaluated if a() is false
a() | b(); // both a() and b() are evaluated
a() & b(); // both a() and b() are evaluated
```
Null-Coalescing Operator
------------------------
| Operator | Description | Arity | Short-circuits? |
| :------: | :-----------: | :----: | :-------------: |
| `??` | Null-coalesce | binary | yes |
The null-coalescing operator (`??`) returns the first operand if it is not [`()`], or the second
operand if the first operand is [`()`].
It _short-circuits_ &ndash; meaning that the second operand will not be evaluated if the first
operand is not [`()`].
```rust
a ?? b // returns 'a' if it is not (), otherwise 'b'
a() ?? b(); // b() is only evaluated if a() is ()
```
~~~admonish tip.small "Tip: Default value for object map property"
Use the null-coalescing operator to implement default values for non-existent [object map] properties.
```rust
let map = #{ foo: 42 };
// Regular property access
let x = map.foo; // x == 42
// Non-existent property
let x = map.bar; // x == ()
// Default value for property
let x = map.bar ?? 42; // x == 42
```
~~~
### Short-circuit loops and early returns
The following statements are allowed to follow the null-coalescing operator:
* `break`
* `continue`
* [`return`]
* [`throw`]
This means that you can use the null-coalescing operator to short-circuit loops and/or
early-return from functions when the value tested is [`()`].
```rust
let total = 0;
for value in list {
// Whenever 'calculate' returns '()', the loop stops
total += calculate(value) ?? break;
}
```

View File

@ -0,0 +1,71 @@
Infinite Loop
=============
{{#include ../links.md}}
Infinite loops follow Rust syntax.
Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements;
`break` can be used to break out of the loop unconditionally.
~~~admonish tip.small "Tip: Disable `loop`"
`loop` can be disabled via [`Engine::set_allow_looping`][options].
~~~
```rust
let x = 10;
loop {
x -= 1;
if x > 5 { continue; } // skip to the next iteration
print(x);
if x == 0 { break; } // break out of loop
}
```
~~~admonish danger.small "Remember the `break` statement"
A `loop` statement without a `break` statement inside its loop block is infinite.
There is no way for the loop to stop iterating.
~~~
Loop Expression
---------------
Like Rust, `loop` statements can also be used as _expressions_.
The `break` statement takes an optional expression that provides the return value.
The default return value of a `loop` expression is [`()`].
~~~admonish tip.small "Tip: Disable all loop expressions"
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
~~~
```js
let x = 0;
// 'loop' can be used just like an expression
let result = loop {
if is_magic_number(x) {
// if the loop breaks here, return a specific value
break get_magic_result(x);
}
x += 1;
// ... if the loop exits here, the return value is ()
};
if result == () {
print("Magic number not found!");
} else {
print(`Magic result = ${result}!`);
}
```

View File

@ -0,0 +1,116 @@
Export Variables, Functions and Sub-Modules From a Script
=========================================================
{{#include ../../links.md}}
```admonish info.side "See also"
See [_Create a Module from AST_]({{rootUrl}}/rust/modules/ast.md) for more details.
```
The easiest way to expose a collection of [functions] as a self-contained [module] is to do it via a Rhai script itself.
The script text is evaluated.
[Variables] are then selectively exposed via the `export` statement.
[Functions] defined by the script are automatically exported, unless marked as `private`.
Modules loaded within this [module] at the global level become _sub-modules_ and are also automatically exported.
Export Global Variables
-----------------------
The `export` statement, which can only be at global level, exposes a selected [variable] as member of a [module].
[Variables] not exported are _private_ and hidden. They are merely used to initialize the [module],
but cannot be accessed from outside.
Everything exported from a [module] is **[constant]** (i.e. read-only).
```js
// This is a module script.
let hidden = 123; // variable not exported - default hidden
let x = 42; // this will be exported below
export x; // the variable 'x' is exported under its own name
export const x = 42; // convenient short-hand to declare a constant and export it
// under its own name
export let x = 123; // variables can be exported as well, though it'll still be constant
export x as answer; // the variable 'x' is exported under the alias 'answer'
// another script can load this module and access 'x' as 'module::answer'
{
let inner = 0; // local variable - it disappears when the statements block ends,
// therefore it is not 'global' and cannot be exported
export inner; // <- syntax error: cannot export a local variable
}
```
```admonish tip.small "Tip: Multiple exports"
[Variables] can be exported under multiple names.
For example, the following exports three [variables]:
* `x` as `x` and `hello`
* `y` as `foo` and `bar`
* `z` as `z`
~~~js
export x;
export x as hello;
export y as foo;
export x as world;
export y as bar;
export z;
~~~
```
Export Functions
----------------
```admonish info.side "Private functions"
`private` [functions] are commonly called within the [module] only.
They cannot be accessed otherwise.
```
All [functions] are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
[Functions] declared `private` are hidden to the outside.
```rust
// This is a module script.
fn inc(x) { x + 1 } // script-defined function - default public
private fn foo() {} // private function - hidden
```
Sub-Modules
-----------
All loaded [modules] are automatically exported as sub-modules.
~~~admonish tip.small "Tip: Skip exporting a module"
To prevent a [module] from being exported, load it inside a block statement so that it goes away at the
end of the block.
```js
// This is a module script.
import "hello" as foo; // <- exported
{
import "world" as bar; // <- not exported
}
```
~~~

View File

@ -0,0 +1,132 @@
Import a Module
===============
{{#include ../../links.md}}
```admonish info.side "See also"
See [_Module Resolvers_][module resolver] for more details.
```
Before a [module] can be used (via an `import` statement) in a script, there must be a
[module resolver] registered into the [`Engine`], the default being the `FileModuleResolver`.
`import` Statement
------------------
```admonish tip.side.wide "Tip"
A [module] that is only `import`-ed but not given any name is simply run.
This is a very simple way to run another script file from within a script.
```
A [module] can be _imported_ via the `import` statement, and be given a name.
Its members can be accessed via `::` similar to C++.
```js
import "crypto_banner"; // run the script file 'crypto_banner.rhai' without creating an imported module
import "crypto" as lock; // run the script file 'crypto.rhai' and import it as a module named 'lock'
const SECRET_NUMBER = 42;
let mod_file = `crypto_${SECRET_NUMBER}`;
import mod_file as my_mod; // load the script file "crypto_42.rhai" and import it as a module named 'my_mod'
// notice that module path names can be dynamically constructed!
// any expression that evaluates to a string is acceptable after the 'import' keyword
lock::encrypt(secret); // use functions defined under the module via '::'
lock::hash::sha256(key); // sub-modules are also supported
print(lock::status); // module variables are constants
lock::status = "off"; // <- runtime error: cannot modify a constant
```
```admonish info "Imports are _scoped_"
[Modules] imported via `import` statements are only accessible inside the relevant block scope.
~~~js
import "hacker" as h; // import module - visible globally
if secured { // <- new block scope
let mod = "crypt";
import mod + "o" as c; // import module (the path needs not be a constant string)
let x = c::encrypt(key); // use a function in the module
h::hack(x); // global module 'h' is visible here
} // <- module 'c' disappears at the end of the block scope
h::hack(something); // this works as 'h' is visible
c::encrypt(something); // <- this causes a run-time error because
// module 'c' is no longer available!
fn foo(something) {
h::hack(something); // <- this also works as 'h' is visible
}
for x in 0..1000 {
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea
c.encrypt(something);
}
~~~
```
~~~admonish note "Place `import` statements at the top"
`import` statements can appear anywhere a normal statement can be, but in the vast majority of cases they are
usually grouped at the top (beginning) of a script for manageability and visibility.
It is not advised to deviate from this common practice unless there is a _Very Good Reason™_.
Especially, do not place an `import` statement within a loop; doing so will repeatedly re-load the
same [module] during every iteration of the loop!
~~~
~~~admonish danger "Recursive imports"
Beware of _import cycles_ &ndash; i.e. recursively loading the same [module]. This is a sure-fire way to
cause a stack overflow in the [`Engine`], unless stopped by setting a limit for [maximum number of modules].
For instance, importing itself always causes an infinite recursion:
```js
┌────────────┐
│ hello.rhai │
└────────────┘
import "hello" as foo; // import itself - infinite recursion!
foo::do_something();
```
[Modules] cross-referencing also cause infinite recursion:
```js
┌────────────┐
│ hello.rhai │
└────────────┘
import "world" as foo;
foo::do_something();
┌────────────┐
│ world.rhai │
└────────────┘
import "hello" as bar;
bar::do_something_else();
```
~~~

View File

@ -0,0 +1,14 @@
Modules
=======
{{#include ../../links.md}}
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
Modules can be disabled via the [`no_module`] feature.
A module has the type `Module` and holds a collection of functions, variables, [type iterators] and sub-modules.
It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions
and variables defined by that script.
Other scripts can then load this module and use the functions and variables exported as if they were
defined inside the same script.

View File

@ -0,0 +1,122 @@
Numeric Functions
=================
{{#include ../links.md}}
Integer Functions
-----------------
The following standard functions are defined.
| Function | Not available under | Package | Description |
| ----------------------------- | :-----------------: | :--------------------------------------: | ---------------------------------------------------------------- |
| `is_odd` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an odd number, otherwise `false` |
| `is_even` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an even number, otherwise `false` |
| `min` | | [`LogicPackage`][built-in packages] | returns the smaller of two numbers |
| `max` | | [`LogicPackage`][built-in packages] | returns the larger of two numbers |
| `to_float` | [`no_float`] | [`BasicMathPackage`][built-in packages] | convert the value into `f64` (`f32` under [`f32_float`]) |
| `to_decimal` | non-[`decimal`] | [`BasicMathPackage`][built-in packages] | convert the value into [`Decimal`][rust_decimal] |
Signed Numeric Functions
------------------------
The following standard functions are defined in the [`ArithmeticPackage`][built-in packages]
(excluded when using a [raw `Engine`]) and operate on `i8`, `i16`, `i32`, `i64`, `f32`, `f64` and
[`Decimal`][rust_decimal] (requires [`decimal`]) only.
| Function | Description |
| ----------------------------- | -------------------------------------------------------------- |
| `abs` | absolute value |
| `sign` | returns (`INT`) 1 if negative, &plus;1 if positive, 0 if zero |
| `is_zero` method and property | returns `true` if the value is zero, otherwise `false` |
Floating-Point Functions
------------------------
The following standard functions are defined in the [`BasicMathPackage`][built-in packages]
(excluded when using a [raw `Engine`]) and operate on `f64` (`f32` under [`f32_float`]) and
[`Decimal`][rust_decimal] (requires [`decimal`]) only.
| Category | Supports `Decimal` | Functions |
| ---------------- | :----------------: | ---------------------------------------------------------------------------------------- |
| Trigonometry | yes | `sin`, `cos`, `tan` |
| Trigonometry | **no** | `sinh`, `cosh`, `tanh` in radians, `hypot(`_x_`,`_y_`)` |
| Arc-trigonometry | **no** | `asin`, `acos`, `atan(`_v_`)`, `atan(`_x_`,`_y_`)`, `asinh`, `acosh`, `atanh` in radians |
| Square root | yes | `sqrt` |
| Exponential | yes | `exp` (base _e_) |
| Logarithmic | yes | `ln` (base _e_) |
| Logarithmic | yes | `log` (base 10) |
| Logarithmic | **no** | `log(`_x_`,`_base_`)` |
| Rounding | yes | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
| Conversion | yes | [`to_int`], [`to_decimal`] (requires [`decimal`]), [`to_float`] (not under [`no_float`]) |
| Conversion | **no** | `to_degrees`, `to_radians` |
| Comparison | yes | `min`, `max` (also inter-operates with integers) |
| Testing | **no** | `is_nan`, `is_finite`, `is_infinite` methods and properties |
Decimal Rounding Functions
--------------------------
The following rounding methods are defined in the [`BasicMathPackage`][built-in packages]
(excluded when using a [raw `Engine`]) and operate on [`Decimal`][rust_decimal] only,
which requires the [`decimal`] feature.
| Rounding type | Behavior | Methods |
| ----------------- | ------------------------------------------- | ------------------------------------------------------------ |
| None | | `floor`, `ceiling`, `int`, `fraction` methods and properties |
| Banker's rounding | round to integer | `round` method and property |
| Banker's rounding | round to specified number of decimal points | `round(`_decimal points_`)` |
| Round up | away from zero | `round_up(`_decimal points_`)` |
| Round down | towards zero | `round_down(`_decimal points_`)` |
| Round half-up | mid-point away from zero | `round_half_up(`_decimal points_`)` |
| Round half-down | mid-point towards zero | `round_half_down(`_decimal points_`)` |
Parsing Functions
-----------------
The following standard functions are defined in the [`BasicMathPackage`][built-in packages]
(excluded when using a [raw `Engine`]) to parse numbers.
| Function | No available under | Description |
| ----------------- | :------------------------------: | --------------------------------------------------------------------------------------------- |
| [`parse_int`] | | converts a [string] to `INT` with an optional radix |
| [`parse_float`] | [`no_float`] and non-[`decimal`] | converts a [string] to `FLOAT` ([`Decimal`][rust_decimal] under [`no_float`] and [`decimal`]) |
| [`parse_decimal`] | non-[`decimal`] | converts a [string] to [`Decimal`][rust_decimal] |
Formatting Functions
--------------------
The following standard functions are defined in the [`BasicStringPackage`][built-in packages]
(excluded when using a [raw `Engine`]) to convert integer numbers into a [string] of hex, octal
or binary representations.
| Function | Description |
| ------------- | ------------------------------------ |
| [`to_binary`] | converts an integer number to binary |
| [`to_octal`] | converts an integer number to octal |
| [`to_hex`] | converts an integer number to hex |
These formatting functions are defined for all available integer numbers &ndash; i.e. `INT`, `u8`,
`i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128` and `i128` unless disabled by feature flags.
Floating-point Constants
------------------------
The following functions return standard mathematical constants.
| Function | Description |
| -------- | ------------------------- |
| `PI` | returns the value of &pi; |
| `E` | returns the value of _e_ |
Numerical Functions for Scientific Computing
--------------------------------------------
Check out the [`rhai-sci`] crate for more numerical functions.

View File

@ -0,0 +1,135 @@
Numeric Operators
=================
{{#include ../links.md}}
Numeric operators generally follow C styles.
Unary Operators
---------------
| Operator | Description |
| :------: | ----------- |
| `+` | positive |
| `-` | negative |
```rust
let number = +42;
number = -5;
number = -5 - +5;
-(-42) == +42; // two '-' equals '+'
// beware: '++' and '--' are reserved symbols
```
Binary Operators
----------------
| Operator | Description | Result type | `INT` | `FLOAT` | [`Decimal`][rust_decimal] |
| :-------------------------------: | ---------------------------------------------------------------- | :---------: | :---: | :--------------------: | :-----------------------: |
| `+`, `+=` | plus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
| `-`, `-=` | minus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
| `*`, `*=` | multiply | numeric | yes | yes, also with `INT` | yes, also with `INT` |
| `/`, `/=` | divide (integer division if acting on integer types) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
| `%`, `%=` | modulo (remainder) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
| `**`, `**=` | power/exponentiation | numeric | yes | yes, also `FLOAT**INT` | **no** |
| `<<`, `<<=` | left bit-shift (if negative number of bits, shift right instead) | numeric | yes | **no** | **no** |
| `>>`, `>>=` | right bit-shift (if negative number of bits, shift left instead) | numeric | yes | **no** | **no** |
| `&`, `&=` | bit-wise _And_ | numeric | yes | **no** | **no** |
| <code>\|</code>, <code>\|=</code> | bit-wise _Or_ | numeric | yes | **no** | **no** |
| `^`, `^=` | bit-wise _Xor_ | numeric | yes | **no** | **no** |
| `==` | equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `!=` | not equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `>` | greater than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `>=` | greater than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `<` | less than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `<=` | less than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
| `..` | exclusive range | [range] | yes | **no** | **no** |
| `..=` | inclusive range | [range] | yes | **no** | **no** |
Examples
--------
```rust
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
let reminder = 42 % 10; // modulo
let power = 42 ** 2; // power
let left_shifted = 42 << 3; // left shift
let right_shifted = 42 >> 3; // right shift
let bit_op = 42 | 99; // bit masking
```
Floating-Point Interoperates with Integers
------------------------------------------
When one of the operands to a binary arithmetic [operator] is floating-point, it works with `INT` for
the other operand and the result is floating-point.
```rust
let x = 41.0 + 1; // 'FLOAT' + 'INT'
type_of(x) == "f64"; // result is 'FLOAT'
let x = 21 * 2.0; // 'FLOAT' * 'INT'
type_of(x) == "f64";
(x == 42) == true; // 'FLOAT' == 'INT'
(10 < x) == true; // 'INT' < 'FLOAT'
```
Decimal Interoperates with Integers
-----------------------------------
When one of the operands to a binary arithmetic [operator] is [`Decimal`][rust_decimal],
it works with `INT` for the other operand and the result is [`Decimal`][rust_decimal].
```rust
let d = parse_decimal("2");
let x = d + 1; // 'Decimal' + 'INT'
type_of(x) == "decimal"; // result is 'Decimal'
let x = 21 * d; // 'Decimal' * 'INT'
type_of(x) == "decimal";
(x == 42) == true; // 'Decimal' == 'INT'
(10 < x) == true; // 'INT' < 'Decimal'
```
Unary Before Binary
-------------------
In Rhai, unary operators take [precedence] over binary operators. This is especially important to
remember when handling operators such as `**` which in some languages bind tighter than the unary
`-` operator.
```rust
-2 + 2 == 0;
-2 - 2 == -4;
-2 * 2 == -4;
-2 / 2 == -1;
-2 % 2 == 0;
-2 ** 2 = 4; // means: (-2) ** 2
// in some languages this means: -(2 ** 2)
```

View File

@ -0,0 +1,138 @@
Numbers
=======
{{#include ../links.md}}
Integers
--------
```admonish tip.side "Tip: Bit-fields"
Integers can also be conveniently manipulated as [bit-fields].
```
Integer numbers follow C-style format with support for decimal, binary (`0b`), octal (`0o`) and hex (`0x`) notations.
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
Floating-Point Numbers
----------------------
```admonish tip.side "Tip: Notations"
Both decimal and scientific notations can be used to represent floating-point numbers.
```
Floating-point numbers are also supported if not disabled with [`no_float`].
The default system floating-point type is `f64` (also aliased to `FLOAT`).
It can be turned into `f32` via the [`f32_float`] feature.
`Decimal` Numbers
-----------------
When rounding errors cannot be accepted, such as in financial calculations, the [`decimal`] feature
turns on support for the [`Decimal`][rust_decimal] type, which is a fixed-precision floating-point
number with no rounding errors.
Number Literals
---------------
`_` separators can be added freely and are ignored within a number &ndash; except at the very beginning or right after
a decimal point (`.`).
| Sample | Format | Value type | [`no_float`] | [`no_float`] + [`decimal`] |
| ------------------ | ------------------------- | :--------: | :------------: | :------------------------: |
| `_123` | _improper separator_ | | | |
| `123_345`, `-42` | decimal | `INT` | `INT` | `INT` |
| `0o07_76` | octal | `INT` | `INT` | `INT` |
| `0xab_cd_ef` | hex | `INT` | `INT` | `INT` |
| `0b0101_1001` | binary | `INT` | `INT` | `INT` |
| `123._456` | _improper separator_ | | | |
| `123_456.78_9` | normal floating-point | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
| `-42.` | ending with decimal point | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
| `123_456_.789e-10` | scientific notation | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
| `.456` | _missing leading `0`_ | | | |
| `123.456e_10` | _improper separator_ | | | |
| `123.e-10` | _missing decimal `0`_ | | | |
Warning &ndash; No Implicit Type Conversions
--------------------------------------------
Unlike most C-like languages, Rhai does _not_ provide implicit type conversions between different
numeric types.
For example, a `u8` is never implicitly converted to `i64` when used as a parameter in a function
call or as a comparison operand. `f32` is never implicitly converted to `f64`.
This is exactly the same as Rust where all numeric types are distinct. Rhai is written in Rust afterall.
```admonish warning.small
Integer variables pushed inside a custom [`Scope`] must be the correct type.
It is extremely easy to mess up numeric types since the Rust default integer type is `i32` while for
Rhai it is `i64` (unless under [`only_i32`]).
```
```rust
use rhai::{Engine, Scope, INT};
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("r", 42); // 'r' is i32 (Rust default integer type)
scope.push("x", 42_u8); // 'x' is u8
scope.push("y", 42_i64); // 'y' is i64
scope.push("z", 42 as INT); // 'z' is i64 (or i32 under 'only_i32')
scope.push("f", 42.0_f32); // 'f' is f32
// Rhai integers are i64 (i32 under 'only_i32')
engine.eval::<String>("type_of(42)")? == "i64";
// false - i32 is never equal to i64
engine.eval_with_scope::<bool>(&mut scope, "r == 42")?;
// false - u8 is never equal to i64
engine.eval_with_scope::<bool>(&mut scope, "x == 42")?;
// true - i64 is equal to i64
engine.eval_with_scope::<bool>(&mut scope, "y == 42")?;
// true - INT is i64
engine.eval_with_scope::<bool>(&mut scope, "z == 42")?;
// false - f32 is never equal to f64
engine.eval_with_scope::<bool>(&mut scope, "f == 42.0")?;
```
Floating-Point vs. Decimal
--------------------------
~~~admonish tip.side.wide "Tip: `no_float` + `decimal`"
When both [`no_float`] and [`decimal`] features are turned on, [`Decimal`][rust_decimal] _replaces_
the standard floating-point type.
Floating-point number literals in scripts parse to [`Decimal`][rust_decimal] values.
~~~
[`Decimal`][rust_decimal] (enabled via the [`decimal`] feature) represents a fixed-precision
floating-point number which is popular with financial calculations and other usage scenarios where
round-off errors are not acceptable.
[`Decimal`][rust_decimal] takes up more space (16 bytes) than a standard `FLOAT` (4-8 bytes) and is
much slower in calculations due to the lack of CPU hardware support. Use it only when necessary.
For most situations, the standard floating-point number type `FLOAT` (`f64` or `f32` with
[`f32_float`]) is enough and is faster than [`Decimal`][rust_decimal].
It is possible to use both `FLOAT` and [`Decimal`][rust_decimal] together with just the [`decimal`] feature
&ndash; use [`parse_decimal`] or [`to_decimal`] to create a [`Decimal`][rust_decimal] value.

View File

@ -0,0 +1,78 @@
Non-Existent Property Handling for Object Maps
==============================================
{{#include ../links.md}}
[`Engine::on_map_missing_property`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_map_missing_property
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
~~~admonish warning.small "Requires `internals`"
This is an advanced feature that requires the [`internals`] feature to be enabled.
~~~
Normally, when a property is accessed from an [object map] that does not exist, [`()`] is returned.
Via [`Engine:: set_fail_on_invalid_map_property`][options], it is possible to make this an error
instead.
Other than that, it is possible to completely control this behavior via a special callback function
registered into an [`Engine`] via `on_map_missing_property`.
Using this callback, for instance, it is simple to instruct Rhai to create a new property in the
[object map] on the fly, possibly with a default value, when a non-existent property is accessed.
Function Signature
------------------
The function signature passed to [`Engine::on_map_missing_property`] takes the following form.
> ```rust
> Fn(map: &mut Map, prop: &str, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :----------------------: | ----------------------------------- |
| `map` | [`&mut Map`][object map] | the [object map] being accessed |
| `prop` | `&str` | name of the property being accessed |
| `context` | [`EvalContext`] | the current _evaluation context_ |
### Return value
The return value is `Result<Target, Box<EvalAltResult>>`.
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
_reference_ to a [`Dynamic`] value.
It can be used to point to a particular value within the [object map].
Example
-------
```rust
engine.on_map_missing_property(|map, prop, context| {
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();
Ok(value_ref.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()),
}
});
```

Some files were not shown because too many files have changed in this diff Show More