...
This commit is contained in:
parent
f6935492fb
commit
7e9ad524cc
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target/
|
523
rhai_engine/Cargo.lock
generated
Normal file
523
rhai_engine/Cargo.lock
generated
Normal file
@ -0,0 +1,523 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"instant",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"rhai",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
18
rhai_engine/Cargo.toml
Normal file
18
rhai_engine/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "rhai_engine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "rhai_engine"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rhai_engine"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.12.0"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
176
rhai_engine/rhaibook/LICENSE-APACHE.txt
Normal file
176
rhai_engine/rhaibook/LICENSE-APACHE.txt
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
23
rhai_engine/rhaibook/LICENSE-MIT.txt
Normal file
23
rhai_engine/rhaibook/LICENSE-MIT.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
338
rhai_engine/rhaibook/SUMMARY.md
Normal file
338
rhai_engine/rhaibook/SUMMARY.md
Normal file
@ -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)
|
95
rhai_engine/rhaibook/about/features.md
Normal file
95
rhai_engine/rhaibook/about/features.md
Normal file
@ -0,0 +1,95 @@
|
||||
Features of Rhai
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
```admonish success "Easy"
|
||||
|
||||
* Simple language similar to JavaScript+Rust with dynamic typing.
|
||||
|
||||
* Tight integration with native Rust [functions] and [types][custom types] including
|
||||
[getters/setters], [methods] and [indexers].
|
||||
|
||||
* Freely pass Rust values into a script as [variables]/[constants] via an external [`Scope`] –
|
||||
all clonable Rust types are supported seamlessly without the need to implement any special trait.
|
||||
|
||||
* Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust.
|
||||
|
||||
* Very few additional dependencies – right now only [`smallvec`](https://crates.io/crates/smallvec),
|
||||
[`thin-vec`](https://crates.io/crates/thin-vec), [`num-traits`](https://crates.io/crates/num-traits),
|
||||
[`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`];
|
||||
for [`no-std`] and [WASM] builds, a number of additional dependencies are pulled in to provide for missing functionalities.
|
||||
|
||||
* [Plugins] system powered by procedural macros simplifies custom API development.
|
||||
```
|
||||
|
||||
```admonish danger "Fast"
|
||||
|
||||
* Fairly efficient evaluation – 1 million iterations in 0.14 sec on a single-core, 2.6 GHz Linux VM
|
||||
running [this script](https://github.com/rhaiscript/rhai/blob/main/scripts/speed_test.rhai)
|
||||
(also see [benchmarks](benchmarks.md)).
|
||||
|
||||
* Compile once to [AST][`AST`] for repeated evaluations.
|
||||
|
||||
* Scripts are [optimized][script optimization] – useful for template-based machine-generated scripts.
|
||||
```
|
||||
|
||||
```admonish tip "Dynamic"
|
||||
|
||||
* [Function overloading]({{rootUrl}}/language/overload.md).
|
||||
|
||||
* [Operator overloading]({{rootUrl}}/rust/operators.md).
|
||||
|
||||
* Organize code base with dynamically-loadable [modules], optionally overriding the
|
||||
[resolution][module resolver] process.
|
||||
|
||||
* Dynamic dispatch via [function pointers] with additional support for [currying].
|
||||
|
||||
* [Closures] that can capture shared variables.
|
||||
|
||||
* Some support for [object-oriented programming (OOP)][OOP].
|
||||
|
||||
* Hook into variables access via a [variable resolver], or control definition of variables via a
|
||||
[variable definition filter].
|
||||
```
|
||||
|
||||
```admonish warning "Safe"
|
||||
|
||||
* Relatively little `unsafe` code – yes there are some for performance reasons.
|
||||
|
||||
* Sand-boxed – the scripting [`Engine`], if declared immutable, cannot mutate the containing
|
||||
environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md).
|
||||
|
||||
* Passes Miri.
|
||||
```
|
||||
|
||||
```admonish bug "Rugged"
|
||||
|
||||
* [_Don't Panic_][safety] guarantee – Any panic is a bug. It never panics the host system.
|
||||
|
||||
* Protected against malicious attacks – such as [stack-overflow][maximum call stack depth],
|
||||
[over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations]
|
||||
etc. – that may come from untrusted third-party user-land scripts.
|
||||
|
||||
* Track script evaluation [progress] and manually terminate a script run.
|
||||
```
|
||||
|
||||
```admonish example "Flexible"
|
||||
|
||||
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
|
||||
|
||||
* Support for [`Decimal`][rust_decimal] numbers.
|
||||
|
||||
* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde).
|
||||
|
||||
* Support for [minimal builds] by excluding unneeded language [features].
|
||||
|
||||
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
||||
|
||||
* Surgically [disable keywords and operators] to restrict the language.
|
||||
|
||||
* Use as a [DSL] by defining [custom operators] and/or extending the language with [custom syntax].
|
||||
|
||||
* A [debugging][debugger] interface provides powerful debugging support.
|
||||
```
|
44
rhai_engine/rhaibook/about/index.md
Normal file
44
rhai_engine/rhaibook/about/index.md
Normal file
@ -0,0 +1,44 @@
|
||||
Introduction to Rhai
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Trivia
|
||||
------
|
||||
|
||||
```admonish question "Etymology of the name \\"Rhai\\""
|
||||
|
||||
In the beginning there was [ChaiScript](http://chaiscript.com),
|
||||
which is an embedded scripting language for C++.
|
||||
Originally it was intended to be a scripting language similar to **JavaScript**.
|
||||
|
||||
With java being a kind of hot beverage, the new language was named after
|
||||
another hot beverage – [**Chai**](https://en.wikipedia.org/wiki/Chai),
|
||||
which is the word for "tea" in many world languages and, in particular,
|
||||
a popular kind of [spicy milk tea consumed in India](https://en.wikipedia.org/wiki/Masala_chai).
|
||||
|
||||
Later, when the novel implementation technique behind ChaiScript was ported from C++ to Rust,
|
||||
logically the `C` was changed to an `R` to make it "RhaiScript", or just "Rhai".
|
||||
|
||||
– Rhai author [Johnathan Turner](https://github.com/jntrnr)
|
||||
```
|
||||
|
||||
```admonish question "Origin of the Rhai logo"
|
||||
|
||||
<div style="float:right;text-align:center;width:20%">
|
||||
<img src="{{rootUrl}}/images/logo/rhai_logo_old.png" title="Prototype Rhai logo">
|
||||
<div style="font-size:60%">Original prototype</div>
|
||||
</div>
|
||||
|
||||
One of Rhai's maintainers, [`@schungx`](https://github.com/schungx), was thinking about a logo
|
||||
when he accidentally came across a copy of _Catcher in the Rye_ in a restaurant, and drew the
|
||||
first prototype version of the logo.
|
||||
|
||||
Then [`@semirix`](https://github.com/semirix) refined it to the current version.
|
||||
```
|
||||
|
||||
~~~admonish question "The \`rhai.rs\` domain"
|
||||
|
||||
[`@yrashk`](https://github.com/yrashk) sponsored the domain [`rhai.rs`](https://rhai.rs).
|
||||
~~~
|
77
rhai_engine/rhaibook/appendix/keywords.md
Normal file
77
rhai_engine/rhaibook/appendix/keywords.md
Normal file
@ -0,0 +1,77 @@
|
||||
Keywords List
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Inactive under | Is function? |
|
||||
| :-------------------: | ----------------------------------------------------------------------- | :-----------------------------: | :----------: |
|
||||
| `true` | boolean true literal | | **no** |
|
||||
| `false` | boolean false literal | | **no** |
|
||||
| `let` | [variable] declaration | | **no** |
|
||||
| `const` | [constant] declaration | | **no** |
|
||||
| `if` | [`if`] statement | | **no** |
|
||||
| `else` | `else` block of [`if`] statement | | **no** |
|
||||
| `switch` | [matching][`switch`] | | **no** |
|
||||
| `do` | [looping][`do`] | | **no** |
|
||||
| `while` | <ol><li>[`while`] loop</li><li>condition for [`do`] loop</li></ol> | | **no** |
|
||||
| `until` | [`do`] loop | | **no** |
|
||||
| `loop` | infinite [`loop`] | | **no** |
|
||||
| `for` | [`for`] loop | | **no** |
|
||||
| `in` | <ol><li>[containment][`in`] test</li><li>part of [`for`] loop</li></ol> | | **no** |
|
||||
| `!in` | negated [containment][`in`] test | | **no** |
|
||||
| `continue` | continue a loop at the next iteration | | **no** |
|
||||
| `break` | break out of loop iteration | | **no** |
|
||||
| `return` | [return][`return`] value | | **no** |
|
||||
| `throw` | throw [exception] | | **no** |
|
||||
| `try` | [trap][`try`] [exception] | | **no** |
|
||||
| `catch` | [catch][`catch`] [exception] | | **no** |
|
||||
| `import` | [import][`import`] [module] | [`no_module`] | **no** |
|
||||
| `export` | [export][`export`] [variable] | [`no_module`] | **no** |
|
||||
| `as` | alias for [variable] [export][`export`] | [`no_module`] | **no** |
|
||||
| `global` | automatic [global][`global`] [module] | [`no_module`], [`no_function`] | **no** |
|
||||
| `private` | mark [function] [private][`private`] | [`no_function`] | **no** |
|
||||
| `fn` (lower-case `f`) | [function] definition | [`no_function`] | **no** |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes |
|
||||
| `call` | call a [function pointer] | | yes |
|
||||
| `curry` | curry a [function pointer] | | yes |
|
||||
| `is_shared` | is a [variable] shared? | [`no_function`], [`no_closure`] | yes |
|
||||
| `is_def_fn` | is [function] defined? | [`no_function`] | yes |
|
||||
| `is_def_var` | is [variable] defined? | | yes |
|
||||
| `this` | reference to base object for method call | [`no_function`] | **no** |
|
||||
| `type_of` | get type name of value | | yes |
|
||||
| `print` | print value | | yes |
|
||||
| `debug` | print value in debug format | | yes |
|
||||
| `eval` | evaluate script | | yes |
|
||||
|
||||
|
||||
Reserved Keywords
|
||||
-----------------
|
||||
|
||||
| Keyword | Potential usage |
|
||||
| :---------: | --------------------- |
|
||||
| `var` | variable declaration |
|
||||
| `static` | variable declaration |
|
||||
| `shared` | share value |
|
||||
| `goto` | control flow |
|
||||
| `match` | matching |
|
||||
| `case` | matching |
|
||||
| `public` | function/field access |
|
||||
| `protected` | function/field access |
|
||||
| `new` | constructor |
|
||||
| `use` | import namespace |
|
||||
| `with` | scope |
|
||||
| `is` | type check |
|
||||
| `module` | module |
|
||||
| `package` | package |
|
||||
| `super` | base class/module |
|
||||
| `thread` | threading |
|
||||
| `spawn` | threading |
|
||||
| `go` | threading |
|
||||
| `await` | async |
|
||||
| `async` | async |
|
||||
| `sync` | async |
|
||||
| `yield` | async |
|
||||
| `default` | special value |
|
||||
| `void` | special value |
|
||||
| `null` | special value |
|
||||
| `nil` | special value |
|
20
rhai_engine/rhaibook/appendix/literals.md
Normal file
20
rhai_engine/rhaibook/appendix/literals.md
Normal file
@ -0,0 +1,20 @@
|
||||
Literals Syntax
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Type | Literal syntax |
|
||||
| :------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
|
||||
| `FLOAT`,<br/>[`Decimal`][rust_decimal] (requires [`no_float`]+[`decimal`]) | `42.0`, `-123.456`, `123.`, `123.456e-10` |
|
||||
| [Ranges] in [`switch`] cases | `-10..10` (exclusive), `0..=50` (inclusive) |
|
||||
| Normal [string] | `"... \x?? \u???? \U???????? ..."` |
|
||||
| [String] with continuation | `"this is the first line\`<br/>`second line\`<br/>`the third line"` |
|
||||
| Multi-line literal [string] | `` `this is the first line``<br/>``second line``</br>``the last line` `` |
|
||||
| Multi-line literal [string] with interpolation | `` `this is the first field: ${obj.field1}``<br/>``second field: {obj.field2}``</br>``the last field: ${obj.field3}` `` |
|
||||
| [Character] | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
|
||||
| [`Array`] | `[ ???, ???, ??? ]` |
|
||||
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
|
||||
| Boolean true | `true` |
|
||||
| Boolean false | `false` |
|
||||
| `Nothing`/`null`/`nil`/`void`/Unit | `()` |
|
86
rhai_engine/rhaibook/appendix/operators.md
Normal file
86
rhai_engine/rhaibook/appendix/operators.md
Normal file
@ -0,0 +1,86 @@
|
||||
Operators and Symbols
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
||||
| Operator | Description | Binary? | Binding direction |
|
||||
| :------------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: |
|
||||
| `+` | add | yes | left |
|
||||
| `-` | 1) subtract<br/>2) negative (prefix) | yes<br/>no | left<br/>right |
|
||||
| `*` | multiply | yes | left |
|
||||
| `/` | divide | yes | left |
|
||||
| `%` | modulo | yes | left |
|
||||
| `**` | power/exponentiation | yes | right |
|
||||
| `>>` | right bit-shift | yes | left |
|
||||
| `<<` | left bit-shift | yes | left |
|
||||
| `&` | 1) bit-wise _AND_<br/>2) boolean _AND_ | yes | left |
|
||||
| <code>\|</code> | 1) bit-wise _OR_<br/>2) boolean _OR_ | yes | left |
|
||||
| `^` | 1) bit-wise _XOR_<br/>2) boolean _XOR_ | yes | left |
|
||||
| `=`, `+=`, `-=`, `*=`, `/=`,<br/>`**=`, `%=`, `<<=`, `>>=`, `&=`,<br/><code>\|=</code>, `^=` | assignments | yes | n/a |
|
||||
| `==` | equals to | yes | left |
|
||||
| `!=` | not equals to | yes | left |
|
||||
| `>` | greater than | yes | left |
|
||||
| `>=` | greater than or equals to | yes | left |
|
||||
| `<` | less than | yes | left |
|
||||
| `<=` | less than or equals to | yes | left |
|
||||
| `&&` | boolean _AND_ (short-circuits) | yes | left |
|
||||
| <code>\|\|</code> | boolean _OR_ (short-circuits) | yes | left |
|
||||
| `??` | null-coalesce (short-circuits) | yes | left |
|
||||
| `!` | boolean _NOT_ | **no** | right |
|
||||
| `[` ... `]`, `?[` ... `]` | indexing | yes | left |
|
||||
| `.`, `?.` | 1) property access<br/>2) method call | yes | left |
|
||||
| `..` | exclusive range | yes | left |
|
||||
| `..=` | inclusive range | yes | left |
|
||||
|
||||
|
||||
Symbols and Patterns
|
||||
--------------------
|
||||
|
||||
| Symbol | Name | Description |
|
||||
| :---------------------------------: | ---------------------------------- | ------------------------------------- |
|
||||
| `_` | underscore | default `switch` case |
|
||||
| `;` | semicolon | statement separator |
|
||||
| `,` | comma | list separator |
|
||||
| `:` | colon | [object map] property value separator |
|
||||
| `::` | path | module path separator |
|
||||
| `#{` ... `}` | hash map | [object map] literal |
|
||||
| `"` ... `"` | double quote | [string] |
|
||||
| `` ` `` ... `` ` `` | back-tick | multi-line literal [string] |
|
||||
| `'` ... `'` | single quote | [character] |
|
||||
| `\` | 1) escape<br/>2) line continuation | escape character literal |
|
||||
| `()` | unit | null value |
|
||||
| `(` ... `)` | parentheses | expression grouping |
|
||||
| `{` ... `}` | braces | block statement |
|
||||
| <code>\|</code> ... <code>\|</code> | pipes | closure |
|
||||
| `[` ... `]` | brackets | [array] literal |
|
||||
| `!` | bang | function call in calling scope |
|
||||
| `=>` | double arrow | `switch` expression case separator |
|
||||
| `//` | comment | line comment |
|
||||
| `///` | doc-comment | line [doc-comment] |
|
||||
| `//!` | module doc | [module documentation][comments] |
|
||||
| `/*` ... `*/` | comment | block comment |
|
||||
| `/**` ... `*/` | doc-comment | block [doc-comment] |
|
||||
| `(*` ... `*)` | comment | _reserved_ |
|
||||
| `#!` | shebang | _reserved_ |
|
||||
| `++` | increment | _reserved_ |
|
||||
| `--` | decrement | _reserved_ |
|
||||
| `...` | rest | _reserved_ |
|
||||
| `~` | tilde | _reserved_ |
|
||||
| `!.` | | _reserved_ |
|
||||
| `?` | question | _reserved_ |
|
||||
| `#` | hash | _reserved_ |
|
||||
| `@` | at | _reserved_ |
|
||||
| `$` | dollar | _reserved_ |
|
||||
| `->` | arrow | _reserved_ |
|
||||
| `<-` | left arrow | _reserved_ |
|
||||
| <code><\|</code> | left triangle | _reserved_ |
|
||||
| <code>\|></code> | right triangle | _reserved_ |
|
||||
| `===` | strict equals to | _reserved_ |
|
||||
| `!==` | strict not equals to | _reserved_ |
|
||||
| `:=` | assignment | _reserved_ |
|
||||
| `:;` | typo to `::` | _reserved_ |
|
||||
| `::<` ... `>` | turbofish | _reserved_ |
|
5
rhai_engine/rhaibook/context.toml
Normal file
5
rhai_engine/rhaibook/context.toml
Normal file
@ -0,0 +1,5 @@
|
||||
version = "1.21.0"
|
||||
repoHome = "https://github.com/rhaiscript/rhai/blob/main"
|
||||
rootUrl = ""
|
||||
#rootUrl = "/book"
|
||||
#rootUrl = "/book/vnext"
|
164
rhai_engine/rhaibook/engine/ast.md
Normal file
164
rhai_engine/rhaibook/engine/ast.md
Normal file
@ -0,0 +1,164 @@
|
||||
Manage `AST`'s
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
When compiling a Rhai script to an [`AST`], the following data are packaged together as a single unit:
|
||||
|
||||
| Data | Type | Description | Requires feature | Access API |
|
||||
| -------------------------------- | :---------------------------------------: | ------------------------------------------------------------------------------------------------- | :------------------------------------: | :------------------------------------------------------------------------------------------------------------: |
|
||||
| Source name | [`ImmutableString`] | optional text name to identify the source of the script | | `source(&self)`,<br/>`clone_source(&self)`,<br/>`set_source(&mut self, source)`,<br/>`clear_source(&mut self)` |
|
||||
| [Module documentation][comments] | [`Vec<SmartString>`][`SmartString`] | documentation of the script | [`metadata`] | `doc(&self)`,<br/>`clear_doc(&mut self)` |
|
||||
| Statements | `Vec<Stmt>` | list of script statements at global level | [`internals`] | `statements(&self)`,<br/>`statements_mut(&mut self)` |
|
||||
| Functions | [`Shared<Module>`][`Module`] | [functions] defined in the script | [`internals`],<br/>not [`no_function`] | `shared_lib(&self)` |
|
||||
| Embedded [module resolver] | [`StaticModuleResolver`][module resolver] | embedded [module resolver] for [self-contained `AST`]({{rootUrl}}/rust/modules/self-contained.md) | [`internals`],<br/>not [`no_module`] | `resolver(&self)` |
|
||||
|
||||
Most of the [`AST`] API is available only under the [`internals`] feature.
|
||||
|
||||
```admonish tip.small "Tip: Source name"
|
||||
|
||||
Use the source name to identify the source script in errors – useful when multiple [modules]
|
||||
are imported recursively.
|
||||
```
|
||||
|
||||
~~~admonish info.small "`AST` public API"
|
||||
|
||||
For the complete [`AST`] API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.AST.html) online.
|
||||
~~~
|
||||
|
||||
|
||||
Extract Only Functions
|
||||
----------------------
|
||||
|
||||
The following methods, not available under [`no_function`], allow manipulation of the [functions]
|
||||
encapsulated within an [`AST`]:
|
||||
|
||||
| Method | Description |
|
||||
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `clone_functions_only(&self)` | clone the [`AST`] into a new [`AST`] with only [functions], excluding statements |
|
||||
| `clone_functions_only_filtered(&self, filter)` | clone the [`AST`] into a new [`AST`] with only [functions] that pass the filter predicate, excluding statements |
|
||||
| `retain_functions(&mut self, filter)` | remove all [functions] in the [`AST`] that do not pass a particular predicate filter; statements are untouched |
|
||||
| `iter_functions(&self)` | return an iterator on all the [functions] in the [`AST`] |
|
||||
| `clear_functions(&mut self)` | remove all [functions] from the [`AST`], leaving only statements |
|
||||
|
||||
|
||||
Extract Only Statements
|
||||
-----------------------
|
||||
|
||||
The following methods allow manipulation of the statements in an [`AST`]:
|
||||
|
||||
| Method | Description |
|
||||
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `clone_statements_only(&self)` | clone the [`AST`] into a new [`AST`] with only the statements, excluding [functions] |
|
||||
| `clear_statements(&mut self)` | remove all statements from the [`AST`], leaving only [functions] |
|
||||
| `iter_literal_variables(&self, constants, variables)` | return an iterator on all top-level literal constant and/or variable definitions in the [`AST`] |
|
||||
|
||||
|
||||
Merge and Combine AST's
|
||||
-----------------------
|
||||
|
||||
The following methods merge one [`AST`] with another:
|
||||
|
||||
| Method | Description |
|
||||
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `merge(&self, &ast)`,<br />`+` operator | append the second [`AST`] to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `merge_filtered(&self, &ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `combine(&mut self, ast)`,<br />`+=` operator | append the second [`AST`] to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `combine_filtered(&mut self, ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
|
||||
When statements are appended, beware that this may change the semantics of the script.
|
||||
|
||||
```rust
|
||||
// First script
|
||||
let ast1 = engine.compile(
|
||||
"
|
||||
fn foo(x) { 42 + x }
|
||||
foo(1)
|
||||
")?;
|
||||
|
||||
// Second script
|
||||
let ast2 = engine.compile(
|
||||
"
|
||||
fn foo(n) { `hello${n}` }
|
||||
foo("!")
|
||||
")?;
|
||||
|
||||
// Merge them
|
||||
let merged = ast1.merge(&ast2);
|
||||
|
||||
// Notice that using the '+' operator also works:
|
||||
let merged = &ast1 + &ast2;
|
||||
```
|
||||
|
||||
`merged` in the above example essentially contains the following script program:
|
||||
|
||||
```js
|
||||
fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
|
||||
foo(1) // <- notice this will be "hello1" instead of 43,
|
||||
// but it is no longer the return value
|
||||
foo("!") // <- returns "hello!"
|
||||
```
|
||||
|
||||
|
||||
Walk an AST
|
||||
-----------
|
||||
|
||||
The [`internals`] feature allows access to internal Rhai data structures, particularly the nodes
|
||||
that make up the [`AST`].
|
||||
|
||||
### AST node types
|
||||
|
||||
There are a few useful types when walking an [`AST`]:
|
||||
|
||||
| Type | Description |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `ASTNode` | an `enum` with two variants: `Expr` or `Stmt` |
|
||||
| `Expr` | an _expression_ |
|
||||
| `Stmt` | a _statement_ |
|
||||
| `BinaryExpr` | a sub-type containing the LHS and RHS of a binary expression |
|
||||
| `FnCallExpr` | a sub-type containing information on a function call |
|
||||
| `CustomExpr` | a sub-type containing information on a [custom syntax] expression |
|
||||
|
||||
The `AST::walk` method takes a callback function and recursively walks the [`AST`] in depth-first
|
||||
manner, with the parent node visited before its children.
|
||||
|
||||
### Callback function signature
|
||||
|
||||
The signature of the callback function takes the following form.
|
||||
|
||||
> ```rust
|
||||
> FnMut(&[ASTNode]) -> bool
|
||||
> ```
|
||||
|
||||
The single argument passed to the method contains a slice of `ASTNode` types representing the path
|
||||
from the current node to the root of the [`AST`].
|
||||
|
||||
Return `true` to continue walking the [`AST`], or `false` to terminate.
|
||||
|
||||
### Children visit order
|
||||
|
||||
The order of visits to the children of each node type:
|
||||
|
||||
| Node type | Children visit order |
|
||||
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`if`] statement | <ol><li>condition expression</li><li>_then_ statements</li><li>_else_ statements (if any)</li></ol> |
|
||||
| [`switch`] statement | <ol><li>match element</li><li>each of the case conditions and statements, in order</li><li>each of the range conditions and statements, in order</li><li>default statements (if any)</li></ol> |
|
||||
| [`while`], [`do`], [`loop`] statement | <ol><li>condition expression</li><li>statements body</li></ol> |
|
||||
| [`for`] statement | <ol><li>collection expression</li><li>statements body</li></ol> |
|
||||
| [`return`] statement | return value expression |
|
||||
| [`throw`] statement | exception value expression |
|
||||
| [`try` ... `catch`][exception] statement | <ol><li>`try` statements body</li><li>`catch` statements body</li></ol> |
|
||||
| [`import`] statement | path expression |
|
||||
| [Array] literal | each of the element expressions, in order |
|
||||
| [Object map] literal | each of the element expressions, in order |
|
||||
| Interpolated [string] | each of the [string]/expression segments, in order |
|
||||
| Indexing | <ol><li>LHS expression</li><li>RHS (index) expression</li></ol> |
|
||||
| Field access/method call | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
|
||||
| `&&`, <code>\|\|</code>, `??` | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
|
||||
| [Function] call, [operator] expression | each of the argument expressions, in order |
|
||||
| [`let`][variable], [`const`][constant] statement | value expression |
|
||||
| Assignment statement | <ol><li>l-value expression</li><li>value expression</li></ol> |
|
||||
| Statements block | each of the statements, in order |
|
||||
| Custom syntax expression | each of the inputs stream, in order |
|
||||
| All others | single child (if any) |
|
24
rhai_engine/rhaibook/engine/builtin.md
Normal file
24
rhai_engine/rhaibook/engine/builtin.md
Normal file
@ -0,0 +1,24 @@
|
||||
Built-in Operators
|
||||
==================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following operators are built-in, meaning that they are always available, even when using a [raw `Engine`].
|
||||
|
||||
All built-in operators are binary, and are supported for both operands of the same type.
|
||||
|
||||
| Operators | Assignment operators | Supported types<br/>(see [standard types]) |
|
||||
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `+`, | `+=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li></ul> |
|
||||
| `-`, `*`, `/`, `%`, `**`, | `-=`, `*=`, `/=`, `%=`, `**=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li></ul> |
|
||||
| `<<`, `>>` | `<<=`, `>>=` | <ul><li>`INT`</li></ul> |
|
||||
| `&`, <code>\|</code>, `^` | `&=`, <code>\|=</code>, `^=` | <ul><li>`INT` (bit-wise)</li><li>`bool` (non-short-circuiting)</li></ul> |
|
||||
| `&&`, <code>\|\|</code> | | <ul><li>`bool` (short-circuits)</li></ul> |
|
||||
| `==`, `!=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`bool`</li><li>`char`</li><li>[string]</li><li>[BLOB]</li><li>numeric [range]</li><li>`()`</li></ul> |
|
||||
| `>`, `>=`, `<`, `<=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li><li>`()`</li></ul> |
|
||||
|
||||
```admonish tip.small
|
||||
|
||||
`FLOAT` and [`Decimal`][rust_decimal] also inter-operate with `INT`, while [strings] inter-operate
|
||||
with [characters][string] for certain operators (e.g. `+`).
|
||||
```
|
275
rhai_engine/rhaibook/engine/call-fn.md
Normal file
275
rhai_engine/rhaibook/engine/call-fn.md
Normal file
@ -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::<i64>(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
|
||||
// ^^^ ^^^^^^^^^^^^^^^^^^
|
||||
// return type must be specified put arguments in a tuple
|
||||
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( 123_i64, ) )?;
|
||||
// ^^^^^^^^^^^^ tuple of one
|
||||
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", () )?;
|
||||
// ^^ unit = tuple of zero
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Warning: Functions with one parameter"
|
||||
|
||||
Functions with only one single parameter is easy to get wrong.
|
||||
|
||||
The proper Rust syntax is a _tuple with one item_:
|
||||
|
||||
```rust
|
||||
( arg , )
|
||||
```
|
||||
|
||||
Notice the comma (`,`) after the argument. Without it, the expression is a single value
|
||||
`(arg)` which is the same as `arg` and not a tuple.
|
||||
|
||||
A syntax error with very confusing error message will be generated by the Rust compiler
|
||||
if the comma is omitted.
|
||||
~~~
|
||||
|
||||
~~~admonish warning.small "Default behavior"
|
||||
|
||||
When using `Engine::call_fn`, the [`AST`] is always evaluated _before_ the [function] is called.
|
||||
|
||||
This is usually desirable in order to [import][`import`] the necessary external [modules] that are
|
||||
needed by the [function].
|
||||
|
||||
All new [variables]/[constants] introduced are, by default, _not_ retained inside the [`Scope`].
|
||||
In other words, the [`Scope`] is _rewound_ before each call.
|
||||
|
||||
If these default behaviors are not desirable, override them with `Engine::call_fn_with_options`.
|
||||
~~~
|
||||
|
||||
|
||||
`FuncArgs` Trait
|
||||
----------------
|
||||
|
||||
```admonish note.side
|
||||
|
||||
Rhai implements [`FuncArgs`][traits] for tuples, arrays and `Vec<T>`.
|
||||
```
|
||||
|
||||
`Engine::call_fn` takes a parameter of any type that implements the [`FuncArgs`][traits] trait,
|
||||
which is used to parse a data type into individual argument values for the [function] call.
|
||||
|
||||
Custom types (e.g. structures) can also implement [`FuncArgs`][traits] so they can be used for
|
||||
calling `Engine::call_fn`.
|
||||
|
||||
```rust
|
||||
use std::iter::once;
|
||||
use rhai::FuncArgs;
|
||||
|
||||
// A struct containing function arguments
|
||||
struct Options {
|
||||
pub foo: bool,
|
||||
pub bar: String,
|
||||
pub baz: i64
|
||||
}
|
||||
|
||||
impl FuncArgs for Options {
|
||||
fn parse<C: Extend<Dynamic>>(self, container: &mut C) {
|
||||
container.extend(once(self.foo.into()));
|
||||
container.extend(once(self.bar.into()));
|
||||
container.extend(once(self.baz.into()));
|
||||
}
|
||||
}
|
||||
|
||||
let options = Options { foo: true, bar: "world", baz: 42 };
|
||||
|
||||
// The type 'Options' can now be used as argument to 'call_fn'
|
||||
// to call a function with three parameters: fn hello(foo, bar, baz)
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", options)?;
|
||||
```
|
||||
|
||||
```admonish warning.small "Warning: You don't need this"
|
||||
|
||||
Implementing `FuncArgs` is almost never needed because Rhai works directly with
|
||||
any [custom type].
|
||||
|
||||
It is used only in niche cases where a [custom type's][custom type] fields need
|
||||
to be split up to pass to functions.
|
||||
```
|
||||
|
||||
|
||||
`Engine::call_fn_with_options`
|
||||
------------------------------
|
||||
|
||||
For more control, use `Engine::call_fn_with_options`, which takes a type `CallFnOptions`:
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, CallFnOptions};
|
||||
|
||||
let options = CallFnOptions::new()
|
||||
.eval_ast(false) // do not evaluate the AST
|
||||
.rewind_scope(false) // do not rewind the scope (i.e. keep new variables)
|
||||
.bind_this_ptr(&mut state); // 'this' pointer
|
||||
|
||||
let result = engine.call_fn_with_options::<i64>(
|
||||
options, // options
|
||||
&mut scope, // scope to use
|
||||
&ast, // AST containing the functions
|
||||
"hello", // function entry-point
|
||||
( "abc", 123_i64 ) // arguments
|
||||
)?;
|
||||
```
|
||||
|
||||
`CallFnOptions` allows control of the following:
|
||||
|
||||
| Field | Type | Default | Build method | Description |
|
||||
| -------------- | :---------------------------------: | :-----: | :-------------: | --------------------------------------------------------------------------------------------------------- |
|
||||
| `eval_ast` | `bool` | `true` | `eval_ast` | evaluate the [`AST`] before calling the target [function] (useful to run [`import` statements]) |
|
||||
| `rewind_scope` | `bool` | `true` | `rewind_scope` | rewind the custom [`Scope`] at the end of the [function] call so new local variables are removed |
|
||||
| `this_ptr` | [`Option<&mut Dynamic>`][`Dynamic`] | `None` | `bind_this_ptr` | bind the `this` pointer to a specific value |
|
||||
| `tag` | [`Option<Dynamic>`][`Dynamic`] | `None` | `with_tag` | set the _custom state_ for this evaluation (accessed via [`NativeCallContext::tag`][`NativeCallContext`]) |
|
||||
|
||||
### Skip evaluation of the `AST`
|
||||
|
||||
By default, the [`AST`] is evaluated before calling the target [function].
|
||||
|
||||
This is necessary to make sure that necessary [modules] imported via [`import`] statements are available.
|
||||
|
||||
Setting `eval_ast` to `false` skips this evaluation.
|
||||
|
||||
### Keep new variables/constants
|
||||
|
||||
By default, the [`Engine`] _rewinds_ the custom [`Scope`] after each call to the initial size,
|
||||
so any new [variable]/[constant] defined are cleared and will not spill into the custom [`Scope`].
|
||||
|
||||
This prevents the [`Scope`] from being continuously polluted by new [variables] and is usually the
|
||||
intuitively expected behavior.
|
||||
|
||||
Setting `rewind_scope` to `false` retains new [variables]/[constants] within the custom [`Scope`].
|
||||
|
||||
This allows the [function] to easily pass values back to the caller by leaving them inside the
|
||||
custom [`Scope`].
|
||||
|
||||
~~~admonish warning.small "Warning: new variables persist in `Scope`"
|
||||
|
||||
If the [`Scope`] is not rewound, beware that all [variables]/[constants] defined at top level of the
|
||||
[function] or in the script body will _persist_ inside the custom [`Scope`].
|
||||
|
||||
If any of them are temporary and not intended to be retained, define them inside a statements block
|
||||
(see example below).
|
||||
~~~
|
||||
|
||||
```rust
|
||||
┌─────────────┐
|
||||
│ Rhai script │
|
||||
└─────────────┘
|
||||
|
||||
fn initialize() {
|
||||
let x = 42; // 'x' is retained
|
||||
let y = x * 2; // 'y' is retained
|
||||
|
||||
// Use a new statements block to define temp variables
|
||||
{
|
||||
let temp = x + y; // 'temp' is NOT retained
|
||||
|
||||
foo = temp * temp; // 'foo' is visible in the scope
|
||||
}
|
||||
}
|
||||
|
||||
let foo = 123; // 'foo' is retained
|
||||
|
||||
// Use a new statements block to define temp variables
|
||||
{
|
||||
let bar = foo / 2; // 'bar' is NOT retained
|
||||
|
||||
foo = bar * bar;
|
||||
}
|
||||
|
||||
|
||||
┌──────┐
|
||||
│ Rust │
|
||||
└──────┘
|
||||
|
||||
let options = CallFnOptions::new().rewind_scope(false);
|
||||
|
||||
engine.call_fn_with_options(options, &mut scope, &ast, "initialize", ())?;
|
||||
|
||||
// At this point, 'scope' contains these variables: 'foo', 'x', 'y'
|
||||
```
|
||||
|
||||
### Bind the `this` pointer
|
||||
|
||||
```admonish note.side
|
||||
|
||||
`Engine::call_fn` cannot call functions in _method-call_ style.
|
||||
```
|
||||
|
||||
`CallFnOptions` can also bind a value to the `this` pointer of a script-defined [function].
|
||||
|
||||
It is possible, then, to call a [function] that uses `this`.
|
||||
|
||||
```rust
|
||||
let ast = engine.compile("fn action(x) { this += x; }")?;
|
||||
|
||||
let mut value: Dynamic = 1_i64.into();
|
||||
|
||||
let options = CallFnOptions::new()
|
||||
.eval_ast(false)
|
||||
.rewind_scope(false)
|
||||
.bind_this_ptr(&mut value);
|
||||
|
||||
engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?;
|
||||
|
||||
assert_eq!(value.as_int()?, 42);
|
||||
```
|
160
rhai_engine/rhaibook/engine/compile.md
Normal file
160
rhai_engine/rhaibook/engine/compile.md
Normal file
@ -0,0 +1,160 @@
|
||||
Compile a Script (to AST)
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
To repeatedly evaluate a script, _compile_ it first with `Engine::compile` into an `AST`
|
||||
(**A**bstract **S**yntax **T**ree) form.
|
||||
|
||||
`Engine::eval_ast_XXX` and `Engine::run_ast_XXX` evaluate a pre-compiled `AST`.
|
||||
|
||||
```rust
|
||||
// Compile to an AST and store it for later evaluations
|
||||
let ast = engine.compile("40 + 2")?;
|
||||
|
||||
for _ in 0..42 {
|
||||
let result: i64 = engine.eval_ast(&ast)?;
|
||||
|
||||
println!("Answer #{i}: {result}"); // prints 42
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Compile script file"
|
||||
|
||||
Compiling script files is also supported via `Engine::compile_file`
|
||||
(not available for [`no_std`] or [WASM] builds).
|
||||
|
||||
```rust
|
||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish info.small "See also: `AST` manipulation API"
|
||||
|
||||
Advanced users who may want to manipulate an `AST`, especially the functions contained within,
|
||||
should see the section on [_Manage AST's_](ast.md) for more details.
|
||||
~~~
|
||||
|
||||
|
||||
Practical Use – 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::<i64>(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::<i64>(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::<i64>(user_script)?;
|
||||
|
||||
println!("{result}");
|
||||
}
|
||||
```
|
97
rhai_engine/rhaibook/engine/custom-op.md
Normal file
97
rhai_engine/rhaibook/engine/custom-op.md
Normal file
@ -0,0 +1,97 @@
|
||||
Custom Operators
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [this section][precedence] for details on operator [precedence].
|
||||
```
|
||||
|
||||
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
|
||||
customized operators performing specific logic.
|
||||
|
||||
`Engine::register_custom_operator` registers a [keyword] as a custom operator, giving it a particular
|
||||
_[precedence]_ (which cannot be zero).
|
||||
|
||||
Support for custom operators can be disabled via the [`no_custom_syntax`] feature.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a custom operator '#' and give it a precedence of 160
|
||||
// (i.e. between +|- and *|/)
|
||||
// Also register the implementation of the custom operator as a function
|
||||
engine.register_custom_operator("#", 160)?
|
||||
.register_fn("#", |x: i64, y: i64| (x * y) - (x + y));
|
||||
|
||||
// The custom operator can be used in expressions
|
||||
let result = engine.eval_expression::<i64>("1 + 2 * 3 # 4 - 5 / 6")?;
|
||||
// ^ custom operator
|
||||
|
||||
// The above is equivalent to: 1 + ((2 * 3) # 4) - (5 / 6)
|
||||
result == 15;
|
||||
```
|
||||
|
||||
|
||||
Alternatives to a Custom Operator
|
||||
---------------------------------
|
||||
|
||||
Custom operators are merely _syntactic sugar_. They map directly to registered functions.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Define 'foo' operator
|
||||
engine.register_custom_operator("foo", 160)?;
|
||||
|
||||
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // use custom operator
|
||||
|
||||
engine.eval::<i64>("1 + foo(2 * 3, 4) - 5 / 6")?; // <- above is equivalent to this
|
||||
```
|
||||
|
||||
A script using custom operators can always be pre-processed, via a pre-processor application,
|
||||
into a syntax that uses the corresponding function calls.
|
||||
|
||||
Using `Engine::register_custom_operator` merely enables a convenient shortcut.
|
||||
|
||||
|
||||
Must be a Valid Identifier or Reserved Symbol
|
||||
---------------------------------------------
|
||||
|
||||
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
|
||||
|
||||
Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols),
|
||||
[disabled operators or keywords][disable keywords and operators].
|
||||
|
||||
```rust
|
||||
engine.register_custom_operator("foo", 20)?; // 'foo' is a valid custom operator
|
||||
|
||||
engine.register_custom_operator("#", 20)?; // the reserved symbol '#' is also
|
||||
// a valid custom operator
|
||||
|
||||
engine.register_custom_operator("+", 30)?; // <- error: '+' is an active operator
|
||||
|
||||
engine.register_custom_operator("=>", 30)?; // <- error: '=>' is an active symbol
|
||||
```
|
||||
|
||||
|
||||
Binary Operators Only
|
||||
---------------------
|
||||
|
||||
All custom operators must be _binary_ (i.e. they take two operands).
|
||||
_Unary_ custom operators are not supported.
|
||||
|
||||
```rust
|
||||
// Register unary '#' operator
|
||||
engine.register_custom_operator("#", 160)?
|
||||
.register_fn("#", |x: i64| x * x);
|
||||
|
||||
engine.eval::<i64>("# 42")?; // <- syntax error
|
||||
```
|
246
rhai_engine/rhaibook/engine/custom-syntax-parsers.md
Normal file
246
rhai_engine/rhaibook/engine/custom-syntax-parsers.md
Normal file
@ -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<Option<ImmutableString>, ParseError>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | :---------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `symbols` | [`&[ImmutableString]`][`ImmutableString`] | a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`; `$ident$` and other literal markers are replaced by the actual text |
|
||||
| `look_ahead` | `&str` | a string slice containing the next symbol that is about to be read |
|
||||
| `state` | [`&mut Dynamic`][`Dynamic`] | mutable reference to a user-defined _state_ |
|
||||
|
||||
Most strings are [`ImmutableString`]'s so it is usually more efficient to just `clone` the appropriate one
|
||||
(if any matches, or keep an internal cache for commonly-used symbols) as the return value.
|
||||
|
||||
### Parameter #1 – 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<Option<ImmutableString>, ParseError>` where:
|
||||
|
||||
| Value | Description |
|
||||
| :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Ok(None)` | parsing is complete and there is no more symbol to match |
|
||||
| `Ok(Some(symbol))` | the next `symbol` to match, which can also be `$expr$`, `$ident$`, `$block$` etc. |
|
||||
| `Err(error)` | `error` that is reflected back to the [`Engine`] – normally `ParseError( ParseErrorType::BadInput( LexError::ImproperSymbol(message) ), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. |
|
||||
|
||||
A custom parser always returns `Some` with the _next_ symbol expected (which can be `$ident$`,
|
||||
`$expr$`, `$block$` etc.) or `None` if parsing should terminate (_without_ reading the
|
||||
look-ahead symbol).
|
||||
|
||||
#### The `$$` return symbol short-cut
|
||||
|
||||
A return symbol starting with `$$` is treated specially.
|
||||
|
||||
Like `None`, it also terminates parsing, but at the same time it adds this symbol as text into the
|
||||
_inputs_ stream at the end.
|
||||
|
||||
This is typically used to inform the implementation function which [custom syntax] variant was
|
||||
actually parsed.
|
||||
|
||||
```rust
|
||||
fn implementation_fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
{
|
||||
// Get the last symbol
|
||||
let key = inputs.last().unwrap().get_string_value().unwrap();
|
||||
|
||||
// Make sure it starts with '$$'
|
||||
assert!(key.starts_with("$$"));
|
||||
|
||||
// Execute the custom syntax expression
|
||||
match key {
|
||||
"$$hello" => { ... }
|
||||
"$$world" => { ... }
|
||||
"$$foo" => { ... }
|
||||
"$$bar" => { ... }
|
||||
_ => Err(...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`$$` is a convenient _short-cut_. An alternative method is to pass such information in the user-defined
|
||||
custom _state_.
|
||||
|
||||
### Implementation Function Signature
|
||||
|
||||
The signature of an implementation function for `Engine::register_custom_syntax_with_state_raw` is
|
||||
as follows, which is slightly different from the function for `Engine::register_custom_syntax`.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------: | ----------------------------------------------------- |
|
||||
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
| `inputs` | `&[Expression]` | a list of input expression trees |
|
||||
| `state` | [`&Dynamic`][`Dynamic`] | reference to the user-defined state |
|
||||
|
||||
|
||||
Custom Parser Example
|
||||
---------------------
|
||||
|
||||
```rust
|
||||
engine.register_custom_syntax_with_state_raw(
|
||||
// The leading symbol - which needs not be an identifier.
|
||||
"perform",
|
||||
// The custom parser implementation - always returns the next symbol expected
|
||||
// 'look_ahead' is the next symbol about to be read
|
||||
//
|
||||
// Return symbols starting with '$$' also terminate parsing but allows us
|
||||
// to determine which syntax variant was actually parsed so we can perform the
|
||||
// appropriate action. This is a convenient short-cut to keeping the value
|
||||
// inside the state.
|
||||
//
|
||||
// The return type is 'Option<ImmutableString>' to allow common text strings
|
||||
// to be interned and shared easily, reducing allocations during parsing.
|
||||
|symbols, look_ahead, state| match symbols.len() {
|
||||
// perform ...
|
||||
1 => Ok(Some("$ident$".into())),
|
||||
// perform command ...
|
||||
2 => match symbols[1].as_str() {
|
||||
"action" => Ok(Some("$expr$".into())),
|
||||
"hello" => Ok(Some("world".into())),
|
||||
"update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())),
|
||||
"cleanup" => Ok(Some("$$cleanup".into())),
|
||||
cmd => Err(LexError::ImproperSymbol(format!("Improper command: {cmd}"))
|
||||
.into_err(Position::NONE)),
|
||||
},
|
||||
// perform command arg ...
|
||||
3 => match (symbols[1].as_str(), symbols[2].as_str()) {
|
||||
("action", _) => Ok(Some("$$action".into())),
|
||||
("hello", "world") => Ok(Some("$$hello-world".into())),
|
||||
("update", arg) => match arg {
|
||||
"system" => Ok(Some("$$update-system".into())),
|
||||
"client" => Ok(Some("$$update-client".into())),
|
||||
_ => Err(LexError::ImproperSymbol(format!("Cannot update {arg}"))
|
||||
.into_err(Position::NONE))
|
||||
},
|
||||
("check", arg) => Ok(Some("$$check".into())),
|
||||
("add", arg) => Ok(Some("$$add".into())),
|
||||
("remove", arg) => Ok(Some("$$remove".into())),
|
||||
(cmd, arg) => Err(LexError::ImproperSymbol(
|
||||
format!("Invalid argument for command {cmd}: {arg}")
|
||||
).into_err(Position::NONE)),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
// No variables declared/removed by this custom syntax
|
||||
false,
|
||||
// Implementation function
|
||||
|context, inputs, state| {
|
||||
let cmd = inputs.last().unwrap().get_string_value().unwrap();
|
||||
|
||||
match cmd {
|
||||
"$$cleanup" => { ... }
|
||||
"$$action" => { ... }
|
||||
"$$update-system" => { ... }
|
||||
"$$update-client" => { ... }
|
||||
"$$check" => { ... }
|
||||
"$$add" => { ... }
|
||||
"$$remove" => { ... }
|
||||
_ => Err(format!("Invalid command: {cmd}"))
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
491
rhai_engine/rhaibook/engine/custom-syntax.md
Normal file
491
rhai_engine/rhaibook/engine/custom-syntax.md
Normal file
@ -0,0 +1,491 @@
|
||||
Extend Rhai with Custom Syntax
|
||||
==============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
For the ultimate adventurous, there is a built-in facility to _extend_ the Rhai language with
|
||||
custom-defined _syntax_.
|
||||
|
||||
But before going off to define the next weird statement type, heed this warning:
|
||||
|
||||
```admonish danger.small "Don't Do It™"
|
||||
|
||||
Stick with standard language syntax as much as possible.
|
||||
|
||||
Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another obscure
|
||||
language syntax just to do something.
|
||||
|
||||
Try [custom operators] first. A custom syntax should be considered a _last resort_.
|
||||
```
|
||||
|
||||
```admonish success.small "Where this might be useful"
|
||||
|
||||
* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing.
|
||||
|
||||
* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances
|
||||
understanding of the code's intent.
|
||||
|
||||
* Where certain logic cannot be easily encapsulated inside a function.
|
||||
|
||||
* Where you just want to confuse your user and make their lives miserable, because you can.
|
||||
```
|
||||
|
||||
```admonish tip.small "Disable custom syntax"
|
||||
|
||||
Custom syntax can be disabled via the [`no_custom_syntax`] feature.
|
||||
```
|
||||
|
||||
|
||||
How to Do It
|
||||
------------
|
||||
|
||||
### Step One – 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<Dynamic, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------: | ----------------------------------------------------- |
|
||||
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
| `inputs` | `&[Expression]` | a list of input expression trees |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
#### Return value
|
||||
|
||||
Return value is the result of evaluating the custom syntax expression.
|
||||
|
||||
#### Access arguments
|
||||
|
||||
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
|
||||
and statements blocks (`$block$`) are provided.
|
||||
|
||||
To access a particular argument, use the following patterns:
|
||||
|
||||
| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description |
|
||||
| :-----------: | ------------------------------------------------------------------------------------------------------------ | :---------------------------------: | --------------------------------------------------- |
|
||||
| `$ident$` | `inputs[n].get_string_value().unwrap()` | `&str` | [variable] name |
|
||||
| `$symbol$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()` | [`ImmutableString`] | symbol literal |
|
||||
| `$expr$` | `&inputs[n]` | `&Expression` | an expression tree |
|
||||
| `$block$` | `&inputs[n]` | `&Expression` | an expression tree |
|
||||
| `$func$` | `&inputs[n]` | `&Expression` | an expression tree (output is a [function pointer]) |
|
||||
| `$bool$` | `inputs[n].get_literal_value::<bool>().unwrap()` | `bool` | boolean value |
|
||||
| `$int$` | `inputs[n].get_literal_value::<INT>().unwrap()` | `INT` | integer number |
|
||||
| `$float$` | `inputs[n].get_literal_value::<FLOAT>().unwrap()` | `FLOAT` | floating-point number |
|
||||
| `$string$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()`<br/><br/>`inputs[n].get_string_value().unwrap()` | [`ImmutableString`]<br/><br/>`&str` | [string] text |
|
||||
|
||||
#### Get literal constants
|
||||
|
||||
Several argument types represent literal constants that can be obtained directly via
|
||||
`Expression::get_literal_value<T>` or `Expression::get_string_value` (for [strings]).
|
||||
|
||||
```rust
|
||||
let expression = &inputs[0];
|
||||
|
||||
// Use 'get_literal_value' with a turbo-fish type to extract the value
|
||||
let string_value = expression.get_literal_value::<ImmutableString>().unwrap();
|
||||
let string_slice = expression.get_string_value().unwrap();
|
||||
|
||||
let float_value = expression.get_literal_value::<FLOAT>().unwrap();
|
||||
|
||||
// Or assign directly to a variable with type...
|
||||
let int_value: i64 = expression.get_literal_value().unwrap();
|
||||
|
||||
// Or use type inference!
|
||||
let bool_value = expression.get_literal_value().unwrap();
|
||||
|
||||
if bool_value { ... } // 'bool_value' inferred to be 'bool'
|
||||
```
|
||||
|
||||
#### Evaluate an expression tree
|
||||
|
||||
Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expression tree
|
||||
within the current evaluation context.
|
||||
|
||||
```rust
|
||||
let expression = &inputs[0];
|
||||
let result = context.eval_expression_tree(expression)?;
|
||||
```
|
||||
|
||||
#### Retain variables in block scope
|
||||
|
||||
When an expression tree actually contains a statements block (i.e. `$block`), local
|
||||
[variables]/[constants] defined within that block are usually removed at the end of the block.
|
||||
|
||||
Sometimes it is useful to retain these local [variables]/[constants] for further processing
|
||||
(e.g. collecting new [variables] into an [object map]).
|
||||
|
||||
As such, evaluate the expression tree using the `EvalContext::eval_expression_tree_raw` method which
|
||||
contains a parameter to control whether the statements block should be rewound.
|
||||
|
||||
```rust
|
||||
// Assume 'expression' contains a statements block with local variable definitions
|
||||
let expression = &inputs[0];
|
||||
let result = context.eval_expression_tree_raw(expression, false)?;
|
||||
|
||||
// Variables defined within 'expression' persist in context.scope()
|
||||
```
|
||||
|
||||
#### Declare variables
|
||||
|
||||
New [variables]/[constants] maybe declared (usually with a [variable] name that is passed in via `$ident$`).
|
||||
|
||||
It can simply be pushed into the [`Scope`].
|
||||
|
||||
```rust
|
||||
let var_name = inputs[0].get_string_value().unwrap();
|
||||
let expression = &inputs[1];
|
||||
|
||||
context.scope_mut().push(var_name, 0_i64); // declare new variable
|
||||
|
||||
let result = context.eval_expression_tree(expression)?;
|
||||
```
|
||||
|
||||
|
||||
### Step Three – Register the Custom Syntax
|
||||
|
||||
Use `Engine::register_custom_syntax` to register a custom syntax.
|
||||
|
||||
Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting
|
||||
with that symbol, the previous syntax will be overwritten.
|
||||
|
||||
The syntax is passed simply as a slice of `&str`.
|
||||
|
||||
```rust
|
||||
// Custom syntax implementation
|
||||
fn implementation_func(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let var_name = inputs[0].get_string_value().unwrap();
|
||||
let stmt = &inputs[1];
|
||||
let condition = &inputs[2];
|
||||
|
||||
// Push new variable into the scope BEFORE 'context.eval_expression_tree'
|
||||
context.scope_mut().push(var_name.to_string(), 0_i64);
|
||||
|
||||
let mut count = 0_i64;
|
||||
|
||||
loop {
|
||||
// Evaluate the statements block
|
||||
context.eval_expression_tree(stmt)?;
|
||||
|
||||
count += 1;
|
||||
|
||||
// Declare a new variable every three turns...
|
||||
if count % 3 == 0 {
|
||||
context.scope_mut().push(format!("{var_name}{count}"), count);
|
||||
}
|
||||
|
||||
// Evaluate the condition expression
|
||||
let expr_result = !context.eval_expression_tree(condition)?;
|
||||
|
||||
match expr_result.as_bool() {
|
||||
Ok(true) => (),
|
||||
Ok(false) => break,
|
||||
Err(err) => return Err(EvalAltResult::ErrorMismatchDataType(
|
||||
"bool".to_string(),
|
||||
err.to_string(),
|
||||
condition.position(),
|
||||
).into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
// Register the custom syntax (sample): exec<x> -> { x += 1 } while x < 0
|
||||
engine.register_custom_syntax(
|
||||
[ "exec", "<", "$ident$", ">", "->", "$block$", "while", "$expr$" ], // the custom syntax
|
||||
true, // variables declared within this custom syntax
|
||||
implementation_func
|
||||
)?;
|
||||
```
|
||||
|
||||
Remember that a custom syntax acts as an _expression_, so it can show up practically anywhere:
|
||||
|
||||
```rust
|
||||
// Use as an expression:
|
||||
let foo = (exec<x> -> { x += 1 } while x < 42) * 100;
|
||||
|
||||
// New variables are successfully declared...
|
||||
x == 42;
|
||||
x3 == 3;
|
||||
x6 == 6;
|
||||
|
||||
// Use as a function call argument:
|
||||
do_something(exec<x> -> { x += 1 } while x < 42, 24, true);
|
||||
|
||||
// Use as a statement:
|
||||
exec<x> -> { x += 1 } while x < 0;
|
||||
// ^ terminate statement with ';' unless the custom
|
||||
// syntax already ends with '}'
|
||||
```
|
||||
|
||||
|
||||
### Step Four – 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())
|
||||
}
|
||||
})?;
|
||||
```
|
54
rhai_engine/rhaibook/engine/debugging/break-points.md
Normal file
54
rhai_engine/rhaibook/engine/debugging/break-points.md
Normal file
@ -0,0 +1,54 @@
|
||||
Break-Points
|
||||
============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A _break-point_ **always** stops the current evaluation and calls the [debugging][debugger]
|
||||
callback.
|
||||
|
||||
A break-point is represented by the `debugger::BreakPoint` type, which is an `enum` with
|
||||
the following variants.
|
||||
|
||||
| `BreakPoint` variant | Not available under | Description |
|
||||
| ---------------------------------------- | :-----------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AtPosition { source, pos, enabled }` | [`no_position`] | breaks at the specified position in the specified source (empty if none);<br/>if `pos` is at beginning of line, breaks anywhere on the line |
|
||||
| `AtFunctionName { name, enabled }` | | breaks when a function matching the specified name is called (can be [operator]) |
|
||||
| `AtFunctionCall { name, args, enabled }` | | breaks when a function matching the specified name (can be [operator]) and the specified number of arguments is called |
|
||||
| `AtProperty { name, enabled }` | [`no_object`] | breaks at the specified property access |
|
||||
|
||||
|
||||
Access Break-Points
|
||||
-------------------
|
||||
|
||||
The following [`debugger::Debugger`] methods allow access to break-points for manipulation.
|
||||
|
||||
| Method | Return type | Description |
|
||||
| ------------------ | :--------------------: | ------------------------------------------------- |
|
||||
| `break_points` | `&[BreakPoint]` | returns a slice of all `BreakPoint`'s |
|
||||
| `break_points_mut` | `&mut Vec<BreakPoint>` | returns a mutable reference to all `BreakPoint`'s |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::debugger::*;
|
||||
|
||||
let debugger = &mut context.global_runtime_state_mut().debugger_mut();
|
||||
|
||||
// Get number of break-points.
|
||||
let num_break_points = debugger.break_points().len();
|
||||
|
||||
// Add a new break-point on calls to 'foo(_, _, _)'
|
||||
debugger.break_points_mut().push(
|
||||
BreakPoint::AtFunctionCall { name: "foo".into(), args: 3 }
|
||||
);
|
||||
|
||||
// Display all break-points
|
||||
for bp in debugger.break_points().iter() {
|
||||
println!("{bp}");
|
||||
}
|
||||
|
||||
// Clear all break-points
|
||||
debugger.break_points_mut().clear();
|
||||
```
|
32
rhai_engine/rhaibook/engine/debugging/call-stack.md
Normal file
32
rhai_engine/rhaibook/engine/debugging/call-stack.md
Normal file
@ -0,0 +1,32 @@
|
||||
Call Stack
|
||||
==========
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side.wide "Call stack frames"
|
||||
|
||||
Each "frame" in the call stack corresponds to one layer of [function] call (script-defined
|
||||
or native Rust).
|
||||
|
||||
A call stack frame has the type `debugger::CallStackFrame`.
|
||||
```
|
||||
|
||||
The [debugger] keeps a _call stack_ of [function] calls with argument values.
|
||||
|
||||
This call stack can be examined to determine the control flow at any particular point.
|
||||
|
||||
The `Debugger::call_stack` method returns a slice of all call stack frames.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::*;
|
||||
|
||||
let debugger = &mut context.global_runtime_state().debugger();
|
||||
|
||||
// Get depth of the call stack.
|
||||
let depth = debugger.call_stack().len();
|
||||
|
||||
// Display all function calls
|
||||
for frame in debugger.call_stack().iter() {
|
||||
println!("{frame}");
|
||||
}
|
||||
```
|
110
rhai_engine/rhaibook/engine/debugging/debugger.md
Normal file
110
rhai_engine/rhaibook/engine/debugging/debugger.md
Normal file
@ -0,0 +1,110 @@
|
||||
Register with the Debugger
|
||||
==========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Hooking up a debugging interface is as simple as providing closures to the [`Engine`]'s built-in
|
||||
debugger via `Engine::register_debugger`.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::{ASTNode, DebuggerCommand};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_debugger(
|
||||
// Provide a callback to initialize the debugger state
|
||||
|engine, mut debugger| {
|
||||
debugger.set_state(...);
|
||||
debugger
|
||||
},
|
||||
// Provide a callback for each debugging step
|
||||
|context, event, node, source, pos| {
|
||||
...
|
||||
|
||||
DebuggerCommand::StepOver
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Accessing the `Debugger`"
|
||||
|
||||
The type `debugger::Debugger` allows for manipulating [break-points], among others.
|
||||
|
||||
The [`Engine`]'s debugger instance can be accessed via `context.global_runtime_state().debugger()` (immutable)
|
||||
or `context.global_runtime_state_mut().debugger_mut()` (mutable).
|
||||
~~~
|
||||
|
||||
|
||||
Callback Functions Signature
|
||||
----------------------------
|
||||
|
||||
There are two callback functions to register for the debugger.
|
||||
|
||||
The first is simply a function to initialize the state of the debugger with the following signature.
|
||||
|
||||
> ```rust
|
||||
> Fn(&Engine, debugger::Debugger) -> debugger::Debugger
|
||||
> ```
|
||||
|
||||
The second callback is a function which will be called by the debugger during each step, with the
|
||||
following signature.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: EvalContext, event: debugger::DebuggerEvent, node: ASTNode, source: &str, pos: Position) -> Result<debugger::DebuggerCommand, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
| `event` | `DebuggerEvent` | an `enum` indicating the event that triggered the debugger |
|
||||
| `node` | `ASTNode` | an `enum` with two variants: `Expr` or `Stmt`, corresponding to the current expression node or statement node in the [`AST`] |
|
||||
| `source` | `&str` | the source of the current [`AST`], or empty if none |
|
||||
| `pos` | `Position` | position of the current node, same as `node.position()` |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Event
|
||||
|
||||
The `event` parameter of the second closure passed to `Engine::register_debugger` contains a
|
||||
`debugger::DebuggerEvent` which is an `enum` with the following variants.
|
||||
|
||||
| `DebuggerEvent` variant | Description |
|
||||
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `Start` | the debugger is triggered at the beginning of evaluation |
|
||||
| `Step` | the debugger is triggered at the next step of evaluation |
|
||||
| `BreakPoint(`_n_`)` | the debugger is triggered by the _n_-th [break-point] |
|
||||
| `FunctionExitWithValue(`_r_`)` | the debugger is triggered by a function call returning with value _r_ which is `&Dynamic` |
|
||||
| `FunctionExitWithError(`_err_`)` | the debugger is triggered by a function call exiting with error _err_ which is `&EvalAltResult` |
|
||||
| `End` | the debugger is triggered at the end of evaluation |
|
||||
|
||||
### Return value
|
||||
|
||||
```admonish tip.side.wide "Tip: Initialization"
|
||||
|
||||
When a script starts evaluation, the debugger always stops at the very _first_ [`AST`] node
|
||||
with the `event` parameter set to `DebuggerStatus::Start`.
|
||||
|
||||
This allows initialization to be done (e.g. setting up [break-points]).
|
||||
```
|
||||
|
||||
The second closure passed to `Engine::register_debugger` will be called when stepping into or over
|
||||
expressions and statements, or when [break-points] are hit.
|
||||
|
||||
The return type of the closure is `Result<debugger::DebuggerCommand, Box<EvalAltResult>>`.
|
||||
|
||||
If an error is returned, the script evaluation at that particular instance returns with that
|
||||
particular error. It is thus possible to _abort_ the script evaluation by returning an error that is
|
||||
not _catchable_, such as `EvalAltResult::ErrorTerminated`.
|
||||
|
||||
If no error is returned, then the return `debugger::DebuggerCommand` variant determines the
|
||||
continued behavior of the debugger.
|
||||
|
||||
| `DebuggerCommand` variant | Behavior | `gdb` equivalent |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | :--------------: |
|
||||
| `Continue` | continue with normal script evaluation | `continue` |
|
||||
| `StepInto` | run to the next expression or statement, diving into functions | `step` |
|
||||
| `StepOver` | run to the next expression or statement, skipping over functions | |
|
||||
| `Next` | run to the next statement, skipping over functions | `next` |
|
||||
| `FunctionExit` | run to the end of the current function call; debugger is triggered _before_ the function call returns and the [`Scope`] cleared | `finish` |
|
50
rhai_engine/rhaibook/engine/debugging/index.md
Normal file
50
rhai_engine/rhaibook/engine/debugging/index.md
Normal file
@ -0,0 +1,50 @@
|
||||
Debugging Interface
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
For systems open to external user-created scripts, it is usually desirable to provide a _debugging_
|
||||
experience to the user. The alternative is to provide a custom implementation of [`debug`] via
|
||||
`Engine::on_debug` that traps debug output to show in a side panel, for example, which is actually
|
||||
extremely simple.
|
||||
|
||||
Nevertheless, in some systems, it may not be convenient, or even possible, for the user to debug his
|
||||
or her scripts simply via good-old [`print`] or [`debug`] statements – 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;</br>returns an empty [array] if no [debugger] is registered |
|
||||
|
||||
```rust
|
||||
// This recursive function prints its own call stack during each run
|
||||
fn foo(x) {
|
||||
print(back_trace()); // prints the current call stack
|
||||
|
||||
if x > 0 {
|
||||
foo(x - 1)
|
||||
}
|
||||
}
|
||||
```
|
92
rhai_engine/rhaibook/engine/debugging/server.md
Normal file
92
rhai_engine/rhaibook/engine/debugging/server.md
Normal file
@ -0,0 +1,92 @@
|
||||
Implement a Debugging Server
|
||||
============================
|
||||
|
||||
Sometimes it is desirable to embed a debugging _server_ inside the application such that an external
|
||||
debugger interface can connect to the application's running instance at runtime.
|
||||
|
||||
This way, when scripts are run within the application, it is easy for an external interface to debug
|
||||
those scripts as they run.
|
||||
|
||||
Such connections may take the form of any communication channel, for example a TCP/IP connection, a
|
||||
named pipe, or an MPSC channel.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
### Server side
|
||||
|
||||
The following example assumes bi-direction, blocking messaging channels, such as a WebSocket
|
||||
connection, with a server that accepts connections and creates those channels.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::{ASTNode, DebuggerCommand};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_debugger(
|
||||
// Use the initialization callback to set up the communications channel
|
||||
// and listen to it
|
||||
|engine, mut debugger| {
|
||||
// Create server that will listen to requests
|
||||
let mut server = MyCommServer::new();
|
||||
server.listen("localhost:8080");
|
||||
|
||||
// Wrap it up in a shared locked cell so it can be 'Clone'
|
||||
let server = Rc::new(RefCell::new(server));
|
||||
|
||||
// Store the channel in the debugger state
|
||||
debugger.set_state(Dynamic::from(server));
|
||||
debugger
|
||||
},
|
||||
// Trigger the server during each debugger stop point
|
||||
|context, event, node, source, pos| {
|
||||
// Get the state
|
||||
let mut state = context.tag_mut();
|
||||
|
||||
// Get the server
|
||||
let mut server = state.write_lock::<MyCommServer>().unwrap();
|
||||
|
||||
// Send the event to the server - blocking call
|
||||
server.send_message(...);
|
||||
|
||||
// Receive command - blocking call
|
||||
match server.receive_message() {
|
||||
None => DebuggerCommand::StepOver,
|
||||
// Decode command
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
:
|
||||
:
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Client side
|
||||
|
||||
The client can be any system that can work with WebSockets for messaging.
|
||||
|
||||
```js
|
||||
// Connect to the application's debugger
|
||||
let webSocket = new WebSocket("wss://localhost:8080");
|
||||
|
||||
webSocket.on_message = (event) => {
|
||||
let msg = JSON.parse(event.data);
|
||||
|
||||
switch msg.type {
|
||||
// handle debugging events from the application...
|
||||
case "step": {
|
||||
:
|
||||
}
|
||||
:
|
||||
:
|
||||
}
|
||||
};
|
||||
|
||||
// Send command to the application
|
||||
webSocket.send("step-over");
|
||||
```
|
67
rhai_engine/rhaibook/engine/debugging/state.md
Normal file
67
rhai_engine/rhaibook/engine/debugging/state.md
Normal file
@ -0,0 +1,67 @@
|
||||
Debugger State
|
||||
==============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Sometimes it is useful to keep a persistent _state_ within the [debugger].
|
||||
|
||||
The `Engine::register_debugger` API accepts a function that returns the initial value of the
|
||||
[debugger's][debugger] state, which is a [`Dynamic`] and can hold any value.
|
||||
|
||||
This state value is the stored into the [debugger]'s custom state.
|
||||
|
||||
|
||||
Access the Debugger State
|
||||
-------------------------
|
||||
|
||||
Use `EvalContext::global_runtime_state().debugger()` (immutable) or
|
||||
`EvalContext::global_runtime_state_mut().debugger_mut()` (mutable) to gain access to the current
|
||||
[`debugger::Debugger`] instance.
|
||||
|
||||
The following [`debugger::Debugger`] methods allow access to the custom [debugger] state.
|
||||
|
||||
| Method | Parameter type | Return type | Description |
|
||||
| ----------- | :-------------------------------: | :-------------------------: | ----------------------------------------------- |
|
||||
| `state` | _none_ | [`&Dynamic`][`Dynamic`] | returns the custom state |
|
||||
| `state_mut` | _none_ | [`&mut Dynamic`][`Dynamic`] | returns a mutable reference to the custom state |
|
||||
| `set_state` | [`impl Into<Dynamic>`][`Dynamic`] | _none_ | sets the value of the custom state |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.register_debugger(
|
||||
|engine, mut debugger| {
|
||||
// Say, use an object map for the debugger state
|
||||
let mut state = Map::new();
|
||||
// Initialize properties
|
||||
state.insert("hello".into(), 42_64.into());
|
||||
state.insert("foo".into(), false.into());
|
||||
|
||||
debugger.set_state(state);
|
||||
debugger
|
||||
},
|
||||
|context, node, source, pos| {
|
||||
// Print debugger state - which is an object map
|
||||
let state = context.global_runtime_state().debugger().state();
|
||||
println!("Current state = {state}");
|
||||
|
||||
// Get the state as an object map
|
||||
let mut state = context.global_runtime_state_mut()
|
||||
.debugger_mut().state_mut()
|
||||
.write_lock::<Map>().unwrap();
|
||||
|
||||
// Read state
|
||||
let hello = state.get("hello").unwrap().as_int().unwrap();
|
||||
|
||||
// Modify state
|
||||
state.insert("hello".into(), (hello + 1).into());
|
||||
state.insert("foo".into(), true.into());
|
||||
state.insert("something_new".into(), "hello, world!".into());
|
||||
|
||||
// Continue with debugging
|
||||
Ok(DebuggerCommand::StepInto)
|
||||
}
|
||||
);
|
||||
```
|
85
rhai_engine/rhaibook/engine/def-var.md
Normal file
85
rhai_engine/rhaibook/engine/def-var.md
Normal file
@ -0,0 +1,85 @@
|
||||
Variable Definition Filter
|
||||
==========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_def_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_def_var
|
||||
|
||||
|
||||
Although it is easy to disable variable _[shadowing]_ via [`Engine::set_allow_shadowing`][options],
|
||||
sometimes more fine-grained control is needed.
|
||||
|
||||
For example, it may be the case that not _all_ variables [shadowing] must be disallowed, but that only
|
||||
a particular variable name needs to be protected and not others. Or only under very special
|
||||
circumstances.
|
||||
|
||||
Under this scenario, it is possible to provide a _filter_ closure to the [`Engine`] via
|
||||
[`Engine::on_def_var`] that traps variable definitions (i.e. [`let`][variable] or
|
||||
[`const`][constant] statements) in a Rhai script.
|
||||
|
||||
The filter is called when a [variable] or [constant] is defined both during runtime and compilation.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a variable definition filter.
|
||||
engine.on_def_var(|is_runtime, info, context| {
|
||||
match (info.name, info.is_const) {
|
||||
// Disallow defining 'MYSTIC_NUMBER' as a constant!
|
||||
("MYSTIC_NUMBER", true) => Ok(false),
|
||||
// Disallow defining constants not at global level!
|
||||
(_, true) if info.nesting_level > 0 => Ok(false),
|
||||
// Throw any exception you like...
|
||||
("hello", _) => Err(EvalAltResult::ErrorVariableNotFound(info.name.to_string(), Position::NONE).into()),
|
||||
// Return Ok(true) to continue with normal variable definition.
|
||||
_ => Ok(true)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_def_var`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(is_runtime: bool, info: VarDefInfo, context: EvalContext) -> Result<bool, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | :-------------: | ----------------------------------------------------------------------------------------------- |
|
||||
| `is_runtime` | `bool` | `true` if the [variable] definition event happens during runtime, `false` if during compilation |
|
||||
| `info` | `VarDefInfo` | information on the [variable] being defined |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
and `VarDefInfo` is a simple `struct` that contains the following fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------------- | :-----: | --------------------------------------------------------------------------------------- |
|
||||
| `name` | `&str` | [variable] name |
|
||||
| `is_const` | `bool` | `true` if the definition is a [`const`][constant]; `false` if it is a [`let`][variable] |
|
||||
| `nesting_level` | `usize` | the current nesting level; the global level is zero |
|
||||
| `will_shadow` | `bool` | will this [variable] _[shadow]_ an existing [variable] of the same name? |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<bool, Box<EvalAltResult>>` where:
|
||||
|
||||
| Value | Description |
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| `Ok(true)` | normal [variable] definition should continue |
|
||||
| `Ok(false)` | [throws][exception] a runtime or compilation error |
|
||||
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`] |
|
||||
|
||||
```admonish bug.small "Error during compilation"
|
||||
|
||||
During compilation (i.e. when `is_runtime` is `false`), `EvalAltResult::ErrorParsing` is passed
|
||||
through as the compilation error.
|
||||
|
||||
All other errors map to `ParseErrorType::ForbiddenVariable`.
|
||||
```
|
28
rhai_engine/rhaibook/engine/disable-keywords.md
Normal file
28
rhai_engine/rhaibook/engine/disable-keywords.md
Normal file
@ -0,0 +1,28 @@
|
||||
Disable Certain Keywords and/or Operators
|
||||
=========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
For certain embedded usage, it is sometimes necessary to restrict the language to a strict subset of
|
||||
Rhai to prevent usage of certain language features.
|
||||
|
||||
Rhai supports surgically disabling a [keyword] or [operator] via `Engine::disable_symbol`.
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.disable_symbol("if") // disable the 'if' keyword
|
||||
.disable_symbol("+="); // disable the '+=' operator
|
||||
|
||||
// The following all return parse errors.
|
||||
|
||||
engine.compile("let x = if true { 42 } else { 0 };")?;
|
||||
// ^ 'if' is rejected as a reserved keyword
|
||||
|
||||
engine.compile("let x = 40 + 2; x += 1;")?;
|
||||
// ^ '+=' is not recognized as an operator
|
||||
// ^ other operators are not affected
|
||||
```
|
33
rhai_engine/rhaibook/engine/disable-looping.md
Normal file
33
rhai_engine/rhaibook/engine/disable-looping.md
Normal file
@ -0,0 +1,33 @@
|
||||
Disable Looping
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
For certain scripts, especially those in embedded usage for straight calculations, or where Rhai
|
||||
script [`AST`]'s are eventually transcribed into some other instruction set, looping may be
|
||||
undesirable as it may not be supported by the application itself.
|
||||
|
||||
Rhai looping constructs include the [`while`], [`loop`], [`do`] and [`for`] statements.
|
||||
|
||||
Although it is possible to disable these keywords via
|
||||
[`Engine::disable_symbol`][disable keywords and operators], it is simpler to disable all looping
|
||||
via [`Engine::set_allow_looping`][options].
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Disable looping
|
||||
engine.set_allow_looping(false);
|
||||
|
||||
// The following all return parse errors.
|
||||
|
||||
engine.compile("while x == y { x += 1; }")?;
|
||||
|
||||
engine.compile(r#"loop { print("hello world!"); }"#)?;
|
||||
|
||||
engine.compile("do { x += 1; } until x > 10;")?;
|
||||
|
||||
engine.compile("for n in 0..10 { print(n); }")?;
|
||||
```
|
94
rhai_engine/rhaibook/engine/dsl.md
Normal file
94
rhai_engine/rhaibook/engine/dsl.md
Normal file
@ -0,0 +1,94 @@
|
||||
Use Rhai as a Domain-Specific Language (DSL)
|
||||
============================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai can be successfully used as a domain-specific language (DSL).
|
||||
|
||||
|
||||
Expressions Only
|
||||
----------------
|
||||
|
||||
In many DSL scenarios, only evaluation of expressions is needed.
|
||||
|
||||
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict a script to
|
||||
expressions only.
|
||||
|
||||
|
||||
Unicode Standard Annex #31 Identifiers
|
||||
--------------------------------------
|
||||
|
||||
[Variable] names and other identifiers do not necessarily need to be ASCII-only.
|
||||
|
||||
The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow [variable] names and
|
||||
identifiers that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
|
||||
|
||||
This is sometimes useful in a non-English DSL.
|
||||
|
||||
|
||||
Disable Keywords and/or Operators
|
||||
---------------------------------
|
||||
|
||||
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
|
||||
language features that are not necessary or dangerous to the application.
|
||||
|
||||
For example, a DSL may disable the [`while`] loop while keeping all other statement types intact.
|
||||
|
||||
It is possible, in Rhai, to surgically [disable keywords and operators].
|
||||
|
||||
|
||||
Custom Operators
|
||||
----------------
|
||||
|
||||
Some DSL scenarios require special operators that make sense only for that specific environment.
|
||||
In such cases, it is possible to define [custom operators] in Rhai.
|
||||
|
||||
```rust
|
||||
let animal = "rabbit";
|
||||
let food = "carrot";
|
||||
|
||||
animal eats food // custom operator 'eats'
|
||||
|
||||
eats(animal, food) // <- the above actually de-sugars to this
|
||||
|
||||
let x = foo # bar; // custom operator '#'
|
||||
|
||||
let x = #(foo, bar) // <- the above actually de-sugars to this
|
||||
```
|
||||
|
||||
Although a [custom operator] always de-sugars to a simple function call, nevertheless it makes the
|
||||
DSL syntax much simpler and expressive.
|
||||
|
||||
|
||||
Custom Syntax
|
||||
-------------
|
||||
|
||||
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] –
|
||||
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].
|
56
rhai_engine/rhaibook/engine/dynamic-lib.md
Normal file
56
rhai_engine/rhaibook/engine/dynamic-lib.md
Normal file
@ -0,0 +1,56 @@
|
||||
Use Rhai in Dynamic Libraries
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Sometimes functions registered into a Rhai [`Engine`] come from _dynamic libraries_ (a.k.a. _shared
|
||||
libraries_ in Linux or _DLL's_ in Windows), which are compiled separately from the main binary, not
|
||||
statically linked but loaded dynamically at runtime.
|
||||
|
||||
~~~admonish example.small "`rhai-dylib`"
|
||||
|
||||
The project [`rhai-dylib`] demonstrates an API for creating dynamically-loadable libraries
|
||||
for use with a Rhai [`Engine`].
|
||||
~~~
|
||||
|
||||
|
||||
Problem Symptom
|
||||
---------------
|
||||
|
||||
The symptom is usually _Function Not Found_ errors even though the relevant functions (within the
|
||||
dynamic library) have already been registered into the [`Engine`].
|
||||
|
||||
This usually happens when a mutable reference to the [`Engine`] is passed into an entry-point
|
||||
function exposed by the dynamic library and the [`Engine`]'s function registration API is called
|
||||
inside the dynamic library.
|
||||
|
||||
|
||||
Problem Cause
|
||||
-------------
|
||||
|
||||
To counter [DOS] attacks, the _hasher_ used by Rhai, [`ahash`], automatically generates a different
|
||||
_seed_ for hashing during each compilation and execution run.
|
||||
|
||||
This means that hash values generated by the hasher will not be _stable_ – 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.
|
||||
```
|
26
rhai_engine/rhaibook/engine/eval-context.md
Normal file
26
rhai_engine/rhaibook/engine/eval-context.md
Normal file
@ -0,0 +1,26 @@
|
||||
`EvalContext`
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Many functions in advanced APIs contain a parameter of type `EvalContext` in order to allow the
|
||||
current evaluation state to be accessed and/or modified.
|
||||
|
||||
`EvalContext` encapsulates the current _evaluation context_ and exposes the following methods.
|
||||
|
||||
| Method | Return type | Description |
|
||||
| ---------------------------- | :----------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `scope()` | [`&Scope`][`Scope`] | reference to the current [`Scope`] |
|
||||
| `scope_mut()` | [`&mut Scope`][`Scope`] | mutable reference to the current [`Scope`]; [variables] can be added to/removed from it |
|
||||
| `engine()` | [`&Engine`][`Engine`] | reference to the current [`Engine`] |
|
||||
| `source()` | `Option<&str>` | reference to the current source, if any |
|
||||
| `tag()` | [`&Dynamic`][`Dynamic`] | reference to the custom state that is persistent during the current run |
|
||||
| `tag_mut()` | [`&mut Dynamic`][`Dynamic`] | mutable reference to the custom state that is persistent during the current run |
|
||||
| `iter_imports()` | `impl Iterator<Item = (&str,`[`&Module`][`Module`]`)>` | iterator of the current stack of [modules] imported via [`import`] statements, in reverse order (i.e. later [modules] come first); not available under [`no_module`] |
|
||||
| `global_runtime_state()` | [`&GlobalRuntimeState`][`GlobalRuntimeState`] | reference to the current [global runtime state][`GlobalRuntimeState`] (including the stack of [modules] imported via [`import`] statements) |
|
||||
| `global_runtime_state_mut()` | [`&mut &mut GlobalRuntimeState`][`GlobalRuntimeState`] | mutable reference to the current [global runtime state][`GlobalRuntimeState`]; use this to access the [`debugger`][debugger] field in order to set/clear [break-points] |
|
||||
| `iter_namespaces()` | `impl Iterator<Item =`[`&Module`][`Module`]`>` | iterator of the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions], in reverse order (i.e. later [modules] come first) |
|
||||
| `namespaces()` | [`&[&Module]`][`Module`] | reference to the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions] |
|
||||
| `this_ptr()` | [`Option<&Dynamic>`][`Dynamic`] | reference to the current bound `this` pointer, if any |
|
||||
| `this_ptr_mut()` | [`&mut Option<&mut Dynamic>`][`Dynamic`] | mutable reference to the current bound `this` pointer, if any |
|
||||
| `call_level()` | `usize` | the current nesting level of [function] calls |
|
84
rhai_engine/rhaibook/engine/expressions.md
Normal file
84
rhai_engine/rhaibook/engine/expressions.md
Normal file
@ -0,0 +1,84 @@
|
||||
Evaluate Expressions Only
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "Tip: `Dynamic`"
|
||||
|
||||
Use [`Dynamic`] if you're uncertain of the return type.
|
||||
~~~
|
||||
|
||||
Very often, a use case does not require a full-blown scripting _language_, but only needs to
|
||||
evaluate _expressions_.
|
||||
|
||||
In these cases, use the `Engine::compile_expression` and `Engine::eval_expression` methods or their
|
||||
`_with_scope` variants.
|
||||
|
||||
```rust
|
||||
let result: i64 = engine.eval_expression("2 + (10 + 10) * 2")?;
|
||||
|
||||
let result: Dynamic = engine.eval_expression("get_value(42)")?;
|
||||
|
||||
// Usually this is done together with a custom scope with variables...
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push("x", 42_i64);
|
||||
scope.push_constant("SCALE", 10_i64);
|
||||
|
||||
let result: i64 = engine.eval_expression_with_scope(&mut scope,
|
||||
"(x + 1) * SCALE"
|
||||
)?;
|
||||
```
|
||||
|
||||
~~~admonish bug "No statements allowed"
|
||||
|
||||
When evaluating _expressions_, no full-blown statement (e.g. [`while`], [`for`], `fn`) –
|
||||
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::<i64>(&mut scope,
|
||||
"{ let y = calc(x); x + y }"
|
||||
)?;
|
||||
|
||||
let fp: FnPtr = engine.eval_expression("|x| x + 1")?;
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish tip "Tip: `if`-expressions and `switch`-expressions"
|
||||
|
||||
[`if` expressions]({{rootUrl}}/language/if.md#if-expression) are allowed if both statement blocks
|
||||
contain only a single expression each.
|
||||
|
||||
[`switch` expressions]({{rootUrl}}/language/switch-expression.md) are allowed if all match
|
||||
actions are expressions and not statements.
|
||||
|
||||
loop expressions are not allowed.
|
||||
|
||||
```rust
|
||||
// The following are allowed.
|
||||
|
||||
let result = engine.eval_expression_with_scope::<i64>(&mut scope,
|
||||
"if x { 42 } else { 123 }"
|
||||
)?;
|
||||
|
||||
let result = engine.eval_expression_with_scope::<i64>(&mut scope, "
|
||||
switch x {
|
||||
0 => x * 42,
|
||||
1..=9 => foo(123) + bar(1),
|
||||
10 => 0,
|
||||
}
|
||||
")?;
|
||||
```
|
||||
~~~
|
46
rhai_engine/rhaibook/engine/func.md
Normal file
46
rhai_engine/rhaibook/engine/func.md
Normal file
@ -0,0 +1,46 @@
|
||||
Create a Rust Closure from a Rhai Function
|
||||
==========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Tip"
|
||||
|
||||
Very useful as callback functions!
|
||||
```
|
||||
|
||||
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust closure.
|
||||
|
||||
Creating them is accomplished via the `Func` trait which contains `create_from_script`
|
||||
(as well as its companion method `create_from_ast`).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
|
||||
|
||||
let engine = Engine::new(); // create a new 'Engine' just for this
|
||||
|
||||
let script = "fn calc(x, y) { x + y.len < 42 }";
|
||||
|
||||
// Func takes two type parameters:
|
||||
// 1) a tuple made up of the types of the script function's parameters
|
||||
// 2) the return type of the script function
|
||||
//
|
||||
// 'func' will have type Box<dyn Fn(i64, &str) -> Result<bool, Box<EvalAltResult>>> and is callable!
|
||||
let func = Func::<(i64, &str), bool>::create_from_script(
|
||||
// ^^^^^^^^^^^ function parameter types in tuple
|
||||
|
||||
engine, // the 'Engine' is consumed into the closure
|
||||
script, // the script, notice number of parameters must match
|
||||
"calc" // the entry-point function name
|
||||
)?;
|
||||
|
||||
func(123, "hello")? == false; // call the closure
|
||||
|
||||
schedule_callback(func); // pass it as a callback to another function
|
||||
|
||||
// Although there is nothing you can't do by manually writing out the closure yourself...
|
||||
let engine = Engine::new();
|
||||
let ast = engine.compile(script)?;
|
||||
schedule_callback(Box::new(move |x: i64, y: String| {
|
||||
engine.call_fn::<bool>(&mut Scope::new(), &ast, "calc", (x, y))
|
||||
}));
|
||||
```
|
134
rhai_engine/rhaibook/engine/hello-world.md
Normal file
134
rhai_engine/rhaibook/engine/hello-world.md
Normal file
@ -0,0 +1,134 @@
|
||||
Your First Script in Rhai
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Run a Script
|
||||
------------
|
||||
|
||||
To get going with Rhai is as simple as creating an instance of the scripting engine `rhai::Engine`
|
||||
via `Engine::new`, then calling `Engine::run`.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
pub fn main() -> Result<(), Box<EvalAltResult>>
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
// Rhai API error type
|
||||
{
|
||||
// Create an 'Engine'
|
||||
let engine = Engine::new();
|
||||
|
||||
// Your first Rhai Script
|
||||
let script = "print(40 + 2);";
|
||||
|
||||
// Run the script - prints "42"
|
||||
engine.run(script)?;
|
||||
|
||||
// Done!
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Get a Return Value
|
||||
------------------
|
||||
|
||||
To return a value from the script, use `Engine::eval` instead.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
pub fn main() -> Result<(), Box<EvalAltResult>>
|
||||
{
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
// ^^^^^^^ required: cast the result to a type
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Use Script Files
|
||||
----------------
|
||||
|
||||
```admonish info.side "Script file extension"
|
||||
|
||||
Rhai script files are customarily named with extension `.rhai`.
|
||||
```
|
||||
|
||||
Or evaluate a script file directly with `Engine::run_file` or `Engine::eval_file`.
|
||||
|
||||
Loading and running script files is not available for [`no_std`] or [WASM] builds.
|
||||
|
||||
```rust
|
||||
let result = engine.eval_file::<i64>("hello_world.rhai".into())?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// a 'PathBuf' is needed
|
||||
|
||||
// Running a script file also works in a similar manner
|
||||
engine.run_file("hello_world.rhai".into())?;
|
||||
```
|
||||
|
||||
```admonish tip "Tip: Unix shebangs"
|
||||
|
||||
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
|
||||
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
|
||||
|
||||
If a script file starts with `#!`, the entire first line is skipped by `Engine::compile_file` and
|
||||
`Engine::eval_file`. Because of this, Rhai scripts with shebangs at the beginning need no special processing.
|
||||
|
||||
This behavior is also present for non-Unix (e.g. Windows) environments so scripts are portable.
|
||||
|
||||
~~~js
|
||||
#!/home/to/me/bin/rhai-run
|
||||
|
||||
// This is a Rhai script
|
||||
|
||||
let answer = 42;
|
||||
print(`The answer is: ${answer}`);
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Specify the Return Type
|
||||
-----------------------
|
||||
|
||||
~~~admonish tip.side "Tip: `Dynamic`"
|
||||
|
||||
Use [`Dynamic`] if you're uncertain of the return type.
|
||||
~~~
|
||||
|
||||
The type parameter for `Engine::eval` is used to specify the type of the return value, which _must_
|
||||
match the actual type or an error is returned. Rhai is very strict here.
|
||||
|
||||
There are two ways to specify the return type: _turbofish_ notation, or type inference.
|
||||
|
||||
### Turbofish
|
||||
|
||||
```rust
|
||||
let result = engine.eval::<i64>("40 + 2")?; // return type is i64
|
||||
|
||||
result.is::<i64>() == true;
|
||||
|
||||
let result = engine.eval::<Dynamic>("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
|
||||
|
||||
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||
```
|
||||
|
||||
### Type inference
|
||||
|
||||
```rust
|
||||
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
||||
|
||||
result.is::<i64>() == true;
|
||||
|
||||
let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
|
||||
|
||||
let result: String = engine.eval("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||
```
|
8
rhai_engine/rhaibook/engine/index.md
Normal file
8
rhai_engine/rhaibook/engine/index.md
Normal file
@ -0,0 +1,8 @@
|
||||
Using the Engine
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.
|
||||
|
||||
This section shows how to set up, configure and use this scripting engine.
|
184
rhai_engine/rhaibook/engine/metadata/definitions.md
Normal file
184
rhai_engine/rhaibook/engine/metadata/definitions.md
Normal file
@ -0,0 +1,184 @@
|
||||
Generate Definition Files for Language Server
|
||||
=============================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai's [language server][lsp] works with IDEs to provide integrated support for the Rhai scripting language.
|
||||
|
||||
Functions and [modules] registered with an [`Engine`] can output their [metadata][functions metadata]
|
||||
into _definition files_ which are used by the [language server][lsp].
|
||||
|
||||
Definitions are generated via the `Engine::definitions` and `Engine::definitions_with_scope` API.
|
||||
|
||||
This API requires the [`metadata`] and [`internals`] feature.
|
||||
|
||||
|
||||
Configurable Options
|
||||
--------------------
|
||||
|
||||
The `Definitions` type supports the following options in a fluent method-chaining style.
|
||||
|
||||
| Option | Method | Default |
|
||||
| ------------------------------------------------------------------- | --------------------------- | :-----: |
|
||||
| Write headers in definition files? | `with_headers` | `false` |
|
||||
| Include [standard packages][built-in packages] in definition files? | `include_standard_packages` | `true` |
|
||||
|
||||
```rust
|
||||
engine
|
||||
.definitions()
|
||||
.with_headers(true) // write headers in all files
|
||||
.include_standard_packages(false) // skip standard packages
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
use rhai::plugin::*;
|
||||
|
||||
// Plugin module: 'general_kenobi'
|
||||
#[export_module]
|
||||
pub mod general_kenobi {
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
pub fn hello_there(n: i64) -> String {
|
||||
"hello there ".repeat(n.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// Create scripting engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Create custom Scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// This variable will also show up in the generated definition file.
|
||||
scope.push("hello_there", "hello there");
|
||||
|
||||
// Static module namespaces will generate independent definition files.
|
||||
engine.register_static_module(
|
||||
"general_kenobi",
|
||||
exported_module!(general_kenobi).into()
|
||||
);
|
||||
|
||||
// Custom operators will also show up in the generated definition file.
|
||||
engine.register_custom_operator("minus", 100).unwrap();
|
||||
engine.register_fn("minus", |a: i64, b: i64| a - b);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"hello_there = general_kenobi::hello_there(4 minus 2);"
|
||||
)?;
|
||||
|
||||
// Output definition files in the specified directory.
|
||||
engine
|
||||
.definitions()
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
|
||||
// Output definition files in the specified directory.
|
||||
// Variables in the provided 'Scope' are included.
|
||||
engine
|
||||
.definitions_with_scope(&scope)
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
|
||||
// Output a single definition file with everything merged.
|
||||
// Variables in the provided 'Scope' are included.
|
||||
engine
|
||||
.definitions_with_scope(&scope)
|
||||
.write_to_file("path/to/my/definitions/all_in_one.d.rhai")
|
||||
.unwrap();
|
||||
|
||||
// Output functions metadata to a JSON string.
|
||||
// Functions in standard packages are skipped and not included.
|
||||
let json = engine
|
||||
.definitions()
|
||||
.include_standard_packages(false) // skip standard packages
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
|
||||
Definition Files
|
||||
----------------
|
||||
|
||||
The generated definition files will look like the following.
|
||||
|
||||
```rust
|
||||
┌───────────────────────┐
|
||||
│ general_kenobi.d.rhai │
|
||||
└───────────────────────┘
|
||||
|
||||
module general_kenobi;
|
||||
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
fn hello_there(n: int) -> String;
|
||||
|
||||
|
||||
┌──────────────────┐
|
||||
│ __scope__.d.rhai │
|
||||
└──────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
let hello_there;
|
||||
|
||||
|
||||
┌───────────────────┐
|
||||
│ __static__.d.rhai │
|
||||
└───────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
op minus(int, int) -> int;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
|
||||
┌────────────────────┐
|
||||
│ __builtin__.d.rhai │
|
||||
└────────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ __builtin-operators__.d.rhai │
|
||||
└──────────────────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
```
|
||||
|
||||
|
||||
All-in-One Definition File
|
||||
--------------------------
|
||||
|
||||
`Definitions::write_to_file` generates a single definition file with everything merged in, like the following.
|
||||
|
||||
```rust
|
||||
module static;
|
||||
|
||||
op minus(int, int) -> int;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
module general_kenobi {
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
fn hello_there(n: int) -> String;
|
||||
}
|
||||
|
||||
let hello_there;
|
||||
```
|
147
rhai_engine/rhaibook/engine/metadata/export_to_json.md
Normal file
147
rhai_engine/rhaibook/engine/metadata/export_to_json.md
Normal file
@ -0,0 +1,147 @@
|
||||
Export Functions Metadata to JSON
|
||||
=================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Engine::gen_fn_metadata_to_json`<br/>`Engine::gen_fn_metadata_with_ast_to_json`
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` and the corresponding
|
||||
`Engine::gen_fn_metadata_with_ast_to_json` export the full list of [custom types] and
|
||||
[functions metadata] in JSON format.
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
The [`metadata`] feature is required for this API, which also pulls in the
|
||||
[`serde_json`](https://crates.io/crates/serde_json) crate.
|
||||
~~~
|
||||
|
||||
### Sources
|
||||
|
||||
Functions and [custom types] from the following sources are included:
|
||||
|
||||
1. Script-defined functions in an [`AST`] (for `Engine::gen_fn_metadata_with_ast_to_json`)
|
||||
2. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||
3. [Custom types] registered into the global namespace via the `Engine::register_type_with_name` API
|
||||
4. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) and [custom types] in static modules
|
||||
registered via `Engine::register_static_module`
|
||||
5. Native Rust functions and [custom types] in external [packages] registered via `Engine::register_global_module`
|
||||
6. Native Rust functions and [custom types] in [built-in packages] (optional)
|
||||
|
||||
|
||||
JSON Schema
|
||||
-----------
|
||||
|
||||
The JSON schema used to hold metadata is very simple, containing a nested structure of
|
||||
`modules`, a list of `customTypes` and a list of `functions`.
|
||||
|
||||
### Module Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"doc": "//! Module documentation",
|
||||
|
||||
"modules":
|
||||
{
|
||||
"sub_module_1": /* namespace 'sub_module_1' */
|
||||
{
|
||||
"modules":
|
||||
{
|
||||
"sub_sub_module_A": /* namespace 'sub_module_1::sub_sub_module_A' */
|
||||
{
|
||||
"doc": "//! Module documentation can also occur in any sub-module",
|
||||
|
||||
"customTypes": /* custom types exported in 'sub_module_1::sub_sub_module_A' */
|
||||
[
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... }
|
||||
...
|
||||
],
|
||||
"functions": /* functions exported in 'sub_module_1::sub_sub_module_A' */
|
||||
[
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... }
|
||||
...
|
||||
]
|
||||
},
|
||||
"sub_sub_module_B": /* namespace 'sub_module_1::sub_sub_module_B' */
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
},
|
||||
"sub_module_2": /* namespace 'sub_module_2' */
|
||||
{
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
|
||||
"customTypes": /* custom types registered globally */
|
||||
[
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
...
|
||||
],
|
||||
|
||||
"functions": /* functions registered globally or in the 'AST' */
|
||||
[
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Type Metadata Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"typeName": "alloc::string::String", /* name of Rust type */
|
||||
"displayName": "MyType",
|
||||
"docComments": /* omitted if none */
|
||||
[
|
||||
"/// My super-string type.",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Function Metadata Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"baseHash": 9876543210, /* partial hash with only number of parameters */
|
||||
"fullHash": 1234567890, /* full hash with actual parameter types */
|
||||
"namespace": "internal" | "global",
|
||||
"access": "public" | "private",
|
||||
"name": "fn_name",
|
||||
"isAnonymous": false,
|
||||
"type": "native" | "script",
|
||||
"numParams": 42, /* number of parameters */
|
||||
"params": /* omitted if no parameters */
|
||||
[
|
||||
{ "name": "param_1", "type": "type_1" },
|
||||
{ "name": "param_2" }, /* no type name */
|
||||
{ "type": "type_3" }, /* no parameter name */
|
||||
...
|
||||
],
|
||||
"thisType": "this_type", /* omitted if none */
|
||||
"returnType": "ret_type", /* omitted if () or unknown */
|
||||
"signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type",
|
||||
"docComments": /* omitted if none */
|
||||
[
|
||||
"/// doc-comment line 1",
|
||||
"/// doc-comment line 2",
|
||||
"/** doc-comment block */",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
93
rhai_engine/rhaibook/engine/metadata/gen_fn_sig.md
Normal file
93
rhai_engine/rhaibook/engine/metadata/gen_fn_sig.md
Normal file
@ -0,0 +1,93 @@
|
||||
Get Native Function Signatures
|
||||
==============================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Engine::gen_fn_signatures`
|
||||
---------------------------
|
||||
|
||||
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_
|
||||
(as `Vec<String>`), each corresponding to a particular native function available to that [`Engine`] instance.
|
||||
|
||||
> _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
|
||||
|
||||
The [`metadata`] feature must be used to turn on this API.
|
||||
|
||||
### Sources
|
||||
|
||||
Functions from the following sources are included, in order:
|
||||
|
||||
1. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||
2. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules
|
||||
registered via `Engine::register_static_module`.
|
||||
3. Native Rust functions in external [packages] registered via `Engine::register_global_module`
|
||||
4. Native Rust functions in [built-in packages] (optional)
|
||||
|
||||
|
||||
Functions Metadata
|
||||
------------------
|
||||
|
||||
Beware, however, that not all function signatures contain parameters and return value information.
|
||||
|
||||
### `Engine::register_XXX`
|
||||
|
||||
For instance, functions registered via `Engine::register_XXX` contain no information on the names of
|
||||
parameter because Rust simply does not make such metadata available natively.
|
||||
|
||||
Type names, however, _are_ provided.
|
||||
|
||||
A function registered under the name `foo` with three parameters.
|
||||
|
||||
> `foo(_: i64, _: char, _: &str) -> String`
|
||||
|
||||
An [operator] function. Notice that function names do not need to be valid identifiers.
|
||||
|
||||
> `+=(_: &mut i64, _: i64)`
|
||||
|
||||
A [property setter][getters/setters].
|
||||
Notice that function names do not need to be valid identifiers.
|
||||
In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`:
|
||||
|
||||
> `set$prop(_: &mut TestStruct, _: i64)`
|
||||
|
||||
### Script-Defined Functions
|
||||
|
||||
Script-defined [function] signatures contain parameter names.
|
||||
Since _all_ parameters, as well as the return value, are [`Dynamic`] the types are simply not shown.
|
||||
|
||||
> `foo(x, y, z)`
|
||||
|
||||
is probably defined simply as:
|
||||
|
||||
```rust
|
||||
/// This is a doc-comment, included in this function's metadata.
|
||||
fn foo(x, y, z) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
which is really the same as:
|
||||
|
||||
> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result<Dynamic, Box<EvalAltResult>>`
|
||||
|
||||
### Plugin Functions
|
||||
|
||||
Functions defined in [plugin modules] are the best.
|
||||
They contain all metadata describing the functions, including [doc-comments].
|
||||
|
||||
For example, a plugin function `combine`:
|
||||
|
||||
> `/// This is a doc-comment, included in this function's metadata.`
|
||||
> `combine(list: &mut MyStruct<i64>, num: usize, name: &str) -> bool`
|
||||
|
||||
Notice that function names do not need to be valid identifiers.
|
||||
|
||||
For example, an [operator] defined as a [fallible function] in a [plugin module] via
|
||||
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
|
||||
|
||||
> `+=(list: &mut MyStruct<i64>, value: &str) -> Result<bool, Box<EvalAltResult>>`
|
||||
|
||||
For example, a [property getter][getters/setters] defined in a [plugin module]:
|
||||
|
||||
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
49
rhai_engine/rhaibook/engine/metadata/index.md
Normal file
49
rhai_engine/rhaibook/engine/metadata/index.md
Normal file
@ -0,0 +1,49 @@
|
||||
Functions and Custom Types Metadata
|
||||
===================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
Exporting metadata requires the [`metadata`] feature.
|
||||
~~~
|
||||
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
The _metadata_ of a [function] means all relevant information related to a function's
|
||||
definition including:
|
||||
|
||||
1. Its callable name
|
||||
|
||||
2. Its access mode (public or [private][`private`])
|
||||
|
||||
3. Its parameter names and types (if any)
|
||||
|
||||
4. Its return value and type (if any)
|
||||
|
||||
5. Its nature (i.e. native Rust or Rhai-scripted)
|
||||
|
||||
6. Its [namespace][function namespace] ([module] or global)
|
||||
|
||||
7. Its purpose, in the form of [doc-comments]
|
||||
|
||||
8. Usage notes, warnings, examples etc., in the form of [doc-comments]
|
||||
|
||||
A function's _signature_ encapsulates the first four pieces of information in a single concise line
|
||||
of definition:
|
||||
|
||||
> `[private]` _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
|
||||
|
||||
|
||||
Custom Types
|
||||
------------
|
||||
|
||||
The _metadata_ of a [custom type] include:
|
||||
|
||||
1. Its full Rust type name
|
||||
|
||||
2. Its pretty-print _display name_ (which can be the same as its Rust type name)
|
||||
|
||||
3. Its purpose, in the form of [doc-comments]
|
212
rhai_engine/rhaibook/engine/optimize/constants.md
Normal file
212
rhai_engine/rhaibook/engine/optimize/constants.md
Normal file
@ -0,0 +1,212 @@
|
||||
Constants Propagation
|
||||
=====================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish tip.side
|
||||
|
||||
Effective in template-based machine-generated scripts to turn on/off certain sections.
|
||||
```
|
||||
|
||||
[Constants] propagation is commonly used to:
|
||||
|
||||
* remove dead code,
|
||||
|
||||
* avoid [variable] lookups,
|
||||
|
||||
* [pre-calculate](op-eval.md) [constant] expressions.
|
||||
|
||||
```rust
|
||||
const ABC = true;
|
||||
const X = 41;
|
||||
|
||||
if ABC || calc(X+1) { print("done!"); } // 'ABC' is constant so replaced by 'true'...
|
||||
// 'X' is constant so replaced by 41...
|
||||
|
||||
if true || calc(42) { print("done!"); } // '41+1' is replaced by 42
|
||||
// since '||' short-circuits, 'calc' is never called
|
||||
|
||||
if true { print("done!"); } // <- the line above is equivalent to this
|
||||
|
||||
print("done!"); // <- the line above is further simplified to this
|
||||
// because the condition is always true
|
||||
```
|
||||
|
||||
~~~admonish tip "Tip: Custom `Scope` constants"
|
||||
|
||||
[Constant] values can be provided in a custom [`Scope`] object to the [`Engine`]
|
||||
for optimization purposes.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("ABC", true);
|
||||
|
||||
// Evaluate script with custom scope
|
||||
engine.run_with_scope(&mut scope,
|
||||
r#"
|
||||
if ABC { // 'ABC' is replaced by 'true'
|
||||
print("done!");
|
||||
}
|
||||
"#)?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish tip "Tip: Customer module constants"
|
||||
|
||||
[Constants] defined in [modules] that are registered into an [`Engine`] via
|
||||
`Engine::register_global_module` are used in optimization.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let mut module = Module::new();
|
||||
|
||||
// Add constant to module
|
||||
module.set_var("ABC", true);
|
||||
|
||||
// Register global module
|
||||
engine.register_global_module(module.into());
|
||||
|
||||
// Evaluate script
|
||||
engine.run(
|
||||
r#"
|
||||
if ABC { // 'ABC' is replaced by 'true'
|
||||
print("done!");
|
||||
}
|
||||
"#)?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Constants in custom scope and modules are also propagated into functions"
|
||||
|
||||
[Constants] defined at _global_ level typically cannot be seen by script [functions] because they are _pure_.
|
||||
|
||||
```rust
|
||||
const MY_CONSTANT = 42; // <- constant defined at global level
|
||||
|
||||
print(MY_CONSTANT); // <- optimized to: print(42)
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // <- not optimized: 'foo' cannot see 'MY_CONSTANT'
|
||||
}
|
||||
|
||||
print(foo()); // error: 'MY_CONSTANT' not found
|
||||
```
|
||||
|
||||
When [constants] are provided in a custom [`Scope`] (e.g. via `Engine::compile_with_scope`,
|
||||
`Engine::eval_with_scope` or `Engine::run_with_scope`), or in a [module] registered via
|
||||
`Engine::register_global_module`, instead of defined within the same script, they are also
|
||||
propagated to [functions].
|
||||
|
||||
This is usually the intuitive usage and behavior expected by regular users, even though it means
|
||||
that a script will behave differently (essentially a runtime error) when [script optimization] is disabled.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("MY_CONSTANT", 42_i64);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
print(MY_CONSTANT); // optimized to: print(42)
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // optimized to: fn foo() { 42 }
|
||||
}
|
||||
|
||||
print(foo()); // prints 42
|
||||
")?;
|
||||
```
|
||||
|
||||
The script will act differently when [script optimization] is disabled because script [functions]
|
||||
are _pure_ and typically cannot see [constants] within the custom [`Scope`].
|
||||
|
||||
Therefore, constants in [functions] now throw a runtime error.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, OptimizationLevel};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Turn off script optimization, no constants propagation is performed
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("MY_CONSTANT", 42_i64);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
print(MY_CONSTANT); // prints 42
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // <- 'foo' cannot see 'MY_CONSTANT'
|
||||
}
|
||||
|
||||
print(foo()); // error: 'MY_CONSTANT' not found
|
||||
")?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Beware of large constants"
|
||||
|
||||
[Constants] propagation replaces each usage of the [constant] with a clone of its value.
|
||||
|
||||
This may have negative implications to performance if the [constant] value is expensive to clone
|
||||
(e.g. if the type is very large).
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Push a large constant into the scope...
|
||||
let big_type = AVeryLargeType::take_long_time_to_create();
|
||||
scope.push_constant("MY_BIG_TYPE", big_type);
|
||||
|
||||
// Causes each usage of 'MY_BIG_TYPE' in the script below to be replaced
|
||||
// by cloned copies of 'AVeryLargeType'.
|
||||
let result = engine.run_with_scope(&mut scope,
|
||||
"
|
||||
let value = MY_BIG_TYPE.value;
|
||||
let data = MY_BIG_TYPE.data;
|
||||
let len = MY_BIG_TYPE.len();
|
||||
let has_options = MY_BIG_TYPE.has_options();
|
||||
let num_options = MY_BIG_TYPE.options_len();
|
||||
")?;
|
||||
```
|
||||
|
||||
To avoid this, compile the script first to an [`AST`] _without_ the [constants], then evaluate the
|
||||
[`AST`] (e.g. with `Engine::eval_ast_with_scope` or `Engine::run_ast_with_scope`) together with
|
||||
the [constants].
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Constants may be modified by Rust methods"
|
||||
|
||||
If the [constants] are modified later on (yes, it is possible, via Rust _methods_),
|
||||
the modified values will not show up in the optimized script.
|
||||
Only the initialization values of [constants] are ever retained.
|
||||
|
||||
```rust
|
||||
const MY_SECRET_ANSWER = 42;
|
||||
|
||||
MY_SECRET_ANSWER.update_to(666); // assume 'update_to(&mut i64)' is a Rust function
|
||||
|
||||
print(MY_SECRET_ANSWER); // prints 42 because the constant is propagated
|
||||
```
|
||||
|
||||
This is almost never a problem because real-world scripts seldom modify a [constant],
|
||||
but the possibility is always there.
|
||||
~~~
|
51
rhai_engine/rhaibook/engine/optimize/dead-code.md
Normal file
51
rhai_engine/rhaibook/engine/optimize/dead-code.md
Normal file
@ -0,0 +1,51 @@
|
||||
Dead Code Elimination
|
||||
=====================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish question.side.wide "But who writes dead code?"
|
||||
|
||||
Nobody deliberately writes scripts with dead code (we hope).
|
||||
|
||||
They are, however, extremely common in template-based machine-generated scripts.
|
||||
```
|
||||
|
||||
Rhai attempts to eliminate _dead code_.
|
||||
|
||||
"Dead code" is code that does nothing and has no side effects.
|
||||
|
||||
Example is an pure expression by itself as a statement (allowed in Rhai).
|
||||
The result of the expression is calculated then immediately discarded and not used.
|
||||
|
||||
```rust
|
||||
{
|
||||
let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval')
|
||||
|
||||
123; // eliminated: no effect
|
||||
|
||||
"hello"; // eliminated: no effect
|
||||
|
||||
[1, 2, x, 4]; // eliminated: no effect
|
||||
|
||||
if 42 > 0 { // '42 > 0' is replaced by 'true' and the first branch promoted
|
||||
foo(42); // promoted, NOT eliminated: the function 'foo' may have side-effects
|
||||
} else {
|
||||
bar(x); // eliminated: branch is never reached
|
||||
}
|
||||
|
||||
let z = x; // eliminated: local variable, no side-effects, and only pure afterwards
|
||||
|
||||
666 // NOT eliminated: this is the return value of the block,
|
||||
// and the block is the last one so this is the return value of the whole script
|
||||
}
|
||||
```
|
||||
|
||||
The above script optimizes to:
|
||||
|
||||
```rust
|
||||
{
|
||||
let x = 999;
|
||||
foo(42);
|
||||
666
|
||||
}
|
||||
```
|
25
rhai_engine/rhaibook/engine/optimize/disable.md
Normal file
25
rhai_engine/rhaibook/engine/optimize/disable.md
Normal file
@ -0,0 +1,25 @@
|
||||
Turn Off Script Optimizations
|
||||
=============================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When scripts:
|
||||
|
||||
* are known to be run only _once_ and then thrown away,
|
||||
|
||||
* are known to contain no dead code,
|
||||
|
||||
* do not use constants in calculations
|
||||
|
||||
the optimization pass may be a waste of time and resources.
|
||||
|
||||
In that case, turn optimization off by setting the optimization level to [`OptimizationLevel::None`].
|
||||
|
||||
```rust
|
||||
let engine = rhai::Engine::new();
|
||||
|
||||
// Turn off the optimizer
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
```
|
||||
|
||||
Alternatively, disable optimizations via the [`no_optimize`] feature.
|
75
rhai_engine/rhaibook/engine/optimize/eager.md
Normal file
75
rhai_engine/rhaibook/engine/optimize/eager.md
Normal file
@ -0,0 +1,75 @@
|
||||
Eager Function Evaluation When Using Full Optimization Level
|
||||
============================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to
|
||||
be _pure_ and will _eagerly_ evaluated all function calls with constant arguments, using the result
|
||||
to replace the call.
|
||||
|
||||
This also applies to all operators (which are implemented as functions).
|
||||
|
||||
```rust
|
||||
// When compiling the following with OptimizationLevel::Full...
|
||||
|
||||
const DECISION = 1;
|
||||
// this condition is now eliminated because 'sign(DECISION) > 0'
|
||||
if sign(DECISION) > 0 // <- is a call to the 'sign' and '>' functions, and they return 'true'
|
||||
{
|
||||
print("hello!"); // <- this block is promoted to the parent level
|
||||
} else {
|
||||
print("boo!"); // <- this block is eliminated because it is never reached
|
||||
}
|
||||
|
||||
print("hello!"); // <- the above is equivalent to this
|
||||
// ('print' and 'debug' are handled specially)
|
||||
```
|
||||
|
||||
~~~admonish danger "Won't this be dangerous?"
|
||||
|
||||
Yes! _Very!_
|
||||
|
||||
```rust
|
||||
// Nuclear silo control
|
||||
if launch_nukes && president_okeyed {
|
||||
print("This is NOT a drill!");
|
||||
update_defcon(1);
|
||||
start_world_war(3);
|
||||
launch_all_nukes();
|
||||
} else {
|
||||
print("This is a drill. Thank you for your cooperation.");
|
||||
}
|
||||
```
|
||||
|
||||
In the script above (well... as if nuclear silos will one day be controlled by Rhai scripts),
|
||||
the functions `update_defcon`, `start_world_war` and `launch_all_nukes` will be evaluated
|
||||
during _compilation_ because they have constant arguments.
|
||||
|
||||
The [variables] `launch_nukes` and `president_okeyed` are never checked, because the script
|
||||
actually has not yet been run! The functions are called during compilation.
|
||||
This is, _obviously_, not what you want.
|
||||
|
||||
**Moral of the story: compile with an [`Engine`] that does not have any functions registered.
|
||||
Register functions _AFTER_ compilation.**
|
||||
~~~
|
||||
|
||||
~~~admonish question "Why would I ever want to do this then?"
|
||||
|
||||
Good question! There are two reasons:
|
||||
|
||||
* A function call may result in cleaner code than the resultant value.
|
||||
In Rust, this would have been handled via a `const` function.
|
||||
|
||||
* Evaluating a value to a [custom type] that has no representation in script.
|
||||
|
||||
```rust
|
||||
// A complex function that returns a unique ID based on the arguments
|
||||
let id = make_unique_id(123, "hello", true);
|
||||
|
||||
// The above is arguably clearer than:
|
||||
// let id = 835781293546; // generated from 123, "hello" and true
|
||||
|
||||
// A custom type that cannot be represented in script
|
||||
let complex_obj = make_complex_obj(42);
|
||||
```
|
||||
~~~
|
62
rhai_engine/rhaibook/engine/optimize/index.md
Normal file
62
rhai_engine/rhaibook/engine/optimize/index.md
Normal file
@ -0,0 +1,62 @@
|
||||
Script Optimization
|
||||
===================
|
||||
|
||||
{{#title Script Optimization}}
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
|
||||
This can reduce resource utilization and increase execution speed.
|
||||
|
||||
Script optimization can be turned off via the [`no_optimize`] feature.
|
||||
|
||||
|
||||
Optimization Levels
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
There are three levels of optimization: `None`, `Simple` and `Full`.
|
||||
The default is `Simple`.
|
||||
|
||||
An [`Engine`]'s optimization level is set via [`Engine::set_optimization_level`][options].
|
||||
|
||||
```rust
|
||||
// Turn on aggressive optimizations
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::Full);
|
||||
```
|
||||
|
||||
`None`
|
||||
------
|
||||
|
||||
`None` is obvious – 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.
|
85
rhai_engine/rhaibook/engine/optimize/op-eval.md
Normal file
85
rhai_engine/rhaibook/engine/optimize/op-eval.md
Normal file
@ -0,0 +1,85 @@
|
||||
Eager Operator Evaluation
|
||||
=========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Most operators are actually function calls, and those functions can be overridden, so whether they
|
||||
are optimized away depends on the situation:
|
||||
|
||||
```admonish info.side.wide "No external functions"
|
||||
|
||||
Rhai guarantees that no external function will be run, which may trigger side-effects
|
||||
(unless the optimization level is [`OptimizationLevel::Full`]).
|
||||
```
|
||||
|
||||
* if the operands are not [constant] values, it is **not** optimized;
|
||||
|
||||
* if the [operator] is [overloaded][operator overloading], it is **not** optimized because the
|
||||
overloading function may not be _pure_ (i.e. may cause side-effects when called);
|
||||
|
||||
* if the [operator] is not _built-in_ (see list of [built-in operators]), it is **not** optimized;
|
||||
|
||||
* if the [operator] is a [built-in operator] for a [standard type][standard types], it is called and
|
||||
replaced by a [constant] result.
|
||||
|
||||
```js
|
||||
// The following is most likely generated by machine.
|
||||
|
||||
const DECISION = 1; // this is an integer, one of the standard types
|
||||
|
||||
if DECISION == 1 { // this is optimized into 'true'
|
||||
:
|
||||
} else if DECISION == 2 { // this is optimized into 'false'
|
||||
:
|
||||
} else if DECISION == 3 { // this is optimized into 'false'
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
|
||||
// Or an equivalent using 'switch':
|
||||
|
||||
switch DECISION {
|
||||
1 => ..., // this statement is promoted
|
||||
2 => ..., // this statement is eliminated
|
||||
3 => ..., // this statement is eliminated
|
||||
_ => ... // this statement is eliminated
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Pre-Evaluation of Constant Expressions
|
||||
--------------------------------------
|
||||
|
||||
Because of the eager evaluation of [operators][built-in operators] for [standard types], many
|
||||
[constant] expressions will be evaluated and replaced by the result.
|
||||
|
||||
```rust
|
||||
let x = (1+2) * 3 - 4/5 % 6; // will be replaced by 'let x = 9'
|
||||
|
||||
let y = (1 > 2) || (3 <= 4); // will be replaced by 'let y = true'
|
||||
```
|
||||
|
||||
For operators that are not optimized away due to one of the above reasons, the function calls are
|
||||
simply left behind.
|
||||
|
||||
```rust
|
||||
// Assume 'new_state' returns some custom type that is NOT one of the standard types.
|
||||
// Also assume that the '==' operator is defined for that custom type.
|
||||
const DECISION_1 = new_state(1);
|
||||
const DECISION_2 = new_state(2);
|
||||
const DECISION_3 = new_state(3);
|
||||
|
||||
if DECISION == 1 { // NOT optimized away because the operator is not built-in
|
||||
: // and may cause side-effects if called!
|
||||
:
|
||||
} else if DECISION == 2 { // same here, NOT optimized away
|
||||
:
|
||||
} else if DECISION == 3 { // same here, NOT optimized away
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
|
21
rhai_engine/rhaibook/engine/optimize/passes.md
Normal file
21
rhai_engine/rhaibook/engine/optimize/passes.md
Normal file
@ -0,0 +1,21 @@
|
||||
Optimization Passes
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
[Script optimization] is performed via multiple _passes_.
|
||||
Each pass does a specific optimization.
|
||||
|
||||
The optimization is completed when no passes can simplify the [`AST`] any further.
|
||||
|
||||
|
||||
Built-in Optimization Passes
|
||||
----------------------------
|
||||
|
||||
| Pass | Description |
|
||||
| ------------------------------------------ | ------------------------------------------------- |
|
||||
| [Dead code elimination](dead-code.md) | Eliminates code that cannot be reached |
|
||||
| [Constants propagation](constants.md) | Replaces [constants] with values |
|
||||
| [Compound assignments rewrite](rewrite.md) | Rewrites assignments into compound assignments |
|
||||
| [Eager operator evaluation](op-eval.md) | Eagerly calls operators with [constant] arguments |
|
||||
| [Eager function evaluation](eager.md) | Eagerly calls functions with [constant] arguments |
|
50
rhai_engine/rhaibook/engine/optimize/reoptimize.md
Normal file
50
rhai_engine/rhaibook/engine/optimize/reoptimize.md
Normal file
@ -0,0 +1,50 @@
|
||||
Re-Optimize an AST
|
||||
==================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by
|
||||
constant variables. This script is compiled once to an [`AST`].
|
||||
|
||||
Then, depending on the execution environment, constants are passed into the [`Engine`] and the
|
||||
[`AST`] is _re_-optimized based on those constants via `Engine::optimize_ast`, effectively pruning
|
||||
out unused code sections.
|
||||
|
||||
The final, optimized [`AST`] is then used for evaluations.
|
||||
|
||||
```rust
|
||||
// Compile master script to AST
|
||||
let master_ast = engine.compile(
|
||||
"
|
||||
fn do_work() {
|
||||
// Constants in scope are also propagated into functions
|
||||
print(SCENARIO);
|
||||
}
|
||||
|
||||
switch SCENARIO {
|
||||
1 => do_work(),
|
||||
2 => do_something(),
|
||||
3 => do_something_else(),
|
||||
_ => do_nothing()
|
||||
}
|
||||
")?;
|
||||
|
||||
for n in 0..5_i64 {
|
||||
// Create a new scope - put constants in it to aid optimization
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("SCENARIO", n);
|
||||
|
||||
// Re-optimize the AST
|
||||
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
|
||||
|
||||
// Run it
|
||||
engine.run_ast(&new_ast)?;
|
||||
}
|
||||
```
|
||||
|
||||
```admonish note.small "Constants propagation"
|
||||
|
||||
Beware that [constants] inside the custom [`Scope`] will also be propagated to [functions] defined
|
||||
within the script while normally such [functions] are _pure_ and cannot see [variables]/[constants]
|
||||
within the global [`Scope`].
|
||||
```
|
47
rhai_engine/rhaibook/engine/optimize/rewrite.md
Normal file
47
rhai_engine/rhaibook/engine/optimize/rewrite.md
Normal file
@ -0,0 +1,47 @@
|
||||
Compound Assignment Rewrite
|
||||
===========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "Avoid cloning"
|
||||
|
||||
Arguments passed as value are always cloned.
|
||||
```
|
||||
|
||||
Usually, a _compound assignment_ (e.g. `+=` for append) takes a mutable first parameter
|
||||
(i.e. `&mut`) while the corresponding simple [operator] (i.e. `+`) does not.
|
||||
|
||||
The script optimizer rewrites normal assignments into _compound assignments_ wherever possible in
|
||||
order to avoid unnecessary cloning.
|
||||
|
||||
```rust
|
||||
let big = create_some_very_big_type();
|
||||
|
||||
big = big + 1;
|
||||
// ^ 'big' is cloned here
|
||||
|
||||
// The above is equivalent to:
|
||||
let temp_value = big + 1;
|
||||
big = temp_value;
|
||||
|
||||
big += 1; // <- 'big' is NOT cloned
|
||||
```
|
||||
|
||||
~~~admonish warning.small "Warning: Simple references only"
|
||||
|
||||
Only _simple variable references_ are optimized.
|
||||
|
||||
No [_common sub-expression elimination_](https://en.wikipedia.org/wiki/Common_subexpression_elimination)
|
||||
is performed by Rhai.
|
||||
|
||||
```rust
|
||||
x = x + 1; // <- this statement...
|
||||
|
||||
x += 1; // <- ... is rewritten to this
|
||||
|
||||
x[y] = x[y] + 1; // <- but this is not,
|
||||
// so MUCH slower...
|
||||
|
||||
x[y] += 1; // <- ... than this
|
||||
```
|
||||
~~~
|
85
rhai_engine/rhaibook/engine/optimize/semantics.md
Normal file
85
rhai_engine/rhaibook/engine/optimize/semantics.md
Normal file
@ -0,0 +1,85 @@
|
||||
Subtle Semantic Changes After Optimization
|
||||
==========================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
Some optimizations can alter subtle semantics of the script, causing the script to behave
|
||||
differently when run with or without optimization.
|
||||
|
||||
Typically, this involves some form of error that may arise in the original, unoptimized script but
|
||||
is optimized away by the [script optimizer][script optimization].
|
||||
|
||||
```admonish danger.small "DO NOT depend on runtime errors"
|
||||
|
||||
Needless to say, it is usually a _Very Bad Idea™_ to depend on a script failing with a runtime error
|
||||
or such kind of subtleties.
|
||||
|
||||
If it turns out to be necessary (why? I would never guess), turn script optimization off by setting
|
||||
the optimization level to [`OptimizationLevel::None`].
|
||||
```
|
||||
|
||||
|
||||
Disappearing Runtime Errors
|
||||
---------------------------
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
if true { // condition always true
|
||||
123.456; // eliminated
|
||||
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
|
||||
foo(42) // promoted up-level
|
||||
}
|
||||
|
||||
foo(42) // <- the above optimizes to this
|
||||
```
|
||||
|
||||
If the original script were evaluated instead, it would have been an error –
|
||||
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.
|
23
rhai_engine/rhaibook/engine/optimize/side-effects.md
Normal file
23
rhai_engine/rhaibook/engine/optimize/side-effects.md
Normal file
@ -0,0 +1,23 @@
|
||||
Side-Effect Considerations for Full Optimization Level
|
||||
======================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
|
||||
(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
|
||||
which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_
|
||||
custom types and functions are registered.
|
||||
|
||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie
|
||||
within a pruned code block).
|
||||
|
||||
If custom functions are registered to overload [built-in operators], they will also be called when
|
||||
the operators are used (in an [`if`] statement, for example), potentially causing side-effects.
|
||||
|
||||
```admonish tip.small "Rule of thumb"
|
||||
|
||||
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
|
||||
|
||||
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and,
|
||||
when that happens, existing scripts may break in subtle ways.
|
||||
```
|
53
rhai_engine/rhaibook/engine/optimize/volatility.md
Normal file
53
rhai_engine/rhaibook/engine/optimize/volatility.md
Normal file
@ -0,0 +1,53 @@
|
||||
Volatility Considerations for Full Optimization Level
|
||||
=====================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_,
|
||||
i.e. it _depends_ on external environment and does not guarantee the same result for the same inputs.
|
||||
|
||||
A perfect example is a function that gets the current time – 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 {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
54
rhai_engine/rhaibook/engine/options.md
Normal file
54
rhai_engine/rhaibook/engine/options.md
Normal file
@ -0,0 +1,54 @@
|
||||
Engine Configuration Options
|
||||
============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A number of other configuration options are available from the [`Engine`] to fine-tune behavior and safeguards.
|
||||
|
||||
|
||||
Compile-Time Language Features
|
||||
------------------------------
|
||||
|
||||
| Method | Description | Default |
|
||||
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | :-------------------------------------: |
|
||||
| `set_optimization_level`<br/>(not available under [`no_optimize`]) | sets the amount of script _optimizations_ performed (see [script optimization]) | [`Simple`][`OptimizationLevel::Simple`] |
|
||||
| `set_allow_if_expression` | allows/disallows [`if`-expressions]({{rootUrl}}/language/if.md#if-expression) | allow |
|
||||
| `set_allow_switch_expression` | allows/disallows [`switch` expressions]({{rootUrl}}/language/switch-expression.md) | allow |
|
||||
| `set_allow_loop_expressions` | allows/disallows loop expressions | allow |
|
||||
| `set_allow_statement_expression` | allows/disallows [statement expressions]({{rootUrl}}/language/statement-expression.md) | allow |
|
||||
| `set_allow_anonymous_fn`<br/>(not available under [`no_function`]) | allows/disallows [anonymous functions] | allow |
|
||||
| `set_allow_looping` | allows/disallows looping (i.e. [`while`], [`loop`], [`do`] and [`for`] statements) | allow |
|
||||
| `set_allow_shadowing` | allows/disallows _[shadowing]_ of [variables] | allow |
|
||||
| `set_strict_variables` | enables/disables [_Strict Variables_ mode][strict variables] | disabled |
|
||||
| `set_fast_operators` | enables/disables [_Fast Operators_ mode][fast operators] | enabled |
|
||||
| `disable_symbol` | disables a certain [keyword] or [operator] (see [disable keywords and operators]) | |
|
||||
|
||||
Beware that these options activate during _compile-time_ only. If an [`AST`] is compiled on an
|
||||
[`Engine`] but then evaluated on a different [`Engine`] with different configuration, disallowed
|
||||
features contained inside the [`AST`] will still run as normal.
|
||||
|
||||
|
||||
Runtime Behavior
|
||||
----------------
|
||||
|
||||
| Method | Description |
|
||||
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `set_fail_on_invalid_map_property`<br/>(not available under [`no_object`]) | sets whether to raise errors (instead of returning [`()`]) when invalid properties are accessed on [object maps] |
|
||||
| `set_default_tag` | sets the default value of the _custom state_ (which can be obtained via [`NativeCallContext::tag`][`NativeCallContext`]) for each evaluation run |
|
||||
|
||||
|
||||
Safety Limits
|
||||
-------------
|
||||
|
||||
| Method | Not available under | Description |
|
||||
| -------------------------- | :----------------------------: | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement (see [maximum statement depth]) |
|
||||
| `set_max_call_levels` | [`unchecked`], [`no_function`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion (see [maximum call stack depth]) |
|
||||
| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume (see [maximum number of operations]) |
|
||||
| `set_max_variables` | [`unchecked`] | sets the maximum number of [variables] that a script is allowed to define within a single [`Scope`] (see [maximum number of variables]) |
|
||||
| `set_max_functions` | [`unchecked`], [`no_function`] | sets the maximum number of [functions] that a script is allowed to define (see [maximum number of functions]) |
|
||||
| `set_max_modules` | [`unchecked`], [`no_modules`] | sets the maximum number of [modules] that a script is allowed to load (see [maximum number of modules]) |
|
||||
| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings] (see [maximum length of strings]) |
|
||||
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays] (see [maximum size of arrays]) |
|
||||
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps] (see [maximum size of object maps]) |
|
||||
| `set_max_strings_interned` | | sets the maximum number of [strings] to be interned (if zero, the [strings interner] is disabled) |
|
28
rhai_engine/rhaibook/engine/precedence.md
Normal file
28
rhai_engine/rhaibook/engine/precedence.md
Normal file
@ -0,0 +1,28 @@
|
||||
Operator Precedence
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
All operators in Rhai has a _precedence_ indicating how tightly they bind.
|
||||
|
||||
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
|
||||
|
||||
When registering a custom operator, the operator's precedence must also be provided.
|
||||
|
||||
The following _precedence table_ shows the built-in precedence of standard Rhai operators:
|
||||
|
||||
| Category | Operators | Binding | Precedence (0-255) |
|
||||
| ------------------- | :--------------------------------------: | :-----: | :----------------: |
|
||||
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | left | 30 |
|
||||
| Logic and bit masks | `&&`, `&` | left | 60 |
|
||||
| Comparisons | `==`, `!=` | left | 90 |
|
||||
| Containment | [`in`] | left | 110 |
|
||||
| Comparisons | `>`, `>=`, `<`, `<=` | left | 130 |
|
||||
| Null-coalesce | `??` | left | 135 |
|
||||
| Ranges | `..`, `..=` | left | 140 |
|
||||
| Arithmetic | `+`, `-` | left | 150 |
|
||||
| Arithmetic | `*`, `/`, `%` | left | 180 |
|
||||
| Arithmetic | `**` | right | 190 |
|
||||
| Bit-shifts | `<<`, `>>` | left | 210 |
|
||||
| Unary operators | `+`, `-`, `!` | right | highest |
|
||||
| Object field access | `.`, `?.` | right | highest |
|
76
rhai_engine/rhaibook/engine/raw.md
Normal file
76
rhai_engine/rhaibook/engine/raw.md
Normal file
@ -0,0 +1,76 @@
|
||||
Raw `Engine`
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to `stdout`
|
||||
via [`print`] or [`debug`]).
|
||||
|
||||
In many controlled embedded environments, however, these may not be needed and unnecessarily occupy
|
||||
application code storage space.
|
||||
|
||||
```admonish info.side.wide "Built-in operators"
|
||||
|
||||
Even with a raw [`Engine`], some operators are built-in and always available.
|
||||
|
||||
See [_Built-in Operators_][built-in operators] for a full list.
|
||||
```
|
||||
|
||||
Use `Engine::new_raw` to create a _raw_ [`Engine`], in which only a minimal set of
|
||||
[built-in][built-in operators] basic arithmetic and logical operators are supported.
|
||||
|
||||
To add more functionalities to a _raw_ [`Engine`], load [packages] into it.
|
||||
|
||||
Since [packages] can be _shared_, this is an extremely efficient way to create multiple instances of
|
||||
the same [`Engine`] with the same set of functions.
|
||||
|
||||
| | `Engine::new` | `Engine::new_raw` |
|
||||
| --------------------- | :------------------: | :---------------: |
|
||||
| [Built-in operators] | yes | yes |
|
||||
| [Package] loaded | `StandardPackage` | _none_ |
|
||||
| [Module resolver] | `FileModuleResolver` | _none_ |
|
||||
| [Strings interner] | yes | _no_ |
|
||||
| [`on_print`][`print`] | yes | _none_ |
|
||||
| [`on_debug`][`debug`] | yes | _none_ |
|
||||
|
||||
|
||||
```admonish warning.small "Warning: No strings interner"
|
||||
|
||||
A _raw_ [`Engine`] disables the _[strings interner]_ by default.
|
||||
|
||||
This may lead to a significant increase in memory usage if many strings are created in scripts.
|
||||
|
||||
Turn the _[strings interner]_ back on via [`Engine::set_max_strings_interned`][options].
|
||||
```
|
||||
|
||||
~~~admonish example "`Engine::new` is equivalent to..."
|
||||
```rust
|
||||
use rhai::module_resolvers::FileModuleResolver;
|
||||
use rhai::packages::StandardPackage;
|
||||
|
||||
// Create a raw scripting Engine
|
||||
let mut engine = Engine::new_raw();
|
||||
|
||||
// Use the file-based module resolver
|
||||
engine.set_module_resolver(FileModuleResolver::new());
|
||||
|
||||
// Enable the strings interner
|
||||
engine.set_max_strings_interned(1024);
|
||||
|
||||
// Default print/debug implementations
|
||||
engine.on_print(|text| println!("{text}"));
|
||||
|
||||
engine.on_debug(|text, source, pos| match (source, pos) {
|
||||
(Some(source), Position::NONE) => println!("{source} | {text}"),
|
||||
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
|
||||
(None, Position::NONE) => println!("{text}"),
|
||||
(None, pos) => println!("{pos:?} | {text}"),
|
||||
});
|
||||
|
||||
// Register the Standard Package
|
||||
let package = StandardPackage::new();
|
||||
|
||||
// Load the package into the [`Engine`]
|
||||
package.register_into_engine(&mut engine);
|
||||
```
|
||||
~~~
|
215
rhai_engine/rhaibook/engine/scope.md
Normal file
215
rhai_engine/rhaibook/engine/scope.md
Normal file
@ -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<T>`, `get_mut<T>` and `set_value<T>`.
|
||||
|
||||
Essentially, a `Scope` is always searched in _reverse order_.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: The lifetime parameter"
|
||||
|
||||
`Scope` has a _lifetime_ parameter, in the vast majority of cases it can be omitted and
|
||||
automatically inferred to be `'static`.
|
||||
|
||||
Currently, that lifetime parameter is not used. It is there to maintain backwards compatibility
|
||||
as well as for possible future expansion when references can also be put into the `Scope`.
|
||||
|
||||
The lifetime parameter is not guaranteed to remain unused for future versions.
|
||||
|
||||
In order to put a `Scope` into a `struct`, use `Scope<'static>`.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: The `const` generic parameter"
|
||||
|
||||
`Scope` also has a _`const` generic_ parameter, which is a number that defaults to 8.
|
||||
It indicates the number of entries that the `Scope` can keep _inline_ without allocations.
|
||||
|
||||
The larger this number, the larger the `Scope` type gets, but allocations will happen far
|
||||
less frequently.
|
||||
|
||||
A smaller number makes `Scope` smaller, but allocation costs will be incurred when the
|
||||
number of entries exceed the _inline_ capacity.
|
||||
~~~
|
||||
|
||||
|
||||
`Scope` API
|
||||
-----------
|
||||
|
||||
| Method | Description |
|
||||
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `new` _instance method_ | create a new empty `Scope` |
|
||||
| `with_capacity` _instance method_ | create a new empty `Scope` with a specified initial capacity |
|
||||
| `len` | number of [variables]/[constants] currently within the `Scope` |
|
||||
| `rewind` | _rewind_ (i.e. reset) the `Scope` to a particular number of [variables]/[constants] |
|
||||
| `clear` | remove all [variables]/[constants] from the `Scope`, making it empty |
|
||||
| `is_empty` | is the `Scope` empty? |
|
||||
| `is_constant` | is the particular [variable]/[constant] in the `Scope` a [constant]? |
|
||||
| `push`, `push_constant` | add a new [variable]/[constant] into the `Scope` with a specified value |
|
||||
| `push_dynamic`, `push_constant_dynamic` | add a new [variable]/[constant] into the `Scope` with a [`Dynamic`] value |
|
||||
| `set_or_push<T>` | set the value of the last [variable] within the `Scope` by name if it exists and is not [constant]; add a new [variable] into the `Scope` otherwise |
|
||||
| `contains` | does the particular [variable] or [constant] exist in the `Scope`? |
|
||||
| `get_value<T>` | get the value of the last [variable]/[constant] within the `Scope` by name |
|
||||
| `set_value<T>` | set the value of the last [variable] within the `Scope` by name, panics if it is [constant] |
|
||||
| `remove<T>` | remove the last [variable]/[constant] from the `Scope` by name, returning its value |
|
||||
| `get` | get a reference to the value of the last [variable]/[constant] within the `Scope` by name |
|
||||
| `get_mut` | get a reference to the value of the last [variable] within the `Scope` by name, `None` if it is [constant] |
|
||||
| `set_alias` | [exported][`export`] the last [variable]/[constant] within the `Scope` by name |
|
||||
| `iter`, `iter_raw`, `IntoIterator::into_iter` | get an iterator to the [variables]/[constants] within the `Scope` |
|
||||
| `Extend::extend` | add [variables]/[constants] to the `Scope` |
|
||||
|
||||
~~~admonish info.small "`Scope` public API"
|
||||
|
||||
For details on the `Scope` API, refer to the
|
||||
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Scope.html) online.
|
||||
~~~
|
||||
|
||||
|
||||
Serializing/Deserializing
|
||||
-------------------------
|
||||
|
||||
With the [`serde`] feature, `Scope` is serializable and deserializable via
|
||||
[`serde`](https://crates.io/crates/serde).
|
||||
|
||||
[Custom types] stored in the `Scope`, however, are serialized as full type-name strings.
|
||||
Data in [custom types] are not serialized.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In the following example, a `Scope` is created with a few initialized variables, then it is threaded
|
||||
through multiple evaluations.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, EvalAltResult};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// First create the state
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Then push (i.e. add) some initialized variables into the state.
|
||||
// Remember the system number types in Rhai are i64 (i32 if 'only_i32')
|
||||
// and f64 (f32 if 'f32_float').
|
||||
// Better stick to them or it gets hard working with the script.
|
||||
scope.push("y", 42_i64)
|
||||
.push("z", 999_i64)
|
||||
.push_constant("MY_NUMBER", 123_i64) // constants can also be added
|
||||
.set_value("s", "hello, world!"); // 'set_value' adds a new variable when one doesn't exist
|
||||
|
||||
// First invocation
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
let x = 4 + 5 - y + z + MY_NUMBER + s.len;
|
||||
y = 1;
|
||||
")?;
|
||||
|
||||
// Second invocation using the same state.
|
||||
// Notice that the new variable 'x', defined previously, is still here.
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, "x + y")?;
|
||||
|
||||
println!("result: {result}"); // prints 1103
|
||||
|
||||
// Variable y is changed in the script - read it with 'get_value'
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);
|
||||
|
||||
// We can modify scope variables directly with 'set_value'
|
||||
scope.set_value("y", 42_i64);
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 42);
|
||||
```
|
||||
|
||||
|
||||
`Engine` API Using `Scope`
|
||||
--------------------------
|
||||
|
||||
[`Engine`] API methods that accept a `Scope` parameter all end in `_with_scope`, making that
|
||||
`Scope` (and everything inside it) available to the script:
|
||||
|
||||
| `Engine` API | Not available under |
|
||||
| --------------------------------------- | :-----------------: |
|
||||
| `Engine::eval_with_scope` | |
|
||||
| `Engine::eval_ast_with_scope` | |
|
||||
| `Engine::eval_file_with_scope` | [`no_std`] |
|
||||
| `Engine::eval_expression_with_scope` | |
|
||||
| `Engine::run_with_scope` | |
|
||||
| `Engine::run_ast_with_scope` | |
|
||||
| `Engine::run_file_with_scope` | [`no_std`] |
|
||||
| `Engine::compile_file_with_scope` | [`no_std`] |
|
||||
| `Engine::compile_expression_with_scope` | |
|
||||
|
||||
~~~admonish danger "Don't forget to `rewind`"
|
||||
|
||||
[Variables] or [constants] defined at the global level of a script persist inside the custom `Scope`
|
||||
even after the script ends.
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
|
||||
// Variable 'x' stays inside the custom scope!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
|
||||
```
|
||||
|
||||
Due to [variable shadowing][shadowing], new [variables]/[constants] are simply added on top of
|
||||
existing ones (even when they already exist), so care must be taken that new [variables]/[constants]
|
||||
inside the custom `Scope` do not grow without bounds.
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Don't do this - this creates 1 million variables named 'x'
|
||||
// inside 'scope'!!!
|
||||
for _ in 0..1_000_000 {
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
}
|
||||
|
||||
// The 'scope' contains a LOT of variables...
|
||||
assert_eq!(scope.len(), 1_000_000);
|
||||
|
||||
// Variable 'x' stays inside the custom scope!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
|
||||
```
|
||||
|
||||
In order to remove [variables] or [constants] introduced by a script, use the `rewind` method.
|
||||
|
||||
```rust
|
||||
// Run a million times
|
||||
for _ in 0..1_000_000 {
|
||||
// Save the current size of the 'scope'
|
||||
let orig_scope_size = scope.len();
|
||||
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
|
||||
// Rewind the 'scope' to the original size
|
||||
scope.rewind(orig_scope_size);
|
||||
}
|
||||
|
||||
// The 'scope' is empty
|
||||
assert_eq!(scope.len(), 0);
|
||||
|
||||
// Variable 'x' is no longer inside 'scope'!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // error: variable 'x' not found
|
||||
```
|
||||
~~~
|
75
rhai_engine/rhaibook/engine/strict-var.md
Normal file
75
rhai_engine/rhaibook/engine/strict-var.md
Normal file
@ -0,0 +1,75 @@
|
||||
Strict Variables Mode
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "`Scope` constants"
|
||||
|
||||
[Constants] in the external [`Scope`], when provided, count as definition.
|
||||
~~~
|
||||
|
||||
By default, Rhai looks up access to [variables] from the enclosing block scope,
|
||||
working its way outwards until it reaches the top (global) level, then it
|
||||
searches the [`Scope`] (if any) that is passed into the `Engine::eval_with_scope` call.
|
||||
|
||||
Setting [`Engine::set_strict_variables`][options] to `true` turns on _Strict Variables Mode_,
|
||||
which requires that:
|
||||
|
||||
* all [variables]/[constants] be defined within the same script before use,
|
||||
or they must be [variables]/[constants] within the provided [`Scope`] (if any),
|
||||
* [modules] must be [imported][`import`], also within the same script, before use.
|
||||
|
||||
Within _Strict Variables_ mode, any attempt to access a [variable] or [module] before
|
||||
definition/[import][`import`] results in a parse error.
|
||||
|
||||
This way, variable access errors (usually typos) are caught during compile time instead of runtime.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
let y = x * z; // <- parse error under strict variables mode:
|
||||
// variable 'z' is not yet defined
|
||||
|
||||
let z = x + w; // <- parse error under strict variables mode:
|
||||
// variable 'w' is undefined
|
||||
|
||||
foo::bar::baz(); // <- parse error under strict variables mode:
|
||||
// module 'foo' is not yet defined
|
||||
|
||||
fn test1() {
|
||||
foo::bar::baz(); // <- parse error under strict variables mode:
|
||||
// module 'foo' is defined
|
||||
}
|
||||
|
||||
import "my_module" as foo;
|
||||
|
||||
foo::bar::baz(); // ok!
|
||||
|
||||
print(foo::xyz); // ok!
|
||||
|
||||
let x = abc::def; // <- parse error under strict variables mode:
|
||||
// module 'abc' is undefined
|
||||
|
||||
fn test2() {
|
||||
foo:bar::baz(); // ok!
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```admonish question "TL;DR – 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].
|
||||
```
|
79
rhai_engine/rhaibook/engine/token-mapper.md
Normal file
79
rhai_engine/rhaibook/engine/token-mapper.md
Normal file
@ -0,0 +1,79 @@
|
||||
Remap Tokens During Parsing
|
||||
===========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_parse_token`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_parse_token
|
||||
[`Token`]: https://docs.rs/rhai/{{version}}/rhai/enum.Token.html
|
||||
|
||||
|
||||
The Rhai [`Engine`] first parses a script into a stream of _tokens_.
|
||||
|
||||
Tokens have the type [`Token`] which is only exported under [`internals`].
|
||||
|
||||
The function [`Engine::on_parse_token`], available only under [`internals`], allows registration of a
|
||||
_mapper function_ that converts (remaps) a [`Token`] into another.
|
||||
|
||||
|
||||
```admonish tip.small "Hot Tips: Use as safety checks"
|
||||
|
||||
Since it is called for _every_ token parsed from the script, this token mapper function
|
||||
can also be used to implement _safety checks_ against, say, stack-overflow or out-of-memory
|
||||
situations during parsing.
|
||||
|
||||
See [here][memory] for more details.
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
```admonish tip.side "Tip: Raising errors"
|
||||
|
||||
Raise a parse error by returning [`Token::LexError`](https://docs.rs/rhai/{{version}}/rhai/enum.Token.html#variant.LexError)
|
||||
as the mapped token.
|
||||
```
|
||||
|
||||
The function signature passed to [`Engine::on_parse_token`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(token: Token, pos: Position, state: &TokenizeState) -> Token
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------------------------------------------------------: | -------------------------------- |
|
||||
| `token` | [`Token`] | the next symbol parsed |
|
||||
| `pos` | `Position` | location of the [token][`Token`] |
|
||||
| `state` | [`&TokenizeState`](https://docs.rs/rhai/{{version}}/rhai/struct.TokenizeState.html) | current state of the tokenizer |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FLOAT, Token};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a token mapper function.
|
||||
engine.on_parse_token(|token, pos, state| {
|
||||
match token {
|
||||
// Change 'begin' ... 'end' to '{' ... '}'
|
||||
Token::Identifier(s) if &s == "begin" => Token::LeftBrace,
|
||||
Token::Identifier(s) if &s == "end" => Token::RightBrace,
|
||||
|
||||
// Change all integer literals to floating-point
|
||||
Token::IntegerConstant(n) => Token::FloatConstant((n as FLOAT).into()),
|
||||
|
||||
// Disallow '()'
|
||||
Token::Unit => Token::LexError(
|
||||
LexError::ImproperSymbol("()".to_string(), "".to_string()).into()
|
||||
),
|
||||
|
||||
// Pass through all other tokens unchanged
|
||||
_ => token
|
||||
}
|
||||
});
|
||||
```
|
96
rhai_engine/rhaibook/engine/var.md
Normal file
96
rhai_engine/rhaibook/engine/var.md
Normal file
@ -0,0 +1,96 @@
|
||||
Variable Resolver
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_var
|
||||
|
||||
|
||||
By default, Rhai looks up access to [variables] from the enclosing block scope, working its way
|
||||
outwards until it reaches the top (global) level, then it searches the [`Scope`] that is passed into
|
||||
the `Engine::eval` call.
|
||||
|
||||
There is a built-in facility for advanced users to _hook_ into the [variable] resolution service and
|
||||
to override its default behavior.
|
||||
|
||||
To do so, provide a closure to the [`Engine`] via [`Engine::on_var`].
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a variable resolver.
|
||||
engine.on_var(|name, index, context| {
|
||||
match name {
|
||||
"MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
|
||||
// Override a variable - make it not found even if it exists!
|
||||
"DO_NOT_USE" => Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()),
|
||||
// Silently maps 'chameleon' into 'innocent'.
|
||||
"chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(||
|
||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()
|
||||
),
|
||||
// Return Ok(None) to continue with the normal variable resolution process.
|
||||
_ => Ok(None)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```admonish info.small "Benefits of using a variable resolver"
|
||||
|
||||
1. Avoid having to maintain a custom [`Scope`] with all [variables] regardless of need
|
||||
(because a script may not use them all).
|
||||
|
||||
2. _Short-circuit_ [variable] access, essentially overriding standard behavior.
|
||||
|
||||
3. _Lazy-load_ [variables] when they are accessed, not up-front.
|
||||
This benefits when the number of [variables] is very large, when they are timing-dependent,
|
||||
or when they are expensive to load.
|
||||
|
||||
4. Rename system [variables] on a script-by-script basis without having to construct different [`Scope`]'s.
|
||||
```
|
||||
|
||||
```admonish warning.small "Returned values are constants"
|
||||
|
||||
[Variable] values returned by a variable resolver are treated as _[constants]_.
|
||||
|
||||
This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.
|
||||
|
||||
To change these [variables], better push them into a custom [`Scope`] instead of
|
||||
using a variable resolver.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Returning shared values"
|
||||
|
||||
It is possible to return a _shared_ value from a variable resolver.
|
||||
|
||||
This is one way to implement [Mutable Global State]({{rootUrl}}/patterns/global-mutable-state.md).
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_var`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(name: &str, index: usize, context: EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | `&str` | [variable] name |
|
||||
| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the [variable] is supposed to reside.<br/>Offsets start from 1, with 1 meaning the last [variable] in the current [`Scope`]. Essentially the correct [variable] is at position `scope.len() - index`.<br/>If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. |
|
||||
| `context` | [`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where:
|
||||
|
||||
| Value | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Ok(None)` | normal [variable] resolution process should continue, i.e. continue searching through the [`Scope`] |
|
||||
| `Ok(Some(value))` | value (a [`Dynamic`]) of the [variable], treated as a [constant] |
|
||||
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`], normally `EvalAltResult::ErrorVariableNotFound` to indicate that the [variable] does not exist, but it can be any `EvalAltResult`. |
|
17
rhai_engine/rhaibook/index.md
Normal file
17
rhai_engine/rhaibook/index.md
Normal file
@ -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.
|
76
rhai_engine/rhaibook/language/arrays-oob.md
Normal file
76
rhai_engine/rhaibook/language/arrays-oob.md
Normal file
@ -0,0 +1,76 @@
|
||||
Out-of-Bounds Index for Arrays
|
||||
==============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_invalid_array_index`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_invalid_array_index
|
||||
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires `internals`"
|
||||
|
||||
This is an advanced feature that requires the [`internals`] feature to be enabled.
|
||||
~~~
|
||||
|
||||
Normally, when an index is out-of-bounds for an [array], an error is raised.
|
||||
|
||||
It is possible to completely control this behavior via a special callback function
|
||||
registered into an [`Engine`] via `on_invalid_array_index`.
|
||||
|
||||
Using this callback, for instance, it is simple to instruct Rhai to extend the [array] to
|
||||
accommodate this new element, or to return a default value instead of raising an error.
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_invalid_array_index`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(array: &mut Array, index: i64, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------------: | -------------------------------- |
|
||||
| `array` | [`&mut Array`][array] | the [array] being accessed |
|
||||
| `index` | `i64` | index value |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Target, Box<EvalAltResult>>`.
|
||||
|
||||
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
|
||||
_reference_ to a [`Dynamic`] value.
|
||||
|
||||
It can be used to point to a particular value within the [array] or a new temporary value.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.on_invalid_array_index(|arr, index, _| {
|
||||
match index {
|
||||
-100 => {
|
||||
// The array can be modified in place
|
||||
arr.push((42_i64).into());
|
||||
|
||||
// Return a mutable reference to an element
|
||||
let value_ref = arr.last_mut().unwrap();
|
||||
Ok(value_ref.into())
|
||||
}
|
||||
100 => {
|
||||
// Return a temporary value (not a reference)
|
||||
let value = Dynamic::from(100_i64);
|
||||
Ok(value.into())
|
||||
}
|
||||
// Return the standard out-of-bounds error
|
||||
_ => Err(EvalAltResult::ErrorArrayBounds(
|
||||
arr.len(), index, Position::NONE
|
||||
).into()),
|
||||
}
|
||||
});
|
||||
```
|
300
rhai_engine/rhaibook/language/arrays.md
Normal file
300
rhai_engine/rhaibook/language/arrays.md
Normal file
@ -0,0 +1,300 @@
|
||||
Arrays
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum size of arrays].
|
||||
```
|
||||
|
||||
Arrays are first-class citizens in Rhai.
|
||||
|
||||
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with
|
||||
elements added or removed.
|
||||
|
||||
The Rust type of a Rhai array is `rhai::Array` which is an alias to `Vec<Dynamic>`.
|
||||
|
||||
[`type_of()`] an array returns `"array"`.
|
||||
|
||||
Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
|
||||
Literal Syntax
|
||||
--------------
|
||||
|
||||
Array literals are built within square brackets `[` ... `]` and separated by commas `,`:
|
||||
|
||||
> `[` _value_`,` _value_`,` ... `,` _value_ `]`
|
||||
>
|
||||
> `[` _value_`,` _value_`,` ... `,` _value_ `,` `]` `// trailing comma is OK`
|
||||
|
||||
|
||||
Element Access Syntax
|
||||
---------------------
|
||||
|
||||
### From beginning
|
||||
|
||||
Like C, arrays are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _array_ `[` _index position from 0 to (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` | <ol><li>position, counting from end if < 0</li><li>new element</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
|
||||
| `push`, `+=` operator | element to append (not an array) | appends an element to the end |
|
||||
| `append`, `+=` operator | array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | <ol><li>first array</li><li>second array</li></ol> | concatenates the first array with the second |
|
||||
| `==` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays the same (elements compared with the `==` operator, if defined)? |
|
||||
| `!=` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays different (elements compared with the `==` operator, if defined)? |
|
||||
| `insert` | <ol><li>position, counting from end if < 0, end if ≥ length</li><li>element to insert</li></ol> | inserts an element at a certain position |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `extract` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>_(optional)_ number of elements to extract, none if ≤ 0, to end if omitted</li></ol> | extracts a portion of the array into a new array |
|
||||
| `extract` | [range] of elements to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the array into a new array |
|
||||
| `remove` | position, counting from end if < 0 | removes an element at a particular position and returns it ([`()`] if the position is not valid) |
|
||||
| `reverse` | _none_ | reverses the array |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `is_empty` method and property | _none_ | returns `true` if the array is empty |
|
||||
| `pad` | <ol><li>target length</li><li>element to pad</li></ol> | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length |
|
||||
| `split` | <ol><li>array</li><li>position to split at, counting from end if < 0, end if ≥ length</li></ol> | splits the array into two arrays, starting from a specified position |
|
||||
| `for_each` | [function pointer] for processing elements | run through each element in the array in order, binding each to `this` and calling the processing function taking the following parameters: <ol><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `drain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `drain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to remove, none if ≤ 0</li></ol> | removes a portion of the array, returning the removed elements as a new array |
|
||||
| `drain` | [range] of elements to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the array, returning the removed elements as a new array |
|
||||
| `retain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that do not return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `retain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to retain, none if ≤ 0</li></ol> | retains a portion of the array, removes all other elements and returning them as a new array |
|
||||
| `retain` | [range] of elements to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the array, removes all other bytes and returning them as a new array |
|
||||
| `splice` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to remove, none if ≤ 0</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
|
||||
| `splice` | <ol><li>[range] of elements to remove, from beginning if ≤ 0, to end if ≥ length</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
|
||||
| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all elements that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `contains`, [`in`] operator | element to find | does the array contain an element? The `==` operator (if defined) is used to compare [custom types] |
|
||||
| `index_of` | <ol><li>element to find (not a [function pointer])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the position of the first element in the array that equals the supplied element (using the `==` operator, if defined), or −1 if not found</li></ol> |
|
||||
| `index_of` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the position of the first element in the array that returns `true` when called with the predicate function, or −1 if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `find` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the first element in the array that returns `true` when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `find_map` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the first non-[`()`] value of the first element in the array when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `dedup` | _(optional)_ [function pointer] to predicate (usually a [closure]); if omitted, the `==` operator is used, if defined | removes all but the first of _consecutive_ elements in the array that return `true` when called with the predicate function (non-consecutive duplicates are _not_ removed):<br/>1st & 2nd parameters: two elements in the array |
|
||||
| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all elements mapped to the result of applying the conversion function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `reduce` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `reduce_rev` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array (in reverse order) into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `zip` | <ol><li>array to zip</li><li>[function pointer] to conversion function (usually a [closure])</li></ol> | constructs a new array with all element pairs from two arrays mapped to the result of applying the conversion function taking the following parameters:<ol><li>first array element</li><li>second array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any element returns `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all elements return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function taking the following parameters:<ol><li>first element</li><li>second element<br/>return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second</li></ol> |
|
||||
| `sort` | _none_ | sorts a _homogeneous_ array containing only elements of the same comparable built-in type (`INT`, `FLOAT`, [`Decimal`][rust_decimal], [string], [character], `bool`, [`()`]) |
|
||||
|
||||
|
||||
```admonish tip.small "Tip: Use custom types with arrays"
|
||||
|
||||
To use a [custom type] with arrays, a number of functions need to be manually implemented,
|
||||
in particular the `==` operator in order to support the [`in`] operator which uses `==` (via the
|
||||
`contains` method) to compare elements.
|
||||
|
||||
See the section on [custom types] for more details.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let y = [2, 3]; // y == [2, 3]
|
||||
|
||||
let y = [2, 3,]; // y == [2, 3]
|
||||
|
||||
y.insert(0, 1); // y == [1, 2, 3]
|
||||
|
||||
y.insert(999, 4); // y == [1, 2, 3, 4]
|
||||
|
||||
y.len == 4;
|
||||
|
||||
y[0] == 1;
|
||||
y[1] == 2;
|
||||
y[2] == 3;
|
||||
y[3] == 4;
|
||||
|
||||
(1 in y) == true; // use 'in' to test if an element exists in the array
|
||||
|
||||
(42 in y) == false; // 'in' uses the 'contains' function, which uses the
|
||||
// '==' operator (that users can override)
|
||||
// to check if the target element exists in the array
|
||||
|
||||
y.contains(1) == true; // the above de-sugars to this
|
||||
|
||||
y[1] = 42; // y == [1, 42, 3, 4]
|
||||
|
||||
(42 in y) == true;
|
||||
|
||||
y.remove(2) == 3; // y == [1, 42, 4]
|
||||
|
||||
y.len == 3;
|
||||
|
||||
y[2] == 4; // elements after the removed element are shifted
|
||||
|
||||
ts.list = y; // arrays can be assigned completely (by value copy)
|
||||
|
||||
ts.list[1] == 42;
|
||||
|
||||
[1, 2, 3][0] == 1; // indexing on array literal
|
||||
|
||||
[1, 2, 3][-1] == 3; // negative position counts from the end
|
||||
|
||||
fn abc() {
|
||||
[42, 43, 44] // a function returning an array
|
||||
}
|
||||
|
||||
abc()[0] == 42;
|
||||
|
||||
y.push(4); // y == [1, 42, 4, 4]
|
||||
|
||||
y += 5; // y == [1, 42, 4, 4, 5]
|
||||
|
||||
y.len == 5;
|
||||
|
||||
y.shift() == 1; // y == [42, 4, 4, 5]
|
||||
|
||||
y.chop(3); // y == [4, 4, 5]
|
||||
|
||||
y.len == 3;
|
||||
|
||||
y.pop() == 5; // y == [4, 4]
|
||||
|
||||
y.len == 2;
|
||||
|
||||
for element in y { // arrays can be iterated with a 'for' statement
|
||||
print(element);
|
||||
}
|
||||
|
||||
y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"]
|
||||
|
||||
y.len == 6;
|
||||
|
||||
y.truncate(4); // y == [4, 4, "hello", "hello"]
|
||||
|
||||
y.len == 4;
|
||||
|
||||
y.clear(); // y == []
|
||||
|
||||
y.len == 0;
|
||||
|
||||
// The examples below use 'a' as the master array
|
||||
|
||||
let a = [42, 123, 99];
|
||||
|
||||
a.for_each(|| this *= 2);
|
||||
|
||||
a == [84, 246, 198];
|
||||
|
||||
a.for_each(|i| this /= 2);
|
||||
|
||||
a == [42, 123, 99];
|
||||
|
||||
a.map(|v| v + 1); // returns [43, 124, 100]
|
||||
|
||||
a.map(|| this + 1); // returns [43, 124, 100]
|
||||
|
||||
a.map(|v, i| v + i); // returns [42, 124, 101]
|
||||
|
||||
a.filter(|v| v > 50); // returns [123, 99]
|
||||
|
||||
a.filter(|| this > 50); // returns [123, 99]
|
||||
|
||||
a.filter(|v, i| i == 1); // returns [123]
|
||||
|
||||
a.filter("is_odd"); // returns [123, 99]
|
||||
|
||||
a.filter(Fn("is_odd")); // <- previous statement is equivalent to this...
|
||||
|
||||
a.filter(|v| is_odd(v)); // <- or this
|
||||
|
||||
a.some(|v| v > 50); // returns true
|
||||
|
||||
a.some(|| this > 50); // returns true
|
||||
|
||||
a.some(|v, i| v < i); // returns false
|
||||
|
||||
a.all(|v| v > 50); // returns false
|
||||
|
||||
a.all(|| this > 50); // returns false
|
||||
|
||||
a.all(|v, i| v > i); // returns true
|
||||
|
||||
// Reducing - initial value provided directly
|
||||
a.reduce(|sum| sum + this, 0) == 264;
|
||||
|
||||
// Reducing - initial value provided directly
|
||||
a.reduce(|sum, v| sum + v, 0) == 264;
|
||||
|
||||
// Reducing - initial value is '()'
|
||||
a.reduce(
|
||||
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing - initial value has index position == 0
|
||||
a.reduce(|sum, v, i|
|
||||
if i == 0 { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing in reverse - initial value provided directly
|
||||
a.reduce_rev(|sum| sum + this, 0) == 264;
|
||||
|
||||
// Reducing in reverse - initial value provided directly
|
||||
a.reduce_rev(|sum, v| sum + v, 0) == 264;
|
||||
|
||||
// Reducing in reverse - initial value is '()'
|
||||
a.reduce_rev(
|
||||
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing in reverse - initial value has index position == 0
|
||||
a.reduce_rev(|sum, v, i|
|
||||
if i == 2 { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// In-place modification
|
||||
|
||||
a.splice(1..=1, [1, 3, 2]); // a == [42, 1, 3, 2, 99]
|
||||
|
||||
a.extract(1..=3); // returns [1, 3, 2]
|
||||
|
||||
a.sort(|x, y| y - x); // a == [99, 42, 3, 2, 1]
|
||||
|
||||
a.sort(); // a == [1, 2, 3, 42, 99]
|
||||
|
||||
a.drain(|v| v <= 1); // a == [2, 3, 42, 99]
|
||||
|
||||
a.drain(|v, i| i ≥ 3); // a == [2, 3, 42]
|
||||
|
||||
a.retain(|v| v > 10); // a == [42]
|
||||
|
||||
a.retain(|v, i| i > 0); // a == []
|
||||
```
|
89
rhai_engine/rhaibook/language/assignment-op.md
Normal file
89
rhai_engine/rhaibook/language/assignment-op.md
Normal file
@ -0,0 +1,89 @@
|
||||
Compound Assignments
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Compound assignments are assignments with a [binary operator][operators] attached.
|
||||
|
||||
```rust
|
||||
number += 8; // number = number + 8
|
||||
|
||||
number -= 7; // number = number - 7
|
||||
|
||||
number *= 6; // number = number * 6
|
||||
|
||||
number /= 5; // number = number / 5
|
||||
|
||||
number %= 4; // number = number % 4
|
||||
|
||||
number **= 3; // number = number ** 3
|
||||
|
||||
number <<= 2; // number = number << 2
|
||||
|
||||
number >>= 1; // number = number >> 1
|
||||
|
||||
number &= 0x00ff; // number = number & 0x00ff;
|
||||
|
||||
number |= 0x00ff; // number = number | 0x00ff;
|
||||
|
||||
number ^= 0x00ff; // number = number ^ 0x00ff;
|
||||
```
|
||||
|
||||
|
||||
The Flexible `+=`
|
||||
-----------------
|
||||
|
||||
The the `+` and `+=` operators are often [overloaded][function overloading] to perform build-up
|
||||
operations for different data types.
|
||||
|
||||
### Build strings
|
||||
|
||||
```rust
|
||||
let my_str = "abc";
|
||||
|
||||
my_str += "ABC";
|
||||
my_str += 12345;
|
||||
|
||||
my_str == "abcABC12345"
|
||||
```
|
||||
|
||||
### Concatenate arrays
|
||||
|
||||
```rust
|
||||
let my_array = [1, 2, 3];
|
||||
|
||||
my_array += [4, 5];
|
||||
|
||||
my_array == [1, 2, 3, 4, 5];
|
||||
```
|
||||
|
||||
### Concatenate BLOB's
|
||||
|
||||
```rust
|
||||
let my_blob = blob(3, 0x42);
|
||||
|
||||
my_blob += blob(5, 0x89);
|
||||
|
||||
my_blob.to_string() == "[4242428989898989]";
|
||||
```
|
||||
|
||||
### Mix two object maps together
|
||||
|
||||
```rust
|
||||
let my_obj = #{ a:1, b:2 };
|
||||
|
||||
my_obj += #{ c:3, d:4, e:5 };
|
||||
|
||||
my_obj == #{ a:1, b:2, c:3, d:4, e:5 };
|
||||
```
|
||||
|
||||
### Add seconds to timestamps
|
||||
|
||||
```rust
|
||||
let now = timestamp();
|
||||
|
||||
now += 42.0;
|
||||
|
||||
(now - timestamp()).round() == 42.0;
|
||||
```
|
125
rhai_engine/rhaibook/language/assignment.md
Normal file
125
rhai_engine/rhaibook/language/assignment.md
Normal file
@ -0,0 +1,125 @@
|
||||
Assignments
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Value assignments to [variables] use the `=` symbol.
|
||||
|
||||
```rust
|
||||
let foo = 42;
|
||||
|
||||
bar = 123 * 456 - 789;
|
||||
|
||||
x[1][2].prop = do_calculation();
|
||||
```
|
||||
|
||||
|
||||
Valid Assignment Targets
|
||||
------------------------
|
||||
|
||||
The left-hand-side (LHS) of an assignment statement must be a valid
|
||||
_[l-value](https://en.wikipedia.org/wiki/Value_(computer_science))_, which must be rooted in a
|
||||
[variable], potentially extended via indexing or properties.
|
||||
|
||||
~~~admonish bug "Assigning to invalid l-value"
|
||||
|
||||
Expressions that are not valid _l-values_ cannot be assigned to.
|
||||
|
||||
```rust
|
||||
x = 42; // variable is an l-value
|
||||
|
||||
x[1][2][3] = 42 // variable indexing is an l-value
|
||||
|
||||
x.prop1.prop2 = 42; // variable property is an l-value
|
||||
|
||||
foo(x) = 42; // syntax error: function call is not an l-value
|
||||
|
||||
x.foo() = 42; // syntax error: method call is not an l-value
|
||||
|
||||
(x + y) = 42; // syntax error: binary op is not an l-value
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Values are Cloned
|
||||
-----------------
|
||||
|
||||
Values assigned are always _cloned_.
|
||||
So care must be taken when assigning large data types (such as [arrays]).
|
||||
|
||||
```rust
|
||||
x = y; // value of 'y' is cloned
|
||||
|
||||
x == y; // both 'x' and 'y' hold different copies
|
||||
// of the same value
|
||||
```
|
||||
|
||||
|
||||
Moving Data
|
||||
-----------
|
||||
|
||||
When assigning large data types, sometimes it is desirable to _move_ the data instead of cloning it.
|
||||
|
||||
Use the `take` function (defined in the [`LangCorePackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) to _move_ data.
|
||||
|
||||
### The original variable is left with `()`
|
||||
|
||||
```rust
|
||||
x = take(y); // value of 'y' is moved to 'x'
|
||||
|
||||
y == (); // 'y' now holds '()'
|
||||
|
||||
x != y; // 'x' holds the original value of 'y'
|
||||
```
|
||||
|
||||
### Return large data types from functions
|
||||
|
||||
`take` is convenient when returning large data types from a [function].
|
||||
|
||||
```rust
|
||||
fn get_large_value_naive() {
|
||||
let large_result = do_complex_calculation();
|
||||
|
||||
large_result.done = true;
|
||||
|
||||
// Return a cloned copy of the result, then the
|
||||
// local variable 'large_result' is thrown away!
|
||||
large_result
|
||||
}
|
||||
|
||||
fn get_large_value_smart() {
|
||||
let large_result = do_complex_calculation();
|
||||
|
||||
large_result.done = true;
|
||||
|
||||
// Return the result without cloning!
|
||||
// Method style call is also OK.
|
||||
large_result.take()
|
||||
}
|
||||
```
|
||||
|
||||
### Assigning large data types to object map properties
|
||||
|
||||
`take` is useful when assigning large data types to [object map] properties.
|
||||
|
||||
```rust
|
||||
let x = [];
|
||||
|
||||
// Build a large array
|
||||
for n in 0..1000000 { x += n; }
|
||||
|
||||
// The following clones the large array from 'x'.
|
||||
// Both 'my_object.my_property' and 'x' now hold exact copies
|
||||
// of the same large array!
|
||||
my_object.my_property = x;
|
||||
|
||||
// Move it to object map property via 'take' without cloning.
|
||||
// 'x' now holds '()'.
|
||||
my_object.my_property = x.take();
|
||||
|
||||
// Without 'take', the following must be done to avoid cloning:
|
||||
my_object.my_property = [];
|
||||
|
||||
for n in 0..1000000 { my_object.my_property += n; }
|
||||
```
|
125
rhai_engine/rhaibook/language/bit-fields.md
Normal file
125
rhai_engine/rhaibook/language/bit-fields.md
Normal file
@ -0,0 +1,125 @@
|
||||
Integer as Bit-Fields
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish note.side
|
||||
|
||||
Nothing here cannot be done via standard bit-manipulation (i.e. shifting and masking).
|
||||
|
||||
Built-in support is more elegant and performant since it usually replaces a sequence of multiple steps.
|
||||
|
||||
```
|
||||
|
||||
Since bit-wise operators are defined on integer numbers, individual bits can also be accessed and
|
||||
manipulated via an indexing syntax.
|
||||
|
||||
If a bit is set (i.e. `1`), the index access returns `true`.
|
||||
|
||||
If a bit is not set (i.e. `0`), the index access returns `false`.
|
||||
|
||||
When a [range] is used, the bits within the [range] are shifted and extracted as an integer value.
|
||||
|
||||
Bit-fields are very commonly used in embedded systems which must squeeze data into limited memory.
|
||||
Built-in support makes handling them efficient.
|
||||
|
||||
Indexing an integer as a bit-field is disabled for the [`no_index`] feature.
|
||||
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
### From Least-Significant Bit (LSB)
|
||||
|
||||
Bits in a bit-field are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _integer_ `[` _index from 0 to 63 or 31_ `]`
|
||||
>
|
||||
> _integer_ `[` _index from 0 to 63 or 31_ `] =` `true` or `false` ;
|
||||
|
||||
[Ranges] can also be used:
|
||||
|
||||
> _integer_ `[` _start_ `..` _end_ `]`
|
||||
> _integer_ `[` _start_ `..=` _end_ `]`
|
||||
>
|
||||
> _integer_ `[` _start_ `..` _end_ `] =` _new integer value_ ;
|
||||
> _integer_ `[` _start_ `..=` _end_ `] =` _new integer value_ ;
|
||||
|
||||
```admonish warning.small "Number of bits"
|
||||
|
||||
The maximum bit number that can be accessed is 63 (or 31 under [`only_i32`]).
|
||||
|
||||
Bits outside of the range are ignored.
|
||||
```
|
||||
|
||||
|
||||
### From Most-Significant Bit (MSB)
|
||||
|
||||
A _negative_ index accesses a bit in the bit-field counting from the _end_, or from the
|
||||
_most-significant bit_, with −1 being the _highest_ bit.
|
||||
|
||||
> _integer_ `[` _index from −1 to −64 or −32_ `]`
|
||||
>
|
||||
> _integer_ `[` _index from −1 to −64 or −32_ `] =` `true` or `false` ;
|
||||
|
||||
[Ranges] always count from the least-significant bit (LSB) and has no support for negative positions.
|
||||
|
||||
```admonish warning.small "Number of bits"
|
||||
|
||||
The maximum bit number that can be accessed is −64 (or −32 under [`only_i32`]).
|
||||
```
|
||||
|
||||
|
||||
Bit-Field Functions
|
||||
-------------------
|
||||
|
||||
The following standard functions (defined in the [`BitFieldPackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) operate on `INT` bit-fields.
|
||||
|
||||
These functions are available even under the [`no_index`] feature.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| `get_bit` | bit number, counting from MSB if < 0 | returns the state of a bit: `true` if `1`, `false` if `0` |
|
||||
| `set_bit` | <ol><li>bit number, counting from MSB if < 0</li><li>new state: `true` if `1`, `false` if `0`</li></ol> | sets the state of a bit |
|
||||
| `get_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to extract, none if < 1, to MSB if ≥ _length_</li></ol> | extracts a number of bits, shifted towards LSB |
|
||||
| `get_bits` | [range] of bits | extracts a number of bits, shifted towards LSB |
|
||||
| `set_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to set, none if < 1, to MSB if ≥ _length_<br/>3) new value</li></ol> | sets a number of bits from the new value |
|
||||
| `set_bits` | <ol><li>[range] of bits</li><li>new value</li></ol> | sets a number of bits from the new value |
|
||||
| `bits` method and property | <ol><li>_(optional)_ starting bit number, counting from MSB if < 0</li><li>_(optional)_ number of bits to extract, none if < 1, to MSB if ≥ _length_</li></ol> | allows iteration over the bits of a bit-field |
|
||||
| `bits` | [range] of bits | allows iteration over the bits of a bit-field |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```js , no_run
|
||||
// Assume the following bits fields in a single 16-bit word:
|
||||
// ┌─────────┬────────────┬──────┬─────────┐
|
||||
// │ 15-12 │ 11-4 │ 3 │ 2-0 │
|
||||
// ├─────────┼────────────┼──────┼─────────┤
|
||||
// │ 0 │ 0-255 data │ flag │ command │
|
||||
// └─────────┴────────────┴──────┴─────────┘
|
||||
|
||||
let value = read_start_hw_register(42);
|
||||
|
||||
let command = value.get_bits(0, 3); // Command = bits 0-2
|
||||
|
||||
let flag = value[3]; // Flag = bit 3
|
||||
|
||||
let data = value[4..=11]; // Data = bits 4-11
|
||||
let data = value.get_bits(4..=11); // <- above is the same as this
|
||||
|
||||
let reserved = value.get_bits(-4); // Reserved = last 4 bits
|
||||
|
||||
if reserved != 0 {
|
||||
throw reserved;
|
||||
}
|
||||
|
||||
switch command {
|
||||
0 => print(`Data = ${data}`),
|
||||
1 => value[4..=11] = data / 2,
|
||||
2 => value[3] = !flag,
|
||||
_ => print(`Unknown: ${command}`)
|
||||
}
|
||||
```
|
202
rhai_engine/rhaibook/language/blobs.md
Normal file
202
rhai_engine/rhaibook/language/blobs.md
Normal file
@ -0,0 +1,202 @@
|
||||
BLOB's
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum size of arrays].
|
||||
```
|
||||
|
||||
BLOB's (**B**inary **L**arge **OB**jects), used to hold packed arrays of bytes, have built-in support in Rhai.
|
||||
|
||||
A BLOB has no literal representation, but is created via the `blob` function, or simply returned as
|
||||
the result of a function call (e.g. `generate_thumbnail_image` that generates a thumbnail version of
|
||||
a large image as a BLOB).
|
||||
|
||||
All items stored in a BLOB are bytes (i.e. `u8`) and the BLOB can freely grow or shrink with bytes
|
||||
added or removed.
|
||||
|
||||
The Rust type of a Rhai BLOB is `rhai::Blob` which is an alias to `Vec<u8>`.
|
||||
|
||||
[`type_of()`] a BLOB returns `"blob"`.
|
||||
|
||||
BLOB's are disabled via the [`no_index`] feature.
|
||||
|
||||
|
||||
Element Access Syntax
|
||||
---------------------
|
||||
|
||||
### From beginning
|
||||
|
||||
Like [arrays], BLOB's are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _blob_ `[` _index position from 0 to (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 | <ol><li>_(optional)_ initial length of the BLOB</li><li>_(optional)_ initial byte value</li></ol> | creates a new BLOB, optionally of a particular length filled with an initial byte value (default = 0) |
|
||||
| `to_array` | _none_ | converts the BLOB into an [array] of integers |
|
||||
| `as_string` | _none_ | converts the BLOB into a [string] (the byte stream is interpreted as UTF-8) |
|
||||
| `get` | position, counting from end if < 0 | gets a copy of the byte at a certain position (0 if the position is not valid) |
|
||||
| `set` | <ol><li>position, counting from end if < 0</li><li>new byte value</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
|
||||
| `push`, `append`, `+=` operator | <ol><li>BLOB</li><li>byte to append</li></ol> | appends a byte to the end |
|
||||
| `append`, `+=` operator | <ol><li>BLOB</li><li>BLOB to append</li></ol> | concatenates the second BLOB to the end of the first |
|
||||
| `append`, `+=` operator | <ol><li>BLOB</li><li>[string]/[character] to append</li></ol> | concatenates a [string] or [character] (as UTF-8 encoded byte-stream) to the end of the BLOB |
|
||||
| `+` operator | <ol><li>first BLOB</li><li>[string] to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string] |
|
||||
| `+` operator | <ol><li>[string]</li><li>BLOB to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string] |
|
||||
| `+` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | concatenates the first BLOB with the second |
|
||||
| `==` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's the same? |
|
||||
| `!=` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's different? |
|
||||
| `insert` | <ol><li>position, counting from end if < 0, end if ≥ length</li><li>byte to insert</li></ol> | inserts a byte at a certain position |
|
||||
| `pop` | _none_ | removes the last byte and returns it (0 if empty) |
|
||||
| `shift` | _none_ | removes the first byte and returns it (0 if empty) |
|
||||
| `extract` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>_(optional)_ number of bytes to extract, none if ≤ 0</li></ol> | extracts a portion of the BLOB into a new BLOB |
|
||||
| `extract` | [range] of bytes to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the BLOB into a new BLOB |
|
||||
| `remove` | position, counting from end if < 0 | removes a byte at a particular position and returns it (0 if the position is not valid) |
|
||||
| `reverse` | _none_ | reverses the BLOB byte by byte |
|
||||
| `len` method and property | _none_ | returns the number of bytes in the BLOB |
|
||||
| `is_empty` method and property | _none_ | returns `true` if the BLOB is empty |
|
||||
| `pad` | <ol><li>target length</li><li>byte value to pad</li></ol> | pads the BLOB with a byte value to at least a specified length |
|
||||
| `clear` | _none_ | empties the BLOB |
|
||||
| `truncate` | target length | cuts off the BLOB at exactly a specified length (discarding all subsequent bytes) |
|
||||
| `chop` | target length | cuts off the head of the BLOB, leaving the tail at exactly a specified length |
|
||||
| `contains`, [`in`] operator | byte value to find | does the BLOB contain a particular byte value? |
|
||||
| `split` | <ol><li>BLOB</li><li>position to split at, counting from end if < 0, end if ≥ length</li></ol> | splits the BLOB into two BLOB's, starting from a specified position |
|
||||
| `drain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to remove, none if ≤ 0</li></ol> | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
|
||||
| `drain` | [range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
|
||||
| `retain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to retain, none if ≤ 0</li></ol> | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
|
||||
| `retain` | [range] of bytes to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
|
||||
| `splice` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to remove, none if ≤ 0</li><li>BLOB to insert</li></ol> | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
|
||||
| `splice` | <ol><li>[range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length</li><li>BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
|
||||
| `parse_le_int` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_int` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_le` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_le` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_be` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_be` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_utf8` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
|
||||
| `write_utf8` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
|
||||
| `write_ascii` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of [characters] to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
|
||||
| `write_ascii` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
|
65
rhai_engine/rhaibook/language/comments.md
Normal file
65
rhai_engine/rhaibook/language/comments.md
Normal file
@ -0,0 +1,65 @@
|
||||
Comments
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Comments are C-style, including `/*` ... `*/` pairs for block comments and `//` for comments to the
|
||||
end of the line.
|
||||
|
||||
Block comments can be nested.
|
||||
|
||||
```rust
|
||||
let /* intruder comment */ name = "Bob";
|
||||
|
||||
// This is a very important one-line comment
|
||||
|
||||
/* This comment spans
|
||||
multiple lines, so it
|
||||
only makes sense that
|
||||
it is even more important */
|
||||
|
||||
/* Fear not, Rhai satisfies all nesting needs with nested comments:
|
||||
/*/*/*/*/**/*/*/*/*/
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
Module Documentation
|
||||
--------------------
|
||||
|
||||
Comment lines starting with `//!` make up the _module documentation_.
|
||||
|
||||
They are used to document the containing [module] – 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.
|
||||
```
|
145
rhai_engine/rhaibook/language/constants.md
Normal file
145
rhai_engine/rhaibook/language/constants.md
Normal file
@ -0,0 +1,145 @@
|
||||
Constants
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Constants can be defined using the `const` keyword and are immutable.
|
||||
|
||||
```rust
|
||||
const X; // 'X' is a constant '()'
|
||||
|
||||
const X = 40 + 2; // 'X' is a constant 42
|
||||
|
||||
print(X * 2); // prints 84
|
||||
|
||||
X = 123; // <- syntax error: constant modified
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Naming"
|
||||
|
||||
Constants follow the same naming rules as [variables], but as a convention are often named with
|
||||
all-capital letters.
|
||||
```
|
||||
|
||||
|
||||
Manually Add Constant into Custom Scope
|
||||
---------------------------------------
|
||||
|
||||
```admonish tip.side "Tip: Singleton"
|
||||
|
||||
A constant value holding a [custom type] essentially acts
|
||||
as a [_singleton_]({{rootUrl}}/patterns/singleton.md).
|
||||
```
|
||||
|
||||
It is possible to add a constant into a custom [`Scope`] via `Scope::push_constant` so it'll be
|
||||
available to scripts running with that [`Scope`].
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct(i64); // custom type
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
|
||||
.register_get("value", |obj: &mut TestStruct| obj.0), // property getter
|
||||
.register_fn("update_value",
|
||||
|obj: &mut TestStruct, value: i64| obj.0 = value // mutating method
|
||||
);
|
||||
|
||||
let script =
|
||||
"
|
||||
MY_NUMBER.update_value(42);
|
||||
print(MY_NUMBER.value);
|
||||
";
|
||||
|
||||
let ast = engine.compile(script)?;
|
||||
|
||||
let mut scope = Scope::new(); // create custom scope
|
||||
|
||||
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
|
||||
|
||||
// Beware: constant objects can still be modified via a method call!
|
||||
engine.run_ast_with_scope(&mut scope, &ast)?; // prints 42
|
||||
|
||||
// Running the script directly, as below, is less desirable because
|
||||
// the constant 'MY_NUMBER' will be propagated and copied into each usage
|
||||
// during the script optimization step
|
||||
engine.run_with_scope(&mut scope, script)?;
|
||||
```
|
||||
|
||||
|
||||
Caveat – 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.
|
||||
```
|
98
rhai_engine/rhaibook/language/convert.md
Normal file
98
rhai_engine/rhaibook/language/convert.md
Normal file
@ -0,0 +1,98 @@
|
||||
Value Conversions
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Convert Between Integer and Floating-Point
|
||||
------------------------------------------
|
||||
|
||||
| Function | Not available under | From type | To type |
|
||||
| ------------ | :-----------------: | :---------------------------------------: | :-----------------------: |
|
||||
| `to_int` | | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `INT` |
|
||||
| `to_float` | [`no_float`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `FLOAT` |
|
||||
| `to_decimal` | non-[`decimal`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
|
||||
|
||||
That's it; for other conversions, register custom conversion functions.
|
||||
|
||||
```js
|
||||
let x = 42; // 'x' is an integer
|
||||
|
||||
let y = x * 100.0; // integer and floating-point can inter-operate
|
||||
|
||||
let y = x.to_float() * 100.0; // convert integer to floating-point with 'to_float'
|
||||
|
||||
let z = y.to_int() + x; // convert floating-point to integer with 'to_int'
|
||||
|
||||
let d = y.to_decimal(); // convert floating-point to Decimal with 'to_decimal'
|
||||
|
||||
let w = z.to_decimal() + x; // Decimal and integer can inter-operate
|
||||
|
||||
let c = 'X'; // character
|
||||
|
||||
print(`c is '${c}' and its code is ${c.to_int()}`); // prints "c is 'X' and its code is 88"
|
||||
```
|
||||
|
||||
|
||||
Parse String into Number
|
||||
------------------------
|
||||
|
||||
| Function | From type | To type |
|
||||
| ---------------------------------------- | :-------: | :-----------------------: |
|
||||
| `parse_int` | [string] | `INT` |
|
||||
| `parse_int` with radix 2-36 | [string] | `INT` (specified radix) |
|
||||
| `parse_float` (not [`no_float`]) | [string] | `FLOAT` |
|
||||
| `parse_float` ([`no_float`]+[`decimal`]) | [string] | [`Decimal`][rust_decimal] |
|
||||
| `parse_decimal` (requires [`decimal`]) | [string] | [`Decimal`][rust_decimal] |
|
||||
|
||||
```rust
|
||||
let x = parse_float("123.4"); // parse as floating-point
|
||||
x == 123.4;
|
||||
type_of(x) == "f64";
|
||||
|
||||
let x = parse_decimal("123.4"); // parse as Decimal value
|
||||
type_of(x) == "decimal";
|
||||
|
||||
let x = 1234.to_decimal() / 10; // alternate method to create a Decimal value
|
||||
type_of(x) == "decimal";
|
||||
|
||||
let dec = parse_int("42"); // parse as integer
|
||||
dec == 42;
|
||||
type_of(dec) == "i64";
|
||||
|
||||
let dec = parse_int("42", 10); // radix = 10 is the default
|
||||
dec == 42;
|
||||
type_of(dec) == "i64";
|
||||
|
||||
let bin = parse_int("110", 2); // parse as binary (radix = 2)
|
||||
bin == 0b110;
|
||||
type_of(bin) == "i64";
|
||||
|
||||
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
|
||||
hex == 0xab;
|
||||
type_of(hex) == "i64";
|
||||
```
|
||||
|
||||
|
||||
Format Numbers
|
||||
--------------
|
||||
|
||||
| Function | From type | To type | Format |
|
||||
| ----------- | :-------: | :------: | :----------------------------: |
|
||||
| `to_binary` | `INT` | [string] | binary (i.e. only `1` and `0`) |
|
||||
| `to_octal` | `INT` | [string] | octal (i.e. `0` ... `7`) |
|
||||
| `to_hex` | `INT` | [string] | hex (i.e. `0` ... `f`) |
|
||||
|
||||
```rust
|
||||
let x = 0x1234abcd;
|
||||
|
||||
x == 305441741;
|
||||
|
||||
x.to_string() == "305441741";
|
||||
|
||||
x.to_binary() == "10010001101001010101111001101";
|
||||
|
||||
x.to_octal() == "2215125715";
|
||||
|
||||
x.to_hex() == "1234abcd";
|
||||
```
|
70
rhai_engine/rhaibook/language/do.md
Normal file
70
rhai_engine/rhaibook/language/do.md
Normal file
@ -0,0 +1,70 @@
|
||||
Do Loop
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`.
|
||||
|
||||
Like the [`while`] loop, `continue` can be used to skip to the next iteration, by-passing all
|
||||
following statements; `break` can be used to break out of the loop unconditionally.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `do` loops"
|
||||
|
||||
`do` loops can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
do {
|
||||
x -= 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of do loop
|
||||
} while x > 0;
|
||||
|
||||
|
||||
do {
|
||||
x -= 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of do loop
|
||||
} until x == 0;
|
||||
```
|
||||
|
||||
|
||||
Do Expression
|
||||
-------------
|
||||
|
||||
Like Rust, `do` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `do` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = 0;
|
||||
|
||||
// 'do' can be used just like an expression
|
||||
let result = do {
|
||||
if is_magic_number(x) {
|
||||
// if the 'do' loop breaks here, return a specific value
|
||||
break get_magic_result(x);
|
||||
}
|
||||
|
||||
x += 1;
|
||||
|
||||
// ... if the 'do' loop exits here, the return value is ()
|
||||
} until x >= 100;
|
||||
|
||||
if result == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic result = ${result}!`);
|
||||
}
|
||||
```
|
79
rhai_engine/rhaibook/language/doc-comments.md
Normal file
79
rhai_engine/rhaibook/language/doc-comments.md
Normal file
@ -0,0 +1,79 @@
|
||||
Doc-Comments
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Similar to Rust, [comments] starting with `///` (three slashes) or `/**` (two asterisks)
|
||||
are _doc-comments_.
|
||||
|
||||
Doc-comments can only appear in front of [function] definitions, not any other elements.
|
||||
Therefore, doc-comments are not available under [`no_function`].
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
Doc-comments are only supported under the [`metadata`] feature.
|
||||
|
||||
If [`metadata`] is not active, doc-comments are treated as normal [comments].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
/// This is a valid one-line doc-comment
|
||||
fn foo() {}
|
||||
|
||||
/** This is a
|
||||
** valid block
|
||||
** doc-comment
|
||||
**/
|
||||
fn bar(x) {
|
||||
/// Syntax error: this doc-comment is invalid
|
||||
x + 1
|
||||
}
|
||||
|
||||
/** Syntax error: this doc-comment is invalid */
|
||||
let x = 42;
|
||||
|
||||
/// Syntax error: this doc-comment is also invalid
|
||||
{
|
||||
let x = 42;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
~~~admonish tip "Tip: Special cases"
|
||||
|
||||
Long streams of `//////`... and `/*****`... do _NOT_ form doc-comments.
|
||||
This is consistent with popular [comment] block styles for C-like languages.
|
||||
|
||||
```rust
|
||||
/////////////////////////////// <- this is not a doc-comment
|
||||
// This is not a doc-comment // <- this is not a doc-comment
|
||||
/////////////////////////////// <- this is not a doc-comment
|
||||
|
||||
// However, watch out for comment lines starting with '///'
|
||||
|
||||
////////////////////////////////////////// <- this is not a doc-comment
|
||||
/// This, however, IS a doc-comment!!! /// <- doc-comment!
|
||||
////////////////////////////////////////// <- this is not a doc-comment
|
||||
|
||||
/****************************************
|
||||
* *
|
||||
* This is also not a doc-comment block *
|
||||
* so we don't have to put this in *
|
||||
* front of a function. *
|
||||
* *
|
||||
****************************************/
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Using Doc-Comments
|
||||
------------------
|
||||
|
||||
Doc-comments are stored within the script's [`AST`] after compilation.
|
||||
|
||||
The `AST::iter_functions` method provides a `ScriptFnMetadata` instance for each function defined
|
||||
within the script, which includes doc-comments.
|
||||
|
||||
Doc-comments never affect the evaluation of a script nor do they incur significant performance overhead.
|
||||
However, third party tools can take advantage of this information to auto-generate documentation for
|
||||
Rhai script functions.
|
257
rhai_engine/rhaibook/language/dynamic-rust.md
Normal file
257
rhai_engine/rhaibook/language/dynamic-rust.md
Normal file
@ -0,0 +1,257 @@
|
||||
Interop `Dynamic` Data with Rust
|
||||
================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Create a `Dynamic` from Rust Type
|
||||
---------------------------------
|
||||
|
||||
| Rust type<br/>`T: Clone`,<br/>`K: Into<String>` | Unavailable under | Use API |
|
||||
| ------------------------------------------------------------------ | :-----------------------: | ---------------------------- |
|
||||
| `INT` (`i64` or `i32`) | | `value.into()` |
|
||||
| `FLOAT` (`f64` or `f32`) | [`no_float`] | `value.into()` |
|
||||
| [`Decimal`][rust_decimal] (requires [`decimal`]) | | `value.into()` |
|
||||
| `bool` | | `value.into()` |
|
||||
| [`()`] | | `value.into()` |
|
||||
| [`String`][string], [`&str`][string], [`ImmutableString`] | | `value.into()` |
|
||||
| `char` | | `value.into()` |
|
||||
| [`Array`][array] | [`no_index`] | `Dynamic::from_array(value)` |
|
||||
| [`Blob`][BLOB] | [`no_index`] | `Dynamic::from_blob(value)` |
|
||||
| `Vec<T>`, `&[T]`, `Iterator<T>` | [`no_index`] | `value.into()` |
|
||||
| [`Map`][object map] | [`no_object`] | `Dynamic::from_map(value)` |
|
||||
| `HashMap<K, T>`, `HashSet<K>`,<br/>`BTreeMap<K, T>`, `BTreeSet<K>` | [`no_object`] | `value.into()` |
|
||||
| [`INT..INT`][range], [`INT..=INT`][range] | | `value.into()` |
|
||||
| `Rc<RwLock<T>>` or `Arc<Mutex<T>>` | [`no_closure`] | `value.into()` |
|
||||
| [`Instant`][timestamp] | [`no_time`] or [`no_std`] | `value.into()` |
|
||||
| All types (including above) | | `Dynamic::from(value)` |
|
||||
|
||||
|
||||
Type Checking and Casting
|
||||
-------------------------
|
||||
|
||||
~~~admonish tip.side "Tip: `try_cast` and `try_cast_result`"
|
||||
|
||||
The `try_cast` method does not panic but returns `None` upon failure.
|
||||
|
||||
The `try_cast_result` method also does not panic but returns the original value upon failure.
|
||||
~~~
|
||||
|
||||
A [`Dynamic`] value's actual type can be checked via `Dynamic::is`.
|
||||
|
||||
The `cast` method then converts the value into a specific, known type.
|
||||
|
||||
Use `clone_cast` to clone a reference to [`Dynamic`].
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0].clone(); // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
|
||||
|
||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
||||
let value: i64 = item.cast(); // type can also be inferred
|
||||
|
||||
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
|
||||
|
||||
let value = list[0].clone_cast::<i64>(); // use 'clone_cast' on '&Dynamic'
|
||||
let value: i64 = list[0].clone_cast();
|
||||
```
|
||||
|
||||
|
||||
Type Name and Matching Types
|
||||
----------------------------
|
||||
|
||||
The `type_name` method gets the name of the actual type as a static string slice,
|
||||
which can be `match`-ed against.
|
||||
|
||||
This is a very simple and direct way to act on a [`Dynamic`] value based on the actual type of
|
||||
the data value.
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
match item.type_name() { // 'type_name' returns the name of the actual Rust type
|
||||
"()" => ...
|
||||
"i64" => ...
|
||||
"f64" => ...
|
||||
"rust_decimal::Decimal" => ...
|
||||
"core::ops::range::Range<i64>" => ...
|
||||
"core::ops::range::RangeInclusive<i64>" => ...
|
||||
"alloc::string::String" => ...
|
||||
"bool" => ...
|
||||
"char" => ...
|
||||
"rhai::FnPtr" => ...
|
||||
"std::time::Instant" => ...
|
||||
"crate::path::to::module::TestStruct" => ...
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
```admonish warning.small "Always full path name"
|
||||
|
||||
`type_name` always returns the _full_ Rust path name of the type, even when the type
|
||||
has been registered with a friendly name via `Engine::register_type_with_name`.
|
||||
|
||||
This behavior is different from that of the [`type_of`][`type_of()`] function in Rhai.
|
||||
```
|
||||
|
||||
|
||||
Getting a Reference to Data
|
||||
---------------------------
|
||||
|
||||
Use `Dynamic::read_lock` and `Dynamic::write_lock` to get an immutable/mutable reference to the data
|
||||
inside a [`Dynamic`].
|
||||
|
||||
```rust
|
||||
struct TheGreatQuestion {
|
||||
answer: i64
|
||||
}
|
||||
|
||||
let question = TheGreatQuestion { answer: 42 };
|
||||
|
||||
let mut value: Dynamic = Dynamic::from(question);
|
||||
|
||||
let q_ref: &TheGreatQuestion =
|
||||
&*value.read_lock::<TheGreatQuestion>().unwrap();
|
||||
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
|
||||
|
||||
println!("answer = {}", q_ref.answer); // prints 42
|
||||
|
||||
let q_mut: &mut TheGreatQuestion =
|
||||
&mut *value.write_lock::<TheGreatQuestion>().unwrap();
|
||||
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
|
||||
|
||||
q_mut.answer = 0; // mutate value
|
||||
|
||||
let value = value.cast::<TheGreatQuestion>();
|
||||
|
||||
println!("new answer = {}", value.answer); // prints 0
|
||||
```
|
||||
|
||||
~~~admonish question "TL;DR – Why `read_lock` and `write_lock`?"
|
||||
|
||||
As the naming shows, something is _locked_ in order to allow accessing the data within a [`Dynamic`],
|
||||
and that something is a _shared value_ created by capturing variables from [closures].
|
||||
|
||||
Shared values are implemented as `Rc<RefCell<Dynamic>>` (`Arc<RwLock<Dynamic>>` under [`sync`]).
|
||||
|
||||
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
|
||||
no capturing, this API de-sugars to a simple reference cast.
|
||||
|
||||
In other words, there is no locking and reference counting overhead for the vast majority of
|
||||
non-shared values.
|
||||
|
||||
If the value _is_ a shared value, then it is first _locked_ and the returned _lock guard_
|
||||
allows access to the underlying value in the specified type.
|
||||
~~~
|
||||
|
||||
|
||||
Methods and Traits
|
||||
------------------
|
||||
|
||||
The following methods are available when working with [`Dynamic`]:
|
||||
|
||||
| Method | Not available under | Return type | Description |
|
||||
| --------------- | :-------------------------: | :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type_name` | | `&str` | name of the value's type |
|
||||
| `into_shared` | [`no_closure`] | [`Dynamic`] | turn the value into a _shared_ value |
|
||||
| `flatten_clone` | | [`Dynamic`] | clone the value (a _shared_ value, if any, is cloned into a separate copy) |
|
||||
| `flatten` | | [`Dynamic`] | clone the value into a separate copy if it is _shared_ and there are multiple outstanding references, otherwise _shared_ values are turned unshared |
|
||||
| `read_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value for reading |
|
||||
| `write_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value exclusively for writing |
|
||||
| `deep_scan` | | | recursively scan for [`Dynamic`] values (e.g. items inside an [array] or [object map], or [curried arguments][currying] in a [function pointer]) |
|
||||
|
||||
### Constructor instance methods
|
||||
|
||||
| Method | Not available under | Value type | Data type |
|
||||
| ---------------- | :-----------------------: | :---------------------------------------------------------: | :-----------------------: |
|
||||
| `from_bool` | | `bool` | `bool` |
|
||||
| `from_int` | | `INT` | integer number |
|
||||
| `from_float` | [`no_float`] | `FLOAT` | floating-point number |
|
||||
| `from_decimal` | non-[`decimal`] | [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
|
||||
| `from_str` | | `&str` | [string] |
|
||||
| `from_char` | | `char` | [character] |
|
||||
| `from_array` | [`no_index`] | `Vec<T>` | [array] |
|
||||
| `from_blob` | [`no_index`] | `Vec<u8>` | [BLOB] |
|
||||
| `from_map` | [`no_object`] | `Map` | [object map] |
|
||||
| `from_timestamp` | [`no_time`] or [`no_std`] | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | [timestamp] |
|
||||
| `from<T>` | | `T` | [custom type] |
|
||||
|
||||
### Detection methods
|
||||
|
||||
| Method | Not available under | Return type | Description |
|
||||
| -------------- | :-----------------------: | :---------: | ---------------------------------------------------------------------- |
|
||||
| `is<T>` | | `bool` | is the value of type `T`? |
|
||||
| `is_variant` | | `bool` | is the value a trait object (i.e. not one of Rhai's [standard types])? |
|
||||
| `is_read_only` | | `bool` | is the value [constant]? A [constant] value should not be modified. |
|
||||
| `is_shared` | [`no_closure`] | `bool` | is the value _shared_ via a [closure]? |
|
||||
| `is_locked` | [`no_closure`] | `bool` | is the value _shared_ and locked (i.e. currently being read)? |
|
||||
| `is_unit` | | `bool` | is the value [`()`]? |
|
||||
| `is_int` | | `bool` | is the value an integer? |
|
||||
| `is_float` | [`no_float`] | `bool` | is the value a floating-point number? |
|
||||
| `is_decimal` | non-[`decimal`] | `bool` | is the value a [`Decimal`][rust_decimal]? |
|
||||
| `is_bool` | | `bool` | is the value a `bool`? |
|
||||
| `is_char` | | `bool` | is the value a [character]? |
|
||||
| `is_string` | | `bool` | is the value a [string]? |
|
||||
| `is_array` | [`no_index`] | `bool` | is the value an [array]? |
|
||||
| `is_blob` | [`no_index`] | `bool` | is the value a [BLOB]? |
|
||||
| `is_map` | [`no_object`] | `bool` | is the value an [object map]? |
|
||||
| `is_timestamp` | [`no_time`] or [`no_std`] | `bool` | is the value a [timestamp]? |
|
||||
|
||||
|
||||
### Casting methods
|
||||
|
||||
The following methods cast a [`Dynamic`] into a specific type:
|
||||
|
||||
| Method | Not available under | Return type (error is name of actual type if `&str`) |
|
||||
| ------------------------- | :-----------------: | :------------------------------------------------------------------------: |
|
||||
| `cast<T>` | | `T` (panics on failure) |
|
||||
| `try_cast<T>` | | `Option<T>` |
|
||||
| `try_cast_result<T>` | | `Result<T, Dynamic>` |
|
||||
| `clone_cast<T>` | | cloned copy of `T` (panics on failure) |
|
||||
| `as_unit` | | `Result<(), &str>` |
|
||||
| `as_int` | | `Result<INT, &str>` |
|
||||
| `as_float` | [`no_float`] | `Result<FLOAT, &str>` |
|
||||
| `as_decimal` | non-[`decimal`] | [`Result<Decimal, &str>`][rust_decimal] |
|
||||
| `as_bool` | | `Result<bool, &str>` |
|
||||
| `as_char` | | `Result<char, &str>` |
|
||||
| `as_immutable_string_ref` | | [`Result<impl Deref<Target=ImmutableString>, &str>`][`ImmutableString`] |
|
||||
| `as_immutable_string_mut` | | [`Result<impl DerefMut<Target=ImmutableString>, &str>`][`ImmutableString`] |
|
||||
| `as_array_ref` | [`no_index`] | [`Result<impl Deref<Target=Array>, &str>`][array] |
|
||||
| `as_array_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Array>, &str>`][array] |
|
||||
| `as_blob_ref` | [`no_index`] | [`Result<impl Deref<Target=Blob>, &str>`][BLOB] |
|
||||
| `as_blob_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Blob>, &str>`][BLOB] |
|
||||
| `as_map_ref` | [`no_object`] | [`Result<impl Deref<Target=Map>, &str>`][object map] |
|
||||
| `as_map_mut` | [`no_object`] | [`Result<impl DerefMut<Target=Map>, &str>`][object map] |
|
||||
| `into_string` | | `Result<String, &str>` |
|
||||
| `into_immutable_string` | | [`Result<ImmutableString, &str>`][`ImmutableString`] |
|
||||
| `into_array` | [`no_index`] | [`Result<Array, &str>`][array] |
|
||||
| `into_blob` | [`no_index`] | [`Result<Blob, &str>`][BLOB] |
|
||||
| `into_typed_array<T>` | [`no_index`] | `Result<Vec<T>, &str>` |
|
||||
|
||||
### Constructor traits
|
||||
|
||||
The following constructor traits are implemented for [`Dynamic`] where `T: Clone`:
|
||||
|
||||
| Trait | Not available under | Data type |
|
||||
| ------------------------------------------------------------------------------ | :----------------------------: | :-----------------------: |
|
||||
| `From<()>` | | `()` |
|
||||
| `From<INT>` | | integer number |
|
||||
| `From<FLOAT>` | [`no_float`] | floating-point number |
|
||||
| `From<Decimal>` | non-[`decimal`] | [`Decimal`][rust_decimal] |
|
||||
| `From<bool>` | | `bool` |
|
||||
| `From<S: Into<ImmutableString>>`<br/>e.g. `From<String>`, `From<&str>` | | [`ImmutableString`] |
|
||||
| `From<char>` | | [character] |
|
||||
| `From<Vec<T>>` | [`no_index`] | [array] |
|
||||
| `From<&[T]>` | [`no_index`] | [array] |
|
||||
| `From<BTreeMap<K: Into<SmartString>, T>>`<br/>e.g. `From<BTreeMap<String, T>>` | [`no_object`] | [object map] |
|
||||
| `From<BTreeSet<K: Into<SmartString>>>`<br/>e.g. `From<BTreeSet<String>>` | [`no_object`] | [object map] |
|
||||
| `From<HashMap<K: Into<SmartString>, T>>`<br/>e.g. `From<HashMap<String, T>>` | [`no_object`] or [`no_std`] | [object map] |
|
||||
| `From<HashSet<K: Into<SmartString>>>`<br/>e.g. `From<HashSet<String>>` | [`no_object`] or [`no_std`] | [object map] |
|
||||
| `From<FnPtr>` | | [function pointer] |
|
||||
| `From<Instant>` | [`no_time`] or [`no_std`] | [timestamp] |
|
||||
| `From<Rc<RefCell<Dynamic>>>` | [`sync`] or [`no_closure`] | [`Dynamic`] |
|
||||
| `From<Arc<RwLock<Dynamic>>>` ([`sync`]) | non-[`sync`] or [`no_closure`] | [`Dynamic`] |
|
||||
| `FromIterator<X: IntoIterator<Item=T>>` | [`no_index`] | [array] |
|
255
rhai_engine/rhaibook/language/dynamic-tag.md
Normal file
255
rhai_engine/rhaibook/language/dynamic-tag.md
Normal file
@ -0,0 +1,255 @@
|
||||
Dynamic Value Tag
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Each [`Dynamic`] value can contain a _tag_ that is `i32` and can contain any arbitrary data.
|
||||
|
||||
On 32-bit targets, however, the tag is only `i16`.
|
||||
|
||||
The tag defaults to zero.
|
||||
|
||||
```admonish bug.small "Value out of bounds"
|
||||
|
||||
It is an error to set a tag to a value beyond the bounds of `i32` (`i16` on 32-bit targets).
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
x.tag == 0; // tag defaults to zero
|
||||
|
||||
x.tag = 123; // set tag value
|
||||
|
||||
set_tag(x, 123); // 'set_tag' function also works
|
||||
|
||||
x.tag == 123; // get updated tag value
|
||||
|
||||
x.tag() == 123; // method also works
|
||||
|
||||
tag(x) == 123; // function call style also works
|
||||
|
||||
x.tag[3..5] = 2; // tag can be used as a bit-field
|
||||
|
||||
x.tag[3..5] == 2;
|
||||
|
||||
let y = x;
|
||||
|
||||
y.tag == 123; // the tag is copied across assignment
|
||||
|
||||
y.tag = 3000000000; // runtime error: 3000000000 is too large for 'i32'
|
||||
```
|
||||
|
||||
|
||||
Practical Applications
|
||||
----------------------
|
||||
|
||||
Attaching arbitrary information together with a value has a lot of practical uses.
|
||||
|
||||
### Identify code path
|
||||
|
||||
For example, it is easy to attach an ID number to a value to indicate how or why that value is
|
||||
originally set.
|
||||
|
||||
This is tremendously convenient for debugging purposes where it is necessary to figure out which
|
||||
code path a particular value went through.
|
||||
|
||||
After the script is verified, all tag assignment statements can simply be removed.
|
||||
|
||||
```js
|
||||
const ROUTE1 = 1;
|
||||
const ROUTE2 = 2;
|
||||
const ROUTE3 = 3;
|
||||
const ERROR_ROUTE = 9;
|
||||
|
||||
fn some_complex_calculation(x) {
|
||||
let result;
|
||||
|
||||
if some_complex_condition(x) {
|
||||
result = 42;
|
||||
result.tag = ROUTE1; // record route #1
|
||||
} else if some_other_very_complex_condition(x) == 1 {
|
||||
result = 123;
|
||||
result.tag = ROUTE2; // record route #2
|
||||
} else if some_non_understandable_calculation(x) > 0 {
|
||||
result = 0;
|
||||
result.tag = ROUTE3; // record route #3
|
||||
} else {
|
||||
result = -1;
|
||||
result.tag = ERROR_ROUTE; // record error
|
||||
}
|
||||
|
||||
result // this value now contains the tag
|
||||
}
|
||||
|
||||
let my_result = some_complex_calculation(key);
|
||||
|
||||
// The code path that 'my_result' went through is now in its tag.
|
||||
|
||||
// It is now easy to trace how 'my_result' gets its final value.
|
||||
|
||||
print(`Result = ${my_result} and reason = ${my_result.tag}`);
|
||||
```
|
||||
|
||||
### Identify data source
|
||||
|
||||
It is convenient to use the tag value to record the _source_ of a piece of data.
|
||||
|
||||
```js
|
||||
let x = [0, 1, 2, 3, 42, 99, 123];
|
||||
|
||||
// Store the index number of each value into its tag before
|
||||
// filtering out all even numbers, leaving only odd numbers
|
||||
let filtered = x.map(|v, i| { v.tag = i; v }).filter(|v| v.is_odd());
|
||||
|
||||
// The tag now contains the original index position
|
||||
|
||||
for (data, i) in filtered {
|
||||
print(`${i + 1}: Value ${data} from position #${data.tag + 1}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Identify code conditions
|
||||
|
||||
The tag value may also contain a _[bit-field]_ of up to 32 (16 under 32-bit targets) individual bits,
|
||||
recording up to 32 (or 16 under 32-bit targets) logic conditions that contributed to the value.
|
||||
|
||||
Again, after the script is verified, all tag assignment statements can simply be removed.
|
||||
|
||||
```js
|
||||
|
||||
fn some_complex_calculation(x) {
|
||||
let result = x;
|
||||
|
||||
// Check first condition
|
||||
if some_complex_condition() {
|
||||
result += 1;
|
||||
result.tag[0] = true; // Set first bit in bit-field
|
||||
}
|
||||
|
||||
// Check second condition
|
||||
if some_other_very_complex_condition(x) == 1 {
|
||||
result *= 10;
|
||||
result.tag[1] = true; // Set second bit in bit-field
|
||||
}
|
||||
|
||||
// Check third condition
|
||||
if some_non_understandable_calculation(x) > 0 {
|
||||
result -= 42;
|
||||
result.tag[2] = true; // Set third bit in bit-field
|
||||
}
|
||||
|
||||
// Check result
|
||||
if result > 100 {
|
||||
result = 0;
|
||||
result.tag[3] = true; // Set forth bit in bit-field
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
let my_result = some_complex_calculation(42);
|
||||
|
||||
// The tag of 'my_result' now contains a bit-field indicating
|
||||
// the result of each condition.
|
||||
|
||||
// It is now easy to trace how 'my_result' gets its final value.
|
||||
// Use indexing on the tag to get at individual bits.
|
||||
|
||||
print(`Result = ${my_result}`);
|
||||
print(`First condition = ${my_result.tag[0]}`);
|
||||
print(`Second condition = ${my_result.tag[1]}`);
|
||||
print(`Third condition = ${my_result.tag[2]}`);
|
||||
print(`Result check = ${my_result.tag[3]}`);
|
||||
```
|
||||
|
||||
### Return auxillary info
|
||||
|
||||
Sometimes it is useful to return auxillary info from a [function].
|
||||
|
||||
```rust
|
||||
// Verify Bell's Inequality by calculating a norm
|
||||
// and comparing it with a hypotenuse.
|
||||
// https://en.wikipedia.org/wiki/Bell%27s_theorem
|
||||
//
|
||||
// Returns the smaller of the norm or hypotenuse.
|
||||
// Tag is 1 if norm <= hypo, 0 if otherwise.
|
||||
fn bells_inequality(x, y, z) {
|
||||
let norm = sqrt(x ** 2 + y ** 2);
|
||||
let result;
|
||||
|
||||
if norm <= z {
|
||||
result = norm;
|
||||
result.tag = 1;
|
||||
} else {
|
||||
result = z;
|
||||
result.tag = 0;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
let dist = bells_inequality(x, y, z);
|
||||
|
||||
print(`Value = ${dist}`);
|
||||
|
||||
if dist.tag == 1 {
|
||||
print("Local realism maintained! Einstein rules!");
|
||||
} else {
|
||||
print("Spooky action at a distance detected! Einstein will hate this...");
|
||||
}
|
||||
```
|
||||
|
||||
### Poor-man's tuples
|
||||
|
||||
Rust has _tuples_ but Rhai does not (nor does JavaScript in this sense).
|
||||
|
||||
Similar to the JavaScript situation, practical alternatives using Rhai include returning an
|
||||
[object map] or an [array].
|
||||
|
||||
Both of these alternatives, however, incur overhead that may be wasteful when the amount of
|
||||
additional information is small – 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].
|
||||
```
|
77
rhai_engine/rhaibook/language/dynamic.md
Normal file
77
rhai_engine/rhaibook/language/dynamic.md
Normal file
@ -0,0 +1,77 @@
|
||||
Dynamic Values
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A `Dynamic` value can be _any_ type, as long as it implements `Clone`.
|
||||
|
||||
~~~admonish warning.small "`Send + Sync`"
|
||||
|
||||
Under the [`sync`] feature, all types must also be `Send + Sync`.
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 42; // value is an integer
|
||||
|
||||
x = 123.456; // value is now a floating-point number
|
||||
|
||||
x = "hello"; // value is now a string
|
||||
|
||||
x = x.len > 0; // value is now a boolean
|
||||
|
||||
x = [x]; // value is now an array
|
||||
|
||||
x = #{x: x}; // value is now an object map
|
||||
```
|
||||
|
||||
|
||||
Use `type_of()` to Get Value Type
|
||||
---------------------------------
|
||||
|
||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
|
||||
it is usually used to perform type-specific actions based on the actual value's type.
|
||||
|
||||
```js
|
||||
let mystery = get_some_dynamic_value();
|
||||
|
||||
switch type_of(mystery) {
|
||||
"()" => print("Hey, I got the unit () here!"),
|
||||
"i64" => print("Hey, I got an integer here!"),
|
||||
"f64" => print("Hey, I got a float here!"),
|
||||
"decimal" => print("Hey, I got a decimal here!"),
|
||||
"range" => print("Hey, I got an exclusive range here!"),
|
||||
"range=" => print("Hey, I got an inclusive range here!"),
|
||||
"string" => print("Hey, I got a string here!"),
|
||||
"bool" => print("Hey, I got a boolean here!"),
|
||||
"array" => print("Hey, I got an array here!"),
|
||||
"blob" => print("Hey, I got a BLOB here!"),
|
||||
"map" => print("Hey, I got an object map here!"),
|
||||
"Fn" => print("Hey, I got a function pointer here!"),
|
||||
"timestamp" => print("Hey, I got a time-stamp here!"),
|
||||
"TestStruct" => print("Hey, I got the TestStruct custom type here!"),
|
||||
_ => print(`I don't know what this is: ${type_of(mystery)}`)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Parse from JSON
|
||||
---------------
|
||||
|
||||
~~~admonish warning.side "Requires `metadata`"
|
||||
|
||||
`parse_json` is defined in the [`LanguageCorePackage`][built-in packages], which is excluded when using a [raw `Engine`].
|
||||
|
||||
It also requires the [`metadata`] feature; the [`no_index`] and [`no_object`] features must _not_ be set.
|
||||
~~~
|
||||
|
||||
Use `parse_json` to parse a JSON string into a [`Dynamic`] value.
|
||||
|
||||
| JSON type | Rhai type |
|
||||
| :---------------------------: | :----------: |
|
||||
| `number` (no decimal point) | `INT` |
|
||||
| `number` (with decimal point) | `FLOAT` |
|
||||
| `string` | [string] |
|
||||
| `boolean` | `bool` |
|
||||
| `Array` | [array] |
|
||||
| `Object` | [object map] |
|
||||
| `null` | [`()`] |
|
97
rhai_engine/rhaibook/language/eval.md
Normal file
97
rhai_engine/rhaibook/language/eval.md
Normal file
@ -0,0 +1,97 @@
|
||||
`eval` Function
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Or "How to Shoot Yourself in the Foot even Easier"
|
||||
--------------------------------------------------
|
||||
|
||||
Saving the best for last, there is the ever-dreaded... `eval` [function]!
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
fn foo(x) { x += 12; x }
|
||||
|
||||
let script =
|
||||
"
|
||||
let y = x;
|
||||
y += foo(y);
|
||||
x + y
|
||||
";
|
||||
|
||||
let result = eval(script); // <- look, JavaScript, we can also do this!
|
||||
|
||||
result == 42;
|
||||
|
||||
x == 10; // prints 10 - arguments are passed by value
|
||||
y == 32; // prints 32 - variables defined in 'eval' persist!
|
||||
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statements block
|
||||
|
||||
print(z); // <- error: variable 'z' not found
|
||||
|
||||
"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
|
||||
```
|
||||
|
||||
~~~admonish danger.small "`eval` executes inside the current scope!"
|
||||
|
||||
Script segments passed to `eval` execute inside the _current_ [`Scope`], so they can access and modify
|
||||
_everything_, including all [variables] that are visible at that position in code!
|
||||
|
||||
```rust
|
||||
let script = "x += 32";
|
||||
|
||||
let x = 10;
|
||||
eval(script); // variable 'x' is visible!
|
||||
print(x); // prints 42
|
||||
|
||||
// The above is equivalent to:
|
||||
let script = "x += 32";
|
||||
let x = 10;
|
||||
x += 32;
|
||||
print(x);
|
||||
```
|
||||
|
||||
`eval` can also be used to define new [variables] and do other things normally forbidden inside
|
||||
a [function] call.
|
||||
|
||||
```rust
|
||||
let script = "let x = 42";
|
||||
eval(script);
|
||||
print(x); // prints 42
|
||||
```
|
||||
|
||||
Treat it as if the script segments are physically pasted in at the position of the `eval` call.
|
||||
~~~
|
||||
|
||||
~~~admonish warning.small "Cannot define new functions"
|
||||
|
||||
New [functions] cannot be defined within an `eval` call, since [functions] can only be defined at
|
||||
the _global_ level!
|
||||
~~~
|
||||
|
||||
~~~admonish failure.small "`eval` is evil"
|
||||
|
||||
For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil),
|
||||
disable `eval` via [`Engine::disable_symbol`][disable keywords and operators].
|
||||
|
||||
```rust
|
||||
// Disable usage of 'eval'
|
||||
engine.disable_symbol("eval");
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish question.small "Do you regret implementing `eval` in Rhai?"
|
||||
|
||||
Or course we do.
|
||||
|
||||
Having the possibility of an `eval` call disrupts any predictability in the Rhai script,
|
||||
thus disabling a large number of optimizations.
|
||||
~~~
|
||||
|
||||
```admonish question.small "Why did it then???!!!"
|
||||
|
||||
Brendan Eich puts it well: "it is just too easy to implement." _(source wanted)_
|
||||
```
|
109
rhai_engine/rhaibook/language/fn-anon.md
Normal file
109
rhai_engine/rhaibook/language/fn-anon.md
Normal file
@ -0,0 +1,109 @@
|
||||
Anonymous Functions
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Many functions in the standard API expect [function pointer] as parameters.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
// Function 'double' defined here - used only once
|
||||
fn double(x) { 2 * x }
|
||||
|
||||
// Function 'square' defined here - again used only once
|
||||
fn square(x) { x * x }
|
||||
|
||||
let x = [1, 2, 3, 4, 5];
|
||||
|
||||
// Pass a function pointer to 'double'
|
||||
let y = x.map(double);
|
||||
|
||||
// Pass a function pointer to 'square' using Fn(...) notation
|
||||
let z = y.map(Fn("square"));
|
||||
```
|
||||
|
||||
Sometimes it gets tedious to define separate [functions] only to dispatch them via single [function pointers] –
|
||||
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
|
||||
};
|
||||
```
|
194
rhai_engine/rhaibook/language/fn-closure.md
Normal file
194
rhai_engine/rhaibook/language/fn-closure.md
Normal file
@ -0,0 +1,194 @@
|
||||
Closures
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "Tip: `is_shared`"
|
||||
|
||||
Use `Dynamic::is_shared` to check whether a particular [`Dynamic`] value is shared.
|
||||
~~~
|
||||
|
||||
Although [anonymous functions] de-sugar to standard function definitions, they differ from standard
|
||||
functions because they can _captures_ [variables] that are not defined within the current scope,
|
||||
but are instead defined in an external scope – i.e. where the [anonymous function] is created.
|
||||
|
||||
All [variables] that are accessible during the time the [anonymous function] is created are
|
||||
automatically captured when they are used, as long as they are not shadowed by local [variables]
|
||||
defined within the function's.
|
||||
|
||||
The captured [variables] are automatically converted into **reference-counted shared values**
|
||||
(`Rc<RefCell<Dynamic>>`, or `Arc<RwLock<Dynamic>>` under [`sync`]).
|
||||
|
||||
Therefore, similar to closures in many languages, these captured shared values persist through
|
||||
reference counting, and may be read or modified even after the [variables] that hold them go out of
|
||||
scope and no longer exist.
|
||||
|
||||
```admonish tip.small "Tip: Disable closures"
|
||||
|
||||
Capturing external [variables] can be turned off via the [`no_closure`] feature.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 1; // a normal variable
|
||||
|
||||
x.is_shared() == false;
|
||||
|
||||
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
|
||||
|
||||
x.is_shared() == true; // 'x' is now a shared value!
|
||||
|
||||
f.call(2) == 3; // 1 + 2 == 3
|
||||
|
||||
x = 40; // changing 'x'...
|
||||
|
||||
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
|
||||
|
||||
// The above de-sugars into something like this:
|
||||
|
||||
fn anon_0001(x, y) { x + y } // parameter 'x' is inserted
|
||||
|
||||
make_shared(x); // convert variable 'x' into a shared value
|
||||
|
||||
let f = anon_0001.curry(x); // shared 'x' is curried
|
||||
```
|
||||
|
||||
|
||||
~~~admonish bug "Beware: Captured Variables are Truly Shared"
|
||||
|
||||
The example below is a typical tutorial sample for many languages to illustrate the traps
|
||||
that may accompany capturing external [variables] in closures.
|
||||
|
||||
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is
|
||||
ever only _one_ captured [variable], and all ten closures capture the _same_ [variable].
|
||||
|
||||
```rust
|
||||
let list = [];
|
||||
|
||||
for i in 0..10 {
|
||||
list.push(|| print(i)); // the for loop variable 'i' is captured
|
||||
}
|
||||
|
||||
list.len() == 10; // 10 closures stored in the array
|
||||
|
||||
list[0].type_of() == "Fn"; // make sure these are closures
|
||||
|
||||
for f in list {
|
||||
f.call(); // all references to 'i' are the same variable!
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Be Careful to Prevent Data Races"
|
||||
|
||||
Rust does not have data races, but that doesn't mean Rhai doesn't.
|
||||
|
||||
Avoid performing a method call on a captured shared [variable] (which essentially takes a
|
||||
mutable reference to the shared object) while using that same [variable] as a parameter
|
||||
in the method call – 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.
|
||||
```
|
31
rhai_engine/rhaibook/language/fn-curry.md
Normal file
31
rhai_engine/rhaibook/language/fn-curry.md
Normal file
@ -0,0 +1,31 @@
|
||||
Function Pointer Currying
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
It is possible to _curry_ a [function pointer] by providing partial (or all) arguments.
|
||||
|
||||
Currying is done via the `curry` keyword and produces a new [function pointer] which carries
|
||||
the curried arguments.
|
||||
|
||||
When the curried [function pointer] is called, the curried arguments are inserted starting from the _left_.
|
||||
|
||||
The actual call arguments should be reduced by the number of curried arguments.
|
||||
|
||||
```rust
|
||||
fn mul(x, y) { // function with two parameters
|
||||
x * y
|
||||
}
|
||||
|
||||
let func = mul; // <- de-sugars to 'Fn("mul")'
|
||||
|
||||
func.call(21, 2) == 42; // two arguments are required for 'mul'
|
||||
|
||||
let curried = func.curry(21); // currying produces a new function pointer which
|
||||
// carries 21 as the first argument
|
||||
|
||||
let curried = curry(func, 21); // function-call style also works
|
||||
|
||||
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
|
||||
// only one argument is now required
|
||||
```
|
39
rhai_engine/rhaibook/language/fn-metadata.md
Normal file
39
rhai_engine/rhaibook/language/fn-metadata.md
Normal file
@ -0,0 +1,39 @@
|
||||
Get Functions Metadata in Scripts
|
||||
=================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The built-in function `get_fn_metadata_list` returns an [array] of [object maps], each containing
|
||||
the metadata of one script-defined [function] in scope.
|
||||
|
||||
`get_fn_metadata_list` is defined in the [`LanguageCorePackage`][built-in packages], which is
|
||||
excluded when using a [raw `Engine`].
|
||||
|
||||
`get_fn_metadata_list` has a few versions taking different parameters:
|
||||
|
||||
| Signature | Description |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `get_fn_metadata_list()` | returns an [array] for _all_ script-defined [functions] |
|
||||
| `get_fn_metadata_list(name)` | returns an [array] containing all script-defined [functions] matching a specified name |
|
||||
| `get_fn_metadata_list(name, params)` | returns an [array] containing all script-defined [functions] matching a specified name and accepting the specified number of parameters |
|
||||
|
||||
[Functions] from the following sources are returned, in order:
|
||||
|
||||
1. Encapsulated script environment (e.g. when loading a [module] from a script file),
|
||||
2. Current script,
|
||||
3. [Modules] registered via `Engine::register_global_module` (latest registrations first)
|
||||
4. [Modules] imported via the [`import`] statement (latest imports first),
|
||||
5. [Modules] added via `Engine::register_static_module` (latest registrations first)
|
||||
|
||||
The return value is an [array] of [object maps] (so `get_fn_metadata_list` is also not available under
|
||||
[`no_index`] or [`no_object`]), containing the following fields.
|
||||
|
||||
| Field | Type | Optional? | Description |
|
||||
| -------------- | :------------------------: | :-------: | ----------------------------------------------------------------------------------- |
|
||||
| `namespace` | [string] | **yes** | the module _namespace_ if the [function] is defined within a [module] |
|
||||
| `access` | [string] | no | `"public"` if the function is public,<br/>`"private"` if it is [private][`private`] |
|
||||
| `name` | [string] | no | [function] name |
|
||||
| `params` | [array] of [strings] | no | parameter names |
|
||||
| `this_type` | [string](strings-chars.md) | **yes** | restrict the type of `this` if the [function] is a [method] |
|
||||
| `is_anonymous` | `bool` | no | is this [function] an [anonymous function]? |
|
||||
| `comments` | [array] of [strings] | **yes** | [doc-comments], if any, one per line |
|
197
rhai_engine/rhaibook/language/fn-method.md
Normal file
197
rhai_engine/rhaibook/language/fn-method.md
Normal file
@ -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;
|
||||
```
|
124
rhai_engine/rhaibook/language/fn-namespaces.md
Normal file
124
rhai_engine/rhaibook/language/fn-namespaces.md
Normal file
@ -0,0 +1,124 @@
|
||||
Function Namespaces
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Each Function is a Separate Compilation Unit
|
||||
--------------------------------------------
|
||||
|
||||
[Functions] in Rhai are _pure_ and they form individual _compilation units_.
|
||||
|
||||
This means that individual [functions] can be separated, exported, re-grouped, imported, and
|
||||
generally mix-'n-matched with other completely unrelated scripts.
|
||||
|
||||
For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators)
|
||||
allow combining all [functions] in one [`AST`] into another, forming a new, unified, group of [functions].
|
||||
|
||||
|
||||
Namespace Types
|
||||
---------------
|
||||
|
||||
In general, there are two main types of _namespaces_ where [functions] are looked up:
|
||||
|
||||
| Namespace | Quantity | Source | Lookup | Sub-modules? | Variables? |
|
||||
| --------- | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | :----------: | :--------: |
|
||||
| Global | one | <ol><li>[`AST`] being evaluated</li><li>`Engine::register_XXX` API</li><li>global registered [modules]</li><li>[functions] in [imported][`import`] [modules] marked _global_</li><li>[functions] in registered static [modules] marked _global_</li></ol> | simple name | ignored | ignored |
|
||||
| Module | many | <ol><li>[Module] registered via `Engine::register_static_module`</li><li>[Module] loaded via [`import`] statement</li></ol> | namespace-qualified name | yes | yes |
|
||||
|
||||
### Module Namespaces
|
||||
|
||||
There can be multiple [module] namespaces at any time during a script evaluation, usually loaded via
|
||||
the [`import`] statement.
|
||||
|
||||
_Static_ [module] namespaces can also be registered into an [`Engine`] via `Engine::register_static_module`.
|
||||
|
||||
[Functions] and [variables] in module namespaces are isolated and encapsulated within their own environments.
|
||||
|
||||
They must be called or accessed in a _namespace-qualified_ manner.
|
||||
|
||||
```js
|
||||
import "my_module" as m; // new module namespace 'm' created via 'import'
|
||||
|
||||
let x = m::calc_result(); // namespace-qualified function call
|
||||
|
||||
let y = m::MY_NUMBER; // namespace-qualified variable/constant access
|
||||
|
||||
let z = calc_result(); // <- error: function 'calc_result' not found
|
||||
// in global namespace!
|
||||
```
|
||||
|
||||
### Global Namespace
|
||||
|
||||
There is one _global_ namespace for every [`Engine`], which includes (in the following search order):
|
||||
|
||||
* all [functions] defined in the [`AST`] currently being evaluated,
|
||||
|
||||
* all native Rust functions and iterators registered via the `Engine::register_XXX` API,
|
||||
|
||||
* all functions and iterators defined in global [modules] that are registered into the [`Engine`]
|
||||
via `register_global_module`,
|
||||
|
||||
* functions defined in [modules] registered into the [`Engine`] via `register_static_module` that
|
||||
are specifically marked for exposure to the global namespace (e.g. via the `#[rhai(global)]`
|
||||
attribute in a [plugin module]).
|
||||
|
||||
* [functions] defined in [imported][`import`] [modules] that are specifically marked for exposure to
|
||||
the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]).
|
||||
|
||||
Anywhere in a Rhai script, when a function call is made, the function is searched within the
|
||||
global namespace, in the above search order.
|
||||
|
||||
Therefore, function calls in Rhai are _late_ bound – 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();
|
||||
```
|
96
rhai_engine/rhaibook/language/fn-parent-scope.md
Normal file
96
rhai_engine/rhaibook/language/fn-parent-scope.md
Normal file
@ -0,0 +1,96 @@
|
||||
Call a Function Within the Caller's Scope
|
||||
=========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Peeking Out of The Pure Box
|
||||
---------------------------
|
||||
|
||||
```admonish info.side "Only scripts"
|
||||
|
||||
This is only meaningful for _scripted_ [functions].
|
||||
|
||||
Native Rust functions can never access any [`Scope`].
|
||||
```
|
||||
|
||||
Rhai [functions] are _pure_, meaning that they depend on on their arguments and have no access to
|
||||
the calling environment.
|
||||
|
||||
When a [function] accesses a [variable] that is not defined within that [function]'s [`Scope`],
|
||||
it raises an evaluation error.
|
||||
|
||||
It is possible, through a special syntax, to actually run the [function] call within the [`Scope`]
|
||||
of the parent caller – 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**.
|
||||
```
|
310
rhai_engine/rhaibook/language/fn-ptr.md
Normal file
310
rhai_engine/rhaibook/language/fn-ptr.md
Normal file
@ -0,0 +1,310 @@
|
||||
Function Pointers
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish question.side "Trivia"
|
||||
|
||||
A function pointer simply stores the _name_ of the [function] as a [string].
|
||||
```
|
||||
|
||||
It is possible to store a _function pointer_ in a variable just like a normal value.
|
||||
|
||||
A function pointer is created via the `Fn` [function], which takes a [string] parameter.
|
||||
|
||||
Call a function pointer via the `call` method.
|
||||
|
||||
|
||||
Short-Hand Notation
|
||||
-------------------
|
||||
|
||||
```admonish warning.side "Not for native"
|
||||
|
||||
Native Rust functions cannot use this short-hand notation.
|
||||
```
|
||||
|
||||
Having to write `Fn("foo")` in order to create a function pointer to the [function] `foo` is a chore,
|
||||
so there is a short-hand available.
|
||||
|
||||
A function pointer to any _script-defined_ [function] _within the same script_ can be obtained simply
|
||||
by referring to the [function's][function] name.
|
||||
|
||||
```rust
|
||||
fn foo() { ... } // function definition
|
||||
|
||||
let f = foo; // function pointer to 'foo'
|
||||
|
||||
let f = Fn("foo"); // <- the above is equivalent to this
|
||||
|
||||
let g = bar; // error: variable 'bar' not found
|
||||
```
|
||||
|
||||
The short-hand notation is particularly useful when passing [functions] as [closure] arguments.
|
||||
|
||||
```rust
|
||||
fn is_even(n) { n % 2 == 0 }
|
||||
|
||||
let array = [1, 2, 3, 4, 5];
|
||||
|
||||
array.filter(is_even);
|
||||
|
||||
array.filter(Fn("is_even")); // <- the above is equivalent to this
|
||||
|
||||
array.filter(|n| n % 2 == 0); // <- ... or this
|
||||
```
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following standard methods (mostly defined in the [`BasicFnPackage`][built-in packages] but
|
||||
excluded when using a [raw `Engine`]) operate on function pointers.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ |
|
||||
| `name` method and property | _none_ | returns the name of the [function] encapsulated by the function pointer |
|
||||
| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? Not available under [`no_function`]. |
|
||||
| `call` | _arguments_ | calls the [function] matching the function pointer's name with the _arguments_ |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
fn foo(x) { 41 + x }
|
||||
|
||||
let func = Fn("foo"); // use the 'Fn' function to create a function pointer
|
||||
|
||||
let func = foo; // <- short-hand: equivalent to 'Fn("foo")'
|
||||
|
||||
print(func); // prints 'Fn(foo)'
|
||||
|
||||
let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
|
||||
|
||||
func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
|
||||
|
||||
func.name == "foo";
|
||||
|
||||
func.call(1) == 42; // call a function pointer with the 'call' method
|
||||
|
||||
foo(1) == 42; // <- the above de-sugars to this
|
||||
|
||||
call(func, 1); // normal function call style also works for 'call'
|
||||
|
||||
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
||||
|
||||
len.call("hello") == 5;
|
||||
|
||||
let fn_name = "hello"; // the function name does not have to exist yet
|
||||
|
||||
let hello = Fn(fn_name + "_world");
|
||||
|
||||
hello.call(0); // error: function not found - 'hello_world (i64)'
|
||||
```
|
||||
|
||||
|
||||
```admonish warning "Not First-Class Functions"
|
||||
|
||||
Beware that function pointers are _not_ first-class functions.
|
||||
|
||||
They are _syntactic sugar_ only, capturing only the _name_ of a [function] to call.
|
||||
They do not hold the actual [functions].
|
||||
|
||||
The actual [function] must be defined in the appropriate [namespace][function namespace]
|
||||
for the call to succeed.
|
||||
```
|
||||
|
||||
~~~admonish warning "Global Namespace Only"
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
|
||||
|
||||
They can only refer to [functions] within the global [namespace][function namespace].
|
||||
|
||||
```js
|
||||
import "foo" as f; // assume there is 'f::do_work()'
|
||||
|
||||
f::do_work(); // works!
|
||||
|
||||
let p = Fn("f::do_work"); // error: invalid function name
|
||||
|
||||
fn do_work_now() { // call it from a local function
|
||||
f::do_work();
|
||||
}
|
||||
|
||||
let p = Fn("do_work_now");
|
||||
|
||||
p.call(); // works!
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Dynamic Dispatch
|
||||
----------------
|
||||
|
||||
The purpose of function pointers is to enable rudimentary _dynamic dispatch_, meaning to determine,
|
||||
at runtime, which function to call among a group.
|
||||
|
||||
Although it is possible to simulate dynamic dispatch via a number and a large
|
||||
[`if-then-else-if`][`if`] statement, using function pointers significantly simplifies the code.
|
||||
|
||||
```rust
|
||||
let x = some_calculation();
|
||||
|
||||
// These are the functions to call depending on the value of 'x'
|
||||
fn method1(x) { ... }
|
||||
fn method2(x) { ... }
|
||||
fn method3(x) { ... }
|
||||
|
||||
// Traditional - using decision variable
|
||||
let func = sign(x);
|
||||
|
||||
// Dispatch with if-statement
|
||||
if func == -1 {
|
||||
method1(42);
|
||||
} else if func == 0 {
|
||||
method2(42);
|
||||
} else if func == 1 {
|
||||
method3(42);
|
||||
}
|
||||
|
||||
// Using pure function pointer
|
||||
let func = if x < 0 {
|
||||
method1
|
||||
} else if x == 0 {
|
||||
method2
|
||||
} else if x > 0 {
|
||||
method3
|
||||
};
|
||||
|
||||
// Dynamic dispatch
|
||||
func.call(42);
|
||||
|
||||
// Using functions map
|
||||
let map = [ method1, method2, method3 ];
|
||||
|
||||
let func = sign(x) + 1;
|
||||
|
||||
// Dynamic dispatch
|
||||
map[func].call(42);
|
||||
```
|
||||
|
||||
|
||||
Bind the `this` Pointer
|
||||
-----------------------
|
||||
|
||||
When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch
|
||||
to a function call while binding the object in the method call to the `this` pointer of the function.
|
||||
|
||||
To achieve this, pass the function pointer as the _first_ argument to `call`:
|
||||
|
||||
```rust
|
||||
fn add(x) { // define function which uses 'this'
|
||||
this += x;
|
||||
}
|
||||
|
||||
let func = add; // function pointer to 'add'
|
||||
|
||||
func.call(1); // error: 'this' pointer is not bound
|
||||
|
||||
let x = 41;
|
||||
|
||||
func.call(x, 1); // error: function 'add (i64, i64)' not found
|
||||
|
||||
call(func, x, 1); // error: function 'add (i64, i64)' not found
|
||||
|
||||
x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func'
|
||||
|
||||
x == 42;
|
||||
```
|
||||
|
||||
Beware that this only works for [_method-call_](fn-method.md) style.
|
||||
Normal function-call style cannot bind the `this` pointer (for syntactic reasons).
|
||||
|
||||
Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].
|
||||
|
||||
|
||||
Call a Function Pointer within a Rust Function (as a Callback)
|
||||
--------------------------------------------------------------
|
||||
|
||||
It is completely normal to register a Rust function with an [`Engine`] that takes parameters
|
||||
whose types are function pointers. The Rust type in question is `rhai::FnPtr`.
|
||||
|
||||
A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function
|
||||
to call in script. Therefore, the script's _execution context_ (i.e. [`NativeCallContext`])
|
||||
is needed in order to call a function pointer.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FnPtr, NativeCallContext};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// A function expecting a callback in form of a function pointer.
|
||||
fn super_call(context: NativeCallContext, callback: FnPtr, value: i64)
|
||||
-> Result<String, Box<EvalAltResult>>
|
||||
{
|
||||
// Use 'FnPtr::call_within_context' to call the function pointer using the call context.
|
||||
// 'FnPtr::call_within_context' automatically casts to the required result type.
|
||||
callback.call_within_context(&context, (value,))
|
||||
// ^^^^^^^^ arguments passed in tuple
|
||||
}
|
||||
|
||||
engine.register_fn("super_call", super_call);
|
||||
```
|
||||
|
||||
|
||||
Call a Function Pointer Directly
|
||||
--------------------------------
|
||||
|
||||
The `FnPtr::call` method allows the function pointer to be called directly on any [`Engine`] and
|
||||
[`AST`], making it possible to reuse the `FnPtr` data type in may different calls and scripting
|
||||
environments.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FnPtr};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile script to AST
|
||||
let ast = engine.compile(
|
||||
r#"
|
||||
let test = "hello";
|
||||
|x| test + x // this creates a closure
|
||||
"#)?;
|
||||
|
||||
// Save the closure together with captured variables
|
||||
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
||||
|
||||
// 'f' captures: the Engine, the AST, and the closure
|
||||
let f = move |x: i64| -> Result<String, _> {
|
||||
fn_ptr.call(&engine, &ast, (x,))
|
||||
};
|
||||
|
||||
// 'f' can be called like a normal function
|
||||
let result = f(42)?;
|
||||
|
||||
result == "hello42";
|
||||
```
|
||||
|
||||
|
||||
Bind to a native Rust Function
|
||||
------------------------------
|
||||
|
||||
It is also possible to create a function pointer that binds to a native Rust function or a Rust closure.
|
||||
|
||||
The signature of the native Rust function takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: NativeCallContext, args: &mut [&mut Dynamic])
|
||||
> -> Result<Dynamic, Box<EvalAltResult>> + 'static
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------------: | ----------------------------------------------- |
|
||||
| `context` | [`NativeCallContext`] | mutable reference to the current _call context_ |
|
||||
| `args` | `&mut [&mut Dynamic]` | mutable reference to list of arguments |
|
||||
|
||||
When such a function pointer is used in script, the native Rust function will be called
|
||||
with the arguments provided.
|
||||
|
||||
The Rust function should check whether the appropriate number of arguments have been passed.
|
84
rhai_engine/rhaibook/language/for.md
Normal file
84
rhai_engine/rhaibook/language/for.md
Normal file
@ -0,0 +1,84 @@
|
||||
For Loop
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Iterating through a numeric [range] or an [array], or any type with a registered [type iterator],
|
||||
is provided by the `for` ... `in` loop.
|
||||
|
||||
There are two alternative syntaxes, one including a counter variable:
|
||||
|
||||
> `for` _variable_ `in` _expression_ `{` ... `}`
|
||||
>
|
||||
> `for (` _variable_ `,` _counter_ `)` `in` _expression_ `{` ... `}`
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `for` loops"
|
||||
|
||||
`for` loops can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
Break or Continue
|
||||
-----------------
|
||||
|
||||
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements.
|
||||
|
||||
`break` can be used to break out of the loop unconditionally.
|
||||
|
||||
|
||||
For Expression
|
||||
--------------
|
||||
|
||||
Unlike Rust, `for` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `for` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
|
||||
|
||||
// 'for' can be used just like an expression
|
||||
let index = for (item, count) in a {
|
||||
// if the 'for' loop breaks here, return a specific value
|
||||
switch item.type_of() {
|
||||
"i64" if item.is_even => break count,
|
||||
"f64" if item.to_int().is_even => break count,
|
||||
}
|
||||
|
||||
// ... if the 'for' loop exits here, the return value is ()
|
||||
};
|
||||
|
||||
if index == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic number found at index ${index}!`);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Counter Variable
|
||||
----------------
|
||||
|
||||
The counter variable, if specified, starts from zero, incrementing upwards.
|
||||
|
||||
```js , no_run
|
||||
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
|
||||
|
||||
// Loop through the array
|
||||
for (item, count) in a {
|
||||
if x.type_of() == "string" {
|
||||
continue; // skip to the next iteration
|
||||
}
|
||||
|
||||
// 'item' contains a copy of each element during each iteration
|
||||
// 'count' increments (starting from zero) for each iteration
|
||||
print(`Item #${count + 1} = ${item}`);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
```
|
222
rhai_engine/rhaibook/language/functions.md
Normal file
222
rhai_engine/rhaibook/language/functions.md
Normal file
@ -0,0 +1,222 @@
|
||||
Functions
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai supports defining functions in script via the `fn` keyword, with a syntax that is very similar to Rust without types.
|
||||
|
||||
Valid function names are the same as valid [variable] names.
|
||||
|
||||
```rust
|
||||
fn add(x, y) {
|
||||
x + y
|
||||
}
|
||||
|
||||
fn sub(x, y,) { // trailing comma in parameters list is OK
|
||||
x - y
|
||||
}
|
||||
|
||||
add(2, 3) == 5;
|
||||
|
||||
sub(2, 3,) == -1; // trailing comma in arguments list is OK
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Disable functions"
|
||||
|
||||
Defining functions can be disabled via the [`no_function`] feature.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: `is_def_fn`"
|
||||
|
||||
Use `is_def_fn` (not available under [`no_function`]) to detect if a Rhai function is defined
|
||||
(and therefore callable) based on its name and the number of parameters (_arity_).
|
||||
|
||||
```rust
|
||||
fn foo(x) { x + 1 }
|
||||
|
||||
is_def_fn("foo", 1) == true;
|
||||
|
||||
is_def_fn("foo", 0) == false;
|
||||
|
||||
is_def_fn("foo", 2) == false;
|
||||
|
||||
is_def_fn("bar", 1) == false;
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Implicit Return
|
||||
---------------
|
||||
|
||||
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is
|
||||
_always_ the block's return value regardless of whether it is terminated with a semicolon `;`.
|
||||
This is different from Rust.
|
||||
|
||||
```rust
|
||||
fn add(x, y) { // implicit return:
|
||||
x + y; // value of the last statement (no need for ending semicolon)
|
||||
// is used as the return value
|
||||
}
|
||||
|
||||
fn add2(x) {
|
||||
return x + 2; // explicit return
|
||||
}
|
||||
|
||||
add(2, 3) == 5;
|
||||
|
||||
add2(42) == 44;
|
||||
```
|
||||
|
||||
|
||||
Global Definitions Only
|
||||
-----------------------
|
||||
|
||||
Functions can only be defined at the global level, never inside a block or another function.
|
||||
|
||||
Again, this is different from Rust.
|
||||
|
||||
```rust
|
||||
// Global level is OK
|
||||
fn add(x, y) {
|
||||
x + y
|
||||
}
|
||||
|
||||
// The following will not compile
|
||||
fn do_addition(x) {
|
||||
fn add_y(n) { // <- syntax error: cannot define inside another function
|
||||
n + y
|
||||
}
|
||||
|
||||
add_y(x)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
No Access to External Scope
|
||||
---------------------------
|
||||
|
||||
Functions are not _closures_. They do not capture the calling environment and can only access their
|
||||
own parameters.
|
||||
|
||||
They cannot access [variables] external to the function itself.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
fn foo() {
|
||||
x // <- error: variable 'x' not found
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
But Can Call Other Functions and Access Modules
|
||||
-----------------------------------------------
|
||||
|
||||
All functions in the same [`AST`] can call each other.
|
||||
|
||||
```rust
|
||||
fn foo(x) { // function defined in the global namespace
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn bar(x) {
|
||||
foo(x) // ok! function 'foo' can be called
|
||||
}
|
||||
```
|
||||
|
||||
In addition, [modules] [imported][`import`] at global level can be accessed.
|
||||
|
||||
```js
|
||||
import "hello" as hey;
|
||||
import "world" as woo;
|
||||
|
||||
{
|
||||
import "x" as xyz; // <- this module is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
hey::process(x); // ok! imported module 'hey' can be accessed
|
||||
|
||||
print(woo::value); // ok! imported module 'woo' can be accessed
|
||||
|
||||
xyz::do_work(); // <- error: module 'xyz' not found
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Automatic Global Module
|
||||
-----------------------
|
||||
|
||||
When a [constant] is declared at global scope, it is added to a special [module] called [`global`].
|
||||
|
||||
Functions can access those [constants] via the special [`global`] [module].
|
||||
|
||||
Naturally, the automatic [`global`] [module] is not available under [`no_function`] nor [`no_module`].
|
||||
|
||||
```rust
|
||||
const CONSTANT = 42; // this constant is automatically added to 'global'
|
||||
|
||||
let hello = 1; // variables are not added to 'global'
|
||||
|
||||
{
|
||||
const INNER = 0; // this constant is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
x * global::hello // <- error: variable 'hello' not found in 'global'
|
||||
|
||||
x * global::CONSTANT // ok! 'CONSTANT' exists in 'global'
|
||||
|
||||
x * global::INNER // <- error: constant 'INNER' not found in 'global'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Use Before Definition Allowed
|
||||
-----------------------------
|
||||
|
||||
Unlike C/C++, functions in Rhai can be defined _anywhere_ at global level.
|
||||
|
||||
A function does not need to be defined prior to being used in a script; a statement in the script
|
||||
can freely call a function defined afterwards.
|
||||
|
||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||
|
||||
```rust
|
||||
let x = foo(41); // <- I can do this!
|
||||
|
||||
fn foo(x) { // <- define 'foo' after use
|
||||
x + 1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Arguments are Passed by Value
|
||||
-----------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. they can be of any types).
|
||||
Therefore, functions with the same name and same _number_ of parameters are equivalent.
|
||||
|
||||
All arguments are passed by _value_, so all Rhai script-defined functions are _pure_
|
||||
(i.e. they never modify their arguments).
|
||||
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
|
||||
change(x);
|
||||
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
```admonish warning.small "Rhai functions are pure"
|
||||
|
||||
The only possibility for a Rhai script-defined function to modify an external variable is
|
||||
via the [`this`](fn-method.md) pointer.
|
||||
```
|
42
rhai_engine/rhaibook/language/global.md
Normal file
42
rhai_engine/rhaibook/language/global.md
Normal file
@ -0,0 +1,42 @@
|
||||
Automatic Global Module
|
||||
=======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
When a [constant] is declared at global scope, it is added to a special [module] called `global`.
|
||||
|
||||
[Functions] can access those [constants] via the special `global` [module].
|
||||
|
||||
Naturally, the automatic `global` [module] is not available under [`no_function`] nor [`no_module`].
|
||||
|
||||
```rust
|
||||
const CONSTANT = 42; // this constant is automatically added to 'global'
|
||||
|
||||
{
|
||||
const INNER = 0; // this constant is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
x *= global::CONSTANT; // ok! 'CONSTANT' exists in 'global'
|
||||
|
||||
x * global::INNER // <- error: constant 'INNER' not found in 'global'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Override `global`
|
||||
-----------------
|
||||
|
||||
It is possible to _override_ the automatic global [module] by [importing][`import`] another [module]
|
||||
under the name `global`.
|
||||
|
||||
```rust
|
||||
import "foo" as global; // import a module as 'global'
|
||||
|
||||
const CONSTANT = 42; // this constant is NOT added to 'global'
|
||||
|
||||
fn foo(x) {
|
||||
global::CONSTANT // <- error: constant 'CONSTANT' not found in 'global'
|
||||
}
|
||||
```
|
92
rhai_engine/rhaibook/language/if.md
Normal file
92
rhai_engine/rhaibook/language/if.md
Normal file
@ -0,0 +1,92 @@
|
||||
If Statement
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`if` statements follow C syntax.
|
||||
|
||||
```rust
|
||||
if foo(x) {
|
||||
print("It's true!");
|
||||
} else if bar == baz {
|
||||
print("It's true again!");
|
||||
} else if baz.is_foo() {
|
||||
print("Yet again true.");
|
||||
} else if foo(bar - baz) {
|
||||
print("True again... this is getting boring.");
|
||||
} else {
|
||||
print("It's finally false!");
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish warning.small "Braces are mandatory"
|
||||
|
||||
Unlike C, the condition expression does _not_ need to be enclosed in parentheses `(`...`)`, but all
|
||||
branches of the `if` statement must be enclosed within braces `{`...`}`, even when there is only
|
||||
one statement inside the branch.
|
||||
|
||||
Like Rust, there is no ambiguity regarding which `if` clause a branch belongs to.
|
||||
|
||||
```rust
|
||||
// Rhai is not C!
|
||||
if (decision) print(42);
|
||||
// ^ syntax error, expecting '{'
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
If Expression
|
||||
-------------
|
||||
|
||||
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional
|
||||
operators in other C-like languages.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `if` expressions"
|
||||
|
||||
`if` expressions can be disabled via [`Engine::set_allow_if_expression`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2;
|
||||
let x = 1 + if decision { 42 } else { 123 } / 2;
|
||||
x == 22;
|
||||
|
||||
let x = if decision { 42 }; // no else branch defaults to '()'
|
||||
x == ();
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Statement before expression"
|
||||
|
||||
Beware that, like Rust, `if` is parsed primarily as a statement where it makes sense.
|
||||
This is to avoid surprises.
|
||||
|
||||
```rust
|
||||
fn index_of(x) {
|
||||
// 'if' is parsed primarily as a statement
|
||||
if this.contains(x) {
|
||||
return this.find_index(x)
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
```
|
||||
|
||||
The above will not be parsed as a single expression:
|
||||
|
||||
```rust
|
||||
fn index_of(x) {
|
||||
if this.contains(x) { return this.find_index(x) } - 1
|
||||
// error due to '() - 1' ^
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To force parsing as an expression, parentheses are required:
|
||||
|
||||
```rust
|
||||
fn calc_index(b, offset) {
|
||||
(if b { 1 } else { 0 }) + offset
|
||||
// ^---------------------^ parentheses
|
||||
}
|
||||
```
|
||||
~~~
|
138
rhai_engine/rhaibook/language/in.md
Normal file
138
rhai_engine/rhaibook/language/in.md
Normal file
@ -0,0 +1,138 @@
|
||||
In Operator
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish question.side.wide "Trivia"
|
||||
|
||||
The `in` operator is simply syntactic sugar for a call to the `contains` function.
|
||||
|
||||
Similarly, `!in` is a call to `!contains`.
|
||||
```
|
||||
|
||||
The `in` operator is used to check for _containment_ – 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::<TestStruct>()
|
||||
.register_fn("new_ts", || TestStruct::new())
|
||||
.register_fn("contains", |ts: &mut TestStruct, item: i64| -> bool {
|
||||
// Remember the parameters are switched from the 'in' expression
|
||||
ts.contains(item)
|
||||
});
|
||||
|
||||
// Now the 'in' operator can be used for 'TestStruct' and integer
|
||||
|
||||
engine.run(
|
||||
r#"
|
||||
let ts = new_ts();
|
||||
|
||||
if 42 in ts { // this calls 'ts.contains(42)'
|
||||
print("I got 42!");
|
||||
} else if 123 !in ts { // this calls '!ts.contains(123)'
|
||||
print("I ain't got 123!");
|
||||
}
|
||||
|
||||
let err = "hello" in ts; // <- runtime error: 'contains' not found
|
||||
// for 'TestStruct' and string
|
||||
"#)?;
|
||||
```
|
183
rhai_engine/rhaibook/language/iter.md
Normal file
183
rhai_engine/rhaibook/language/iter.md
Normal file
@ -0,0 +1,183 @@
|
||||
Standard Iterable Types
|
||||
========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Certain [standard types] are iterable via a [`for`] statement.
|
||||
|
||||
|
||||
Iterate Through Arrays
|
||||
----------------------
|
||||
|
||||
Iterating through an [array] yields cloned _copies_ of each element.
|
||||
|
||||
```rust
|
||||
let a = [1, 3, 5, 7, 9, 42];
|
||||
|
||||
// Loop through the array
|
||||
for x in a {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Strings
|
||||
-----------------------
|
||||
|
||||
Iterating through a [string] yields individual [characters].
|
||||
|
||||
The `chars` method also allow iterating through characters in a [string], optionally accepting the
|
||||
character position to start from (counting from the end if negative), as well as the number of
|
||||
characters to iterate (defaults to all).
|
||||
|
||||
`char` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
```rust
|
||||
let s = "hello, world!";
|
||||
|
||||
// Iterate through all the characters.
|
||||
for ch in s {
|
||||
print(ch);
|
||||
}
|
||||
|
||||
// Iterate starting from the 3rd character and stopping at the 7th.
|
||||
for ch in s.chars(2, 5) {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
|
||||
print(ch);
|
||||
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate starting from the 3rd character and stopping at the end.
|
||||
for ch in s.chars(2..s.len) {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
|
||||
print(ch);
|
||||
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Iterate Through Numeric Ranges
|
||||
------------------------------
|
||||
|
||||
[Ranges] are created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
The `range` function similarly creates exclusive [ranges], plus allowing optional step values.
|
||||
|
||||
```rust
|
||||
// Iterate starting from 0 and stopping at 49
|
||||
// The step is assumed to be 1 when omitted for integers
|
||||
for x in 0..50 {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function is just the same
|
||||
for x in range(0, 50) {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function also takes a step
|
||||
for x in range(0, 50, 3) { // step by 3
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function can also step backwards
|
||||
for x in range(50..0, -3) { // step down by -3
|
||||
if x < 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// It works also for floating-point numbers
|
||||
for x in range(5.0, 0.0, -2.0) { // step down by -2.0
|
||||
if x < 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 4.2 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Bit-Fields
|
||||
--------------------------
|
||||
|
||||
The `bits` function allows iterating through an integer as a [bit-field].
|
||||
|
||||
`bits` optionally accepts the bit number to start from (counting from the most-significant-bit if
|
||||
negative), as well as the number of bits to iterate (defaults all).
|
||||
|
||||
`bits` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
```js , no_run
|
||||
let x = 0b_1001110010_1101100010_1100010100;
|
||||
let num_on = 0;
|
||||
|
||||
// Iterate through all the bits
|
||||
for bit in x.bits() {
|
||||
if bit { num_on += 1; }
|
||||
}
|
||||
|
||||
print(`There are ${num_on} bits turned on!`);
|
||||
|
||||
const START = 3;
|
||||
|
||||
// Iterate through all the bits from 3 through 12
|
||||
for (bit, index) in x.bits(START, 10) {
|
||||
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
|
||||
|
||||
if index >= 7 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate through all the bits from 3 through 12
|
||||
for (bit, index) in x.bits(3..=12) {
|
||||
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
|
||||
|
||||
if index >= 7 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Object Maps
|
||||
---------------------------
|
||||
|
||||
Two methods, `keys` and `values`, return [arrays] containing cloned _copies_
|
||||
of all property names and values of an [object map], respectively.
|
||||
|
||||
These [arrays] can be iterated.
|
||||
|
||||
```rust
|
||||
let map = #{a:1, b:3, c:5, d:7, e:9};
|
||||
|
||||
// Property names are returned in unsorted, random order
|
||||
for x in map.keys() {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Property values are returned in unsorted, random order
|
||||
for val in map.values() {
|
||||
print(val);
|
||||
}
|
||||
```
|
59
rhai_engine/rhaibook/language/iterator.md
Normal file
59
rhai_engine/rhaibook/language/iterator.md
Normal file
@ -0,0 +1,59 @@
|
||||
Make a Custom Type Iterable
|
||||
===========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish info.side "Built-in type iterators"
|
||||
|
||||
Type iterators are already defined for built-in [standard types] such as [strings], [ranges],
|
||||
[bit-fields], [arrays] and [object maps].
|
||||
|
||||
That's why they can be used with the [`for`] loop.
|
||||
```
|
||||
|
||||
If a [custom type] is iterable, the [`for`] loop can be used to iterate through
|
||||
its items in sequence, as long as it has a _type iterator_ registered.
|
||||
|
||||
`Engine::register_iterator<T>` allows registration of a type iterator for any type
|
||||
that implements `IntoIterator`.
|
||||
|
||||
With a type iterator registered, the [custom type] can be iterated through.
|
||||
|
||||
```rust
|
||||
// Custom type
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct { fields: Vec<i64> }
|
||||
|
||||
// Implement 'IntoIterator' trait
|
||||
impl IntoIterator<Item = i64> for TestStruct {
|
||||
type Item = i64;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.fields.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register API and type iterator for 'TestStruct'
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", || TestStruct { fields: vec![1, 2, 3, 42] })
|
||||
.register_iterator::<TestStruct>();
|
||||
|
||||
// 'TestStruct' is now iterable
|
||||
engine.run(
|
||||
"
|
||||
for value in new_ts() {
|
||||
...
|
||||
}
|
||||
")?;
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Fallible type iterators"
|
||||
|
||||
`Engine::register_iterator_result` allows registration of a _fallible_ type iterator –
|
||||
i.e. an iterator that returns `Result<T, Box<EvalAltResult>>`.
|
||||
|
||||
On in very rare situations will this be necessary though.
|
||||
```
|
108
rhai_engine/rhaibook/language/json.md
Normal file
108
rhai_engine/rhaibook/language/json.md
Normal file
@ -0,0 +1,108 @@
|
||||
Parse an Object Map from JSON
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Do It Without `serde`
|
||||
---------------------
|
||||
|
||||
```admonish info.side.wide "Object map vs. JSON"
|
||||
|
||||
A valid JSON object hash does not start with a hash character `#` while a Rhai [object map] does.
|
||||
That's the only difference!
|
||||
```
|
||||
|
||||
The syntax for an [object map] is extremely similar to the JSON representation of a object hash,
|
||||
with the exception of `null` values which can technically be mapped to [`()`].
|
||||
|
||||
Use the `Engine::parse_json` method to parse a piece of JSON into an [object map].
|
||||
|
||||
```rust
|
||||
// JSON string - notice that JSON property names are always quoted
|
||||
// notice also that comments are acceptable within the JSON string
|
||||
let json = r#"{
|
||||
"a": 1, // <- this is an integer number
|
||||
"b": true,
|
||||
"c": 123.0, // <- this is a floating-point number
|
||||
"$d e f!": "hello", // <- any text can be a property name
|
||||
"^^^!!!": [1,42,"999"], // <- value can be array or another hash
|
||||
"z": null // <- JSON 'null' value
|
||||
}"#;
|
||||
|
||||
// Parse the JSON expression as an object map
|
||||
// Set the second boolean parameter to true in order to map 'null' to '()'
|
||||
let map = engine.parse_json(json, true)?;
|
||||
|
||||
map.len() == 6; // 'map' contains all properties in the JSON string
|
||||
|
||||
// Put the object map into a 'Scope'
|
||||
let mut scope = Scope::new();
|
||||
scope.push("map", map);
|
||||
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, r#"map["^^^!!!"].len()"#)?;
|
||||
|
||||
result == 3; // the object map is successfully used in the script
|
||||
```
|
||||
|
||||
```admonish warning.small "Warning: Must be object hash"
|
||||
|
||||
The JSON text must represent a single object hash – 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.
|
30
rhai_engine/rhaibook/language/keywords.md
Normal file
30
rhai_engine/rhaibook/language/keywords.md
Normal file
@ -0,0 +1,30 @@
|
||||
Keywords
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following are reserved keywords in Rhai.
|
||||
|
||||
| Active keywords | Reserved keywords | Usage | Inactive under feature |
|
||||
| -------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------- | :----------------------------: |
|
||||
| `true`, `false` | | [constants] | |
|
||||
| [`let`][variable], [`const`][constant] | `var`, `static` | [variables] | |
|
||||
| `is_shared` | | _shared_ values | [`no_closure`] |
|
||||
| | `is` | type checking | |
|
||||
| [`if`], [`else`][`if`] | `goto` | control flow | |
|
||||
| [`switch`] | `match`, `case` | switching and matching | |
|
||||
| [`do`], [`while`], [`loop`], `until`, [`for`], [`in`], `continue`, `break` | | looping | |
|
||||
| [`fn`][function], [`private`], `is_def_fn`, `this` | `public`, `protected`, `new` | [functions] | [`no_function`] |
|
||||
| [`return`] | | return values | |
|
||||
| [`throw`], [`try`], [`catch`] | | [throw/catch][`catch`] [exceptions] | |
|
||||
| [`import`], [`export`], `as` | `use`, `with`, `module`, `package`, `super` | [modules] | [`no_module`] |
|
||||
| [`global`] | | automatic global [module] | [`no_function`], [`no_module`] |
|
||||
| [`Fn`][function pointer], `call`, [`curry`][currying] | | [function pointers] | |
|
||||
| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async | |
|
||||
| [`type_of`], [`print`], [`debug`], [`eval`], `is_def_var` | | special functions | |
|
||||
| | `default`, `void`, `null`, `nil` | special values | |
|
||||
|
||||
```admonish warning.small
|
||||
Keywords cannot become the name of a [function] or [variable], even when they are
|
||||
[disabled][disable keywords and operators].
|
||||
```
|
234
rhai_engine/rhaibook/language/logic.md
Normal file
234
rhai_engine/rhaibook/language/logic.md
Normal file
@ -0,0 +1,234 @@
|
||||
Logic Operators
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Comparison Operators
|
||||
--------------------
|
||||
|
||||
| Operator | Description<br/>(`x` _operator_ `y`) | `x`, `y` same type or are numeric | `x`, `y` different types |
|
||||
| :------: | ------------------------------------ | :-------------------------------: | :----------------------: |
|
||||
| `==` | `x` is equals to `y` | error if not defined | `false` if not defined |
|
||||
| `!=` | `x` is not equals to `y` | error if not defined | `true` if not defined |
|
||||
| `>` | `x` is greater than `y` | error if not defined | `false` if not defined |
|
||||
| `>=` | `x` is greater than or equals to `y` | error if not defined | `false` if not defined |
|
||||
| `<` | `x` is less than `y` | error if not defined | `false` if not defined |
|
||||
| `<=` | `x` is less than or equals to `y` | error if not defined | `false` if not defined |
|
||||
|
||||
Comparison operators between most values of the same type are built in for all [standard types].
|
||||
|
||||
Others are defined in the [`LogicPackage`][built-in packages] but excluded when using a [raw `Engine`].
|
||||
|
||||
|
||||
### Floating-point numbers interoperate with integers
|
||||
|
||||
Comparing a floating-point number (`FLOAT`) with an integer is also supported.
|
||||
|
||||
```rust
|
||||
42 == 42.0; // true
|
||||
|
||||
42.0 == 42; // true
|
||||
|
||||
42.0 > 42; // false
|
||||
|
||||
42 >= 42.0; // true
|
||||
|
||||
42.0 < 42; // false
|
||||
```
|
||||
|
||||
### Decimal numbers interoperate with integers
|
||||
|
||||
Comparing a [`Decimal`][rust_decimal] number with an integer is also supported.
|
||||
|
||||
```rust
|
||||
let d = parse_decimal("42");
|
||||
|
||||
42 == d; // true
|
||||
|
||||
d == 42; // true
|
||||
|
||||
d > 42; // false
|
||||
|
||||
42 >= d; // true
|
||||
|
||||
d < 42; // false
|
||||
```
|
||||
|
||||
### Strings interoperate with characters
|
||||
|
||||
Comparing a [string] with a [character] is also supported, with the character first turned into a
|
||||
[string] before performing the comparison.
|
||||
|
||||
```rust
|
||||
'x' == "x"; // true
|
||||
|
||||
"" < 'a'; // true
|
||||
|
||||
'x' > "hello"; // false
|
||||
```
|
||||
|
||||
### Comparing different types defaults to `false`
|
||||
|
||||
Comparing two values of _different_ data types defaults to `false` unless the appropriate operator
|
||||
functions have been registered.
|
||||
|
||||
The exception is `!=` (not equals) which defaults to `true`. This is in line with intuition.
|
||||
|
||||
```rust
|
||||
42 > "42"; // false: i64 cannot be compared with string
|
||||
|
||||
42 <= "42"; // false: i64 cannot be compared with string
|
||||
|
||||
let ts = new_ts(); // custom type
|
||||
|
||||
ts == 42; // false: different types cannot be compared
|
||||
|
||||
ts != 42; // true: different types cannot be compared
|
||||
|
||||
ts == ts; // error: '==' not defined for the custom type
|
||||
```
|
||||
|
||||
### Safety valve: Comparing different _numeric_ types has no default
|
||||
|
||||
Beware that the above default does _NOT_ apply to numeric values of different types
|
||||
(e.g. comparison between `i64` and `u16`, `i32` and `f64`) – when multiple numeric types are
|
||||
used it is too easy to mess up and for subtle errors to creep in.
|
||||
|
||||
```rust
|
||||
// Assume variable 'x' = 42_u16, 'y' = 42_u16 (both types of u16)
|
||||
|
||||
x == y; // true: '==' operator for u16 is built-in
|
||||
|
||||
x == "hello"; // false: different non-numeric operand types default to false
|
||||
|
||||
x == 42; // error: ==(u16, i64) not defined, no default for numeric types
|
||||
|
||||
42 == y; // error: ==(i64, u16) not defined, no default for numeric types
|
||||
```
|
||||
|
||||
### Caution: Beware operators for custom types
|
||||
|
||||
```admonish tip.side.wide "Tip: Always the full set"
|
||||
|
||||
It is strongly recommended that, when defining operators for [custom types], always define the
|
||||
**full set** of six operators together, or at least the `==` and `!=` pair.
|
||||
```
|
||||
|
||||
Operators are completely separate from each other. For example:
|
||||
|
||||
* `!=` does not equal `!(==)`
|
||||
|
||||
* `>` does not equal `!(<=)`
|
||||
|
||||
* `<=` does not equal `<` plus `==`
|
||||
|
||||
* `<=` does not imply `<`
|
||||
|
||||
Therefore, if a [custom type] misses an [operator] definition, it simply raises an error
|
||||
or returns the default.
|
||||
|
||||
This behavior can be counter-intuitive.
|
||||
|
||||
```rust
|
||||
let ts = new_ts(); // custom type with '<=' and '==' defined
|
||||
|
||||
ts <= ts; // true: '<=' defined
|
||||
|
||||
ts < ts; // error: '<' not defined, even though '<=' is
|
||||
|
||||
ts == ts; // true: '==' defined
|
||||
|
||||
ts != ts; // error: '!=' not defined, even though '==' is
|
||||
```
|
||||
|
||||
|
||||
Boolean Operators
|
||||
-----------------
|
||||
|
||||
```admonish note.side
|
||||
|
||||
All boolean operators are [built in][built-in operators] for the `bool` data type.
|
||||
```
|
||||
|
||||
| Operator | Description | Arity | Short-circuits? |
|
||||
| :---------------: | :---------: | :----: | :-------------: |
|
||||
| `!` _(prefix)_ | _NOT_ | unary | no |
|
||||
| `&&` | _AND_ | binary | **yes** |
|
||||
| `&` | _AND_ | binary | no |
|
||||
| <code>\|\|</code> | _OR_ | binary | **yes** |
|
||||
| <code>\|</code> | _OR_ | binary | no |
|
||||
|
||||
Double boolean operators `&&` and `||` _short-circuit_ – 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;
|
||||
}
|
||||
```
|
71
rhai_engine/rhaibook/language/loop.md
Normal file
71
rhai_engine/rhaibook/language/loop.md
Normal file
@ -0,0 +1,71 @@
|
||||
Infinite Loop
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Infinite loops follow Rust syntax.
|
||||
|
||||
Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements;
|
||||
`break` can be used to break out of the loop unconditionally.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `loop`"
|
||||
|
||||
`loop` can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
loop {
|
||||
x -= 1;
|
||||
|
||||
if x > 5 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 0 { break; } // break out of loop
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Remember the `break` statement"
|
||||
|
||||
A `loop` statement without a `break` statement inside its loop block is infinite.
|
||||
There is no way for the loop to stop iterating.
|
||||
~~~
|
||||
|
||||
|
||||
Loop Expression
|
||||
---------------
|
||||
|
||||
Like Rust, `loop` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `loop` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = 0;
|
||||
|
||||
// 'loop' can be used just like an expression
|
||||
let result = loop {
|
||||
if is_magic_number(x) {
|
||||
// if the loop breaks here, return a specific value
|
||||
break get_magic_result(x);
|
||||
}
|
||||
|
||||
x += 1;
|
||||
|
||||
// ... if the loop exits here, the return value is ()
|
||||
};
|
||||
|
||||
if result == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic result = ${result}!`);
|
||||
}
|
||||
```
|
116
rhai_engine/rhaibook/language/modules/export.md
Normal file
116
rhai_engine/rhaibook/language/modules/export.md
Normal file
@ -0,0 +1,116 @@
|
||||
Export Variables, Functions and Sub-Modules From a Script
|
||||
=========================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [_Create a Module from AST_]({{rootUrl}}/rust/modules/ast.md) for more details.
|
||||
```
|
||||
|
||||
The easiest way to expose a collection of [functions] as a self-contained [module] is to do it via a Rhai script itself.
|
||||
|
||||
The script text is evaluated.
|
||||
|
||||
[Variables] are then selectively exposed via the `export` statement.
|
||||
|
||||
[Functions] defined by the script are automatically exported, unless marked as `private`.
|
||||
|
||||
Modules loaded within this [module] at the global level become _sub-modules_ and are also automatically exported.
|
||||
|
||||
|
||||
Export Global Variables
|
||||
-----------------------
|
||||
|
||||
The `export` statement, which can only be at global level, exposes a selected [variable] as member of a [module].
|
||||
|
||||
[Variables] not exported are _private_ and hidden. They are merely used to initialize the [module],
|
||||
but cannot be accessed from outside.
|
||||
|
||||
Everything exported from a [module] is **[constant]** (i.e. read-only).
|
||||
|
||||
```js
|
||||
// This is a module script.
|
||||
|
||||
let hidden = 123; // variable not exported - default hidden
|
||||
let x = 42; // this will be exported below
|
||||
|
||||
export x; // the variable 'x' is exported under its own name
|
||||
|
||||
export const x = 42; // convenient short-hand to declare a constant and export it
|
||||
// under its own name
|
||||
|
||||
export let x = 123; // variables can be exported as well, though it'll still be constant
|
||||
|
||||
export x as answer; // the variable 'x' is exported under the alias 'answer'
|
||||
// another script can load this module and access 'x' as 'module::answer'
|
||||
|
||||
{
|
||||
let inner = 0; // local variable - it disappears when the statements block ends,
|
||||
// therefore it is not 'global' and cannot be exported
|
||||
|
||||
export inner; // <- syntax error: cannot export a local variable
|
||||
}
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Multiple exports"
|
||||
|
||||
[Variables] can be exported under multiple names.
|
||||
For example, the following exports three [variables]:
|
||||
* `x` as `x` and `hello`
|
||||
* `y` as `foo` and `bar`
|
||||
* `z` as `z`
|
||||
|
||||
~~~js
|
||||
export x;
|
||||
export x as hello;
|
||||
export y as foo;
|
||||
export x as world;
|
||||
export y as bar;
|
||||
export z;
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Export Functions
|
||||
----------------
|
||||
|
||||
```admonish info.side "Private functions"
|
||||
|
||||
`private` [functions] are commonly called within the [module] only.
|
||||
They cannot be accessed otherwise.
|
||||
```
|
||||
|
||||
All [functions] are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
|
||||
|
||||
[Functions] declared `private` are hidden to the outside.
|
||||
|
||||
```rust
|
||||
// This is a module script.
|
||||
|
||||
fn inc(x) { x + 1 } // script-defined function - default public
|
||||
|
||||
private fn foo() {} // private function - hidden
|
||||
```
|
||||
|
||||
|
||||
Sub-Modules
|
||||
-----------
|
||||
|
||||
All loaded [modules] are automatically exported as sub-modules.
|
||||
|
||||
~~~admonish tip.small "Tip: Skip exporting a module"
|
||||
|
||||
To prevent a [module] from being exported, load it inside a block statement so that it goes away at the
|
||||
end of the block.
|
||||
|
||||
```js
|
||||
// This is a module script.
|
||||
|
||||
import "hello" as foo; // <- exported
|
||||
|
||||
{
|
||||
import "world" as bar; // <- not exported
|
||||
}
|
||||
```
|
||||
~~~
|
132
rhai_engine/rhaibook/language/modules/import.md
Normal file
132
rhai_engine/rhaibook/language/modules/import.md
Normal file
@ -0,0 +1,132 @@
|
||||
Import a Module
|
||||
===============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [_Module Resolvers_][module resolver] for more details.
|
||||
```
|
||||
|
||||
Before a [module] can be used (via an `import` statement) in a script, there must be a
|
||||
[module resolver] registered into the [`Engine`], the default being the `FileModuleResolver`.
|
||||
|
||||
|
||||
`import` Statement
|
||||
------------------
|
||||
|
||||
```admonish tip.side.wide "Tip"
|
||||
|
||||
A [module] that is only `import`-ed but not given any name is simply run.
|
||||
|
||||
This is a very simple way to run another script file from within a script.
|
||||
```
|
||||
|
||||
A [module] can be _imported_ via the `import` statement, and be given a name.
|
||||
|
||||
Its members can be accessed via `::` similar to C++.
|
||||
|
||||
```js
|
||||
import "crypto_banner"; // run the script file 'crypto_banner.rhai' without creating an imported module
|
||||
|
||||
import "crypto" as lock; // run the script file 'crypto.rhai' and import it as a module named 'lock'
|
||||
|
||||
const SECRET_NUMBER = 42;
|
||||
|
||||
let mod_file = `crypto_${SECRET_NUMBER}`;
|
||||
|
||||
import mod_file as my_mod; // load the script file "crypto_42.rhai" and import it as a module named 'my_mod'
|
||||
// notice that module path names can be dynamically constructed!
|
||||
// any expression that evaluates to a string is acceptable after the 'import' keyword
|
||||
|
||||
lock::encrypt(secret); // use functions defined under the module via '::'
|
||||
|
||||
lock::hash::sha256(key); // sub-modules are also supported
|
||||
|
||||
print(lock::status); // module variables are constants
|
||||
|
||||
lock::status = "off"; // <- runtime error: cannot modify a constant
|
||||
```
|
||||
|
||||
|
||||
```admonish info "Imports are _scoped_"
|
||||
|
||||
[Modules] imported via `import` statements are only accessible inside the relevant block scope.
|
||||
|
||||
~~~js
|
||||
import "hacker" as h; // import module - visible globally
|
||||
|
||||
if secured { // <- new block scope
|
||||
let mod = "crypt";
|
||||
|
||||
import mod + "o" as c; // import module (the path needs not be a constant string)
|
||||
|
||||
let x = c::encrypt(key); // use a function in the module
|
||||
|
||||
h::hack(x); // global module 'h' is visible here
|
||||
} // <- module 'c' disappears at the end of the block scope
|
||||
|
||||
h::hack(something); // this works as 'h' is visible
|
||||
|
||||
c::encrypt(something); // <- this causes a run-time error because
|
||||
// module 'c' is no longer available!
|
||||
|
||||
fn foo(something) {
|
||||
h::hack(something); // <- this also works as 'h' is visible
|
||||
}
|
||||
|
||||
for x in 0..1000 {
|
||||
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea™
|
||||
|
||||
c.encrypt(something);
|
||||
}
|
||||
~~~
|
||||
```
|
||||
|
||||
~~~admonish note "Place `import` statements at the top"
|
||||
|
||||
`import` statements can appear anywhere a normal statement can be, but in the vast majority of cases they are
|
||||
usually grouped at the top (beginning) of a script for manageability and visibility.
|
||||
|
||||
It is not advised to deviate from this common practice unless there is a _Very Good Reason™_.
|
||||
|
||||
Especially, do not place an `import` statement within a loop; doing so will repeatedly re-load the
|
||||
same [module] during every iteration of the loop!
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Recursive imports"
|
||||
|
||||
Beware of _import cycles_ – 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();
|
||||
```
|
||||
~~~
|
14
rhai_engine/rhaibook/language/modules/index.md
Normal file
14
rhai_engine/rhaibook/language/modules/index.md
Normal file
@ -0,0 +1,14 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
A module has the type `Module` and holds a collection of functions, variables, [type iterators] and sub-modules.
|
||||
It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions
|
||||
and variables defined by that script.
|
||||
|
||||
Other scripts can then load this module and use the functions and variables exported as if they were
|
||||
defined inside the same script.
|
122
rhai_engine/rhaibook/language/num-fn.md
Normal file
122
rhai_engine/rhaibook/language/num-fn.md
Normal file
@ -0,0 +1,122 @@
|
||||
Numeric Functions
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Integer Functions
|
||||
-----------------
|
||||
|
||||
The following standard functions are defined.
|
||||
|
||||
| Function | Not available under | Package | Description |
|
||||
| ----------------------------- | :-----------------: | :--------------------------------------: | ---------------------------------------------------------------- |
|
||||
| `is_odd` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an odd number, otherwise `false` |
|
||||
| `is_even` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an even number, otherwise `false` |
|
||||
| `min` | | [`LogicPackage`][built-in packages] | returns the smaller of two numbers |
|
||||
| `max` | | [`LogicPackage`][built-in packages] | returns the larger of two numbers |
|
||||
| `to_float` | [`no_float`] | [`BasicMathPackage`][built-in packages] | convert the value into `f64` (`f32` under [`f32_float`]) |
|
||||
| `to_decimal` | non-[`decimal`] | [`BasicMathPackage`][built-in packages] | convert the value into [`Decimal`][rust_decimal] |
|
||||
|
||||
|
||||
Signed Numeric Functions
|
||||
------------------------
|
||||
|
||||
The following standard functions are defined in the [`ArithmeticPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) and operate on `i8`, `i16`, `i32`, `i64`, `f32`, `f64` and
|
||||
[`Decimal`][rust_decimal] (requires [`decimal`]) only.
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------- | -------------------------------------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| `sign` | returns (`INT`) −1 if negative, +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.
|
135
rhai_engine/rhaibook/language/num-op.md
Normal file
135
rhai_engine/rhaibook/language/num-op.md
Normal file
@ -0,0 +1,135 @@
|
||||
Numeric Operators
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Numeric operators generally follow C styles.
|
||||
|
||||
Unary Operators
|
||||
---------------
|
||||
|
||||
| Operator | Description |
|
||||
| :------: | ----------- |
|
||||
| `+` | positive |
|
||||
| `-` | negative |
|
||||
|
||||
```rust
|
||||
let number = +42;
|
||||
|
||||
number = -5;
|
||||
|
||||
number = -5 - +5;
|
||||
|
||||
-(-42) == +42; // two '-' equals '+'
|
||||
// beware: '++' and '--' are reserved symbols
|
||||
```
|
||||
|
||||
Binary Operators
|
||||
----------------
|
||||
|
||||
| Operator | Description | Result type | `INT` | `FLOAT` | [`Decimal`][rust_decimal] |
|
||||
| :-------------------------------: | ---------------------------------------------------------------- | :---------: | :---: | :--------------------: | :-----------------------: |
|
||||
| `+`, `+=` | plus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `-`, `-=` | minus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `*`, `*=` | multiply | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `/`, `/=` | divide (integer division if acting on integer types) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `%`, `%=` | modulo (remainder) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `**`, `**=` | power/exponentiation | numeric | yes | yes, also `FLOAT**INT` | **no** |
|
||||
| `<<`, `<<=` | left bit-shift (if negative number of bits, shift right instead) | numeric | yes | **no** | **no** |
|
||||
| `>>`, `>>=` | right bit-shift (if negative number of bits, shift left instead) | numeric | yes | **no** | **no** |
|
||||
| `&`, `&=` | bit-wise _And_ | numeric | yes | **no** | **no** |
|
||||
| <code>\|</code>, <code>\|=</code> | bit-wise _Or_ | numeric | yes | **no** | **no** |
|
||||
| `^`, `^=` | bit-wise _Xor_ | numeric | yes | **no** | **no** |
|
||||
| `==` | equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `!=` | not equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `>` | greater than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `>=` | greater than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `<` | less than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `<=` | less than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `..` | exclusive range | [range] | yes | **no** | **no** |
|
||||
| `..=` | inclusive range | [range] | yes | **no** | **no** |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
|
||||
|
||||
let reminder = 42 % 10; // modulo
|
||||
|
||||
let power = 42 ** 2; // power
|
||||
|
||||
let left_shifted = 42 << 3; // left shift
|
||||
|
||||
let right_shifted = 42 >> 3; // right shift
|
||||
|
||||
let bit_op = 42 | 99; // bit masking
|
||||
```
|
||||
|
||||
|
||||
Floating-Point Interoperates with Integers
|
||||
------------------------------------------
|
||||
|
||||
When one of the operands to a binary arithmetic [operator] is floating-point, it works with `INT` for
|
||||
the other operand and the result is floating-point.
|
||||
|
||||
```rust
|
||||
let x = 41.0 + 1; // 'FLOAT' + 'INT'
|
||||
|
||||
type_of(x) == "f64"; // result is 'FLOAT'
|
||||
|
||||
let x = 21 * 2.0; // 'FLOAT' * 'INT'
|
||||
|
||||
type_of(x) == "f64";
|
||||
|
||||
(x == 42) == true; // 'FLOAT' == 'INT'
|
||||
|
||||
(10 < x) == true; // 'INT' < 'FLOAT'
|
||||
```
|
||||
|
||||
|
||||
Decimal Interoperates with Integers
|
||||
-----------------------------------
|
||||
|
||||
When one of the operands to a binary arithmetic [operator] is [`Decimal`][rust_decimal],
|
||||
it works with `INT` for the other operand and the result is [`Decimal`][rust_decimal].
|
||||
|
||||
```rust
|
||||
let d = parse_decimal("2");
|
||||
|
||||
let x = d + 1; // 'Decimal' + 'INT'
|
||||
|
||||
type_of(x) == "decimal"; // result is 'Decimal'
|
||||
|
||||
let x = 21 * d; // 'Decimal' * 'INT'
|
||||
|
||||
type_of(x) == "decimal";
|
||||
|
||||
(x == 42) == true; // 'Decimal' == 'INT'
|
||||
|
||||
(10 < x) == true; // 'INT' < 'Decimal'
|
||||
```
|
||||
|
||||
|
||||
Unary Before Binary
|
||||
-------------------
|
||||
|
||||
In Rhai, unary operators take [precedence] over binary operators. This is especially important to
|
||||
remember when handling operators such as `**` which in some languages bind tighter than the unary
|
||||
`-` operator.
|
||||
|
||||
```rust
|
||||
-2 + 2 == 0;
|
||||
|
||||
-2 - 2 == -4;
|
||||
|
||||
-2 * 2 == -4;
|
||||
|
||||
-2 / 2 == -1;
|
||||
|
||||
-2 % 2 == 0;
|
||||
|
||||
-2 ** 2 = 4; // means: (-2) ** 2
|
||||
// in some languages this means: -(2 ** 2)
|
||||
```
|
138
rhai_engine/rhaibook/language/numbers.md
Normal file
138
rhai_engine/rhaibook/language/numbers.md
Normal file
@ -0,0 +1,138 @@
|
||||
Numbers
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Integers
|
||||
--------
|
||||
|
||||
```admonish tip.side "Tip: Bit-fields"
|
||||
|
||||
Integers can also be conveniently manipulated as [bit-fields].
|
||||
```
|
||||
|
||||
Integer numbers follow C-style format with support for decimal, binary (`0b`), octal (`0o`) and hex (`0x`) notations.
|
||||
|
||||
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
|
||||
|
||||
|
||||
Floating-Point Numbers
|
||||
----------------------
|
||||
|
||||
```admonish tip.side "Tip: Notations"
|
||||
|
||||
Both decimal and scientific notations can be used to represent floating-point numbers.
|
||||
```
|
||||
|
||||
Floating-point numbers are also supported if not disabled with [`no_float`].
|
||||
|
||||
The default system floating-point type is `f64` (also aliased to `FLOAT`).
|
||||
It can be turned into `f32` via the [`f32_float`] feature.
|
||||
|
||||
|
||||
`Decimal` Numbers
|
||||
-----------------
|
||||
|
||||
When rounding errors cannot be accepted, such as in financial calculations, the [`decimal`] feature
|
||||
turns on support for the [`Decimal`][rust_decimal] type, which is a fixed-precision floating-point
|
||||
number with no rounding errors.
|
||||
|
||||
|
||||
Number Literals
|
||||
---------------
|
||||
|
||||
`_` separators can be added freely and are ignored within a number – 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::<String>("type_of(42)")? == "i64";
|
||||
|
||||
// false - i32 is never equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "r == 42")?;
|
||||
|
||||
// false - u8 is never equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "x == 42")?;
|
||||
|
||||
// true - i64 is equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "y == 42")?;
|
||||
|
||||
// true - INT is i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "z == 42")?;
|
||||
|
||||
// false - f32 is never equal to f64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "f == 42.0")?;
|
||||
```
|
||||
|
||||
|
||||
Floating-Point vs. Decimal
|
||||
--------------------------
|
||||
|
||||
~~~admonish tip.side.wide "Tip: `no_float` + `decimal`"
|
||||
|
||||
When both [`no_float`] and [`decimal`] features are turned on, [`Decimal`][rust_decimal] _replaces_
|
||||
the standard floating-point type.
|
||||
|
||||
Floating-point number literals in scripts parse to [`Decimal`][rust_decimal] values.
|
||||
~~~
|
||||
|
||||
[`Decimal`][rust_decimal] (enabled via the [`decimal`] feature) represents a fixed-precision
|
||||
floating-point number which is popular with financial calculations and other usage scenarios where
|
||||
round-off errors are not acceptable.
|
||||
|
||||
[`Decimal`][rust_decimal] takes up more space (16 bytes) than a standard `FLOAT` (4-8 bytes) and is
|
||||
much slower in calculations due to the lack of CPU hardware support. Use it only when necessary.
|
||||
|
||||
For most situations, the standard floating-point number type `FLOAT` (`f64` or `f32` with
|
||||
[`f32_float`]) is enough and is faster than [`Decimal`][rust_decimal].
|
||||
|
||||
It is possible to use both `FLOAT` and [`Decimal`][rust_decimal] together with just the [`decimal`] feature
|
||||
– use [`parse_decimal`] or [`to_decimal`] to create a [`Decimal`][rust_decimal] value.
|
78
rhai_engine/rhaibook/language/object-maps-missing-prop.md
Normal file
78
rhai_engine/rhaibook/language/object-maps-missing-prop.md
Normal file
@ -0,0 +1,78 @@
|
||||
Non-Existent Property Handling for Object Maps
|
||||
==============================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_map_missing_property`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_map_missing_property
|
||||
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires `internals`"
|
||||
|
||||
This is an advanced feature that requires the [`internals`] feature to be enabled.
|
||||
~~~
|
||||
|
||||
Normally, when a property is accessed from an [object map] that does not exist, [`()`] is returned.
|
||||
Via [`Engine:: set_fail_on_invalid_map_property`][options], it is possible to make this an error
|
||||
instead.
|
||||
|
||||
Other than that, it is possible to completely control this behavior via a special callback function
|
||||
registered into an [`Engine`] via `on_map_missing_property`.
|
||||
|
||||
Using this callback, for instance, it is simple to instruct Rhai to create a new property in the
|
||||
[object map] on the fly, possibly with a default value, when a non-existent property is accessed.
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_map_missing_property`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(map: &mut Map, prop: &str, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :----------------------: | ----------------------------------- |
|
||||
| `map` | [`&mut Map`][object map] | the [object map] being accessed |
|
||||
| `prop` | `&str` | name of the property being accessed |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Target, Box<EvalAltResult>>`.
|
||||
|
||||
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
|
||||
_reference_ to a [`Dynamic`] value.
|
||||
|
||||
It can be used to point to a particular value within the [object map].
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.on_map_missing_property(|map, prop, context| {
|
||||
match prop {
|
||||
"x" => {
|
||||
// The object-map can be modified in place
|
||||
map.insert("y".into(), (42_i64).into());
|
||||
|
||||
// Return a mutable reference to an element
|
||||
let value_ref = map.get_mut("y").unwrap();
|
||||
Ok(value_ref.into())
|
||||
}
|
||||
"z" => {
|
||||
// Return a temporary value (not a reference)
|
||||
let value = Dynamic::from(100_i64);
|
||||
Ok(value.into())
|
||||
}
|
||||
// Return the standard property-not-found error
|
||||
_ => Err(EvalAltResult::ErrorPropertyNotFound(
|
||||
prop.to_string(), Position::NONE
|
||||
).into()),
|
||||
}
|
||||
});
|
||||
```
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user