mirror of
https://github.com/gorules/zen.git
synced 2026-10-05 00:02:26 +00:00
perf: general performance improvements (#500)
* perf: general performance improvements * remove unsafe usages, further improve perf * perf: add table indexing
This commit is contained in:
Generated
+481
-21
@@ -260,6 +260,29 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -551,6 +574,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex 2.0.1",
|
||||
]
|
||||
|
||||
@@ -695,6 +720,15 @@ dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.4"
|
||||
@@ -716,6 +750,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.7.1"
|
||||
@@ -799,6 +843,16 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -1071,6 +1125,12 @@ version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
@@ -1152,9 +1212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.16.2"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -1202,9 +1262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
version = "0.3.2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
|
||||
checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
|
||||
dependencies = [
|
||||
"borrow-or-share",
|
||||
"ref-cast",
|
||||
@@ -1267,6 +1327,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -1361,6 +1427,21 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -1428,6 +1509,25 @@ dependencies = [
|
||||
"scroll",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -1483,6 +1583,11 @@ name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -1508,6 +1613,16 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hipstr"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07a5072958d04f9147e517881d929d3f4706612712f8f4cfcd247f2b716d5262"
|
||||
dependencies = [
|
||||
"loom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
@@ -1613,6 +1728,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1954,6 +2070,65 @@ dependencies = [
|
||||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||
dependencies = [
|
||||
"jni-sys-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.100"
|
||||
@@ -1979,31 +2154,58 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.33.0"
|
||||
version = "0.49.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d46662859bc5f60a145b75f4632fbadc84e829e45df6c5de74cfc8e05acb96b5"
|
||||
checksum = "f8a77951c56b6a0af03c22af6953d6ffcfba9403c765578921f3f1089b999db6"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"base64",
|
||||
"bytecount",
|
||||
"data-encoding",
|
||||
"email_address",
|
||||
"fancy-regex",
|
||||
"fraction",
|
||||
"getrandom 0.3.4",
|
||||
"idna",
|
||||
"itoa",
|
||||
"jsonschema-regex",
|
||||
"jsonschema-value",
|
||||
"num-cmp",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"referencing",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"reqwest 0.12.28",
|
||||
"reqwest 0.13.4",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.28.0",
|
||||
"unicode-general-category",
|
||||
"uuid-simd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-regex"
|
||||
version = "0.49.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c2e64f341a1d6a15d2daa3c967e48921cf15b9c7f23a9899e8b63c7f7c4199"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-value"
|
||||
version = "0.49.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94e097778f3e9c6a33862077dbf07aca0360d0b18d2a7abc323da132df49ee83"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"bytecount",
|
||||
"fraction",
|
||||
"num-cmp",
|
||||
"num-traits",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -2087,12 +2289,34 @@ version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
@@ -2108,6 +2332,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.52"
|
||||
@@ -2248,6 +2478,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@@ -2382,6 +2621,12 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
@@ -2624,6 +2869,12 @@ dependencies = [
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
@@ -3103,13 +3354,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.33.0"
|
||||
version = "0.49.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e9c261f7ce75418b3beadfb3f0eb1299fe8eb9640deba45ffa2cb783098697d"
|
||||
checksum = "b772e96f8eb6badd4eb10fbc08b8a98244c09b4af37e72e8fa612c854604b870"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"fluent-uri",
|
||||
"once_cell",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.17.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -3314,9 +3568,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
@@ -3354,18 +3606,28 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
@@ -3533,6 +3795,15 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
@@ -3552,6 +3823,7 @@ version = "0.23.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -3560,6 +3832,18 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
@@ -3570,12 +3854,40 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@@ -3633,6 +3945,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -3682,6 +4003,35 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
@@ -3839,6 +4189,15 @@ dependencies = [
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -3877,6 +4236,16 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
@@ -4004,7 +4373,16 @@ version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.27.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||
dependencies = [
|
||||
"strum_macros 0.28.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4019,6 +4397,18 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -4472,6 +4862,15 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
@@ -4721,6 +5120,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4739,6 +5168,12 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.12.3"
|
||||
@@ -4757,6 +5192,12 @@ version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id-start"
|
||||
version = "1.4.0"
|
||||
@@ -4974,10 +5415,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
|
||||
dependencies = [
|
||||
"outref",
|
||||
"uuid",
|
||||
"vsimd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -5143,6 +5589,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.7"
|
||||
@@ -5580,11 +6035,12 @@ dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"rquickjs",
|
||||
"rust_decimal",
|
||||
"self_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2 0.10.9",
|
||||
"strum",
|
||||
"strum 0.27.2",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_parser",
|
||||
@@ -5592,6 +6048,7 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"toml 0.8.23",
|
||||
"typed-arena",
|
||||
"zen-expression",
|
||||
"zen-tmpl",
|
||||
"zen-types",
|
||||
@@ -5622,9 +6079,10 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json5",
|
||||
"smallvec",
|
||||
"strsim",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"strum 0.27.2",
|
||||
"strum_macros 0.27.2",
|
||||
"thiserror 1.0.69",
|
||||
"toml 0.8.23",
|
||||
"zen-macros",
|
||||
@@ -5640,7 +6098,7 @@ dependencies = [
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum 0.27.2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"zen-engine",
|
||||
@@ -5711,11 +6169,13 @@ name = "zen-types"
|
||||
version = "1.0.0-beta.11"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"hipstr",
|
||||
"nohash-hasher",
|
||||
"rust_decimal",
|
||||
"rust_decimal_macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ impl ZenDecisionContent {
|
||||
}
|
||||
};
|
||||
if let DecisionContent::Graph(g) = &mut decision_content {
|
||||
g.compile();
|
||||
Arc::make_mut(g).compile();
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use napi::bindgen_prelude::ToNapiValue;
|
||||
use napi::sys;
|
||||
use napi::sys::{napi_env, napi_value};
|
||||
use serde_json::Value;
|
||||
use zen_engine::{DecisionGraphResponse, EvaluationTrace, EvaluationTraceKind};
|
||||
use zen_expression::variable::ToVariable;
|
||||
use zen_expression::variable::{RcCell, ToVariable};
|
||||
use zen_expression::Variable;
|
||||
|
||||
enum PNode {
|
||||
@@ -32,8 +31,8 @@ impl PortableArena {
|
||||
|
||||
pub fn add(&mut self, var: &Variable, memo: &mut HashMap<usize, u32>) -> u32 {
|
||||
let addr = match var {
|
||||
Variable::Array(a) => Some(Rc::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(Rc::as_ptr(o) as *const () as usize),
|
||||
Variable::Array(a) => Some(RcCell::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(RcCell::as_ptr(o) as *const () as usize),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ impl PyZenDecisionContent {
|
||||
let mut content: DecisionContent =
|
||||
serde_json::from_str(data).context("Failed to parse JSON")?;
|
||||
if let DecisionContent::Graph(g) = &mut content {
|
||||
g.compile();
|
||||
Arc::make_mut(g).compile();
|
||||
}
|
||||
Ok(Self(Arc::new(content)))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use pyo3::prelude::{PyDictMethods, PyListMethods};
|
||||
use pyo3::types::{PyDict, PyList};
|
||||
@@ -9,7 +8,7 @@ use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::Value;
|
||||
use zen_engine::{DecisionGraphResponse, EvaluationTrace};
|
||||
use zen_expression::variable::ToVariable;
|
||||
use zen_expression::variable::{RcCell, ToVariable};
|
||||
use zen_expression::Variable;
|
||||
|
||||
use crate::value::value_to_object;
|
||||
@@ -29,8 +28,8 @@ impl<'py> VariableConverter<'py> {
|
||||
|
||||
pub fn convert(&mut self, var: &Variable) -> PyResult<Bound<'py, PyAny>> {
|
||||
let addr = match var {
|
||||
Variable::Array(a) => Some(Rc::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(Rc::as_ptr(o) as *const () as usize),
|
||||
Variable::Array(a) => Some(RcCell::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(RcCell::as_ptr(o) as *const () as usize),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -67,7 +66,7 @@ impl<'py> VariableConverter<'py> {
|
||||
let dict = PyDict::new(self.py);
|
||||
let borrowed = o.borrow();
|
||||
for (key, value) in borrowed.iter() {
|
||||
dict.set_item(key.as_ref(), self.convert(value)?)?;
|
||||
dict.set_item(key.as_str(), self.convert(value)?)?;
|
||||
}
|
||||
|
||||
dict.into_bound_py_any(self.py)
|
||||
@@ -148,8 +147,8 @@ impl PortableResponse {
|
||||
|
||||
fn add(nodes: &mut Vec<PNode>, memo: &mut HashMap<usize, u32>, var: &Variable) -> u32 {
|
||||
let addr = match var {
|
||||
Variable::Array(a) => Some(Rc::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(Rc::as_ptr(o) as *const () as usize),
|
||||
Variable::Array(a) => Some(RcCell::as_ptr(a) as *const () as usize),
|
||||
Variable::Object(o) => Some(RcCell::as_ptr(o) as *const () as usize),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn variable_to_object<'py>(py: Python<'py>, val: &Variable) -> PyResult<Boun
|
||||
let dict = PyDict::new(py);
|
||||
let b = m.borrow();
|
||||
for (key, value) in b.iter() {
|
||||
dict.set_item(String::from(key.as_ref()), variable_to_object(py, value)?)?;
|
||||
dict.set_item(key.as_str().to_owned(), variable_to_object(py, value)?)?;
|
||||
}
|
||||
|
||||
dict.into_bound_py_any(py)
|
||||
|
||||
@@ -38,6 +38,8 @@ swc_ts_fast_strip = "53.0.0"
|
||||
swc_common = "23"
|
||||
swc_ecma_parser = "41"
|
||||
swc_ecma_ast = "25"
|
||||
typed-arena = "2"
|
||||
self_cell = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
@@ -52,10 +54,10 @@ http = { version = "1.3" }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
||||
reqsign = { version = "0.20", features = ["aws", "azure", "google", "default-context"] }
|
||||
sha2 = { version = "0.10" }
|
||||
jsonschema = { version = "0.33" }
|
||||
jsonschema = { version = "0.49" }
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
jsonschema = { version = "0.33", default-features = false }
|
||||
jsonschema = { version = "0.49", default-features = false }
|
||||
rquickjs = { version = "0.10", features = ["macro", "loader", "rust-alloc", "futures", "either", "properties", "bindgen"] }
|
||||
|
||||
[[bench]]
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::loader::{DynamicLoader, NoopLoader};
|
||||
use crate::model::GraphContent;
|
||||
use crate::nodes::custom::{DynamicCustomNode, NoopCustomNode};
|
||||
use crate::nodes::function::http_handler::DynamicHttpHandler;
|
||||
use crate::nodes::validator_cache::ValidatorCache;
|
||||
use crate::nodes::NodeHandlerExtensions;
|
||||
use crate::{DecisionGraphValidationError, EvaluationError};
|
||||
use serde_json::Value;
|
||||
@@ -19,7 +18,6 @@ pub struct Decision {
|
||||
loader: DynamicLoader,
|
||||
adapter: DynamicCustomNode,
|
||||
http_handler: DynamicHttpHandler,
|
||||
validator_cache: ValidatorCache,
|
||||
}
|
||||
|
||||
impl From<GraphContent> for Decision {
|
||||
@@ -29,7 +27,6 @@ impl From<GraphContent> for Decision {
|
||||
loader: Arc::new(NoopLoader::default()),
|
||||
adapter: Arc::new(NoopCustomNode::default()),
|
||||
http_handler: None,
|
||||
validator_cache: ValidatorCache::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +38,6 @@ impl From<Arc<GraphContent>> for Decision {
|
||||
loader: Arc::new(NoopLoader::default()),
|
||||
adapter: Arc::new(NoopCustomNode::default()),
|
||||
http_handler: None,
|
||||
validator_cache: ValidatorCache::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,8 +82,9 @@ impl Decision {
|
||||
custom_node: self.adapter.clone(),
|
||||
http_handler: self.http_handler.clone(),
|
||||
compiled_cache: self.content.compiled_cache.clone(),
|
||||
dt_indexes: self.content.dt_indexes.clone(),
|
||||
stripped_functions: self.content.stripped_functions.clone(),
|
||||
validator_cache: Arc::new(OnceCell::from(self.validator_cache.clone())),
|
||||
validator_cache: Arc::new(OnceCell::from(self.content.validator_cache.clone())),
|
||||
..Default::default()
|
||||
},
|
||||
})?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use ahash::{HashSet, HashSetExt};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use zen_types::rccell::RcCell;
|
||||
use zen_types::variable::Variable;
|
||||
|
||||
pub(crate) const ZEN_RESERVED_PROPERTIES: &[&str] = &["$nodes"];
|
||||
@@ -25,7 +24,7 @@ impl VariableCleaner {
|
||||
| Variable::Dynamic(_) => {}
|
||||
|
||||
Variable::Array(arr) => {
|
||||
let ptr = Rc::as_ptr(arr) as usize;
|
||||
let ptr = RcCell::as_ptr(arr) as usize;
|
||||
if !self.visited.insert(ptr) {
|
||||
return;
|
||||
}
|
||||
@@ -37,14 +36,14 @@ impl VariableCleaner {
|
||||
}
|
||||
|
||||
Variable::Object(obj) => {
|
||||
let ptr = Rc::as_ptr(obj) as usize;
|
||||
let ptr = RcCell::as_ptr(obj) as usize;
|
||||
if !self.visited.insert(ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut map = obj.borrow_mut();
|
||||
for key in ZEN_RESERVED_PROPERTIES {
|
||||
map.remove(*key);
|
||||
map.remove_str(key);
|
||||
}
|
||||
|
||||
for (_, value) in map.iter() {
|
||||
@@ -53,51 +52,4 @@ impl VariableCleaner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_clean(&mut self, var: &Variable) -> Variable {
|
||||
match var {
|
||||
Variable::Null
|
||||
| Variable::Bool(_)
|
||||
| Variable::Number(_)
|
||||
| Variable::String(_)
|
||||
| Variable::Dynamic(_) => var.shallow_clone(),
|
||||
|
||||
Variable::Array(arr) => {
|
||||
let ptr = Rc::as_ptr(&arr) as usize;
|
||||
if !self.visited.insert(ptr) {
|
||||
return Variable::Array(arr.clone());
|
||||
}
|
||||
|
||||
let items = arr.borrow();
|
||||
Variable::from_array(items.iter().map(|v| self.clone_clean(v)).collect())
|
||||
}
|
||||
|
||||
Variable::Object(obj) => {
|
||||
let ptr = Rc::as_ptr(obj) as usize;
|
||||
if !self.visited.insert(ptr) {
|
||||
return Variable::Object(obj.clone());
|
||||
}
|
||||
|
||||
let map = obj.borrow();
|
||||
let will_remove_key = map
|
||||
.keys()
|
||||
.any(|k| ZEN_RESERVED_PROPERTIES.contains(&k.as_ref()));
|
||||
if !will_remove_key {
|
||||
return Variable::Object(obj.clone());
|
||||
}
|
||||
|
||||
let mut new_map = map.deref().clone();
|
||||
for key in ZEN_RESERVED_PROPERTIES {
|
||||
new_map.remove(*key);
|
||||
}
|
||||
|
||||
let cleaned_map = new_map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, self.clone_clean(&v)))
|
||||
.collect();
|
||||
|
||||
Variable::from_object(cleaned_map)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ use serde::ser::SerializeMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use zen_expression::variable::{ToVariable, Variable};
|
||||
@@ -35,6 +34,7 @@ pub struct DecisionGraph {
|
||||
initial_graph: StableDiDecisionGraph,
|
||||
graph: StableDiDecisionGraph,
|
||||
config: DecisionGraphConfig,
|
||||
parent_nodes: Option<Variable>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -53,9 +53,14 @@ impl DecisionGraph {
|
||||
initial_graph: graph.clone(),
|
||||
graph,
|
||||
config,
|
||||
parent_nodes: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_parent_nodes(&mut self, nodes: Option<Variable>) {
|
||||
self.parent_nodes = nodes;
|
||||
}
|
||||
|
||||
fn build_graph(
|
||||
content: &GraphContent,
|
||||
) -> Result<StableDiDecisionGraph, DecisionGraphValidationError> {
|
||||
@@ -109,27 +114,39 @@ impl DecisionGraph {
|
||||
|
||||
async fn validation_schema(
|
||||
&self,
|
||||
node_id: &str,
|
||||
schema: Option<&serde_json::Value>,
|
||||
) -> Result<Option<(serde_json::Value, u64)>, String> {
|
||||
) -> Result<Option<(Arc<serde_json::Value>, u64)>, String> {
|
||||
let Some(schema) = schema else {
|
||||
return Ok(None);
|
||||
};
|
||||
if let Some(resolved) = &self.config.content.resolved_schemas {
|
||||
return Ok(resolved.get(node_id).cloned());
|
||||
}
|
||||
if !schema_dict::schema_references_dictionary(schema) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let dictionaries = schema_dict::load_import_dictionaries(
|
||||
self.config.extensions.loader(),
|
||||
&self.config.content.imports,
|
||||
)
|
||||
.await?;
|
||||
schema_dict::resolve_schema(schema, &dictionaries).map(Some)
|
||||
schema_dict::resolve_schema(schema, &dictionaries)
|
||||
.map(|resolved| Some((Arc::new(resolved.0), resolved.1)))
|
||||
}
|
||||
|
||||
fn build_node_context(&self, node: &DecisionNode, input: Variable) -> NodeContextBase {
|
||||
fn build_node_context(
|
||||
&self,
|
||||
node: &DecisionNode,
|
||||
input: Variable,
|
||||
nodes: Option<Variable>,
|
||||
) -> NodeContextBase {
|
||||
NodeContextBase {
|
||||
id: node.id.clone(),
|
||||
name: node.name.clone(),
|
||||
input,
|
||||
nodes,
|
||||
extensions: self.config.extensions.clone(),
|
||||
iteration: self.config.iteration,
|
||||
trace: match self.config.trace {
|
||||
@@ -164,33 +181,39 @@ impl DecisionGraph {
|
||||
}
|
||||
|
||||
let node = &self.graph[nid];
|
||||
let start = Instant::now();
|
||||
let (input, input_trace) = walker.incoming_node_data(&self.graph, nid, true);
|
||||
let mut base_ctx = self.build_node_context(node.deref(), input);
|
||||
let start = self.config.trace.then(Instant::now);
|
||||
let (input, input_trace) = walker.incoming_node_data(&self.graph, nid);
|
||||
let mut base_ctx = self.build_node_context(node.deref(), input, walker.nodes_context());
|
||||
|
||||
let node_execution = match &node.kind {
|
||||
DecisionNodeKind::InputNode { content } => {
|
||||
base_ctx.input = context.clone();
|
||||
match self.validation_schema(content.schema.as_deref()).await {
|
||||
match self
|
||||
.validation_schema(&node.id, content.schema.as_deref())
|
||||
.await
|
||||
{
|
||||
Err(message) => base_ctx.error(message),
|
||||
Ok(None) => handle_node(base_ctx, content.clone(), InputNodeHandler).await,
|
||||
Ok(Some((schema, salt))) => {
|
||||
base_ctx.config.validation_salt = salt;
|
||||
let resolved = InputNodeContent {
|
||||
schema: Some(Arc::new(schema)),
|
||||
schema: Some(schema),
|
||||
};
|
||||
handle_node(base_ctx, resolved, InputNodeHandler).await
|
||||
}
|
||||
}
|
||||
}
|
||||
DecisionNodeKind::OutputNode { content } => {
|
||||
match self.validation_schema(content.schema.as_deref()).await {
|
||||
match self
|
||||
.validation_schema(&node.id, content.schema.as_deref())
|
||||
.await
|
||||
{
|
||||
Err(message) => base_ctx.error(message),
|
||||
Ok(None) => handle_node(base_ctx, content.clone(), OutputNodeHandler).await,
|
||||
Ok(Some((schema, salt))) => {
|
||||
base_ctx.config.validation_salt = salt;
|
||||
let resolved = OutputNodeContent {
|
||||
schema: Some(Arc::new(schema)),
|
||||
schema: Some(schema),
|
||||
};
|
||||
handle_node(base_ctx, resolved, OutputNodeHandler).await
|
||||
}
|
||||
@@ -217,14 +240,19 @@ impl DecisionGraph {
|
||||
}
|
||||
};
|
||||
|
||||
tracer.record_execution(node.deref(), input_trace, &node_execution, start.elapsed());
|
||||
tracer.record_execution(
|
||||
node.deref(),
|
||||
input_trace,
|
||||
&node_execution,
|
||||
start.map(|s| s.elapsed()).unwrap_or_default(),
|
||||
);
|
||||
|
||||
let output = match node_execution {
|
||||
Ok(ok) => ok.output,
|
||||
Err(err) => {
|
||||
let mut cleaner = VariableCleaner::new();
|
||||
let trace = tracer.into_traces();
|
||||
if let Some(t) = &trace {
|
||||
let mut cleaner = VariableCleaner::new();
|
||||
t.values().for_each(|v| {
|
||||
cleaner.clean(&v.input);
|
||||
cleaner.clean(&v.output);
|
||||
@@ -242,11 +270,21 @@ impl DecisionGraph {
|
||||
}
|
||||
};
|
||||
|
||||
let nodes_view = match (&node.kind, &self.parent_nodes) {
|
||||
(DecisionNodeKind::InputNode { .. }, Some(parent_nodes)) => {
|
||||
let view = output.depth_clone(1);
|
||||
view.dot_insert(Variable::nodes_key().as_ref(), parent_nodes.clone());
|
||||
Some(view)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
walker.set_node_data(
|
||||
nid,
|
||||
NodeData {
|
||||
name: Rc::from(node.name.deref()),
|
||||
name: zen_types::symbol::Symbol::from(node.name.deref()),
|
||||
data: output,
|
||||
nodes_view,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub(crate) mod cleaner;
|
||||
mod error;
|
||||
pub(crate) mod graph;
|
||||
mod schema_dict;
|
||||
pub(crate) mod schema_dict;
|
||||
mod tracer;
|
||||
mod walker;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use petgraph::prelude::{EdgeIndex, NodeIndex, StableDiGraph};
|
||||
use petgraph::visit::{EdgeRef, IntoNodeIdentifiers, VisitMap, Visitable};
|
||||
use petgraph::{Incoming, Outgoing};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
@@ -22,8 +21,9 @@ use zen_expression::Isolate;
|
||||
pub(crate) type StableDiDecisionGraph = StableDiGraph<Arc<DecisionNode>, Arc<DecisionEdge>>;
|
||||
|
||||
pub(crate) struct NodeData {
|
||||
pub name: Rc<str>,
|
||||
pub name: zen_types::symbol::Symbol,
|
||||
pub data: Variable,
|
||||
pub nodes_view: Option<Variable>,
|
||||
}
|
||||
|
||||
pub(crate) struct GraphWalker {
|
||||
@@ -96,7 +96,10 @@ impl GraphWalker {
|
||||
let node_values = self
|
||||
.node_data
|
||||
.iter()
|
||||
.filter_map(|(_, nd)| Some((nd.name.clone(), nd.data.clone())))
|
||||
.filter_map(|(_, nd)| {
|
||||
let value = nd.nodes_view.clone().unwrap_or_else(|| nd.data.clone());
|
||||
Some((nd.name.clone(), value))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Variable::from_object(node_values)
|
||||
@@ -106,34 +109,35 @@ impl GraphWalker {
|
||||
self.node_data.insert(node_id, value);
|
||||
}
|
||||
|
||||
pub fn nodes_context(&self) -> Option<Variable> {
|
||||
self.nodes_in_context.then(|| self.get_all_node_data())
|
||||
}
|
||||
|
||||
pub fn incoming_node_data(
|
||||
&self,
|
||||
g: &StableDiDecisionGraph,
|
||||
node_id: NodeIndex,
|
||||
with_nodes: bool,
|
||||
) -> (Variable, Variable) {
|
||||
let value = self.merge_node_data(g.neighbors_directed(node_id, Incoming));
|
||||
|
||||
if self.nodes_in_context && with_nodes {
|
||||
if let Some(object_ref) = value.as_object() {
|
||||
let mut new_object = object_ref.borrow().clone();
|
||||
new_object.insert(Rc::from("$nodes"), self.get_all_node_data());
|
||||
|
||||
return (Variable::from_object(new_object), value);
|
||||
}
|
||||
}
|
||||
|
||||
(value.depth_clone(1), value)
|
||||
(value.clone(), value)
|
||||
}
|
||||
|
||||
pub fn merge_node_data<I>(&self, iter: I) -> Variable
|
||||
where
|
||||
I: Iterator<Item = NodeIndex>,
|
||||
{
|
||||
iter.filter_map(|nid| self.node_data.get(&nid))
|
||||
.fold(Variable::empty_object(), |mut prev, nd| {
|
||||
prev.merge_clone(&nd.data)
|
||||
})
|
||||
let mut incoming = iter.filter_map(|nid| self.node_data.get(&nid));
|
||||
let Some(first) = incoming.next() else {
|
||||
return Variable::empty_object();
|
||||
};
|
||||
|
||||
let head = match &first.data {
|
||||
Variable::Object(_) | Variable::Array(_) => first.data.clone(),
|
||||
_ => Variable::empty_object(),
|
||||
};
|
||||
|
||||
incoming.fold(head, |mut prev, nd| prev.merge_clone(&nd.data))
|
||||
}
|
||||
|
||||
pub fn next<F: FnMut(DecisionGraphTrace)>(
|
||||
@@ -163,8 +167,11 @@ impl GraphWalker {
|
||||
let decision_node = g.node_weight(nid)?.clone();
|
||||
if let DecisionNodeKind::SwitchNode { content } = &decision_node.kind {
|
||||
if !self.visited_switch_nodes.contains(&nid) {
|
||||
let (input, input_trace) = self.incoming_node_data(g, nid, true);
|
||||
let (input, input_trace) = self.incoming_node_data(g, nid);
|
||||
let mut isolate = Isolate::with_environment(input);
|
||||
if let Some(nodes) = self.nodes_context() {
|
||||
isolate.set_local(Variable::nodes_key(), nodes);
|
||||
}
|
||||
|
||||
let mut statement_iter = content.statements.iter();
|
||||
let valid_statements: Vec<SwitchStatementTraceRow> = match content.hit_policy {
|
||||
@@ -183,7 +190,6 @@ impl GraphWalker {
|
||||
|
||||
if let Some(on_trace) = &mut on_trace {
|
||||
let output = input_trace.depth_clone(1);
|
||||
output.dot_remove("$nodes");
|
||||
|
||||
on_trace(DecisionGraphTrace {
|
||||
id: decision_node.id.clone(),
|
||||
|
||||
@@ -306,9 +306,9 @@ impl DecisionEngine {
|
||||
|
||||
fn decision_from_graph_arc(&self, content: Arc<DecisionContent>) -> Decision {
|
||||
let graph: Arc<GraphContent> = match Arc::try_unwrap(content) {
|
||||
Ok(DecisionContent::Graph(g)) => Arc::new(g),
|
||||
Ok(DecisionContent::Graph(g)) => g,
|
||||
Err(arc) => match arc.as_ref() {
|
||||
DecisionContent::Graph(g) => Arc::new(g.clone()),
|
||||
DecisionContent::Graph(g) => g.clone(),
|
||||
DecisionContent::Policy(_) => {
|
||||
panic!("decision_from_graph_arc called with Policy variant")
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
// `deny` rather than `forbid` so `nodes::variable_json` can opt in: reading a
|
||||
// `RefCell` without a guard is the only way to hand jsonschema `'a` borrows.
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::loader::{DecisionLoader, DynamicLoader, LoaderResponse};
|
||||
use crate::model::DecisionContent;
|
||||
@@ -10,31 +9,71 @@ use crate::model::DecisionContent;
|
||||
#[derive(Debug)]
|
||||
pub struct CachedLoader {
|
||||
loader: DynamicLoader,
|
||||
cache: Mutex<HashMap<String, Arc<DecisionContent>>>,
|
||||
cache: RwLock<HashMap<String, Arc<DecisionContent>>>,
|
||||
}
|
||||
|
||||
impl From<DynamicLoader> for CachedLoader {
|
||||
fn from(value: DynamicLoader) -> Self {
|
||||
Self {
|
||||
loader: value,
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
cache: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compiled(content: Arc<DecisionContent>) -> Arc<DecisionContent> {
|
||||
let DecisionContent::Graph(graph) = content.as_ref() else {
|
||||
return content;
|
||||
};
|
||||
if graph.compiled_cache.is_some() {
|
||||
return content;
|
||||
}
|
||||
|
||||
let mut owned = (**graph).clone();
|
||||
owned.compile();
|
||||
Arc::new(DecisionContent::Graph(Arc::new(owned)))
|
||||
}
|
||||
|
||||
async fn prepared(loader: &DynamicLoader, content: Arc<DecisionContent>) -> Arc<DecisionContent> {
|
||||
let DecisionContent::Graph(graph) = content.as_ref() else {
|
||||
return content;
|
||||
};
|
||||
if graph.compiled_cache.is_some() && graph.resolved_schemas.is_some() {
|
||||
return content;
|
||||
}
|
||||
|
||||
let mut owned = (**graph).clone();
|
||||
owned.compile();
|
||||
let _ = owned.resolve_schemas(loader).await;
|
||||
Arc::new(DecisionContent::Graph(Arc::new(owned)))
|
||||
}
|
||||
|
||||
impl DecisionLoader for CachedLoader {
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
key: &'a str,
|
||||
) -> Pin<Box<dyn Future<Output = LoaderResponse> + 'a + Send>> {
|
||||
Box::pin(async move {
|
||||
let mut cache = self.cache.lock().await;
|
||||
if let Some(content) = cache.get(key) {
|
||||
return Ok(content.clone());
|
||||
}
|
||||
let cached = self
|
||||
.cache
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|cache| cache.get(key).cloned());
|
||||
|
||||
let decision_content = self.loader.load(key).await?;
|
||||
cache.insert(key.to_string(), decision_content.clone());
|
||||
let loaded = match &cached {
|
||||
Some(content) => content.clone(),
|
||||
None => self.loader.load(key).await?,
|
||||
};
|
||||
|
||||
let decision_content = prepared(&self.loader, loaded).await;
|
||||
let unchanged = cached
|
||||
.as_ref()
|
||||
.is_some_and(|content| Arc::ptr_eq(content, &decision_content));
|
||||
if !unchanged {
|
||||
if let Ok(mut cache) = self.cache.write() {
|
||||
cache.insert(key.to_string(), decision_content.clone());
|
||||
}
|
||||
}
|
||||
Ok(decision_content)
|
||||
})
|
||||
}
|
||||
@@ -44,15 +83,17 @@ impl DecisionLoader for CachedLoader {
|
||||
}
|
||||
|
||||
fn load_sync(&self, key: &str) -> Option<LoaderResponse> {
|
||||
let Ok(mut cache) = self.cache.try_lock() else {
|
||||
return self.loader.load_sync(key);
|
||||
};
|
||||
if let Some(content) = cache.get(key) {
|
||||
return Some(Ok(content.clone()));
|
||||
if let Ok(cache) = self.cache.read() {
|
||||
if let Some(content) = cache.get(key) {
|
||||
return Some(Ok(content.clone()));
|
||||
}
|
||||
}
|
||||
let response = self.loader.load_sync(key)?;
|
||||
|
||||
let response = self.loader.load_sync(key)?.map(compiled);
|
||||
if let Ok(content) = &response {
|
||||
cache.insert(key.to_string(), content.clone());
|
||||
if let Ok(mut cache) = self.cache.write() {
|
||||
cache.insert(key.to_string(), content.clone());
|
||||
}
|
||||
}
|
||||
Some(response)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::decision_graph::schema_dict;
|
||||
use crate::loader::DynamicLoader;
|
||||
use crate::nodes::decision_table::index::TableIndex;
|
||||
use crate::nodes::function::v2::strip::TypeStripper;
|
||||
use crate::nodes::validator_cache::ValidatorCache;
|
||||
use crate::policy::PolicyDocument;
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
@@ -9,7 +13,7 @@ use zen_types::decision::{DecisionEdge, DecisionNode, DecisionNodeKind, Function
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DecisionContent {
|
||||
Graph(GraphContent),
|
||||
Graph(Arc<GraphContent>),
|
||||
Policy(PolicyContent),
|
||||
}
|
||||
|
||||
@@ -26,7 +30,8 @@ impl<'de> Deserialize<'de> for DecisionContent {
|
||||
let content = if is_policy {
|
||||
serde_path_to_error::deserialize::<_, PolicyContent>(value).map(Self::Policy)
|
||||
} else {
|
||||
serde_path_to_error::deserialize::<_, GraphContent>(value).map(Self::Graph)
|
||||
serde_path_to_error::deserialize::<_, GraphContent>(value)
|
||||
.map(|graph| Self::Graph(Arc::new(graph)))
|
||||
};
|
||||
|
||||
content.map_err(serde::de::Error::custom)
|
||||
@@ -35,7 +40,7 @@ impl<'de> Deserialize<'de> for DecisionContent {
|
||||
|
||||
impl Default for DecisionContent {
|
||||
fn default() -> Self {
|
||||
Self::Graph(GraphContent::default())
|
||||
Self::Graph(Arc::new(GraphContent::default()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,19 +67,21 @@ impl DecisionContent {
|
||||
}
|
||||
|
||||
pub fn into_graph_arc(self: Arc<Self>) -> Option<Arc<GraphContent>> {
|
||||
match Arc::try_unwrap(self) {
|
||||
Ok(Self::Graph(g)) => Some(Arc::new(g)),
|
||||
Ok(Self::Policy(_)) => None,
|
||||
Err(arc) => match arc.as_ref() {
|
||||
Self::Graph(g) => Some(Arc::new(g.clone())),
|
||||
Self::Policy(_) => None,
|
||||
},
|
||||
match self.as_ref() {
|
||||
Self::Graph(g) => Some(g.clone()),
|
||||
Self::Policy(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GraphContent> for DecisionContent {
|
||||
fn from(value: GraphContent) -> Self {
|
||||
Self::Graph(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<GraphContent>> for DecisionContent {
|
||||
fn from(value: Arc<GraphContent>) -> Self {
|
||||
Self::Graph(value)
|
||||
}
|
||||
}
|
||||
@@ -105,6 +112,15 @@ pub struct GraphContent {
|
||||
|
||||
#[serde(skip)]
|
||||
pub stripped_functions: Option<Arc<HashMap<Arc<str>, Arc<str>>>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub resolved_schemas: Option<Arc<HashMap<Arc<str>, (Arc<serde_json::Value>, u64)>>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(crate) validator_cache: ValidatorCache,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(crate) dt_indexes: Option<Arc<HashMap<Arc<str>, TableIndex>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@@ -114,6 +130,7 @@ pub struct PolicyContent(pub Arc<PolicyDocument>);
|
||||
impl GraphContent {
|
||||
pub fn compile(&mut self) {
|
||||
self.compile_functions();
|
||||
self.build_dt_indexes();
|
||||
if self.compiled_cache.is_some() {
|
||||
return;
|
||||
}
|
||||
@@ -185,6 +202,61 @@ impl GraphContent {
|
||||
self.compiled_cache.replace(Arc::new(cache));
|
||||
}
|
||||
|
||||
fn build_dt_indexes(&mut self) {
|
||||
if self.dt_indexes.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let indexes: HashMap<Arc<str>, TableIndex> = self
|
||||
.nodes
|
||||
.iter()
|
||||
.filter_map(|node| match &node.kind {
|
||||
DecisionNodeKind::DecisionTableNode { content } => {
|
||||
TableIndex::build(&content.inputs, &content.rules)
|
||||
.map(|index| (node.id.clone(), index))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.dt_indexes = Some(Arc::new(indexes));
|
||||
}
|
||||
|
||||
pub async fn resolve_schemas(&mut self, loader: &DynamicLoader) -> Result<(), String> {
|
||||
if self.resolved_schemas.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut referencing: Vec<(Arc<str>, Arc<serde_json::Value>)> = Vec::new();
|
||||
for node in &self.nodes {
|
||||
let schema = match &node.kind {
|
||||
DecisionNodeKind::InputNode { content } => content.schema.as_ref(),
|
||||
DecisionNodeKind::OutputNode { content } => content.schema.as_ref(),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(schema) = schema {
|
||||
if schema_dict::schema_references_dictionary(schema) {
|
||||
referencing.push((node.id.clone(), schema.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if referencing.is_empty() {
|
||||
self.resolved_schemas = Some(Arc::new(HashMap::new()));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dictionaries = schema_dict::load_import_dictionaries(loader, &self.imports).await?;
|
||||
let mut resolved = HashMap::with_capacity(referencing.len());
|
||||
for (id, schema) in referencing {
|
||||
let (value, salt) = schema_dict::resolve_schema(&schema, &dictionaries)?;
|
||||
resolved.insert(id, (Arc::new(value), salt));
|
||||
}
|
||||
|
||||
self.resolved_schemas = Some(Arc::new(resolved));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_functions(&mut self) {
|
||||
if self.stripped_functions.is_some() {
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::nodes::definition::{NodeDataType, TraceDataType};
|
||||
use crate::nodes::extensions::NodeHandlerExtensions;
|
||||
use crate::nodes::function::v2::function::Function;
|
||||
use crate::nodes::result::{NodeResponse, NodeResult};
|
||||
use crate::nodes::variable_json::{Guards, VariableNode};
|
||||
use crate::nodes::NodeError;
|
||||
use crate::ZEN_CONFIG;
|
||||
use ahash::AHasher;
|
||||
@@ -14,6 +15,7 @@ use std::hash::Hasher;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use zen_expression::Isolate;
|
||||
use zen_types::variable::{ToVariable, Variable};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -26,6 +28,7 @@ where
|
||||
pub name: Arc<str>,
|
||||
pub node: NodeData,
|
||||
pub input: Variable,
|
||||
pub nodes: Option<Variable>,
|
||||
pub trace: Option<RefCell<TraceData>>,
|
||||
pub extensions: NodeHandlerExtensions,
|
||||
pub iteration: u8,
|
||||
@@ -37,11 +40,25 @@ where
|
||||
NodeData: NodeDataType,
|
||||
TraceData: TraceDataType,
|
||||
{
|
||||
pub fn input_with_nodes(&self) -> Variable {
|
||||
let Some(nodes) = &self.nodes else {
|
||||
return self.input.shallow_clone();
|
||||
};
|
||||
let Variable::Object(object) = &self.input else {
|
||||
return self.input.shallow_clone();
|
||||
};
|
||||
|
||||
let mut map = object.borrow().clone();
|
||||
map.insert(Variable::nodes_key(), nodes.clone());
|
||||
Variable::from_object(map)
|
||||
}
|
||||
|
||||
pub fn from_base(base: NodeContextBase, data: NodeData) -> Self {
|
||||
Self {
|
||||
id: base.id,
|
||||
name: base.name,
|
||||
input: base.input,
|
||||
nodes: base.nodes,
|
||||
extensions: base.extensions,
|
||||
iteration: base.iteration,
|
||||
trace: base.config.trace.then(|| Default::default()),
|
||||
@@ -50,6 +67,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isolate(&self) -> Isolate {
|
||||
make_isolate(&self.input, self.nodes.as_ref(), &self.extensions)
|
||||
}
|
||||
|
||||
pub fn trace<Function>(&self, mutator: Function)
|
||||
where
|
||||
Function: FnOnce(&mut TraceData),
|
||||
@@ -88,7 +109,7 @@ where
|
||||
self.extensions.function_runtime().await.node_context(self)
|
||||
}
|
||||
|
||||
pub fn validate(&self, schema: &Value, value: &Value) -> Result<(), NodeError> {
|
||||
pub fn validate(&self, schema: &Value, value: &Variable) -> Result<(), NodeError> {
|
||||
let validator_cache = self.extensions.validator_cache();
|
||||
let hash = self.hash_node();
|
||||
|
||||
@@ -96,8 +117,9 @@ where
|
||||
.get_or_insert(hash, schema)
|
||||
.node_context(self)?;
|
||||
|
||||
let guards = Guards::default();
|
||||
validator
|
||||
.validate(value)
|
||||
.validate(VariableNode::new(value, &guards))
|
||||
.map_err(|err| ValidationErrorJson::from(err))
|
||||
.node_context(self)?;
|
||||
|
||||
@@ -188,13 +210,32 @@ pub struct NodeContextBase {
|
||||
pub id: Arc<str>,
|
||||
pub name: Arc<str>,
|
||||
pub input: Variable,
|
||||
pub nodes: Option<Variable>,
|
||||
pub iteration: u8,
|
||||
pub extensions: NodeHandlerExtensions,
|
||||
pub config: NodeContextConfig,
|
||||
pub trace: Option<RefCell<Variable>>,
|
||||
}
|
||||
|
||||
pub(crate) fn make_isolate(
|
||||
input: &Variable,
|
||||
nodes: Option<&Variable>,
|
||||
extensions: &NodeHandlerExtensions,
|
||||
) -> Isolate {
|
||||
let mut isolate =
|
||||
Isolate::with_environment(input.clone()).with_cache(extensions.compiled_cache.clone());
|
||||
if let Some(nodes) = nodes {
|
||||
isolate.set_local(Variable::nodes_key(), nodes.clone());
|
||||
}
|
||||
|
||||
isolate
|
||||
}
|
||||
|
||||
impl NodeContextBase {
|
||||
pub fn isolate(&self) -> Isolate {
|
||||
make_isolate(&self.input, self.nodes.as_ref(), &self.extensions)
|
||||
}
|
||||
|
||||
pub fn error<Error>(&self, error: Error) -> NodeResult
|
||||
where
|
||||
Error: Into<Box<dyn std::error::Error>>,
|
||||
@@ -245,6 +286,7 @@ where
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
input: value.input,
|
||||
nodes: value.nodes,
|
||||
extensions: value.extensions,
|
||||
iteration: value.iteration,
|
||||
config: value.config,
|
||||
@@ -308,7 +350,7 @@ impl Display for ValidationErrorJson {
|
||||
impl<'a> From<ValidationError<'a>> for ValidationErrorJson {
|
||||
fn from(value: ValidationError<'a>) -> Self {
|
||||
ValidationErrorJson {
|
||||
path: value.instance_path.to_string(),
|
||||
path: value.instance_path().to_string(),
|
||||
message: format!("{}", value),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ impl NodeHandler for CustomNodeHandler {
|
||||
|
||||
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
|
||||
let custom_node_request = CustomNodeRequest {
|
||||
input: ctx.input.clone(),
|
||||
input: ctx.input_with_nodes(),
|
||||
node: CustomDecisionNode {
|
||||
id: ctx.id.clone(),
|
||||
name: ctx.name.clone(),
|
||||
|
||||
@@ -38,37 +38,53 @@ impl NodeHandler for DecisionNodeHandler {
|
||||
}
|
||||
|
||||
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
|
||||
let loader = ctx.extensions.loader();
|
||||
let sub_decision = loader.load(ctx.node.key.deref()).await.node_context(&ctx)?;
|
||||
let sub_kind = sub_decision.kind();
|
||||
let Some(sub_graph) = sub_decision.into_graph_arc() else {
|
||||
return ctx.error(format!(
|
||||
"sub-decision '{}' is a {sub_kind}, expected graph",
|
||||
ctx.node.key
|
||||
));
|
||||
};
|
||||
|
||||
let mut decision_graph_ref = self.decision_graph.borrow_mut();
|
||||
let decision_graph = match decision_graph_ref.as_mut() {
|
||||
Some(dg) => dg,
|
||||
None => {
|
||||
let dg = DecisionGraph::try_new(DecisionGraphConfig {
|
||||
content: sub_graph,
|
||||
extensions: ctx.extensions.clone(),
|
||||
trace: ctx.config.trace,
|
||||
iteration: ctx.iteration + 1,
|
||||
max_depth: ctx.config.max_depth,
|
||||
})
|
||||
.node_context(&ctx)?;
|
||||
|
||||
*decision_graph_ref = Some(dg);
|
||||
match decision_graph_ref.as_mut() {
|
||||
Some(dg) => dg,
|
||||
None => return ctx.error("Failed to initialize decision graph".to_string()),
|
||||
}
|
||||
}
|
||||
if decision_graph_ref.is_none() {
|
||||
let loader = ctx.extensions.loader();
|
||||
let sub_decision = loader.load(ctx.node.key.deref()).await.node_context(&ctx)?;
|
||||
let sub_kind = sub_decision.kind();
|
||||
let Some(sub_graph) = sub_decision.into_graph_arc() else {
|
||||
return ctx.error(format!(
|
||||
"sub-decision '{}' is a {sub_kind}, expected graph",
|
||||
ctx.node.key
|
||||
));
|
||||
};
|
||||
|
||||
let sub_graph =
|
||||
if sub_graph.compiled_cache.is_some() && sub_graph.resolved_schemas.is_some() {
|
||||
sub_graph
|
||||
} else {
|
||||
let mut owned = (*sub_graph).clone();
|
||||
owned.compile();
|
||||
let _ = owned.resolve_schemas(loader).await;
|
||||
std::sync::Arc::new(owned)
|
||||
};
|
||||
|
||||
let mut extensions = ctx.extensions.clone();
|
||||
extensions.compiled_cache = sub_graph.compiled_cache.clone();
|
||||
extensions.dt_indexes = sub_graph.dt_indexes.clone();
|
||||
extensions.validator_cache =
|
||||
std::sync::Arc::new(std::cell::OnceCell::from(sub_graph.validator_cache.clone()));
|
||||
|
||||
let dg = DecisionGraph::try_new(DecisionGraphConfig {
|
||||
content: sub_graph,
|
||||
extensions,
|
||||
trace: ctx.config.trace,
|
||||
iteration: ctx.iteration + 1,
|
||||
max_depth: ctx.config.max_depth,
|
||||
})
|
||||
.node_context(&ctx)?;
|
||||
|
||||
*decision_graph_ref = Some(dg);
|
||||
}
|
||||
|
||||
let Some(decision_graph) = decision_graph_ref.as_mut() else {
|
||||
return ctx.error("Failed to initialize decision graph".to_string());
|
||||
};
|
||||
|
||||
decision_graph.set_parent_nodes(ctx.nodes.clone());
|
||||
|
||||
let evaluate_result = Box::pin(decision_graph.evaluate(ctx.input.clone())).await;
|
||||
match evaluate_result {
|
||||
Ok(result) => {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
use ahash::HashMap;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use rust_decimal::Decimal;
|
||||
use std::sync::Arc;
|
||||
use zen_expression::intellisense::{ArmTest, IntelliSense};
|
||||
use zen_types::decision::DecisionTableInputField;
|
||||
use zen_types::variable::Variable;
|
||||
|
||||
pub(crate) const MIN_INDEX_ROWS: usize = 8;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct TableIndex {
|
||||
pub(crate) columns: Vec<Option<ColumnIndex>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct ColumnIndex {
|
||||
strings: HashMap<Arc<str>, FixedBitSet>,
|
||||
numbers: HashMap<Decimal, FixedBitSet>,
|
||||
bools: HashMap<bool, FixedBitSet>,
|
||||
captured: FixedBitSet,
|
||||
pub(crate) fallback: FixedBitSet,
|
||||
}
|
||||
|
||||
impl TableIndex {
|
||||
pub(crate) fn build(
|
||||
inputs: &[DecisionTableInputField],
|
||||
rules: &[HashMap<Arc<str>, Arc<str>>],
|
||||
) -> Option<TableIndex> {
|
||||
let rows = rules.len();
|
||||
if rows < MIN_INDEX_ROWS {
|
||||
return None;
|
||||
}
|
||||
let mut intellisense = IntelliSense::new();
|
||||
let columns: Vec<Option<ColumnIndex>> = inputs
|
||||
.iter()
|
||||
.map(|col| ColumnIndex::build(col, rules, rows, &mut intellisense))
|
||||
.collect();
|
||||
columns
|
||||
.iter()
|
||||
.any(Option::is_some)
|
||||
.then_some(TableIndex { columns })
|
||||
}
|
||||
|
||||
pub(crate) fn decides(&self, col_idx: usize, row_idx: usize) -> bool {
|
||||
self.columns
|
||||
.get(col_idx)
|
||||
.and_then(Option::as_ref)
|
||||
.is_some_and(|c| c.captured.contains(row_idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl ColumnIndex {
|
||||
fn build(
|
||||
col: &DecisionTableInputField,
|
||||
rules: &[HashMap<Arc<str>, Arc<str>>],
|
||||
rows: usize,
|
||||
intellisense: &mut IntelliSense,
|
||||
) -> Option<ColumnIndex> {
|
||||
if col.field.as_deref().is_none_or(|f| f.is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let mut strings: HashMap<Arc<str>, FixedBitSet> = HashMap::default();
|
||||
let mut numbers: HashMap<Decimal, FixedBitSet> = HashMap::default();
|
||||
let mut bools: HashMap<bool, FixedBitSet> = HashMap::default();
|
||||
let mut captured = FixedBitSet::with_capacity(rows);
|
||||
let mut fallback = FixedBitSet::with_capacity(rows);
|
||||
|
||||
for (row_idx, rule) in rules.iter().enumerate() {
|
||||
let Some(cell) = rule.get(&col.id).filter(|c| !c.is_empty()) else {
|
||||
fallback.insert(row_idx);
|
||||
continue;
|
||||
};
|
||||
match intellisense.cell_test(cell) {
|
||||
ArmTest::Enum { values, .. } => {
|
||||
for value in values {
|
||||
strings
|
||||
.entry(Arc::from(value.as_ref()))
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
ArmTest::Bool { values, .. } => {
|
||||
for value in values {
|
||||
bools
|
||||
.entry(value)
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
ArmTest::Number { cover, .. } => match cover.points() {
|
||||
Some(points) => {
|
||||
for point in points {
|
||||
numbers
|
||||
.entry(point.normalize())
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
None => {
|
||||
fallback.insert(row_idx);
|
||||
}
|
||||
},
|
||||
ArmTest::Default | ArmTest::Unrecognized => {
|
||||
fallback.insert(row_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(captured.count_ones(..) > 0).then_some(ColumnIndex {
|
||||
strings,
|
||||
numbers,
|
||||
bools,
|
||||
captured,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn rows_for(&self, value: &Variable) -> Option<&FixedBitSet> {
|
||||
match value {
|
||||
Variable::String(s) => self.strings.get(s.as_str()),
|
||||
Variable::Number(n) => self.numbers.get(&n.normalize()),
|
||||
Variable::Bool(b) => self.bools.get(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,20 @@ use crate::nodes::definition::NodeHandler;
|
||||
use crate::nodes::result::NodeResult;
|
||||
use crate::nodes::{NodeContext, NodeResponse};
|
||||
use ahash::HashMap;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use index::TableIndex;
|
||||
use serde::Serialize;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use zen_expression::variable::ToVariable;
|
||||
use zen_expression::Isolate;
|
||||
use zen_types::decision::{DecisionTableContent, DecisionTableHitPolicy, TransformAttributes};
|
||||
use zen_types::decision::{
|
||||
DecisionTableContent, DecisionTableHitPolicy, DecisionTableInputField, TransformAttributes,
|
||||
};
|
||||
use zen_types::variable::Variable;
|
||||
pub(crate) mod index;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DecisionTableNodeHandler;
|
||||
|
||||
@@ -38,12 +44,20 @@ impl NodeHandler for DecisionTableNodeHandler {
|
||||
|
||||
impl DecisionTableNodeHandler {
|
||||
fn handle_first_hit(&self, ctx: DecisionTableContext) -> NodeResult {
|
||||
let mut isolate = Isolate::with_environment(ctx.input.depth_clone(1))
|
||||
.with_cache(ctx.extensions.compiled_cache.clone());
|
||||
let mut isolate = ctx.isolate();
|
||||
|
||||
if !ctx.config.trace {
|
||||
for rule in ctx.node.rules.iter() {
|
||||
if let Some(RowResult::Output(output)) = self.evaluate_row(&ctx, rule, &mut isolate)
|
||||
let index = Self::table_index(&ctx);
|
||||
let candidates =
|
||||
index.and_then(|ix| Self::candidate_rows(ix, &ctx.node.inputs, &mut isolate));
|
||||
let pruner = candidates.as_ref().and(index);
|
||||
for (row_idx, rule) in ctx.node.rules.iter().enumerate() {
|
||||
if candidates.as_ref().is_some_and(|c| !c.contains(row_idx)) {
|
||||
continue;
|
||||
}
|
||||
let pruned = pruner.map(|ix| (ix, row_idx));
|
||||
if let Some(RowResult::Output(output)) =
|
||||
self.evaluate_row(&ctx, rule, &mut isolate, pruned)
|
||||
{
|
||||
return ctx.success(output);
|
||||
}
|
||||
@@ -55,7 +69,7 @@ impl DecisionTableNodeHandler {
|
||||
}
|
||||
|
||||
let hit = ctx.node.rules.iter().enumerate().find_map(|(index, rule)| {
|
||||
match self.evaluate_row(&ctx, rule, &mut isolate)? {
|
||||
match self.evaluate_row(&ctx, rule, &mut isolate, None)? {
|
||||
RowResult::WithTrace {
|
||||
output,
|
||||
reference_map,
|
||||
@@ -88,11 +102,21 @@ impl DecisionTableNodeHandler {
|
||||
fn handle_collect(&self, ctx: DecisionTableContext) -> NodeResult {
|
||||
let mut outputs = Vec::new();
|
||||
let mut traces = Vec::new();
|
||||
let mut isolate = Isolate::with_environment(ctx.input.depth_clone(1))
|
||||
.with_cache(ctx.extensions.compiled_cache.clone());
|
||||
let mut isolate = ctx.isolate();
|
||||
|
||||
let table_index = (!ctx.config.trace)
|
||||
.then(|| Self::table_index(&ctx))
|
||||
.flatten();
|
||||
let candidates =
|
||||
table_index.and_then(|ix| Self::candidate_rows(ix, &ctx.node.inputs, &mut isolate));
|
||||
let pruner = candidates.as_ref().and(table_index);
|
||||
|
||||
for (index, rule) in ctx.node.rules.iter().enumerate() {
|
||||
if let Some(result) = self.evaluate_row(&ctx, rule, &mut isolate) {
|
||||
if candidates.as_ref().is_some_and(|c| !c.contains(index)) {
|
||||
continue;
|
||||
}
|
||||
let pruned = pruner.map(|ix| (ix, index));
|
||||
if let Some(result) = self.evaluate_row(&ctx, rule, &mut isolate, pruned) {
|
||||
match result {
|
||||
RowResult::Output(output) => {
|
||||
outputs.push(output);
|
||||
@@ -146,14 +170,63 @@ impl DecisionTableNodeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn table_index(ctx: &DecisionTableContext) -> Option<&TableIndex> {
|
||||
ctx.extensions.dt_indexes.as_ref()?.get(&ctx.id)
|
||||
}
|
||||
|
||||
fn candidate_rows(
|
||||
index: &TableIndex,
|
||||
inputs: &[DecisionTableInputField],
|
||||
isolate: &mut Isolate,
|
||||
) -> Option<FixedBitSet> {
|
||||
let mut acc: Option<FixedBitSet> = None;
|
||||
for (col_idx, column) in index.columns.iter().enumerate() {
|
||||
let Some(column) = column else {
|
||||
continue;
|
||||
};
|
||||
let Some(field) = inputs[col_idx].field.as_ref().filter(|f| !f.is_empty()) else {
|
||||
continue;
|
||||
};
|
||||
isolate.set_reference(field).ok()?;
|
||||
let value = isolate.get_reference(field)?;
|
||||
if matches!(value, Variable::Dynamic(_)) {
|
||||
return None;
|
||||
}
|
||||
let hit = column.rows_for(&value);
|
||||
match &mut acc {
|
||||
None => {
|
||||
let mut first = column.fallback.clone();
|
||||
if let Some(hit) = hit {
|
||||
first.union_with(hit);
|
||||
}
|
||||
acc = Some(first);
|
||||
}
|
||||
Some(acc) => {
|
||||
let fallback = column.fallback.as_slice();
|
||||
let hit = hit.map(FixedBitSet::as_slice).unwrap_or_default();
|
||||
for (i, word) in acc.as_mut_slice().iter_mut().enumerate() {
|
||||
let f = fallback.get(i).copied().unwrap_or(0);
|
||||
let h = hit.get(i).copied().unwrap_or(0);
|
||||
*word &= f | h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
fn evaluate_row<'a>(
|
||||
&self,
|
||||
ctx: &'a DecisionTableContext,
|
||||
rule: &'a HashMap<Arc<str>, Arc<str>>,
|
||||
isolate: &mut Isolate,
|
||||
pruned: Option<(&TableIndex, usize)>,
|
||||
) -> Option<RowResult> {
|
||||
let content = &ctx.node;
|
||||
for input in content.inputs.iter() {
|
||||
for (col_idx, input) in content.inputs.iter().enumerate() {
|
||||
if pruned.is_some_and(|(ix, row_idx)| ix.decides(col_idx, row_idx)) {
|
||||
continue;
|
||||
}
|
||||
let Some(rule_value) = rule.get(&input.id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ use std::rc::Rc;
|
||||
use crate::nodes::context::{NodeContext, NodeContextExt};
|
||||
use crate::nodes::definition::NodeHandler;
|
||||
use zen_expression::variable::{ToVariable, Variable};
|
||||
use zen_expression::Isolate;
|
||||
use zen_types::decision::TransformAttributes;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -28,8 +27,7 @@ impl NodeHandler for ExpressionNodeHandler {
|
||||
|
||||
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
|
||||
let result = Variable::empty_object();
|
||||
let mut isolate = Isolate::with_environment(ctx.input.depth_clone(1))
|
||||
.with_cache(ctx.extensions.compiled_cache.clone());
|
||||
let mut isolate = ctx.isolate();
|
||||
|
||||
for expression in ctx.node.expressions.iter() {
|
||||
if expression.key.is_empty() || expression.value.is_empty() {
|
||||
@@ -50,14 +48,7 @@ impl NodeHandler for ExpressionNodeHandler {
|
||||
);
|
||||
});
|
||||
|
||||
isolate.update_environment(|env| {
|
||||
let Some(environment) = env else {
|
||||
return;
|
||||
};
|
||||
|
||||
let key = format!("$.{}", &expression.key);
|
||||
let _ = environment.dot_insert(key.as_str(), value.depth_clone(2));
|
||||
});
|
||||
isolate.insert_dollar(&expression.key, value.clone());
|
||||
|
||||
result.dot_insert(&expression.key, value);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::loader::{DynamicLoader, NoopLoader};
|
||||
use crate::nodes::custom::{DynamicCustomNode, NoopCustomNode};
|
||||
use crate::nodes::decision_table::index::TableIndex;
|
||||
use crate::nodes::function::http_handler::DynamicHttpHandler;
|
||||
use crate::nodes::function::v2::function::{Function, FunctionConfig};
|
||||
use crate::nodes::function::v2::module::console::ConsoleListener;
|
||||
@@ -21,6 +22,7 @@ pub struct NodeHandlerExtensions {
|
||||
pub(crate) http_handler: DynamicHttpHandler,
|
||||
pub(crate) compiled_cache: Option<Arc<OpcodeCache>>,
|
||||
pub(crate) stripped_functions: Option<Arc<ahash::HashMap<Arc<str>, Arc<str>>>>,
|
||||
pub(crate) dt_indexes: Option<Arc<ahash::HashMap<Arc<str>, TableIndex>>>,
|
||||
}
|
||||
|
||||
impl Default for NodeHandlerExtensions {
|
||||
@@ -33,6 +35,7 @@ impl Default for NodeHandlerExtensions {
|
||||
custom_node: Arc::new(NoopCustomNode::default()),
|
||||
compiled_cache: None,
|
||||
stripped_functions: None,
|
||||
dt_indexes: None,
|
||||
http_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ impl NodeHandler for FunctionNodeHandler {
|
||||
id: ctx.id.clone(),
|
||||
name: ctx.name.clone(),
|
||||
input: ctx.input.clone(),
|
||||
nodes: ctx.nodes.clone(),
|
||||
extensions: ctx.extensions.clone(),
|
||||
trace: ctx.config.trace.then(|| Default::default()),
|
||||
iteration: ctx.iteration,
|
||||
@@ -43,6 +44,7 @@ impl NodeHandler for FunctionNodeHandler {
|
||||
id: ctx.id.clone(),
|
||||
name: ctx.name.clone(),
|
||||
input: ctx.input.clone(),
|
||||
nodes: ctx.nodes.clone(),
|
||||
extensions: ctx.extensions.clone(),
|
||||
trace: ctx.config.trace.then(|| Default::default()),
|
||||
iteration: ctx.iteration,
|
||||
|
||||
@@ -30,7 +30,7 @@ impl NodeHandler for FunctionV1NodeHandler {
|
||||
runtime.set_interrupt_handler(Some(interrupt_handler));
|
||||
|
||||
let mut script = Script::new(runtime.clone());
|
||||
let result_response = script.call(ctx.node.deref(), &ctx.input).await;
|
||||
let result_response = script.call(ctx.node.deref(), &ctx.input_with_nodes()).await;
|
||||
|
||||
runtime.set_interrupt_handler(None);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use rquickjs::{Context, Ctx, Error as QError, FromJs, Module, Runtime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use zen_expression::variable::Variable;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -59,7 +58,7 @@ impl Script {
|
||||
fn map_js_error(ctx: &Ctx, e: QError) -> anyhow::Error {
|
||||
let error = JsValue::from_js(&ctx, ctx.catch())
|
||||
.map(|v| v.0)
|
||||
.unwrap_or(Variable::String(Rc::from(e.to_string().as_str())));
|
||||
.unwrap_or(Variable::String((e.to_string().as_str()).into()));
|
||||
|
||||
anyhow::Error::msg(error.to_string())
|
||||
}
|
||||
|
||||
@@ -65,10 +65,8 @@ impl NodeHandler for FunctionV2NodeHandler {
|
||||
.function_context(&function_context)
|
||||
.await?;
|
||||
|
||||
let input_omit_nodes = ctx.input.depth_clone(1);
|
||||
input_omit_nodes.dot_remove("$nodes");
|
||||
let response_result = function
|
||||
.call_handler(&module_name, JsValueWithNodes(JsValue(input_omit_nodes)))
|
||||
.call_handler(&module_name, JsValueWithNodes(JsValue(ctx.input.clone())))
|
||||
.await;
|
||||
|
||||
function.runtime().set_interrupt_handler(None).await;
|
||||
@@ -97,7 +95,7 @@ impl FunctionV2NodeHandler {
|
||||
|
||||
ctx.globals().set("config", config).catch(&ctx)?;
|
||||
|
||||
let nodes_data = node_ctx.input.dot("$nodes").unwrap_or_default();
|
||||
let nodes_data = node_ctx.nodes.clone().unwrap_or_default();
|
||||
|
||||
ctx.globals()
|
||||
.set(
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::decision_graph::cleaner::ZEN_RESERVED_PROPERTIES;
|
||||
use crate::nodes::function::v2::error::ResultExt;
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use nohash_hasher::BuildNoHashHasher;
|
||||
use rquickjs::{Ctx, FromJs, IntoJs, Type, Value as QValue};
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap as StdHashMap;
|
||||
use std::rc::Rc;
|
||||
use zen_expression::variable::Variable;
|
||||
use zen_expression::variable::{Variable, VariableMap};
|
||||
use zen_types::rccell::RcCell;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct JsValue(pub(crate) Variable);
|
||||
@@ -40,7 +39,7 @@ impl<'js> FromJs<'js> for JsValue {
|
||||
v.into_string()
|
||||
.map(|s| s.to_string().ok())
|
||||
.flatten()
|
||||
.map(|s| Rc::from(s.as_str()))
|
||||
.map(|s| zen_types::symbol::Symbol::from(s.as_str()))
|
||||
.or_throw_msg(ctx, "failed to convert to string")?,
|
||||
),
|
||||
Type::Array => {
|
||||
@@ -60,14 +59,17 @@ impl<'js> FromJs<'js> for JsValue {
|
||||
.into_object()
|
||||
.or_throw_msg(ctx, "failed to convert to object")?;
|
||||
|
||||
let mut js_object = HashMap::with_capacity(object.len());
|
||||
let mut js_object = VariableMap::new();
|
||||
for p in object.props::<String, QValue>() {
|
||||
let (k, v) = p?;
|
||||
if ZEN_RESERVED_PROPERTIES.contains(&k.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
js_object.insert(Rc::from(k.as_str()), JsValue::from_js(ctx, v)?.0);
|
||||
js_object.insert(
|
||||
zen_types::symbol::Symbol::from(k.as_str()),
|
||||
JsValue::from_js(ctx, v)?.0,
|
||||
);
|
||||
}
|
||||
|
||||
Variable::from_object(js_object)
|
||||
@@ -167,7 +169,7 @@ impl<'r, 'js> JsConverter<'r, 'js> {
|
||||
Variable::Number(n) => n.to_f64().into_js(self.ctx),
|
||||
Variable::String(str) => str.into_js(self.ctx),
|
||||
Variable::Array(a) => {
|
||||
let addr = Rc::as_ptr(&a) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(&a) as *const () as usize;
|
||||
if let Some(cached) = self.cache.get(&addr) {
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
@@ -183,7 +185,7 @@ impl<'r, 'js> JsConverter<'r, 'js> {
|
||||
Ok(val)
|
||||
}
|
||||
Variable::Object(o) => {
|
||||
let addr = Rc::as_ptr(&o) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(&o) as *const () as usize;
|
||||
if let Some(cached) = self.cache.get(&addr) {
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
@@ -191,7 +193,7 @@ impl<'r, 'js> JsConverter<'r, 'js> {
|
||||
let qmap = rquickjs::Object::new(self.ctx.clone())?;
|
||||
let obj = o.borrow();
|
||||
for (key, value) in obj.iter() {
|
||||
qmap.set(key.as_ref(), self.convert_with_cache(value)?)?;
|
||||
qmap.set(key.as_str(), self.convert_with_cache(value)?)?;
|
||||
}
|
||||
|
||||
let val = qmap.into_value();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::decision_graph::cleaner::VariableCleaner;
|
||||
use crate::nodes::definition::NodeHandler;
|
||||
use crate::nodes::result::NodeResult;
|
||||
use crate::nodes::NodeContext;
|
||||
@@ -17,8 +16,7 @@ impl NodeHandler for InputNodeHandler {
|
||||
|
||||
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
|
||||
if let Some(json_schema) = &ctx.node.schema {
|
||||
let input_json = VariableCleaner::new().clone_clean(&ctx.input).to_value();
|
||||
ctx.validate(json_schema, &input_json)?;
|
||||
ctx.validate(json_schema, &ctx.input)?;
|
||||
};
|
||||
|
||||
ctx.success(ctx.input.clone())
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod output;
|
||||
mod result;
|
||||
pub(crate) mod transform_attributes;
|
||||
pub(crate) mod validator_cache;
|
||||
pub mod variable_json;
|
||||
|
||||
pub use context::{NodeContext, NodeContextBase, NodeContextConfig, NodeContextExt};
|
||||
pub use definition::NodeHandler;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::decision_graph::cleaner::VariableCleaner;
|
||||
use crate::nodes::definition::NodeHandler;
|
||||
use crate::nodes::result::NodeResult;
|
||||
use crate::nodes::NodeContext;
|
||||
@@ -17,8 +16,7 @@ impl NodeHandler for OutputNodeHandler {
|
||||
|
||||
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> NodeResult {
|
||||
if let Some(json_schema) = &ctx.node.schema {
|
||||
let input_json = VariableCleaner::new().clone_clean(&ctx.input).to_value();
|
||||
ctx.validate(json_schema, &input_json)?;
|
||||
ctx.validate(json_schema, &ctx.input)?;
|
||||
};
|
||||
|
||||
ctx.success(ctx.input.clone())
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::nodes::result::NodeResult;
|
||||
use crate::nodes::{NodeContextBase, NodeContextExt};
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use zen_expression::{Isolate, Variable};
|
||||
use zen_expression::Variable;
|
||||
|
||||
pub(crate) trait TransformAttributesExecution {
|
||||
async fn run_with<F, Fut>(&self, ctx: NodeContextBase, evaluate: F) -> NodeResult
|
||||
@@ -21,33 +21,10 @@ impl TransformAttributesExecution for TransformAttributes {
|
||||
let input = match &self.input_field {
|
||||
None => ctx.input.clone(),
|
||||
Some(input_field) => {
|
||||
let mut isolate = Isolate::new();
|
||||
isolate.set_environment(ctx.input.clone());
|
||||
let calculated_input = isolate
|
||||
let mut isolate = ctx.isolate();
|
||||
isolate
|
||||
.run_standard(input_field.deref())
|
||||
.node_context_message(&ctx, "Failed to evaluate expression")?;
|
||||
|
||||
let nodes = ctx.input.dot("$nodes").unwrap_or(Variable::Null);
|
||||
match &calculated_input {
|
||||
Variable::Array(arr) => {
|
||||
let arr = arr.borrow();
|
||||
let s: Vec<_> = arr
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let new_v = v.depth_clone(1);
|
||||
new_v.dot_insert("$nodes", nodes.clone());
|
||||
new_v
|
||||
})
|
||||
.collect();
|
||||
|
||||
Variable::from_array(s)
|
||||
}
|
||||
_ => {
|
||||
let new_input = calculated_input.depth_clone(1);
|
||||
new_input.dot_insert("$nodes", nodes);
|
||||
new_input
|
||||
}
|
||||
}
|
||||
.node_context_message(&ctx, "Failed to evaluate expression")?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,7 +38,6 @@ impl TransformAttributesExecution for TransformAttributes {
|
||||
});
|
||||
}
|
||||
|
||||
response.output.dot_remove("$nodes");
|
||||
response.output
|
||||
}
|
||||
TransformExecutionMode::Loop => {
|
||||
@@ -90,7 +66,6 @@ impl TransformAttributesExecution for TransformAttributes {
|
||||
response.output = input.clone().merge_clone(&response.output);
|
||||
}
|
||||
|
||||
response.output.dot_remove("$nodes");
|
||||
output_array.push(response.output);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::nodes::variable_json::VariableJson;
|
||||
use ahash::HashMap;
|
||||
use anyhow::Context;
|
||||
use jsonschema::Validator;
|
||||
@@ -6,16 +7,26 @@ use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct ValidatorCache {
|
||||
inner: Arc<RwLock<HashMap<u64, Arc<Validator>>>>,
|
||||
inner: Arc<RwLock<HashMap<u64, Arc<Validator<VariableJson>>>>>,
|
||||
}
|
||||
|
||||
impl PartialEq for ValidatorCache {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorCache {
|
||||
pub fn get(&self, key: u64) -> Option<Arc<Validator>> {
|
||||
pub fn get(&self, key: u64) -> Option<Arc<Validator<VariableJson>>> {
|
||||
let read = self.inner.read().ok()?;
|
||||
read.get(&key).cloned()
|
||||
}
|
||||
|
||||
pub fn get_or_insert(&self, key: u64, schema: &Value) -> anyhow::Result<Arc<Validator>> {
|
||||
pub fn get_or_insert(
|
||||
&self,
|
||||
key: u64,
|
||||
schema: &Value,
|
||||
) -> anyhow::Result<Arc<Validator<VariableJson>>> {
|
||||
if let Some(v) = self.get(key) {
|
||||
return Ok(v);
|
||||
}
|
||||
@@ -25,7 +36,11 @@ impl ValidatorCache {
|
||||
.write()
|
||||
.ok()
|
||||
.context("Failed to acquire lock on validator cache")?;
|
||||
let validator = Arc::new(jsonschema::draft7::new(&schema)?);
|
||||
let validator = Arc::new(
|
||||
jsonschema::options_for::<VariableJson>()
|
||||
.with_draft(jsonschema::Draft::Draft7)
|
||||
.build(schema)?,
|
||||
);
|
||||
w_shared.insert(key, validator.clone());
|
||||
|
||||
Ok(validator)
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::Ref;
|
||||
|
||||
use jsonschema::json::{Array, Json, JsonNumber, Node, NodeIdentity, Object};
|
||||
use jsonschema::JsonType;
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use self_cell::self_cell;
|
||||
use serde_json::Value;
|
||||
use typed_arena::Arena;
|
||||
use zen_types::variable::{MapIter, RcCell, Variable, VariableMap};
|
||||
|
||||
pub struct VariableJson;
|
||||
|
||||
type MapRef<'a> = Ref<'a, VariableMap>;
|
||||
type VecRef<'a> = Ref<'a, Vec<Variable>>;
|
||||
|
||||
self_cell!(
|
||||
struct MapGuard {
|
||||
owner: RcCell<VariableMap>,
|
||||
|
||||
#[covariant]
|
||||
dependent: MapRef,
|
||||
}
|
||||
);
|
||||
|
||||
self_cell!(
|
||||
struct VecGuard {
|
||||
owner: RcCell<Vec<Variable>>,
|
||||
|
||||
#[covariant]
|
||||
dependent: VecRef,
|
||||
}
|
||||
);
|
||||
|
||||
pub struct Guards {
|
||||
objects: Arena<MapGuard>,
|
||||
arrays: Arena<VecGuard>,
|
||||
}
|
||||
|
||||
impl Default for Guards {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
objects: Arena::new(),
|
||||
arrays: Arena::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VariableNode<'a> {
|
||||
var: &'a Variable,
|
||||
guards: &'a Guards,
|
||||
}
|
||||
|
||||
impl<'a> VariableNode<'a> {
|
||||
pub fn new(var: &'a Variable, guards: &'a Guards) -> Self {
|
||||
Self { var, guards }
|
||||
}
|
||||
}
|
||||
|
||||
impl Json for VariableJson {
|
||||
type Node<'a> = VariableNode<'a>;
|
||||
type PreparedKey = Box<str>;
|
||||
type StringBuffer = Variable;
|
||||
|
||||
fn prepare_key(key: &str) -> Box<str> {
|
||||
Box::from(key)
|
||||
}
|
||||
|
||||
fn with_string_node<T>(
|
||||
buffer: &mut Variable,
|
||||
string: &str,
|
||||
f: impl FnOnce(VariableNode<'_>) -> T,
|
||||
) -> T {
|
||||
*buffer = Variable::String((string).into());
|
||||
let guards = Guards::default();
|
||||
f(VariableNode::new(buffer, &guards))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VariableNumber(Decimal);
|
||||
|
||||
impl JsonNumber for VariableNumber {
|
||||
fn as_u64(&self) -> Option<u64> {
|
||||
self.0.is_integer().then(|| self.0.to_u64()).flatten()
|
||||
}
|
||||
|
||||
fn as_i64(&self) -> Option<i64> {
|
||||
self.0.is_integer().then(|| self.0.to_i64()).flatten()
|
||||
}
|
||||
|
||||
fn as_f64(&self) -> Option<f64> {
|
||||
self.0.to_f64()
|
||||
}
|
||||
|
||||
fn as_str(&self) -> Cow<'_, str> {
|
||||
Cow::Owned(self.0.normalize().to_string())
|
||||
}
|
||||
|
||||
fn to_number(&self) -> Cow<'_, serde_json::Number> {
|
||||
let number = self
|
||||
.0
|
||||
.to_f64()
|
||||
.and_then(serde_json::Number::from_f64)
|
||||
.unwrap_or_else(|| serde_json::Number::from(0));
|
||||
Cow::Owned(number)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Node<'a, VariableJson> for VariableNode<'a> {
|
||||
type Object = ObjectNode<'a>;
|
||||
type Array = ArrayNode<'a>;
|
||||
type Number = VariableNumber;
|
||||
|
||||
fn as_object(&self) -> Option<ObjectNode<'a>> {
|
||||
let Variable::Object(cell) = self.var else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let guard = self
|
||||
.guards
|
||||
.objects
|
||||
.alloc(MapGuard::new(cell.clone(), |cell| cell.borrow()));
|
||||
Some(ObjectNode {
|
||||
map: guard.borrow_dependent(),
|
||||
guards: self.guards,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_array(&self) -> Option<ArrayNode<'a>> {
|
||||
let Variable::Array(cell) = self.var else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let guard = self
|
||||
.guards
|
||||
.arrays
|
||||
.alloc(VecGuard::new(cell.clone(), |cell| cell.borrow()));
|
||||
Some(ArrayNode {
|
||||
items: guard.borrow_dependent().as_slice(),
|
||||
guards: self.guards,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_string(&self) -> Option<Cow<'a, str>> {
|
||||
match self.var {
|
||||
Variable::String(string) => Some(Cow::Borrowed(string.as_str())),
|
||||
Variable::Dynamic(dynamic) => Some(Cow::Owned(dynamic.to_string())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_number(&self) -> Option<VariableNumber> {
|
||||
match self.var {
|
||||
Variable::Number(number) => Some(VariableNumber(*number)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_boolean(&self) -> Option<bool> {
|
||||
match self.var {
|
||||
Variable::Bool(boolean) => Some(*boolean),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_null(&self) -> bool {
|
||||
matches!(self.var, Variable::Null)
|
||||
}
|
||||
|
||||
fn json_type(&self) -> JsonType {
|
||||
match self.var {
|
||||
Variable::Null => JsonType::Null,
|
||||
Variable::Bool(_) => JsonType::Boolean,
|
||||
Variable::Number(_) => JsonType::Number,
|
||||
Variable::String(_) => JsonType::String,
|
||||
Variable::Array(_) => JsonType::Array,
|
||||
Variable::Object(_) => JsonType::Object,
|
||||
Variable::Dynamic(_) => JsonType::String,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Cow<'a, Value> {
|
||||
Cow::Owned(Variable::to_value(self.var))
|
||||
}
|
||||
|
||||
fn identity(&self) -> Option<NodeIdentity> {
|
||||
Some(NodeIdentity::new(
|
||||
std::ptr::from_ref::<Variable>(self.var) as usize
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjectNode<'a> {
|
||||
map: &'a VariableMap,
|
||||
guards: &'a Guards,
|
||||
}
|
||||
|
||||
impl<'a> Object<'a, VariableJson> for ObjectNode<'a> {
|
||||
type Node = VariableNode<'a>;
|
||||
type MemberName = &'a str;
|
||||
type MembersIter = VariableMembersIter<'a>;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
fn get(&self, key: &Box<str>) -> Option<VariableNode<'a>> {
|
||||
self.map
|
||||
.get_str(key)
|
||||
.map(|var| VariableNode::new(var, self.guards))
|
||||
}
|
||||
|
||||
fn members(&self) -> VariableMembersIter<'a> {
|
||||
VariableMembersIter {
|
||||
iter: self.map.iter(),
|
||||
guards: self.guards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VariableMembersIter<'a> {
|
||||
iter: MapIter<'a>,
|
||||
guards: &'a Guards,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for VariableMembersIter<'a> {
|
||||
type Item = (&'a str, VariableNode<'a>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter
|
||||
.next()
|
||||
.map(|(name, value)| (name.as_str(), VariableNode::new(value, self.guards)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArrayNode<'a> {
|
||||
items: &'a [Variable],
|
||||
guards: &'a Guards,
|
||||
}
|
||||
|
||||
impl<'a> Array<'a, VariableJson> for ArrayNode<'a> {
|
||||
type Node = VariableNode<'a>;
|
||||
type ElementsIter = VariableElementsIter<'a>;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
fn elements(&self) -> VariableElementsIter<'a> {
|
||||
VariableElementsIter {
|
||||
iter: self.items.iter(),
|
||||
guards: self.guards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VariableElementsIter<'a> {
|
||||
iter: std::slice::Iter<'a, Variable>,
|
||||
guards: &'a Guards,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for VariableElementsIter<'a> {
|
||||
type Item = VariableNode<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter
|
||||
.next()
|
||||
.map(|var| VariableNode::new(var, self.guards))
|
||||
}
|
||||
}
|
||||
@@ -451,7 +451,9 @@ impl ExecutionContext<'_> {
|
||||
};
|
||||
let segment = &path[..path.find('.').unwrap_or(path.len())];
|
||||
let store_fields = store_fields.borrow();
|
||||
let Some((key, value)) = store_fields.get_key_value(segment) else {
|
||||
let Some((key, value)) =
|
||||
store_fields.get_key_value(&zen_types::symbol::Symbol::from(segment))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
env_fields
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::sync::{Arc, OnceLock};
|
||||
|
||||
use ahash::{HashMap, HashSet};
|
||||
use fixedbitset::FixedBitSet;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zen_expression::intellisense::{ArmTest, IntelliSense, NumberCover};
|
||||
use zen_expression::variable::{Variable, VariableType};
|
||||
@@ -26,6 +25,7 @@ use super::{
|
||||
Block, BlockKind, BlockReadPlan, CellReads, ConditionalReads, ExpressionLocation, ParseContext,
|
||||
ReadFlattenFn, WriteSite, WriteTarget,
|
||||
};
|
||||
use crate::nodes::decision_table::index::TableIndex;
|
||||
|
||||
pub(crate) struct TableSelection {
|
||||
pub(crate) matched_rows: Vec<u32>,
|
||||
@@ -101,127 +101,6 @@ pub struct DecisionTableIr {
|
||||
index: OnceLock<Option<TableIndex>>,
|
||||
}
|
||||
|
||||
const MIN_INDEX_ROWS: usize = 8;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TableIndex {
|
||||
columns: Vec<Option<ColumnIndex>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ColumnIndex {
|
||||
strings: HashMap<Arc<str>, FixedBitSet>,
|
||||
numbers: HashMap<Decimal, FixedBitSet>,
|
||||
bools: HashMap<bool, FixedBitSet>,
|
||||
captured: FixedBitSet,
|
||||
fallback: FixedBitSet,
|
||||
}
|
||||
|
||||
impl TableIndex {
|
||||
fn build(table: &DecisionTableIr) -> Option<TableIndex> {
|
||||
let rows = table.rules.len();
|
||||
if rows < MIN_INDEX_ROWS {
|
||||
return None;
|
||||
}
|
||||
let mut intellisense = IntelliSense::new();
|
||||
let columns: Vec<Option<ColumnIndex>> = table
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|col| ColumnIndex::build(table, col, rows, &mut intellisense))
|
||||
.collect();
|
||||
columns
|
||||
.iter()
|
||||
.any(Option::is_some)
|
||||
.then_some(TableIndex { columns })
|
||||
}
|
||||
|
||||
fn decides(&self, col_idx: usize, row_idx: usize) -> bool {
|
||||
self.columns
|
||||
.get(col_idx)
|
||||
.and_then(Option::as_ref)
|
||||
.is_some_and(|c| c.captured.contains(row_idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl ColumnIndex {
|
||||
fn build(
|
||||
table: &DecisionTableIr,
|
||||
col: &DecisionTableInputField,
|
||||
rows: usize,
|
||||
intellisense: &mut IntelliSense,
|
||||
) -> Option<ColumnIndex> {
|
||||
if col.field.as_deref().is_none_or(|f| f.is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let mut strings: HashMap<Arc<str>, FixedBitSet> = HashMap::default();
|
||||
let mut numbers: HashMap<Decimal, FixedBitSet> = HashMap::default();
|
||||
let mut bools: HashMap<bool, FixedBitSet> = HashMap::default();
|
||||
let mut captured = FixedBitSet::with_capacity(rows);
|
||||
let mut fallback = FixedBitSet::with_capacity(rows);
|
||||
|
||||
for (row_idx, rule) in table.rules.iter().enumerate() {
|
||||
let Some(cell) = rule.get(&col.id).filter(|c| !c.is_empty()) else {
|
||||
fallback.insert(row_idx);
|
||||
continue;
|
||||
};
|
||||
match intellisense.cell_test(cell) {
|
||||
ArmTest::Enum { values, .. } => {
|
||||
for value in values {
|
||||
strings
|
||||
.entry(Arc::from(value.as_ref()))
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
ArmTest::Bool { values, .. } => {
|
||||
for value in values {
|
||||
bools
|
||||
.entry(value)
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
ArmTest::Number { cover, .. } => match cover.points() {
|
||||
Some(points) => {
|
||||
for point in points {
|
||||
numbers
|
||||
.entry(point.normalize())
|
||||
.or_insert_with(|| FixedBitSet::with_capacity(rows))
|
||||
.insert(row_idx);
|
||||
}
|
||||
captured.insert(row_idx);
|
||||
}
|
||||
None => {
|
||||
fallback.insert(row_idx);
|
||||
}
|
||||
},
|
||||
ArmTest::Default | ArmTest::Unrecognized => {
|
||||
fallback.insert(row_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(captured.count_ones(..) > 0).then_some(ColumnIndex {
|
||||
strings,
|
||||
numbers,
|
||||
bools,
|
||||
captured,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
|
||||
fn rows_for(&self, value: &Variable) -> Option<&FixedBitSet> {
|
||||
match value {
|
||||
Variable::String(s) => self.strings.get(s.as_ref()),
|
||||
Variable::Number(n) => self.numbers.get(&n.normalize()),
|
||||
Variable::Bool(b) => self.bools.get(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputColumn {
|
||||
pub id: Arc<str>,
|
||||
@@ -1173,7 +1052,9 @@ impl DecisionTableIr {
|
||||
}
|
||||
|
||||
fn table_index(&self) -> Option<&TableIndex> {
|
||||
self.index.get_or_init(|| TableIndex::build(self)).as_ref()
|
||||
self.index
|
||||
.get_or_init(|| TableIndex::build(&self.inputs, &self.rules))
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
fn candidate_rows(
|
||||
|
||||
@@ -2,9 +2,11 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use zen_types::symbol::Symbol;
|
||||
|
||||
use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
use zen_expression::variable::Variable;
|
||||
use zen_types::rccell::RcCell;
|
||||
|
||||
use zen_expression::{Isolate, OpcodeCache};
|
||||
|
||||
@@ -302,7 +304,9 @@ impl PhaseScope {
|
||||
let scoped_fields = self.scoped.as_object()?;
|
||||
|
||||
let needs_owner = match (owner_binding, instance.as_object()) {
|
||||
(Some((name, _)), Some(fields)) => !fields.borrow().contains_key(name.as_str()),
|
||||
(Some((name, _)), Some(fields)) => {
|
||||
!fields.borrow().contains_key(&Symbol::from(name.as_str()))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -310,7 +314,7 @@ impl PhaseScope {
|
||||
let wrapper = instance.depth_clone(1);
|
||||
let synthetic_owner = match (owner_binding, wrapper.as_object()) {
|
||||
(Some((name, owner_var)), Some(wrapper_fields)) => {
|
||||
let key: Rc<str> = Rc::from(name.as_str());
|
||||
let key = Symbol::from(name.as_str());
|
||||
let injected = owner_var.shallow_clone();
|
||||
wrapper_fields
|
||||
.borrow_mut()
|
||||
@@ -333,8 +337,8 @@ impl PhaseScope {
|
||||
|
||||
{
|
||||
let mut fields = scoped_fields.borrow_mut();
|
||||
fields.remove("$");
|
||||
fields.insert(self.entity_key.clone(), bound);
|
||||
fields.remove(&Variable::dollar_key());
|
||||
fields.insert(Symbol::from(self.entity_key.as_ref()), bound);
|
||||
}
|
||||
Some(slot)
|
||||
}
|
||||
@@ -344,7 +348,7 @@ enum InstanceSlot {
|
||||
Direct,
|
||||
Wrapped {
|
||||
wrapper: Variable,
|
||||
synthetic_owner: Option<(Rc<str>, Variable)>,
|
||||
synthetic_owner: Option<(Symbol, Variable)>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -363,7 +367,7 @@ impl InstanceSlot {
|
||||
let written = written.borrow();
|
||||
let mut target = target.borrow_mut();
|
||||
for (key, value) in written.iter() {
|
||||
if Self::is_injected_owner(synthetic_owner, key, value) {
|
||||
if Self::is_injected_owner(synthetic_owner, key.clone(), value) {
|
||||
continue;
|
||||
}
|
||||
target.insert(key.clone(), value.shallow_clone());
|
||||
@@ -371,12 +375,12 @@ impl InstanceSlot {
|
||||
}
|
||||
|
||||
fn is_injected_owner(
|
||||
synthetic_owner: &Option<(Rc<str>, Variable)>,
|
||||
key: &Rc<str>,
|
||||
synthetic_owner: &Option<(Symbol, Variable)>,
|
||||
key: Symbol,
|
||||
value: &Variable,
|
||||
) -> bool {
|
||||
match synthetic_owner {
|
||||
Some((owner_key, injected)) if owner_key.as_ref() == key.as_ref() => {
|
||||
Some((owner_key, injected)) if owner_key.as_str() == key.as_str() => {
|
||||
Self::same_ref(value, injected)
|
||||
}
|
||||
_ => false,
|
||||
@@ -385,9 +389,9 @@ impl InstanceSlot {
|
||||
|
||||
fn same_ref(a: &Variable, b: &Variable) -> bool {
|
||||
match (a, b) {
|
||||
(Variable::Object(x), Variable::Object(y)) => Rc::ptr_eq(x, y),
|
||||
(Variable::Array(x), Variable::Array(y)) => Rc::ptr_eq(x, y),
|
||||
(Variable::String(x), Variable::String(y)) => Rc::ptr_eq(x, y),
|
||||
(Variable::Object(x), Variable::Object(y)) => RcCell::ptr_eq(x, y),
|
||||
(Variable::Array(x), Variable::Array(y)) => RcCell::ptr_eq(x, y),
|
||||
(Variable::String(x), Variable::String(y)) => x == y,
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
@@ -419,7 +423,7 @@ impl<'a> Driver<'a> {
|
||||
|
||||
fn bind_env(&self, isolate: &RefCell<Isolate>) {
|
||||
if let Some(fields) = self.env.as_object() {
|
||||
fields.borrow_mut().remove("$");
|
||||
fields.borrow_mut().remove(&Variable::dollar_key());
|
||||
}
|
||||
isolate
|
||||
.borrow_mut()
|
||||
|
||||
@@ -765,7 +765,7 @@ impl VariableTypeScope for VariableType {
|
||||
return self.shallow_clone();
|
||||
};
|
||||
let mut fields: HashMap<Rc<str>, VariableType> = obj.borrow().clone();
|
||||
fields.insert(Variable::dollar_key(), field_type.shallow_clone());
|
||||
fields.insert(Variable::dollar_key_rc(), field_type.shallow_clone());
|
||||
VariableType::Object(Rc::new(RefCell::new(fields)))
|
||||
}
|
||||
|
||||
|
||||
@@ -144,21 +144,19 @@ impl CompiledSet {
|
||||
workspace.set_policy_arc(key.clone(), policy.0.clone());
|
||||
policy_keys.push(key.clone());
|
||||
}
|
||||
DecisionContent::Graph(graph) => {
|
||||
match Decision::from(Arc::new(graph.clone())).validate() {
|
||||
Err(error) => failures.push(CompileFailure {
|
||||
key: key.clone(),
|
||||
kind: "graph",
|
||||
diagnostics: Vec::new(),
|
||||
error: Some(error.to_string()),
|
||||
}),
|
||||
Ok(()) => {
|
||||
let mut compiled = graph.clone();
|
||||
compiled.compile();
|
||||
entries.insert(key.clone(), CompiledEntry::Graph(Arc::new(compiled)));
|
||||
}
|
||||
DecisionContent::Graph(graph) => match Decision::from(graph.clone()).validate() {
|
||||
Err(error) => failures.push(CompileFailure {
|
||||
key: key.clone(),
|
||||
kind: "graph",
|
||||
diagnostics: Vec::new(),
|
||||
error: Some(error.to_string()),
|
||||
}),
|
||||
Ok(()) => {
|
||||
let mut compiled = graph.clone();
|
||||
Arc::make_mut(&mut compiled).compile();
|
||||
entries.insert(key.clone(), CompiledEntry::Graph(compiled));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ impl InputValidator<'_> {
|
||||
let Some(prop) = dm_props
|
||||
.properties
|
||||
.iter()
|
||||
.find(|p| p.name.as_ref() == key.as_ref())
|
||||
.find(|p| *p.name == *key.as_str())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
|
||||
@@ -42,7 +41,12 @@ impl EnhanceState<'_> {
|
||||
trace
|
||||
.values()
|
||||
.filter(|entry| entry.order < node_trace.order)
|
||||
.map(|entry| (Rc::from(entry.name.as_ref()), entry.output.clone()))
|
||||
.map(|entry| {
|
||||
(
|
||||
zen_types::symbol::Symbol::from(entry.name.as_ref()),
|
||||
entry.output.clone(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
let base = node_trace.input.depth_clone(1);
|
||||
@@ -618,7 +622,10 @@ fn operand_values(
|
||||
/// those entries — read the map key directly.
|
||||
fn expression_trace_result(entry: &Variable, key: &str) -> Option<Variable> {
|
||||
let obj = entry.as_object()?;
|
||||
let slot = obj.borrow().get(key)?.shallow_clone();
|
||||
let slot = obj
|
||||
.borrow()
|
||||
.get(&zen_types::symbol::Symbol::from(key))?
|
||||
.shallow_clone();
|
||||
slot.dot("result")
|
||||
}
|
||||
|
||||
@@ -645,7 +652,10 @@ fn shallow_writes(input: &Variable, output: &Variable) -> Vec<WriteTrace> {
|
||||
if key.starts_with('$') {
|
||||
continue;
|
||||
}
|
||||
if input.dot(key).is_some_and(|previous| previous == *value) {
|
||||
if input
|
||||
.dot(key.as_str())
|
||||
.is_some_and(|previous| previous == *value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
writes.push(WriteTrace {
|
||||
|
||||
@@ -209,12 +209,11 @@ async fn engine_function_imports() {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let function_content = GraphContent {
|
||||
edges: function_content.edges,
|
||||
nodes: new_nodes,
|
||||
imports: Vec::new(),
|
||||
compiled_cache: None,
|
||||
stripped_functions: None,
|
||||
let function_content = {
|
||||
let mut content = GraphContent::default();
|
||||
content.edges = function_content.edges;
|
||||
content.nodes = new_nodes;
|
||||
content
|
||||
};
|
||||
let decision = DecisionEngine::default()
|
||||
.create_decision(Arc::new(function_content.into()))
|
||||
@@ -497,3 +496,89 @@ async fn test_nodes_reference() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn decision_table_index_matches_linear_semantics() {
|
||||
let table_node = json!({
|
||||
"id": "dt-node",
|
||||
"type": "decisionTableNode",
|
||||
"name": "dt",
|
||||
"content": {
|
||||
"hitPolicy": "first",
|
||||
"inputs": [
|
||||
{"id": "c1", "name": "Tier", "field": "tier"},
|
||||
{"id": "c2", "name": "Amount", "field": "amount"},
|
||||
{"id": "c3", "name": "Active", "field": "active"}
|
||||
],
|
||||
"outputs": [{"id": "o1", "name": "Rate", "field": "rate"}],
|
||||
"rules": [
|
||||
{"_id": "r1", "c1": "'gold'", "c2": "100", "c3": "true", "o1": "1"},
|
||||
{"_id": "r2", "c1": "'gold'", "c2": "> 500", "c3": "", "o1": "2"},
|
||||
{"_id": "r3", "c1": "'silver'", "c2": "[100..200]", "c3": "", "o1": "3"},
|
||||
{"_id": "r4", "c1": "'silver', 'bronze'", "c2": "", "c3": "false", "o1": "4"},
|
||||
{"_id": "r5", "c1": "", "c2": "42", "c3": "", "o1": "5"},
|
||||
{"_id": "r6", "c1": "'gold'", "c2": "", "c3": "", "o1": "6"},
|
||||
{"_id": "r7", "c1": "'bronze'", "c2": "7, 8, 9", "c3": "true", "o1": "7"},
|
||||
{"_id": "r8", "c1": "", "c2": "", "c3": "", "o1": "8"},
|
||||
{"_id": "r9", "c1": "'platinum'", "c2": "1000", "c3": "true", "o1": "9"}
|
||||
]
|
||||
}
|
||||
});
|
||||
let graph = json!({
|
||||
"nodes": [
|
||||
{"id": "in", "type": "inputNode", "name": "request"},
|
||||
table_node,
|
||||
{"id": "out", "type": "outputNode", "name": "response"}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "sourceId": "in", "targetId": "dt-node"},
|
||||
{"id": "e2", "sourceId": "dt-node", "targetId": "out"}
|
||||
]
|
||||
});
|
||||
|
||||
let probes = [
|
||||
json!({"tier": "gold", "amount": 100, "active": true}),
|
||||
json!({"tier": "gold", "amount": 600, "active": false}),
|
||||
json!({"tier": "silver", "amount": 150, "active": true}),
|
||||
json!({"tier": "silver", "amount": 50, "active": false}),
|
||||
json!({"tier": "bronze", "amount": 8, "active": true}),
|
||||
json!({"tier": "unknown", "amount": 42, "active": false}),
|
||||
json!({"tier": "unknown", "amount": 0, "active": false}),
|
||||
json!({"tier": "platinum", "amount": 1000, "active": true}),
|
||||
json!({"tier": 5, "amount": "x", "active": null}),
|
||||
];
|
||||
|
||||
for hit_policy in ["first", "collect"] {
|
||||
let mut graph = graph.clone();
|
||||
graph["nodes"][1]["content"]["hitPolicy"] = json!(hit_policy);
|
||||
let mut content: GraphContent = serde_json::from_value(graph).unwrap();
|
||||
content.compile();
|
||||
let decision = DecisionEngine::default()
|
||||
.create_decision(Arc::new(content.into()))
|
||||
.unwrap();
|
||||
|
||||
for probe in &probes {
|
||||
let indexed = decision
|
||||
.evaluate(Variable::from(probe))
|
||||
.await
|
||||
.unwrap()
|
||||
.result;
|
||||
let linear = decision
|
||||
.evaluate_with_opts(
|
||||
Variable::from(probe),
|
||||
EvaluationOptions {
|
||||
trace: true,
|
||||
max_depth: 5,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.result;
|
||||
assert_eq!(
|
||||
serde_json::to_value(&indexed).unwrap(),
|
||||
serde_json::to_value(&linear).unwrap(),
|
||||
"hit_policy={hit_policy} probe={probe}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn load_raw_test_data(key: &str) -> BufReader<File> {
|
||||
pub fn load_test_data(key: &str) -> GraphContent {
|
||||
let content: DecisionContent = serde_json::from_reader(load_raw_test_data(key)).unwrap();
|
||||
match content {
|
||||
DecisionContent::Graph(g) => g,
|
||||
DecisionContent::Graph(g) => (*g).clone(),
|
||||
DecisionContent::Policy(_) => {
|
||||
panic!("expected graph test fixture, got policy: {key}")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2021"
|
||||
repository = "https://github.com/gorules/zen.git"
|
||||
|
||||
[dependencies]
|
||||
smallvec = { version = "1", features = ["union"] }
|
||||
anyhow = { workspace = true }
|
||||
ahash = { workspace = true }
|
||||
bumpalo = { workspace = true, features = ["collections"] }
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn evaluate_unary_expression(
|
||||
};
|
||||
|
||||
let context_object = context_object_ref.borrow();
|
||||
if !context_object.contains_key("$") {
|
||||
if !context_object.contains_key(&Variable::dollar_key()) {
|
||||
return Err(IsolateError::MissingContextReference);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::compiler::Opcode;
|
||||
use crate::scope::Scope;
|
||||
use crate::vm::VM;
|
||||
use crate::{IsolateError, Variable};
|
||||
use std::marker::PhantomData;
|
||||
@@ -62,7 +63,15 @@ impl Expression<Standard> {
|
||||
}
|
||||
|
||||
pub fn evaluate_with(&self, context: Variable, vm: &mut VM) -> Result<Variable, IsolateError> {
|
||||
let output = vm.run(self.bytecode.as_ref(), context)?;
|
||||
self.evaluate_with_scope(&Scope::new(context), vm)
|
||||
}
|
||||
|
||||
pub fn evaluate_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
vm: &mut VM,
|
||||
) -> Result<Variable, IsolateError> {
|
||||
let output = vm.run(self.bytecode.as_ref(), scope)?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
@@ -85,17 +94,16 @@ impl Expression<Unary> {
|
||||
}
|
||||
|
||||
pub fn evaluate_with(&self, context: Variable, vm: &mut VM) -> Result<bool, IsolateError> {
|
||||
let Some(context_object_ref) = context.as_object() else {
|
||||
return Err(IsolateError::MissingContextReference);
|
||||
};
|
||||
self.evaluate_with_scope(&Scope::new(context), vm)
|
||||
}
|
||||
|
||||
let context_object = context_object_ref.borrow();
|
||||
if !context_object.contains_key("$") {
|
||||
pub fn evaluate_with_scope(&self, scope: &Scope, vm: &mut VM) -> Result<bool, IsolateError> {
|
||||
if scope.get(&Variable::dollar_key()).is_none() {
|
||||
return Err(IsolateError::MissingContextReference);
|
||||
}
|
||||
|
||||
let output = vm
|
||||
.run(self.bytecode.as_ref(), context)?
|
||||
.run(self.bytecode.as_ref(), scope)?
|
||||
.as_bool()
|
||||
.ok_or_else(|| IsolateError::ValueCastError)?;
|
||||
Ok(output)
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
use crate::variable::DynamicVariable;
|
||||
use crate::Variable;
|
||||
use ahash::HashMap;
|
||||
use anyhow::Context;
|
||||
use rust_decimal::Decimal;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use zen_types::rccell::RcCell;
|
||||
|
||||
pub struct Arguments<'a>(pub &'a [Variable]);
|
||||
|
||||
type RcCell<T> = Rc<RefCell<T>>;
|
||||
|
||||
impl<'a> Deref for Arguments<'a> {
|
||||
type Target = [Variable];
|
||||
|
||||
@@ -92,7 +88,7 @@ impl<'a> Arguments<'a> {
|
||||
pub fn oobject(
|
||||
&self,
|
||||
pos: usize,
|
||||
) -> anyhow::Result<Option<RcCell<HashMap<Rc<str>, Variable>>>> {
|
||||
) -> anyhow::Result<Option<RcCell<crate::variable::VariableMap>>> {
|
||||
match self.ovar(pos) {
|
||||
Some(v) => v
|
||||
.as_object()
|
||||
@@ -102,7 +98,7 @@ impl<'a> Arguments<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn object(&self, pos: usize) -> anyhow::Result<RcCell<HashMap<Rc<str>, Variable>>> {
|
||||
pub fn object(&self, pos: usize) -> anyhow::Result<RcCell<crate::variable::VariableMap>> {
|
||||
self.oobject(pos)?
|
||||
.with_context(|| format!("Argument on {pos} position is not a valid object"))
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ mod imp {
|
||||
let format = args.ostr(1)?;
|
||||
|
||||
let formatted = this.format(format);
|
||||
Ok(V::String(Rc::from(formatted)))
|
||||
Ok(V::String((formatted).into()))
|
||||
}
|
||||
|
||||
pub fn start_of(args: Arguments) -> anyhow::Result<V> {
|
||||
@@ -402,7 +402,7 @@ mod imp {
|
||||
}
|
||||
GetterOperation::IsLeapYear => V::Bool(dt.date_naive().leap_year()),
|
||||
// String
|
||||
GetterOperation::OffsetName => V::String(Rc::from(dt.timezone().name())),
|
||||
GetterOperation::OffsetName => V::String((dt.timezone().name()).into()),
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -221,19 +221,19 @@ mod imp {
|
||||
pub fn month_string(args: Arguments) -> anyhow::Result<V> {
|
||||
let timestamp = args.var(0)?;
|
||||
let time = __internal_convert_datetime(×tamp)?;
|
||||
Ok(V::String(Rc::from(time.format("%b").to_string())))
|
||||
Ok(V::String((time.format("%b").to_string()).into()))
|
||||
}
|
||||
|
||||
pub fn weekday_string(args: Arguments) -> anyhow::Result<V> {
|
||||
let timestamp = args.var(0)?;
|
||||
let time = __internal_convert_datetime(×tamp)?;
|
||||
Ok(V::String(Rc::from(time.weekday().to_string())))
|
||||
Ok(V::String((time.weekday().to_string()).into()))
|
||||
}
|
||||
|
||||
pub fn date_string(args: Arguments) -> anyhow::Result<V> {
|
||||
let timestamp = args.var(0)?;
|
||||
let time = __internal_convert_datetime(×tamp)?;
|
||||
Ok(V::String(Rc::from(time.to_string())))
|
||||
Ok(V::String((time.to_string()).into()))
|
||||
}
|
||||
|
||||
pub fn start_of(args: Arguments) -> anyhow::Result<V> {
|
||||
|
||||
@@ -328,7 +328,6 @@ pub(crate) mod imp {
|
||||
use crate::vm::date::DynamicVariableExt;
|
||||
use crate::vm::VmDate;
|
||||
use crate::{Variable as V, Variable};
|
||||
use ahash::HashMapExt;
|
||||
use anyhow::{anyhow, Context};
|
||||
use chrono_tz::Tz;
|
||||
#[cfg(not(feature = "regex-lite"))]
|
||||
@@ -436,7 +435,7 @@ pub(crate) mod imp {
|
||||
.iter()
|
||||
.map(|c| c.map(|c| c.as_str()))
|
||||
.filter_map(|c| c)
|
||||
.map(|s| V::String(Rc::from(s)))
|
||||
.map(|s| V::String((s).into()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
@@ -508,8 +507,7 @@ pub(crate) mod imp {
|
||||
Ok(V::from_array(merged))
|
||||
}
|
||||
V::Object(_) => {
|
||||
let mut merged: ahash::HashMap<Rc<str>, V> =
|
||||
ahash::HashMap::with_capacity(capacity);
|
||||
let mut merged = zen_types::variable::VariableMap::with_capacity(capacity);
|
||||
for item in arr.iter() {
|
||||
match item {
|
||||
V::Object(obj) => {
|
||||
@@ -555,8 +553,7 @@ pub(crate) mod imp {
|
||||
(V::Object(a), V::Object(b)) => {
|
||||
let a = a.borrow();
|
||||
let b = b.borrow();
|
||||
let mut merged: ahash::HashMap<Rc<str>, V> =
|
||||
ahash::HashMap::with_capacity(a.len() + b.len());
|
||||
let mut merged = zen_types::variable::VariableMap::with_capacity(a.len() + b.len());
|
||||
|
||||
for (key, value) in a.iter() {
|
||||
merged.insert(key.clone(), value.clone());
|
||||
@@ -724,7 +721,7 @@ pub(crate) mod imp {
|
||||
V::Bool(v) => *v,
|
||||
V::Number(n) => !n.is_zero(),
|
||||
V::Array(_) | V::Object(_) | V::Dynamic(_) => true,
|
||||
V::String(s) => match (*s).trim() {
|
||||
V::String(s) => match s.as_str().trim() {
|
||||
"true" => true,
|
||||
"false" => false,
|
||||
_ => s.is_empty(),
|
||||
@@ -737,9 +734,9 @@ pub(crate) mod imp {
|
||||
pub fn to_string(args: Arguments) -> anyhow::Result<V> {
|
||||
let a = args.var(0)?;
|
||||
let val = match a {
|
||||
V::Null => Rc::from("null"),
|
||||
V::Bool(v) => Rc::from(v.to_string().as_str()),
|
||||
V::Number(n) => Rc::from(n.to_string().as_str()),
|
||||
V::Null => "null".into(),
|
||||
V::Bool(v) => v.to_string().into(),
|
||||
V::Number(n) => n.to_string().into(),
|
||||
V::String(s) => s.clone(),
|
||||
_ => return Err(anyhow!("Cannot convert type {} to string", a.type_name())),
|
||||
};
|
||||
@@ -752,7 +749,7 @@ pub(crate) mod imp {
|
||||
let val = match a {
|
||||
V::Number(n) => *n,
|
||||
V::String(str) => {
|
||||
let s = str.trim();
|
||||
let s = str.as_str().trim();
|
||||
Decimal::from_str_exact(s)
|
||||
.or_else(|_| Decimal::from_scientific(s))
|
||||
.context("Invalid number")?
|
||||
@@ -772,7 +769,7 @@ pub(crate) mod imp {
|
||||
let is_ok = match a {
|
||||
V::Number(_) => true,
|
||||
V::String(str) => {
|
||||
let s = str.trim();
|
||||
let s = str.as_str().trim();
|
||||
Decimal::from_str_exact(s)
|
||||
.or_else(|_| Decimal::from_scientific(s))
|
||||
.is_ok()
|
||||
@@ -804,7 +801,7 @@ pub(crate) mod imp {
|
||||
let b = args.var(1)?;
|
||||
|
||||
let val = match (a, b) {
|
||||
(V::String(a), V::String(b)) => a.contains(b.as_ref()),
|
||||
(V::String(a), V::String(b)) => a.contains(b.as_str()),
|
||||
(V::Array(a), _) => {
|
||||
let arr = a.borrow();
|
||||
|
||||
@@ -875,7 +872,10 @@ pub(crate) mod imp {
|
||||
}
|
||||
V::Object(a) => {
|
||||
let obj = a.borrow();
|
||||
let keys = obj.iter().map(|(key, _)| V::String(key.clone())).collect();
|
||||
let keys = obj
|
||||
.iter()
|
||||
.map(|(key, _)| V::String((key.as_str()).into()))
|
||||
.collect();
|
||||
|
||||
V::from_array(keys)
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
fn extract_path_with_spans(&self, node: &Node) -> Option<(Vec<Rc<str>>, Vec<(u32, u32)>)> {
|
||||
match node {
|
||||
Node::Identifier(name) => Some((vec![Rc::from(*name)], vec![self.node_span(node)])),
|
||||
Node::Root => Some((vec![Variable::root_key()], vec![self.node_span(node)])),
|
||||
Node::Root => Some((vec![Variable::root_key_rc()], vec![self.node_span(node)])),
|
||||
Node::Member { node: n, property } => {
|
||||
let (mut path, mut spans) = self.extract_path_with_spans(n)?;
|
||||
match property {
|
||||
@@ -312,7 +312,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
fn collection_source_path(node: &Node) -> Option<Vec<Rc<str>>> {
|
||||
match node {
|
||||
Node::Identifier(name) => Some(vec![Rc::from(*name)]),
|
||||
Node::Root => Some(vec![Variable::root_key()]),
|
||||
Node::Root => Some(vec![Variable::root_key_rc()]),
|
||||
Node::Member { .. } => Self::extract_read_path(node),
|
||||
Node::Parenthesized(inner) => Self::collection_source_path(inner),
|
||||
Node::Binary {
|
||||
@@ -375,7 +375,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
return;
|
||||
}
|
||||
Node::Identifier(name) => Rc::from(*name),
|
||||
Node::Root => Variable::root_key(),
|
||||
Node::Root => Variable::root_key_rc(),
|
||||
Node::Pointer => {
|
||||
self.resolve_pointer_chain(&chain, scope);
|
||||
return;
|
||||
@@ -488,7 +488,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
}
|
||||
|
||||
fn reference_dollar_chain(&mut self, chain: &FlatChain, scope: &mut Scope) {
|
||||
let mut path: Vec<Rc<str>> = vec![Rc::from("$")];
|
||||
let mut path: Vec<Rc<str>> = vec![Variable::dollar_key_rc()];
|
||||
let mut spans = vec![self.node_span(chain.root)];
|
||||
let mut grouping = true;
|
||||
for segment in &chain.segments {
|
||||
@@ -674,7 +674,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
let (alias, inner_reads, inner_refs) = match closure_node {
|
||||
Node::Closure { body, alias } => {
|
||||
let mut inner_scope = scope.clone();
|
||||
inner_scope.locals.insert(Variable::dollar_key());
|
||||
inner_scope.locals.insert(Variable::dollar_key_rc());
|
||||
inner_scope.pointer_collection = collection_source
|
||||
.as_ref()
|
||||
.filter(|source| !scope.is_local(source))
|
||||
@@ -763,7 +763,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
|
||||
Node::Closure { body, alias: _ } => {
|
||||
let mut inner = scope.clone();
|
||||
inner.locals.insert(Variable::dollar_key());
|
||||
inner.locals.insert(Variable::dollar_key_rc());
|
||||
inner.pointer_collection = None;
|
||||
self.resolve(body, &mut inner);
|
||||
}
|
||||
@@ -837,7 +837,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
}
|
||||
|
||||
Node::Root => {
|
||||
let path: Vec<Rc<str>> = vec![Variable::root_key()];
|
||||
let path: Vec<Rc<str>> = vec![Variable::root_key_rc()];
|
||||
let span = self.node_span(node);
|
||||
self.reads.push(ReadDependency::Direct {
|
||||
path: path.clone(),
|
||||
@@ -866,7 +866,7 @@ impl<'a> DependencyResolutionWalker<'a> {
|
||||
fn extract_read_path(node: &Node) -> Option<Vec<Rc<str>>> {
|
||||
match node {
|
||||
Node::Identifier(name) => Some(vec![Rc::from(*name)]),
|
||||
Node::Root => Some(vec![Variable::root_key()]),
|
||||
Node::Root => Some(vec![Variable::root_key_rc()]),
|
||||
Node::Member { node, property } => {
|
||||
let mut path = Self::extract_read_path(node)?;
|
||||
match property {
|
||||
|
||||
@@ -8,10 +8,12 @@ use crate::compiler::{Compiler, CompilerError, Opcode};
|
||||
use crate::expression::{OpcodeCache, Standard, Unary};
|
||||
use crate::lexer::{Lexer, LexerError};
|
||||
use crate::parser::{Parser, ParserError};
|
||||
use crate::scope::Scope;
|
||||
use crate::variable::Variable;
|
||||
use crate::vm::{VMError, VM};
|
||||
use crate::{Expression, ExpressionKind};
|
||||
use bumpalo::Bump;
|
||||
use zen_types::symbol::Symbol;
|
||||
|
||||
/// Isolate is a component that encapsulates an isolated environment for executing expressions.
|
||||
///
|
||||
@@ -26,7 +28,7 @@ pub struct Isolate {
|
||||
|
||||
bump: Bump,
|
||||
|
||||
environment: Option<Variable>,
|
||||
scope: Scope,
|
||||
references: HashMap<String, Variable>,
|
||||
cache: Option<Arc<OpcodeCache>>,
|
||||
}
|
||||
@@ -40,7 +42,7 @@ impl Isolate {
|
||||
|
||||
bump: Bump::new(),
|
||||
|
||||
environment: None,
|
||||
scope: Scope::default(),
|
||||
references: Default::default(),
|
||||
cache: None,
|
||||
}
|
||||
@@ -59,7 +61,7 @@ impl Isolate {
|
||||
}
|
||||
|
||||
pub fn set_environment(&mut self, variable: Variable) {
|
||||
self.environment.replace(variable);
|
||||
self.scope.set_base(variable);
|
||||
self.references.clear();
|
||||
}
|
||||
|
||||
@@ -67,11 +69,26 @@ impl Isolate {
|
||||
self.cache = Some(cache);
|
||||
}
|
||||
|
||||
pub fn update_environment<F>(&mut self, mut updater: F)
|
||||
where
|
||||
F: FnMut(Option<&mut Variable>),
|
||||
{
|
||||
updater(self.environment.as_mut());
|
||||
pub fn scope(&self) -> &Scope {
|
||||
&self.scope
|
||||
}
|
||||
|
||||
pub fn set_local(&mut self, name: Symbol, value: Variable) {
|
||||
self.scope.set_local(name, value);
|
||||
}
|
||||
|
||||
pub fn insert_dollar(&mut self, path: &str, value: Variable) {
|
||||
let dollar = match self.scope.local(&Variable::dollar_key()) {
|
||||
Some(existing @ Variable::Object(_)) => existing.shallow_clone(),
|
||||
_ => {
|
||||
let created = Variable::empty_object();
|
||||
self.scope
|
||||
.set_local(Variable::dollar_key(), created.clone());
|
||||
created
|
||||
}
|
||||
};
|
||||
|
||||
let _ = dollar.dot_insert(path, value);
|
||||
}
|
||||
|
||||
pub fn set_reference(&mut self, reference: &str) -> Result<(), IsolateError> {
|
||||
@@ -89,16 +106,7 @@ impl Isolate {
|
||||
}
|
||||
|
||||
pub fn set_reference_value(&mut self, value: Variable) -> Result<(), IsolateError> {
|
||||
if !matches!(&mut self.environment, Some(Variable::Object(_))) {
|
||||
self.environment.replace(Variable::empty_object());
|
||||
}
|
||||
|
||||
let Some(Variable::Object(environment_object_ref)) = &self.environment else {
|
||||
return Err(IsolateError::ReferenceError);
|
||||
};
|
||||
|
||||
let mut environment_object = environment_object_ref.borrow_mut();
|
||||
environment_object.insert(Variable::dollar_key(), value);
|
||||
self.scope.set_local(Variable::dollar_key(), value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -145,16 +153,12 @@ impl Isolate {
|
||||
self.run_internal(source, ExpressionKind::Standard)?;
|
||||
|
||||
let bytecode = self.compiler.get_bytecode();
|
||||
let result = self
|
||||
.vm
|
||||
.run(bytecode, self.environment.clone().unwrap_or(Variable::Null))?;
|
||||
let result = self.vm.run(bytecode, &self.scope)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
pub fn run_compiled(&mut self, source: &[Opcode]) -> Result<Variable, IsolateError> {
|
||||
let result = self
|
||||
.vm
|
||||
.run(source, self.environment.clone().unwrap_or(Variable::Null))?;
|
||||
let result = self.vm.run(source, &self.scope)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -178,17 +182,13 @@ impl Isolate {
|
||||
self.run_internal(source, ExpressionKind::Unary)?;
|
||||
|
||||
let bytecode = self.compiler.get_bytecode();
|
||||
let result = self
|
||||
.vm
|
||||
.run(bytecode, self.environment.clone().unwrap_or(Variable::Null))?;
|
||||
let result = self.vm.run(bytecode, &self.scope)?;
|
||||
|
||||
result.as_bool().ok_or_else(|| IsolateError::ValueCastError)
|
||||
}
|
||||
|
||||
pub fn run_unary_compiled(&mut self, code: &[Opcode]) -> Result<bool, IsolateError> {
|
||||
let result = self
|
||||
.vm
|
||||
.run(code, self.environment.clone().unwrap_or(Variable::Null))?;
|
||||
let result = self.vm.run(code, &self.scope)?;
|
||||
|
||||
result.as_bool().ok_or_else(|| IsolateError::ValueCastError)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ pub mod intellisense;
|
||||
pub mod lexer;
|
||||
pub mod nl;
|
||||
pub mod parser;
|
||||
pub mod scope;
|
||||
pub mod validate;
|
||||
pub mod variable;
|
||||
pub mod vm;
|
||||
@@ -76,4 +77,5 @@ pub use exports::{
|
||||
};
|
||||
pub use expression::{Expression, ExpressionKind, OpcodeCache};
|
||||
pub use isolate::{Isolate, IsolateError};
|
||||
pub use scope::Scope;
|
||||
pub use variable::Variable;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use crate::variable::Variable;
|
||||
use smallvec::SmallVec;
|
||||
use zen_types::symbol::Symbol;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scope {
|
||||
base: Variable,
|
||||
locals: SmallVec<[(Symbol, Variable); 4]>,
|
||||
}
|
||||
|
||||
impl Default for Scope {
|
||||
fn default() -> Self {
|
||||
Self::new(Variable::Null)
|
||||
}
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(base: Variable) -> Self {
|
||||
Self {
|
||||
base,
|
||||
locals: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &Variable {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub fn base_mut(&mut self) -> &mut Variable {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn set_base(&mut self, base: Variable) {
|
||||
self.base = base;
|
||||
self.locals.clear();
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &[(Symbol, Variable)] {
|
||||
&self.locals
|
||||
}
|
||||
|
||||
pub fn clear_locals(&mut self) {
|
||||
self.locals.clear();
|
||||
}
|
||||
|
||||
pub fn set_local(&mut self, name: Symbol, value: Variable) {
|
||||
match self
|
||||
.locals
|
||||
.iter_mut()
|
||||
.find(|(key, _)| key.as_str() == name.as_str())
|
||||
{
|
||||
Some(slot) => slot.1 = value,
|
||||
None => self.locals.push((name, value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local(&self, name: &Symbol) -> Option<&Variable> {
|
||||
self.locals
|
||||
.iter()
|
||||
.find(|(key, _)| key.as_str() == name.as_str())
|
||||
.map(|(_, value)| value)
|
||||
}
|
||||
|
||||
pub fn local_str(&self, name: &str) -> Option<&Variable> {
|
||||
self.locals
|
||||
.iter()
|
||||
.find(|(key, _)| key.as_str() == name)
|
||||
.map(|(_, value)| value)
|
||||
}
|
||||
|
||||
pub fn get_str(&self, name: &str) -> Option<Variable> {
|
||||
if let Some(value) = self.local_str(name) {
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
match &self.base {
|
||||
Variable::Object(object) => object.borrow().get_str(name).cloned(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &Symbol) -> Option<Variable> {
|
||||
if let Some(value) = self.local(name) {
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
match &self.base {
|
||||
Variable::Object(object) => object.borrow().get(name).cloned(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn materialize(&self) -> Variable {
|
||||
if self.locals.is_empty() {
|
||||
return self.base.shallow_clone();
|
||||
}
|
||||
|
||||
let Variable::Object(base) = &self.base else {
|
||||
return self.base.shallow_clone();
|
||||
};
|
||||
|
||||
let mut map = base.borrow().clone();
|
||||
for (key, value) in &self.locals {
|
||||
map.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
Variable::from_object(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Variable> for Scope {
|
||||
fn from(value: Variable) -> Self {
|
||||
Scope::new(value)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use zen_types::rcvalue::*;
|
||||
pub use zen_types::symbol::Symbol;
|
||||
pub use zen_types::variable::*;
|
||||
pub use zen_types::variable_type::*;
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@ use crate::compiler::{Compare, FetchFastTarget, Jump, Opcode};
|
||||
use crate::functions::arguments::Arguments;
|
||||
use crate::functions::registry::FunctionRegistry;
|
||||
use crate::functions::{internal, MethodRegistry};
|
||||
use crate::scope::Scope;
|
||||
use crate::variable::Variable;
|
||||
use crate::variable::Variable::*;
|
||||
use crate::vm::date::DynamicVariableExt;
|
||||
use crate::vm::error::VMError::*;
|
||||
use crate::vm::error::VMResult;
|
||||
use crate::vm::interval::{VmInterval, VmIntervalData};
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
|
||||
use rust_decimal::{Decimal, MathematicalOps};
|
||||
use std::rc::Rc;
|
||||
use std::string::String as StdString;
|
||||
use zen_types::symbol::Symbol;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scope {
|
||||
pub struct LoopScope {
|
||||
array: Variable,
|
||||
len: usize,
|
||||
iter: usize,
|
||||
@@ -24,7 +25,7 @@ pub struct Scope {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VM {
|
||||
scopes: Vec<Scope>,
|
||||
scopes: Vec<LoopScope>,
|
||||
stack: Vec<Variable>,
|
||||
}
|
||||
|
||||
@@ -36,17 +37,17 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, bytecode: &[Opcode], env: Variable) -> VMResult<Variable> {
|
||||
pub fn run(&mut self, bytecode: &[Opcode], scope: &Scope) -> VMResult<Variable> {
|
||||
self.stack.clear();
|
||||
self.scopes.clear();
|
||||
|
||||
let s = VMInner::new(bytecode, &mut self.stack, &mut self.scopes).run(env);
|
||||
let s = VMInner::new(bytecode, &mut self.stack, &mut self.scopes).run(scope);
|
||||
Ok(s?)
|
||||
}
|
||||
}
|
||||
|
||||
struct VMInner<'parent_ref, 'bytecode_ref> {
|
||||
scopes: &'parent_ref mut Vec<Scope>,
|
||||
scopes: &'parent_ref mut Vec<LoopScope>,
|
||||
stack: &'parent_ref mut Vec<Variable>,
|
||||
bytecode: &'bytecode_ref [Opcode],
|
||||
ip: u32,
|
||||
@@ -56,7 +57,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
pub fn new(
|
||||
bytecode: &'bytecode_ref [Opcode],
|
||||
stack: &'parent_ref mut Vec<Variable>,
|
||||
scopes: &'parent_ref mut Vec<Scope>,
|
||||
scopes: &'parent_ref mut Vec<LoopScope>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ip: 0,
|
||||
@@ -76,8 +77,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&mut self, root_env: Variable) -> VMResult<Variable> {
|
||||
let mut env = root_env.clone();
|
||||
pub fn run(&mut self, root_scope: &Scope) -> VMResult<Variable> {
|
||||
let mut env = root_scope.clone();
|
||||
let mut assigned_object: Option<Variable> = None;
|
||||
if self.ip != 0 {
|
||||
self.ip = 0;
|
||||
@@ -98,7 +99,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
Opcode::PushNull => self.push(Null),
|
||||
Opcode::PushBool(b) => self.push(Bool(*b)),
|
||||
Opcode::PushNumber(n) => self.push(Number(*n)),
|
||||
Opcode::PushString(s) => self.push(String(Rc::from(s.as_ref()))),
|
||||
Opcode::PushString(s) => self.push(String((s.as_ref()).into())),
|
||||
Opcode::Pop => {
|
||||
self.pop()?;
|
||||
}
|
||||
@@ -109,7 +110,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
match (a, b) {
|
||||
(Object(o), String(s)) => {
|
||||
let obj = o.borrow();
|
||||
self.push(obj.get(s.as_ref()).cloned().unwrap_or(Null));
|
||||
self.push(obj.get_str(s.as_str()).cloned().unwrap_or(Null));
|
||||
}
|
||||
(Array(a), Number(n)) => {
|
||||
let arr = a.borrow();
|
||||
@@ -129,7 +130,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
})?;
|
||||
|
||||
if let Some(slice) = str.get(index..index + 1) {
|
||||
self.push(String(Rc::from(slice)));
|
||||
self.push(String((slice).into()));
|
||||
} else {
|
||||
self.push(Null)
|
||||
};
|
||||
@@ -138,13 +139,26 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
}
|
||||
}
|
||||
Opcode::FetchFast(path) => {
|
||||
let variable = path.iter().fold(Null, |v, p| match p {
|
||||
FetchFastTarget::Root => root_env.clone(),
|
||||
FetchFastTarget::Begin => env.clone(),
|
||||
let mut steps = path.iter();
|
||||
let mut variable = match steps.next() {
|
||||
Some(FetchFastTarget::Root) => root_scope.materialize(),
|
||||
Some(FetchFastTarget::Begin) => match steps.clone().next() {
|
||||
Some(FetchFastTarget::String(key)) => {
|
||||
steps.next();
|
||||
env.get_str(key).unwrap_or(Null)
|
||||
}
|
||||
_ => env.materialize(),
|
||||
},
|
||||
_ => Null,
|
||||
};
|
||||
|
||||
variable = steps.fold(variable, |v, p| match p {
|
||||
FetchFastTarget::Root => root_scope.materialize(),
|
||||
FetchFastTarget::Begin => env.materialize(),
|
||||
FetchFastTarget::String(key) => match v {
|
||||
Object(obj) => {
|
||||
let obj_ref = obj.borrow();
|
||||
obj_ref.get(key.as_ref()).cloned().unwrap_or(Null)
|
||||
obj_ref.get_str(key).cloned().unwrap_or(Null)
|
||||
}
|
||||
_ => Null,
|
||||
},
|
||||
@@ -156,7 +170,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
String(str) => {
|
||||
let index = *num as usize;
|
||||
str.get(index..index + 1)
|
||||
.map(|slice| String(Rc::from(slice)))
|
||||
.map(|slice| String((slice).into()))
|
||||
.unwrap_or(Null)
|
||||
}
|
||||
_ => Null,
|
||||
@@ -165,24 +179,27 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
|
||||
self.push(variable);
|
||||
}
|
||||
Opcode::FetchEnv(f) => match &env {
|
||||
Object(o) => {
|
||||
let obj = o.borrow();
|
||||
match obj.get(f.as_ref()) {
|
||||
None => self.push(Null),
|
||||
Some(v) => self.push(v.clone()),
|
||||
Opcode::FetchEnv(f) => match env.local_str(f) {
|
||||
Some(v) => self.push(v.clone()),
|
||||
None => match env.base() {
|
||||
Object(o) => {
|
||||
let obj = o.borrow();
|
||||
match obj.get_str(f) {
|
||||
None => self.push(Null),
|
||||
Some(v) => self.push(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Null => self.push(Null),
|
||||
_ => {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "FetchEnv".into(),
|
||||
message: "Unsupported type".into(),
|
||||
});
|
||||
}
|
||||
Null => self.push(Null),
|
||||
_ => {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "FetchEnv".into(),
|
||||
message: "Unsupported type".into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
Opcode::FetchRootEnv => {
|
||||
self.push(env.clone());
|
||||
self.push(env.materialize());
|
||||
}
|
||||
Opcode::Negate => {
|
||||
let a = self.pop()?;
|
||||
@@ -381,7 +398,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
}
|
||||
(String(a), Object(b)) => {
|
||||
let obj = b.borrow();
|
||||
self.push(Bool(obj.contains_key(a.as_ref())));
|
||||
self.push(Bool(obj.contains_key_str(a.as_str())));
|
||||
}
|
||||
(Bool(a), Array(b)) => {
|
||||
let arr = b.borrow();
|
||||
@@ -457,7 +474,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
c.push_str(a.as_ref());
|
||||
c.push_str(b.as_ref());
|
||||
|
||||
self.push(String(Rc::from(c.as_str())));
|
||||
self.push(String((c.as_str()).into()));
|
||||
}
|
||||
_ => {
|
||||
return Err(OpcodeErr {
|
||||
@@ -634,7 +651,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
}
|
||||
}
|
||||
|
||||
self.push(String(Rc::from(s)));
|
||||
self.push(String((s).into()));
|
||||
}
|
||||
Opcode::Slice => {
|
||||
let from_var = self.pop()?;
|
||||
@@ -668,7 +685,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
message: "Index out of range".into(),
|
||||
})?;
|
||||
|
||||
self.push(String(Rc::from(slice)));
|
||||
self.push(String((slice).into()));
|
||||
}
|
||||
_ => {
|
||||
return Err(OpcodeErr {
|
||||
@@ -722,7 +739,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
message: "Failed to extract argument".into(),
|
||||
})?;
|
||||
|
||||
let mut map = HashMap::with_capacity(to);
|
||||
let mut map = crate::variable::VariableMap::with_capacity(to);
|
||||
for _ in 0..to {
|
||||
let value = self.pop()?;
|
||||
let String(key) = self.pop()? else {
|
||||
@@ -732,7 +749,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
});
|
||||
};
|
||||
|
||||
map.insert(key.clone(), value);
|
||||
map.insert(Symbol::from(key.as_str()), value);
|
||||
}
|
||||
|
||||
self.push(Variable::from_object(map));
|
||||
@@ -756,14 +773,30 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
});
|
||||
};
|
||||
|
||||
let Some(new_env) = env.dot_insert_detached(key.as_ref(), value.clone()) else {
|
||||
if !matches!(env.base(), Object(_)) {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "AssignedObjectStep".into(),
|
||||
message: "Failed to mutate existing env".to_owned(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
match key.contains('.') {
|
||||
false => env.set_local(Symbol::from(key.as_str()), value.clone()),
|
||||
true => {
|
||||
let Some(new_env) = env
|
||||
.materialize()
|
||||
.dot_insert_detached(key.as_ref(), value.clone())
|
||||
else {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "AssignedObjectStep".into(),
|
||||
message: "Failed to mutate existing env".to_owned(),
|
||||
});
|
||||
};
|
||||
|
||||
env = Scope::new(new_env);
|
||||
}
|
||||
}
|
||||
|
||||
env = new_env;
|
||||
assigned_object.dot_insert(key.as_ref(), value);
|
||||
}
|
||||
Opcode::AssignedObjectEnd { with_return } => {
|
||||
@@ -891,7 +924,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
let maybe_scope = match &var {
|
||||
Array(a) => {
|
||||
let arr = a.borrow();
|
||||
Some(Scope {
|
||||
Some(LoopScope {
|
||||
len: arr.len(),
|
||||
array: var.clone(),
|
||||
count: 0,
|
||||
@@ -900,7 +933,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'parent_ref, 'bytecode_ref> {
|
||||
}
|
||||
_ => match var.dynamic::<VmInterval>().map(|s| s.to_array()).flatten() {
|
||||
None => None,
|
||||
Some(arr) => Some(Scope {
|
||||
Some(arr) => Some(LoopScope {
|
||||
len: arr.len(),
|
||||
array: Variable::from_array(arr),
|
||||
count: 0,
|
||||
|
||||
@@ -807,6 +807,62 @@ fn isolate_unary_tests() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolate_does_not_mutate_environment() {
|
||||
let environment: Variable = json!({ "a": 1, "nested": { "b": 2 } }).into();
|
||||
|
||||
let mut isolate = Isolate::new();
|
||||
isolate.set_environment(environment.clone());
|
||||
|
||||
isolate.set_reference("a").unwrap();
|
||||
isolate.set_local(Variable::nodes_key(), json!({ "some": "node" }).into());
|
||||
isolate.run_standard("x = 1; y = x + a; y").unwrap();
|
||||
isolate.insert_dollar("computed", Variable::Number(42.into()));
|
||||
|
||||
assert_eq!(
|
||||
environment.to_value(),
|
||||
json!({ "a": 1, "nested": { "b": 2 } }),
|
||||
"the caller's environment must come back exactly as it went in"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
isolate.run_standard("$nodes.some").unwrap().to_value(),
|
||||
json!("node")
|
||||
);
|
||||
assert_eq!(
|
||||
isolate.run_standard("$.computed").unwrap().to_value(),
|
||||
json!(42)
|
||||
);
|
||||
assert_eq!(isolate.run_standard("a").unwrap().to_value(), json!(1));
|
||||
let mut root_keys = isolate.run_standard("keys($root)").unwrap().to_value();
|
||||
root_keys
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.sort_by_key(|k| k.to_string());
|
||||
assert_eq!(
|
||||
root_keys,
|
||||
json!(["$", "$nodes", "a", "nested"]),
|
||||
"`$root` still sees the input and the bindings as one object"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignments_do_not_leak_between_runs() {
|
||||
let environment: Variable = json!({ "a": 1 }).into();
|
||||
|
||||
let mut isolate = Isolate::with_environment(environment.clone());
|
||||
assert_eq!(
|
||||
isolate.run_standard("b = a + 1; b").unwrap().to_value(),
|
||||
json!(2)
|
||||
);
|
||||
assert_eq!(
|
||||
isolate.run_standard("b").unwrap(),
|
||||
Variable::Null,
|
||||
"an assignment lives for one run only"
|
||||
);
|
||||
assert_eq!(environment.to_value(), json!({ "a": 1 }));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use anyhow::Context;
|
||||
|
||||
@@ -47,8 +47,7 @@ pub fn to_variable_impl(input: TokenStream) -> TokenStream {
|
||||
const _: () = {
|
||||
extern crate zen_expression as _zen_expression;
|
||||
|
||||
use _zen_expression::variable::{Variable as _Variable, VariableMap as _VariableMap, VariableMapExt, ToVariable as _ToVariable};
|
||||
use ::std::rc::Rc as _Rc;
|
||||
use _zen_expression::variable::{Variable as _Variable, VariableMap as _VariableMap, VariableMapExt, ToVariable as _ToVariable, Symbol as _Symbol};
|
||||
|
||||
#impl_block
|
||||
};
|
||||
@@ -73,7 +72,7 @@ fn generate_struct_body(fields: &[serde_derive_internals::ast::Field]) -> proc_m
|
||||
|
||||
quote! {
|
||||
map.insert(
|
||||
_Rc::from(#serialized_name),
|
||||
_Symbol::from(#serialized_name),
|
||||
self.#field_ident.to_variable()
|
||||
);
|
||||
}
|
||||
@@ -187,7 +186,7 @@ fn generate_untagged_variant_arm(
|
||||
let field_name = field.attrs.name().serialize_name();
|
||||
|
||||
quote! {
|
||||
map.insert(_Rc::from(#field_name), #field_ident.to_variable());
|
||||
map.insert(_Symbol::from(#field_name), #field_ident.to_variable());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -224,7 +223,7 @@ fn generate_variant_arm(
|
||||
serde_derive_internals::ast::Style::Unit => {
|
||||
quote! {
|
||||
#enum_ident::#variant_ident => {
|
||||
_Variable::String(_Rc::from(#variant_name))
|
||||
_Variable::String(_Symbol::from_static(#variant_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,8 +232,8 @@ fn generate_variant_arm(
|
||||
quote! {
|
||||
#enum_ident::#variant_ident(value) => {
|
||||
let mut map = _VariableMap::with_capacity(2);
|
||||
map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
|
||||
map.insert(_Rc::from(#value_key), value.to_variable());
|
||||
map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name)));
|
||||
map.insert(_Symbol::from(#value_key), value.to_variable());
|
||||
_Variable::from_object(map)
|
||||
}
|
||||
}
|
||||
@@ -250,8 +249,8 @@ fn generate_variant_arm(
|
||||
quote! {
|
||||
#enum_ident::#variant_ident(#(#field_patterns),*) => {
|
||||
let mut map = _VariableMap::with_capacity(2);
|
||||
map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
|
||||
map.insert(_Rc::from(#value_key), (#(#field_patterns)*).to_variable());
|
||||
map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name)));
|
||||
map.insert(_Symbol::from(#value_key), (#(#field_patterns)*).to_variable());
|
||||
_Variable::from_object(map)
|
||||
}
|
||||
}
|
||||
@@ -259,14 +258,14 @@ fn generate_variant_arm(
|
||||
let field_mappings = field_patterns.iter().enumerate().map(|(i, pattern)| {
|
||||
let field_key = rename_rule.apply_to_field(&format!("field_{}", i));
|
||||
quote! {
|
||||
map.insert(_Rc::from(#field_key), (#pattern).to_variable());
|
||||
map.insert(_Symbol::from(#field_key), (#pattern).to_variable());
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#enum_ident::#variant_ident(#(#field_patterns),*) => {
|
||||
let mut map = _VariableMap::with_capacity(#field_count + 1);
|
||||
map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
|
||||
map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name)));
|
||||
#(#field_mappings)*
|
||||
_Variable::from_object(map)
|
||||
}
|
||||
@@ -290,7 +289,7 @@ fn generate_variant_arm(
|
||||
let field_name = field.attrs.name().serialize_name();
|
||||
|
||||
quote! {
|
||||
map.insert(_Rc::from(#field_name), #field_ident.to_variable());
|
||||
map.insert(_Symbol::from(#field_name), #field_ident.to_variable());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -303,7 +302,7 @@ fn generate_variant_arm(
|
||||
quote! {
|
||||
#enum_ident::#variant_ident { #(#field_patterns),* } => {
|
||||
let mut map = _VariableMap::with_capacity(#field_count);
|
||||
map.insert(_Rc::from(#type_key), _Variable::String(_Rc::from(#variant_name)));
|
||||
map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name)));
|
||||
#(#field_mappings)*
|
||||
_Variable::from_object(map)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::iter::Peekable;
|
||||
use std::rc::Rc;
|
||||
use std::slice::Iter;
|
||||
|
||||
use crate::error::TemplateRenderError;
|
||||
@@ -56,7 +55,7 @@ impl<'source, 'nodes> Interpreter<'source, 'nodes> {
|
||||
let item = self.results.remove(0);
|
||||
match item {
|
||||
InterpreterResult::Variable(val) => Ok(val),
|
||||
InterpreterResult::String(str) => Ok(Variable::String(Rc::from(str))),
|
||||
InterpreterResult::String(str) => Ok(Variable::String((str).into())),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -69,7 +68,7 @@ impl<'source, 'nodes> Interpreter<'source, 'nodes> {
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
Ok(Variable::String(Rc::from(string_data.as_str())))
|
||||
Ok(Variable::String((string_data.as_str()).into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ license = "MIT"
|
||||
ahash = { workspace = true }
|
||||
serde = { workspace = true, features = ["rc", "derive"] }
|
||||
serde_json = { workspace = true, features = [] }
|
||||
smallvec = { version = "1", features = ["union"] }
|
||||
hipstr = { version = "0.8", features = ["serde"] }
|
||||
rust_decimal = { workspace = true, features = ["maths-nopanic"] }
|
||||
rust_decimal_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
mod constant;
|
||||
pub mod decision;
|
||||
pub mod rccell;
|
||||
pub mod rcvalue;
|
||||
pub mod symbol;
|
||||
pub mod variable;
|
||||
pub mod variable_type;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub type RcCell<T> = Rc<RefCell<T>>;
|
||||
@@ -12,13 +12,13 @@ impl ToVariable for RcValue {
|
||||
RcValue::Null => Variable::Null,
|
||||
RcValue::Bool(b) => Variable::Bool(*b),
|
||||
RcValue::Number(n) => Variable::Number(*n),
|
||||
RcValue::String(s) => Variable::String(Rc::from(s.as_ref())),
|
||||
RcValue::String(s) => Variable::String((s.as_ref()).into()),
|
||||
RcValue::Array(arr) => {
|
||||
Variable::from_array(arr.iter().map(|v| v.to_variable()).collect())
|
||||
}
|
||||
RcValue::Object(obj) => Variable::from_object(
|
||||
obj.iter()
|
||||
.map(|(k, v)| (Rc::from(k.as_ref()), v.to_variable()))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.as_ref()), v.to_variable()))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
@@ -31,7 +31,7 @@ impl From<&Variable> for RcValue {
|
||||
Variable::Null => RcValue::Null,
|
||||
Variable::Bool(b) => RcValue::Bool(*b),
|
||||
Variable::Number(n) => RcValue::Number(*n),
|
||||
Variable::String(s) => RcValue::String(s.clone()),
|
||||
Variable::String(s) => RcValue::String((s.as_str()).into()),
|
||||
Variable::Array(arr) => {
|
||||
let arr = arr.borrow();
|
||||
RcValue::Array(arr.iter().map(RcValue::from).collect())
|
||||
@@ -40,7 +40,7 @@ impl From<&Variable> for RcValue {
|
||||
let obj = obj.borrow();
|
||||
RcValue::Object(
|
||||
obj.iter()
|
||||
.map(|(k, v)| (k.clone(), RcValue::from(v)))
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), RcValue::from(v)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl From<&Value> for RcValue {
|
||||
);
|
||||
}
|
||||
}
|
||||
Value::String(s) => RcValue::String(Rc::from(s.as_str())),
|
||||
Value::String(s) => RcValue::String((s.as_str()).into()),
|
||||
Value::Array(arr) => RcValue::Array(arr.iter().map(RcValue::from).collect()),
|
||||
Value::Object(obj) => RcValue::Object(
|
||||
obj.iter()
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'de> Visitor<'de> for RcValueVisitor {
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(RcValue::String(Rc::from(v)))
|
||||
Ok(RcValue::String((v).into()))
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub type Symbol = hipstr::LocalHipStr<'static>;
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::rccell::RcCell;
|
||||
use crate::variable::Variable;
|
||||
use rust_decimal::Decimal;
|
||||
#[cfg(not(feature = "arbitrary_precision"))]
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use serde_json::{Number, Value};
|
||||
use std::rc::Rc;
|
||||
#[cfg(not(feature = "arbitrary_precision"))]
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -41,13 +41,13 @@ impl From<Value> for Variable {
|
||||
)
|
||||
}
|
||||
}
|
||||
Value::String(s) => Variable::String(Rc::from(s.as_str())),
|
||||
Value::String(s) => Variable::String((s.as_str()).into()),
|
||||
Value::Array(arr) => {
|
||||
Variable::from_array(arr.into_iter().map(Variable::from).collect())
|
||||
}
|
||||
Value::Object(obj) => Variable::from_object(
|
||||
obj.into_iter()
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), Variable::from(v)))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.as_str()), Variable::from(v)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
@@ -88,11 +88,11 @@ impl From<&Value> for Variable {
|
||||
);
|
||||
}
|
||||
}
|
||||
Value::String(s) => Variable::String(Rc::from(s.as_str())),
|
||||
Value::String(s) => Variable::String((s.as_str()).into()),
|
||||
Value::Array(arr) => Variable::from_array(arr.iter().map(Variable::from).collect()),
|
||||
Value::Object(obj) => Variable::from_object(
|
||||
obj.iter()
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), Variable::from(v)))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.as_str()), Variable::from(v)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
@@ -119,8 +119,8 @@ impl From<Variable> for Value {
|
||||
}
|
||||
Variable::String(s) => Value::String(s.to_string()),
|
||||
Variable::Array(arr) => {
|
||||
let vec = Rc::try_unwrap(arr)
|
||||
.map(|a| a.into_inner())
|
||||
let vec = RcCell::try_unwrap(arr)
|
||||
.map(|cell| cell.into_inner())
|
||||
.unwrap_or_else(|s| {
|
||||
let borrowed = s.borrow();
|
||||
borrowed.clone()
|
||||
@@ -129,8 +129,8 @@ impl From<Variable> for Value {
|
||||
Value::Array(vec.into_iter().map(Value::from).collect())
|
||||
}
|
||||
Variable::Object(obj) => {
|
||||
let hmap = Rc::try_unwrap(obj)
|
||||
.map(|a| a.into_inner())
|
||||
let hmap = RcCell::try_unwrap(obj)
|
||||
.map(|cell| cell.into_inner())
|
||||
.unwrap_or_else(|s| {
|
||||
let borrowed = s.borrow();
|
||||
borrowed.clone()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "arbitrary_precision")]
|
||||
use crate::constant::NUMBER_TOKEN;
|
||||
use crate::variable::Variable;
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use crate::variable::VariableMap;
|
||||
use rust_decimal::Decimal;
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use serde::de::{DeserializeSeed, Error, MapAccess, SeqAccess, Unexpected, Visitor};
|
||||
@@ -59,7 +59,7 @@ impl<'de> Visitor<'de> for VariableVisitor {
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Variable::String(Rc::from(v)))
|
||||
Ok(Variable::String((v).into()))
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
@@ -85,7 +85,7 @@ impl<'de> Visitor<'de> for VariableVisitor {
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut m = HashMap::with_capacity(map.size_hint().unwrap_or_default());
|
||||
let mut m = VariableMap::with_capacity(map.size_hint().unwrap_or_default());
|
||||
#[cfg(feature = "arbitrary_precision")]
|
||||
let mut first = true;
|
||||
|
||||
@@ -105,7 +105,7 @@ impl<'de> Visitor<'de> for VariableVisitor {
|
||||
));
|
||||
}
|
||||
|
||||
m.insert(key, value);
|
||||
m.insert(crate::symbol::Symbol::from(key.as_ref()), value);
|
||||
#[cfg(feature = "arbitrary_precision")]
|
||||
{
|
||||
first = false;
|
||||
|
||||
@@ -13,13 +13,13 @@ pub trait ToVariable {
|
||||
|
||||
impl ToVariable for String {
|
||||
fn to_variable(&self) -> Variable {
|
||||
Variable::String(Rc::from(self.as_str()))
|
||||
Variable::String((self.as_str()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToVariable for str {
|
||||
fn to_variable(&self) -> Variable {
|
||||
Variable::String(Rc::from(self))
|
||||
Variable::String((self).into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ where
|
||||
fn to_variable(&self) -> Variable {
|
||||
Variable::from_object(
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.clone(), v.to_variable()))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.as_ref()), v.to_variable()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -106,7 +106,7 @@ where
|
||||
fn to_variable(&self) -> Variable {
|
||||
Variable::from_object(
|
||||
self.iter()
|
||||
.map(|(k, v)| (Rc::<str>::from(k.deref()), v.to_variable()))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.deref()), v.to_variable()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ where
|
||||
fn to_variable(&self) -> Variable {
|
||||
Variable::from_object(
|
||||
self.iter()
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), v.to_variable()))
|
||||
.map(|(k, v)| (crate::symbol::Symbol::from(k.as_str()), v.to_variable()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
use crate::symbol::Symbol;
|
||||
use crate::variable::Variable;
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
const INLINE: usize = 8;
|
||||
|
||||
const SPILL_AT: usize = 32;
|
||||
|
||||
type Entries = SmallVec<[(Symbol, Variable); INLINE]>;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Repr {
|
||||
Small(Entries),
|
||||
Large(HashMap<Symbol, Variable>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableMap(Repr);
|
||||
|
||||
impl VariableMap {
|
||||
pub fn new() -> Self {
|
||||
Self(Repr::Small(SmallVec::new()))
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
match capacity > SPILL_AT {
|
||||
true => Self(Repr::Large(HashMap::with_capacity(capacity))),
|
||||
false => Self(Repr::Small(SmallVec::with_capacity(capacity))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.0 {
|
||||
Repr::Small(entries) => entries.len(),
|
||||
Repr::Large(map) => map.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => entries.clear(),
|
||||
Repr::Large(map) => map.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, key: &Symbol) -> Option<&Variable> {
|
||||
match &self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_str() == key.as_str())
|
||||
.map(|(_, v)| v),
|
||||
Repr::Large(map) => map.get(key),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_str(&self, key: &str) -> Option<&Variable> {
|
||||
match &self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_str() == key)
|
||||
.map(|(_, v)| v),
|
||||
Repr::Large(map) => map.get(key),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: &Symbol) -> Option<&mut Variable> {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter_mut()
|
||||
.find(|(k, _)| k.as_str() == key.as_str())
|
||||
.map(|(_, v)| v),
|
||||
Repr::Large(map) => map.get_mut(key),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key_value(&self, key: &Symbol) -> Option<(&Symbol, &Variable)> {
|
||||
match &self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter()
|
||||
.find(|(k, _)| k.as_str() == key.as_str())
|
||||
.map(|(k, v)| (k, v)),
|
||||
Repr::Large(map) => map.get_key_value(key),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &Symbol) -> bool {
|
||||
self.get(key).is_some()
|
||||
}
|
||||
|
||||
pub fn contains_key_str(&self, key: &str) -> bool {
|
||||
self.get_str(key).is_some()
|
||||
}
|
||||
|
||||
pub fn remove_str(&mut self, key: &str) -> Option<Variable> {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter()
|
||||
.position(|(k, _)| k.as_str() == key)
|
||||
.map(|index| entries.remove(index).1),
|
||||
Repr::Large(map) => map.remove(key),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: Symbol, value: Variable) -> Option<Variable> {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => {
|
||||
if let Some(slot) = entries.iter_mut().find(|(k, _)| k.as_str() == key.as_str()) {
|
||||
return Some(std::mem::replace(&mut slot.1, value));
|
||||
}
|
||||
if entries.len() >= SPILL_AT {
|
||||
self.spill();
|
||||
let Repr::Large(map) = &mut self.0 else {
|
||||
unreachable!("just spilled")
|
||||
};
|
||||
return map.insert(key, value);
|
||||
}
|
||||
entries.push((key, value));
|
||||
None
|
||||
}
|
||||
Repr::Large(map) => map.insert(key, value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Symbol) -> Option<Variable> {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => entries
|
||||
.iter()
|
||||
.position(|(k, _)| k.as_str() == key.as_str())
|
||||
.map(|index| entries.remove(index).1),
|
||||
Repr::Large(map) => map.remove(key),
|
||||
}
|
||||
}
|
||||
|
||||
fn spill(&mut self) {
|
||||
let Repr::Small(entries) = &mut self.0 else {
|
||||
return;
|
||||
};
|
||||
let mut map = HashMap::with_capacity(entries.len() * 2);
|
||||
for (key, value) in entries.drain(..) {
|
||||
map.insert(key, value);
|
||||
}
|
||||
self.0 = Repr::Large(map);
|
||||
}
|
||||
|
||||
pub fn entry(&mut self, key: Symbol) -> Entry<'_> {
|
||||
if matches!(&self.0, Repr::Small(entries)
|
||||
if entries.len() >= SPILL_AT && self.get(&key).is_none())
|
||||
{
|
||||
self.spill();
|
||||
}
|
||||
|
||||
match self.contains_key(&key) {
|
||||
true => Entry::Occupied(OccupiedEntry { map: self, key }),
|
||||
false => Entry::Vacant(VacantEntry { map: self, key }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
match &self.0 {
|
||||
Repr::Small(entries) => Iter::Small(entries.iter()),
|
||||
Repr::Large(map) => Iter::Large(map.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_> {
|
||||
match &mut self.0 {
|
||||
Repr::Small(entries) => IterMut::Small(entries.iter_mut()),
|
||||
Repr::Large(map) => IterMut::Large(map.iter_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &Symbol> + '_ {
|
||||
self.iter().map(|(key, _)| key)
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &Variable> {
|
||||
self.iter().map(|(_, value)| value)
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Variable> {
|
||||
self.iter_mut().map(|(_, value)| value)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Entry<'a> {
|
||||
Occupied(OccupiedEntry<'a>),
|
||||
Vacant(VacantEntry<'a>),
|
||||
}
|
||||
|
||||
pub struct OccupiedEntry<'a> {
|
||||
map: &'a mut VariableMap,
|
||||
key: Symbol,
|
||||
}
|
||||
|
||||
pub struct VacantEntry<'a> {
|
||||
map: &'a mut VariableMap,
|
||||
key: Symbol,
|
||||
}
|
||||
|
||||
impl<'a> Entry<'a> {
|
||||
pub fn or_insert(self, default: Variable) -> &'a mut Variable {
|
||||
match self {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => entry.insert(default),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or_insert_with<F: FnOnce() -> Variable>(self, default: F) -> &'a mut Variable {
|
||||
match self {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => entry.insert(default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OccupiedEntry<'a> {
|
||||
pub fn get(&self) -> &Variable {
|
||||
self.map.get(&self.key).expect("occupied")
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut Variable {
|
||||
self.map.get_mut(&self.key).expect("occupied")
|
||||
}
|
||||
|
||||
pub fn into_mut(self) -> &'a mut Variable {
|
||||
let key = self.key;
|
||||
self.map.get_mut(&key).expect("occupied")
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: Variable) -> Variable {
|
||||
std::mem::replace(self.get_mut(), value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VacantEntry<'a> {
|
||||
pub fn insert(self, value: Variable) -> &'a mut Variable {
|
||||
let key = self.key;
|
||||
self.map.insert(key.clone(), value);
|
||||
self.map.get_mut(&key).expect("just inserted")
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Iter<'a> {
|
||||
Small(std::slice::Iter<'a, (Symbol, Variable)>),
|
||||
Large(std::collections::hash_map::Iter<'a, Symbol, Variable>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a Symbol, &'a Variable);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Iter::Small(iter) => iter.next().map(|(key, value)| (key, value)),
|
||||
Iter::Large(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Iter::Small(iter) => iter.size_hint(),
|
||||
Iter::Large(iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum IterMut<'a> {
|
||||
Small(std::slice::IterMut<'a, (Symbol, Variable)>),
|
||||
Large(std::collections::hash_map::IterMut<'a, Symbol, Variable>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IterMut<'a> {
|
||||
type Item = (&'a Symbol, &'a mut Variable);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
IterMut::Small(iter) => iter.next().map(|(key, value)| (&*key, value)),
|
||||
IterMut::Large(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum IntoIter {
|
||||
Small(smallvec::IntoIter<[(Symbol, Variable); INLINE]>),
|
||||
Large(std::collections::hash_map::IntoIter<Symbol, Variable>),
|
||||
}
|
||||
|
||||
impl Iterator for IntoIter {
|
||||
type Item = (Symbol, Variable);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
IntoIter::Small(iter) => iter.next(),
|
||||
IntoIter::Large(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for VariableMap {
|
||||
type Item = (Symbol, Variable);
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
fn into_iter(self) -> IntoIter {
|
||||
match self.0 {
|
||||
Repr::Small(entries) => IntoIter::Small(entries.into_iter()),
|
||||
Repr::Large(map) => IntoIter::Large(map.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a VariableMap {
|
||||
type Item = (&'a Symbol, &'a Variable);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Iter<'a> {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VariableMap {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Symbol, Variable)> for VariableMap {
|
||||
fn from_iter<T: IntoIterator<Item = (Symbol, Variable)>>(iter: T) -> Self {
|
||||
let iter = iter.into_iter();
|
||||
let mut map = VariableMap::with_capacity(iter.size_hint().0);
|
||||
for (key, value) in iter {
|
||||
map.insert(key, value);
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(Symbol, Variable)> for VariableMap {
|
||||
fn extend<T: IntoIterator<Item = (Symbol, Variable)>>(&mut self, iter: T) {
|
||||
for (key, value) in iter {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for VariableMap {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.len() == other.len()
|
||||
&& self
|
||||
.iter()
|
||||
.all(|(key, value)| other.get(key).is_some_and(|o| o == value))
|
||||
}
|
||||
}
|
||||
|
||||
impl VariableMap {
|
||||
pub fn insert_str(&mut self, key: &str, value: Variable) -> Option<Variable> {
|
||||
self.insert(Symbol::from(key), value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VariableMap {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_map()
|
||||
.entries(self.iter().map(|(k, v)| (k.as_str(), v)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for VariableMap {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||
for (key, value) in self.iter() {
|
||||
map.serialize_entry(key.as_str(), value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::symbol::Symbol;
|
||||
use crate::variable::map::Entry;
|
||||
use crate::variable::ref_ser::RefSerializer;
|
||||
use ahash::HashMap;
|
||||
use rust_decimal::Decimal;
|
||||
use rust_decimal::prelude::Zero;
|
||||
use serde_json::Value;
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
@@ -19,24 +19,25 @@ pub use impls::ToVariable;
|
||||
mod conv;
|
||||
mod de;
|
||||
mod impls;
|
||||
mod map;
|
||||
mod ref_deser;
|
||||
mod ref_ser;
|
||||
mod ser;
|
||||
|
||||
pub(crate) type RcCell<T> = Rc<RefCell<T>>;
|
||||
pub use crate::rccell::RcCell;
|
||||
|
||||
pub type VariableMap = HashMap<Rc<str>, Variable>;
|
||||
pub use crate::variable::map::{Iter as MapIter, VariableMap};
|
||||
|
||||
thread_local! {
|
||||
static DOLLAR_KEY: Rc<str> = Rc::from("$");
|
||||
static ROOT_KEY: Rc<str> = Rc::from("$root");
|
||||
static DOLLAR_KEY_RC: Rc<str> = Rc::from("$");
|
||||
static ROOT_KEY_RC: Rc<str> = Rc::from("$root");
|
||||
}
|
||||
|
||||
pub enum Variable {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(Decimal),
|
||||
String(Rc<str>),
|
||||
String(Symbol),
|
||||
Array(RcCell<Vec<Variable>>),
|
||||
Object(RcCell<VariableMap>),
|
||||
Dynamic(Rc<dyn DynamicVariable>),
|
||||
@@ -51,12 +52,28 @@ pub trait DynamicVariable: Display {
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
pub fn dollar_key() -> Rc<str> {
|
||||
DOLLAR_KEY.with(Rc::clone)
|
||||
pub fn dollar_key() -> Symbol {
|
||||
Symbol::from_static("$")
|
||||
}
|
||||
|
||||
pub fn root_key() -> Rc<str> {
|
||||
ROOT_KEY.with(Rc::clone)
|
||||
pub fn root_key() -> Symbol {
|
||||
Symbol::from_static("$root")
|
||||
}
|
||||
|
||||
pub fn nodes_key() -> Symbol {
|
||||
Symbol::from_static("$nodes")
|
||||
}
|
||||
|
||||
pub fn key(name: &str) -> Symbol {
|
||||
Symbol::from(name)
|
||||
}
|
||||
|
||||
pub fn dollar_key_rc() -> Rc<str> {
|
||||
DOLLAR_KEY_RC.with(Rc::clone)
|
||||
}
|
||||
|
||||
pub fn root_key_rc() -> Rc<str> {
|
||||
ROOT_KEY_RC.with(Rc::clone)
|
||||
}
|
||||
|
||||
pub fn from_array(arr: Vec<Self>) -> Self {
|
||||
@@ -71,7 +88,7 @@ impl Variable {
|
||||
RefDeserializer::new().deserialize(serialized)
|
||||
}
|
||||
|
||||
pub fn from_object(obj: HashMap<Rc<str>, Self>) -> Self {
|
||||
pub fn from_object(obj: VariableMap) -> Self {
|
||||
Self::Object(Rc::new(RefCell::new(obj)))
|
||||
}
|
||||
|
||||
@@ -92,7 +109,14 @@ impl Variable {
|
||||
|
||||
pub fn as_rc_str(&self) -> Option<Rc<str>> {
|
||||
match self {
|
||||
Variable::String(s) => Some(s.clone()),
|
||||
Variable::String(s) => Some(Rc::from(s.as_str())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_sym(&self) -> Option<&Symbol> {
|
||||
match self {
|
||||
Variable::String(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -111,7 +135,7 @@ impl Variable {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Option<RcCell<HashMap<Rc<str>, Variable>>> {
|
||||
pub fn as_object(&self) -> Option<RcCell<VariableMap>> {
|
||||
match self {
|
||||
Variable::Object(obj) => Some(obj.clone()),
|
||||
_ => None,
|
||||
@@ -167,7 +191,7 @@ impl Variable {
|
||||
.try_fold(self.shallow_clone(), |var, part| match var {
|
||||
Variable::Object(obj) => {
|
||||
let reference = obj.borrow();
|
||||
reference.get(part).map(|v| v.shallow_clone())
|
||||
reference.get_str(part).map(|v| v.shallow_clone())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
@@ -183,7 +207,7 @@ impl Variable {
|
||||
.try_fold(cloned_self.shallow_clone(), |var, part| match var {
|
||||
Variable::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(*part)) {
|
||||
Some(match obj_ref.entry(Symbol::from(*part)) {
|
||||
Entry::Occupied(mut occ) => {
|
||||
let var = occ.get();
|
||||
let new_obj = match var {
|
||||
@@ -209,7 +233,7 @@ impl Variable {
|
||||
let head = parts.try_fold(self.shallow_clone(), |var, part| match var {
|
||||
Variable::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(part)) {
|
||||
Some(match obj_ref.entry(Symbol::from(part)) {
|
||||
Entry::Occupied(occ) => occ.get().shallow_clone(),
|
||||
Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(),
|
||||
})
|
||||
@@ -221,7 +245,7 @@ impl Variable {
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.remove(last_part)
|
||||
object.remove_str(last_part)
|
||||
}
|
||||
|
||||
pub fn dot_insert(&self, key: &str, variable: Variable) -> Option<Variable> {
|
||||
@@ -230,7 +254,7 @@ impl Variable {
|
||||
let head = parts.try_fold(self.shallow_clone(), |var, part| match var {
|
||||
Variable::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(part)) {
|
||||
Some(match obj_ref.entry(Symbol::from(part)) {
|
||||
Entry::Occupied(occ) => occ.get().shallow_clone(),
|
||||
Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(),
|
||||
})
|
||||
@@ -242,7 +266,7 @@ impl Variable {
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.insert(Rc::from(last_part), variable)
|
||||
object.insert(Symbol::from(last_part), variable)
|
||||
}
|
||||
|
||||
pub fn dot_insert_detached(&self, key: &str, variable: Variable) -> Option<Variable> {
|
||||
@@ -254,7 +278,7 @@ impl Variable {
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.insert(Rc::from(last_part), variable);
|
||||
object.insert(Symbol::from(last_part), variable);
|
||||
Some(new_var)
|
||||
}
|
||||
|
||||
@@ -353,7 +377,7 @@ fn merge_variables(
|
||||
if doc.is_object() && patch.is_object() {
|
||||
let doc_ref = doc.as_object().unwrap();
|
||||
let patch_ref = patch.as_object().unwrap();
|
||||
if Rc::ptr_eq(&doc_ref, &patch_ref) {
|
||||
if RcCell::ptr_eq(&doc_ref, &patch_ref) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -374,7 +398,7 @@ fn merge_variables(
|
||||
}
|
||||
MergeStrategy::CloneOnWrite => {
|
||||
let mut changed = false;
|
||||
let mut new_map = None;
|
||||
let mut new_map: Option<VariableMap> = None;
|
||||
|
||||
for (key, value) in patch.deref() {
|
||||
// Get or create the new map if we haven't yet
|
||||
@@ -474,7 +498,7 @@ impl PartialEq for Variable {
|
||||
(Variable::Null, Variable::Null) => true,
|
||||
(Variable::Bool(b1), Variable::Bool(b2)) => b1 == b2,
|
||||
(Variable::Number(n1), Variable::Number(n2)) => n1 == n2,
|
||||
(Variable::String(s1), Variable::String(s2)) => s1 == s2,
|
||||
(Variable::String(s1), Variable::String(s2)) => s1.as_str() == s2.as_str(),
|
||||
(Variable::Array(a1), Variable::Array(a2)) => a1 == a2,
|
||||
(Variable::Object(obj1), Variable::Object(obj2)) => obj1 == obj2,
|
||||
(Variable::Dynamic(d1), Variable::Dynamic(d2)) => Rc::ptr_eq(d1, d2),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::rcvalue::RcValue;
|
||||
use crate::variable::Variable;
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use std::cell::RefCell;
|
||||
use crate::variable::VariableMap;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -27,11 +26,10 @@ impl RefDeserializer {
|
||||
for (i, _) in refs_array.iter().enumerate() {
|
||||
match &refs_array[i] {
|
||||
RcValue::Array(_) => {
|
||||
self.refs[i] = Some(Variable::Array(Rc::new(RefCell::new(Vec::new()))));
|
||||
self.refs[i] = Some(Variable::from_array(Vec::new()));
|
||||
}
|
||||
RcValue::Object(_) => {
|
||||
self.refs[i] =
|
||||
Some(Variable::Object(Rc::new(RefCell::new(HashMap::default()))));
|
||||
self.refs[i] = Some(Variable::from_object(VariableMap::default()));
|
||||
}
|
||||
_ => {
|
||||
self.refs[i] = Some(self.deserialize_value(&refs_array[i])?);
|
||||
@@ -52,7 +50,7 @@ impl RefDeserializer {
|
||||
}
|
||||
RcValue::Object(obj) => {
|
||||
if let Some(Variable::Object(target)) = &self.refs[i] {
|
||||
let mut map = HashMap::with_capacity(obj.len());
|
||||
let mut map = VariableMap::with_capacity(obj.len());
|
||||
for (key, value) in obj {
|
||||
let key_var = self.deserialize_key(key)?;
|
||||
let value_var = self.deserialize_value(value)?;
|
||||
@@ -67,13 +65,13 @@ impl RefDeserializer {
|
||||
}
|
||||
|
||||
let root_value = root_obj
|
||||
.remove(&Variable::root_key())
|
||||
.remove("$root")
|
||||
.ok_or_else(|| RefDeserializeError::InvalidFormat("Missing $root".into()))?;
|
||||
|
||||
self.deserialize_value(&root_value)
|
||||
}
|
||||
|
||||
fn deserialize_key(&self, key: &Rc<str>) -> Result<Rc<str>, RefDeserializeError> {
|
||||
fn deserialize_key(&self, key: &Rc<str>) -> Result<crate::symbol::Symbol, RefDeserializeError> {
|
||||
if let Some(ref_id) = parse_ref_id(key) {
|
||||
if ref_id >= self.refs.len() {
|
||||
return Err(RefDeserializeError::InvalidReference(ref_id));
|
||||
@@ -114,16 +112,16 @@ impl RefDeserializer {
|
||||
for item in arr {
|
||||
items.push(self.deserialize_value(item)?);
|
||||
}
|
||||
Ok(Variable::Array(Rc::new(RefCell::new(items))))
|
||||
Ok(Variable::from_array(items))
|
||||
}
|
||||
RcValue::Object(obj) => {
|
||||
let mut map = HashMap::with_capacity(obj.len());
|
||||
let mut map = VariableMap::with_capacity(obj.len());
|
||||
for (key, value) in obj {
|
||||
let key_var = self.deserialize_key(key)?;
|
||||
let value_var = self.deserialize_value(value)?;
|
||||
map.insert(key_var, value_var);
|
||||
}
|
||||
Ok(Variable::Object(Rc::new(RefCell::new(map))))
|
||||
Ok(Variable::from_object(map))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,11 +137,10 @@ pub enum RefDeserializeError {
|
||||
UnresolvedReference(usize),
|
||||
}
|
||||
|
||||
fn unescape_at_string(s: &Rc<str>) -> Rc<str> {
|
||||
if s.starts_with("@@") {
|
||||
Rc::from(&s[1..])
|
||||
} else {
|
||||
s.clone()
|
||||
fn unescape_at_string(s: &Rc<str>) -> crate::symbol::Symbol {
|
||||
match s.starts_with("@@") {
|
||||
true => crate::symbol::Symbol::from(&s[1..]),
|
||||
false => crate::symbol::Symbol::from(s.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::rccell::RcCell;
|
||||
use crate::rcvalue::RcValue;
|
||||
use crate::variable::Variable;
|
||||
use ahash::AHashMap;
|
||||
@@ -26,25 +27,20 @@ impl RefSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_at_string(s: &Rc<str>) -> Rc<str> {
|
||||
if s.starts_with('@') {
|
||||
let string = format!("@{s}");
|
||||
Rc::from(string.as_str())
|
||||
} else {
|
||||
s.clone()
|
||||
fn escape_at_string(s: &crate::symbol::Symbol) -> Rc<str> {
|
||||
match s.starts_with('@') {
|
||||
true => Rc::from(format!("@{s}").as_str()),
|
||||
false => Rc::from(s.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
fn intern_string_addr(&mut self, s: &Rc<str>) -> usize {
|
||||
let reference = match self.string_intern.get(s) {
|
||||
Some(interned) => interned,
|
||||
None => {
|
||||
self.string_intern.insert(s.clone(), s.clone());
|
||||
s
|
||||
}
|
||||
};
|
||||
fn intern_string_addr(&mut self, s: &str) -> usize {
|
||||
if !self.string_intern.contains_key(s) {
|
||||
let owned: Rc<str> = Rc::from(s);
|
||||
self.string_intern.insert(owned.clone(), owned);
|
||||
}
|
||||
|
||||
Rc::as_ptr(&reference) as *const () as usize
|
||||
Rc::as_ptr(self.string_intern.get(s).expect("interned")) as *const () as usize
|
||||
}
|
||||
|
||||
pub fn serialize(mut self, var: &Variable) -> RcValue {
|
||||
@@ -58,7 +54,7 @@ impl RefSerializer {
|
||||
result.insert(Rc::from("$refs"), RcValue::Array(self.ref_data));
|
||||
}
|
||||
|
||||
result.insert(Variable::root_key(), data);
|
||||
result.insert(Variable::root_key_rc(), data);
|
||||
RcValue::Object(result)
|
||||
}
|
||||
|
||||
@@ -69,11 +65,11 @@ impl RefSerializer {
|
||||
return;
|
||||
}
|
||||
|
||||
let addr = self.intern_string_addr(s);
|
||||
let addr = self.intern_string_addr(s.as_str());
|
||||
*self.ref_counts.entry(addr).or_insert(0) += 1;
|
||||
}
|
||||
Variable::Array(arr) => {
|
||||
let addr = Rc::as_ptr(arr) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(arr) as *const () as usize;
|
||||
*self.ref_counts.entry(addr).or_insert(0) += 1;
|
||||
|
||||
let borrowed = arr.borrow();
|
||||
@@ -82,12 +78,12 @@ impl RefSerializer {
|
||||
}
|
||||
}
|
||||
Variable::Object(obj) => {
|
||||
let addr = Rc::as_ptr(obj) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(obj) as *const () as usize;
|
||||
*self.ref_counts.entry(addr).or_insert(0) += 1;
|
||||
|
||||
let borrowed = obj.borrow();
|
||||
for (key, value) in borrowed.iter() {
|
||||
let key_addr = self.intern_string_addr(key);
|
||||
let key_addr = self.intern_string_addr(key.as_str());
|
||||
*self.ref_counts.entry(key_addr).or_insert(0) += 1;
|
||||
self.count_refs(value);
|
||||
}
|
||||
@@ -121,7 +117,7 @@ impl RefSerializer {
|
||||
fn serialize_with_refs(&mut self, var: &Variable) -> RcValue {
|
||||
match var {
|
||||
Variable::String(s) => {
|
||||
let addr = self.intern_string_addr(s);
|
||||
let addr = self.intern_string_addr(s.as_str());
|
||||
let Some((id, id_str)) = self.refs.get(&addr) else {
|
||||
return RcValue::String(Self::escape_at_string(s));
|
||||
};
|
||||
@@ -134,7 +130,7 @@ impl RefSerializer {
|
||||
}
|
||||
|
||||
Variable::Array(arr) => {
|
||||
let addr = Rc::as_ptr(arr) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(arr) as *const () as usize;
|
||||
let data = {
|
||||
let borrowed = arr.borrow();
|
||||
let items: Vec<_> = borrowed
|
||||
@@ -157,7 +153,7 @@ impl RefSerializer {
|
||||
}
|
||||
|
||||
Variable::Object(obj) => {
|
||||
let addr = Rc::as_ptr(obj) as *const () as usize;
|
||||
let addr = RcCell::as_ptr(obj) as *const () as usize;
|
||||
let data = {
|
||||
let borrowed = obj.borrow();
|
||||
let mut map = HashMap::with_capacity_and_hasher(
|
||||
@@ -166,7 +162,7 @@ impl RefSerializer {
|
||||
);
|
||||
|
||||
for (key, value) in borrowed.iter() {
|
||||
let key_addr = self.intern_string_addr(key);
|
||||
let key_addr = self.intern_string_addr(key.as_str());
|
||||
let key_str = if let Some((key_id, key_id_str)) = self.refs.get(&key_addr) {
|
||||
if self.ref_data[*key_id] == RcValue::Null {
|
||||
self.ref_data[*key_id] =
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
mod conv;
|
||||
mod util;
|
||||
|
||||
use crate::variable::RcCell;
|
||||
use ahash::HashMap;
|
||||
pub use ahash::HashMapExt as VariableMapExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
|
||||
type RcCell<T> = Rc<RefCell<T>>;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum VariableType {
|
||||
Any,
|
||||
|
||||
+42
-23
@@ -1,4 +1,3 @@
|
||||
use ahash::{HashMap, HashMapExt};
|
||||
use rust_decimal_macros::dec;
|
||||
use serde_json::json;
|
||||
use std::cell::RefCell;
|
||||
@@ -6,6 +5,7 @@ use std::error::Error;
|
||||
use std::rc::Rc;
|
||||
use zen_types::rcvalue::RcValue;
|
||||
use zen_types::variable::Variable;
|
||||
use zen_types::variable::VariableMap as HashMap;
|
||||
|
||||
type TestResult = Result<(), Box<dyn Error>>;
|
||||
|
||||
@@ -46,8 +46,8 @@ fn serialize_deserialize_with_refs() -> TestResult {
|
||||
|
||||
// Check that refs were created
|
||||
if let RcValue::Object(ref obj) = serialized {
|
||||
assert!(obj.contains_key(&Rc::from("$refs")));
|
||||
assert!(obj.contains_key(&Rc::from("$root")));
|
||||
assert!(obj.contains_key("$refs"));
|
||||
assert!(obj.contains_key("$root"));
|
||||
} else {
|
||||
panic!("Expected object");
|
||||
}
|
||||
@@ -133,7 +133,7 @@ fn no_refs_when_below_threshold() -> TestResult {
|
||||
|
||||
// Should not have refs section
|
||||
if let RcValue::Object(ref obj) = serialized {
|
||||
assert!(!obj.contains_key(&Rc::from("$refs")));
|
||||
assert!(!obj.contains_key("$refs"));
|
||||
}
|
||||
|
||||
let deserialized = Variable::deserialize_ref(serialized)?;
|
||||
@@ -148,18 +148,30 @@ fn serialize_circular_references() -> TestResult {
|
||||
let shared_obj = Rc::new(RefCell::new({
|
||||
let mut map = HashMap::new();
|
||||
map.insert(
|
||||
Rc::from("shared_data"),
|
||||
Variable::String(Rc::from("important_value")),
|
||||
zen_types::symbol::Symbol::from("shared_data"),
|
||||
Variable::String(("important_value").into()),
|
||||
);
|
||||
map.insert(
|
||||
zen_types::symbol::Symbol::from("id"),
|
||||
Variable::Number(dec!(42.0)),
|
||||
);
|
||||
map.insert(Rc::from("id"), Variable::Number(dec!(42.0)));
|
||||
map
|
||||
}));
|
||||
|
||||
// Create a structure where the same object appears in multiple places
|
||||
let mut root_map = HashMap::new();
|
||||
root_map.insert(Rc::from("first_ref"), Variable::Object(shared_obj.clone()));
|
||||
root_map.insert(Rc::from("second_ref"), Variable::Object(shared_obj.clone()));
|
||||
root_map.insert(Rc::from("third_ref"), Variable::Object(shared_obj));
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("first_ref"),
|
||||
Variable::Object(shared_obj.clone()),
|
||||
);
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("second_ref"),
|
||||
Variable::Object(shared_obj.clone()),
|
||||
);
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("third_ref"),
|
||||
Variable::Object(shared_obj),
|
||||
);
|
||||
|
||||
let var = Variable::Object(Rc::new(RefCell::new(root_map)));
|
||||
|
||||
@@ -173,12 +185,10 @@ fn serialize_circular_references() -> TestResult {
|
||||
|
||||
#[test]
|
||||
fn serialize_same_array_multiple_locations() -> TestResult {
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Create a shared array
|
||||
let shared_array = Rc::new(RefCell::new(vec![
|
||||
Variable::String(Rc::from("item_one")),
|
||||
Variable::String(Rc::from("item_two")),
|
||||
Variable::String(("item_one").into()),
|
||||
Variable::String(("item_two").into()),
|
||||
Variable::Number(dec!(123.0)),
|
||||
]));
|
||||
|
||||
@@ -205,12 +215,12 @@ fn serialize_mixed_shared_references() -> TestResult {
|
||||
// Create an object where the same string is used as both key and value
|
||||
let mut obj_map = HashMap::new();
|
||||
obj_map.insert(
|
||||
shared_string.clone(),
|
||||
Variable::String(shared_string.clone()),
|
||||
zen_types::symbol::Symbol::from(shared_string.as_ref()),
|
||||
Variable::String(shared_string.as_ref().into()),
|
||||
);
|
||||
obj_map.insert(
|
||||
Rc::from("other_key"),
|
||||
Variable::String(shared_string.clone()),
|
||||
zen_types::symbol::Symbol::from("other_key"),
|
||||
Variable::String(shared_string.as_ref().into()),
|
||||
);
|
||||
|
||||
let shared_obj = Rc::new(RefCell::new(obj_map));
|
||||
@@ -238,16 +248,25 @@ fn serialize_shared_array_with_shared_strings() -> TestResult {
|
||||
|
||||
// Create a shared array containing the shared string
|
||||
let shared_array = Rc::new(RefCell::new(vec![
|
||||
Variable::String(shared_string.clone()),
|
||||
Variable::String(shared_string.as_ref().into()),
|
||||
Variable::Number(dec!(42.0)),
|
||||
Variable::String(shared_string.clone()),
|
||||
Variable::String(shared_string.as_ref().into()),
|
||||
]));
|
||||
|
||||
// Use the shared array in multiple places
|
||||
let mut root_map = HashMap::new();
|
||||
root_map.insert(Rc::from("array1"), Variable::Array(shared_array.clone()));
|
||||
root_map.insert(Rc::from("array2"), Variable::Array(shared_array.clone()));
|
||||
root_map.insert(Rc::from("array3"), Variable::Array(shared_array));
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("array1"),
|
||||
Variable::Array(shared_array.clone()),
|
||||
);
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("array2"),
|
||||
Variable::Array(shared_array.clone()),
|
||||
);
|
||||
root_map.insert(
|
||||
zen_types::symbol::Symbol::from("array3"),
|
||||
Variable::Array(shared_array),
|
||||
);
|
||||
|
||||
let var = Variable::Object(Rc::new(RefCell::new(root_map)));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use rust_decimal_macros::dec;
|
||||
use serde_json::json;
|
||||
use std::error::Error;
|
||||
use std::rc::Rc;
|
||||
use zen_types::rccell::RcCell;
|
||||
use zen_types::variable::Variable;
|
||||
|
||||
type TestResult = Result<(), Box<dyn Error>>;
|
||||
@@ -19,7 +19,7 @@ fn dot_operations() -> TestResult {
|
||||
// Test dot get
|
||||
assert_eq!(
|
||||
var.dot("user.profile.name"),
|
||||
Some(Variable::String(Rc::from("Alice")))
|
||||
Some(Variable::String(("Alice").into()))
|
||||
);
|
||||
assert_eq!(var.dot("user.profile.nonexistent"), None);
|
||||
assert_eq!(var.dot("nonexistent.path"), None);
|
||||
@@ -27,21 +27,21 @@ fn dot_operations() -> TestResult {
|
||||
// Test dot insert
|
||||
let updated = var.dot_insert(
|
||||
"user.profile.email",
|
||||
Variable::String(Rc::from("alice@example.com")),
|
||||
Variable::String(("alice@example.com").into()),
|
||||
);
|
||||
assert!(updated.is_none()); // Returns previous value (none)
|
||||
assert_eq!(
|
||||
var.dot("user.profile.email"),
|
||||
Some(Variable::String(Rc::from("alice@example.com")))
|
||||
Some(Variable::String(("alice@example.com").into()))
|
||||
);
|
||||
|
||||
// Test dot insert detached
|
||||
let new_var = var
|
||||
.dot_insert_detached("settings.theme", Variable::String(Rc::from("dark")))
|
||||
.dot_insert_detached("settings.theme", Variable::String(("dark").into()))
|
||||
.ok_or_else(|| "Failed to insert detached path".to_string())?;
|
||||
assert_eq!(
|
||||
new_var.dot("settings.theme"),
|
||||
Some(Variable::String(Rc::from("dark")))
|
||||
Some(Variable::String(("dark").into()))
|
||||
);
|
||||
assert_eq!(var.dot("settings.theme"), None); // Original unchanged
|
||||
|
||||
@@ -63,33 +63,33 @@ fn clone_operations() -> TestResult {
|
||||
// Test shallow clone - shares references
|
||||
let shallow = original.shallow_clone();
|
||||
if let (Variable::Array(orig_arr), Variable::Array(shallow_arr)) = (&original, &shallow) {
|
||||
assert!(Rc::ptr_eq(orig_arr, shallow_arr));
|
||||
assert!(RcCell::ptr_eq(orig_arr, shallow_arr));
|
||||
}
|
||||
|
||||
// Test depth clone
|
||||
let depth1 = original.depth_clone(1);
|
||||
if let (Variable::Array(orig_arr), Variable::Array(depth_arr)) = (&original, &depth1) {
|
||||
assert!(!Rc::ptr_eq(orig_arr, depth_arr)); // Different array refs
|
||||
assert!(!RcCell::ptr_eq(orig_arr, depth_arr)); // Different array refs
|
||||
|
||||
let orig_nested = &orig_arr.borrow()[2];
|
||||
let depth_nested = &depth_arr.borrow()[2];
|
||||
if let (Variable::Object(orig_obj), Variable::Object(depth_obj)) =
|
||||
(orig_nested, depth_nested)
|
||||
{
|
||||
assert!(Rc::ptr_eq(orig_obj, depth_obj)); // Nested still shared at depth 1
|
||||
assert!(RcCell::ptr_eq(orig_obj, depth_obj)); // Nested still shared at depth 1
|
||||
}
|
||||
}
|
||||
|
||||
// Test deep clone - everything separate
|
||||
let deep = original.deep_clone();
|
||||
if let (Variable::Array(orig_arr), Variable::Array(deep_arr)) = (&original, &deep) {
|
||||
assert!(!Rc::ptr_eq(orig_arr, deep_arr));
|
||||
assert!(!RcCell::ptr_eq(orig_arr, deep_arr));
|
||||
|
||||
let orig_nested = &orig_arr.borrow()[2];
|
||||
let deep_nested = &deep_arr.borrow()[2];
|
||||
if let (Variable::Object(orig_obj), Variable::Object(deep_obj)) = (orig_nested, deep_nested)
|
||||
{
|
||||
assert!(!Rc::ptr_eq(orig_obj, deep_obj)); // Nested also separate
|
||||
assert!(!RcCell::ptr_eq(orig_obj, deep_obj)); // Nested also separate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +115,11 @@ fn merge_operations() -> TestResult {
|
||||
assert_eq!(merged.dot("user.age"), Some(Variable::Number(dec!(31)))); // Merged updated
|
||||
assert_eq!(
|
||||
merged.dot("user.email"),
|
||||
Some(Variable::String(Rc::from("alice@example.com")))
|
||||
Some(Variable::String(("alice@example.com").into()))
|
||||
);
|
||||
assert_eq!(
|
||||
merged.dot("new_field"),
|
||||
Some(Variable::String(Rc::from("value")))
|
||||
Some(Variable::String(("value").into()))
|
||||
);
|
||||
|
||||
// Test in-place merge
|
||||
@@ -127,7 +127,7 @@ fn merge_operations() -> TestResult {
|
||||
assert_eq!(doc.dot("user.age"), Some(Variable::Number(dec!(31)))); // Original now changed
|
||||
assert_eq!(
|
||||
doc.dot("user.name"),
|
||||
Some(Variable::String(Rc::from("Alice")))
|
||||
Some(Variable::String(("Alice").into()))
|
||||
); // Preserved
|
||||
assert_eq!(
|
||||
doc.dot("settings.notifications"),
|
||||
|
||||
Reference in New Issue
Block a user