diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f97022
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
\ No newline at end of file
diff --git a/rhai_engine/Cargo.lock b/rhai_engine/Cargo.lock
new file mode 100644
index 0000000..4129da9
--- /dev/null
+++ b/rhai_engine/Cargo.lock
@@ -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",
+]
diff --git a/rhai_engine/Cargo.toml b/rhai_engine/Cargo.toml
new file mode 100644
index 0000000..2d26432
--- /dev/null
+++ b/rhai_engine/Cargo.toml
@@ -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"
diff --git a/rhai_engine/rhaibook/LICENSE-APACHE.txt b/rhai_engine/rhaibook/LICENSE-APACHE.txt
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/rhai_engine/rhaibook/LICENSE-APACHE.txt
@@ -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
diff --git a/rhai_engine/rhaibook/LICENSE-MIT.txt b/rhai_engine/rhaibook/LICENSE-MIT.txt
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/rhai_engine/rhaibook/LICENSE-MIT.txt
@@ -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.
diff --git a/rhai_engine/rhaibook/SUMMARY.md b/rhai_engine/rhaibook/SUMMARY.md
new file mode 100644
index 0000000..e83de1a
--- /dev/null
+++ b/rhai_engine/rhaibook/SUMMARY.md
@@ -0,0 +1,338 @@
+The Rhai Scripting Language
+==========================
+
+[The Rhai Book](index.md)
+
+----------------------
+
+User’s 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)
diff --git a/rhai_engine/rhaibook/about/features.md b/rhai_engine/rhaibook/about/features.md
new file mode 100644
index 0000000..908b657
--- /dev/null
+++ b/rhai_engine/rhaibook/about/features.md
@@ -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.
+```
diff --git a/rhai_engine/rhaibook/about/index.md b/rhai_engine/rhaibook/about/index.md
new file mode 100644
index 0000000..75f36ab
--- /dev/null
+++ b/rhai_engine/rhaibook/about/index.md
@@ -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"
+
+
+
+
Original prototype
+
+
+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).
+~~~
diff --git a/rhai_engine/rhaibook/appendix/keywords.md b/rhai_engine/rhaibook/appendix/keywords.md
new file mode 100644
index 0000000..9f1a88e
--- /dev/null
+++ b/rhai_engine/rhaibook/appendix/keywords.md
@@ -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` | [`while`] loop condition for [`do`] loop | | **no** |
+| `until` | [`do`] loop | | **no** |
+| `loop` | infinite [`loop`] | | **no** |
+| `for` | [`for`] loop | | **no** |
+| `in` | [containment][`in`] test part of [`for`] loop | | **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 |
diff --git a/rhai_engine/rhaibook/appendix/literals.md b/rhai_engine/rhaibook/appendix/literals.md
new file mode 100644
index 0000000..3ee6753
--- /dev/null
+++ b/rhai_engine/rhaibook/appendix/literals.md
@@ -0,0 +1,20 @@
+Literals Syntax
+===============
+
+{{#include ../links.md}}
+
+| Type | Literal syntax |
+| :------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------- |
+| `INT` | decimal: `42`, `-123`, `0` hex: `0x????..` binary: `0b????..` octal: `0o????..` |
+| `FLOAT`, [`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\` `second line\` `the third line"` |
+| Multi-line literal [string] | `` `this is the first line`` ``second line````the last line` `` |
+| Multi-line literal [string] with interpolation | `` `this is the first field: ${obj.field1}`` ``second field: {obj.field2}````the last field: ${obj.field3}` `` |
+| [Character] | single: `'?'` ASCII hex: `'\x??'` Unicode: `'\u????'`, `'\U????????'` |
+| [`Array`] | `[ ???, ???, ??? ]` |
+| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
+| Boolean true | `true` |
+| Boolean false | `false` |
+| `Nothing`/`null`/`nil`/`void`/Unit | `()` |
diff --git a/rhai_engine/rhaibook/appendix/operators.md b/rhai_engine/rhaibook/appendix/operators.md
new file mode 100644
index 0000000..22ffcc7
--- /dev/null
+++ b/rhai_engine/rhaibook/appendix/operators.md
@@ -0,0 +1,86 @@
+Operators and Symbols
+=====================
+
+{{#include ../links.md}}
+
+
+Operators
+---------
+
+| Operator | Description | Binary? | Binding direction |
+| :------------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: |
+| `+` | add | yes | left |
+| `-` | 1) subtract 2) negative (prefix) | yes no | left 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_ 2) boolean _AND_ | yes | left |
+| \|
| 1) bit-wise _OR_ 2) boolean _OR_ | yes | left |
+| `^` | 1) bit-wise _XOR_ 2) boolean _XOR_ | yes | left |
+| `=`, `+=`, `-=`, `*=`, `/=`, `**=`, `%=`, `<<=`, `>>=`, `&=`,\|=
, `^=` | 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 |
+| \|\|
| boolean _OR_ (short-circuits) | yes | left |
+| `??` | null-coalesce (short-circuits) | yes | left |
+| `!` | boolean _NOT_ | **no** | right |
+| `[` ... `]`, `?[` ... `]` | indexing | yes | left |
+| `.`, `?.` | 1) property access 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 2) line continuation | escape character literal |
+| `()` | unit | null value |
+| `(` ... `)` | parentheses | expression grouping |
+| `{` ... `}` | braces | block statement |
+| \|
... \|
| 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_ |
+| <\|
| left triangle | _reserved_ |
+| \|>
| right triangle | _reserved_ |
+| `===` | strict equals to | _reserved_ |
+| `!==` | strict not equals to | _reserved_ |
+| `:=` | assignment | _reserved_ |
+| `:;` | typo to `::` | _reserved_ |
+| `::<` ... `>` | turbofish | _reserved_ |
diff --git a/rhai_engine/rhaibook/context.toml b/rhai_engine/rhaibook/context.toml
new file mode 100644
index 0000000..cfe5875
--- /dev/null
+++ b/rhai_engine/rhaibook/context.toml
@@ -0,0 +1,5 @@
+version = "1.21.0"
+repoHome = "https://github.com/rhaiscript/rhai/blob/main"
+rootUrl = ""
+#rootUrl = "/book"
+#rootUrl = "/book/vnext"
diff --git a/rhai_engine/rhaibook/engine/ast.md b/rhai_engine/rhaibook/engine/ast.md
new file mode 100644
index 0000000..8f42a89
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/ast.md
@@ -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)`, `clone_source(&self)`, `set_source(&mut self, source)`, `clear_source(&mut self)` |
+| [Module documentation][comments] | [`Vec`][`SmartString`] | documentation of the script | [`metadata`] | `doc(&self)`, `clear_doc(&mut self)` |
+| Statements | `Vec` | list of script statements at global level | [`internals`] | `statements(&self)`, `statements_mut(&mut self)` |
+| Functions | [`Shared`][`Module`] | [functions] defined in the script | [`internals`], 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`], 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 – 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)`, `+` 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)`, `+=` 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 | condition expression _then_ statements _else_ statements (if any) |
+| [`switch`] statement | match element each of the case conditions and statements, in order each of the range conditions and statements, in order default statements (if any) |
+| [`while`], [`do`], [`loop`] statement | condition expression statements body |
+| [`for`] statement | collection expression statements body |
+| [`return`] statement | return value expression |
+| [`throw`] statement | exception value expression |
+| [`try` ... `catch`][exception] statement | `try` statements body `catch` statements body |
+| [`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 | LHS expression RHS (index) expression |
+| Field access/method call | LHS expression RHS expression |
+| `&&`, \|\|
, `??` | LHS expression RHS expression |
+| [Function] call, [operator] expression | each of the argument expressions, in order |
+| [`let`][variable], [`const`][constant] statement | value expression |
+| Assignment statement | l-value expression value expression |
+| Statements block | each of the statements, in order |
+| Custom syntax expression | each of the inputs stream, in order |
+| All others | single child (if any) |
diff --git a/rhai_engine/rhaibook/engine/builtin.md b/rhai_engine/rhaibook/engine/builtin.md
new file mode 100644
index 0000000..03ff0aa
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/builtin.md
@@ -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 (see [standard types]) |
+| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `+`, | `+=` | `INT` `FLOAT` (if not [`no_float`]) [`Decimal`][rust_decimal] (requires [`decimal`]) `char` [string] |
+| `-`, `*`, `/`, `%`, `**`, | `-=`, `*=`, `/=`, `%=`, `**=` | `INT` `FLOAT` (if not [`no_float`]) [`Decimal`][rust_decimal] (requires [`decimal`]) |
+| `<<`, `>>` | `<<=`, `>>=` | |
+| `&`, \|
, `^` | `&=`, \|=
, `^=` | `INT` (bit-wise) `bool` (non-short-circuiting) |
+| `&&`, \|\|
| | |
+| `==`, `!=` | | `INT` `FLOAT` (if not [`no_float`]) [`Decimal`][rust_decimal] (requires [`decimal`]) `bool` `char` [string] [BLOB] numeric [range] `()` |
+| `>`, `>=`, `<`, `<=` | | `INT` `FLOAT` (if not [`no_float`]) [`Decimal`][rust_decimal] (requires [`decimal`]) `char` [string] `()` |
+
+```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. `+`).
+```
diff --git a/rhai_engine/rhaibook/engine/call-fn.md b/rhai_engine/rhaibook/engine/call-fn.md
new file mode 100644
index 0000000..6c98e4c
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/call-fn.md
@@ -0,0 +1,275 @@
+Call Rhai Functions from Rust
+=============================
+
+{{#include ../links.md}}
+
+Rhai also allows working _backwards_ from the other direction – 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::(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
+// ^^^ ^^^^^^^^^^^^^^^^^^
+// return type must be specified put arguments in a tuple
+
+let result = engine.call_fn::(&mut scope, &ast, "hello", ( 123_i64, ) )?;
+// ^^^^^^^^^^^^ tuple of one
+
+let result = engine.call_fn::(&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`.
+```
+
+`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>(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::(&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::(
+ 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`] | `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);
+```
diff --git a/rhai_engine/rhaibook/engine/compile.md b/rhai_engine/rhaibook/engine/compile.md
new file mode 100644
index 0000000..272b46a
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/compile.md
@@ -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 – 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 – 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::(combined_script)?;
+
+ println!("{result}");
+}
+```
+
+### Option 2 – 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::(combined_ast)?;
+
+ println!("{result}");
+```
+
+### Option 3 – 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::(user_script)?;
+
+ println!("{result}");
+}
+```
diff --git a/rhai_engine/rhaibook/engine/custom-op.md b/rhai_engine/rhaibook/engine/custom-op.md
new file mode 100644
index 0000000..d1b0da4
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/custom-op.md
@@ -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::("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::("1 + 2 * 3 foo 4 - 5 / 6")?; // use custom operator
+
+engine.eval::("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::("# 42")?; // <- syntax error
+```
diff --git a/rhai_engine/rhaibook/engine/custom-syntax-parsers.md b/rhai_engine/rhaibook/engine/custom-syntax-parsers.md
new file mode 100644
index 0000000..13d088b
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/custom-syntax-parsers.md
@@ -0,0 +1,246 @@
+Really Advanced – 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, 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 – 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 – 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 – 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 , 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`] – 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>
+{
+ // 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>
+> ```
+
+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' 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}"))
+ }
+ }
+);
+```
diff --git a/rhai_engine/rhaibook/engine/custom-syntax.md b/rhai_engine/rhaibook/engine/custom-syntax.md
new file mode 100644
index 0000000..b0f2ff4
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/custom-syntax.md
@@ -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 – 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$` – any valid expression, statement or statements block.
+* `$block$` – any valid statements block (i.e. must be enclosed by `{` ... `}`).
+* `$func$` – any valid [closure], or any valid statements block as the body of a [closure] with no parameters (if not [`no_function`]).
+* `$ident$` – any [variable] name.
+* `$symbol$` – any [symbol][operator], active or reserved.
+* `$bool$` – a boolean value.
+* `$int$` – an integer number.
+* `$float$` – a floating-point number (if not [`no_float`]).
+* `$string$` – 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 – 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>
+> ```
+
+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::().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::().unwrap()` | `bool` | boolean value |
+| `$int$` | `inputs[n].get_literal_value::().unwrap()` | `INT` | integer number |
+| `$float$` | `inputs[n].get_literal_value::().unwrap()` | `FLOAT` | floating-point number |
+| `$string$` | `inputs[n].get_literal_value::().unwrap()` `inputs[n].get_string_value().unwrap()` | [`ImmutableString`] `&str` | [string] text |
+
+#### Get literal constants
+
+Several argument types represent literal constants that can be obtained directly via
+`Expression::get_literal_value` 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::().unwrap();
+let string_slice = expression.get_string_value().unwrap();
+
+let float_value = expression.get_literal_value::().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 – 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> {
+ 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 += 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 += 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 += 1 } while x < 42, 24, true);
+
+// Use as a statement:
+exec -> { x += 1 } while x < 0;
+// ^ terminate statement with ';' unless the custom
+// syntax already ends with '}'
+```
+
+
+### Step Four – 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™ – unless you are certain
+of what you're doing.
+
+
+### Step Five – Document
+
+For custom syntax, documentation is crucial.
+
+Make sure there are _lots_ of examples for users to follow.
+
+
+### Step Six – Profit!
+
+
+Practical Example – 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 – 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 – 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 – 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())
+ }
+})?;
+```
diff --git a/rhai_engine/rhaibook/engine/debugging/break-points.md b/rhai_engine/rhaibook/engine/debugging/break-points.md
new file mode 100644
index 0000000..b29a5b7
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/break-points.md
@@ -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); 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` | 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();
+```
diff --git a/rhai_engine/rhaibook/engine/debugging/call-stack.md b/rhai_engine/rhaibook/engine/debugging/call-stack.md
new file mode 100644
index 0000000..7d57255
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/call-stack.md
@@ -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}");
+}
+```
diff --git a/rhai_engine/rhaibook/engine/debugging/debugger.md b/rhai_engine/rhaibook/engine/debugging/debugger.md
new file mode 100644
index 0000000..d641237
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/debugger.md
@@ -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>
+> ```
+
+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>`.
+
+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` |
diff --git a/rhai_engine/rhaibook/engine/debugging/index.md b/rhai_engine/rhaibook/engine/debugging/index.md
new file mode 100644
index 0000000..f3edc8a
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/index.md
@@ -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 – 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 –
+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;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)
+ }
+}
+```
diff --git a/rhai_engine/rhaibook/engine/debugging/server.md b/rhai_engine/rhaibook/engine/debugging/server.md
new file mode 100644
index 0000000..31c7698
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/server.md
@@ -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::().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");
+```
diff --git a/rhai_engine/rhaibook/engine/debugging/state.md b/rhai_engine/rhaibook/engine/debugging/state.md
new file mode 100644
index 0000000..8b8b60e
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/debugging/state.md
@@ -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`] | _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::().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)
+ }
+);
+```
diff --git a/rhai_engine/rhaibook/engine/def-var.md b/rhai_engine/rhaibook/engine/def-var.md
new file mode 100644
index 0000000..45c01ce
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/def-var.md
@@ -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>
+> ```
+
+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>` where:
+
+| Value | Description |
+| ------------------------- | -------------------------------------------------- |
+| `Ok(true)` | normal [variable] definition should continue |
+| `Ok(false)` | [throws][exception] a runtime or compilation error |
+| `Err(Box)` | 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`.
+```
diff --git a/rhai_engine/rhaibook/engine/disable-keywords.md b/rhai_engine/rhaibook/engine/disable-keywords.md
new file mode 100644
index 0000000..fd6b1ca
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/disable-keywords.md
@@ -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
+```
diff --git a/rhai_engine/rhaibook/engine/disable-looping.md b/rhai_engine/rhaibook/engine/disable-looping.md
new file mode 100644
index 0000000..cf52b2e
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/disable-looping.md
@@ -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); }")?;
+```
diff --git a/rhai_engine/rhaibook/engine/dsl.md b/rhai_engine/rhaibook/engine/dsl.md
new file mode 100644
index 0000000..0a48712
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/dsl.md
@@ -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] –
+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"` – math operator
+* `inputs[1] = "price"` – field name
+* `inputs[2] = "row"` – loop variable name
+* `inputs[3] = Expression(table)` – data source
+* `inputs[4] = Expression(row.weight > 50)` – 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].
diff --git a/rhai_engine/rhaibook/engine/dynamic-lib.md b/rhai_engine/rhaibook/engine/dynamic-lib.md
new file mode 100644
index 0000000..d37f796
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/dynamic-lib.md
@@ -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_ – 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.
+```
diff --git a/rhai_engine/rhaibook/engine/eval-context.md b/rhai_engine/rhaibook/engine/eval-context.md
new file mode 100644
index 0000000..b60dd2f
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/eval-context.md
@@ -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- ` | 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
- ` | 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 |
diff --git a/rhai_engine/rhaibook/engine/expressions.md b/rhai_engine/rhaibook/engine/expressions.md
new file mode 100644
index 0000000..cd81052
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/expressions.md
@@ -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`) –
+not even [variable] assignment – 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::
(&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::(&mut scope,
+ "if x { 42 } else { 123 }"
+ )?;
+
+let result = engine.eval_expression_with_scope::(&mut scope, "
+ switch x {
+ 0 => x * 42,
+ 1..=9 => foo(123) + bar(1),
+ 10 => 0,
+ }
+ ")?;
+```
+~~~
diff --git a/rhai_engine/rhaibook/engine/func.md b/rhai_engine/rhaibook/engine/func.md
new file mode 100644
index 0000000..4751fe2
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/func.md
@@ -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 Result>> and is callable!
+let func = Func::<(i64, &str), bool>::create_from_script(
+// ^^^^^^^^^^^ function parameter types in tuple
+
+ engine, // the 'Engine' is consumed into the closure
+ script, // the script, notice number of parameters must match
+ "calc" // the entry-point function name
+)?;
+
+func(123, "hello")? == false; // call the 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::(&mut Scope::new(), &ast, "calc", (x, y))
+}));
+```
diff --git a/rhai_engine/rhaibook/engine/hello-world.md b/rhai_engine/rhaibook/engine/hello-world.md
new file mode 100644
index 0000000..59228a1
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/hello-world.md
@@ -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>
+// ^^^^^^^^^^^^^^^^^^
+// 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>
+{
+ let engine = Engine::new();
+
+ let result = engine.eval::("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::("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::("40 + 2")?; // return type is i64
+
+result.is::() == true;
+
+let result = engine.eval::("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
+
+let result = engine.eval::("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::() == 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
+```
diff --git a/rhai_engine/rhaibook/engine/index.md b/rhai_engine/rhaibook/engine/index.md
new file mode 100644
index 0000000..8bf5850
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/index.md
@@ -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.
diff --git a/rhai_engine/rhaibook/engine/metadata/definitions.md b/rhai_engine/rhaibook/engine/metadata/definitions.md
new file mode 100644
index 0000000..567ce65
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/metadata/definitions.md
@@ -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;
+```
diff --git a/rhai_engine/rhaibook/engine/metadata/export_to_json.md b/rhai_engine/rhaibook/engine/metadata/export_to_json.md
new file mode 100644
index 0000000..8898883
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/metadata/export_to_json.md
@@ -0,0 +1,147 @@
+Export Functions Metadata to JSON
+=================================
+
+{{#include ../../links.md}}
+
+
+`Engine::gen_fn_metadata_to_json` `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 */",
+ ...
+ ]
+}
+```
diff --git a/rhai_engine/rhaibook/engine/metadata/gen_fn_sig.md b/rhai_engine/rhaibook/engine/metadata/gen_fn_sig.md
new file mode 100644
index 0000000..4e5a672
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/metadata/gen_fn_sig.md
@@ -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`), 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>`
+
+### 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, 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>`:
+
+> `+=(list: &mut MyStruct, value: &str) -> Result>`
+
+For example, a [property getter][getters/setters] defined in a [plugin module]:
+
+> `get$prop(obj: &mut MyStruct) -> String`
diff --git a/rhai_engine/rhaibook/engine/metadata/index.md b/rhai_engine/rhaibook/engine/metadata/index.md
new file mode 100644
index 0000000..98ee77d
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/metadata/index.md
@@ -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]
diff --git a/rhai_engine/rhaibook/engine/optimize/constants.md b/rhai_engine/rhaibook/engine/optimize/constants.md
new file mode 100644
index 0000000..a00cac2
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/constants.md
@@ -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.
+~~~
diff --git a/rhai_engine/rhaibook/engine/optimize/dead-code.md b/rhai_engine/rhaibook/engine/optimize/dead-code.md
new file mode 100644
index 0000000..7e4526b
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/dead-code.md
@@ -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
+}
+```
diff --git a/rhai_engine/rhaibook/engine/optimize/disable.md b/rhai_engine/rhaibook/engine/optimize/disable.md
new file mode 100644
index 0000000..8858219
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/disable.md
@@ -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.
diff --git a/rhai_engine/rhaibook/engine/optimize/eager.md b/rhai_engine/rhaibook/engine/optimize/eager.md
new file mode 100644
index 0000000..e8f0a67
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/eager.md
@@ -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);
+```
+~~~
\ No newline at end of file
diff --git a/rhai_engine/rhaibook/engine/optimize/index.md b/rhai_engine/rhaibook/engine/optimize/index.md
new file mode 100644
index 0000000..5e61cfb
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/index.md
@@ -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 – 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.
diff --git a/rhai_engine/rhaibook/engine/optimize/op-eval.md b/rhai_engine/rhaibook/engine/optimize/op-eval.md
new file mode 100644
index 0000000..a6fc96f
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/op-eval.md
@@ -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`].
diff --git a/rhai_engine/rhaibook/engine/optimize/passes.md b/rhai_engine/rhaibook/engine/optimize/passes.md
new file mode 100644
index 0000000..60f7ff7
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/passes.md
@@ -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 |
diff --git a/rhai_engine/rhaibook/engine/optimize/reoptimize.md b/rhai_engine/rhaibook/engine/optimize/reoptimize.md
new file mode 100644
index 0000000..8f8a590
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/reoptimize.md
@@ -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`].
+```
diff --git a/rhai_engine/rhaibook/engine/optimize/rewrite.md b/rhai_engine/rhaibook/engine/optimize/rewrite.md
new file mode 100644
index 0000000..eabc4b2
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/rewrite.md
@@ -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
+```
+~~~
diff --git a/rhai_engine/rhaibook/engine/optimize/semantics.md b/rhai_engine/rhaibook/engine/optimize/semantics.md
new file mode 100644
index 0000000..9d51d15
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/semantics.md
@@ -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 –
+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 – 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.
diff --git a/rhai_engine/rhaibook/engine/optimize/side-effects.md b/rhai_engine/rhaibook/engine/optimize/side-effects.md
new file mode 100644
index 0000000..be7ae61
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/side-effects.md
@@ -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.
+```
diff --git a/rhai_engine/rhaibook/engine/optimize/volatility.md b/rhai_engine/rhaibook/engine/optimize/volatility.md
new file mode 100644
index 0000000..e22d24c
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/optimize/volatility.md
@@ -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 – 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 {
+ // ...
+ }
+}
+```
+~~~
diff --git a/rhai_engine/rhaibook/engine/options.md b/rhai_engine/rhaibook/engine/options.md
new file mode 100644
index 0000000..c4c878d
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/options.md
@@ -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` (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` (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` (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) |
diff --git a/rhai_engine/rhaibook/engine/precedence.md b/rhai_engine/rhaibook/engine/precedence.md
new file mode 100644
index 0000000..0ab2587
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/precedence.md
@@ -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 | \|\|
, \|
, `^` | 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 |
diff --git a/rhai_engine/rhaibook/engine/raw.md b/rhai_engine/rhaibook/engine/raw.md
new file mode 100644
index 0000000..bd9d33c
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/raw.md
@@ -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);
+```
+~~~
diff --git a/rhai_engine/rhaibook/engine/scope.md b/rhai_engine/rhaibook/engine/scope.md
new file mode 100644
index 0000000..cd8d2dc
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/scope.md
@@ -0,0 +1,215 @@
+`Scope` – 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`, `get_mut` and `set_value`.
+
+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` | 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` | get the value of the last [variable]/[constant] within the `Scope` by name |
+| `set_value` | set the value of the last [variable] within the `Scope` by name, panics if it is [constant] |
+| `remove` | 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::(&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::("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::("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
+```
+~~~
diff --git a/rhai_engine/rhaibook/engine/strict-var.md b/rhai_engine/rhaibook/engine/strict-var.md
new file mode 100644
index 0000000..a0fac29
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/strict-var.md
@@ -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 – 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].
+```
diff --git a/rhai_engine/rhaibook/engine/token-mapper.md b/rhai_engine/rhaibook/engine/token-mapper.md
new file mode 100644
index 0000000..2931bec
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/token-mapper.md
@@ -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
+ }
+});
+```
diff --git a/rhai_engine/rhaibook/engine/var.md b/rhai_engine/rhaibook/engine/var.md
new file mode 100644
index 0000000..22c93ab
--- /dev/null
+++ b/rhai_engine/rhaibook/engine/var.md
@@ -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, Box>
+> ```
+
+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. Offsets start from 1, with 1 meaning the last [variable] in the current [`Scope`]. Essentially the correct [variable] is at position `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] 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, Box>` 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)` | 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`. |
diff --git a/rhai_engine/rhaibook/index.md b/rhai_engine/rhaibook/index.md
new file mode 100644
index 0000000..7ce66b0
--- /dev/null
+++ b/rhai_engine/rhaibook/index.md
@@ -0,0 +1,17 @@
+The Rhai Book
+=============
+
+{{#title The Rhai Book}}
+
+{{#include links.md}}
+
+
+
+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.
diff --git a/rhai_engine/rhaibook/language/arrays-oob.md b/rhai_engine/rhaibook/language/arrays-oob.md
new file mode 100644
index 0000000..569ab93
--- /dev/null
+++ b/rhai_engine/rhaibook/language/arrays-oob.md
@@ -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>
+> ```
+
+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`] 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()),
+ }
+});
+```
diff --git a/rhai_engine/rhaibook/language/arrays.md b/rhai_engine/rhaibook/language/arrays.md
new file mode 100644
index 0000000..0af2b1c
--- /dev/null
+++ b/rhai_engine/rhaibook/language/arrays.md
@@ -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`.
+
+[`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 (length−1)_ `]`
+
+### 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` | position, counting from end if < 0 new element | 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 | first array second array | concatenates the first array with the second |
+| `==` operator | first array second array | are two arrays the same (elements compared with the `==` operator, if defined)? |
+| `!=` operator | first array second array | are two arrays different (elements compared with the `==` operator, if defined)? |
+| `insert` | position, counting from end if < 0, end if ≥ length element to insert | 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` | start position, counting from end if < 0, end if ≥ length _(optional)_ number of elements to extract, none if ≤ 0, to end if omitted | 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` | target length element to pad | 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` | array position to split at, counting from end if < 0, end if ≥ length | 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: `this`: array element _(optional)_ index position |
+| `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`):array element _(optional)_ index position |
+| `drain` | start position, counting from end if < 0, end if ≥ length number of elements to remove, none if ≤ 0 | 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`):array element _(optional)_ index position |
+| `retain` | start position, counting from end if < 0, end if ≥ length number of elements to retain, none if ≤ 0 | 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` | start position, counting from end if < 0, end if ≥ length number of elements to remove, none if ≤ 0 array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
+| `splice` | [range] of elements to remove, from beginning if ≤ 0, to end if ≥ length array to insert | 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`):array element _(optional)_ index position |
+| `contains`, [`in`] operator | element to find | does the array contain an element? The `==` operator (if defined) is used to compare [custom types] |
+| `index_of` | element to find (not a [function pointer]) _(optional)_ start position, counting from end if < 0, end if ≥ length | 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 |
+| `index_of` | [function pointer] to predicate (usually a [closure]) _(optional)_ start position, counting from end if < 0, end if ≥ length | returns the position of the first element in the array that returns `true` when called with the predicate function, or −1 if not found:array element (if none, the array element is bound to `this`) _(optional)_ index position |
+| `find` | [function pointer] to predicate (usually a [closure]) _(optional)_ start position, counting from end if < 0, end if ≥ length | returns the first element in the array that returns `true` when called with the predicate function, or [`()`] if not found:array element (if none, the array element is bound to `this`) _(optional)_ index position |
+| `find_map` | [function pointer] to predicate (usually a [closure]) _(optional)_ start position, counting from end if < 0, end if ≥ length | returns the first non-[`()`] value of the first element in the array when called with the predicate function, or [`()`] if not found:array element (if none, the array element is bound to `this`) _(optional)_ index position |
+| `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): 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`):array element _(optional)_ index position |
+| `reduce` | [function pointer] to accumulator function (usually a [closure]) _(optional)_ the initial value | 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`):accumulated value ([`()`] initially) `this`: array element _(optional)_ index position |
+| `reduce_rev` | [function pointer] to accumulator function (usually a [closure]) _(optional)_ the initial value | 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`):accumulated value ([`()`] initially) `this`: array element _(optional)_ index position |
+| `zip` | array to zip [function pointer] to conversion function (usually a [closure]) | constructs a new array with all element pairs from two arrays mapped to the result of applying the conversion function taking the following parameters:first array element second array element _(optional)_ index position |
+| `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`):array element _(optional)_ index position |
+| `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`):array element _(optional)_ index position |
+| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function taking the following parameters:first element second element return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second |
+| `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 == []
+```
diff --git a/rhai_engine/rhaibook/language/assignment-op.md b/rhai_engine/rhaibook/language/assignment-op.md
new file mode 100644
index 0000000..dec7ccb
--- /dev/null
+++ b/rhai_engine/rhaibook/language/assignment-op.md
@@ -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;
+```
diff --git a/rhai_engine/rhaibook/language/assignment.md b/rhai_engine/rhaibook/language/assignment.md
new file mode 100644
index 0000000..92fac1c
--- /dev/null
+++ b/rhai_engine/rhaibook/language/assignment.md
@@ -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; }
+```
diff --git a/rhai_engine/rhaibook/language/bit-fields.md b/rhai_engine/rhaibook/language/bit-fields.md
new file mode 100644
index 0000000..fc3a9dc
--- /dev/null
+++ b/rhai_engine/rhaibook/language/bit-fields.md
@@ -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` | bit number, counting from MSB if < 0 new state: `true` if `1`, `false` if `0` | sets the state of a bit |
+| `get_bits` | starting bit number, counting from MSB if < 0 number of bits to extract, none if < 1, to MSB if ≥ _length_ | extracts a number of bits, shifted towards LSB |
+| `get_bits` | [range] of bits | extracts a number of bits, shifted towards LSB |
+| `set_bits` | starting bit number, counting from MSB if < 0 number of bits to set, none if < 1, to MSB if ≥ _length_ 3) new value | sets a number of bits from the new value |
+| `set_bits` | [range] of bits new value | sets a number of bits from the new value |
+| `bits` method and property | _(optional)_ starting bit number, counting from MSB if < 0 _(optional)_ number of bits to extract, none if < 1, to MSB if ≥ _length_ | 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}`)
+}
+```
diff --git a/rhai_engine/rhaibook/language/blobs.md b/rhai_engine/rhaibook/language/blobs.md
new file mode 100644
index 0000000..24829e3
--- /dev/null
+++ b/rhai_engine/rhaibook/language/blobs.md
@@ -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`.
+
+[`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 (length−1)_ `]`
+
+### 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 | _(optional)_ initial length of the BLOB _(optional)_ initial byte value | 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` | position, counting from end if < 0 new byte value | sets a certain position to a new value (no effect if the position is not valid) |
+| `push`, `append`, `+=` operator | BLOB byte to append | appends a byte to the end |
+| `append`, `+=` operator | BLOB BLOB to append | concatenates the second BLOB to the end of the first |
+| `append`, `+=` operator | BLOB [string]/[character] to append | concatenates a [string] or [character] (as UTF-8 encoded byte-stream) to the end of the BLOB |
+| `+` operator | first BLOB [string] to append | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string] |
+| `+` operator | [string] BLOB to append | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string] |
+| `+` operator | first BLOB second BLOB | concatenates the first BLOB with the second |
+| `==` operator | first BLOB second BLOB | are two BLOB's the same? |
+| `!=` operator | first BLOB second BLOB | are two BLOB's different? |
+| `insert` | position, counting from end if < 0, end if ≥ length byte to insert | 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` | start position, counting from end if < 0, end if ≥ length _(optional)_ number of bytes to extract, none if ≤ 0 | 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` | target length byte value to pad | 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` | BLOB position to split at, counting from end if < 0, end if ≥ length | splits the BLOB into two BLOB's, starting from a specified position |
+| `drain` | start position, counting from end if < 0, end if ≥ length number of bytes to remove, none if ≤ 0 | 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` | start position, counting from end if < 0, end if ≥ length number of bytes to retain, none if ≤ 0 | 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` | start position, counting from end if < 0, end if ≥ length number of bytes to remove, none if ≤ 0 BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
+| `splice` | [range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
+| `parse_le_int` | start position, counting from end if < 0, end if ≥ length number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0 | 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` | start position, counting from end if < 0, end if ≥ length number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0 | 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` (not available under [`no_float`]) | start position, counting from end if < 0, end if ≥ length number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0 | 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` (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` (not available under [`no_float`]) | start position, counting from end if < 0, end if ≥ length number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0 | 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` (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` | start position, counting from end if < 0, end if ≥ length number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0 integer or floating-point value | 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` | [range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`]) integer or floating-point value | 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` | start position, counting from end if < 0, end if ≥ length number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0 integer or floating-point value | 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` | [range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`]) integer or floating-point value | 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` | start position, counting from end if < 0, end if ≥ length number of bytes to write, none if ≤ 0, to end if ≥ length [string] to write | writes a [string] to the particular offset in UTF-8 encoding |
+| `write_utf8` | [range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length [string] to write | writes a [string] to the particular offset in UTF-8 encoding |
+| `write_ascii` | start position, counting from end if < 0, end if ≥ length number of [characters] to write, none if ≤ 0, to end if ≥ length [string] to write | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
+| `write_ascii` | [range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length [string] to write | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
diff --git a/rhai_engine/rhaibook/language/comments.md b/rhai_engine/rhaibook/language/comments.md
new file mode 100644
index 0000000..cb4772a
--- /dev/null
+++ b/rhai_engine/rhaibook/language/comments.md
@@ -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] – 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.
+```
diff --git a/rhai_engine/rhaibook/language/constants.md b/rhai_engine/rhaibook/language/constants.md
new file mode 100644
index 0000000..7ae900b
--- /dev/null
+++ b/rhai_engine/rhaibook/language/constants.md
@@ -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") // 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 – 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 – 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 – 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.
+```
diff --git a/rhai_engine/rhaibook/language/convert.md b/rhai_engine/rhaibook/language/convert.md
new file mode 100644
index 0000000..f5c3a8d
--- /dev/null
+++ b/rhai_engine/rhaibook/language/convert.md
@@ -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";
+```
diff --git a/rhai_engine/rhaibook/language/do.md b/rhai_engine/rhaibook/language/do.md
new file mode 100644
index 0000000..9531046
--- /dev/null
+++ b/rhai_engine/rhaibook/language/do.md
@@ -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}!`);
+}
+```
diff --git a/rhai_engine/rhaibook/language/doc-comments.md b/rhai_engine/rhaibook/language/doc-comments.md
new file mode 100644
index 0000000..2796f84
--- /dev/null
+++ b/rhai_engine/rhaibook/language/doc-comments.md
@@ -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.
diff --git a/rhai_engine/rhaibook/language/dynamic-rust.md b/rhai_engine/rhaibook/language/dynamic-rust.md
new file mode 100644
index 0000000..186d943
--- /dev/null
+++ b/rhai_engine/rhaibook/language/dynamic-rust.md
@@ -0,0 +1,257 @@
+Interop `Dynamic` Data with Rust
+================================
+
+{{#include ../links.md}}
+
+
+Create a `Dynamic` from Rust Type
+---------------------------------
+
+| Rust type `T: Clone`, `K: Into` | 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]`, `Iterator` | [`no_index`] | `value.into()` |
+| [`Map`][object map] | [`no_object`] | `Dynamic::from_map(value)` |
+| `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet` | [`no_object`] | `value.into()` |
+| [`INT..INT`][range], [`INT..=INT`][range] | | `value.into()` |
+| `Rc>` or `Arc>` | [`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::() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
+
+let value = item.cast::(); // 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::()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
+
+let value = list[0].clone_cast::(); // 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" => ...
+ "core::ops::range::RangeInclusive" => ...
+ "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::().unwrap();
+// ^^^^^^^^^^^^^^^^^^^^ cast to data type
+
+println!("answer = {}", q_ref.answer); // prints 42
+
+let q_mut: &mut TheGreatQuestion =
+ &mut *value.write_lock::().unwrap();
+// ^^^^^^^^^^^^^^^^^^^^ cast to data type
+
+q_mut.answer = 0; // mutate value
+
+let value = value.cast::();
+
+println!("new answer = {}", value.answer); // prints 0
+```
+
+~~~admonish question "TL;DR – 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>` (`Arc>` 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` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value for reading |
+| `write_lock` | [`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` | [array] |
+| `from_blob` | [`no_index`] | `Vec` | [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` | [custom type] |
+
+### Detection methods
+
+| Method | Not available under | Return type | Description |
+| -------------- | :-----------------------: | :---------: | ---------------------------------------------------------------------- |
+| `is` | | `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` (panics on failure) |
+| `try_cast` | | `Option` |
+| `try_cast_result` | | `Result` |
+| `clone_cast` | | cloned copy of `T` (panics on failure) |
+| `as_unit` | | `Result<(), &str>` |
+| `as_int` | | `Result` |
+| `as_float` | [`no_float`] | `Result` |
+| `as_decimal` | non-[`decimal`] | [`Result`][rust_decimal] |
+| `as_bool` | | `Result` |
+| `as_char` | | `Result` |
+| `as_immutable_string_ref` | | [`Result, &str>`][`ImmutableString`] |
+| `as_immutable_string_mut` | | [`Result, &str>`][`ImmutableString`] |
+| `as_array_ref` | [`no_index`] | [`Result, &str>`][array] |
+| `as_array_mut` | [`no_index`] | [`Result, &str>`][array] |
+| `as_blob_ref` | [`no_index`] | [`Result, &str>`][BLOB] |
+| `as_blob_mut` | [`no_index`] | [`Result, &str>`][BLOB] |
+| `as_map_ref` | [`no_object`] | [`Result, &str>`][object map] |
+| `as_map_mut` | [`no_object`] | [`Result, &str>`][object map] |
+| `into_string` | | `Result` |
+| `into_immutable_string` | | [`Result`][`ImmutableString`] |
+| `into_array` | [`no_index`] | [`Result`][array] |
+| `into_blob` | [`no_index`] | [`Result`][BLOB] |
+| `into_typed_array` | [`no_index`] | `Result, &str>` |
+
+### Constructor traits
+
+The following constructor traits are implemented for [`Dynamic`] where `T: Clone`:
+
+| Trait | Not available under | Data type |
+| ------------------------------------------------------------------------------ | :----------------------------: | :-----------------------: |
+| `From<()>` | | `()` |
+| `From` | | integer number |
+| `From` | [`no_float`] | floating-point number |
+| `From` | non-[`decimal`] | [`Decimal`][rust_decimal] |
+| `From` | | `bool` |
+| `From>` e.g. `From`, `From<&str>` | | [`ImmutableString`] |
+| `From` | | [character] |
+| `From>` | [`no_index`] | [array] |
+| `From<&[T]>` | [`no_index`] | [array] |
+| `From, T>>` e.g. `From>` | [`no_object`] | [object map] |
+| `From>>` e.g. `From>` | [`no_object`] | [object map] |
+| `From, T>>` e.g. `From>` | [`no_object`] or [`no_std`] | [object map] |
+| `From>>` e.g. `From>` | [`no_object`] or [`no_std`] | [object map] |
+| `From` | | [function pointer] |
+| `From` | [`no_time`] or [`no_std`] | [timestamp] |
+| `From>>` | [`sync`] or [`no_closure`] | [`Dynamic`] |
+| `From>>` ([`sync`]) | non-[`sync`] or [`no_closure`] | [`Dynamic`] |
+| `FromIterator>` | [`no_index`] | [array] |
diff --git a/rhai_engine/rhaibook/language/dynamic-tag.md b/rhai_engine/rhaibook/language/dynamic-tag.md
new file mode 100644
index 0000000..276cbe8
--- /dev/null
+++ b/rhai_engine/rhaibook/language/dynamic-tag.md
@@ -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 – 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].
+```
diff --git a/rhai_engine/rhaibook/language/dynamic.md b/rhai_engine/rhaibook/language/dynamic.md
new file mode 100644
index 0000000..032a62b
--- /dev/null
+++ b/rhai_engine/rhaibook/language/dynamic.md
@@ -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` | [`()`] |
diff --git a/rhai_engine/rhaibook/language/eval.md b/rhai_engine/rhaibook/language/eval.md
new file mode 100644
index 0000000..b79245a
--- /dev/null
+++ b/rhai_engine/rhaibook/language/eval.md
@@ -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)_
+```
diff --git a/rhai_engine/rhaibook/language/fn-anon.md b/rhai_engine/rhaibook/language/fn-anon.md
new file mode 100644
index 0000000..c40bffd
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-anon.md
@@ -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] –
+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
+};
+```
diff --git a/rhai_engine/rhaibook/language/fn-closure.md b/rhai_engine/rhaibook/language/fn-closure.md
new file mode 100644
index 0000000..d29b3d0
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-closure.md
@@ -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 – 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>`, or `Arc>` 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 – 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 –
+ 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 – 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 – 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.
+```
diff --git a/rhai_engine/rhaibook/language/fn-curry.md b/rhai_engine/rhaibook/language/fn-curry.md
new file mode 100644
index 0000000..348fd10
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-curry.md
@@ -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
+```
diff --git a/rhai_engine/rhaibook/language/fn-metadata.md b/rhai_engine/rhaibook/language/fn-metadata.md
new file mode 100644
index 0000000..5ea9ec9
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-metadata.md
@@ -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, `"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 |
diff --git a/rhai_engine/rhaibook/language/fn-method.md b/rhai_engine/rhaibook/language/fn-method.md
new file mode 100644
index 0000000..c9635ae
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-method.md
@@ -0,0 +1,197 @@
+`this` – 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;
+```
diff --git a/rhai_engine/rhaibook/language/fn-namespaces.md b/rhai_engine/rhaibook/language/fn-namespaces.md
new file mode 100644
index 0000000..f76f78f
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-namespaces.md
@@ -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 | [`AST`] being evaluated `Engine::register_XXX` API global registered [modules] [functions] in [imported][`import`] [modules] marked _global_ [functions] in registered static [modules] marked _global_ | simple name | ignored | ignored |
+| Module | many | [Module] registered via `Engine::register_static_module` [Module] loaded via [`import`] statement | 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 – 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] –
+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();
+```
diff --git a/rhai_engine/rhaibook/language/fn-parent-scope.md b/rhai_engine/rhaibook/language/fn-parent-scope.md
new file mode 100644
index 0000000..ba8f00b
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-parent-scope.md
@@ -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 – i.e. the [`Scope`] that makes the [function] call – 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**.
+```
diff --git a/rhai_engine/rhaibook/language/fn-ptr.md b/rhai_engine/rhaibook/language/fn-ptr.md
new file mode 100644
index 0000000..469efd6
--- /dev/null
+++ b/rhai_engine/rhaibook/language/fn-ptr.md
@@ -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>
+{
+ // 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::(&ast)?;
+
+// 'f' captures: the Engine, the AST, and the closure
+let f = move |x: i64| -> Result {
+ 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> + '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.
diff --git a/rhai_engine/rhaibook/language/for.md b/rhai_engine/rhaibook/language/for.md
new file mode 100644
index 0000000..b1bc31f
--- /dev/null
+++ b/rhai_engine/rhaibook/language/for.md
@@ -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
+}
+```
diff --git a/rhai_engine/rhaibook/language/functions.md b/rhai_engine/rhaibook/language/functions.md
new file mode 100644
index 0000000..93cd871
--- /dev/null
+++ b/rhai_engine/rhaibook/language/functions.md
@@ -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.
+```
diff --git a/rhai_engine/rhaibook/language/global.md b/rhai_engine/rhaibook/language/global.md
new file mode 100644
index 0000000..cc44ffa
--- /dev/null
+++ b/rhai_engine/rhaibook/language/global.md
@@ -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'
+}
+```
diff --git a/rhai_engine/rhaibook/language/if.md b/rhai_engine/rhaibook/language/if.md
new file mode 100644
index 0000000..9fbf338
--- /dev/null
+++ b/rhai_engine/rhaibook/language/if.md
@@ -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
+}
+```
+~~~
diff --git a/rhai_engine/rhaibook/language/in.md b/rhai_engine/rhaibook/language/in.md
new file mode 100644
index 0000000..65c0304
--- /dev/null
+++ b/rhai_engine/rhaibook/language/in.md
@@ -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_ – i.e. whether a particular collection
+data type _contains_ a particular item.
+
+Similarly, `!in` is used to check for non-existence – 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::()
+ .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
+"#)?;
+```
diff --git a/rhai_engine/rhaibook/language/iter.md b/rhai_engine/rhaibook/language/iter.md
new file mode 100644
index 0000000..1c3eb61
--- /dev/null
+++ b/rhai_engine/rhaibook/language/iter.md
@@ -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);
+}
+```
diff --git a/rhai_engine/rhaibook/language/iterator.md b/rhai_engine/rhaibook/language/iterator.md
new file mode 100644
index 0000000..6f5f2bd
--- /dev/null
+++ b/rhai_engine/rhaibook/language/iterator.md
@@ -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` 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 }
+
+// Implement 'IntoIterator' trait
+impl IntoIterator- for TestStruct {
+ type Item = i64;
+ type IntoIter = std::vec::IntoIter
;
+
+ 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")
+ .register_fn("new_ts", || TestStruct { fields: vec![1, 2, 3, 42] })
+ .register_iterator::();
+
+// '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 –
+i.e. an iterator that returns `Result>`.
+
+On in very rare situations will this be necessary though.
+```
diff --git a/rhai_engine/rhaibook/language/json.md b/rhai_engine/rhaibook/language/json.md
new file mode 100644
index 0000000..4f077b4
--- /dev/null
+++ b/rhai_engine/rhaibook/language/json.md
@@ -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::(&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 – 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 – 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.
diff --git a/rhai_engine/rhaibook/language/keywords.md b/rhai_engine/rhaibook/language/keywords.md
new file mode 100644
index 0000000..5a8d4cd
--- /dev/null
+++ b/rhai_engine/rhaibook/language/keywords.md
@@ -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].
+```
diff --git a/rhai_engine/rhaibook/language/logic.md b/rhai_engine/rhaibook/language/logic.md
new file mode 100644
index 0000000..7c8cbf7
--- /dev/null
+++ b/rhai_engine/rhaibook/language/logic.md
@@ -0,0 +1,234 @@
+Logic Operators
+===============
+
+{{#include ../links.md}}
+
+
+Comparison Operators
+--------------------
+
+| Operator | Description (`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`) – 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 |
+| \|\|
| _OR_ | binary | **yes** |
+| \|
| _OR_ | binary | no |
+
+Double boolean operators `&&` and `||` _short-circuit_ – 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_ – 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;
+}
+```
diff --git a/rhai_engine/rhaibook/language/loop.md b/rhai_engine/rhaibook/language/loop.md
new file mode 100644
index 0000000..9238c7a
--- /dev/null
+++ b/rhai_engine/rhaibook/language/loop.md
@@ -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}!`);
+}
+```
diff --git a/rhai_engine/rhaibook/language/modules/export.md b/rhai_engine/rhaibook/language/modules/export.md
new file mode 100644
index 0000000..1326601
--- /dev/null
+++ b/rhai_engine/rhaibook/language/modules/export.md
@@ -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
+}
+```
+~~~
diff --git a/rhai_engine/rhaibook/language/modules/import.md b/rhai_engine/rhaibook/language/modules/import.md
new file mode 100644
index 0000000..c0ed468
--- /dev/null
+++ b/rhai_engine/rhaibook/language/modules/import.md
@@ -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_ – 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();
+```
+~~~
diff --git a/rhai_engine/rhaibook/language/modules/index.md b/rhai_engine/rhaibook/language/modules/index.md
new file mode 100644
index 0000000..a9b4ac7
--- /dev/null
+++ b/rhai_engine/rhaibook/language/modules/index.md
@@ -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.
diff --git a/rhai_engine/rhaibook/language/num-fn.md b/rhai_engine/rhaibook/language/num-fn.md
new file mode 100644
index 0000000..7c2dfac
--- /dev/null
+++ b/rhai_engine/rhaibook/language/num-fn.md
@@ -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, +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 – 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 π |
+| `E` | returns the value of _e_ |
+
+
+Numerical Functions for Scientific Computing
+--------------------------------------------
+
+Check out the [`rhai-sci`] crate for more numerical functions.
diff --git a/rhai_engine/rhaibook/language/num-op.md b/rhai_engine/rhaibook/language/num-op.md
new file mode 100644
index 0000000..30a0f12
--- /dev/null
+++ b/rhai_engine/rhaibook/language/num-op.md
@@ -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** |
+| \|
, \|=
| 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)
+```
diff --git a/rhai_engine/rhaibook/language/numbers.md b/rhai_engine/rhaibook/language/numbers.md
new file mode 100644
index 0000000..ba08384
--- /dev/null
+++ b/rhai_engine/rhaibook/language/numbers.md
@@ -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 – 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 – 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::("type_of(42)")? == "i64";
+
+// false - i32 is never equal to i64
+engine.eval_with_scope::(&mut scope, "r == 42")?;
+
+// false - u8 is never equal to i64
+engine.eval_with_scope::(&mut scope, "x == 42")?;
+
+// true - i64 is equal to i64
+engine.eval_with_scope::(&mut scope, "y == 42")?;
+
+// true - INT is i64
+engine.eval_with_scope::(&mut scope, "z == 42")?;
+
+// false - f32 is never equal to f64
+engine.eval_with_scope::(&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
+– use [`parse_decimal`] or [`to_decimal`] to create a [`Decimal`][rust_decimal] value.
diff --git a/rhai_engine/rhaibook/language/object-maps-missing-prop.md b/rhai_engine/rhaibook/language/object-maps-missing-prop.md
new file mode 100644
index 0000000..740288c
--- /dev/null
+++ b/rhai_engine/rhaibook/language/object-maps-missing-prop.md
@@ -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>
+> ```
+
+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`] 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()),
+ }
+});
+```
diff --git a/rhai_engine/rhaibook/language/object-maps-oop.md b/rhai_engine/rhaibook/language/object-maps-oop.md
new file mode 100644
index 0000000..0284d09
--- /dev/null
+++ b/rhai_engine/rhaibook/language/object-maps-oop.md
@@ -0,0 +1,43 @@
+Special Support for OOP via Object Maps
+=======================================
+
+{{#include ../links.md}}
+
+```admonish info.side "See also"
+
+See the pattern on [_Simulating Object-Oriented Programming_][OOP] for more details.
+```
+
+[Object maps] can be used to simulate [object-oriented programming (OOP)][OOP] by storing data
+as properties and methods as properties holding [function pointers].
+
+If an [object map]'s property holds a [function pointer], the property can simply be called like
+a normal method in method-call syntax.
+
+This is a _short-hand_ to avoid the more verbose syntax of using the `call` function keyword.
+
+When a property holding a [function pointer] or a [closure] is called like a method, it is replaced
+as a method call on the [object map] itself.
+
+```rust
+let obj = #{
+ data: 40,
+ action: || this.data += x // 'action' holds a closure
+ };
+
+obj.action(2); // calls the function pointer with 'this' bound to 'obj'
+
+obj.call(obj.action, 2); // <- the above de-sugars to this
+
+obj.data == 42;
+
+// To achieve the above with normal function pointer call will fail.
+
+fn do_action(map, x) { map.data += x; } // 'map' is a copy
+
+obj.action = do_action; // <- de-sugars to 'Fn("do_action")'
+
+obj.action.call(obj, 2); // a copy of 'obj' is passed by value
+
+obj.data == 42; // 'obj.data' is not changed
+```
diff --git a/rhai_engine/rhaibook/language/object-maps.md b/rhai_engine/rhaibook/language/object-maps.md
new file mode 100644
index 0000000..918a634
--- /dev/null
+++ b/rhai_engine/rhaibook/language/object-maps.md
@@ -0,0 +1,309 @@
+Object Maps
+===========
+
+{{#include ../links.md}}
+
+```admonish tip.side "Safety"
+
+Always limit the [maximum size of object maps].
+```
+
+Object maps are hash dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved.
+
+The Rust type of a Rhai object map is `rhai::Map`.
+Currently it is an alias to `BTreeMap`.
+
+[`type_of()`] an object map returns `"map"`.
+
+Object maps are disabled via the [`no_object`] feature.
+
+~~~admonish tip "Tip: Object maps are _FAST_"
+
+Normally, when [properties][getters/setters] are accessed, copies of the data values are made.
+This is normally slow.
+
+Object maps have special treatment – properties are accessed via _references_, meaning that
+no copies of data values are made.
+
+This makes object map access fast, especially when deep within a properties chain.
+
+```rust
+// 'obj' is a normal custom type
+let x = obj.a.b.c.d;
+
+// The above is equivalent to:
+let a_value = obj.a; // temp copy of 'a'
+let b_value = a_value.b; // temp copy of 'b'
+let c_value = b_value.c; // temp copy of 'c'
+let d_value = c_value.d; // temp copy of 'd'
+let x = d_value;
+
+// 'map' is an object map
+let x = map.a.b.c.d; // direct access to 'd'
+ // 'a', 'b' and 'c' are not copied
+
+map.a.b.c.d = 42; // directly modifies 'd' in 'a', 'b' and 'c'
+ // no copy of any property value is made
+
+map.a.b.c.d.calc(); // directly calls 'calc' on 'd'
+ // no copy of any property value is made
+```
+~~~
+
+~~~admonish question.small "TL;DR: Why `SmartString`?"
+
+[`SmartString`] is used because most object map properties are short (at least shorter than 23 characters)
+and ASCII-based, so they can usually be stored inline without incurring the cost of an allocation.
+~~~
+
+~~~admonish question.small "TL;DR: Why `BTreeMap` and not `HashMap`?"
+
+The vast majority of object maps contain just a few properties.
+
+`BTreeMap` performs significantly better than `HashMap` when the number of entries is small.
+~~~
+
+
+Literal Syntax
+--------------
+
+Object map literals are built within braces `#{` ... `}` with _name_`:`_value_ pairs separated by
+commas `,`:
+
+> `#{` _property_ `:` _value_`,` ... `,` _property_ `:` _value_ `}`
+>
+> `#{` _property_ `:` _value_`,` ... `,` _property_ `:` _value_ `,` `}` `// trailing comma is OK`
+
+The property _name_ can be a simple identifier following the same naming rules as [variables],
+or a [string literal][literals] without interpolation.
+
+
+Property Access Syntax
+----------------------
+
+### Dot notation
+
+The _dot notation_ allows only property names that follow the same naming rules as [variables].
+
+> _object_ `.` _property_
+
+### Elvis notation
+
+The [_Elvis notation_][elvis] is similar to the _dot notation_ except that it returns [`()`] if the object
+itself is [`()`].
+
+> `// returns () if object is ()`
+> _object_ `?.` _property_
+>
+> `// no action if object is ()`
+> _object_ `?.` _property_ `=` _value_ `;`
+
+### Index notation
+
+The _index notation_ allows setting/getting properties of arbitrary names (even the empty [string]).
+
+> _object_ `[` _property_ `]`
+
+
+Handle Non-Existent Properties
+------------------------------
+
+Trying to read a non-existent property returns [`()`] instead of causing an error.
+
+This is similar to JavaScript where accessing a non-existent property returns `undefined`.
+
+```rust
+let map = #{ foo: 42 };
+
+// Regular property access
+let x = map.foo; // x == 42
+
+// Non-existent property
+let x = map.bar; // x == ()
+```
+
+```admonish tip.small "Tip: Force error"
+
+It is possible to force Rhai to return an `EvalAltResult:: ErrorPropertyNotFound` via
+[`Engine:: set_fail_on_invalid_map_property`][options].
+```
+
+```admonish tip.small "Advanced tip: Override standard behavior"
+
+For fine-tuned control on what happens when a non-existent property is accessed,
+see [_Non-Existent Property Handling for Object Maps_](object-maps-missing-prop.md).
+```
+
+### Check for property existence
+
+Use the [`in`] operator to check whether a property exists in an object-map.
+
+```rust
+let map = #{ foo: 42 };
+
+"foo" in map == true;
+
+"bar" in map == false;
+```
+
+### Short-circuit non-existent property access
+
+Use the [_Elvis operator_][elvis] (`?.`) to short-circuit further processing if the object is [`()`].
+
+```rust
+x.a.b.foo(); // <- error if 'x', 'x.a' or 'x.a.b' is ()
+
+x.a.b = 42; // <- error if 'x' or 'x.a' is ()
+
+x?.a?.b?.foo(); // <- ok! returns () if 'x', 'x.a' or 'x.a.b' is ()
+
+x?.a?.b = 42; // <- ok even if 'x' or 'x.a' is ()
+```
+
+### Default property value
+
+Using the [null-coalescing operator](logic.md#null-coalescing-operator) to give non-existent
+properties default values.
+
+```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
+```
+
+
+Built-in Functions
+------------------
+
+The following methods (defined in the [`BasicMapPackage`][built-in packages] but excluded when using
+a [raw `Engine`]) operate on object maps.
+
+| Function | Parameter(s) | Description |
+| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `get` | property name | gets a copy of the value of a certain property ([`()`] if the property does not exist); behavior is not affected by [`Engine::fail_on_invalid_map_property`][options] |
+| `set` | property name new element | sets a certain property to a new value (property is added if not already exists) |
+| `len` | _none_ | returns the number of properties |
+| `is_empty` | _none_ | returns `true` if the object map is empty |
+| `clear` | _none_ | empties the object map |
+| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
+| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
+| `+` operator | first object map second object map | merges the first object map with the second |
+| `==` operator | first object map second object map | are the two object maps the same (elements compared with the `==` operator, if defined)? |
+| `!=` operator | first object map second object map | are the two object maps different (elements compared with the `==` operator, if defined)? |
+| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
+| `contains`, [`in`] operator | property name | does the object map contain a property of a particular name? |
+| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
+| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
+| `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:key _(optional)_ object map element (if omitted, the object map element is bound to `this`) |
+| `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:key _(optional)_ object map element (if omitted, the object map element is bound to `this`) |
+| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a object map with all elements that return `true` when called with the predicate function taking the following parameters:key _(optional)_ object map element (if omitted, the object map element is bound to `this`) |
+| `to_json` | _none_ | returns a JSON representation of the object map ([`()`] is mapped to `null`, all other data types must be supported by JSON) |
+
+
+Examples
+--------
+
+```rust
+let y = #{ // object map literal with 3 properties
+ a: 1,
+ bar: "hello",
+ "baz!$@": 123.456, // like JavaScript, you can use any string as property names...
+ "": false, // even the empty string!
+
+ `hello`: 999, // literal strings are also OK
+
+ a: 42, // <- syntax error: duplicated property name
+
+ `a${2}`: 42, // <- syntax error: property name cannot have string interpolation
+};
+
+y.a = 42; // access via dot notation
+y.a == 42;
+
+y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation
+y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation
+y["baz!$@"] = 42; // access via index notation is OK
+
+"baz!$@" in y == true; // use 'in' to test if a property exists in the object map
+("z" in y) == false;
+
+ts.obj = y; // object maps can be assigned completely (by value copy)
+let foo = ts.list.a;
+foo == 42;
+
+let foo = #{ a:1, }; // trailing comma is OK
+
+let foo = #{ a:1, b:2, c:3 }["a"];
+let foo = #{ a:1, b:2, c:3 }.a;
+foo == 1;
+
+fn abc() {
+ #{ a:1, b:2, c:3 } // a function returning an object map
+}
+
+let foo = abc().b;
+foo == 2;
+
+let foo = y["a"];
+foo == 42;
+
+y.contains("a") == true;
+y.contains("xyz") == false;
+
+y.xyz == (); // a non-existent property returns '()'
+y["xyz"] == ();
+
+y.len == (); // an object map has no property getter function
+y.len() == 3; // method calls are OK
+
+y.remove("a") == 1; // remove property
+
+y.len() == 2;
+y.contains("a") == false;
+
+for name in y.keys() { // get an array of all the property names via 'keys'
+ print(name);
+}
+
+for val in y.values() { // get an array of all the property values via 'values'
+ print(val);
+}
+
+y.clear(); // empty the object map
+
+y.len() == 0;
+```
+
+
+No Support for Property Getters
+-------------------------------
+
+In order not to affect the speed of accessing properties in an object map, new
+[property getters][getters/setters] cannot be registered because they conflict with the syntax of
+property access.
+
+A [property getter][getters/setters] function registered via `Engine::register_get`, for example,
+for a `Map` will never be found – instead, the property will be looked up in the object map.
+
+Properties should be registered as _methods_ instead:
+
+```rust
+map.len // access property 'len', returns '()' if not found
+
+map.len() // 'len' method - returns the number of properties
+
+map.keys // access property 'keys', returns '()' if not found
+
+map.keys() // 'keys' method - returns array of all property names
+
+map.values // access property 'values', returns '()' if not found
+
+map.values() // 'values' method - returns array of all property values
+```
diff --git a/rhai_engine/rhaibook/language/overload.md b/rhai_engine/rhaibook/language/overload.md
new file mode 100644
index 0000000..a9d8ba0
--- /dev/null
+++ b/rhai_engine/rhaibook/language/overload.md
@@ -0,0 +1,36 @@
+Function Overloading
+====================
+
+{{#include ../links.md}}
+
+[Functions] defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the
+function's _name_ and _number_ of parameters, but not parameter _types_ since all parameters are the
+same type – [`Dynamic`]).
+
+New definitions _overwrite_ previous definitions of the same name and number of parameters.
+
+```js
+fn foo(x, y, z) {
+ print(`Three!!! ${x}, ${y}, ${z}`);
+}
+fn foo(x) {
+ print(`One! ${x}`);
+}
+fn foo(x, y) {
+ print(`Two! ${x}, ${y}`);
+}
+fn foo() {
+ print("None.");
+}
+fn foo(x) { // <- overwrites previous definition
+ print(`HA! NEW ONE! ${x}`);
+}
+
+foo(1,2,3); // prints "Three!!! 1,2,3"
+
+foo(42); // prints "HA! NEW ONE! 42"
+
+foo(1,2); // prints "Two!! 1,2"
+
+foo(); // prints "None."
+```
diff --git a/rhai_engine/rhaibook/language/print-debug.md b/rhai_engine/rhaibook/language/print-debug.md
new file mode 100644
index 0000000..5f05cdc
--- /dev/null
+++ b/rhai_engine/rhaibook/language/print-debug.md
@@ -0,0 +1,92 @@
+`print` and `debug`
+===================
+
+{{#include ../links.md}}
+
+[`Engine::on_print`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_print
+[`Engine::on_debug`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_debug
+
+
+The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
+
+```js
+print("hello"); // prints "hello" to stdout
+
+print(1 + 2 + 3); // prints "6" to stdout
+
+let x = 42;
+
+print(`hello${x}`); // prints "hello42" to stdout
+
+debug("world!"); // prints "world!" to stdout using debug formatting
+```
+
+
+Override `print` and `debug` with Callback Functions
+----------------------------------------------------
+
+When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
+(for logging into a tracking log, for example) with the [`Engine::on_print`] and [`Engine::on_debug`] methods.
+
+```rust
+// Any function or closure that takes an '&str' argument can be used to override 'print'.
+engine.on_print(|x| println!("hello: {x}"));
+
+// Any function or closure that takes a '&str', an 'Option<&str>' and a 'Position' argument
+// can be used to override 'debug'.
+engine.on_debug(|x, src, pos| {
+ let src = src.unwrap_or("unknown");
+ println!("DEBUG of {src} at {pos:?}: {s}")
+});
+
+// Example: quick-'n-dirty logging
+let logbook = Arc::new(RwLock::new(Vec::::new()));
+
+// Redirect print/debug output to 'log'
+let log = logbook.clone();
+engine.on_print(move |s| {
+ let entry = format!("entry: {}", s);
+ log.write().unwrap().push(entry);
+});
+
+let log = logbook.clone();
+engine.on_debug(move |s, src, pos| {
+ let src = src.unwrap_or("unknown");
+ let entry = format!("DEBUG of {src} at {pos:?}: {s}");
+ log.write().unwrap().push(entry);
+});
+
+// Evaluate script
+engine.run(script)?;
+
+// 'logbook' captures all the 'print' and 'debug' output
+for entry in logbook.read().unwrap().iter() {
+ println!("{entry}");
+}
+```
+
+
+`on_debug` Callback Signature
+-----------------------------
+
+The function signature passed to [`Engine::on_debug]` takes the following form.
+
+> ```rust
+> Fn(text: &str, source: Option<&str>, pos: Position)
+> ```
+
+where:
+
+| Parameter | Type | Description |
+| --------- | :------------: | --------------------------------------------------------------- |
+| `text` | `&str` | text to display |
+| `source` | `Option<&str>` | source of the current evaluation, if any |
+| `pos` | `Position` | position (line number and character offset) of the `debug` call |
+
+The _source_ of a script evaluation is any text string provided to an [`AST`] via `AST::set_source`.
+
+```admonish tip.small
+
+If a [module] is loaded via an [`import`] statement, then the _source_ of functions defined within
+the module will be the module's _path_.
+```
diff --git a/rhai_engine/rhaibook/language/ranges.md b/rhai_engine/rhaibook/language/ranges.md
new file mode 100644
index 0000000..191a703
--- /dev/null
+++ b/rhai_engine/rhaibook/language/ranges.md
@@ -0,0 +1,205 @@
+Ranges
+======
+
+{{#include ../links.md}}
+
+
+Syntax
+------
+
+Numeric ranges can be constructed by the `..` (exclusive) or `..=` (inclusive) operators.
+
+### Exclusive range
+
+> _start_ `..` _end_
+
+An _exclusive_ range does not include the last (i.e. "end") value.
+
+The Rust type of an exclusive range is `std::ops::Range`.
+
+[`type_of()`] an exclusive range returns `"range"`.
+
+### Inclusive range
+
+> _start_ `..=` _end_
+
+An _inclusive_ range includes the last (i.e. "end") value.
+
+The Rust type of an inclusive range is `std::ops::RangeInclusive`.
+
+[`type_of()`] an inclusive range returns `"range="`.
+
+
+Usage Scenarios
+---------------
+
+Ranges are commonly used in the following scenarios.
+
+| Scenario | Example |
+| -------------------------- | --------------------------------------- |
+| [`for`] statements | `for n in 0..100 { ... }` |
+| [`in`] expressions | `if n in 0..100 { ... }` |
+| [`switch`] expressions | `switch n { 0..100 => ... }` |
+| [Bit-fields] access | `let x = n[2..6];` |
+| Bits iteration | `for bit in n.bits(2..=9) { ... }` |
+| [Array] range-based APIs | `array.extract(2..8)` |
+| [BLOB] range-based APIs | `blob.parse_le_int(4..8)` |
+| [String] range-based APIs | `string.sub_string(4..=12)` |
+| [Characters] iteration | `for ch in string.bits(4..=12) { ... }` |
+| [Custom types] | `my_obj.action(3..=15, "foo");` |
+
+
+Use as Parameter Type
+---------------------
+
+Native Rust functions that take parameters of type `std::ops::Range` or
+`std::ops::RangeInclusive`, when registered into an [`Engine`], accept ranges as arguments.
+
+```admonish warning.small "Different types"
+
+`..` (exclusive range) and `..=` (inclusive range) are _different_ types to Rhai
+and they do not interoperate.
+
+Two different versions of the same API must be registered to handle both range styles.
+```
+
+```rust
+use std::ops::{Range, RangeInclusive};
+
+/// The actual work function
+fn do_work(obj: &mut TestStruct, from: i64, to: i64, inclusive: bool) {
+ ...
+}
+
+let mut engine = Engine::new();
+
+engine
+ /// Version of API that accepts an exclusive range
+ .register_fn("do_work", |obj: &mut TestStruct, range: Range|
+ do_work(obj, range.start, range.end, false)
+ )
+ /// Version of API that accepts an inclusive range
+ .register_fn("do_work", |obj: &mut TestStruct, range: RangeInclusive|
+ do_work(obj, range.start(), range.end(), true)
+ );
+
+engine.run(
+"
+ let obj = new_ts();
+
+ obj.do_work(0..12); // use exclusive range
+
+ obj.do_work(0..=11); // use inclusive range
+")?;
+```
+
+### Indexers Using Ranges
+
+[Indexers] commonly use ranges as parameters.
+
+```rust
+use std::ops::{Range, RangeInclusive};
+
+let mut engine = Engine::new();
+
+engine
+ /// Version of indexer that accepts an exclusive range
+ .register_indexer_get_set(
+ |obj: &mut TestStruct, range: Range| -> bool { ... },
+ |obj: &mut TestStruct, range: Range, value: bool| { ... },
+ )
+ /// Version of indexer that accepts an inclusive range
+ .register_indexer_get_set(
+ |obj: &mut TestStruct, range: RangeInclusive| -> bool { ... },
+ |obj: &mut TestStruct, range: RangeInclusive, value: bool| { ... },
+ );
+
+engine.run(
+"
+ let obj = new_ts();
+
+ let x = obj[0..12]; // use exclusive range
+
+ obj[0..=11] = !x; // use inclusive range
+")?;
+```
+
+Built-in Functions
+------------------
+
+The following methods (mostly defined in the [`BasicIteratorPackage`][built-in packages] but
+excluded when using a [raw `Engine`]) operate on ranges.
+
+| Function | Parameter(s) | Description |
+| ---------------------------------- | :-------------: | --------------------------------------------- |
+| `start` method and property | | beginning of the range |
+| `end` method and property | | end of the range |
+| `contains`, [`in`] operator | number to check | does this range contain the specified number? |
+| `is_empty` method and property | | returns `true` if the range contains no items |
+| `is_inclusive` method and property | | is the range inclusive? |
+| `is_exclusive` method and property | | is the range exclusive? |
+
+
+TL;DR
+-----
+
+```admonish question "What happened to the _open-ended_ ranges?"
+
+Rust has _open-ended_ ranges, such as `start..`, `..end` and `..=end`. They are not available in Rhai.
+
+They are not needed because Rhai can [overload][function overloading] functions.
+
+Typically, an API accepting ranges as parameters would have equivalent versions that accept a
+starting position and a length (the standard `start + len` pair), as well as a versions that accept
+only the starting position (the length assuming to the end).
+
+In fact, usually all versions redirect to a call to one single version.
+
+For example, a naive implementation of the `extract` method for [arrays] (without any error handling)
+would look like:
+
+~~~rust
+use std::ops::{Range, RangeInclusive};
+
+// Version with exclusive range
+#[rhai_fn(name = "extract", pure)]
+pub fn extract_range(array: &mut Array, range: Range) -> Array {
+ array[range].to_vec()
+}
+// Version with inclusive range
+#[rhai_fn(name = "extract", pure)]
+pub fn extract_range2(array: &mut Array, range: RangeInclusive) -> Array {
+ extract_range(array, range.start()..range.end() + 1)
+}
+// Version with start
+#[rhai_fn(name = "extract", pure)]
+pub fn extract_to_end(array: &mut Array, start: i64) -> Array {
+ extract_range(array, start..start + array.len())
+}
+// Version with start+len
+#[rhai_fn(name = "extract", pure)]
+pub fn extract(array: &mut Array, start: i64, len: i64) -> Array {
+ extract_range(array, start..start + len)
+}
+~~~
+
+Therefore, there should always be a function that can do what open-ended ranges are intended for.
+
+The left-open form (i.e. `..end` and `..=end`) is trivially replaced by using zero as the starting
+position with a length that corresponds to the end position (for `..end`).
+
+The right-open form (i.e. `start..`) is trivially replaced by the version taking a single starting position.
+
+~~~rust
+let x = [1, 2, 3, 4, 5];
+
+x.extract(0..3); // normal range argument
+ // copies 'x' from positions 0-2
+
+x.extract(2); // copies 'x' from position 2 onwards
+ // equivalent to '2..'
+
+x.extract(0, 2); // copies 'x' from beginning for 2 items
+ // equivalent to '..2'
+~~~
+```
diff --git a/rhai_engine/rhaibook/language/return.md b/rhai_engine/rhaibook/language/return.md
new file mode 100644
index 0000000..590d8f7
--- /dev/null
+++ b/rhai_engine/rhaibook/language/return.md
@@ -0,0 +1,50 @@
+Return Value
+============
+
+{{#include ../links.md}}
+
+`return`
+--------
+
+The `return` statement is used to immediately stop evaluation and exist the current context
+(typically a [function] call) yielding a _return value_.
+
+```rust
+return; // equivalent to return ();
+
+return 123 + 456; // returns 579
+```
+
+A `return` statement at _global_ level stops the entire script evaluation,
+the return value is taken as the result of the script evaluation.
+
+A `return` statement inside a [function call][function] exits with a return value to the caller.
+
+
+`exit`
+------
+
+Similar to the `return` statement, the `exit` _function_ is used to immediately stop evaluation,
+but it does so regardless of where it is called from, even deep inside nested function calls.
+
+```rust
+fn foo() {
+ exit(42); // exit with result 42
+}
+fn bar() {
+ foo();
+}
+fn baz() {
+ bar();
+}
+
+let x = baz(); // exits with result 42
+
+print(x); // <- this is never run
+```
+
+The `exit` function is defined in the [`LanguageCorePackage`][built-in packages] but excluded when using a [raw `Engine`].
+
+| Function | Parameter(s) | Description |
+| -------- | ------------------------- | ------------------------------------------------------------------------ |
+| `exit` | result value _(optional)_ | immediately terminate script evaluation (default result value is [`()`]) |
diff --git a/rhai_engine/rhaibook/language/shadow.md b/rhai_engine/rhaibook/language/shadow.md
new file mode 100644
index 0000000..bd7fa65
--- /dev/null
+++ b/rhai_engine/rhaibook/language/shadow.md
@@ -0,0 +1,52 @@
+Variable Shadowing
+==================
+
+{{#include ../links.md}}
+
+In Rhai, new [variables] automatically _shadow_ existing ones of the same name. There is no error.
+
+This behavior is consistent with Rust.
+
+```rust
+let x = 42;
+let y = 123;
+
+print(x); // prints 42
+
+let x = 88; // <- 'x' is shadowed here
+
+// At this point, it is no longer possible to access the
+// original 'x' on the first line...
+
+print(x); // prints 88
+
+let x = 0; // <- 'x' is shadowed again
+
+// At this point, it is no longer possible to access both
+// previously-defined 'x'...
+
+print(x); // prints 0
+
+{
+ let x = 999; // <- 'x' is shadowed in a block
+
+ print(x); // prints 999
+}
+
+print(x); // prints 0 - shadowing within the block goes away
+
+print(y); // prints 123 - 'y' is not shadowed
+```
+
+
+~~~admonish tip "Tip: Disable shadowing"
+
+Set [`Engine::set_allow_shadowing`][options] to `false` to turn [variables] shadowing off.
+
+```rust
+let x = 42;
+
+let x = 123; // <- syntax error: variable 'x' already defined
+ // when variables shadowing is disallowed
+```
+~~~
diff --git a/rhai_engine/rhaibook/language/statement-expression.md b/rhai_engine/rhaibook/language/statement-expression.md
new file mode 100644
index 0000000..6b928d3
--- /dev/null
+++ b/rhai_engine/rhaibook/language/statement-expression.md
@@ -0,0 +1,58 @@
+Statement Expression
+====================
+
+{{#include ../links.md}}
+
+```admonish warning.side "Differs from Rust"
+
+This is different from Rust where, if the last statement is terminated by a semicolon, the block's
+return value defaults to `()`.
+```
+
+Like Rust, a statement can be used anywhere where an _expression_ is expected.
+
+These are called, for lack of a more creative name, "statement expressions."
+
+The _last_ statement of a statements block is _always_ the block's return value when used as a statement,
+_regardless_ of whether it is terminated by a semicolon or not.
+
+If the last statement has no return value (e.g. variable definitions, assignments) then it is
+assumed to be [`()`].
+
+```rust
+let x = {
+ let foo = calc_something();
+ let bar = foo + baz;
+ bar.further_processing(); // <- this is the return value
+}; // <- semicolon is needed here...
+
+// The above is equivalent to:
+let result;
+{
+ let foo = calc_something();
+ let bar = foo + baz;
+ result = bar.further_processing();
+}
+let x = result;
+
+// Statement expressions can be inserted inside normal expressions
+// to avoid duplicated calculations
+let x = foo(bar) + { let v = calc(); process(v, v.len, v.abs) } + baz;
+
+// The above is equivalent to:
+let foo_result = foo(bar);
+let calc_result;
+{
+ let v = calc();
+ result = process(v, v.len, v.abs); // <- avoid calculating 'v'
+}
+let x = foo_result + calc_result + baz;
+
+// Statement expressions are also useful as function call arguments
+// when side effects are desired
+do_work(x, y, { let z = foo(x, y); print(z); z });
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ // statement expression
+```
+
+Statement expressions can be disabled via [`Engine::set_allow_statement_expression`][options].
diff --git a/rhai_engine/rhaibook/language/statements.md b/rhai_engine/rhaibook/language/statements.md
new file mode 100644
index 0000000..77703d3
--- /dev/null
+++ b/rhai_engine/rhaibook/language/statements.md
@@ -0,0 +1,73 @@
+Statements
+==========
+
+{{#include ../links.md}}
+
+
+Statements are terminated by semicolons `;` and they are mandatory,
+except for the _last_ statement in a _block_ (enclosed by `{` ... `}` pairs) where it can be omitted.
+
+Semicolons can also be omitted for statement types that always end in a block – for example
+the [`if`], [`while`], [`for`], [`loop`] and [`switch`] statements.
+
+```rust
+let a = 42; // normal assignment statement
+let a = foo(42); // normal function call statement
+foo < 42; // normal expression as statement
+
+let a = { 40 + 2 }; // 'a' is set to the value of the statements block, which is the value of the last statement
+// ^ the last statement does not require a terminating semicolon (but also works with it)
+// ^ semicolon required here to terminate the 'let' statement
+// it is a syntax error without it, even though it ends with '}'
+// that is because the 'let' statement doesn't end in a block
+
+if foo { a = 42 }
+// ^ no need to terminate an if-statement with a semicolon
+// that is because the 'if' statement ends in a block
+
+4 * 10 + 2 // a statement which is just one expression - no ending semicolon is OK
+ // because it is the last statement of the whole block
+```
+
+
+Statements Block
+----------------
+
+### Syntax
+
+Statements blocks in Rhai are formed by enclosing zero or more statements within braces `{`...`}`.
+
+> `{` _statement_`;` _statement_`;` ... _statement_ `}`
+>
+> `{` _statement_`;` _statement_`;` ... _statement_`;` `}` `// trailing semi-colon is optional`
+
+### Closed scope
+
+A statements block forms a _closed_ scope.
+
+Any [variable] and/or [constant] defined within the block are removed outside the block, so are
+[modules] [imported][`import`] within the block.
+
+```rust
+let x = 42;
+let y = 18;
+
+{
+ import "hello" as h;
+ const HELLO = 99;
+ let y = 0;
+
+ h::greet(); // ok
+
+ print(y + HELLO); // prints 99 (y is zero)
+
+ :
+ :
+} // <- 'HELLO' and 'y' go away here...
+
+print(x + y); // prints 60 (y is still 18)
+
+print(HELLO); // <- error: 'HELLO' not found
+
+h::greet(); // <- error: module 'h' not found
+```
diff --git a/rhai_engine/rhaibook/language/string-fn.md b/rhai_engine/rhaibook/language/string-fn.md
new file mode 100644
index 0000000..b292415
--- /dev/null
+++ b/rhai_engine/rhaibook/language/string-fn.md
@@ -0,0 +1,147 @@
+Standard String Functions
+=========================
+
+{{#include ../links.md}}
+
+
+The following standard methods (mostly defined in the [`MoreStringPackage`][built-in packages] but
+excluded when using a [raw `Engine`]) operate on [strings] (and possibly characters).
+
+| Function | Parameter(s) | Description |
+| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `len` method and property | _none_ | returns the number of characters (**not** number of bytes) in the string |
+| `bytes` method and property | _none_ | returns the number of bytes making up the UTF-8 string; for strings containing only ASCII characters, this is much faster than `len` |
+| `is_empty` method and property | _none_ | returns `true` if the string is empty |
+| `to_blob` (not available under [`no_index`]) | _none_ | converts the string into an UTF-8 encoded byte-stream and returns it as a [BLOB]. |
+| `to_chars` (not available under [`no_index`]) | _none_ | splits the string by individual characters, returning them as an [array] |
+| `get` | position, counting from end if < 0 | gets the character at a certain position ([`()`] if the position is not valid) |
+| `set` | position, counting from end if < 0 new character | sets a certain position to a new character (no effect if the position is not valid) |
+| `pad` | target length character/string to pad | pads the string with a character or a string to at least a specified length |
+| `append`, `+=` operator | item to append | adds the display text of an item to the end of the string |
+| `remove` | character/string to remove | removes a character or a string from the string |
+| `pop` | _(optional)_ number of characters to remove, none if ≤ 0, entire string if ≥ length | removes the last character (if no parameter) and returns it ([`()`] if empty); otherwise, removes the last number of characters and returns them as a string |
+| `clear` | _none_ | empties the string |
+| `truncate` | target length | cuts off the string at exactly a specified number of characters |
+| `to_upper` | _none_ | converts the string/character into upper-case as a new string/character and returns it |
+| `to_lower` | _none_ | converts the string/character into lower-case as a new string/character and returns it |
+| `make_upper` | _none_ | converts the string/character into upper-case |
+| `make_lower` | _none_ | converts the string/character into lower-case |
+| `trim` | _none_ | trims the string of whitespace at the beginning and end |
+| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
+| `starts_with` | string | returns `true` if the string starts with a certain string |
+| `ends_with` | string | returns `true` if the string ends with a certain string |
+| `min` | first character/string second character/string | returns the smaller of two characters/strings |
+| `max` | first character/string second character/string | returns the larger of two characters/strings |
+| `index_of` | character/sub-string to search for _(optional)_ start position, counting from end if < 0, end if ≥ length | returns the position that a certain character or sub-string occurs in the string, or −1 if not found |
+| `sub_string` | start position, counting from end if < 0 _(optional)_ number of characters to extract, none if ≤ 0, to end if omitted | extracts a sub-string |
+| `sub_string` | [range] of characters to extract, from beginning if ≤ 0, to end if ≥ length | extracts a sub-string |
+| `split` (not available under [`no_index`]) | _none_ | splits the string by whitespaces, returning an [array] of string segments |
+| `split` (not available under [`no_index`]) | position to split at (in number of characters), counting from end if < 0, end if ≥ length | splits the string into two segments at the specified character position, returning an [array] of two string segments |
+| `split` (not available under [`no_index`]) | delimiter character/string _(optional)_ maximum number of segments, 1 if < 1 | splits the string by the specified delimiter, returning an [array] of string segments |
+| `split_rev` (not available under [`no_index`]) | delimiter character/string _(optional)_ maximum number of segments, 1 if < 1 | splits the string by the specified delimiter in reverse order, returning an [array] of string segments |
+| `crop` | start position, counting from end if < 0 _(optional)_ number of characters to retain, none if ≤ 0, to end if omitted | retains only a portion of the string |
+| `crop` | [range] of characters to retain, from beginning if ≤ 0, to end if ≥ length | retains only a portion of the string |
+| `replace` | target character/sub-string replacement character/string | replaces a sub-string with another |
+| `chars` method and property | _(optional)_ start position, counting from end if < 0 _(optional)_ number of characters to iterate, none if ≤ 0 | allows iteration of the characters inside the string |
+
+Beware that functions that involve indexing into a [string] to get at individual [characters],
+e.g. `sub_string`, require walking through the entire UTF-8 encoded bytes stream to extract
+individual Unicode characters and counting them, which can be slow for long [strings].
+
+
+Building Strings
+----------------
+
+[Strings] can be built from segments via the `+` operator.
+
+| Operator | Description |
+| ------------------ | ------------------------------------------------------------------------- |
+| [string] `+=` item | convert the item into a [string], then append it to the first [string] |
+| [string] `+` item | convert the item into a [string], then concatenate them as a new [string] |
+| item `+` [string] | convert the item into a [string], then concatenate them as a new [string] |
+
+```rust
+let x = 42;
+
+// Build string with '+'
+let s = "The answer is: " + x + "!!!";
+
+// Prints: "The answer is: 42!!!"
+print(s);
+```
+
+### Standard Operators Between Strings and/or Characters
+
+The following standard operators inter-operate between [strings] and/or [characters][strings].
+
+When one (or both) of the operands is a [character], it is first converted into a one-character
+[string] before running the operator.
+
+| Operator | Description |
+| --------- | --------------------------------------------- |
+| `+`, `+=` | [character]/[string] concatenation |
+| `-`, `-=` | remove [character]/sub-[string] from [string] |
+| `==` | equals to |
+| `!=` | not equals to |
+| `>` | greater than |
+| `>=` | greater than or equals to |
+| `<` | less than |
+| `<=` | less than or equals to |
+
+### Interop with BLOB's
+
+For convenience, when a [BLOB] is appended to a [string], or vice versa, it is treated as a UTF-8
+encoded byte stream and automatically first converted into the appropriate [string] value.
+
+That is because it is rarely useful to append a [BLOB] into a string, but extremely useful to be
+able to directly manipulate UTF-8 encoded text.
+
+| Operator | Description |
+| --------- | --------------------------------------------------------------------------- |
+| `+`, `+=` | append a [BLOB] (as a UTF-8 encoded byte stream) to the end of the [string] |
+| `+` | concatenate a [BLOB] (as a UTF-8 encoded byte stream) with a [string] |
+
+
+Examples
+--------
+
+```rust
+let full_name == " Bob C. Davis ";
+full_name.len == 14;
+
+full_name.trim();
+full_name.len == 12;
+full_name == "Bob C. Davis";
+
+full_name.pad(15, '$');
+full_name.len == 15;
+full_name == "Bob C. Davis$$$";
+
+let n = full_name.index_of('$');
+n == 12;
+
+full_name.index_of("$$", n + 1) == 13;
+
+full_name.sub_string(n, 3) == "$$$";
+full_name.sub_string(n..n+3) == "$$$";
+
+full_name.truncate(6);
+full_name.len == 6;
+full_name == "Bob C.";
+
+full_name.replace("Bob", "John");
+full_name.len == 7;
+full_name == "John C.";
+
+full_name.contains('C') == true;
+full_name.contains("John") == true;
+
+full_name.crop(5);
+full_name == "C.";
+
+full_name.crop(0, 1);
+full_name == "C";
+
+full_name.clear();
+full_name.len == 0;
+```
diff --git a/rhai_engine/rhaibook/language/string-interp.md b/rhai_engine/rhaibook/language/string-interp.md
new file mode 100644
index 0000000..3d83641
--- /dev/null
+++ b/rhai_engine/rhaibook/language/string-interp.md
@@ -0,0 +1,106 @@
+Multi-Line Literal Strings
+==========================
+
+{{#include ../links.md}}
+
+A [string] wrapped by a pair of back-tick (`` ` ``) characters is interpreted _literally_.
+
+This means that every single character that lies between the two back-ticks is taken verbatim.
+
+This include new-lines, whitespaces, escape characters etc.
+
+```js
+let x = `hello, world! "\t\x42"
+ hello world again! 'x'
+ this is the last time!!! `;
+
+// The above is the same as:
+let x = "hello, world! \"\\t\\x42\"\n hello world again! 'x'\n this is the last time!!! ";
+```
+
+If a back-tick (`` ` ``) appears at the _end_ of a line, then it is understood that the entire text
+block starts from the _next_ line; the starting new-line character is stripped.
+
+```js
+let x = `
+ hello, world! "\t\x42"
+ hello world again! 'x'
+ this is the last time!!!
+`;
+
+// The above is the same as:
+let x = " hello, world! \"\\t\\x42\"\n hello world again! 'x'\n this is the last time!!!\n";
+```
+
+To actually put a back-tick (`` ` ``) character inside a multi-line literal [string], use two
+back-ticks together (i.e. ` `` `).
+
+```js
+let x = `I have a quote " as well as a back-tick `` here.`;
+
+// The above is the same as:
+let x = "I have a quote \" as well as a back-tick ` here.";
+```
+
+
+String Interpolation
+====================
+
+```admonish warning.side.wide "Only literal strings"
+
+Interpolation is not supported for normal [string] or [character] literals.
+```
+
+~~~admonish question.side.wide "What if I want `${` inside?"
+
+🤦 Well, you just _have_ to ask for the impossible, don't you?
+
+Currently there is no way to escape `${`. Build the [string] in three pieces:
+
+```js
+`Interpolations start with "`
+ + "${"
+ + `" and end with }.`
+```
+~~~
+
+Multi-line literal [strings] support _string interpolation_ wrapped in `${` ... `}`.
+
+`${` ... `}` acts as a statements _block_ and can contain anything that is allowed within a
+statements block, including another interpolated [string]!
+The last result of the block is taken as the value for interpolation.
+
+Rhai uses [`to_string`] to convert any value into a [string], then physically joins all the
+sub-strings together.
+
+For convenience, if any interpolated value is a [BLOB], however, it is automatically treated as a
+UTF-8 encoded string. That is because it is rarely useful to interpolate a [BLOB] into a [string],
+but extremely useful to be able to directly manipulate UTF-8 encoded text.
+
+```js
+let x = 42;
+let y = 123;
+
+let s = `x = ${x} and y = ${y}.`; // <- interpolated string
+
+let s = ("x = " + {x} + " and y = " + {y} + "."); // <- de-sugars to this
+
+s == "x = 42 and y = 123.";
+
+let s = `
+Undeniable logic:
+1) Hello, ${let w = `${x} world`; if x > 1 { w += "s" } w}!
+2) If ${y} > ${x} then it is ${y > x}!
+`;
+
+s == "Undeniable logic:\n1) Hello, 42 worlds!\n2) If 123 > 42 then it is true!\n";
+
+let blob = blob(3, 0x21);
+
+print(blob); // prints [212121]
+
+print(`Data: ${blob}`); // prints "Data: !!!"
+ // BLOB is treated as UTF-8 encoded string
+
+print(`Data: ${blob.to_string()}`); // prints "Data: [212121]"
+```
diff --git a/rhai_engine/rhaibook/language/strings-chars.md b/rhai_engine/rhaibook/language/strings-chars.md
new file mode 100644
index 0000000..fca962f
--- /dev/null
+++ b/rhai_engine/rhaibook/language/strings-chars.md
@@ -0,0 +1,259 @@
+Strings and Characters
+======================
+
+{{#include ../links.md}}
+
+```admonish tip.side "Safety"
+
+Always limit the [maximum length of strings].
+```
+
+String in Rhai contain any text sequence of valid Unicode characters.
+
+Internally strings are stored in UTF-8 encoding.
+
+[`type_of()`] a string returns `"string"`.
+
+
+String and Character Literals
+-----------------------------
+
+String and character literals follow JavaScript-style syntax.
+
+| Type | Quotes | Escapes? | Continuation? | Interpolation? |
+| ------------------------- | :-------------: | :------: | :-----------: | :------------: |
+| Normal string | `"..."` | yes | with `\` | **no** |
+| Raw string | `#..#"..."#..#` | **no** | **no** | **no** |
+| Multi-line literal string | `` `...` `` | **no** | **no** | with `${...}` |
+| Character | `'...'` | yes | **no** | **no** |
+
+```admonish tip.small "Tip: Building strings"
+
+Strings can be built up from other strings and types via the `+` operator
+(provided by the [`MoreStringPackage`][built-in packages] but excluded when using a [raw `Engine`]).
+
+This is particularly useful when printing output.
+```
+
+
+Standard Escape Sequences
+-------------------------
+
+~~~admonish tip.side "Tip: Character `to_int()`"
+
+Use the `to_int` method to convert a Unicode character into its 32-bit Unicode encoding.
+~~~
+
+There is built-in support for Unicode (`\u`_xxxx_ or `\U`_xxxxxxxx_) and hex (`\x`_xx_) escape
+sequences for normal strings and characters.
+
+Hex sequences map to ASCII characters, while `\u` maps to 16-bit common Unicode code points and `\U`
+maps the full, 32-bit extended Unicode code points.
+
+Escape sequences are not supported for multi-line literal strings wrapped by back-ticks (`` ` ``).
+
+| Escape sequence | Meaning |
+| --------------- | -------------------------------- |
+| `\\` | back-slash (`\`) |
+| `\t` | tab |
+| `\r` | carriage-return (`CR`) |
+| `\n` | line-feed (`LF`) |
+| `\"` or `""` | double-quote (`"`) |
+| `\'` | single-quote (`'`) |
+| `\x`_xx_ | ASCII character in 2-digit hex |
+| `\u`_xxxx_ | Unicode character in 4-digit hex |
+| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
+
+
+Line Continuation
+-----------------
+
+For a normal string wrapped by double-quotes (`"`), a back-slash (`\`) character at the end of a
+line indicates that the string continues onto the next line _without any line-break_.
+
+Whitespace up to the indentation of the opening double-quote is ignored in order to enable lining up
+blocks of text.
+
+Spaces are _not_ added, so to separate one line with the next with a space, put a space before the
+ending back-slash (`\`) character.
+
+```rust
+let x = "hello, world!\
+ hello world again! \
+ this is the ""last"" time!!!";
+// ^^^^^^ these whitespaces are ignored
+
+// The above is the same as:
+let x = "hello, world!hello world again! this is the \"last\" time!!!";
+```
+
+A string with continuation does not open up a new line. To do so, a new-line character must be
+manually inserted at the appropriate position.
+
+```rust
+let x = "hello, world!\n\
+ hello world again!\n\
+ this is the last time!!!";
+
+// The above is the same as:
+let x = "hello, world!\nhello world again!\nthis is the last time!!!";
+```
+
+~~~admonish warning.small "No ending quote before the line ends is a syntax error"
+
+If the ending double-quote is omitted, it is a syntax error.
+
+```rust
+let x = "hello
+# ";
+// ^ syntax error: unterminated string literal
+```
+~~~
+
+```admonish question.small "Why not go multi-line?"
+
+Technically speaking, there is no difficulty in allowing strings to run for multiple lines
+_without_ the continuation back-slash.
+
+Rhai forces you to manually mark a continuation with a back-slash because the ending quote is easy to omit.
+Once it happens, the entire remainder of the script would become one giant, multi-line string.
+
+This behavior is different from Rust, where string literals can run for multiple lines.
+```
+
+
+Raw Strings
+-----------
+
+A _raw string_ is any text enclosed by a pair of double-quotes (`"`), wrapped by hash (`#`) characters.
+
+The number of hash (`#`) on each side must be the same.
+
+Any text inside the double-quotes, as long as it is not a double-quote (`"`) followed by the same
+number of hash (`#`) characters, is simply copied verbatim, _including control codes and/or
+line-breaks_.
+
+Raw strings are very useful for embedded regular expressions, file paths, and program code etc.
+
+```rust
+let x = #"Hello, I am a raw string! which means that I can contain
+ line-breaks, \ slashes (not escapes), "quotes" and even # characters!"#
+
+// Use more than one '#' if you happen to have '"###...' inside the string...
+
+let x = ###"In Rhai, you can write ##"hello"## as a raw string."###;
+// ^^^ this is not the end of the raw string
+```
+
+
+Indexing
+--------
+
+Strings can be _indexed_ into to get access to any individual character.
+This is similar to many modern languages but different from Rust.
+
+### From beginning
+
+Individual characters within a string can be accessed with zero-based, non-negative integer indices:
+
+> _string_ `[` _index from 0 to (total number of characters − 1)_ `]`
+
+### From end
+
+A _negative_ index accesses a character in the string counting from the _end_, with −1 being the
+_last_ character.
+
+> _string_ `[` _index from −1 to −(total number of characters)_ `]`
+
+```admonish warning.small "Character indexing can be SLOOOOOOOOW"
+
+Internally, a Rhai string is still stored compactly as a Rust UTF-8 string in order to save memory.
+
+Therefore, getting the character at a particular index involves walking through the entire UTF-8
+encoded bytes stream to extract individual Unicode characters, counting them on the way.
+
+Because of this, indexing can be a _slow_ procedure, especially for long strings.
+Along the same lines, getting the _length_ of a string (which returns the number of characters, not
+bytes) can also be slow.
+```
+
+
+Sub-Strings
+-----------
+
+Sub-strings, or _slices_ in some programming languages, are parts of strings.
+
+In Rhai, a sub-string can be specified by indexing with a [range] of characters:
+
+> _string_ `[` _first character (starting from zero)_ `..` _last character (exclusive)_ `]`
+>
+> _string_ `[` _first character (starting from zero)_ `..=` _last character (inclusive)_ `]`
+
+Sub-string [ranges] always start from zero counting towards the end of the string.
+Negative [ranges] are not supported.
+
+
+Examples
+--------
+
+```js
+let name = "Bob";
+let middle_initial = 'C';
+let last = "Davis";
+
+let full_name = `${name} ${middle_initial}. ${last}`;
+full_name == "Bob C. Davis";
+
+// String building with different types
+let age = 42;
+let record = `${full_name}: age ${age}`;
+record == "Bob C. Davis: age 42";
+
+// Unlike Rust, Rhai strings can be indexed to get a character
+// (disabled with 'no_index')
+let c = record[4];
+c == 'C'; // single character
+
+let slice = record[4..8]; // sub-string slice
+slice == " C. D";
+
+ts.s = record; // custom type properties can take strings
+
+let c = ts.s[4];
+c == 'C';
+
+let c = ts.s[-4]; // negative index counts from the end
+c == 'e';
+
+let c = "foo"[0]; // indexing also works on string literals...
+c == 'f';
+
+let c = ("foo" + "bar")[5]; // ... and expressions returning strings
+c == 'r';
+
+let text = "hello, world!";
+text[0] = 'H'; // modify a single character
+text == "Hello, world!";
+
+text[7..=11] = "Earth"; // modify a sub-string slice
+text == "Hello, Earth!";
+
+// Escape sequences in strings
+record += " \u2764\n"; // escape sequence of '❤' in Unicode
+record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
+
+// Unlike Rust, Rhai strings can be directly modified character-by-character
+// (disabled with 'no_index')
+record[4] = '\x58'; // 0x58 = 'X'
+record == "Bob X. Davis: age 42 ❤\n";
+
+// Use 'in' to test if a substring (or character) exists in a string
+"Davis" in record == true;
+'X' in record == true;
+'C' in record == false;
+
+// Strings can be iterated with a 'for' statement, yielding characters
+for ch in record {
+ print(ch);
+}
+```
diff --git a/rhai_engine/rhaibook/language/switch-expression.md b/rhai_engine/rhaibook/language/switch-expression.md
new file mode 100644
index 0000000..6179299
--- /dev/null
+++ b/rhai_engine/rhaibook/language/switch-expression.md
@@ -0,0 +1,39 @@
+Switch Expression
+=================
+
+{{#include ../links.md}}
+
+Like [`if`], [`switch`] also works as an _expression_.
+
+```admonish tip.small "Tip"
+
+This means that a [`switch`] expression can appear anywhere a regular expression can,
+e.g. as [function] call arguments.
+```
+
+~~~admonish tip.small "Tip: Disable `switch` expressions"
+
+[`switch`] expressions can be disabled via [`Engine::set_allow_switch_expression`][options].
+~~~
+
+```js
+let x = switch foo { 1 => true, _ => false };
+
+func(switch foo {
+ "hello" => 42,
+ "world" => 123,
+ _ => 0
+});
+
+// The above is somewhat equivalent to:
+
+let x = if foo == 1 { true } else { false };
+
+if foo == "hello" {
+ func(42);
+} else if foo == "world" {
+ func(123);
+} else {
+ func(0);
+}
+```
diff --git a/rhai_engine/rhaibook/language/switch.md b/rhai_engine/rhaibook/language/switch.md
new file mode 100644
index 0000000..5ecda36
--- /dev/null
+++ b/rhai_engine/rhaibook/language/switch.md
@@ -0,0 +1,215 @@
+Switch Statement
+================
+
+{{#include ../links.md}}
+
+The `switch` statement allows matching on [literal] values, and it mostly follows Rust's `match` syntax.
+
+```js
+switch calc_secret_value(x) {
+ 1 => print("It's one!"),
+ 2 => {
+ // A statements block instead of a one-line statement
+ print("It's two!");
+ print("Again!");
+ }
+ 3 => print("Go!"),
+ // A list of alternatives
+ 4 | 5 | 6 => print("Some small number!"),
+ // _ is the default when no case matches. It must be the last case.
+ _ => print(`Oops! Something's wrong: ${x}`)
+}
+```
+
+
+Default Case
+------------
+
+A _default_ case (i.e. when no other cases match) can be specified with `_`.
+
+```admonish warning.small "Must be last"
+
+The default case must be the _last_ case in the `switch` statement.
+```
+
+```js
+switch wrong_default {
+ 1 => 2,
+ _ => 9, // <- syntax error: default case not the last
+ 2 => 3,
+ 3 => 4, // <- ending with extra comma is OK
+}
+
+switch wrong_default {
+ 1 => 2,
+ 2 => 3,
+ 3 => 4,
+ _ => 8, // <- syntax error: default case not the last
+ _ => 9
+}
+```
+
+
+Array and Object Map Literals Also Work
+---------------------------------------
+
+The `switch` expression can match against any _[literal]_, including [array] and [object map] [literals].
+
+```js
+// Match on arrays
+switch [foo, bar, baz] {
+ ["hello", 42, true] => ...,
+ ["hello", 123, false] => ...,
+ ["world", 1, true] => ...,
+ _ => ...
+}
+
+// Match on object maps
+switch map {
+ #{ a: 1, b: 2, c: true } => ...,
+ #{ a: 42, d: "hello" } => ...,
+ _ => ...
+}
+```
+
+```admonish tip.small "Tip: Working with enums"
+
+Switching on [arrays] is very useful when working with Rust enums
+(see [this section]({{rootUrl}}/patterns/enums.md) for more details).
+```
+
+
+Case Conditions
+---------------
+
+Similar to Rust, each case (except the default case at the end) can provide an optional condition
+that must evaluate to `true` in order for the case to match.
+
+All cases are checked in order, so an earlier case that matches will override all later cases.
+
+```js
+let result = switch calc_secret_value(x) {
+ 1 if some_external_condition(x, y, z) => 100,
+
+ 1 | 2 | 3 if x < foo => 200, // <- all alternatives share the same condition
+
+ 2 if bar() => 999,
+
+ 2 => "two", // <- fallback value for 2
+
+ 2 => "dead code", // <- this case is a duplicate and will never match
+ // because the previous case matches first
+
+ 5 if CONDITION => 123, // <- value for 5 matching condition
+
+ 5 => "five", // <- fallback value for 5
+
+ _ if CONDITION => 8888 // <- syntax error: default case cannot have condition
+};
+```
+
+~~~admonish tip "Tip: Use with `type_of()`"
+
+Case conditions, together with [`type_of()`], makes it extremely easy to work with
+values which may be of several different types (like properties in a JSON object).
+
+```js
+switch value.type_of() {
+ // if 'value' is a string...
+ "string" if value.len() < 5 => ...,
+ "string" => ...,
+
+ // if 'value' is an array...
+ "array" => ...,
+
+ // if 'value' is an object map...
+ "map" if value.prop == 42 => ...,
+ "map" => ...,
+
+ // if 'value' is a number...
+ "i64" if value > 0 => ...,
+ "i64" => ...,
+
+ // anything else: probably an error...
+ _ => ...
+}
+```
+~~~
+
+
+Range Cases
+-----------
+
+Because of their popularity, [literal] integer [ranges] can also be used as `switch` cases.
+
+Numeric [ranges] are only searched when the `switch` value is itself a number (including
+floating-point and [`Decimal`][rust_decimal]). They never match any other data types.
+
+```admonish warning.small "Must come after numeric cases"
+
+Range cases must come _after_ all numeric cases.
+```
+
+```js
+let x = 42;
+
+switch x {
+ 'x' => ..., // no match: wrong data type
+
+ 1 => ..., // <- specific numeric cases are checked first
+ 2 => ..., // <- but these do not match
+
+ 0..50 if x > 45 => ..., // no match: condition is 'false'
+
+ -10..20 => ..., // no match: not in range
+
+ 0..50 => ..., // <- MATCH!!!
+
+ 30..100 => ..., // no match: even though it is within range,
+ // the previous case matches first
+
+ 42 => ..., // <- syntax error: numeric cases cannot follow range cases
+}
+```
+
+```admonish tip.small "Tip: Ranges can overlap"
+
+When more then one [range] contain the `switch` value, the _first_ one with a fulfilled condition
+(if any) is evaluated.
+
+Numeric [range] cases are tried in the order that they appear in the original script.
+```
+
+
+Difference From `if`-`else if` Chain
+------------------------------------
+
+Although a `switch` expression looks _almost_ the same as an [`if`-`else if`][`if`] chain, there are
+subtle differences between the two.
+
+### Look-up Table vs `x == y`
+
+A `switch` expression matches through _hashing_ via a look-up table. Therefore, matching is very
+fast. Walking down an [`if`-`else if`][`if`] chain is _much_ slower.
+
+On the other hand, operators can be [overloaded][operator overloading] in Rhai, meaning that it is
+possible to override the `==` operator for integers such that `x == y` returns a different result
+from the built-in default.
+
+`switch` expressions do _not_ use the `==` operator for comparison; instead, they _hash_ the data
+values and jump directly to the correct statements via a pre-compiled look-up table. This makes
+matching extremely efficient, but it also means that [overloading][operator overloading] the `==`
+operator will have no effect.
+
+Therefore, in environments where it is desirable to [overload][operator overloading] the `==`
+operator for [standard types] – though it is difficult to think of valid scenarios where you'd
+want `1 == 1` to return something other than `true` – avoid using the `switch` expression.
+
+### Efficiency
+
+Because the `switch` expression works through a look-up table, it is very efficient even for _large_
+number of cases; in fact, switching is an O(1) operation regardless of the size of the data and
+number of cases to match.
+
+A long [`if`-`else if`][`if`] chain becomes increasingly slower with each additional case because
+essentially an O(n) _linear scan_ is performed.
diff --git a/rhai_engine/rhaibook/language/throw.md b/rhai_engine/rhaibook/language/throw.md
new file mode 100644
index 0000000..728f2e1
--- /dev/null
+++ b/rhai_engine/rhaibook/language/throw.md
@@ -0,0 +1,56 @@
+Throw Exception on Error
+========================
+
+{{#include ../links.md}}
+
+All [`Engine`] evaluation API methods return `Result>`
+with `EvalAltResult` holding error information.
+
+To deliberately return an error, use the `throw` keyword.
+
+```js
+if some_bad_condition_has_happened {
+ throw error; // 'throw' any value as the exception
+}
+
+throw; // defaults to '()'
+```
+
+Exceptions thrown via `throw` in the script can be captured in Rust by matching
+`EvalAltResult::ErrorRuntime(value, position)` with the exception value captured by `value`.
+
+```rust
+let result = engine.eval::(
+"
+ let x = 42;
+
+ if x > 0 {
+ throw x;
+ }
+").expect_err();
+
+println!("{result}"); // prints "Runtime error: 42 (line 5, position 15)"
+```
+
+
+Catch a Thrown Exception
+------------------------
+
+It is possible to _catch_ an exception instead of having it abort the evaluation
+of the entire script via the [`try` ... `catch`][`try`]
+statement common to many C-like languages.
+
+```js
+fn code_that_throws() {
+ throw 42;
+}
+
+try
+{
+ code_that_throws();
+}
+catch (err) // 'err' captures the thrown exception value
+{
+ print(err); // prints 42
+}
+```
diff --git a/rhai_engine/rhaibook/language/timestamps.md b/rhai_engine/rhaibook/language/timestamps.md
new file mode 100644
index 0000000..68b83cd
--- /dev/null
+++ b/rhai_engine/rhaibook/language/timestamps.md
@@ -0,0 +1,50 @@
+Timestamps
+==========
+
+{{#include ../links.md}}
+
+Timestamps are provided by the [`BasicTimePackage`][built-in packages] (excluded when using a [raw `Engine`])
+via the `timestamp` function.
+
+Timestamps are not available under [`no_time`] or [`no_std`].
+
+The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`] in [WASM] builds).
+
+[`type_of()`] a timestamp returns `"timestamp"`.
+
+
+Built-in Functions
+------------------
+
+The following methods (defined in the [`BasicTimePackage`][built-in packages] but excluded when
+using a [raw `Engine`]) operate on timestamps.
+
+| Function | Parameter(s) | Description |
+| ----------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
+| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
+| `+` operator | number of seconds to add | returns a new timestamp with a specified number of seconds added |
+| `+=` operator | number of seconds to add | adds a specified number of seconds to the timestamp |
+| `-` operator | number of seconds to subtract | returns a new timestamp with a specified number of seconds subtracted |
+| `-=` operator | number of seconds to subtract | subtracts a specified number of seconds from the timestamp |
+| `-` operator | later timestamp earlier timestamp | returns the number of seconds between the two timestamps |
+
+The following methods are defined in the [`LanguageCorePackage`][built-in packages] but excluded
+when using a [raw `Engine`].
+
+| Function | Not available under | Parameter(s) | Description |
+| -------- | :-----------------: | -------------------------- | ----------------------------------------------------------- |
+| `sleep` | [`no_std`] | number of seconds to sleep | blocks the current thread for a specified number of seconds |
+
+
+Examples
+--------
+
+```rust
+let now = timestamp();
+
+// Do some lengthy operation...
+
+if now.elapsed > 30.0 {
+ print("takes too long (over 30 seconds)!")
+}
+```
diff --git a/rhai_engine/rhaibook/language/try-catch.md b/rhai_engine/rhaibook/language/try-catch.md
new file mode 100644
index 0000000..3f8ff0c
--- /dev/null
+++ b/rhai_engine/rhaibook/language/try-catch.md
@@ -0,0 +1,113 @@
+Catch Exceptions
+================
+
+{{#include ../links.md}}
+
+
+When an [exception] is thrown via a [`throw`] statement, evaluation of the script halts and the
+[`Engine`] returns with `EvalAltResult::ErrorRuntime` containing the exception value thrown.
+
+It is possible, via the `try` ... `catch` statement, to _catch_ exceptions, optionally with an
+_error variable_.
+
+> `try` `{` ... `}` `catch` `{` ... `}`
+>
+> `try` `{` ... `}` `catch` `(` _error variable_ `)` `{` ... `}`
+
+```js
+// Catch an exception and capturing its value
+try
+{
+ throw 42;
+}
+catch (err) // 'err' captures the thrown exception value
+{
+ print(err); // prints 42
+}
+
+// Catch an exception without capturing its value
+try
+{
+ print(42/0); // deliberate divide-by-zero exception
+}
+catch // no error variable - exception value is discarded
+{
+ print("Ouch!");
+}
+
+// Exception in the 'catch' block
+try
+{
+ print(42/0); // throw divide-by-zero exception
+}
+catch
+{
+ print("You seem to be dividing by zero here...");
+
+ throw "die"; // a 'throw' statement inside a 'catch' block
+ // throws a new exception
+}
+```
+
+
+~~~admonish tip "Tip: Re-throw exception"
+
+Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_ an exception
+within the `catch` block simply by another [`throw`] statement without a value.
+
+```js
+try
+{
+ // Call something that will throw an exception...
+ do_something_bad_that_throws();
+}
+catch
+{
+ print("Oooh! You've done something real bad!");
+
+ throw; // 'throw' without a value within a 'catch' block
+ // re-throws the original exception
+}
+
+```
+~~~
+
+```admonish success "Catchable exceptions"
+
+Many script-oriented exceptions can be caught via `try` ... `catch`.
+
+| Error type | Error value |
+| ----------------------------------------------------- | :--------------------------: |
+| Runtime error thrown by a [`throw`] statement | value in [`throw`] statement |
+| Arithmetic error | [object map] |
+| [Variable] not found | [object map] |
+| [Function] not found | [object map] |
+| [Module] not found | [object map] |
+| Unbound `this` | [object map] |
+| Data type mismatch | [object map] |
+| Assignment to a calculated/[constant] value | [object map] |
+| [Array]/[string]/[bit-field] indexing out-of-bounds | [object map] |
+| Indexing with an inappropriate data type | [object map] |
+| Error in property access | [object map] |
+| [`for`] statement on a type without a [type iterator] | [object map] |
+| Data race detected | [object map] |
+| Other runtime error | [object map] |
+
+The error value in the `catch` clause is an [object map] containing information on the particular error,
+including its type, line and character position (if any), and source etc.
+
+When the [`no_object`] feature is turned on, however, the error value is a simple [string] description.
+```
+
+```admonish failure "Non-catchable exceptions"
+
+Some exceptions _cannot_ be caught.
+
+| Error type | Notes |
+| ----------------------------------------------------------------------- | --------------------------------- |
+| System error – e.g. script file not found | system errors are not recoverable |
+| Syntax error during parsing | invalid script |
+| [Custom syntax] mismatch error | incompatible [`Engine`] instance |
+| Script evaluation metrics exceeding [limits][safety] | [safety] protection |
+| Script evaluation manually [terminated]({{rootUrl}}/safety/progress.md) | [safety] protection |
+```
diff --git a/rhai_engine/rhaibook/language/type-of.md b/rhai_engine/rhaibook/language/type-of.md
new file mode 100644
index 0000000..f623eb3
--- /dev/null
+++ b/rhai_engine/rhaibook/language/type-of.md
@@ -0,0 +1,60 @@
+`type_of()`
+===========
+
+{{#include ../links.md}}
+
+The `type_of` function detects the actual type of a value.
+
+This is useful because all [variables] are [`Dynamic`] in nature.
+
+```js
+// Use 'type_of()' to get the actual types of values
+type_of('c') == "char";
+type_of(42) == "i64";
+
+let x = 123;
+x.type_of() == "i64"; // method-call style is also OK
+type_of(x) == "i64";
+
+x = 99.999;
+type_of(x) == "f64";
+
+x = "hello";
+if type_of(x) == "string" {
+ do_something_first_with_string(x);
+}
+
+switch type_of(x) {
+ "string" => do_something_with_string(x),
+ "char" => do_something_with_char(x),
+ "i64" => do_something_with_int(x),
+ "f64" => do_something_with_float(x),
+ "bool" => do_something_with_bool(x),
+ _ => throw `I cannot work with ${type_of(x)}!!!`
+}
+```
+
+```admonish info.small "Standard types"
+
+See [here][standard types] for the `type_of` output of standard types.
+```
+
+```admonish info.small "Custom types"
+
+`type_of()` a [custom type] returns:
+
+* the friendly name, if registered via `Engine::register_type_with_name`
+
+* the full Rust type path, if registered via `Engine::register_type`
+
+~~~rust
+struct TestStruct1;
+struct TestStruct2;
+
+engine
+ // type_of(struct1) == "path::to::module::TestStruct1"
+ .register_type::()
+ // type_of(struct2) == "MyStruct"
+ .register_type_with_name::("MyStruct");
+~~~
+```
diff --git a/rhai_engine/rhaibook/language/values-and-types.md b/rhai_engine/rhaibook/language/values-and-types.md
new file mode 100644
index 0000000..673bd21
--- /dev/null
+++ b/rhai_engine/rhaibook/language/values-and-types.md
@@ -0,0 +1,83 @@
+Values and Types
+================
+
+{{#include ../links.md}}
+
+The following primitive types are supported natively.
+
+| Category | Equivalent Rust types | [`type_of()`](type-of.md) | `to_string()` |
+| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------- |
+| **System integer** | `rhai::INT` (default `i64`, `i32` under [`only_i32`]) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
+| **Other integer number** | `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64` | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
+| **Integer numeric [range]** | `std::ops::Range`, `std::ops::RangeInclusive` | `"range"`, `"range="` | `"2..7"`, `"0..=15"` etc. |
+| **Floating-point number** (disabled with [`no_float`]) | `rhai::FLOAT` (default `f64`, `f32` under [`f32_float`]) | `"f32"` or `"f64"` | `"123.4567"` etc. |
+| **Fixed precision decimal number** (requires [`decimal`]) | [`rust_decimal::Decimal`][rust_decimal] | `"decimal"` | `"42"`, `"123.4567"` etc. |
+| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
+| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
+| **Immutable Unicode [string]** | [`rhai::ImmutableString`][`ImmutableString`] (`Rc`, `Arc` under [`sync`]) | `"string"` | `"hello"` etc. |
+| **[`Array`]** (disabled with [`no_index`]) | [`rhai::Array`][array] (`Vec`) | `"array"` | `"[ 1, 2, 3 ]"` etc. |
+| **Byte array – [`BLOB`]** (disabled with [`no_index`]) | [`rhai::Blob`][BLOB] (`Vec`) | `"blob"` | `"[01020304abcd]"` etc. |
+| **[Object map]** (disabled with [`no_object`]) | [`rhai::Map`][object map] (`BTreeMap`) | `"map"` | `"#{ "a": 1, "b": true }"` etc. |
+| **[Timestamp]** (disabled with [`no_time`] or [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` |
+| **[Function pointer]** | [`rhai::FnPtr`][function pointer] | `"Fn"` | `"Fn(foo)"` etc. |
+| **Dynamic value** (i.e. can be anything) | [`rhai::Dynamic`][`Dynamic`] | _the actual type_ | _actual value_ |
+| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [closures], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ |
+| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
+
+
+```admonish warning.small "No automatic type conversion for integers"
+
+The various integer types are treated strictly _distinct_ by Rhai, meaning that
+`i32` and `i64` and `u32` and `u8` are completely different.
+
+They cannot even be added together or compared with each other.
+
+Nor can a smaller integer type be up-casted to a larger integer type.
+
+This is very similar to Rust.
+```
+
+
+Default Types
+-------------
+
+The default integer type is `i64`. If other integer types are not needed, it is possible to exclude
+them and make a smaller build with the [`only_i64`] feature.
+
+If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all
+integer types other than `i32`, including `i64`.
+This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty.
+
+~~~admonish danger.small "Warning: Default integer is `i64`"
+
+Rhai's default integer type is `i64`, which is _DIFFERENT_ from Rust's `i32`.
+
+It is very easy to unsuspectingly set an `i32` into Rhai, which _still works_ but will incur a significant
+runtime performance hit since the [`Engine`] will treat `i32` as an opaque [custom type] (unless using the
+[`only_i32`] feature).
+
+`i64` is the default even on 32-bit systems. To use `i32` on 32-bit systems requires the [`only_i32`] feature.
+~~~
+
+```admonish tip.small "Tip: Floating-point numbers"
+
+If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
+
+Some applications require fixed-precision decimal numbers, which can be enabled via the [`decimal`] feature.
+```
+
+```admonish info.small "Immutable strings"
+
+[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified.
+
+Internally, the [`ImmutableString`] type is a wrapper over `Rc` or `Arc` (depending on [`sync`]).
+
+Any modification done to a Rhai string causes the [string] to be cloned and the modifications made to the copy.
+```
+
+```admonish tip.small "Tip: Convert to string"
+
+The [`to_string`] function converts a standard type into a [string] for display purposes.
+
+The [`to_debug`] function converts a standard type into a [string] in debug format.
+```
diff --git a/rhai_engine/rhaibook/language/variables.md b/rhai_engine/rhaibook/language/variables.md
new file mode 100644
index 0000000..fddb42e
--- /dev/null
+++ b/rhai_engine/rhaibook/language/variables.md
@@ -0,0 +1,183 @@
+Variables
+=========
+
+{{#include ../links.md}}
+
+
+Valid Names
+-----------
+
+```admonish tip.side.wide "Tip: Unicode Standard Annex #31 identifiers"
+
+The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by
+[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
+```
+
+Variables in Rhai follow normal C naming rules – must contain only ASCII letters, digits and underscores `_`.
+
+| Character set | Description |
+| :-----------: | ------------------------ |
+| `A` ... `Z` | Upper-case ASCII letters |
+| `a` ... `z` | Lower-case ASCII letters |
+| `0` ... `9` | Digit characters |
+| `_` | Underscore character |
+
+However, unlike Rust, a variable name must also contain at least one ASCII letter, and an ASCII
+letter must come _before_ any digits. In other words, the first character that is not an underscore `_`
+must be an ASCII letter and not a digit.
+
+```admonish question.side.wide "Why this restriction?"
+
+To reduce confusion (and subtle bugs) because, for instance, `_1` can easily be misread (or mistyped)
+as `-1`.
+
+Rhai is dynamic without type checking, so there is no compiler to catch these typos.
+```
+
+Therefore, some names acceptable to Rust, like `_`, `_42foo`, `_1` etc., are not valid in Rhai.
+
+For example: `c3po` and `_r2d2_` are valid variable names, but `3abc` and `____49steps` are not.
+
+Variable names are case _sensitive_.
+
+Variable names also cannot be the same as a [keyword] (active or reserved).
+
+```admonish warning.small "Avoid names longer than 11 letters on 32-Bit"
+
+Rhai uses [`SmartString`] which avoids allocations unless a string is over its internal limit
+(23 ASCII characters on 64-bit, but only 11 ASCII characters on 32-bit).
+
+On 64-bit systems, _most_ variable names are shorter than 23 letters, so this is unlikely to become
+an issue.
+
+However, on 32-bit systems, take care to limit, where possible, variable names to within 11 letters.
+This is particularly true for local variables inside a hot loop, where they are created and
+destroyed in rapid succession.
+
+~~~js
+// The following is SLOW on 32-bit
+for my_super_loop_variable in array {
+ print(`Super! ${my_super_loop_variable}`);
+}
+
+// Suggested revision:
+for loop_var in array {
+ print(`Super! ${loop_var}`);
+}
+~~~
+```
+
+
+Declare a Variable
+------------------
+
+Variables are declared using the `let` keyword.
+
+```admonish tip.small "Tip: No initial value"
+
+Variables do not have to be given an initial value.
+If none is provided, it defaults to [`()`].
+```
+
+```admonish warning.small "Variables are local"
+
+A variable defined within a [statements block](statements.md) is _local_ to that block.
+```
+
+~~~admonish tip.small "Tip: `is_def_var`"
+
+Use `is_def_var` to detect if a variable is defined.
+~~~
+
+```rust
+let x; // ok - value is '()'
+let x = 3; // ok
+let _x = 42; // ok
+let x_ = 42; // also ok
+let _x_ = 42; // still ok
+
+let _ = 123; // <- syntax error: illegal variable name
+let _9 = 9; // <- syntax error: illegal variable name
+
+let x = 42; // variable is 'x', lower case
+let X = 123; // variable is 'X', upper case
+
+print(x); // prints 42
+print(X); // prints 123
+
+{
+ let x = 999; // local variable 'x' shadows the 'x' in parent block
+
+ print(x); // prints 999
+}
+
+print(x); // prints 42 - the parent block's 'x' is not changed
+
+let x = 0; // new variable 'x' shadows the old 'x'
+
+print(x); // prints 0
+
+is_def_var("x") == true;
+
+is_def_var("_x") == true;
+
+is_def_var("y") == false;
+```
+
+
+Use Before Definition
+---------------------
+
+By default, variables do not need to be defined before they are used.
+
+If a variable accessed by a script is not defined previously within the same script, it is assumed
+to be provided via an external custom [`Scope`] passed to the [`Engine`] via the
+`Engine::XXX_with_scope` API.
+
+```rust
+let engine = Engine::new();
+
+engine.run("print(answer)")?; // <- error: variable 'answer' not found
+
+// Create custom scope
+let mut scope = Scope::new();
+
+// Add variable to custom scope
+scope.push("answer", 42_i64);
+
+// Run with custom scope
+engine.run_with_scope(&mut scope,
+ "print(answer)" // <- prints 42
+)?;
+```
+
+~~~admonish bug.small "No `Scope`"
+
+If no [`Scope`] is used to evaluate the script (e.g. when using `Engine::run` instead of
+`Engine::run_with_scope`), an undefined variable causes a runtime error when accessed.
+~~~
+
+
+Strict Variables Mode
+---------------------
+
+With [`Engine::set_strict_variables`][options], it is possible to turn on
+[_Strict Variables_][strict variables] mode.
+
+When [strict variables] mode is active, accessing a variable not previously defined within
+the same script directly causes a parse error when compiling the script.
+
+```rust
+let x = 42;
+
+print(x); // prints 42
+
+print(foo); // <- parse error under strict variables mode:
+ // variable 'foo' is undefined
+```
+
+```admonish tip.small
+
+Turn on [strict variables] mode if no [`Scope`] is to be provided for script evaluation runs.
+This way, variable access errors are caught during compile time instead of runtime.
+```
diff --git a/rhai_engine/rhaibook/language/while.md b/rhai_engine/rhaibook/language/while.md
new file mode 100644
index 0000000..023f440
--- /dev/null
+++ b/rhai_engine/rhaibook/language/while.md
@@ -0,0 +1,62 @@
+While Loop
+==========
+
+{{#include ../links.md}}
+
+`while` loops follow C syntax.
+
+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.
+
+~~~admonish tip.small "Tip: Disable `while` loops"
+
+`while` loops can be disabled via [`Engine::set_allow_looping`][options].
+~~~
+
+```rust
+let x = 10;
+
+while x > 0 {
+ x -= 1;
+ if x < 6 { continue; } // skip to the next iteration
+ print(x);
+ if x == 5 { break; } // break out of while loop
+}
+```
+
+
+While Expression
+----------------
+
+Like Rust, `while` 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 `while` 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;
+
+// 'while' can be used just like an expression
+let result = while x < 100 {
+ if is_magic_number(x) {
+ // if the 'while' loop breaks here, return a specific value
+ break get_magic_result(x);
+ }
+
+ x += 1;
+
+ // ... if the 'while' loop exits here, the return value is ()
+};
+
+if result == () {
+ print("Magic number not found!");
+} else {
+ print(`Magic result = ${result}!`);
+}
+```
diff --git a/rhai_engine/rhaibook/lib/index.md b/rhai_engine/rhaibook/lib/index.md
new file mode 100644
index 0000000..984f76b
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/index.md
@@ -0,0 +1,14 @@
+External Packages
+=================
+
+{{#include ../links.md}}
+
+Following are external [packages] that can be used with Rhai for additional functionalities.
+
+| Package | Description |
+| :-----------: | --------------------------------------------------------------------- |
+| [`rhai-rand`] | generate random numbers, shuffling and sampling |
+| [`rhai-sci`] | functions for scientific computing |
+| [`rhai-ml`] | functions for AI and machine learning |
+| [`rhai-fs`] | read/write files in an external filesystem |
+| [`rhai-url`] | working with Urls via the [`url`](https://crates.io/crates/url) crate |
diff --git a/rhai_engine/rhaibook/lib/rhai-autodocs.md b/rhai_engine/rhaibook/lib/rhai-autodocs.md
new file mode 100644
index 0000000..e5b9af0
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-autodocs.md
@@ -0,0 +1,52 @@
+`rhai-autodocs`: Generate API Documentation
+===========================================
+
+{{#include ../links.md}}
+
+
+`rhai-autodocs` helps generate API documentation, in [MarkDown] or [MDX] format, for functions
+registered inside an [`Engine`] instance.
+
+It is typically imported as a build dependency into the build script.
+
+The [MarkDown]/[MDX] files can then be used to create a static documentation site using generators
+such as [`mdbook`](https://crates.io/crates/mdbook) or [Docusaurus](https://docusaurus.io).
+
+
+> On `crates.io`: [`rhai-autodocs`](https://crates.io/crates/rhai-autodocs)
+>
+> On `GitHub`: [`rhaiscript/rhai-autodocs`](https://github.com/ltabis/rhai-autodocs)
+
+
+Usage
+-----
+
+`Cargo.toml`:
+
+```toml
+[dev-dependencies]
+rhai = "{{version}}"
+rhai-autodocs = "0.4" # use rhai-autodocs crate
+```
+
+`build.rs`:
+
+```rust
+fn main() {
+ // Specify an environment variable that points to the directory
+ // where the documentation will be generated.
+ if let Ok(docs_path) = std::env::var("DOCS_DIR") {
+ let mut engine = rhai::Engine::new();
+
+ // register custom functions and types...
+ // or load packages...
+
+ let docs = rhai_autodocs::options()
+ .include_standard_packages(false)
+ .generate(&engine)
+ .expect("failed to generate documentation");
+
+ // Write the documentation to a file etc.
+ }
+}
+```
diff --git a/rhai_engine/rhaibook/lib/rhai-dylib.md b/rhai_engine/rhaibook/lib/rhai-dylib.md
new file mode 100644
index 0000000..3c9ab8b
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-dylib.md
@@ -0,0 +1,24 @@
+Create Dynamically Loadable Rhai Libraries
+===========================================
+
+{{#include ../links.md}}
+
+```admonish danger.small "Linux or Windows only"
+
+`rhai-dylib` currently supports only Linux and Windows.
+```
+
+`rhai-dylib` is an independent crate that demonstrates an API to register Rhai functionalities via
+_dynamic shared libraries_ (i.e. `.so` in Linux or `.dll` in Windows).
+
+In other words, functions and [modules] can be defined in external libraries that are loaded
+dynamically at _runtime_, allowing for great flexibility at the cost of depending on the unstable
+Rust ABI.
+
+A [module resolver] is also included.
+
+> On `crates.io`: [`rhai-dylib`](https://crates.io/crates/rhai-dylib)
+>
+> On `GitHub`: [`rhaiscript/rhai-dylib`](https://github.com/rhaiscript/rhai-dylib)
+>
+> API trait name: `rhai_dylib::Plugin`
diff --git a/rhai_engine/rhaibook/lib/rhai-fs.md b/rhai_engine/rhaibook/lib/rhai-fs.md
new file mode 100644
index 0000000..4c66ab0
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-fs.md
@@ -0,0 +1,83 @@
+`rhai-fs`: Filesystem Access
+============================
+
+{{#include ../links.md}}
+
+
+`rhai-fs` is an independent Rhai [package] that enables reading from and writing to files in an
+external filesystem.
+
+```admonish info.side "Documentation"
+
+See for the list of functions.
+```
+
+> On `crates.io`: [`rhai-fs`](https://crates.io/crates/rhai-fs)
+>
+> On `GitHub`: [`rhaiscript/rhai-fs`](https://github.com/rhaiscript/rhai-fs)
+>
+> Package name: `FilesystemPackage`
+
+
+Dependency
+----------
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+rhai = "{{version}}"
+rhai-fs = "0.1" # use rhai-fs crate
+```
+
+
+Load Package into [`Engine`]
+----------------------------
+
+```rust
+use rhai::Engine;
+use rhai::packages::Package; // needed for 'Package' trait
+use rhai_fs::FilesystemPackage;
+
+let mut engine = Engine::new();
+
+// Create new 'FilesystemPackage' instance
+let fs = FilesystemPackage::new();
+
+// Load the package into the `Engine`
+fs.register_into_engine(&mut engine);
+```
+
+
+Example
+-------
+
+```js
+// Create a file, or open it if already exists
+let file = open_file("example.txt");
+
+// Read the contents of the file (if any) into a BLOB
+let blob_buf = file.read_blob();
+
+print(`file contents: ${blob_buf}`);
+
+// Update BLOB data
+blob_buf.write_utf8(0..=0x20, "foobar");
+
+print(`new file contents: ${blob_buf}`);
+
+// Seek back to the beginning
+file.seek(0);
+
+// Overwrite the original file with new data
+blob_buf.write_to_file(file);
+```
+
+
+Features
+--------
+
+| Feature | Description | Default? | Should be used with Rhai feature |
+| :--------: | ------------------------------------------------------------ | :------: | :------------------------------: |
+| `no_array` | removes support for [arrays] and [BLOB's] | **no** | [`no_index`] |
+| `metadata` | enables [functions metadata] (turns on [`metadata`] in Rhai) | **no** | |
diff --git a/rhai_engine/rhaibook/lib/rhai-ml.md b/rhai_engine/rhaibook/lib/rhai-ml.md
new file mode 100644
index 0000000..34f4502
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-ml.md
@@ -0,0 +1,57 @@
+`rhai-ml`: Functions for AI and Machine Learning
+================================================
+
+{{#include ../links.md}}
+
+
+`rhai-ml` is an independent Rhai [package] that provides functions useful for
+artificial intelligence and machine learning.
+
+```admonish info.side "Documentation"
+
+See [https://docs.rs/rhai-ml](https://docs.rs/rhai-ml#api) for the list of functions.
+```
+
+> On `crates.io`: [`rhai-ml`](https://crates.io/crates/rhai-ml)
+>
+> On `GitHub`: [`rhaiscript/rhai-ml`](https://github.com/rhaiscript/rhai-ml)
+>
+> Package name: `MLPackage`
+
+
+Dependency
+----------
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+rhai = "{{version}}"
+rhai-ml = "0.1" # use rhai-ml crate
+```
+
+
+Features
+--------
+
+| Feature | Description | Default? |
+| :--------: | --------------------------------------------------------------------------------------------- | :------: |
+| `metadata` | enables [functions metadata] (turns on [`metadata`] in Rhai); necessary for running doc-tests | **no** |
+
+
+Load Package into [`Engine`]
+----------------------------
+
+```rust
+use rhai::Engine;
+use rhai::packages::Package; // needed for 'Package' trait
+use rhai_ml::MLPackage;
+
+let mut engine = Engine::new();
+
+// Create new 'MLPackage' instance
+let ml = MLPackage::new();
+
+// Load the package into the [`Engine`]
+ml.register_into_engine(&mut engine);
+```
diff --git a/rhai_engine/rhaibook/lib/rhai-rand.md b/rhai_engine/rhaibook/lib/rhai-rand.md
new file mode 100644
index 0000000..2ec00bc
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-rand.md
@@ -0,0 +1,74 @@
+`rhai-rand`: Random Number Generation, Shuffling and Sampling
+=============================================================
+
+{{#include ../links.md}}
+
+`rhai-rand` is an independent Rhai [package] that provides:
+
+* random number generation using the [`rand`](https://crates.io/crates/rand) crate
+* [array] shuffling and sampling
+
+```admonish info.side "Documentation"
+
+See [https://docs.rs/rhai-rand](https://docs.rs/rhai-rand#api) for the list of functions.
+```
+
+> On `crates.io`: [`rhai-rand`](https://crates.io/crates/rhai-rand)
+>
+> On `GitHub`: [`rhaiscript/rhai-rand`](https://github.com/rhaiscript/rhai-rand)
+>
+> Package name: `RandomPackage`
+
+
+Dependency
+----------
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+rhai = "{{version}}"
+rhai-rand = "0.1" # use rhai-rand crate
+```
+
+
+Load Package into [`Engine`]
+----------------------------
+
+```rust
+use rhai::Engine;
+use rhai::packages::Package; // needed for 'Package' trait
+use rhai_rand::RandomPackage;
+
+let mut engine = Engine::new();
+
+// Create new 'RandomPackage' instance
+let random = RandomPackage::new();
+
+// Load the package into the `Engine`
+random.register_into_engine(&mut engine);
+```
+
+
+Features
+--------
+
+| Feature | Description | Default? | Should not be used with Rhai feature |
+| :--------: | ------------------------------------------------------------ | :------: | :----------------------------------: |
+| `float` | enables random floating-point number generation | yes | [`no_float`] |
+| `array` | enables methods for [arrays] | yes | [`no_index`] |
+| `metadata` | enables [functions metadata] (turns on [`metadata`] in Rhai) | **no** | |
+
+~~~admonish example "Example: Working with `no_float` in Rhai"
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+# Rhai is set for 'no_float', meaning no floating-point support
+rhai = { version="{{version}}", features = ["no_float"] }
+
+# Use 'default-features = false' to clear defaults, then only add 'array'
+rhai-rand = { version="0.1", default-features = false, features = ["array"] }
+```
+~~~
diff --git a/rhai_engine/rhaibook/lib/rhai-sci.md b/rhai_engine/rhaibook/lib/rhai-sci.md
new file mode 100644
index 0000000..f318ded
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-sci.md
@@ -0,0 +1,60 @@
+`rhai-sci`: Functions for Scientific Computing
+==============================================
+
+{{#include ../links.md}}
+
+
+`rhai-sci` is an independent Rhai [package] that provides functions useful for
+scientific computing, inspired by languages like MATLAB, Octave, and R.
+
+```admonish info.side "Documentation"
+
+See [https://docs.rs/rhai-sci](https://docs.rs/rhai-sci#api) for the list of functions.
+```
+
+> On `crates.io`: [`rhai-sci`](https://crates.io/crates/rhai-sci)
+>
+> On `GitHub`: [`rhaiscript/rhai-sci`](https://github.com/rhaiscript/rhai-sci)
+>
+> Package name: `SciPackage`
+
+
+Dependency
+----------
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+rhai = "{{version}}"
+rhai-sci = "0.1" # use rhai-sci crate
+```
+
+
+Features
+--------
+
+| Feature | Description | Default? |
+| :--------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: |
+| `metadata` | enables [functions metadata] (turns on [`metadata`] in Rhai); necessary for running doc-tests | **no** |
+| `io` | enables the `read_matrix` function but pulls in several additional dependencies | yes |
+| `nalgebra` | enables the functions `regress`, `inv`, `mtimes`, `horzcat`, `vertcat`, and `repmat` but pulls in [`nalgebra`](https://crates.io/crates/nalgebra) and [`linregress`](https://crates.io/crates/linregress). | yes |
+| `rand` | enables the `rand` function for generating random values and random matrices, but pulls in [`rand`](https://crates.io/crates/rand). | yes |
+
+
+Load Package into [`Engine`]
+----------------------------
+
+```rust
+use rhai::Engine;
+use rhai::packages::Package; // needed for 'Package' trait
+use rhai_sci::SciPackage;
+
+let mut engine = Engine::new();
+
+// Create new 'SciPackage' instance
+let sci = SciPackage::new();
+
+// Load the package into the [`Engine`]
+sci.register_into_engine(&mut engine);
+```
diff --git a/rhai_engine/rhaibook/lib/rhai-url.md b/rhai_engine/rhaibook/lib/rhai-url.md
new file mode 100644
index 0000000..61186f7
--- /dev/null
+++ b/rhai_engine/rhaibook/lib/rhai-url.md
@@ -0,0 +1,75 @@
+`rhai-url`: Working with Urls
+=============================
+
+{{#include ../links.md}}
+
+
+`rhai-url` is an independent Rhai [package] that enables working with Urls via the
+[`url`](https://crates.io/crates/url) crate.
+
+```admonish info.side "Documentation"
+
+See for the list of functions.
+```
+
+> On `crates.io`: [`rhai-url`](https://crates.io/crates/rhai-url)
+>
+> On `GitHub`: [`rhaiscript/rhai-url`](https://github.com/rhaiscript/rhai-url)
+>
+> Package name: `FilesystemPackage`
+
+
+Dependency
+----------
+
+`Cargo.toml`:
+
+```toml
+[dependencies]
+rhai = "{{version}}"
+rhai-url = "0.0.1" # use rhai-url crate
+```
+
+
+Load Package into [`Engine`]
+----------------------------
+
+```rust
+use rhai::Engine;
+use rhai::packages::Package; // needed for 'Package' trait
+use rhai_url::UrlPackage;
+
+let mut engine = Engine::new();
+
+// Create new 'UrlPackage' instance
+let url = UrlPackage::new();
+
+// Load the package into the `Engine`
+url.register_into_engine(&mut engine);
+```
+
+
+Example
+-------
+
+```rust
+let url = Url("http://example.com/?q=query");
+
+print(url); // prints 'http://example.com/?q=query'
+print(url.href); // prints 'http://example.com/?q=query'
+
+print(url.query); // prints 'q=query'
+
+// fragment and hash are aliases
+print(url.fragment); // prints ''
+print(url.hash); // prints ''
+
+url.query_clear();
+
+print(url.query); // prints ''
+
+url.query_remove("q");
+url.query_append("q", "name");
+
+print(url); // prints 'http://example.com/?q=name'
+```
diff --git a/rhai_engine/rhaibook/links.md b/rhai_engine/rhaibook/links.md
new file mode 100644
index 0000000..83135b0
--- /dev/null
+++ b/rhai_engine/rhaibook/links.md
@@ -0,0 +1,247 @@
+[features]: {{rootUrl}}/start/features.md
+[`unchecked`]: {{rootUrl}}/start/features.md
+[`sync`]: {{rootUrl}}/start/features.md
+[`no_position`]: {{rootUrl}}/start/features.md
+[`no_optimize`]: {{rootUrl}}/start/features.md
+[`no_float`]: {{rootUrl}}/start/features.md
+[`f32_float`]: {{rootUrl}}/start/features.md
+[`decimal`]: {{rootUrl}}/start/features.md
+[`only_i32`]: {{rootUrl}}/start/features.md
+[`only_i64`]: {{rootUrl}}/start/features.md
+[`no_index`]: {{rootUrl}}/start/features.md
+[`no_object`]: {{rootUrl}}/start/features.md
+[`no_function`]: {{rootUrl}}/start/features.md
+[`no_module`]: {{rootUrl}}/start/features.md
+[`no_closure`]: {{rootUrl}}/start/features.md
+[`no_custom_syntax`]: {{rootUrl}}/start/features.md
+[`no_time`]: {{rootUrl}}/start/features.md
+[`no_std`]: {{rootUrl}}/start/features.md
+[`wasm-bindgen`]: {{rootUrl}}/start/features.md
+[`stdweb`]: {{rootUrl}}/start/features.md
+[`metadata`]: {{rootUrl}}/start/features.md
+[`internals`]: {{rootUrl}}/start/features.md
+[`debugging`]: {{rootUrl}}/start/features.md
+[`unicode-xid-ident`]: {{rootUrl}}/start/features.md
+
+[minimal builds]: {{rootUrl}}/start/builds/minimal.md
+[`no-std`]: {{rootUrl}}/start/builds/no-std.md
+[WASM]: {{rootUrl}}/start/builds/wasm.md
+[static hashing]: {{rootUrl}}/patterns/static-hash.md
+[benchmarks]: {{rootUrl}}/about/benchmarks.md
+
+[playground]: https://rhai.rs/playground
+[lsp]: https://github.com/rhaiscript/lsp
+[`rhai-doc`]: https://github.com/rhaiscript/rhai-doc
+[MarkDown]: http://en.wikipedia.org/wiki/Markdown
+[MDX]: https://mdxjs.com/docs/what-is-mdx/
+[`ahash`]: https://crates.io/crates/ahash
+[`smartstring`]: https://crates.io/crates/smartstring
+[`SmartString`]: https://crates.io/crates/smartstring
+
+[`Engine`]: {{rootUrl}}/engine/hello-world.md
+[traits]: {{rootUrl}}/rust/traits.md
+[`call_fn`]: {{rootUrl}}/engine/call-fn.md
+[`Func`]: {{rootUrl}}/engine/func.md
+[`CustomType`]: {{rootUrl}}/rust/build-type.md
+[`AST`]: {{rootUrl}}/engine/compile.md
+[`eval_expression`]: {{rootUrl}}/engine/expressions.md
+[raw `Engine`]: {{rootUrl}}/engine/raw.md
+[built-in operator]: {{rootUrl}}/engine/builtin.md
+[built-in operators]: {{rootUrl}}/engine/builtin.md
+[precedence]: {{rootUrl}}/engine/precedence.md
+[package]: {{rootUrl}}/rust/packages/index.md
+[packages]: {{rootUrl}}/rust/packages/index.md
+[built-in package]: {{rootUrl}}/rust/packages/builtin.md
+[built-in packages]: {{rootUrl}}/rust/packages/builtin.md
+[custom package]: {{rootUrl}}/rust/packages/create.md
+[custom packages]: {{rootUrl}}/rust/packages/create.md
+[plugin]: {{rootUrl}}/plugins/index.md
+[plugins]: {{rootUrl}}/plugins/index.md
+[plugin module]: {{rootUrl}}/plugins/module.md
+[plugin modules]: {{rootUrl}}/plugins/module.md
+[plugin function]: {{rootUrl}}/plugins/function.md
+[plugin functions]: {{rootUrl}}/plugins/function.md
+[functions metadata]: {{rootUrl}}/engine/metadata/index.md
+[`Scope`]: {{rootUrl}}/engine/scope.md
+[`serde`]: {{rootUrl}}/rust/serde.md
+[strings interner]: {{rootUrl}}/rust/strings-interner.md
+
+[`type_of`]: {{rootUrl}}/language/type-of.md
+[`type_of()`]: {{rootUrl}}/language/type-of.md
+[`to_string`]: {{rootUrl}}/rust/print-custom.md
+[`to_debug`]: {{rootUrl}}/rust/print-custom.md
+[`()`]: {{rootUrl}}/language/values-and-types.md
+[standard types]: {{rootUrl}}/language/values-and-types.md
+[`Dynamic`]: {{rootUrl}}/language/dynamic.md
+[rust_decimal]: https://crates.io/crates/rust_decimal
+[`to_int`]: {{rootUrl}}/language/convert.md
+[`to_float`]: {{rootUrl}}/language/convert.md
+[`to_decimal`]: {{rootUrl}}/language/convert.md
+[`parse_int`]: {{rootUrl}}/language/convert.md
+[`parse_float`]: {{rootUrl}}/language/convert.md
+[`parse_decimal`]: {{rootUrl}}/language/convert.md
+[`to_binary`]: {{rootUrl}}/language/convert.md
+[`to_octal`]: {{rootUrl}}/language/convert.md
+[`to_hex`]: {{rootUrl}}/language/convert.md
+[`in`]: {{rootUrl}}/language/in.md
+
+[custom type]: {{rootUrl}}/rust/custom-types.md
+[custom types]: {{rootUrl}}/rust/custom-types.md
+[method]: {{rootUrl}}/rust/methods.md
+[methods]: {{rootUrl}}/rust/methods.md
+[getters/setters]: {{rootUrl}}/rust/getters-setters.md
+[elvis]: https://en.wikipedia.org/wiki/Elvis_operator
+[indexer]: {{rootUrl}}/rust/indexers.md
+[indexers]: {{rootUrl}}/rust/indexers.md
+[type iterator]: {{rootUrl}}/language/iterator.md
+[type iterators]: {{rootUrl}}/language/iterator.md
+[`NativeCallContext`]: {{rootUrl}}/rust/context.md
+[`EvalContext`]: {{rootUrl}}/engine/eval-context.md
+[`GlobalRuntimeState`]: https://docs.rs/rhai/{{version}}/rhai/struct.GlobalRuntimeState.html
+
+[`instant::Instant`]: https://crates.io/crates/instant
+
+[`print`]: {{rootUrl}}/language/print-debug.md
+[`debug`]: {{rootUrl}}/language/print-debug.md
+
+[keyword]: {{rootUrl}}/appendix/keywords.md
+[keywords]: {{rootUrl}}/appendix/keywords.md
+[operator]: {{rootUrl}}/appendix/operators.md#operators
+[operators]: {{rootUrl}}/appendix/operators.md#operators
+
+[variable]: {{rootUrl}}/language/variables.md
+[variables]: {{rootUrl}}/language/variables.md
+[constant]: {{rootUrl}}/language/constants.md
+[constants]: {{rootUrl}}/language/constants.md
+[shadow]: {{rootUrl}}/language/shadow.md
+[shadowing]: {{rootUrl}}/language/shadow.md
+[variable shadowing]: {{rootUrl}}/language/shadow.md
+[variable definition filter]: {{rootUrl}}/engine/def-var.md
+[token remap filter]: {{rootUrl}}/engine/token-mapper.md
+
+[string]: {{rootUrl}}/language/strings-chars.md
+[strings]: {{rootUrl}}/language/strings-chars.md
+[`ImmutableString`]: {{rootUrl}}/rust/immutable-string.md
+[character]: {{rootUrl}}/language/strings-chars.md
+[characters]: {{rootUrl}}/language/strings-chars.md
+
+[bit-field]: {{rootUrl}}/language/bit-fields.md
+[bit-fields]: {{rootUrl}}/language/bit-fields.md
+
+[range]: {{rootUrl}}/language/ranges.md
+[ranges]: {{rootUrl}}/language/ranges.md
+
+[array]: {{rootUrl}}/language/arrays.md
+[arrays]: {{rootUrl}}/language/arrays.md
+[`Array`]: {{rootUrl}}/language/arrays.md
+
+[BLOB]: {{rootUrl}}/language/blobs.md
+[BLOB's]: {{rootUrl}}/language/blobs.md
+[`Blob`]: {{rootUrl}}/language/blobs.md
+
+[`Map`]: {{rootUrl}}/language/object-maps.md
+[object map]: {{rootUrl}}/language/object-maps.md
+[object maps]: {{rootUrl}}/language/object-maps.md
+
+[`timestamp`]: {{rootUrl}}/language/timestamps.md
+[timestamp]: {{rootUrl}}/language/timestamps.md
+[timestamps]: {{rootUrl}}/language/timestamps.md
+
+[comments]: {{rootUrl}}/language/comments.md
+[doc-comments]: {{rootUrl}}/language/doc-comments.md
+[function]: {{rootUrl}}/language/functions.md
+[functions]: {{rootUrl}}/language/functions.md
+[function overloading]: {{rootUrl}}/rust/overloading.md
+[fallible function]: {{rootUrl}}/rust/fallible.md
+[fallible functions]: {{rootUrl}}/rust/fallible.md
+[exception]: {{rootUrl}}/language/throw.md
+[exceptions]: {{rootUrl}}/language/throw.md
+[function pointer]: {{rootUrl}}/language/fn-ptr.md
+[function pointers]: {{rootUrl}}/language/fn-ptr.md
+[currying]: {{rootUrl}}/language/fn-curry.md
+[closure]: {{rootUrl}}/language/fn-closure.md
+[closures]: {{rootUrl}}/language/fn-closure.md
+[function namespace]: {{rootUrl}}/language/fn-namespaces.md
+[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
+[anonymous function]: {{rootUrl}}/language/fn-anon.md
+[anonymous functions]: {{rootUrl}}/language/fn-anon.md
+[operator overloading]: {{rootUrl}}/rust/operators.md
+
+[`Module`]: {{rootUrl}}/rust/modules/index.md
+[module]: {{rootUrl}}/rust/modules/index.md
+[modules]: {{rootUrl}}/rust/modules/index.md
+[module resolver]: {{rootUrl}}/rust/modules/resolvers/index.md
+[module resolvers]: {{rootUrl}}/rust/modules/resolvers/index.md
+[`global`]: {{rootUrl}}/language/global.md
+[variable resolver]: {{rootUrl}}/engine/var.md
+[strict variables]: {{rootUrl}}/engine/strict-var.md
+[fast operators]: {{rootUrl}}/start/builds/performance.md#fast-operators-mode
+[`private`]: {{rootUrl}}/language/modules/export.md
+
+[`eval`]: {{rootUrl}}/language/eval.md
+
+[OOP]: {{rootUrl}}/patterns/oop.md
+[DSL]: {{rootUrl}}/engine/dsl.md
+[DOS]: https://en.wikipedia.org/wiki/Denial-of-service_attack
+
+[safety]: {{rootUrl}}/safety/index.md
+[checked]: {{rootUrl}}/safety/checked.md
+[sand-boxed]: {{rootUrl}}/safety/sandbox.md
+[memory]: {{rootUrl}}/safety/memory.md
+[stack]: {{rootUrl}}/safety/stack.md
+[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
+[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
+[maximum number of operations]: {{rootUrl}}/safety/max-operations.md
+[maximum number of variables]: {{rootUrl}}/safety/max-variables.md
+[maximum number of functions]: {{rootUrl}}/safety/max-functions.md
+[maximum number of modules]: {{rootUrl}}/safety/max-modules.md
+[maximum length of strings]: {{rootUrl}}/safety/max-string-size.md
+[maximum size of arrays]: {{rootUrl}}/safety/max-array-size.md
+[maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md
+[progress]: {{rootUrl}}/safety/progress.md
+
+[script optimization]: {{rootUrl}}/engine/optimize/index.md
+[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/index.md
+[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/index.md
+[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/index.md
+
+[options]: {{rootUrl}}/engine/options.md
+
+[disable keywords and operators]: {{rootUrl}}/engine/disable-keywords.md
+[custom operator]: {{rootUrl}}/engine/custom-op.md
+[custom operators]: {{rootUrl}}/engine/custom-op.md
+[custom syntax]: {{rootUrl}}/engine/custom-syntax.md
+
+[debugger]: {{rootUrl}}/engine/debugging/debugger.md
+[`Debugger`]: {{rootUrl}}/engine/debugging/debugger.md
+[`debugger::Debugger`]: {{rootUrl}}/engine/debugging/debugger.md
+[break-point]: {{rootUrl}}/engine/debugging/break-points.md
+[break-points]: {{rootUrl}}/engine/debugging/break-points.md
+
+[`if`]: {{rootUrl}}/language/if.md
+[`switch`]: {{rootUrl}}/language/switch.md
+[`while`]: {{rootUrl}}/language/while.md
+[`do`]: {{rootUrl}}/language/do.md
+[`loop`]: {{rootUrl}}/language/loop.md
+[`for`]: {{rootUrl}}/language/for.md
+[`in`]: {{rootUrl}}/language/in.md
+[`exit`]: {{rootUrl}}/language/return.md#exit
+[`return`]: {{rootUrl}}/language/return.md
+[`throw`]: {{rootUrl}}/language/throw.md
+[`try`]: {{rootUrl}}/language/try-catch.md
+[`catch`]: {{rootUrl}}/language/try-catch.md
+[`import`]: {{rootUrl}}/language/modules/import.md
+[`export`]: {{rootUrl}}/language/modules/export.md
+
+[literal]: {{rootUrl}}/appendix/literals.md
+[literals]: {{rootUrl}}/appendix/literals.md
+
+[`ahash`]: https://crates.io/crates/ahash
+
+[`rhai-dylib`]: {{rootUrl}}/lib/rhai-dylib.md
+[`rhai-rand`]: {{rootUrl}}/lib/rhai-rand.md
+[`rhai-sci`]: {{rootUrl}}/lib/rhai-sci.md
+[`rhai-ml`]: {{rootUrl}}/lib/rhai-ml.md
+[`rhai-fs`]: {{rootUrl}}/lib/rhai-fs.md
+[`rhai-url`]: {{rootUrl}}/lib/rhai-url.md
+[`rhai-autodocs`]: {{rootUrl}}/lib/rhai-autodocs.md
diff --git a/rhai_engine/rhaibook/patterns/blocking.md b/rhai_engine/rhaibook/patterns/blocking.md
new file mode 100644
index 0000000..d88f08c
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/blocking.md
@@ -0,0 +1,62 @@
+Blocking/Async Function Calls
+=============================
+
+{{#include ../links.md}}
+
+
+```admonish danger.small "Warning: Async and scripting don't mix well"
+
+Otherwise, you reinvent the [_Callback Hell_](https://en.wiktionary.org/wiki/callback_hell)
+which is JavaScript before all the async extensions.
+```
+
+```admonish info "Usage scenarios"
+
+* A system's API contains async functions.
+```
+
+```admonish abstract "Key concepts"
+
+* This pattern is based upon the _[Multi-Threaded Synchronization](multi-threading.md)_ pattern.
+
+* An independent thread is used to run the scripting [`Engine`].
+
+* An MPSC channel (or any other appropriate synchronization primitive) is used to send function call
+ arguments, packaged as a message, to another Rust thread that will perform the actual async calls.
+
+* Results are marshaled back to the [`Engine`] thread via another MPSC channel.
+```
+
+Implementation
+--------------
+
+```admonish info.side "See also"
+
+See the _[Multi-Threaded Synchronization](multi-threading.md)_ pattern.
+```
+
+1. Spawn a thread to run the scripting [`Engine`]. Usually the [`sync`] feature is
+ _NOT_ used for this pattern.
+
+2. Spawn another thread (the `worker` thread) that can perform the actual async calls in Rust.
+ This thread may actually be the main thread of the program.
+
+3. Create a pair of MPSC channels (named `command` and `reply` below) for full-duplex
+ communications between the two threads.
+
+4. Register async API function to the [`Engine`] with a closure that captures the MPSC end-points.
+
+5. If there are more than one async function, the receive end-point on the `reply` channel can simply be cloned.
+ The send end-point on the `command` channel can be wrapped in an `Arc>` for shared access.
+
+6. In the async function, the name of the function and call arguments are serialized into JSON
+ (or any appropriate message format) and sent to `command` channel, where they'll be removed
+ by the `worker` thread and the appropriate async function called.
+
+7. The [`Engine`] blocks on the function call, waiting for a reply message on the `reply` channel.
+
+8. When the async function call complete on the `worker` thread, the result is sent back to
+ the [`Engine`] thread via the `reply` channel.
+
+9. After the result is obtained from the `reply` channel, the [`Engine`] returns it as the return value
+ of the function call, ending the block and continuing evaluation.
diff --git a/rhai_engine/rhaibook/patterns/builder.md b/rhai_engine/rhaibook/patterns/builder.md
new file mode 100644
index 0000000..bd69961
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/builder.md
@@ -0,0 +1,307 @@
+Builder Pattern / Fluent API
+============================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* An API uses the [Builder Pattern](https://en.wikipedia.org/wiki/Builder_pattern) or a [fluent API](https://en.wikipedia.org/wiki/Fluent_interface).
+
+* The builder type is not necessarily `Clone`.
+```
+
+```admonish abstract "Key concepts"
+
+* Wrap the builder type in shared interior mutability (aka `Rc>` or `Arc>`).
+```
+
+```admonish tip.small "Tip: Fluent API"
+
+This same pattern can be used to implement any [_fluent API_](https://en.wikipedia.org/wiki/Fluent_interface).
+```
+
+
+Implementation With Clonable Builder Type
+-----------------------------------------
+
+This assumes that the builder type implements `Clone`.
+This is the most common scenario.
+
+```rust
+/// Builder for `Foo` instances.
+#[derive(Clone)]
+pub struct FooBuilder {
+ /// The `foo` option.
+ foo: i64,
+ /// The `bar` option.
+ bar: bool,
+ /// The `baz` option.
+ baz: String,
+}
+
+/// `FooBuilder` API which uses moves.
+impl FooBuilder {
+ /// Creates a new builder for `Foo`.
+ pub fn new() -> Self {
+ Self { foo: 0, bar: false, baz: String::new() }
+ }
+ /// Sets the `foo` option.
+ pub fn with_foo(mut self, foo: i64) -> Self {
+ self.foo = foo;
+ self
+ }
+ /// Sets the `bar` option.
+ pub fn with_bar(mut self, bar: bool) -> Self {
+ self.bar = bar;
+ self
+ }
+ /// Sets the `baz` option.
+ pub fn with_baz(mut self, baz: &str) -> Self {
+ self.baz = baz.to_string();
+ self
+ }
+ /// Builds the `Foo` instance.
+ pub fn build(self) -> Foo {
+ Foo { foo: self.foo, bar: self.bar, baz: self.baz }
+ }
+}
+
+let mut engine = Engine::new();
+
+engine
+ .register_fn("get_foo", FooBuilder::new)
+ .register_fn("with_foo", FooBuilder::with_foo)
+ .register_fn("with_bar", FooBuilder::with_bar)
+ .register_fn("with_baz", FooBuilder::with_baz)
+ .register_fn("create", FooBuilder::build);
+```
+
+
+Implementation With Mutable Reference
+-------------------------------------
+
+This assumes that the builder type's API uses mutable references.
+The builder type does not need to implement `Clone`.
+
+```rust
+use rhai::plugin::*;
+
+/// Builder for `Foo` instances.
+/// Notice that this type does not need to be `Clone`.
+pub struct FooBuilder {
+ /// The `foo` option.
+ foo: i64,
+ /// The `bar` option.
+ bar: bool,
+ /// The `baz` option.
+ baz: String,
+}
+
+/// Builder type API uses mutable references.
+impl FooBuilder {
+ /// Creates a new builder for `Foo`.
+ pub fn new() -> Self {
+ Self { foo: 0, bar: false, baz: String::new() }
+ }
+ /// Sets the `foo` option.
+ pub fn with_foo(&mut self, foo: i64) -> &mut Self {
+ self.foo = foo; self
+ }
+ /// Sets the `bar` option.
+ pub fn with_bar(&mut self, bar: bool) -> &mut Self {
+ self.bar = bar; self
+ }
+ /// Sets the `baz` option.
+ pub fn with_baz(&mut self, baz: &str) -> &mut Self {
+ self.baz = baz.to_string(); self
+ }
+ /// Builds the `Foo` instance.
+ pub fn build(&self) -> Foo {
+ Foo { foo: self.foo, bar: self.bar, baz: self.baz.clone() }
+ }
+}
+
+/// Builder for `Foo`.
+#[export_module]
+pub mod foo_builder {
+ use super::{Foo, FooBuilder as BuilderImpl};
+ use std::cell::RefCell;
+ use std::rc::Rc;
+
+ /// The builder for `Foo`.
+ // This type is `Clone`.
+ pub type FooBuilder = Rc>;
+
+ /// Creates a new builder for `Foo`.
+ pub fn default() -> FooBuilder {
+ Rc::new(RefCell::new(BuilderImpl::new()))
+ }
+ /// Sets the `foo` option.
+ #[rhai_fn(global, pure)]
+ pub fn with_foo(builder: &mut FooBuilder, foo: i64) -> FooBuilder {
+ builder.set_foo(foo);
+ builder.clone()
+ }
+ /// Sets the `bar` option.
+ #[rhai_fn(global, pure)]
+ pub fn with_bar(builder: &mut FooBuilder, bar: bool) -> FooBuilder {
+ builder.set_bar(bar);
+ builder.clone()
+ }
+ /// Sets the `baz` option.
+ #[rhai_fn(global, pure)]
+ pub fn with_baz(builder: &mut FooBuilder, baz: &str) -> FooBuilder {
+ builder.set_baz(baz);
+ builder.clone()
+ }
+ /// Builds the `Foo` instance.
+ #[rhai_fn(global, pure)]
+ pub fn create(builder: &mut FooBuilder) -> Foo {
+ builder.borrow().build()
+ }
+}
+```
+
+
+Implementation With Moves
+-------------------------
+
+What if the builder type's API relies on moves instead of mutable references?
+And the builder type does not implement `Clone`?
+
+Not too worry: the following trick has you covered!
+
+```rust
+use rhai::plugin::*;
+
+/// Builder for `Foo` instances.
+/// Notice that this type does not need to be `Clone`.
+pub struct FooBuilder {
+ /// The `foo` option.
+ foo: i64,
+ /// The `bar` option.
+ bar: bool,
+ /// The `baz` option.
+ baz: String,
+}
+
+/// `FooBuilder` API which uses moves.
+impl FooBuilder {
+ /// Creates a new builder for `Foo`.
+ pub fn new() -> Self {
+ Self { foo: 0, bar: false, baz: String::new() }
+ }
+ /// Sets the `foo` option.
+ pub fn with_foo(mut self, foo: i64) -> Self {
+ self.foo = foo;
+ self
+ }
+ /// Sets the `bar` option.
+ pub fn with_bar(mut self, bar: bool) -> Self {
+ self.bar = bar;
+ self
+ }
+ /// Sets the `baz` option.
+ pub fn with_baz(mut self, baz: &str) -> Self {
+ self.baz = baz.to_string();
+ self
+ }
+ /// Builds the `Foo` instance.
+ pub fn build(self) -> Foo {
+ Foo { foo: self.foo, bar: self.bar, baz: self.baz }
+ }
+}
+
+/// Builder for `Foo`.
+#[export_module]
+pub mod foo_builder {
+ use super::{Foo, FooBuilder as BuilderImpl};
+ use std::cell::RefCell;
+ use std::mem;
+ use std::rc::Rc;
+
+ /// The builder for `Foo`.
+ // This type is `Clone`.
+ // An `Option` is used for easy extraction of the builder type.
+ // If it is `None` then the builder is already consumed.
+ pub type FooBuilder = Rc>>;
+
+ /// Creates a new builder for `Foo`.
+ pub fn default() -> FooBuilder {
+ Rc::new(RefCell::new(Some(BuilderImpl::new())))
+ }
+ /// Sets the `foo` option.
+ #[rhai_fn(return_raw, global, pure)]
+ pub fn with_foo(builder: &mut FooBuilder, foo: i64) -> Result> {
+ let b = &mut *builder.borrow_mut();
+
+ if let Some(obj) = mem::take(b) {
+ *b = Some(obj.with_foo(foo));
+ Ok(builder.clone())
+ } else {
+ Err("Builder is already consumed".into())
+ }
+ }
+ /// Sets the `bar` option.
+ #[rhai_fn(return_raw, global, pure)]
+ pub fn with_bar(builder: &mut FooBuilder, bar: bool) -> Result> {
+ let b = &mut *builder.borrow_mut();
+
+ if let Some(obj) = mem::take(b) {
+ *b = Some(obj.with_bar(bar));
+ Ok(builder.clone())
+ } else {
+ Err("Builder is already consumed".into())
+ }
+ }
+ /// Sets the `baz` option.
+ #[rhai_fn(return_raw, global, pure)]
+ pub fn with_baz(builder: &mut FooBuilder, baz: &str) -> Result> {
+ let b = &mut *builder.borrow_mut();
+
+ if let Some(obj) = mem::take(b) {
+ *b = Some(obj.with_baz(baz));
+ Ok(builder.clone())
+ } else {
+ Err("Builder is already consumed".into())
+ }
+ }
+ /// Builds the `Foo` instance.
+ #[rhai_fn(return_raw, global, pure)]
+ pub fn create(builder: &mut FooBuilder) -> Result> {
+ let b = &mut *builder.borrow_mut();
+
+ if let Some(obj) = mem::take(b) {
+ Ok(obj.build())
+ } else {
+ Err("Builder is already consumed".into())
+ }
+ }
+}
+```
+
+
+Usage
+-----
+
+It is easy to see that the Rhai script API mirrors the Rust API almost perfectly.
+
+```rust
+┌──────┐
+│ Rust │
+└──────┘
+
+let mut engine = Engine::new();
+
+engine.register_static_module("Foo", exported_module!(foo_builder).into());
+
+let foo = FooBuilder::new().with_foo(42).with_bar(true).with_baz("Hello").build();
+
+
+┌─────────────┐
+│ Rhai script │
+└─────────────┘
+
+let foo = Foo::default().with_foo(42).with_bar(true).with_baz("Hello").create();
+```
diff --git a/rhai_engine/rhaibook/patterns/config.md b/rhai_engine/rhaibook/patterns/config.md
new file mode 100644
index 0000000..3ae9436
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/config.md
@@ -0,0 +1,171 @@
+Loadable Configuration
+======================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system where settings and configurations are complex and logic-driven.
+
+* Where said system is too complex to configure via standard configuration file formats such as
+ `JSON`, `TOML` or `YAML`.
+
+* The system is complex enough to require a full programming language to configure.
+ Essentially _configuration by code_.
+
+* Yet the configuration must be flexible, late-bound and dynamically loadable, just like a
+ configuration file.
+```
+
+```admonish abstract "Key concepts"
+
+* Leverage the loadable [modules] of Rhai. The [`no_module`] feature must not be on.
+
+* Expose the configuration API. Use separate scripts to configure that API.
+ Dynamically load scripts via the `import` statement.
+
+* Leverage [function overloading] to simplify the API design.
+
+* Since Rhai is _sand-boxed_, it cannot mutate the environment. To modify the external
+ configuration object via an API, it must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for
+ [`sync`]) and shared to the [`Engine`].
+```
+
+
+Implementation
+--------------
+
+### Configuration type
+
+```rust
+#[derive(Debug, Clone, Default)]
+struct Config {
+ id: String,
+ some_field: i64,
+ some_list: Vec,
+ some_map: HashMap,
+}
+```
+
+### Make shared object
+
+```rust
+type SharedConfig = Rc>;
+
+let config = SharedConfig::default();
+```
+
+or in multi-threaded environments with the [`sync`] feature, use one of the following:
+
+```rust
+type SharedConfig = Arc>;
+
+type SharedConfig = Arc>;
+```
+
+### Register config API
+
+The trick to building a Config API is to clone the shared configuration object and move it into each
+function registration via a closure.
+
+Therefore, it is not possible to use a [plugin module] to achieve this, and each function must be
+registered one after another.
+
+```rust
+// Notice 'move' is used to move the shared configuration object into the closure.
+let cfg = config.clone();
+engine.register_fn("config_set_id", move |id: String| cfg.borrow_mut().id = id);
+
+let cfg = config.clone();
+engine.register_fn("config_get_id", move || cfg.borrow().id.clone());
+
+let cfg = config.clone();
+engine.register_fn("config_set", move |value: i64| cfg.borrow_mut().some_field = value);
+
+// Remember Rhai functions can be overloaded when designing the API.
+
+let cfg = config.clone();
+engine.register_fn("config_add", move |value: String|
+ cfg.borrow_mut().some_list.push(value)
+);
+
+let cfg = config.clone();
+engine.register_fn("config_add", move |values: &mut Array|
+ cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
+);
+
+let cfg = config.clone();
+engine.register_fn("config_add", move |key: String, value: bool|
+ cfg.borrow_mut().some_map.insert(key, value)
+);
+
+let cfg = config.clone();
+engine.register_fn("config_contains", move |value: String|
+ cfg.borrow().some_list.contains(&value)
+);
+
+let cfg = config.clone();
+engine.register_fn("config_is_set", move |value: String|
+ cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
+);
+```
+
+### Configuration script
+
+```rust
+┌────────────────┐
+│ my_config.rhai │
+└────────────────┘
+
+config_set_id("hello");
+
+config_add("foo"); // add to list
+config_add("bar", true); // add to map
+
+if config_contains("hey") || config_is_set("hey") {
+ config_add("baz", false); // add to map
+}
+```
+
+### Load the configuration
+
+```rust
+import "my_config"; // run configuration script without creating a module
+
+let id = config_get_id();
+
+id == "hello";
+```
+
+
+Consider a Custom Syntax
+------------------------
+
+This is probably one of the few scenarios where a [custom syntax] can be recommended.
+
+A properly-designed [custom syntax] can make the configuration file clean, simple to write,
+easy to understand and quick to modify.
+
+For example, the above configuration example may be expressed by this custom syntax:
+
+```rust
+┌────────────────┐
+│ my_config.rhai │
+└────────────────┘
+
+// Configure ID
+id "hello";
+
+// Add to list
+list + "foo";
+
+// Add to map
+map "bar" => true;
+
+if config contains "hey" || config is_set "hey" {
+ map "baz" => false;
+}
+```
+
+Notice that `contains` and `is_set` may also be implemented as a [custom operator].
diff --git a/rhai_engine/rhaibook/patterns/constants.md b/rhai_engine/rhaibook/patterns/constants.md
new file mode 100644
index 0000000..0121cc4
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/constants.md
@@ -0,0 +1,168 @@
+Global Constants
+================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* Script has a lot of duplicated [constants] used inside [functions].
+
+* For easier management, [constants] are declared at the top of the script.
+
+* As Rhai [functions] are pure, they cannot access [constants] declared at global level
+ except through [`global`].
+
+* Sprinkling large number of [`global::CONSTANT`][`global`] throughout the script makes
+ it slow and cumbersome.
+
+* Using [`global`] or a [variable resolver] defeats
+ [constants propagation]({{rootUrl}}/engine/optimize/constants.md) in [script optimization].
+```
+
+```admonish abstract "Key concepts"
+
+* The key to global [constants] is to use them to [optimize][script optimization] a script.
+ Otherwise, it would be just as simple to pass the constants into a custom [`Scope`] instead.
+
+* The script is first compiled into an [`AST`], and all [constants] are extracted.
+
+* The [constants] are then supplied to [re-optimize][script optimization] the [`AST`].
+
+* This pattern also works under [_Strict Variables Mode_][strict variables].
+```
+
+
+Example
+-------
+
+Assume that the following Rhai script needs to work (but it doesn't).
+
+```rust
+// These are constants
+
+const FOO = 1;
+const BAR = 123;
+const MAGIC_NUMBER = 42;
+
+fn get_magic() {
+ MAGIC_NUMBER // <- oops! 'MAGIC_NUMBER' not found!
+}
+
+fn calc_foo(x) {
+ x * global::FOO // <- works but cumbersome; not desirable!
+}
+
+let magic = get_magic() * BAR;
+
+let x = calc_foo(magic);
+
+print(x);
+```
+
+
+Step 1 – Compile Script into `AST`
+----------------------------------------
+
+Compile the script into [`AST`] form.
+
+Normally, it is useful to disable [optimizations][script optimization] at this stage since
+the [`AST`] will be re-optimized later.
+
+[_Strict Variables Mode_][strict variables] must be OFF for this to work.
+
+```rust
+// Turn Strict Variables Mode OFF (if necessary)
+engine.set_strict_variables(false);
+
+// Turn optimizations OFF
+engine.set_optimization_level(OptimizationLevel::None);
+
+let ast = engine.compile("...")?;
+```
+
+
+Step 2 – Extract Constants
+--------------------------------
+
+Use [`AST::iter_literal_variables`](https://docs.rs/rhai/{{version}}/rhai/struct.AST.html#method.iter_literal_variables)
+to extract top-level [constants] from the [`AST`].
+
+```rust
+let mut scope = Scope::new();
+
+// Extract all top-level constants without running the script
+ast.iter_literal_variables(true, false).for_each(|(name, _, value)|
+ scope.push_constant(name, value);
+);
+
+// 'scope' now contains: FOO, BAR, MAGIC_NUMBER
+```
+
+
+Step 3a – Propagate Constants
+-----------------------------------
+
+[Re-optimize][script optimization] the [`AST`] using the new constants.
+
+```rust
+// Turn optimization back ON
+engine.set_optimization_level(OptimizationLevel::Simple);
+
+let ast = engine.optimize_ast(&scope, ast, engine.optimization_level());
+```
+
+
+Step 3b – Recompile Script (Alternative)
+----------------------------------------------
+
+If [_Strict Variables Mode_][strict variables] is used, however, it is necessary to re-compile the
+script in order to detect undefined [variable] usages.
+
+```rust
+// Turn Strict Variables Mode back ON
+engine.set_strict_variables(true);
+
+// Turn optimization back ON
+engine.set_optimization_level(OptimizationLevel::Simple);
+
+// Re-compile the script using constants in 'scope'
+let ast = engine.compile_with_scope(&scope, "...")?;
+```
+
+
+Step 4 – Run the Script
+-----------------------------
+
+At this step, the [`AST`] is now optimized with constants propagated into all access sites.
+
+The script essentially becomes:
+
+```rust
+// These are constants
+
+const FOO = 1;
+const BAR = 123;
+const MAGIC_NUMBER = 42;
+
+fn get_magic() {
+ 42 // <- constant replaced by value
+}
+
+fn calc_foo(x) {
+ x * global::FOO
+}
+
+let magic = get_magic() * 123; // <- constant replaced by value
+
+let x = calc_foo(magic);
+
+print(x);
+```
+
+Run it via `Engine::run_ast` or `Engine::eval_ast`.
+
+```rust
+// The 'scope' is no longer necessary
+engine.run_ast(&ast)?;
+```
diff --git a/rhai_engine/rhaibook/patterns/control.md b/rhai_engine/rhaibook/patterns/control.md
new file mode 100644
index 0000000..708f044
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/control.md
@@ -0,0 +1,145 @@
+Scriptable Control Layer Over Rust Backend
+==========================================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system provides core functionalities, but no driving logic.
+
+* The driving logic must be dynamic and hot-loadable.
+
+* A script is used to drive the system and provide control intelligence.
+```
+
+```admonish abstract "Key concepts"
+
+* Expose a Control API.
+
+* Leverage [function overloading] to simplify the API design.
+
+* Since Rhai is _[sand-boxed]_, it cannot mutate anything outside of its internal environment.
+ To perform external actions via an API, the actual system must be wrapped in a `RefCell`
+ (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
+```
+
+```admonish danger "Using Rhai for games"
+
+Although this usage pattern appears a perfect fit for _game_ logic, avoid writing the _entire game_
+in Rhai. Performance will not be acceptable.
+
+Implement as much functionalities of the game engine in Rust as possible. Rhai integrates well with
+Rust so this is usually not a hinderance.
+
+Lift as much out of Rhai as possible. Use Rhai only for the logic that _must_ be dynamic or
+hot-loadable.
+```
+
+
+Implementation
+--------------
+
+There are two broad ways for Rhai to control an external system, both of which involve wrapping the
+system in a shared, interior-mutated object.
+
+This is one way which does not involve exposing the data structures of the external system,
+but only through exposing an abstract API primarily made up of functions.
+
+Use this when the API is relatively simple and clean, and the number of functions is small enough.
+
+For a complex API involving lots of functions, or an API that has a clear object structure, use the
+[Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern instead.
+
+
+### Functional API
+
+Assume that a system provides the following functional API:
+
+```rust
+struct EnergizerBunny;
+
+impl EnergizerBunny {
+ pub fn new () -> Self { ... }
+ pub fn go (&mut self) { ... }
+ pub fn stop (&mut self) { ... }
+ pub fn is_going (&self) { ... }
+ pub fn get_speed (&self) -> i64 { ... }
+ pub fn set_speed (&mut self, speed: i64) { ... }
+}
+```
+
+### Wrap API in shared object
+
+```rust
+pub type SharedBunny = Rc>;
+```
+
+or in multi-threaded environments with the [`sync`] feature, use one of the following:
+
+```rust
+pub type SharedBunny = Arc>;
+
+pub type SharedBunny = Arc>;
+```
+
+### Register control API
+
+The trick to building a Control API is to clone the shared API object and
+move it into each function registration via a closure.
+
+Therefore, it is not possible to use a [plugin module] to achieve this, and each function must be
+registered one after another.
+
+```rust
+// Notice 'move' is used to move the shared API object into the closure.
+let b = bunny.clone();
+engine.register_fn("bunny_power", move |on: bool| {
+ if on {
+ if b.borrow().is_going() {
+ println!("Still going...");
+ } else {
+ b.borrow_mut().go();
+ }
+ } else {
+ if b.borrow().is_going() {
+ b.borrow_mut().stop();
+ } else {
+ println!("Already out of battery!");
+ }
+ }
+});
+
+let b = bunny.clone();
+engine.register_fn("bunny_is_going", move || b.borrow().is_going());
+
+let b = bunny.clone();
+engine.register_fn("bunny_get_speed", move ||
+ if b.borrow().is_going() { b.borrow().get_speed() } else { 0 }
+);
+
+let b = bunny.clone();
+engine.register_fn("bunny_set_speed", move |speed: i64| -> Result<_, Box>
+ if speed <= 0 {
+ return Err("Speed must be positive!".into());
+ } else if speed > 100 {
+ return Err("Bunny will be going too fast!".into());
+ }
+
+ if b.borrow().is_going() {
+ b.borrow_mut().set_speed(speed)
+ } else {
+ return Err("Bunny is not yet going!".into());
+ }
+
+ Ok(())
+);
+```
+
+### Use the API
+
+```rust
+if !bunny_is_going() { bunny_power(true); }
+
+if bunny_get_speed() > 50 { bunny_set_speed(50); }
+```
diff --git a/rhai_engine/rhaibook/patterns/domain-tools.md b/rhai_engine/rhaibook/patterns/domain-tools.md
new file mode 100644
index 0000000..85331cd
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/domain-tools.md
@@ -0,0 +1,139 @@
+Domain-Specific Tools
+=====================
+
+{{#include ../links.md}}
+
+[bin tool]: {{rootUrl}}/start/bin.md
+[bin tools]: {{rootUrl}}/start/bin.md
+
+```admonish info "Usage scenario"
+
+* A system has a _domain-specific_ API, requiring [custom types] and/or
+ Rust [functions]({{rootUrl}}/rust/functions.md) to be registered and exposed to scripting.
+
+* The system's behavior is controlled by Rhai script [functions], such as in the
+ [_Scriptable Event Handler with State_](events.md), [_Control Layer_](control.md) or
+ [_Singleton Command Object_](singleton.md) patterns.
+
+* It is desirable to be able to _interactively_ test the system with different scripts,
+ and/or [debug][debugger] them.
+```
+
+```admonish abstract "Key concepts"
+
+* Leverage the pre-packaged [bin tools] – it is easy because each of them is
+ one single source file.
+
+* Modify the [`Engine`] creation code to include domain-specific registrations.
+```
+
+
+Implementation
+--------------
+
+### Copy necessary tool source files
+
+Each [bin tool] is a single source file.
+
+Download the necessary one(s) into the project's `bin` or `example` subdirectory.
+
+| Select this source file | To make |
+| --------------------------------------------------- | ------------------------------------- |
+| [`rhai-run.rs`]({{repoHome}}/src/bin/rhai-run.rs) | a simple script runner for the system |
+| [`rhai-repl.rs`]({{repoHome}}/src/bin/rhai-repl.rs) | an interactive REPL for the system |
+| [`rhai-dbg.rs`]({{repoHome}}/src/bin/rhai-dbg.rs) | a script debugger for the system |
+
+#### Example
+
+```sh
+rhai-run.rs -> /path/to/my_project/examples/test.rs
+rhai-repl.rs -> /path/to/my_project/examples/repl.rs
+rhai-dbg.rs -> /path/to/my_project/examples/db.rs
+```
+
+### Leverage `Engine` configuration code in the project
+
+Assume the project already contains configuration code for a customized [`Engine`].
+
+```rust
+use rhai::Engine;
+use rhai::plugin::*;
+
+// Domain-specific data types
+use my_project::*;
+
+#[export_module]
+mod my_domain_api {
+ :
+ :
+ // plugin module
+ :
+ :
+}
+
+// This function creates a new Rhai scripting engine and
+// configures it properly
+fn create_scripting_engine(config: MySystemConfig) -> Engine {
+ let mut engine = Engine::new();
+
+ // Register domain-specific API into the Engine
+ engine.register_type_with_name::("MyObject")
+ .register_type_with_name::("MyOtherObject")
+ .register_fn(...)
+ .register_fn(...)
+ .register_fn(...)
+ :
+ :
+ // register API functions
+ :
+ :
+ .register_get_set(...)
+ .register_index_get_set(...)
+ .register_fn(...);
+
+ // Plugin modules can be used to easily and quickly
+ // register an entire API
+ engine.register_global_module(exported_module!(my_domain_api).into());
+
+ // Configuration options in 'MySystemConfig' may be used
+ // to control the Engine's behavior
+ if config.strict_mode {
+ engine.set_strict_variables(true);
+ }
+
+ // Return the scripting engine
+ engine
+}
+```
+
+### Modify `Engine` creation
+
+Each [bin tool] contains a line that creates the main script [`Engine`].
+
+Modify it to call the project's creation function.
+
+```rust
+// Initialize scripting engine
+let mut engine = Engine::new();
+
+// Modify to this:
+let mut engine = create_scripting_engine(my_config);
+```
+
+### Make sure that Rhai has the appropriate feature(s)
+
+In the project's `Cargo.toml`, specify the Rhai dependency with `bin-features`.
+
+```toml
+[dependencies]
+rhai = { version = "{{version}}", features = ["bin-features"] }
+```
+
+### Rebuild
+
+Rebuild the project, which should automatically build all the customized tools.
+
+### Run tools
+
+Each customized tool now has access to the entire domain-specific API.
+Functions and [custom types] can be used in REPL, [debugging][debugger] etc.
diff --git a/rhai_engine/rhaibook/patterns/dynamic-const.md b/rhai_engine/rhaibook/patterns/dynamic-const.md
new file mode 100644
index 0000000..9a22ddd
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/dynamic-const.md
@@ -0,0 +1,55 @@
+Dynamic Constants Provider
+==========================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system has a _large_ number of constants, but only a minor set will be used by any script.
+
+* The system constants are expensive to load.
+
+* The system constants set is too massive to push into a custom [`Scope`].
+
+* The values of system constants are volatile and call-dependent.
+```
+
+```admonish abstract "Key concepts"
+
+* Use a [variable resolver] to intercept variable access.
+
+* Only load a variable when it is being used.
+
+* Perform a lookup based on variable name, and provide the correct data value.
+
+* May even perform back-end network access or look up the latest value from a database.
+```
+
+
+Implementation
+--------------
+
+```rust
+let mut engine = Engine::new();
+
+// Create shared data provider.
+// Assume that SystemValuesProvider::get(&str) -> Option gets a value.
+let provider = Arc::new(SystemValuesProvider::new());
+
+// Clone the shared provider
+let db = provider.clone();
+
+// Register a variable resolver.
+// Move the shared provider into the closure.
+engine.on_var(move |name, _, _| Ok(db.get(name).map(Dynamic::from)));
+```
+
+
+```admonish note.small "Values are constants"
+
+All values provided by a [variable resolver] are _[constants]_ due to their dynamic nature.
+They cannot be assigned to.
+
+In order to change values in an external system, register a dedicated API for that purpose.
+```
diff --git a/rhai_engine/rhaibook/patterns/enums.md b/rhai_engine/rhaibook/patterns/enums.md
new file mode 100644
index 0000000..17e20cc
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/enums.md
@@ -0,0 +1,281 @@
+Working With Rust Enums
+=======================
+
+{{#include ../links.md}}
+
+```admonish question.side.wide "Why enums are hard"
+
+Rust enum variants are not considered separate types.
+
+Although Rhai integrates fine with Rust enums (treated transparently as [custom types]),
+it is impossible (short of registering a complete API) to distinguish between individual
+variants and to extract internal data from them.
+```
+
+Enums in Rust can hold data and are typically used with _pattern matching_.
+
+Unlike Rust, Rhai does not have built-in pattern matching capabilities, so working with enum
+variants that contain embedded data is not an easy proposition.
+
+Since Rhai is dynamic and [variables] can hold any type of data, they are essentially enums
+by nature.
+
+Multiple distinct types can be stored in a single [`Dynamic`] without merging them into an enum
+as variants.
+
+This section outlines a number of possible solutions to work with Rust enums.
+
+
+Simulate an Enum API
+--------------------
+
+A [plugin module] is extremely handy in creating an entire API for a custom enum type.
+
+```rust
+use rhai::plugin::*;
+use rhai::{Dynamic, Engine, EvalAltResult};
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+enum MyEnum {
+ Foo,
+ Bar(i64),
+ Baz(String, bool),
+}
+
+// Create a plugin module with functions constructing the 'MyEnum' variants
+#[export_module]
+mod MyEnumModule {
+ // Constructors for 'MyEnum' variants
+
+ /// `MyEnum::Foo` with no inner data.
+ pub const Foo: MyEnum = MyEnum::Foo;
+
+ /// `MyEnum::Bar(value)`
+ pub fn Bar(value: i64) -> MyEnum { MyEnum::Bar(value) }
+
+ /// `MyEnum::Baz(name, flag)`
+ pub fn Baz(name: String, flag: bool) -> MyEnum { MyEnum::Baz(name, flag) }
+
+ /// Return the current variant of `MyEnum`.
+ #[rhai_fn(global, get = "enum_type", pure)]
+ pub fn get_type(my_enum: &mut MyEnum) -> String {
+ match my_enum {
+ MyEnum::Foo => "Foo".to_string(),
+ MyEnum::Bar(_) => "Bar".to_string(),
+ MyEnum::Baz(_, _) => "Baz".to_string()
+ }
+ }
+
+ /// Return the inner value.
+ #[rhai_fn(global, get = "value", pure)]
+ pub fn get_value(my_enum: &mut MyEnum) -> Dynamic {
+ match my_enum {
+ MyEnum::Foo => Dynamic::UNIT,
+ MyEnum::Bar(x) => Dynamic::from(x),
+ MyEnum::Baz(_, f) => Dynamic::from(f),
+ }
+ }
+
+ // Access to inner values by position
+
+ /// Return the value kept in the first position of `MyEnum`.
+ #[rhai_fn(global, get = "field_0", pure)]
+ pub fn get_field_0(my_enum: &mut MyEnum) -> Dynamic {
+ match my_enum {
+ MyEnum::Foo => Dynamic::UNIT,
+ MyEnum::Bar(x) => Dynamic::from(x),
+ MyEnum::Baz(x, _) => Dynamic::from(x)
+ }
+ }
+ /// Return the value kept in the second position of `MyEnum`.
+ #[rhai_fn(global, get = "field_1", pure)]
+ pub fn get_field_1(my_enum: &mut MyEnum) -> Dynamic {
+ match my_enum {
+ MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT,
+ MyEnum::Baz(_, x) => Dynamic::from(x)
+ }
+ }
+
+ // Printing
+ #[rhai_fn(global, name = "to_string", name = "to_debug", pure)]
+ pub fn to_string(my_enum: &mut MyEnum) -> String {
+ format!("{my_enum:?}")
+ }
+
+ // '==' and '!=' operators
+ #[rhai_fn(global, name = "==", pure)]
+ pub fn eq(my_enum: &mut MyEnum, my_enum2: MyEnum) -> bool {
+ my_enum == &my_enum2
+ }
+ #[rhai_fn(global, name = "!=", pure)]
+ pub fn neq(my_enum: &mut MyEnum, my_enum2: MyEnum) -> bool {
+ my_enum != &my_enum2
+ }
+}
+
+let mut engine = Engine::new();
+
+// Load the module as the module namespace "MyEnum"
+engine.register_type_with_name::("MyEnum")
+ .register_static_module("MyEnum", exported_module!(MyEnumModule).into());
+```
+
+With this API in place, working with enums feels almost the same as in Rust:
+
+```rust
+let x = MyEnum::Foo;
+
+let y = MyEnum::Bar(42);
+
+let z = MyEnum::Baz("hello", true);
+
+x == MyEnum::Foo;
+
+y != MyEnum::Bar(0);
+
+// Detect enum types
+
+x.enum_type == "Foo";
+
+y.enum_type == "Bar";
+
+z.enum_type == "Baz";
+
+// Extract enum fields
+
+x.value == ();
+
+y.value == 42;
+
+z.value == ();
+
+x.name == ();
+
+y.name == ();
+
+z.name == "hello";
+
+y.field_0 == 42;
+
+y.field_1 == ();
+
+z.field_0 == "hello";
+
+z.field_1 == true;
+```
+
+~~~admonish tip.small "Tip: Use a macro"
+
+For enums containing only variants with no inner data, it is convenient to use a simple macro to create
+such a [plugin module].
+
+```rust
+// The 'create_enum_module!' macro
+macro_rules! create_enum_module {
+ ($module:ident : $typ:ty => $($variant:ident),+) => {
+ #[export_module]
+ pub mod $module {
+ $(
+ #[allow(non_upper_case_globals)]
+ pub const $variant: $typ = <$typ>::$variant;
+ )*
+ }
+ };
+}
+
+// The enum
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum MyEnum { Foo, Bar, Baz, Hello, World }
+
+// This creates a plugin module called 'my_enum_module'
+expand_enum! { my_enum_module: MyEnum => Foo, Bar, Baz, Hello, World }
+```
+~~~
+
+
+Use Enums With `switch`
+-----------------------
+
+Since enums are internally treated as [custom types], they are not _literals_ and cannot be used as
+a match case in [`switch`] statements. This is quite a limitation because the equivalent `match`
+statement is commonly used in Rust to work with enums and bind variables to variant-internal data.
+
+It is possible, however, to [`switch`] through enum variants based on their types:
+
+```c , no_run
+switch my_enum.enum_type {
+ "Foo" => ...,
+ "Bar" => {
+ let value = foo.value;
+ ...
+ }
+ "Baz" => {
+ let name = foo.name;
+ let flag = foo.flag;
+ ...
+ }
+}
+```
+
+
+Use `switch` Through Arrays
+---------------------------
+
+Another way to work with Rust enums in a [`switch`] statement is through exposing the internal data
+(or at least those that act as effective _discriminants_) of each enum variant as a variable-length
+[array], usually with the name of the variant as the first item for convenience:
+
+```rust
+use rhai::Array;
+
+engine.register_get("enum_data", |my_enum: &mut MyEnum| {
+ match my_enum {
+ MyEnum::Foo => vec![ "Foo".into() ] as Array,
+
+ // Say, skip the data field because it is not
+ // used as a discriminant
+ MyEnum::Bar(value) => vec![ "Bar".into() ] as Array,
+
+ // Say, all fields act as discriminants
+ MyEnum::Baz(name, flag) => vec![
+ "Baz".into(), name.clone().into(), (*flag).into()
+ ] as Array
+ }
+});
+```
+
+Then it is a simple matter to match an enum via a [`switch`] expression.
+
+```c , no_run
+// Assume 'value' = 'MyEnum::Baz("hello", true)'
+// 'enum_data' creates a variable-length array with 'MyEnum' data
+let x = switch value.enum_data {
+ ["Foo"] => 1,
+ ["Bar"] => value.field_1,
+ ["Baz", "hello", false] => 4,
+ ["Baz", "hello", true] => 5,
+ _ => 9
+};
+
+x == 5;
+
+// Which is essentially the same as:
+let x = switch [value.type, value.field_0, value.field_1] {
+ ["Foo", (), ()] => 1,
+ ["Bar", 42, ()] => 42,
+ ["Bar", 123, ()] => 123,
+ :
+ ["Baz", "hello", false] => 4,
+ ["Baz", "hello", true] => 5,
+ _ => 9
+}
+```
+
+Usually, a helper method returns an [array] of values that can uniquely determine the [`switch`] case
+based on actual usage requirements – which means that it probably skips fields that contain
+data instead of discriminants.
+
+Then [`switch`] is used to very quickly match through a large number of [array] shapes and jump to the
+appropriate case implementation.
+
+Data fields can then be extracted from the enum independently.
diff --git a/rhai_engine/rhaibook/patterns/events-1.md b/rhai_engine/rhaibook/patterns/events-1.md
new file mode 100644
index 0000000..1237338
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/events-1.md
@@ -0,0 +1,148 @@
+Scriptable Event Handler with State Main Style
+==================================================
+
+{{#include ../links.md}}
+
+
+```admonish example
+
+A runnable example of this implementation is included.
+
+See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
+```
+
+
+Initialize Handler Instance with `Engine::call_fn_with_options`
+---------------------------------------------------------------
+
+Use `Engine::call_fn_with_options` instead of `Engine::call_fn` in order to retain new [variables]
+defined inside the custom [`Scope`] when running the `init` function.
+
+```rust
+impl Handler {
+ // Create a new 'Handler'.
+ pub fn new(path: impl Into) -> Self {
+ let mut engine = Engine::new();
+
+ :
+ // Code omitted
+ :
+
+ // Run the 'init' function to initialize the state, retaining variables.
+ let options = CallFnOptions::new()
+ .eval_ast(false) // do not re-evaluate the AST
+ .rewind_scope(false); // do not rewind scope
+
+ // In a real application you'd again be handling errors...
+ engine.call_fn_with_options(options, &mut scope, &ast, "init", ()).unwrap();
+
+ :
+ // Code omitted
+ :
+
+ Self { engine, scope, ast }
+ }
+}
+```
+
+
+Handler Scripting Style
+-----------------------
+
+Because the stored state is kept in a custom [`Scope`], it is possible for all [functions] defined
+in the handler script to access and modify these state variables.
+
+The API registered with the [`Engine`] can be also used throughout the script.
+
+### Sample script
+
+```js
+/// Initialize user-provided state (shadows system-provided state, if any).
+/// Because 'CallFnOptions::rewind_scope' is 'false', new variables introduced
+/// will remain inside the custom 'Scope'.
+fn init() {
+ // Add 'bool_state' and 'obj_state' as new state variables
+ let bool_state = false;
+ let obj_state = new_state(0);
+
+ // Constants can also be added!
+ const EXTRA_CONSTANT = "hello, world!";
+}
+
+/// Without 'OOP' support, the can only be a function.
+fn log(value, data) {
+ print(`State = ${value}, data = ${data}`);
+}
+
+/// 'start' event handler
+fn start(data) {
+ if bool_state {
+ throw "Already started!";
+ }
+ if obj_state.func1() || obj_state.func2() {
+ throw "Conditions not yet ready to start!";
+ }
+ bool_state = true;
+ obj_state.value = data;
+
+ // Constants 'MY_CONSTANT' and 'EXTRA_CONSTANT'
+ // in custom scope are also visible!
+ print(`MY_CONSTANT = ${MY_CONSTANT}`);
+ print(`EXTRA_CONSTANT = ${EXTRA_CONSTANT}`);
+}
+
+/// 'end' event handler
+fn end(data) {
+ if !bool_state {
+ throw "Not yet started!";
+ }
+ if !obj_state.func1() && !obj_state.func2() {
+ throw "Conditions not yet ready to end!";
+ }
+ bool_state = false;
+ obj_state.value = data;
+}
+
+/// 'update' event handler
+fn update(data) {
+ obj_state.value += process(data);
+
+ // Without OOP support, can only call function
+ log(obj_state.value, data);
+}
+```
+
+
+Disadvantages of This Style
+---------------------------
+
+This style is simple to implement and intuitive to use, but it is not very flexible.
+
+New user state [variables] are introduced by evaluating a special initialization script [function]
+(e.g. `init`) that defines them, and `Engine::call_fn_with_scope` is used to keep them inside the
+custom [`Scope`] (together with setting `CallFnOptions::rewind_scope` to `false`).
+
+However, this has the disadvantage that no other [function] can introduce new state [variables],
+otherwise they'd simply _[shadow]_ existing [variables] in the custom [`Scope`]. Thus, [functions]
+are called during events via `Engine::call_fn` which does not retain any [variables].
+
+When there are a large number of state [variables], this style also makes it easy for local
+[variables] defined in user [functions] to accidentally _[shadow]_ a state [variable] with a
+[variable] that just happens to be the same name.
+
+```rust
+// 'start' event handler
+fn start(data) {
+ let bool_state = false; // <- oops! bad variable name!
+
+ : // there is now no way to access the
+ : // state variable 'bool_state'...
+
+ if bool_state { // <- 'bool_state' is not the right thing
+ ... // unless this is what you actually want
+ }
+
+ :
+ :
+}
+```
diff --git a/rhai_engine/rhaibook/patterns/events-2.md b/rhai_engine/rhaibook/patterns/events-2.md
new file mode 100644
index 0000000..a5eb3b3
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/events-2.md
@@ -0,0 +1,190 @@
+Scriptable Event Handler with State JS Style
+================================================
+
+{{#include ../links.md}}
+
+
+```admonish example
+
+A runnable example of this implementation is included.
+
+See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
+```
+
+
+Keep State in Object Map
+------------------------
+
+This style allows defining new user state [variables] everywhere by packaging them all inside an
+[object map], which is then exposed via the `this` pointer.
+
+Because this scripting style resembles JavaScript, it is so named.
+
+State variables can be freely created by _all_ [functions] (not just the `init` [function]).
+
+The event handler type needs to hold this [object map] instead of a custom [`Scope`].
+
+```rust
+use rhai::{Engine, Scope, Dynamic, AST};
+
+// Event handler
+struct Handler {
+ // Scripting engine
+ pub engine: Engine,
+ // The custom 'Scope' can be used to hold global constants
+ pub scope: Scope<'static>,
+ // Use an object map (as a 'Dynamic') to keep stored state
+ pub states: Dynamic,
+ // Program script
+ pub ast: AST
+}
+```
+
+
+Bind Object Map to `this` Pointer
+---------------------------------
+
+Initialization can simply be done via binding the [object map] containing global states to the
+`this` pointer.
+
+```rust
+impl Handler {
+ // Create a new 'Handler'.
+ pub fn new(path: impl Into) -> Self {
+ let mut engine = Engine::new();
+
+ :
+ // Code omitted
+ :
+
+ // Use an object map to hold state
+ let mut states = Map::new();
+
+ // Default states can be added
+ states.insert("bool_state".into(), Dynamic::FALSE);
+
+ // Convert the object map into 'Dynamic'
+ let mut states: Dynamic = states.into();
+
+ // Use 'call_fn_with_options' instead of 'call_fn' to bind the 'this' pointer
+ let options = CallFnOptions::new()
+ .eval_ast(false) // do not re-evaluate the AST
+ .rewind_scope(true) // rewind scope
+ .bind_this_ptr(&mut states); // bind the 'this' pointer
+
+ // In a real application you'd again be handling errors...
+ engine.call_fn_with_options(options, &mut scope, &ast, "init", ()).unwrap();
+
+ :
+ // Code omitted
+ :
+
+ Self { engine, scope, states, ast }
+ }
+}
+```
+
+
+Bind `this` Pointer During Events Handling
+------------------------------------------
+
+Events handling should also use `Engine::call_fn_with_options` to bind the [object map] containing
+global states to the `this` pointer via `CallFnOptions::this_ptr`.
+
+```rust
+pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Dynamic {
+ let engine = &self.engine;
+ let scope = &mut self.scope;
+ let states = &mut self.states;
+ let ast = &self.ast;
+
+ let options = CallFnOptions::new()
+ .eval_ast(false) // do not re-evaluate the AST
+ .rewind_scope(true) // rewind scope
+ .bind_this_ptr(&mut states); // bind the 'this' pointer
+
+ match event_name {
+ // In a real application you'd be handling errors...
+ "start" => engine.call_fn_with_options(options, scope, ast, "start", (event_data,)).unwrap(),
+ :
+ :
+ }
+}
+```
+
+
+Handler Scripting Style
+-----------------------
+
+```admonish note.side "No shadowing"
+
+Notice that `this` can never be [shadowed][shadowing] because it is not a valid [variable] name.
+```
+
+Because the stored state is kept in an [object map], which in turn is bound to `this`, it is
+necessary for [functions] to always access or modify these state [variables] via the `this` pointer.
+
+As it is impossible to declare a local [variable] named `this`, there is no risk of accidentally
+_[shadowing]_ a state [variable].
+
+Because an [object map] is used to hold state values, it is even possible to add user-defined
+[functions], leveraging the [OOP] support for [object maps].
+
+### Sample script
+
+```js
+/// Initialize user-provided state.
+/// State is stored inside an object map bound to 'this'.
+fn init() {
+ // Can detect system-provided default states!
+ // Add 'bool_state' as new state variable if one does not exist
+ if "bool_state" !in this {
+ this.bool_state = false;
+ }
+ // Add 'obj_state' as new state variable (overwrites any existing)
+ this.obj_state = new_state(0);
+
+ // Can also add OOP-style functions!
+ this.log = |x| print(`State = ${this.obj_state.value}, data = ${x}`);
+}
+
+/// 'start' event handler
+fn start(data) {
+ // Access state variables via 'this'
+ if this.bool_state {
+ throw "Already started!";
+ }
+
+ // New state variables can be created anywhere
+ this.start_mode = data;
+
+ if this.obj_state.func1() || this.obj_state.func2() {
+ throw "Conditions not yet ready to start!";
+ }
+ this.bool_state = true;
+ this.obj_state.value = data;
+
+ // Constant 'MY_CONSTANT' in custom scope is also visible!
+ print(`MY_CONSTANT = ${MY_CONSTANT}`);
+}
+
+/// 'end' event handler
+fn end(data) {
+ if !this.bool_state || !("start_mode" in this) {
+ throw "Not yet started!";
+ }
+ if !this.obj_state.func1() && !this.obj_state.func2() {
+ throw "Conditions not yet ready to end!";
+ }
+ this.bool_state = false;
+ this.obj_state.value = data;
+}
+
+/// 'update' event handler
+fn update(data) {
+ this.obj_state.value += process(data);
+
+ // Call user-defined function OOP-style!
+ this.log(data);
+}
+```
diff --git a/rhai_engine/rhaibook/patterns/events-3.md b/rhai_engine/rhaibook/patterns/events-3.md
new file mode 100644
index 0000000..ff15dae
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/events-3.md
@@ -0,0 +1,163 @@
+Scriptable Event Handler with State Map Style
+=================================================
+
+{{#include ../links.md}}
+
+
+```admonish example
+
+A runnable example of this implementation is included.
+
+See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.
+```
+
+
+I Hate `this`! How Can I Get Rid of It?
+----------------------------------------
+
+You're using Rust and you don't want people to think you're writing lowly JavaScript?
+
+Taking inspiration from the [_JS Style_](events-2.md), a slight modification of the
+[_Main Style_](events-1.md) is to store all states inside an [object map] inside a custom [`Scope`].
+
+Nevertheless, instead of writing `this.variable_name` everywhere to access a state variable
+(in the [_JS Style_](events-2.md)), you'd write `state.variable_name` instead.
+
+It is up to you to decide whether this is an improvement!
+
+
+Handler Initialization
+----------------------
+
+```admonish note.side "No shadowing 'state'"
+
+Notice that a [variable definition filter] is used to prevent [shadowing] of the states [object map].
+```
+
+Implementation wise, this style follows closely the [_Main Style_](events-1.md), but a single
+[object map] is added to the custom [`Scope`] which holds all state values.
+
+Global [constants] can still be added to the custom [`Scope`] as normal and used through the script.
+
+Calls to the `init` [function] no longer need to avoid rewinding the [`Scope`] because state
+[variables] are added as properties under the states [object map].
+
+```rust
+impl Handler {
+ // Create a new 'Handler'.
+ pub fn new(path: impl Into) -> Self {
+ let mut engine = Engine::new();
+
+ // Forbid shadowing of 'state' variable
+ engine.on_def_var(|_, info, _| Ok(info.name != "state"));
+
+ :
+ // Code omitted
+ :
+
+ // Use an object map to hold state
+ let mut states = Map::new();
+
+ // Default states can be added
+ states.insert("bool_state".into(), Dynamic::FALSE);
+
+ // Add the main states-holding object map and call it 'state'
+ scope.push("state", states);
+
+ // Just a simple 'call_fn' can do here because we're rewinding the 'Scope'
+ // In a real application you'd again be handling errors...
+ engine.call_fn(&mut scope, &ast, "init", ()).unwrap();
+
+ :
+ // Code omitted
+ :
+
+ Self { engine, scope, ast }
+ }
+}
+```
+
+
+Handler Scripting Style
+-----------------------
+
+The stored state is kept in an [object map] in the custom [`Scope`].
+
+In this example, that [object map] is named `state`, but it can be any name.
+
+### User-defined functions in state
+
+Because an [object map] is used to hold state values, it is even possible to add user-defined
+[functions], leveraging the [OOP] support for [object maps].
+
+However, within these user-defined [functions], the `this` pointer binds to the [object map].
+Therefore, the variable-accessing syntax is different from the main body of the script.
+
+```js
+fn do_action() {
+ // Access state: `state.xxx`
+ state.number = 42;
+
+ // Add OOP functions - you still need to use `this`...
+ state.log = |x| print(`State = ${this.value}, data = ${x}`);
+}
+```
+
+### Sample script
+
+```js
+/// Initialize user-provided state.
+/// State is stored inside an object map bound to 'state'.
+fn init() {
+ // Add 'bool_state' as new state variable if one does not exist
+ if "bool_state" !in state {
+ state.bool_state = false;
+ }
+ // Add 'obj_state' as new state variable (overwrites any existing)
+ state.obj_state = new_state(0);
+
+ // Can also add OOP-style functions!
+ state.log = |x| print(`State = ${this.obj_state.value}, data = ${x}`);
+}
+
+/// 'start' event handler
+fn start(data) {
+ // Can detect system-provided default states!
+ // Access state variables in 'state'
+ if state.bool_state {
+ throw "Already started!";
+ }
+
+ // New values can be added to the state
+ state.start_mode = data;
+
+ if state.obj_state.func1() || state.obj_state.func2() {
+ throw "Conditions not yet ready to start!";
+ }
+ state.bool_state = true;
+ state.obj_state.value = data;
+
+ // Constant 'MY_CONSTANT' in custom scope is also visible!
+ print(`MY_CONSTANT = ${MY_CONSTANT}`);
+}
+
+/// 'end' event handler
+fn end(data) {
+ if !state.bool_state || "start_mode" !in state {
+ throw "Not yet started!";
+ }
+ if !state.obj_state.func1() && !state.obj_state.func2() {
+ throw "Conditions not yet ready to end!";
+ }
+ state.bool_state = false;
+ state.obj_state.value = data;
+}
+
+/// 'update' event handler
+fn update(data) {
+ state.obj_state.value += process(data);
+
+ // Call user-defined function OOP-style!
+ state.log(data);
+}
+```
diff --git a/rhai_engine/rhaibook/patterns/events.md b/rhai_engine/rhaibook/patterns/events.md
new file mode 100644
index 0000000..2f72e0a
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/events.md
@@ -0,0 +1,232 @@
+Scriptable Event Handler with State
+===================================
+
+{{#include ../links.md}}
+
+
+```admonish tip "IMPORTANT PATTERN"
+
+In many usage scenarios, a scripting engine is used to provide flexibility in event handling.
+
+That means to execute certain **actions** in response to certain **_events_** that occur at run-time,
+and scripts are used to provide flexibility for coding those actions.
+
+You'd be surprised how many applications fit under this pattern – they are all essentially
+event handling systems.
+```
+
+```admonish example "Examples"
+
+Because of the importance of this pattern, runnable examples are included.
+
+See the [_Examples_](../start/examples/rust.md) section for details.
+```
+
+```admonish info "Usage scenario"
+
+* A system sends _events_ that must be handled.
+
+* Flexibility in event handling must be provided, through user-side scripting.
+
+* State must be kept between invocations of event handlers.
+
+* State may be provided by the system or the user, or both.
+
+* Default implementations of event handlers can be provided.
+```
+
+```admonish abstract "Key concepts"
+
+* An _event handler_ object is declared that holds the following items:
+ * [`Engine`] with registered functions serving as an API,
+ * [`AST`] of the user script,
+ * [`Scope`] containing system-provided default state.
+
+* User-provided state is initialized by a [function] called via [`Engine::call_fn_with_options`][`call_fn`].
+
+* Upon an event, the appropriate event handler [function] in the script is called via
+ [`Engine::call_fn`][`call_fn`].
+
+* Optionally, trap the `EvalAltResult::ErrorFunctionNotFound` error to provide a default implementation.
+```
+
+
+Basic Infrastructure
+--------------------
+
+### Declare handler object
+
+In most cases, it would be simpler to store an [`Engine`] instance together with the handler object
+because it only requires registering all API functions only once.
+
+In rare cases where handlers are created and destroyed in a tight loop, a new [`Engine`] instance
+can be created for each event. See [_One Engine Instance Per Call_](parallel.md) for more details.
+
+```rust
+use rhai::{Engine, Scope, AST};
+
+// Event handler
+struct Handler {
+ // Scripting engine
+ pub engine: Engine,
+ // Use a custom 'Scope' to keep stored state
+ pub scope: Scope<'static>,
+ // Program script
+ pub ast: AST
+}
+```
+
+### Register API for custom types
+
+[Custom types] are often used to hold state. The easiest way to register an entire API is via a [plugin module].
+
+```rust
+use rhai::plugin::*;
+
+// A custom type to a hold state value.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
+pub struct TestStruct {
+ data: i64
+}
+
+// Plugin module containing API to TestStruct
+#[export_module]
+mod test_struct_api {
+ #[rhai_fn(global)]
+ pub fn new_state(value: i64) -> TestStruct {
+ TestStruct { data: value }
+ }
+ #[rhai_fn(global)]
+ pub fn func1(obj: &mut TestStruct) -> bool {
+ :
+ }
+ #[rhai_fn(global)]
+ pub fn func2(obj: &mut TestStruct) -> i64 {
+ :
+ }
+ pub fn process(data: i64) -> i64 {
+ :
+ }
+ #[rhai_fn(get = "value", pure)]
+ pub fn get_value(obj: &mut TestStruct) -> i64 {
+ obj.data
+ }
+ #[rhai_fn(set = "value")]
+ pub fn set_value(obj: &mut TestStruct, value: i64) {
+ obj.data = value;
+ }
+}
+```
+
+### Initialize handler object
+
+Steps to initialize the event handler:
+
+1. Register an API with the [`Engine`],
+2. Create a custom [`Scope`] to serve as the stored state,
+3. Add default state [variables] into the custom [`Scope`],
+4. Optionally, call an initiation [function] to create new state [variables];
+ `Engine::call_fn_with_options` is used instead of `Engine::call_fn` so that [variables] created
+ inside the [function] will not be removed from the custom [`Scope`] upon exit,
+5. Get the handler script and [compile][`AST`] it,
+6. Store the compiled [`AST`] for future evaluations,
+7. Run the [`AST`] to initialize event handler state [variables].
+
+```rust
+impl Handler {
+ // Create a new 'Handler'.
+ pub fn new(path: impl Into) -> Self {
+ let mut engine = Engine::new();
+
+ // Register custom types and APIs
+ engine.register_type_with_name::("TestStruct")
+ .register_global_module(exported_module!(test_struct_api).into());
+
+ // Create a custom 'Scope' to hold state
+ let mut scope = Scope::new();
+
+ // Add any system-provided state into the custom 'Scope'.
+ // Constants can be used to optimize the script.
+ scope.push_constant("MY_CONSTANT", 42_i64);
+
+ :
+ :
+ // Initialize state variables
+ :
+ :
+
+ // Compile the handler script.
+ // In a real application you'd be handling errors...
+ let ast = engine.compile_file_with_scope(&mut scope, path).unwrap();
+
+ // The event handler is essentially these three items:
+ Self { engine, scope, ast }
+ }
+}
+```
+
+### Hook up events
+
+There is usually an interface or trait that gets called when an event comes from the system.
+
+Mapping an event from the system into a scripted handler is straight-forward, via `Engine::call_fn`.
+
+```rust
+impl Handler {
+ // Say there are three events: 'start', 'end', 'update'.
+ // In a real application you'd be handling errors...
+ pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Dynamic {
+ let engine = &self.engine;
+ let scope = &mut self.scope;
+ let ast = &self.ast;
+
+ match event_name {
+ // The 'start' event maps to function 'start'.
+ // In a real application you'd be handling errors...
+ "start" => engine.call_fn(scope, ast, "start", (event_data,)).unwrap(),
+
+ // The 'end' event maps to function 'end'.
+ // In a real application you'd be handling errors...
+ "end" => engine.call_fn(scope, ast, "end", (event_data,)).unwrap(),
+
+ // The 'update' event maps to function 'update'.
+ // This event provides a default implementation when the scripted function is not found.
+ "update" =>
+ engine.call_fn(scope, ast, "update", (event_data,))
+ .or_else(|err| match *err {
+ EvalAltResult::ErrorFunctionNotFound(fn_name, _)
+ if fn_name.starts_with("update") =>
+ {
+ // Default implementation of 'update' event handler.
+ self.scope.set_value("obj_state", TestStruct::new(42));
+ // Turn function-not-found into a success.
+ Ok(Dynamic::UNIT)
+ }
+ _ => Err(err)
+ }).unwrap(),
+
+ // In a real application you'd be handling unknown events...
+ _ => panic!("unknown event: {}", event_name)
+ }
+ }
+}
+```
+
+
+Scripting Styles
+----------------
+
+Depending on needs and scripting style, there are three different ways to implement this pattern.
+
+| | [Main style](events-1.md) | [JS style](events-2.md) | [Map style](events-3.md) |
+| ------------------------------------------------- | :---------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
+| States store | custom [`Scope`] | [object map] bound to `this` | [object map] in custom [`Scope`] |
+| Access state [variable] | normal [variable] | [property][object map] of `this` | [property][object map] of state variable |
+| Access global [constants]? | yes | yes | yes |
+| Add new state [variable]? | `init` [function] only | all [functions] | all [functions] |
+| Add new global [constants]? | yes | **no** | **no** |
+| [OOP]-style [functions] on states? | **no** | yes | yes |
+| Detect system-provided initial states? | **no** | yes | yes |
+| Local [variable] may _[shadow]_ state [variable]? | yes | **no** | **no** |
+| Benefits | simple | fewer surprises | versatile |
+| Disadvantages | no new variables in [functions] (apart from `init`) easy variable name collisions | `this.xxx` all over the place more complex implementation | `state.xxx` all over the place inconsistent syntax |
diff --git a/rhai_engine/rhaibook/patterns/global-mutable-state.md b/rhai_engine/rhaibook/patterns/global-mutable-state.md
new file mode 100644
index 0000000..a7b68f1
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/global-mutable-state.md
@@ -0,0 +1,155 @@
+Mutable Global State
+====================
+
+{{#include ../links.md}}
+
+
+Don't Do It™
+------------
+
+```admonish question.side "Consider JavaScript"
+
+Generations of programmers struggled to get around mutable global state (a.k.a. the `window` object)
+in the design of JavaScript.
+```
+
+In contrast to [global constants](constants.md), _mutable_ global states are **strongly
+discouraged** because:
+
+1) It is a sure-fire way to create race conditions – that is why Rust does not support it;
+
+2) It adds considerably to debug complexity – it is difficult to reason, in large code bases,
+ where/when a state value is being modified;
+
+3) It forces hard (but obscure) dependencies between separate pieces of code that are difficult to
+ break when the need arises;
+
+4) It is almost impossible to add new layers of redirection and/or abstraction afterwards without
+ major surgery.
+
+
+Alternative – Use `this`
+------------------------------
+
+In the majority of the such scenarios, there is only _one_ mutable global state of interest.
+
+Therefore, it is a _much_ better solution to bind that global state to the `this` pointer.
+
+```rust
+// Say this is a mutable global state...
+let state = #{ counter: 0 };
+
+// This function tries to access the global 'state'
+// which will fail.
+fn inc() {
+ state.counter += 1;
+}
+
+// The function should be written with 'this'
+fn inc() {
+ this.counter += 1;
+}
+
+state.inc(); // call 'inc' with 'state' bound to 'this'
+
+// Or this way... why hard-code the state in the first place?
+fn inc() {
+ this += 1;
+}
+
+state.counter.inc();
+```
+
+```admonish question.small "Why is this better?"
+
+There are good reasons why using `this` is a better solution:
+
+* the state is never _hidden_ – it is always clear to see what is being modified
+* it is just as fast – the `this` pointer works by reference
+* you can pass other states in, in the future, without changing the script code
+* there are no hard links within functions that will be difficult to unravel
+* only the [variable] bound to `this` is ever modified; everything else is immutable
+```
+
+```admonish danger.small "I don't care! I want it! Just tell me how to do it! Now!"
+
+This is not something that Rhai encourages. _You Have Been Warned™_.
+
+There are two ways...
+```
+
+
+Option 1 – Get/Set Functions
+----------------------------------
+
+This is similar to the [Control Layer](control.md) pattern.
+
+Use get/set functions to read/write the global mutable state.
+
+```rust
+// The globally mutable shared value
+let value = Rc::new(RefCell::new(42));
+
+// Register an API to access the globally mutable shared value
+let v = value.clone();
+engine.register_fn("get_global_value", move || *v.borrow());
+
+let v = value.clone();
+engine.register_fn("set_global_value", move |value: i64| *v.borrow_mut() = value);
+```
+
+These functions can be used in script [functions] to access the shared global state.
+
+```rust
+fn foo() {
+ let current = get_global_value(); // Get global state value
+ current += 1;
+ set_global_value(current); // Modify global state value
+}
+```
+
+This option is preferred because it is possible to modify the get/set functions later on to
+add/change functionalities without introducing breaking script changes.
+
+
+Option 2 – Variable Resolver
+----------------------------------
+
+Declare a [variable resolver] that returns a _shared_ value which is the global state.
+
+```rust
+// Use a shared value as the global state
+let value: Dynamic = 1.into();
+let mut value = value.into_shared(); // convert into shared value
+
+// Clone the shared value
+let v = value.clone();
+
+// Register a variable resolver.
+engine.on_var(move |name, _, _| {
+ match name
+ "value" => Ok(Some(v.clone())),
+ _ => Ok(None)
+ }
+});
+
+// The shared global state can be modified
+*value.write_lock::().unwrap() = 42;
+```
+
+The global state variable can now be used just like a normal local variable,
+including modifications.
+
+```rust
+fn foo() {
+ value = value * 2;
+// ^ global variable can be read
+// ^ global variable can also be modified
+}
+```
+
+```admonish danger.small "Anti-Pattern"
+
+This option makes mutable global state so easy to implement that it should actually be
+considered an _Anti-Pattern_.
+```
diff --git a/rhai_engine/rhaibook/patterns/hot-reload.md b/rhai_engine/rhaibook/patterns/hot-reload.md
new file mode 100644
index 0000000..1cc505e
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/hot-reload.md
@@ -0,0 +1,107 @@
+Hot Reloading
+=============
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system where scripts are used for behavioral control.
+
+* All or parts of the control scripts need to be modified dynamically without re-initializing the
+ host system.
+
+* New scripts must be active as soon as possible after modifications are detected.
+```
+
+```admonish abstract "Key concepts"
+
+* The Rhai [`Engine`] is _re-entrant_, meaning that it is decoupled from scripts.
+
+* A new script only needs to be recompiled and the new [`AST`] replaces the old for new behaviors
+ to be active.
+
+* Surgically _patch_ scripts when only parts of the scripts are modified.
+```
+
+
+Implementation
+--------------
+
+### Embed scripting engine and script into system
+
+Say, a system has a Rhai [`Engine`] plus a compiled script (in [`AST`] form), with the [`AST`] kept
+with interior mutability...
+
+```rust
+// Main system object
+struct System {
+ engine: Engine,
+ script: Rc>,
+ :
+}
+
+// Embed Rhai 'Engine' and control script
+let engine = Engine::new();
+let ast = engine.compile_file("config.rhai")?;
+
+let mut system = System { engine, script: Rc::new(RefCell::new(ast)) };
+
+// Handle events with script functions
+system.on_event(|sys: &System, event: &str, data: Map| {
+ let mut scope = Scope::new();
+
+ // Call script function which is the same name as the event
+ sys.engine.call_fn(&mut scope, sys.script.borrow(), event, (data,)).unwrap();
+
+ result
+});
+```
+
+### Hot reload entire script upon change
+
+If the control scripts are small enough and changes are infrequent, it is much simpler just to
+recompile the whole set of script and replace the original [`AST`] with the new one.
+
+```rust
+// Watch for script file change
+system.watch(|sys: &System, file: &str| {
+ // Compile the new script
+ let ast = sys.engine.compile_file(file.into())?;
+
+ // Hot reload - just replace the old script!
+ *sys.script.borrow_mut() = ast;
+
+ Ok(())
+});
+```
+
+### Hot patch specific functions
+
+If the control scripts are large and complicated, and if the system can detect changes to specific [functions],
+it is also possible to _patch_ just the changed [functions].
+
+```rust
+// Watch for changes in the script
+system.watch_for_script_change(|sys: &mut System, fn_name: &str| {
+ // Get the script file that contains the function
+ let script = get_script_file_path(fn_name);
+
+ // Compile the new script
+ let mut patch_ast = sys.engine.compile_file(script)?;
+
+ // Remove everything other than the specified function
+ patch_ast.clear_statements();
+ patch_ast.retain_functions(|_, _, name, _| name == fn_name);
+
+ // Hot reload (via +=) only those functions in the script!
+ *sys.script.borrow_mut() += patch_ast;
+});
+```
+
+
+```admonish tip.small "Tip: Multi-threaded considerations"
+
+For a multi-threaded environments, replace `Rc` with `Arc`, `RefCell` with `RwLock` or `Mutex`, and
+turn on the [`sync`] feature.
+```
diff --git a/rhai_engine/rhaibook/patterns/macros.md b/rhai_engine/rhaibook/patterns/macros.md
new file mode 100644
index 0000000..61d5a93
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/macros.md
@@ -0,0 +1,119 @@
+Simulate Macros to Simplify Scripts
+===================================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* Scripts need to access existing data in [variables].
+
+* The particular fields to access correspond to long/complex expressions (e.g. long
+ [indexing][indexers] and/or [property][getters/setters] chains `foo[x][y].bar[z].baz`).
+
+* Usage is prevalent inside the scripts, requiring extensive duplications of code that
+ are prone to typos and errors.
+
+* There are a few such [variables] to modify at the same time – otherwise, it would
+ be simpler to bind the `this` pointer to the [variable].
+```
+
+```admonish abstract "Key concepts"
+
+* Pick a _macro_ syntax that is unlikely to conflict with content in literal [strings].
+
+* Before script evaluation/compilation, globally replace macros with their corresponding expansions.
+```
+
+
+Pick a Macro Syntax
+-------------------
+
+```admonish danger.side "Warning: Not real macros"
+
+The technique described here is to _simulate_ macros.
+They are not _REAL_ macros.
+```
+
+Pick a syntax that is intuitive for the domain but **unlikely to occur naturally inside
+[string] literals**.
+
+| Sample Syntax | Sample usage |
+| ------------- | ------------------ |
+| `#FOO` | `#FOO = 42;` |
+| `$Bar` | `$Bar.work();` |
+| `` | `print();` |
+| `#HELLO#` | `let x = #HELLO#;` |
+| `%HEY%` | `%HEY% += 1;` |
+
+~~~admonish tip.small "Tip: Avoid normal words"
+
+Avoid normal syntax that may show up inside a [string] literal.
+
+For example, if using `Target` as a macro:
+
+```rust
+// This script...
+Target.do_damage(10);
+
+if Target.hp <= 0 {
+ print("Target is destroyed!");
+}
+
+// Will turn to this...
+entities["monster"].do_damage(10);
+
+if entities["monster"].hp <= 0 {
+ // Text in string literal erroneously replaced!
+ print("entities["monster"] is destroyed!");
+}
+```
+~~~
+
+
+Global Search/Replace
+---------------------
+
+```rust
+// Replace macros with expansions
+let script = script.replace("#FOO", "foo[x][y].bar[z].baz");
+
+let mut scope = Scope::new();
+
+// Add global variables
+scope.push("foo", ...);
+scope.push_constant("x", ...);
+scope.push_constant("y", ...);
+scope.push_constant("z", ...);
+
+// Run the script as normal
+engine.run_with_scope(&mut scope, script)?;
+```
+
+~~~admonish example.small "Example script"
+
+```js
+print(`Found entity FOO at (${x},${y},${z})`);
+
+let speed = #FOO.speed;
+
+if speed < 42 {
+ #FOO.speed *= 2;
+} else {
+ #FOO.teleport(#FOO.home());
+}
+
+print(`FOO is now at (${ #FOO.current_location() })`);
+```
+~~~
+
+```admonish bug.small "Character positions"
+
+After macro expansion, the _character positions_ of different script
+elements will be shifted based on the length of the expanded text.
+
+Therefore, error positions may no longer point to the correct locations
+in the original, unexpanded scripts.
+
+Line numbers are not affected.
+```
diff --git a/rhai_engine/rhaibook/patterns/multi-layer.md b/rhai_engine/rhaibook/patterns/multi-layer.md
new file mode 100644
index 0000000..b7c748a
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/multi-layer.md
@@ -0,0 +1,136 @@
+Multi-Layered Functions
+=======================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system is divided into separate _layers_, each providing logic in terms of scripted [functions].
+
+* A lower layer provides _default implementations_ of certain [functions].
+
+* Higher layers each provide progressively more specific implementations of the same [functions].
+
+* A more specific [function], if defined in a higher layer, always overrides the implementation
+ in a lower layer.
+
+* This is akin to object-oriented programming but with [functions].
+
+* This type of system is extremely convenient for dynamic business rules configuration, setting
+ corporate-wide policies, granting permissions for specific roles etc. where specific, local rules
+ need to override corporate-wide defaults.
+```
+
+```admonish tip "Practical scenario"
+
+Assuming a LOB (line-of-business) system for a large MNC (multi-national corporation) with branches,
+facilities and offices across the globe.
+
+The system needs to provide basic, corporate-wide policies to be enforced through the worldwide
+organization, but also cater for country- or region-specific rules, practices and regulations.
+
+| Layer | Description |
+| :---------: | --------------------------------------------------- |
+| `corporate` | corporate-wide policies |
+| `regional` | regional policy overrides |
+| `country` | country-specific modifications for legal compliance |
+| `office` | special treatments for individual office locations |
+```
+
+```admonish abstract "Key concepts"
+
+* Each layer is a separate script.
+
+* The lowest layer script is compiled into a base [`AST`].
+
+* Higher layer scripts are also compiled into [`AST`] and _combined_ into the base using
+ [`AST::combine`]({{rootUrl}}/engine/ast.md) (or the `+=` operator), overriding any existing [functions].
+```
+
+
+Examples
+--------
+
+Assume the following four scripts, one for each layer:
+
+```rust
+┌────────────────┐
+│ corporate.rhai │
+└────────────────┘
+
+// Default implementation of 'foo'.
+fn foo(x) { x + 1 }
+
+// Default implementation of 'bar'.
+fn bar(x, y) { x + y }
+
+// Default implementation of 'no_touch'.
+fn no_touch() { throw "do not touch me!"; }
+
+
+┌───────────────┐
+│ regional.rhai │
+└───────────────┘
+
+// Specific implementation of 'foo'.
+fn foo(x) { x * 2 }
+
+// New implementation for this layer.
+fn baz() { print("hello!"); }
+
+
+┌──────────────┐
+│ country.rhai │
+└──────────────┘
+
+// Specific implementation of 'bar'.
+fn bar(x, y) { x - y }
+
+// Specific implementation of 'baz'.
+fn baz() { print("hey!"); }
+
+
+┌─────────────┐
+│ office.rhai │
+└─────────────┘
+
+// Specific implementation of 'foo'.
+fn foo(x) { x + 42 }
+```
+
+Load and combine them sequentially:
+
+```rust
+let engine = Engine::new();
+
+// Compile the baseline layer.
+let mut ast = engine.compile_file("corporate.rhai".into())?;
+
+// Combine the first layer.
+let lowest = engine.compile_file("regional.rhai".into())?;
+ast += lowest;
+
+// Combine the second layer.
+let middle = engine.compile_file("country.rhai".into())?;
+ast += middle;
+
+// Combine the third layer.
+let highest = engine.compile_file("office.rhai".into())?;
+ast += highest;
+
+// Now, 'ast' contains the following functions:
+//
+// fn no_touch() { // from 'corporate.rhai'
+// throw "do not touch me!";
+// }
+// fn foo(x) { x + 42 } // from 'office.rhai'
+// fn bar(x, y) { x - y } // from 'country.rhai'
+// fn baz() { print("hey!"); } // from 'country.rhai'
+```
+
+```admonish failure.small "No super call"
+
+Unfortunately, there is no `super` call that calls the base implementation (i.e. no way for a
+higher-layer function to call an equivalent lower-layer function).
+```
diff --git a/rhai_engine/rhaibook/patterns/multi-threading.md b/rhai_engine/rhaibook/patterns/multi-threading.md
new file mode 100644
index 0000000..f06e162
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/multi-threading.md
@@ -0,0 +1,116 @@
+Multi-Threaded Synchronization
+==============================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenarios"
+
+* A system needs to communicate with an [`Engine`] running in a separate thread.
+
+* Multiple [`Engine`]s running in separate threads need to coordinate/synchronize with each other.
+```
+
+```admonish abstract "Key concepts"
+
+* An MPSC channel (or any other appropriate synchronization primitive) is used to send/receive
+ messages to/from an [`Engine`] running in a separate thread.
+
+* An API is registered with the [`Engine`] that is essentially _blocking_ until synchronization is achieved.
+```
+
+
+Example
+-------
+
+```rust
+use rhai::{Engine};
+
+fn main() {
+ // Channel: Script -> Master
+ let (tx_script, rx_master) = std::sync::mpsc::channel();
+ // Channel: Master -> Script
+ let (tx_master, rx_script) = std::sync::mpsc::channel();
+
+ // Spawn thread with Engine
+ std::thread::spawn(move || {
+ // Create Engine
+ let mut engine = Engine::new();
+
+ // Register API
+ // Notice that the API functions are blocking
+ engine.register_fn("get", move || rx_script.recv().unwrap())
+ .register_fn("put", move |v: i64| tx_script.send(v).unwrap());
+
+ // Run script
+ engine.run(
+ r#"
+ print("Starting script loop...");
+
+ loop {
+ // The following call blocks until there is data
+ // in the channel
+ let x = get();
+ print(`Script Read: ${x}`);
+
+ x += 1;
+
+ print(`Script Write: ${x}`);
+
+ // The following call blocks until the data
+ // is successfully sent to the channel
+ put(x);
+ }
+ "#).unwrap();
+ });
+
+ // This is the main processing thread
+
+ println!("Starting main loop...");
+
+ let mut value = 0_i64;
+
+ while value < 10 {
+ println!("Value: {value}");
+ // Send value to script
+ tx_master.send(value).unwrap();
+ // Receive value from script
+ value = rx_master.recv().unwrap();
+ }
+}
+```
+
+
+Considerations for [`sync`]
+---------------------------
+
+`std::mpsc::Sender` and `std::mpsc::Receiver` are not `Sync`, therefore they cannot be used in
+registered functions if the [`sync`] feature is enabled.
+
+In that situation, it is possible to wrap the `Sender` and `Receiver` each in a `Mutex` or `RwLock`,
+which makes them `Sync`.
+
+This, however, incurs the additional overhead of locking and unlocking the `Mutex` or `RwLock`
+during every function call, which is technically not necessary because there are no other references
+to them.
+
+
+```admonish note "Async"
+
+The example above highlights the fact that Rhai scripts can call any Rust function, including ones
+that are _blocking_.
+
+However, Rhai is essentially a blocking, single-threaded engine. Therefore it does _not_ provide an
+async API.
+
+That means, although it is simple to use Rhai within a multi-threading environment where blocking a
+thread is acceptable or even expected, it is currently not possible to call _async_ functions within
+Rhai scripts because there is no mechanism in [`Engine`] to wrap the state of the call stack inside
+a future.
+
+Fortunately an [`Engine`] is re-entrant so it can be shared among many async tasks. It is usually
+possible to split a script into multiple parts to avoid having to call async functions.
+
+Creating an [`Engine`] is also relatively cheap (extremely cheap if creating a [raw `Engine`]),
+so it is also a valid pattern to spawn a new [`Engine`] instance for each task.
+```
diff --git a/rhai_engine/rhaibook/patterns/multiple.md b/rhai_engine/rhaibook/patterns/multiple.md
new file mode 100644
index 0000000..12f1e67
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/multiple.md
@@ -0,0 +1,95 @@
+Multiple Instantiation
+======================
+
+{{#include ../links.md}}
+
+Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature
+which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material
+impact on how Rhai behaves.
+
+Many crates resolve this by going the opposite direction: build for `no-std` in default, but add a
+`std` feature, included by default, which builds for the `stdlib`.
+
+Rhai, however, is more complex.
+
+
+Rhai Language Features Are Not Additive
+---------------------------------------
+
+Language features cannot be easily made _additive_.
+
+That is because the _lack_ of a language feature is a feature by itself.
+
+```admonish question "A practical illustration"
+
+Assume an _additive_ feature called `floating-point` that adds floating-point support.
+
+Assume also that the application _omits_ the `floating-point` feature (why? perhaps integers are all
+that make sense within the project domain). Floating-point numbers do not even parse under this
+configuration and will generate syntax errors.
+
+Now, assume that a dependent crate _also_ depends on Rhai, but a new version suddenly decides to
+_require_ floating-point support. That dependent crate would, naturally, specify the
+`floating-point` feature.
+
+Under such circumstances, unless _exact_ versioning is used and the dependent crate depends on a
+_different_ version of Rhai, Cargo automatically _merges_ both dependencies, with the `floating-point`
+feature turned on because features are _additive_.
+
+This will in turn break the application, which by itself specifically omits `floating-point` and
+expects floating-point numbers to be rejected, in unexpected ways. Suddenly and without warning,
+floating-point numbers show up in data which the application is not prepared to handle.
+
+There is no way out of this dilemma, because the _lack_ of a language feature can be depended upon
+as a feature.
+```
+
+
+Multiple Instantiations of Rhai Within The Same Project
+-------------------------------------------------------
+
+The trick is to differentiate between multiple identical copies of Rhai, each having
+a different [features] set, by their _sources_:
+
+* Different versions from [`crates.io`](https://crates.io/crates/rhai/) – The official crate.
+
+* Different releases from [`GitHub`](https://github.com/rhaiscript/rhai) – Crate source on GitHub.
+
+* Forked copy of [https://github.com/rhaiscript/rhai](https://github.com/rhaiscript/rhai) on GitHub.
+
+* Local copy of [https://github.com/rhaiscript/rhai](https://github.com/rhaiscript/rhai) downloaded form GitHub.
+
+Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project:
+
+```toml
+[dependencies]
+rhai = { version = "{{version}}", features = [ "no_float" ] }
+rhai_github = { git = "https://github.com/rhaiscript/rhai", features = [ "unchecked" ] }
+rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] }
+rhai_local = { path = "../rhai_copy" }
+```
+
+The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and
+`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set.
+
+Only one crate of any particular version can be used from each source, because Cargo merges all
+candidate cases within the same source, adding all [features] together.
+
+If more than four different instantiations of Rhai is necessary (why?), create more local
+repositories or GitHub forks or branches.
+
+
+```admonish danger.small "No way To avoid dependency conflicts"
+
+Unfortunately, pulling in Rhai from different sources do not resolve the problem of [features]
+conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest section
+doesn't work – all dependencies will eventually find the only one copy.
+
+What is necessary – multiple copies of Rhai, one for each dependent crate that requires it,
+together with their _unique_ [features] set intact. In other words, turning off Cargo's crate
+merging feature _just for Rhai_.
+
+Unfortunately, as of this writing, there is no known method to achieve it.
+
+Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai.
+```
diff --git a/rhai_engine/rhaibook/patterns/objects.md b/rhai_engine/rhaibook/patterns/objects.md
new file mode 100644
index 0000000..594660e
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/objects.md
@@ -0,0 +1,84 @@
+Objects with Defined Behaviors
+==============================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* To simulate specific _object types_ coded in Rust with fields and methods.
+
+* Native Rust methods (not scripted) are pre-defined for these types.
+```
+
+```admonish abstract "Key concepts"
+
+* Use an [object map] as the main container of the type.
+
+* Create [function pointers] binding to _native Rust functions_ and store them as properties
+ of the [object map].
+```
+
+Rhai does not have _objects_ per se and is not object-oriented (in the traditional sense), but it is
+possible to _simulate_ object-oriented programming via [object maps].
+
+When using [object maps] to simulate objects (See [here](oop.md) for more details), a property that
+holds a [function pointer] can be called like a method function with the [object map] being the
+object of the method call.
+
+It is also possible to create [function pointers] that bind to native Rust functions/closures so
+that those are called when the [function pointers] are called.
+
+
+Implementation
+--------------
+
+A [function pointer] can be created that binds to a specific Rust function or closure.
+
+(See [here]({{rootUrl}}/language/fn-ptr.md#bind-to-a-native-rust-function) for details on using
+`FnPtr::from_fn` and `FnPtr::from_dyn_fn`).
+
+```rust
+// This is the pre-defined behavior for the 'Awesome' object type.
+fn my_awesome_fn(ctx: NativeCallContext, args: &mut[&mut Dynamic]) -> Result> {
+ // Check number of arguments
+ if args.len() != 2 {
+ return Err("one argument is required, plus the object".into());
+ }
+
+ // Get call arguments
+ let x = args[1].try_cast::().map_err(|_| "argument must be an integer".into())?;
+
+ // Get mutable reference to the object map, which is passed as the first argument
+ let map = &mut *args[0].as_map_mut().map_err(|_| "object must be a map".into())?;
+
+ // Do something awesome here ...
+ let result = ...
+
+ Ok(result.into())
+}
+
+// Register a function to create a pre-defined object
+engine.register_fn("create_awesome_object", || {
+ // Use an object map as base
+ let mut map = Map::new();
+
+ // Create a function pointer that binds to 'my_awesome_fn'
+ let fp = FnPtr::from_fn("awesome", my_awesome_fn)?;
+ // ^ name of method
+ // ^ native function
+
+ // Store the function pointer in the object map
+ map.insert("awesome".into(), fp.into());
+
+ Ok(Dynamic::from_map(map))
+});
+```
+
+The [object map] can then be used just like any object-oriented object.
+
+```rust
+let obj = create_awesome_object();
+
+let result = obj.awesome(42); // calls 'my_awesome_fn' with '42' as argument
+```
diff --git a/rhai_engine/rhaibook/patterns/oop.md b/rhai_engine/rhaibook/patterns/oop.md
new file mode 100644
index 0000000..81ab31b
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/oop.md
@@ -0,0 +1,196 @@
+Object-Oriented Programming (OOP)
+=================================
+
+{{#include ../links.md}}
+
+Rhai does not have _objects_ per se and is not object-oriented (in the traditional sense),
+but it is possible to _simulate_ object-oriented programming.
+
+```admonish question.small "To OOP or not to OOP, that is the question."
+
+Regardless of whether object-oriented programming (OOP) should be treated as a pattern or
+an _anti-pattern_ (the programming world is split 50-50 on this), there are always users who
+would like to write Rhai in "the OOP way."
+
+Rust itself is not object-oriented in the traditional sense; JavaScript also isn't, but that didn't
+prevent generations of programmers trying to shoehorn a class-based inheritance system onto it.
+
+So... as soon as Rhai gained in usage, way way before version 1.0, PR's started coming in to make
+it possible to write Rhai in "the OOP way."
+```
+
+
+Use Object Maps to Simulate OOP
+-------------------------------
+
+Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md).
+
+| Rhai concept | Maps to OOP |
+| ----------------------------------------------------- | :---------: |
+| [Object maps] | objects |
+| [Object map] properties holding values | properties |
+| [Object map] properties that hold [function pointers] | methods |
+
+When a property of an [object map] is called like a method function, and if it happens to hold a
+valid [function pointer] (perhaps defined via an [anonymous function] or more commonly as a [closure]),
+then the call will be dispatched to the actual function with `this` binding to the
+[object map] itself.
+
+
+Use Closures to Define Methods
+------------------------------
+
+[Closures] defined as values for [object map] properties take on a syntactic shape which resembles
+very closely that of class methods in an OOP language.
+
+[Closures] also _capture_ [variables] from the defining environment, which is a very common language
+feature. It can be turned off via the [`no_closure`] feature.
+
+```rust
+let factor = 1;
+
+// Define the object
+let obj = #{
+ data: 0, // object field
+ increment: |x| this.data += x, // 'this' binds to 'obj'
+ update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
+ action: || print(this.data) // 'this' binds to 'obj'
+ };
+
+// Use the object
+obj.increment(1);
+obj.action(); // prints 1
+
+obj.update(42);
+obj.action(); // prints 42
+
+factor = 2;
+
+obj.update(42);
+obj.action(); // prints 84
+```
+
+
+Simulating Inheritance with Polyfills
+-------------------------------------
+
+The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default method
+implementations from a _base class_, as per OOP lingo.
+
+Do not use the `mixin` method because it _overwrites_ existing fields.
+
+```rust
+// Define base class
+let BaseClass = #{
+ factor: 1,
+ data: 42,
+
+ get_data: || this.data * 2,
+ update: |x| this.data += x * this.factor
+};
+
+let obj = #{
+ // Override base class field
+ factor: 100,
+
+ // Override base class method
+ // Notice that the base class can also be accessed, if in scope
+ get_data: || this.call(BaseClass.get_data) * 999,
+}
+
+// Polyfill missing fields/methods
+obj.fill_with(BaseClass);
+
+// By this point, 'obj' has the following:
+//
+// #{
+// factor: 100
+// data: 42,
+// get_data: || this.call(BaseClass.get_data) * 999,
+// update: |x| this.data += x * this.factor
+// }
+
+// obj.get_data() => (this.data (42) * 2) * 999
+obj.get_data() == 83916;
+
+obj.update(1);
+
+obj.data == 142
+```
+
+
+Prototypical Inheritance via Mixin
+----------------------------------
+
+Some languages like JavaScript has _prototypical_ inheritance, which bases inheritance on a
+_prototype_ object.
+
+It is possible to simulate this form of inheritance using [object maps], leveraging the fact that,
+in Rhai, all values are cloned and there are no pointers. This significantly simplifies coding logic.
+
+```rust
+// Define prototype 'class'
+
+const PrototypeClass = #{
+ field: 42,
+
+ get_field: || this.field,
+ set_field: |x| this.field = x
+};
+
+// Create instances of the 'class'
+
+let obj1 = PrototypeClass; // a copy of 'PrototypeClass'
+
+obj1.get_field() == 42;
+
+let obj2 = PrototypeClass; // a copy of 'PrototypeClass'
+
+obj2.mixin(#{ // override fields and methods
+ field: 1,
+ get_field: || this.field * 2
+};
+
+obj2.get_field() == 2;
+
+let obj2 = PrototypeClass + #{ // compact syntax with '+'
+ field: 1,
+ get_field: || this.field * 2
+};
+
+obj2.get_field() == 2;
+
+// Inheritance chain
+
+const ParentClass = #{
+ field: 123,
+ new_field: 0,
+ action: || print(this.new_field * this.field)
+};
+
+const ChildClass = #{
+ action: || {
+ this.field = this.new_field;
+ this.new_field = ();
+ }
+}
+
+let obj3 = PrototypeClass + ParentClass + ChildClass;
+
+// Alternate formulation
+
+const ParentClass = PrototypeClass + #{
+ field: 123,
+ new_field: 0,
+ action: || print(this.new_field * this.field)
+};
+
+const ChildClass = ParentClass + #{
+ action: || {
+ this.field = this.new_field;
+ this.new_field = ();
+ }
+}
+
+let obj3 = ChildClass; // a copy of 'ChildClass'
+```
diff --git a/rhai_engine/rhaibook/patterns/parallel.md b/rhai_engine/rhaibook/patterns/parallel.md
new file mode 100644
index 0000000..7176e33
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/parallel.md
@@ -0,0 +1,86 @@
+One `Engine` Instance Per Call
+==============================
+
+{{#include ../links.md}}
+
+
+```admonish info "Usage scenario"
+
+* A system where scripts are called a _lot_, in tight loops or in parallel.
+
+* Keeping a global [`Engine`] instance is sub-optimal due to contention and locking.
+
+* Scripts need to be executed independently from each other, perhaps concurrently.
+
+* Scripts are used to [create Rust closures][`Func`] that are stored and may be called at any time,
+ perhaps concurrently. In this case, the [`Engine`] instance is usually moved into the closure itself.
+```
+
+```admonish abstract "Key concepts"
+
+* Rhai's [`AST`] structure is sharable – meaning that one copy of the [`AST`] can be run on
+ multiple instances of [`Engine`] simultaneously.
+
+* Rhai's [packages] and [modules] are also sharable.
+
+* This means that [`Engine`] instances can be _decoupled_ from the base system ([packages] and
+ [modules]) as well as the scripts ([`AST`]) so they can be created very cheaply.
+```
+
+
+Procedure
+---------
+
+* Gather up all common custom functions into a [custom package].
+
+ * This [custom package] should also include standard [packages] needed. For example, to duplicate
+ `Engine::new`, use a [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md).
+
+ * [Packages] are sharable, so using a [custom package] is _much cheaper_ than registering all the
+ functions one by one.
+
+* Store a global [`AST`] for use with all [`Engine`] instances.
+
+* Always use `Engine::new_raw` to create a [raw `Engine`], instead of `Engine::new` which is _much_
+ more expensive. A [raw `Engine`] is _extremely_ cheap to create.
+
+* Register the [custom package] with the [raw `Engine`] via `Package::register_into_engine`.
+
+
+Examples
+--------
+
+```rust
+use rhai::def_package;
+use rhai::packages::{Package, StandardPackage};
+use rhai::FuncRegistration;
+
+// Define the custom package 'MyCustomPackage'.
+def_package! {
+ /// My own personal super-duper custom package
+ // Aggregate other base packages simply by listing them after the colon.
+ pub MyCustomPackage(module) : StandardPackage {
+ // Register additional Rust functions.
+ FuncRegistration::new("foo")
+ .with_params(&["s: ImmutableString", "i64"])
+ .set_into_module(module, |s: ImmutableString| foo(s.into_owned()));
+ }
+}
+
+let ast = /* ... some AST ... */;
+
+let my_custom_package = MyCustomPackage::new();
+
+// The following loop creates 10,000 Engine instances!
+
+for x in 0..10_000 {
+ // Create a raw Engine - extremely cheap
+ let mut engine = Engine::new_raw();
+
+ // Register custom package - cheap
+ my_custom_package.register_into_engine(&mut engine);
+
+ // Evaluate script
+ engine.run_ast(&ast)?;
+}
+```
diff --git a/rhai_engine/rhaibook/patterns/references.md b/rhai_engine/rhaibook/patterns/references.md
new file mode 100644
index 0000000..85e91e8
--- /dev/null
+++ b/rhai_engine/rhaibook/patterns/references.md
@@ -0,0 +1,164 @@
+Passing External References to Rhai (Unsafe)
+============================================
+
+{{#include ../links.md}}
+
+
+[`transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
+[`Engine::set_default_tag`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.set_default_tag
+
+
+```admonish danger "Don't Do It™"
+
+As with anything `unsafe`, don't do this unless you have exhausted all possible alternatives.
+
+There are usually alternatives.
+```
+
+```admonish info "Usage scenario"
+
+* A system where an embedded [`Engine`] is used to call scripts within a callback function/closure
+ from some external system.
+
+ This is extremely common when working with an ECS (Entity Component System) or a GUI library where the script
+ must draw via the provided graphics context.
+
+* Said external system provides only _references_ (mutable or immutable) to work with their internal states.
+
+* Such states are not available outside of references (and therefore cannot be made shared).
+
+* Said external system's API require such states to function.
+
+* It is impossible to pass references into Rhai because the [`Engine`] does not track lifetimes.
+```
+
+```admonish abstract "Key concepts"
+
+* Make a newtype that wraps an integer which is set to the CPU's pointer size (typically `usize`).
+
+* [`transmute`] the reference provided by the system into an integer and store it within the newtype.
+ This requires `unsafe` code.
+
+* Use the newtype as a _handle_ for all registered API functions, [`transmute`] the integer back
+ to a reference before use. This also requires `unsafe` code.
+```
+
+```admonish bug "Here be dragons..."
+
+Make **absolutely sure** that the newtype is never stored anywhere permanent (e.g. in a [`Scope`])
+nor does it ever live outside of the reference's scope!
+
+Otherwise, a crash is the system being nice to you...
+```
+
+
+Example
+-------
+
+```rust
+/// Newtype wrapping a reference (pointer) cast into 'usize'
+/// together with a unique ID for protection.
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
+pub struct WorldHandle(usize, i64);
+
+/// Create handle from reference
+impl From<&mut World> for WorldHandle {
+ fn from(world: &mut World) -> Self {
+ Self::new(world)
+ }
+}
+
+/// Recover reference from handle
+impl AsMut